Skip to content

Commit 8a80680

Browse files
committed
Merge branch 'main' into refactor/remove-query-class
2 parents f100561 + 73e0ffa commit 8a80680

File tree

10 files changed

+96
-30
lines changed

10 files changed

+96
-30
lines changed

packages/core/src/entity/entity.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,5 +110,5 @@ export function destroyEntity(world: World, entity: Entity) {
110110

111111
/* @inline @pure */ export function getEntityWorld(entity: Entity) {
112112
const worldId = getEntityWorldId(entity);
113-
return universe.worlds[worldId]!.deref()!;
113+
return universe.worlds[worldId]!;
114114
}

packages/core/src/query/modifiers/added.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export function createAdded() {
88

99
for (const world of universe.worlds) {
1010
if (!world) continue;
11-
setTrackingMasks(world.deref()!, id);
11+
setTrackingMasks(world, id);
1212
}
1313

1414
return <T extends Trait[] = Trait[]>(...traits: T) => {

packages/core/src/query/modifiers/changed.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export function createChanged() {
1313

1414
for (const world of universe.worlds) {
1515
if (!world) continue;
16-
setTrackingMasks(world.deref()!, id);
16+
setTrackingMasks(world, id);
1717
}
1818

1919
return <T extends Trait[] = Trait[]>(...traits: T) => {

packages/core/src/query/modifiers/removed.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export function createRemoved() {
88

99
for (const world of universe.worlds) {
1010
if (!world) continue;
11-
setTrackingMasks(world.deref()!, id);
11+
setTrackingMasks(world, id);
1212
}
1313

1414
return <T extends Trait[] = Trait[]>(...traits: T) => {

packages/core/src/query/utils/cache-query.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,9 @@ import { createQueryHash } from './create-query-hash';
77
export function cacheQuery<T extends QueryParameter[]>(...parameters: T): QueryHash<T> {
88
const hash = createQueryHash(parameters);
99

10-
for (const worldRef of universe.worlds) {
11-
if (!worldRef) continue;
10+
for (const world of universe.worlds) {
11+
if (!world) continue;
1212

13-
const world = worldRef.deref()!;
1413
const ctx = world[$internal];
1514

1615
if (!ctx.queriesHashMap.has(hash)) {

packages/core/src/universe/universe.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,13 @@
1-
import { WORLD_ID_BITS } from '../entity/utils/pack-entity';
21
import type { QueryParameter } from '../query/types';
32
import { createWorldIndex } from '../world/utils/world-index';
43
import type { World } from '../world/world';
54

65
export const universe = {
7-
worlds: Array.from({ length: WORLD_ID_BITS ** 2 }, () => null as WeakRef<World> | null),
6+
worlds: [] as (World | null)[],
87
cachedQueries: new Map<string, QueryParameter[]>(),
98
worldIndex: createWorldIndex(),
109
reset: () => {
11-
universe.worlds = Array.from(
12-
{ length: WORLD_ID_BITS ** 2 },
13-
() => null as WeakRef<World> | null
14-
);
10+
universe.worlds = [];
1511
universe.cachedQueries = new Map();
1612
universe.worldIndex = createWorldIndex();
1713
},

packages/core/src/world/world.ts

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ import type {
2727
import { universe } from '../universe/universe';
2828
import { allocateWorldId, releaseWorldId } from './utils/world-index';
2929

30+
type Options = {
31+
traits?: ConfigurableTrait[];
32+
lazy?: boolean;
33+
};
34+
3035
export class World {
3136
#id = allocateWorldId(universe.worldIndex);
3237

@@ -64,16 +69,21 @@ export class World {
6469

6570
traits = new Set<Trait>();
6671

67-
constructor(...traits: ConfigurableTrait[]) {
68-
this.init(...traits);
72+
constructor(polyArg?: Options | ConfigurableTrait, ...traits: ConfigurableTrait[]) {
73+
if (polyArg && typeof polyArg === 'object' && !Array.isArray(polyArg)) {
74+
const { traits: optionTraits = [], lazy = false } = polyArg as Options;
75+
if (!lazy) this.init(...optionTraits);
76+
} else {
77+
this.init(...(polyArg ? [polyArg, ...traits] : traits));
78+
}
6979
}
7080

7181
init(...traits: ConfigurableTrait[]) {
7282
const ctx = this[$internal];
7383
if (this.#isInitialized) return;
7484

7585
this.#isInitialized = true;
76-
universe.worlds[this.#id] = new WeakRef(this);
86+
universe.worlds[this.#id] = this;
7787

7888
// Create uninitialized added masks.
7989
const cursor = getTrackingCursor();
@@ -127,8 +137,6 @@ export class World {
127137
destroyEntity(this, this[$internal].worldEntity);
128138
this[$internal].worldEntity = null!;
129139

130-
// Destroy itself and all entities.
131-
this.entities.forEach((entity) => destroyEntity(this, entity));
132140
this.reset();
133141
this.#isInitialized = false;
134142

@@ -318,14 +326,11 @@ export class World {
318326
}
319327
}
320328

321-
// Clean up the world ID when it is garbage collected.
322-
const worldFinalizer = new FinalizationRegistry((worldId: number) => {
323-
universe.worlds[worldId] = null;
324-
releaseWorldId(universe.worldIndex, worldId);
325-
});
326-
327-
export function createWorld(...traits: ConfigurableTrait[]) {
328-
const world = new World(...traits);
329-
worldFinalizer.register(world, world.id);
330-
return world;
329+
export function createWorld(options: Options): World;
330+
export function createWorld(...traits: ConfigurableTrait[]): World;
331+
export function createWorld(
332+
optionsOrFirstTrait?: Options | ConfigurableTrait,
333+
...traits: ConfigurableTrait[]
334+
) {
335+
return new World(optionsOrFirstTrait, ...traits);
331336
}

packages/core/tests/world.test.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,17 @@ describe('World', () => {
1212

1313
expect(world.isInitialized).toBe(true);
1414
expect(world.id).toBe(0);
15-
expect(universe.worlds[0]!.deref()!).toBe(world);
15+
expect(universe.worlds[0]!).toBe(world);
1616
expect(universe.worldIndex.worldCursor).toBe(1);
1717
});
1818

19+
it('should optionaly init lazily', () => {
20+
const world = createWorld({ lazy: true });
21+
expect(world.isInitialized).toBe(false);
22+
world.init();
23+
expect(world.isInitialized).toBe(true);
24+
});
25+
1926
it('should reset the world', () => {
2027
const world = createWorld();
2128
world.reset();
@@ -42,6 +49,22 @@ describe('World', () => {
4249
expect(world.entities.length).toBe(1);
4350
});
4451

52+
it('destroy should lead to entities with auto-remove relations being removed as well', () => {
53+
const Node = trait();
54+
const ChildOf = relation({ autoRemoveTarget: true, exclusive: true });
55+
56+
const world = createWorld();
57+
58+
// Create a parent node and two child nodes
59+
const parentNode = world.spawn(Node);
60+
world.spawn(Node, ChildOf(parentNode));
61+
world.spawn(Node, ChildOf(parentNode));
62+
63+
// Expect this to not throw, since the ChildOf relation will automatically
64+
// remove the child node when the parent node is destroyed first
65+
expect(() => world.destroy()).not.toThrow();
66+
});
67+
4568
it('errors if more than 16 worlds are created', () => {
4669
for (let i = 0; i < 16; i++) {
4770
createWorld();

packages/publish/tests/core/world.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,22 @@ describe('World', () => {
4242
expect(world.entities.length).toBe(1);
4343
});
4444

45+
it('destroy should lead to entities with auto-remove relations being removed as well', () => {
46+
const Node = trait();
47+
const ChildOf = relation({ autoRemoveTarget: true, exclusive: true });
48+
49+
const world = createWorld();
50+
51+
// Create a parent node and two child nodes
52+
const parentNode = world.spawn(Node);
53+
world.spawn(Node, ChildOf(parentNode));
54+
world.spawn(Node, ChildOf(parentNode));
55+
56+
// Expect this to not throw, since the ChildOf relation will automatically
57+
// remove the child node when the parent node is destroyed first
58+
expect(() => world.destroy()).not.toThrow();
59+
});
60+
4561
it('errors if more than 16 worlds are created', () => {
4662
for (let i = 0; i < 16; i++) {
4763
createWorld();

packages/react/tests/world.test.tsx

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { createWorld, universe, type World } from '@koota/core';
22
import { render } from '@testing-library/react';
3-
import { act, StrictMode } from 'react';
3+
import { act, StrictMode, useEffect, useMemo } from 'react';
44
import { beforeEach, describe, expect, it } from 'vitest';
55
import { useWorld, WorldProvider } from '../src';
66

@@ -39,4 +39,31 @@ describe('World', () => {
3939

4040
expect(worldTest).toBe(world);
4141
});
42+
43+
it('can lazy init to create a world in useMemo', () => {
44+
universe.reset();
45+
46+
let worldTest: World = null!;
47+
48+
function Test() {
49+
worldTest = useMemo(() => createWorld({ lazy: true }), []);
50+
51+
useEffect(() => {
52+
worldTest.init();
53+
return () => worldTest.destroy();
54+
}, [worldTest]);
55+
56+
return null;
57+
}
58+
59+
render(
60+
<StrictMode>
61+
<Test />
62+
</StrictMode>
63+
);
64+
65+
expect(worldTest).toBeDefined();
66+
expect(worldTest!.isInitialized).toBe(true);
67+
expect(universe.worlds.length).toBe(1);
68+
});
4269
});

0 commit comments

Comments
 (0)