Skip to content

Commit

Permalink
#45 implement scheduling and sync-point creation prefabs
Browse files Browse the repository at this point in the history
  • Loading branch information
minecrawler committed Apr 27, 2022
1 parent a6e3964 commit 50c66d9
Show file tree
Hide file tree
Showing 12 changed files with 135 additions and 60 deletions.
28 changes: 9 additions & 19 deletions examples/bench/src/libraries/_sim-ecs/schedule.ts
Expand Up @@ -2,11 +2,11 @@ import {buildWorld, createSystem, queryComponents, World, Write} from '../../../
import {IBenchmark} from "../../benchmark.spec";
import {CheckEndSystem, CounterResource} from "./_";

class A { constructor(public val: number) {} }
class B { constructor(public val: number) {} }
class C { constructor(public val: number) {} }
class D { constructor(public val: number) {} }
class E { constructor(public val: number) {} }
class A { constructor(public val: number = 0) {} }
class B { constructor(public val: number = 0) {} }
class C { constructor(public val: number = 0) {} }
class D { constructor(public val: number = 0) {} }
class E { constructor(public val: number = 0) {} }


const ABSystem = createSystem({
Expand Down Expand Up @@ -77,35 +77,25 @@ export class Benchmark implements IBenchmark {

for (let i = 0; i < 10000; i++) {
this.world.buildEntity()
.with(A, 0)
.with(B, 0)
.withAll(A, B)
.build();
}

for (let i = 0; i < 10000; i++) {
this.world.buildEntity()
.with(A, 0)
.with(B, 0)
.with(C, 0)
.withAll(A, B, C)
.build();
}

for (let i = 0; i < 10000; i++) {
this.world.buildEntity()
.with(A, 0)
.with(B, 0)
.with(C, 0)
.with(D, 0)
.withAll(A, B, C, D)
.build();
}

for (let i = 0; i < 10000; i++) {
this.world.buildEntity()
.with(A, 0)
.with(B, 0)
.with(C, 0)
.with(D, 0)
.with(E, 0)
.withAll(A, B, C, D, E)
.build();
}
}
Expand Down
30 changes: 27 additions & 3 deletions examples/counter.ts
@@ -1,4 +1,4 @@
import {Actions, buildWorld, createSystem, queryComponents, Write} from "../src";
import {Actions, buildWorld, createSystem, ISyncPointPrefab, queryComponents, Write} from "../src";


/// a component.
Expand Down Expand Up @@ -50,18 +50,42 @@ const CounterSystem = createSystem({
})
.build();

// in order to execute all systems in the right order, we can create prefabs for execution schedules
// we can do so by defining what is happening in between sync-points

// sync-points are defined moments in the schedule, when no system runs, and the ECS can sync updates,
// like adding and removing entities or components, to all system caches.
// It is useful to keep the amount of these sync-points to a minimum for a speedy execution!
// The ECS will always have one sync-point, which is the "root" sync-point.
// This means that syncing will always happen between each step.

// for our simple logic, we only need one sync-point, which will be reached after the below plan has been executed
const executionSchedule: ISyncPointPrefab = {
// each sync point contains stages, which are work units with a defined (custom or default) scheduler
stages: [
// each stage also contains several systems, which are orchestrated by the stage's scheduler
[
CounterSystem,
],
],
};

/// then, we need a world which will hold our systems and entities
const world = buildWorld()
/// we can inform the world about our processing logic by adding the above defined system
.withDefaultScheduling(root => root.addNewStage(stage => stage.addSystem(CounterSystem)))
/// we can inform the world about our processing logic by adding the above defined prefab
.withDefaultScheduling(root => root.fromPrefab(executionSchedule))
/// we can register components types at this level in order to enable saving (serialization) and loading (deserialization) of them
.withComponent(CounterInfo)
.build();

/// in order to do something, we still need to add data, which can be processed.
/// think of this like filling up your database, whereas each entity is a row and each component is a column
world
/// the commands interface is a safe way to mutate the world, since all changes are always buffered until a good moment,
/// for example after each step, all commands are applied
/// doing so makes sure that there are no race conditions between different systems accessing entities simultaneously
.commands
/// invoking the entity builder in this way automatically adds the entity to the world
.buildEntity()
.with(CounterInfo)
.build();
Expand Down
36 changes: 4 additions & 32 deletions examples/pong/src/main.ts
@@ -1,27 +1,19 @@
import {buildWorld, IWorld} from "sim-ecs";
import {PaddleSystem} from "./systems/paddle";
import {InputSystem} from "./systems/input";
import {GameStore} from "./models/game-store";
import {MenuState} from "./states/menu";
import {UIItem} from "./components/ui-item";
import {MenuSystem} from "./systems/menu";
import {PauseSystem} from "./systems/pause";
import {Paddle} from "./components/paddle";
import {Position} from "./components/position";
import {BallSystem} from "./systems/ball";
import {Velocity} from "./components/velocity";
import {RenderUISystem} from "./systems/render-ui";
import {RenderGameSystem} from "./systems/render-game";
import {Shape} from "./components/shape";
import {AnimationSystem} from "./systems/animation";
import {ScoreBoard} from "./models/score-board";
import {PaddleTransforms} from "./models/paddle-transforms";
import {Dimensions} from "./models/dimensions";
import {CollisionSystem} from "./systems/collision";
import {Collision} from "./components/collision";
import {Wall} from "./components/wall";
import {BeforeStepSystem} from "./systems/before-step";
import {PauseState} from "./states/pause";
import {defaultSchedule} from "./schedules/default";
import {pauseSchedule} from "./schedules/pause";


const cleanup = () => {
Expand All @@ -38,28 +30,8 @@ const cleanup = () => {

const createWorld = () => {
return buildWorld()
.withDefaultScheduling(root => root
.addNewStage(stage => stage.addSystem(BeforeStepSystem))
.addNewStage(stage => stage.addSystem(InputSystem))
.addNewStage(stage => stage
.addSystem(MenuSystem)
.addSystem(PaddleSystem)
.addSystem(PauseSystem))
.addNewStage(stage => stage.addSystem(CollisionSystem))
.addNewStage(stage => stage.addSystem(BallSystem))
.addNewStage(stage => stage.addSystem(AnimationSystem))
.addNewStage(stage => stage
.addSystem(RenderGameSystem)
.addSystem(RenderUISystem))
)
.withStateScheduling(PauseState, root => root
.addNewStage(stage => stage.addSystem(BeforeStepSystem))
.addNewStage(stage => stage.addSystem(InputSystem))
.addNewStage(stage => stage.addSystem(PauseSystem))
.addNewStage(stage => stage
.addSystem(RenderGameSystem)
.addSystem(RenderUISystem))
)
.withDefaultScheduling(root => root.fromPrefab(defaultSchedule))
.withStateScheduling(PauseState, root => root.fromPrefab(pauseSchedule))
.withComponents(
Collision,
Paddle,
Expand Down
29 changes: 29 additions & 0 deletions examples/pong/src/schedules/default.ts
@@ -0,0 +1,29 @@
import {ISyncPointPrefab} from "sim-ecs";
import {BeforeStepSystem} from "../systems/before-step";
import {MenuSystem} from "../systems/menu";
import {PaddleSystem} from "../systems/paddle";
import {PauseSystem} from "../systems/pause";
import {CollisionSystem} from "../systems/collision";
import {BallSystem} from "../systems/ball";
import {AnimationSystem} from "../systems/animation";
import {RenderGameSystem} from "../systems/render-game";
import {RenderUISystem} from "../systems/render-ui";


export const defaultSchedule: ISyncPointPrefab = {
stages: [
[BeforeStepSystem],
[
MenuSystem,
PaddleSystem,
PauseSystem,
],
[CollisionSystem],
[BallSystem],
[AnimationSystem],
[
RenderGameSystem,
RenderUISystem,
],
],
};
19 changes: 19 additions & 0 deletions examples/pong/src/schedules/pause.ts
@@ -0,0 +1,19 @@
import {ISyncPointPrefab} from "sim-ecs";
import {BeforeStepSystem} from "../systems/before-step";
import {InputSystem} from "../systems/input";
import {PauseSystem} from "../systems/pause";
import {RenderGameSystem} from "../systems/render-game";
import {RenderUISystem} from "../systems/render-ui";


export const pauseSchedule: ISyncPointPrefab = {
stages: [
[BeforeStepSystem],
[InputSystem],
[PauseSystem],
[
RenderGameSystem,
RenderUISystem,
],
],
};
1 change: 1 addition & 0 deletions src/index.ts
Expand Up @@ -2,6 +2,7 @@ export * from './ecs/ecs-world';
export * from './entity';
export * from './entity-builder';
export * from './query';
export * from './scheduler';
export * from './serde';
export * from './state';
export * from './system';
Expand Down
1 change: 0 additions & 1 deletion src/query/query.spec.ts
Expand Up @@ -73,4 +73,3 @@ export interface IQuery<DESC, DATA> {

export interface IComponentsQuery<DESC extends IAccessQuery<TObjectProto>> extends IQuery<DESC, TAccessQueryData<DESC>> {}
export interface IEntitiesQuery extends IQuery<TExistenceQuery<TObjectProto>, IEntity> {}
//export interface IQueryProto<D extends IAccessQuery<TObjectProto> | TExistenceQuery<TObjectProto>> { new(): IQuery<D> }
3 changes: 3 additions & 0 deletions src/scheduler/index.ts
@@ -1 +1,4 @@
export * from "./scheduler";
export * from "./pipeline/pipeline";
export * from "./pipeline/stage";
export * from "./pipeline/sync-point";
4 changes: 2 additions & 2 deletions src/scheduler/pipeline/stage.spec.ts
Expand Up @@ -2,10 +2,10 @@ import {ISystem} from "../../system";
import {TExecutor} from "../../_.spec";
import {World} from "../../world";

export type TSchedulingAlgorithm = (world: World, systems: ISystem[]) => Promise<void>;
export type TStageSchedulingAlgorithm = (world: World, systems: ISystem[]) => Promise<void>;

export interface IStage {
schedulingAlgorithm: TSchedulingAlgorithm
schedulingAlgorithm: TStageSchedulingAlgorithm
systems: ISystem[]

/**
Expand Down
4 changes: 2 additions & 2 deletions src/scheduler/pipeline/stage.ts
Expand Up @@ -7,7 +7,7 @@ import type {World} from "../../world";

export * from "./stage.spec";

export async function defaultSchedulingAlgorithm(world: World, systems: ISystem[]): Promise<void> {
export async function defaultStageSchedulingAlgorithm(world: World, systems: ISystem[]): Promise<void> {
let system;

for (system of systems) {
Expand All @@ -25,7 +25,7 @@ export async function defaultSchedulingAlgorithm(world: World, systems: ISystem[
}

export class Stage implements IStage {
schedulingAlgorithm = defaultSchedulingAlgorithm;
schedulingAlgorithm = defaultStageSchedulingAlgorithm;
systems: ISystem[] = [];

addSystem(System: ISystem): Stage {
Expand Down
13 changes: 13 additions & 0 deletions src/scheduler/pipeline/sync-point.spec.ts
@@ -1,4 +1,11 @@
import {IStage} from "./stage.spec";
import {ISystem} from "../../system";

export interface ISyncPointPrefab {
after?: ISyncPointPrefab
before?: ISyncPointPrefab
stages?: ISystem<any>[][]
}

export interface ISyncPoint {
after?: ISyncPoint
Expand All @@ -9,4 +16,10 @@ export interface ISyncPoint {
* Add a stage to this group
*/
addNewStage(handler: (stage: IStage) => void): ISyncPoint

/**
* Create an execution tree from a schedule-prefab
* @param prefab
*/
fromPrefab(prefab: ISyncPointPrefab): ISyncPoint
}
27 changes: 26 additions & 1 deletion src/scheduler/pipeline/sync-point.ts
@@ -1,5 +1,6 @@
import {ISyncPoint} from "./sync-point.spec";
import {ISyncPoint, ISyncPointPrefab} from "./sync-point.spec";
import {IStage, Stage} from "./stage";
import {ISystem} from "../../system";

export * from "./sync-point.spec";

Expand All @@ -14,4 +15,28 @@ export class SyncPoint implements ISyncPoint {
handler(stage);
return this;
}

fromPrefab({after, before, stages = []}: ISyncPointPrefab): SyncPoint {
this.after = after
? new SyncPoint().fromPrefab(after)
: undefined;
this.before = before
? new SyncPoint().fromPrefab(before)
: undefined;
this.stages.length = 0;

{
let stage: ISystem[];
let system: ISystem;
for (stage of stages) {
this.addNewStage(newStage => {
for (system of stage) {
newStage.addSystem(system);
}
});
}
}

return this;
}
}

0 comments on commit 50c66d9

Please sign in to comment.