diff --git a/.changeset/sour-news-float.md b/.changeset/sour-news-float.md new file mode 100644 index 00000000..0e8bc986 --- /dev/null +++ b/.changeset/sour-news-float.md @@ -0,0 +1,5 @@ +--- +"planck": minor +--- + +Add world.queueUpdate() to queue and defer updates after current simulation step diff --git a/example/8-Ball.ts b/example/8-Ball.ts index 604d5807..ac0b52e4 100644 --- a/example/8-Ball.ts +++ b/example/8-Ball.ts @@ -218,13 +218,12 @@ class BilliardPhysics { const ball = fA.getUserData() === BALL ? bA : fB.getUserData() === BALL ? bB : null; const pocket = fA.getUserData() === POCKET ? bA : fB.getUserData() === POCKET ? bB : null; - // do not change world immediately - setTimeout(() => { - if (ball && pocket) { + if (ball && pocket) { + this.world.queueUpdate(() => { this.world.destroyBody(ball); this.client?.onBallInPocket(ball, pocket); - } - }, 1); + }); + } }; } diff --git a/example/Asteroid.ts b/example/Asteroid.ts index 688311cd..74f69624 100644 --- a/example/Asteroid.ts +++ b/example/Asteroid.ts @@ -230,15 +230,17 @@ class AsteroidPhysics { const asteroid = dataA?.type == "asteroid" ? bodyA : dataB?.type == "asteroid" ? bodyB : null; - setTimeout(() => { - if (ship && asteroid) { + if (ship && asteroid) { + this.world.queueUpdate(() => { this.client?.collideShipAsteroid(ship, asteroid); - } + }); + } - if (bullet && asteroid) { + if (bullet && asteroid) { + this.world.queueUpdate(() => { this.client?.collideBulletAsteroid(bullet, asteroid); - } - }, 1); + }); + } } deleteShip(): boolean { diff --git a/example/Breakable.ts b/example/Breakable.ts index 2cfceb41..bb8a221a 100644 --- a/example/Breakable.ts +++ b/example/Breakable.ts @@ -45,14 +45,12 @@ world.on("post-solve", function (contact, impulse) { } if (maxImpulse > 40.0) { - setTimeout(function () { - Break(); - broke = true; - }); + broke = true; + world.queueUpdate(breakIt); } }); -function Break() { +function breakIt() { // Create two bodies from one. const center = body1.getWorldCenter(); diff --git a/example/Breakout.ts b/example/Breakout.ts index f5b13504..7c569557 100644 --- a/example/Breakout.ts +++ b/example/Breakout.ts @@ -198,19 +198,27 @@ class BreakoutPhysics { const drop = typeA === "drop" ? dataA : typeB === "drop" ? dataB : null; // do not change world immediately - setTimeout(() => { - if (ball && brick) { + if (ball && brick) { + this.world.queueUpdate(() => { this.client?.collideBallBrick(ball as BallData, brick as BrickData); - } else if (ball && bottom) { + }); + } else if (ball && bottom) { + this.world.queueUpdate(() => { this.client?.collideBallBottom(ball as BallData); - } else if (ball && paddle) { + }); + } else if (ball && paddle) { + this.world.queueUpdate(() => { this.client?.collideBallPaddle(ball as BallData); - } else if (drop && paddle) { + }); + } else if (drop && paddle) { + this.world.queueUpdate(() => { this.client?.collideDropPaddle(drop as DropData); - } else if (drop && bottom) { + }); + } else if (drop && bottom) { + this.world.queueUpdate(() => { this.client?.collideDropBottom(drop as DropData); - } - }, 1); + }); + } }; createBoardPhysics() { diff --git a/example/Shuffle.ts b/example/Shuffle.ts index a325677c..6f317b94 100644 --- a/example/Shuffle.ts +++ b/example/Shuffle.ts @@ -80,12 +80,11 @@ world.on("post-solve", function (contact) { ? bB : null; - // do not change world immediately - setTimeout(function () { - if (ball && wall) { + if (ball && wall) { + world.queueUpdate(() => { world.destroyBody(ball); - } - }, 1); + }); + } }); function row(n: number, m: number, r: number, l: number) { diff --git a/example/Soccer.ts b/example/Soccer.ts index 68f2df78..001632db 100644 --- a/example/Soccer.ts +++ b/example/Soccer.ts @@ -123,14 +123,14 @@ world.on("post-solve", function (contact) { ? bB : null; - // do not change world immediately - setTimeout(function () { - if (ball && goal) { + if (ball && goal) { + // do not change world immediately + world.queueUpdate(function () { ball.setPosition({ x: 0, y: 0 }); ball.setLinearVelocity({ x: 0, y: 0 }); // world.destroyBody(ball); - } - }, 1); + }); + } }); function team() { diff --git a/src/dynamics/World.ts b/src/dynamics/World.ts index a1025a26..d69e8cf6 100644 --- a/src/dynamics/World.ts +++ b/src/dynamics/World.ts @@ -128,6 +128,8 @@ export class World { /** @internal */ m_positionIterations: number; /** @internal */ m_t: number; + /** @internal */ m_step_callback: ((world: World) => unknown)[] = []; + // TODO /** @internal */ _listeners: { [key: string]: any[] @@ -491,8 +493,8 @@ export class World { /** @internal Used for deserialize. */ _addBody(body: Body): void { - if (_ASSERT) console.assert(this.isLocked() === false); - if (this.isLocked()) { + if (_ASSERT) console.assert(this.m_locked === false); + if (this.m_locked) { return; } @@ -510,14 +512,14 @@ export class World { * Create a rigid body given a definition. No reference to the definition is * retained. * - * Warning: This function is locked during callbacks. + * Warning: This function is locked when a world simulation step is in progress. */ createBody(def?: BodyDef): Body; createBody(position: Vec2Value, angle?: number): Body; // tslint:disable-next-line:typedef createBody(arg1?, arg2?) { - if (_ASSERT) console.assert(this.isLocked() == false); - if (this.isLocked()) { + if (_ASSERT) console.assert(this.m_locked == false); + if (this.m_locked) { return null; } @@ -565,17 +567,16 @@ export class World { } /** - * Destroy a rigid body given a definition. No reference to the definition is - * retained. + * Destroy a body from the world. * * Warning: This automatically deletes all associated shapes and joints. * - * Warning: This function is locked during callbacks. + * Warning: This function is locked when a world simulation step is in progress. */ destroyBody(b: Body): boolean { if (_ASSERT) console.assert(this.m_bodyCount > 0); - if (_ASSERT) console.assert(this.isLocked() == false); - if (this.isLocked()) { + if (_ASSERT) console.assert(this.m_locked == false); + if (this.m_locked) { return; } @@ -647,13 +648,13 @@ export class World { * Create a joint to constrain bodies together. No reference to the definition * is retained. This may cause the connected bodies to cease colliding. * - * Warning: This function is locked during callbacks. + * Warning: This function is locked when a world simulation step is in progress. */ createJoint(joint: T): T | null { if (_ASSERT) console.assert(!!joint.m_bodyA); if (_ASSERT) console.assert(!!joint.m_bodyB); - if (_ASSERT) console.assert(this.isLocked() == false); - if (this.isLocked()) { + if (_ASSERT) console.assert(this.m_locked == false); + if (this.m_locked) { return null; } @@ -700,12 +701,15 @@ export class World { } /** - * Destroy a joint. This may cause the connected bodies to begin colliding. - * Warning: This function is locked during callbacks. + * Destroy a joint. + * + * Warning: This may cause the connected bodies to begin colliding. + * + * Warning: This function is locked when a world simulation step is in progress. */ destroyJoint(joint: Joint): void { - if (_ASSERT) console.assert(this.isLocked() == false); - if (this.isLocked()) { + if (_ASSERT) console.assert(this.m_locked == false); + if (this.m_locked) { return; } @@ -854,9 +858,25 @@ export class World { this.m_locked = false; + let callback: (world: World) => unknown; + while(callback = this.m_step_callback.pop()) { + callback(this); + } + this.publish("post-step", timeStep); } + /** + * Queue a function to be called after ongoing simulation step. If no simulation is in progress call it immediately. + */ + queueUpdate(callback: (world: World) => unknown): void { + if (!this.m_locked) { + callback(this); + } else { + this.m_step_callback.push(callback); + } + } + /** * @internal * Call this method to find new contacts.