Merged PR 212: Optimize FindChildrenById from O(n) to O(1)

Optimize FindChildrenById from O(n) to O(1):
- Deprecate FindContainerByIdDFS
- Container: Replace Children to string[]
- Add HashMap to IHistoryState that contains all containers

To access a container by id now cost O(1) without any additional cost

+ Implement CICD for SVGLibs
This commit is contained in:
Eric Nguyen 2022-10-12 09:39:54 +00:00
parent 466ef2b08b
commit c256a76e01
45 changed files with 775 additions and 450 deletions

View file

@ -16,16 +16,28 @@
import { IContainerModel } from '../../../Interfaces/IContainerModel';
import { Orientation } from '../../../Enums/Orientation';
import { ConstraintBodyInsideUnallocatedWidth } from './RigidBodyBehaviors';
import { FindContainerById } from '../../../utils/itertools';
/**
* Impose the container position to its siblings
* Apply the following modification to the overlapping rigid body container :
* @param container Container to impose its position
*/
export function ApplyAnchor(container: IContainerModel, parent: IContainerModel): IContainerModel {
const rigidBodies = parent.children.filter(
child => !child.properties.isAnchor
);
export function ApplyAnchor(containers: Map<string, IContainerModel>, container: IContainerModel, parent: IContainerModel): IContainerModel {
const rigidBodies: IContainerModel[] = [];
parent.children.forEach(
childId => {
const child = FindContainerById(containers, childId);
if (child === undefined) {
return;
}
if (child.properties.isAnchor) {
return;
}
rigidBodies.push(child);
});
const isHorizontal = parent.properties.orientation === Orientation.Horizontal;
const overlappingContainers = isHorizontal
@ -33,7 +45,7 @@ export function ApplyAnchor(container: IContainerModel, parent: IContainerModel)
: GetVerticallyOverlappingContainers(container, rigidBodies);
for (const overlappingContainer of overlappingContainers) {
ConstraintBodyInsideUnallocatedWidth(overlappingContainer);
ConstraintBodyInsideUnallocatedWidth(containers, overlappingContainer);
}
return container;
}

View file

@ -1,6 +1,7 @@
import { IContainerModel } from '../../../Interfaces/IContainerModel';
import { ISymbolModel } from '../../../Interfaces/ISymbolModel';
import { APPLY_BEHAVIORS_ON_CHILDREN, ENABLE_RIGID, ENABLE_SWAP } from '../../../utils/default';
import { FindContainerById, MakeChildrenIterator } from '../../../utils/itertools';
import { ApplyAnchor, GetOverlappingContainers } from './AnchorBehaviors';
import { Flex } from './FlexBehaviors';
import { ApplyRigidBody } from './RigidBodyBehaviors';
@ -13,7 +14,7 @@ import { ApplySymbol } from './SymbolBehaviors';
* @param container Container to recalculate its positions
* @returns Updated container
*/
export function ApplyBehaviors(container: IContainerModel, symbols: Map<string, ISymbolModel>): IContainerModel {
export function ApplyBehaviors(containers: Map<string, IContainerModel>, container: IContainerModel, symbols: Map<string, ISymbolModel>): IContainerModel {
try {
const symbol = symbols.get(container.properties.linkedSymbolId);
if (container.properties.linkedSymbolId !== '' && symbol !== undefined) {
@ -24,24 +25,24 @@ export function ApplyBehaviors(container: IContainerModel, symbols: Map<string,
const parent = container.parent;
if (container.properties.isAnchor) {
ApplyAnchor(container, parent);
ApplyAnchor(containers, container, parent);
}
if (ENABLE_SWAP) {
ApplySwap(container, parent);
ApplySwap(containers, container, parent);
}
Flex(container, parent);
Flex(containers, container, parent);
if (ENABLE_RIGID) {
ApplyRigidBody(container, parent);
ApplyRigidBody(containers, container, parent);
}
}
if (APPLY_BEHAVIORS_ON_CHILDREN) {
// Apply DFS by recursion
for (const child of container.children) {
ApplyBehaviors(child, symbols);
for (const child of MakeChildrenIterator(containers, container.children)) {
ApplyBehaviors(containers, child, symbols);
}
}
} catch (e) {
@ -64,23 +65,32 @@ export function ApplyBehaviors(container: IContainerModel, symbols: Map<string,
* @param symbols
* @returns
*/
export function ApplyBehaviorsOnSiblingsChildren(newContainer: IContainerModel, symbols: Map<string, ISymbolModel>): void {
export function ApplyBehaviorsOnSiblingsChildren(
containers: Map<string, IContainerModel>,
newContainer: IContainerModel,
symbols: Map<string, ISymbolModel>): void {
if (newContainer.parent === null || newContainer.parent === undefined) {
return;
}
newContainer.parent.children
.forEach((container: IContainerModel) => {
if (container.parent != null) {
UpdateWarning(container, container.parent);
.forEach((containerId: string) => {
const container = FindContainerById(containers, containerId);
if (container === undefined) {
return;
}
if (container.parent !== null) {
UpdateWarning(containers, container, container.parent);
}
if (container === newContainer) {
return;
}
for (const child of container.children) {
ApplyBehaviors(child, symbols);
for (const child of MakeChildrenIterator(containers, container.children)) {
ApplyBehaviors(containers, child, symbols);
}
});
}
@ -91,30 +101,47 @@ export function ApplyBehaviorsOnSiblingsChildren(newContainer: IContainerModel,
* @param symbols
* @returns
*/
export function ApplyBehaviorsOnSiblings(newContainer: IContainerModel, symbols: Map<string, ISymbolModel>): void {
export function ApplyBehaviorsOnSiblings(containers: Map<string, IContainerModel>, newContainer: IContainerModel, symbols: Map<string, ISymbolModel>): void {
if (newContainer.parent === null || newContainer.parent === undefined) {
return;
}
newContainer.parent.children
.forEach((container: IContainerModel) => {
ApplyBehaviors(container, symbols);
.forEach((containerId: string) => {
const container = FindContainerById(containers, containerId);
if (container === undefined) {
return;
}
ApplyBehaviors(containers, container, symbols);
if (container.parent != null) {
UpdateWarning(container, container.parent);
UpdateWarning(containers, container, container.parent);
}
if (container === newContainer) {
return;
}
for (const child of container.children) {
ApplyBehaviors(child, symbols);
for (const child of MakeChildrenIterator(containers, container.children)) {
ApplyBehaviors(containers, child, symbols);
}
});
}
function UpdateWarning(container: IContainerModel, parent: IContainerModel): void {
const overlappingContainers = GetOverlappingContainers(container, parent.children);
function UpdateWarning(containers: Map<string, IContainerModel>, container: IContainerModel, parent: IContainerModel): void {
const targetContainers: IContainerModel[] = [];
parent.children.forEach((child) => {
const targetContainer = FindContainerById(containers, child);
if (targetContainer === undefined) {
return;
}
targetContainers.push(targetContainer);
});
const overlappingContainers = GetOverlappingContainers(container, targetContainers);
if (overlappingContainers.length > 0) {
container.properties.warning = `There are overlapping containers: ${overlappingContainers.map(c => c.properties.id).join(' ')}`;
} else {

View file

@ -2,6 +2,7 @@ import { IContainerModel } from '../../../Interfaces/IContainerModel';
import { Orientation } from '../../../Enums/Orientation';
import { Simplex } from '../../../utils/simplex';
import { ApplyWidthMargin, ApplyXMargin } from '../../../utils/svg';
import { MakeChildrenIterator } from '../../../utils/itertools';
interface IFlexibleGroup {
group: IContainerModel[]
@ -13,13 +14,13 @@ interface IFlexibleGroup {
* Flex the container and its siblings (mutate)
* @returns Flexed container
*/
export function Flex(container: IContainerModel, parent: IContainerModel): void {
export function Flex(containers: Map<string, IContainerModel>, container: IContainerModel, parent: IContainerModel): void {
const isVertical = parent.properties.orientation === Orientation.Vertical;
if (isVertical) {
const wantedWidth = Math.min(container.properties.maxWidth, parent.properties.width);
container.properties.width = ApplyWidthMargin(wantedWidth, container.properties.margin.left, container.properties.margin.right);
const flexibleGroups = GetVerticalFlexibleGroups(parent);
const flexibleGroups = GetVerticalFlexibleGroups(containers, parent);
for (const flexibleGroup of flexibleGroups) {
FlexGroupVertically(flexibleGroup);
}
@ -28,7 +29,7 @@ export function Flex(container: IContainerModel, parent: IContainerModel): void
const wantedHeight = Math.min(container.properties.maxHeight, parent.properties.height);
container.properties.height = ApplyWidthMargin(wantedHeight, container.properties.margin.top, container.properties.margin.bottom);
const flexibleGroups = GetHorizontalFlexibleGroups(parent);
const flexibleGroups = GetHorizontalFlexibleGroups(containers, parent);
for (const flexibleGroup of flexibleGroups) {
FlexGroupHorizontally(flexibleGroup);
}
@ -39,12 +40,12 @@ export function Flex(container: IContainerModel, parent: IContainerModel): void
* @param parent Parent in which the flexible children will be set in groups
* @returns a list of groups of flexible containers
*/
export function GetHorizontalFlexibleGroups(parent: IContainerModel): IFlexibleGroup[] {
export function GetHorizontalFlexibleGroups(containers: Map<string, IContainerModel>, parent: IContainerModel): IFlexibleGroup[] {
const flexibleGroups: IFlexibleGroup[] = [];
let group: IContainerModel[] = [];
let offset = 0;
let size = 0;
for (const child of parent.children) {
for (const child of MakeChildrenIterator(containers, parent.children)) {
if (child.properties.isAnchor) {
size = child.properties.x - offset;
const flexibleGroup: IFlexibleGroup = {
@ -77,12 +78,15 @@ export function GetHorizontalFlexibleGroups(parent: IContainerModel): IFlexibleG
* @param parent Parent in which the flexible children will be set in groups
* @returns a list of groups of flexible containers
*/
export function GetVerticalFlexibleGroups(parent: IContainerModel): IFlexibleGroup[] {
export function GetVerticalFlexibleGroups(
containers: Map<string, IContainerModel>,
parent: IContainerModel
): IFlexibleGroup[] {
const flexibleGroups: IFlexibleGroup[] = [];
let group: IContainerModel[] = [];
let offset = 0;
let size = 0;
for (const child of parent.children) {
for (const child of MakeChildrenIterator(containers, parent.children)) {
if (child.properties.isAnchor) {
size = child.properties.y - offset;
const flexibleGroup: IFlexibleGroup = {

View file

@ -1,6 +1,6 @@
import { IContainerModel } from '../../../Interfaces/IContainerModel';
import { Orientation } from '../../../Enums/Orientation';
import { ReversePairwise } from '../../../utils/itertools';
import { MakeChildrenIterator, ReversePairwise } from '../../../utils/itertools';
import { Flex } from './FlexBehaviors';
/**
@ -8,12 +8,17 @@ import { Flex } from './FlexBehaviors';
* @param container
* @returns
*/
export function ApplyPush(container: IContainerModel, parent: IContainerModel): IContainerModel {
export function ApplyPush(
containers: Map<string, IContainerModel>,
container: IContainerModel,
parent: IContainerModel
): IContainerModel {
if (parent.children.length <= 1) {
return container;
}
const children = parent.children;
const children: IContainerModel[] = [...MakeChildrenIterator(containers, parent.children)];
const isHorizontal = parent.properties.orientation === Orientation.Horizontal;
if (isHorizontal) {
@ -22,7 +27,7 @@ export function ApplyPush(container: IContainerModel, parent: IContainerModel):
PushContainersVertically(container, children);
}
Flex(container, parent);
Flex(containers, container, parent);
return container;
}

View file

@ -10,6 +10,7 @@ import { IContainerModel } from '../../../Interfaces/IContainerModel';
import { ISizePointer } from '../../../Interfaces/ISizePointer';
import { Orientation } from '../../../Enums/Orientation';
import { ENABLE_HARD_RIGID } from '../../../utils/default';
import { MakeChildrenIterator } from '../../../utils/itertools';
/**
* "Transform the container into a rigid body"
@ -21,13 +22,14 @@ import { ENABLE_HARD_RIGID } from '../../../utils/default';
* @returns A rigid body container
*/
export function ApplyRigidBody(
containers: Map<string, IContainerModel>,
container: IContainerModel,
parent: IContainerModel
): IContainerModel {
container = ConstraintBodyInsideParent(container, parent);
if (ENABLE_HARD_RIGID) {
container = ConstraintBodyInsideUnallocatedWidth(container);
container = ConstraintBodyInsideUnallocatedWidth(containers, container);
}
return container;
@ -117,6 +119,7 @@ function ConstraintBodyInsideSpace(
* @returns Updated container
*/
export function ConstraintBodyInsideUnallocatedWidth(
containers: Map<string, IContainerModel>,
container: IContainerModel
): IContainerModel {
if (container.parent === null || container.parent === undefined) {
@ -126,10 +129,11 @@ export function ConstraintBodyInsideUnallocatedWidth(
// Get the available spaces of the parent
const isHorizontal =
container.parent.properties.orientation === Orientation.Horizontal;
const children: IContainerModel[] = [...MakeChildrenIterator(containers, container.parent.children)];
const availableWidths = GetAvailableWidths(
0,
container.parent.properties.width,
container.parent.children,
children,
container,
isHorizontal
);

View file

@ -5,9 +5,13 @@
import { IContainerModel } from '../../../Interfaces/IContainerModel';
import { Orientation } from '../../../Enums/Orientation';
import { GetHorizontallyOverlappingContainers, GetVerticallyOverlappingContainers } from './AnchorBehaviors';
import { MakeChildrenIterator } from '../../../utils/itertools';
export function ApplySwap(container: IContainerModel, parent: IContainerModel): void {
const children = parent.children;
export function ApplySwap(
containers: Map<string, IContainerModel>,
container: IContainerModel,
parent: IContainerModel): void {
const children = [...MakeChildrenIterator(containers, parent.children)];
const isVertical = parent.properties.orientation === Orientation.Vertical;
if (isVertical) {