Compare commits

...

2 Commits

Author SHA1 Message Date
Davy Hélard
f8f692fff4 Fix duplicated active objects after recycling. 2023-11-28 20:51:27 +01:00
Davy Hélard
d051aa4302 Avoid to iterate on inactive objects 2023-11-28 14:29:38 +01:00
20 changed files with 1062 additions and 754 deletions

View File

@@ -113,6 +113,10 @@ namespace gdjs {
); );
} }
if (this.isNeedingLifecycleFunctions()) {
this.getLifecycleSleepState().wakeUp();
}
// *ALWAYS* call `this.onCreated()` at the very end of your object constructor. // *ALWAYS* call `this.onCreated()` at the very end of your object constructor.
this.onCreated(); this.onCreated();
} }
@@ -194,6 +198,10 @@ namespace gdjs {
} }
} }
isNeedingLifecycleFunctions(): boolean {
return super.isNeedingLifecycleFunctions() || this._animations.length > 0;
}
update(instanceContainer: gdjs.RuntimeInstanceContainer): void { update(instanceContainer: gdjs.RuntimeInstanceContainer): void {
const elapsedTime = this.getElapsedTime() / 1000; const elapsedTime = this.getElapsedTime() / 1000;
this._renderer.updateAnimation(elapsedTime * this._animationSpeedScale); this._renderer.updateAnimation(elapsedTime * this._animationSpeedScale);

View File

@@ -1,11 +1,9 @@
namespace gdjs { namespace gdjs {
declare var rbush: any;
export class LightObstaclesManager { export class LightObstaclesManager {
_obstacleRBush: any; _obstacleRBush: RBush<LightObstacleRuntimeBehavior>;
constructor(instanceContainer: gdjs.RuntimeInstanceContainer) { constructor(instanceContainer: gdjs.RuntimeInstanceContainer) {
this._obstacleRBush = new rbush(); this._obstacleRBush = new RBush<LightObstacleRuntimeBehavior>();
} }
/** /**
@@ -41,6 +39,9 @@ namespace gdjs {
* added before. * added before.
*/ */
removeObstacle(obstacle: gdjs.LightObstacleRuntimeBehavior) { removeObstacle(obstacle: gdjs.LightObstacleRuntimeBehavior) {
if (!obstacle.currentRBushAABB) {
return;
}
this._obstacleRBush.remove(obstacle.currentRBushAABB); this._obstacleRBush.remove(obstacle.currentRBushAABB);
} }
@@ -59,9 +60,9 @@ namespace gdjs {
// is not necessarily in the middle of the object (for sprites for example). // is not necessarily in the middle of the object (for sprites for example).
const x = object.getX(); const x = object.getX();
const y = object.getY(); const y = object.getY();
const searchArea = gdjs.staticObject( const searchArea: SearchArea = gdjs.staticObject(
LightObstaclesManager.prototype.getAllObstaclesAround LightObstaclesManager.prototype.getAllObstaclesAround
); ) as SearchArea;
// @ts-ignore // @ts-ignore
searchArea.minX = x - radius; searchArea.minX = x - radius;
// @ts-ignore // @ts-ignore
@@ -70,13 +71,8 @@ namespace gdjs {
searchArea.maxX = x + radius; searchArea.maxX = x + radius;
// @ts-ignore // @ts-ignore
searchArea.maxY = y + radius; searchArea.maxY = y + radius;
const nearbyObstacles: gdjs.BehaviorRBushAABB<
gdjs.LightObstacleRuntimeBehavior
>[] = this._obstacleRBush.search(searchArea);
result.length = 0; result.length = 0;
nearbyObstacles.forEach((nearbyObstacle) => this._obstacleRBush.search(searchArea, result);
result.push(nearbyObstacle.behavior)
);
} }
} }

View File

@@ -7,7 +7,6 @@ namespace gdjs {
export interface RuntimeInstanceContainer { export interface RuntimeInstanceContainer {
pathfindingObstaclesManager: gdjs.PathfindingObstaclesManager; pathfindingObstaclesManager: gdjs.PathfindingObstaclesManager;
} }
declare var rbush: any;
/** /**
* PathfindingObstaclesManager manages the common objects shared by objects * PathfindingObstaclesManager manages the common objects shared by objects
@@ -18,10 +17,10 @@ namespace gdjs {
* `gdjs.PathfindingRuntimeBehavior.obstaclesManagers`). * `gdjs.PathfindingRuntimeBehavior.obstaclesManagers`).
*/ */
export class PathfindingObstaclesManager { export class PathfindingObstaclesManager {
_obstaclesRBush: any; _obstaclesRBush: RBush<PathfindingObstacleRuntimeBehavior>;
constructor(instanceContainer: gdjs.RuntimeInstanceContainer) { constructor(instanceContainer: gdjs.RuntimeInstanceContainer) {
this._obstaclesRBush = new rbush(); this._obstaclesRBush = new RBush<PathfindingObstacleRuntimeBehavior>();
} }
/** /**
@@ -60,6 +59,9 @@ namespace gdjs {
removeObstacle( removeObstacle(
pathfindingObstacleBehavior: PathfindingObstacleRuntimeBehavior pathfindingObstacleBehavior: PathfindingObstacleRuntimeBehavior
) { ) {
if (!pathfindingObstacleBehavior.currentRBushAABB) {
return;
}
this._obstaclesRBush.remove(pathfindingObstacleBehavior.currentRBushAABB); this._obstaclesRBush.remove(pathfindingObstacleBehavior.currentRBushAABB);
} }
@@ -74,9 +76,9 @@ namespace gdjs {
radius: float, radius: float,
result: gdjs.PathfindingObstacleRuntimeBehavior[] result: gdjs.PathfindingObstacleRuntimeBehavior[]
): void { ): void {
const searchArea = gdjs.staticObject( const searchArea: SearchArea = gdjs.staticObject(
PathfindingObstaclesManager.prototype.getAllObstaclesAround PathfindingObstaclesManager.prototype.getAllObstaclesAround
); ) as SearchArea;
// @ts-ignore // @ts-ignore
searchArea.minX = x - radius; searchArea.minX = x - radius;
// @ts-ignore // @ts-ignore
@@ -85,13 +87,8 @@ namespace gdjs {
searchArea.maxX = x + radius; searchArea.maxX = x + radius;
// @ts-ignore // @ts-ignore
searchArea.maxY = y + radius; searchArea.maxY = y + radius;
const nearbyObstacles: gdjs.BehaviorRBushAABB<
gdjs.PathfindingObstacleRuntimeBehavior
>[] = this._obstaclesRBush.search(searchArea);
result.length = 0; result.length = 0;
nearbyObstacles.forEach((nearbyObstacle) => this._obstaclesRBush.search(searchArea, result);
result.push(nearbyObstacle.behavior)
);
} }
} }

View File

@@ -211,6 +211,9 @@ namespace gdjs {
} }
doStepPreEvents(instanceContainer: gdjs.RuntimeInstanceContainer) { doStepPreEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {
// Update platforms locations.
this._manager.doStepPreEvents();
const LEFTKEY = 37; const LEFTKEY = 37;
const UPKEY = 38; const UPKEY = 38;
const RIGHTKEY = 39; const RIGHTKEY = 39;

View File

@@ -3,7 +3,6 @@ GDevelop - Platform Behavior Extension
Copyright (c) 2013-2016 Florian Rival (Florian.Rival@gmail.com) Copyright (c) 2013-2016 Florian Rival (Florian.Rival@gmail.com)
*/ */
namespace gdjs { namespace gdjs {
declare var rbush: any;
type SearchArea = { minX: float; minY: float; maxX: float; maxY: float }; type SearchArea = { minX: float; minY: float; maxX: float; maxY: float };
/** /**
@@ -13,10 +12,12 @@ namespace gdjs {
* of their associated container (see PlatformRuntimeBehavior.getManager). * of their associated container (see PlatformRuntimeBehavior.getManager).
*/ */
export class PlatformObjectsManager { export class PlatformObjectsManager {
private _platformRBush: any; private _platformRBush: RBush<PlatformRuntimeBehavior>;
private movedPlatforms: Array<gdjs.PlatformRuntimeBehavior>;
constructor(instanceContainer: gdjs.RuntimeInstanceContainer) { constructor(instanceContainer: gdjs.RuntimeInstanceContainer) {
this._platformRBush = new rbush(); this._platformRBush = new RBush<PlatformRuntimeBehavior>();
this.movedPlatforms = [];
} }
/** /**
@@ -38,7 +39,7 @@ namespace gdjs {
/** /**
* Add a platform to the list of existing platforms. * Add a platform to the list of existing platforms.
*/ */
addPlatform(platformBehavior: gdjs.PlatformRuntimeBehavior) { private addPlatform(platformBehavior: gdjs.PlatformRuntimeBehavior) {
if (platformBehavior.currentRBushAABB) if (platformBehavior.currentRBushAABB)
platformBehavior.currentRBushAABB.updateAABBFromOwner(); platformBehavior.currentRBushAABB.updateAABBFromOwner();
else else
@@ -52,10 +53,39 @@ namespace gdjs {
* Remove a platform from the list of existing platforms. Be sure that the platform was * Remove a platform from the list of existing platforms. Be sure that the platform was
* added before. * added before.
*/ */
removePlatform(platformBehavior: gdjs.PlatformRuntimeBehavior) { private removePlatform(platformBehavior: gdjs.PlatformRuntimeBehavior) {
if (!platformBehavior.currentRBushAABB) {
return;
}
this._platformRBush.remove(platformBehavior.currentRBushAABB); this._platformRBush.remove(platformBehavior.currentRBushAABB);
} }
invalidatePlatformHitbox(platformBehavior: gdjs.PlatformRuntimeBehavior) {
this.movedPlatforms.push(platformBehavior);
}
onDestroy(platformBehavior: gdjs.PlatformRuntimeBehavior): void {
if (!platformBehavior.activated()) {
return;
}
if (platformBehavior.isAABBInvalidated()) {
const index = this.movedPlatforms.indexOf(platformBehavior);
this.movedPlatforms.splice(index, 1);
}
this.removePlatform(platformBehavior);
}
doStepPreEvents() {
for (const platformBehavior of this.movedPlatforms) {
this.removePlatform(platformBehavior);
if (platformBehavior.activated()) {
this.addPlatform(platformBehavior);
}
platformBehavior.onHitboxUpdatedInTree();
}
this.movedPlatforms.length = 0;
}
/** /**
* Returns all the platforms around the specified object. * Returns all the platforms around the specified object.
* @param maxMovementLength The maximum distance, in pixels, the object is going to do. * @param maxMovementLength The maximum distance, in pixels, the object is going to do.
@@ -75,21 +105,19 @@ namespace gdjs {
const searchArea: SearchArea = gdjs.staticObject( const searchArea: SearchArea = gdjs.staticObject(
PlatformObjectsManager.prototype.getAllPlatformsAround PlatformObjectsManager.prototype.getAllPlatformsAround
) as SearchArea; ) as SearchArea;
result.length = 0;
searchArea.minX = x - ow / 2 - maxMovementLength; searchArea.minX = x - ow / 2 - maxMovementLength;
searchArea.minY = y - oh / 2 - maxMovementLength; searchArea.minY = y - oh / 2 - maxMovementLength;
searchArea.maxX = x + ow / 2 + maxMovementLength; searchArea.maxX = x + ow / 2 + maxMovementLength;
searchArea.maxY = y + oh / 2 + maxMovementLength; searchArea.maxY = y + oh / 2 + maxMovementLength;
const nearbyPlatforms: gdjs.BehaviorRBushAABB< this._platformRBush.search(searchArea, result);
PlatformRuntimeBehavior
>[] = this._platformRBush.search(searchArea);
result.length = 0;
// Extra check on the platform owner AABB // Extra check on the platform owner AABB
// TODO: PR https://github.com/4ian/GDevelop/pull/2602 should remove the need // TODO: PR https://github.com/4ian/GDevelop/pull/2602 should remove the need
// for this extra check once merged. // for this extra check once merged.
for (let i = 0; i < nearbyPlatforms.length; i++) { let writtenIndex = 0;
const platform = nearbyPlatforms[i].behavior; for (let readIndex = 0; readIndex < result.length; readIndex++) {
const platform = result[readIndex];
const platformAABB = platform.owner.getAABB(); const platformAABB = platform.owner.getAABB();
const platformIsStillAround = const platformIsStillAround =
platformAABB.min[0] <= searchArea.maxX && platformAABB.min[0] <= searchArea.maxX &&
@@ -100,9 +128,11 @@ namespace gdjs {
// This can happen because platforms are not updated in the RBush before that // This can happen because platforms are not updated in the RBush before that
// characters movement are being processed. // characters movement are being processed.
if (platformIsStillAround) { if (platformIsStillAround) {
result.push(platform); result[writtenIndex] = platform;
writtenIndex++;
} }
} }
result.length = writtenIndex;
} }
} }
@@ -127,6 +157,7 @@ namespace gdjs {
> | null = null; > | null = null;
_manager: gdjs.PlatformObjectsManager; _manager: gdjs.PlatformObjectsManager;
_registeredInManager: boolean = false; _registeredInManager: boolean = false;
_isAABBInvalidated = false;
constructor( constructor(
instanceContainer: gdjs.RuntimeInstanceContainer, instanceContainer: gdjs.RuntimeInstanceContainer,
@@ -145,6 +176,10 @@ namespace gdjs {
this._canBeGrabbed = behaviorData.canBeGrabbed || false; this._canBeGrabbed = behaviorData.canBeGrabbed || false;
this._yGrabOffset = behaviorData.yGrabOffset || 0; this._yGrabOffset = behaviorData.yGrabOffset || 0;
this._manager = PlatformObjectsManager.getManager(instanceContainer); this._manager = PlatformObjectsManager.getManager(instanceContainer);
this.owner.registerHitboxChangedCallback((object) =>
this.onHitboxChanged()
);
this.onHitboxChanged();
} }
updateFromBehaviorData(oldBehaviorData, newBehaviorData): boolean { updateFromBehaviorData(oldBehaviorData, newBehaviorData): boolean {
@@ -160,10 +195,12 @@ namespace gdjs {
return true; return true;
} }
onDestroy() { onDestroy(): void {
if (this._manager && this._registeredInManager) { this._manager.onDestroy(this);
this._manager.removePlatform(this); }
}
usesLifecycleFunction(): boolean {
return false;
} }
doStepPreEvents(instanceContainer: gdjs.RuntimeInstanceContainer) { doStepPreEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {
@@ -176,54 +213,32 @@ namespace gdjs {
sceneManager = parentScene ? &ScenePlatformObjectsManager::managers[&scene] : NULL; sceneManager = parentScene ? &ScenePlatformObjectsManager::managers[&scene] : NULL;
registeredInManager = false; registeredInManager = false;
}*/ }*/
//Make sure the platform is or is not in the platforms manager.
if (!this.activated() && this._registeredInManager) {
this._manager.removePlatform(this);
this._registeredInManager = false;
} else {
if (this.activated() && !this._registeredInManager) {
this._manager.addPlatform(this);
this._registeredInManager = true;
}
}
//Track changes in size or position
if (
this._oldX !== this.owner.getX() ||
this._oldY !== this.owner.getY() ||
this._oldWidth !== this.owner.getWidth() ||
this._oldHeight !== this.owner.getHeight() ||
this._oldAngle !== this.owner.getAngle()
) {
if (this._registeredInManager) {
this._manager.removePlatform(this);
this._manager.addPlatform(this);
}
this._oldX = this.owner.getX();
this._oldY = this.owner.getY();
this._oldWidth = this.owner.getWidth();
this._oldHeight = this.owner.getHeight();
this._oldAngle = this.owner.getAngle();
}
} }
doStepPostEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {} doStepPostEvents(instanceContainer: gdjs.RuntimeInstanceContainer) {}
onActivate() { onActivate() {
if (this._registeredInManager) { this.onHitboxChanged();
return;
}
this._manager.addPlatform(this);
this._registeredInManager = true;
} }
onDeActivate() { onDeActivate() {
if (!this._registeredInManager) { this.onHitboxChanged();
}
onHitboxChanged() {
if (this._isAABBInvalidated || !this.owner.isAlive()) {
return; return;
} }
this._manager.removePlatform(this); this._isAABBInvalidated = true;
this._registeredInManager = false; this._manager.invalidatePlatformHitbox(this);
}
onHitboxUpdatedInTree() {
this._isAABBInvalidated = false;
}
isAABBInvalidated() {
return !this._isAABBInvalidated;
} }
changePlatformType(platformType: string) { changePlatformType(platformType: string) {

View File

@@ -1,8 +1,11 @@
describe('gdjs.PlatformerObjectRuntimeBehavior', function () { describe('gdjs.PlatformerObjectRuntimeBehavior', function () {
const epsilon = 1 / (2 << 16); const epsilon = 1 / (2 << 16);
describe('(falling)', function () { describe('(falling)', function () {
/** @type {gdjs.RuntimeScene} */
let runtimeScene; let runtimeScene;
/** @type {gdjs.RuntimeObject} */
let object; let object;
/** @type {gdjs.RuntimeObject} */
let platform; let platform;
beforeEach(function () { beforeEach(function () {
@@ -114,7 +117,7 @@ describe('gdjs.PlatformerObjectRuntimeBehavior', function () {
expect(object.getBehavior('auto1').isMoving()).to.be(false); expect(object.getBehavior('auto1').isMoving()).to.be(false);
// Remove the platform // Remove the platform
runtimeScene.markObjectForDeletion(platform); platform.deleteFromScene(runtimeScene);
runtimeScene.renderAndStep(1000 / 60); runtimeScene.renderAndStep(1000 / 60);
expect(object.getBehavior('auto1').isFalling()).to.be(true); expect(object.getBehavior('auto1').isFalling()).to.be(true);
expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(true); expect(object.getBehavior('auto1').isFallingWithoutJumping()).to.be(true);

View File

@@ -39,6 +39,10 @@ namespace gdjs {
); );
this._updateTileMap(); this._updateTileMap();
if (this.isNeedingLifecycleFunctions()) {
this.getLifecycleSleepState().wakeUp();
}
// *ALWAYS* call `this.onCreated()` at the very end of your object constructor. // *ALWAYS* call `this.onCreated()` at the very end of your object constructor.
this.onCreated(); this.onCreated();
} }
@@ -47,6 +51,11 @@ namespace gdjs {
return this._renderer.getRendererObject(); return this._renderer.getRendererObject();
} }
isNeedingLifecycleFunctions(): boolean {
// TODO Tile maps without animated tiles should return false.
return true;
}
update(instanceContainer: gdjs.RuntimeInstanceContainer): void { update(instanceContainer: gdjs.RuntimeInstanceContainer): void {
if (this._animationSpeedScale <= 0 || this._animationFps === 0) { if (this._animationSpeedScale <= 0 || this._animationFps === 0) {
return; return;

View File

@@ -670,6 +670,7 @@ void ExporterHelper::AddLibsInclude(bool pixiRenderers,
InsertUnique(includesFiles, "ResourceCache.js"); InsertUnique(includesFiles, "ResourceCache.js");
InsertUnique(includesFiles, "timemanager.js"); InsertUnique(includesFiles, "timemanager.js");
InsertUnique(includesFiles, "polygon.js"); InsertUnique(includesFiles, "polygon.js");
InsertUnique(includesFiles, "ObjectSleepState.js");
InsertUnique(includesFiles, "runtimeobject.js"); InsertUnique(includesFiles, "runtimeobject.js");
InsertUnique(includesFiles, "profiler.js"); InsertUnique(includesFiles, "profiler.js");
InsertUnique(includesFiles, "RuntimeInstanceContainer.js"); InsertUnique(includesFiles, "RuntimeInstanceContainer.js");

View File

@@ -60,6 +60,10 @@ namespace gdjs {
this._instanceContainer.loadFrom(objectData); this._instanceContainer.loadFrom(objectData);
this.getRenderer().reinitialize(this, parent); this.getRenderer().reinitialize(this, parent);
if (this.isNeedingLifecycleFunctions()) {
this.getLifecycleSleepState().wakeUp();
}
// The generated code calls onCreated at the constructor end // The generated code calls onCreated at the constructor end
// and onCreated calls its super implementation at its end. // and onCreated calls its super implementation at its end.
} }
@@ -120,6 +124,10 @@ namespace gdjs {
*/ */
onHotReloading(parent: gdjs.RuntimeInstanceContainer) {} onHotReloading(parent: gdjs.RuntimeInstanceContainer) {}
isNeedingLifecycleFunctions(): boolean {
return true;
}
// This is only to handle trigger once. // This is only to handle trigger once.
doStepPreEvents(parent: gdjs.RuntimeInstanceContainer) {} doStepPreEvents(parent: gdjs.RuntimeInstanceContainer) {}

View File

@@ -235,32 +235,6 @@ namespace gdjs {
return; return;
} }
/**
* Update the objects before launching the events.
*/
_updateObjectsPreEvents() {
const allInstancesList = this.getAdhocListOfAllInstances();
for (let i = 0, len = allInstancesList.length; i < len; ++i) {
const obj = allInstancesList[i];
const elapsedTime = obj.getElapsedTime();
if (!obj.hasNoForces()) {
const averageForce = obj.getAverageForce();
const elapsedTimeInSeconds = elapsedTime / 1000;
obj.setX(obj.getX() + averageForce.getX() * elapsedTimeInSeconds);
obj.setY(obj.getY() + averageForce.getY() * elapsedTimeInSeconds);
obj.update(this);
obj.updateForces(elapsedTimeInSeconds);
} else {
obj.update(this);
}
obj.updateTimers(elapsedTime);
obj.stepBehaviorsPreEvents(this);
}
// Some behaviors may have request objects to be deleted.
this._cacheOrClearRemovedInstances();
}
/** /**
* Get the renderer associated to the RuntimeScene. * Get the renderer associated to the RuntimeScene.
*/ */

View File

@@ -0,0 +1,119 @@
/*
* GDevelop JS Platform
* Copyright 2023-2023 Florian Rival (Florian.Rival@gmail.com). All rights reserved.
* This project is released under the MIT License.
*/
namespace gdjs {
export class ObjectSleepState {
private static readonly framesBeforeSleep = 60;
private _object: RuntimeObject;
private _isNeedingToBeAwake: () => boolean;
private _state: ObjectSleepState.State;
private _lastActivityFrameIndex: integer;
_onWakingUpCallbacks: Array<(object: RuntimeObject) => void> = [];
constructor(
object: RuntimeObject,
isNeedingToBeAwake: () => boolean,
initialSleepState: ObjectSleepState.State
) {
this._object = object;
this._isNeedingToBeAwake = isNeedingToBeAwake;
this._state = initialSleepState;
this._lastActivityFrameIndex = this._object
.getRuntimeScene()
.getFrameIndex();
}
_reinitialize(
initialSleepState: ObjectSleepState.State
) : void {
this._onWakingUpCallbacks.length = 0;
this._state = initialSleepState;
this._lastActivityFrameIndex = this._object
.getRuntimeScene()
.getFrameIndex();
}
_destroy(): void {
this._state = gdjs.ObjectSleepState.State.Destroyed;
this._onWakingUpCallbacks.length = 0;
}
canSleep(): boolean {
return (
this._state === gdjs.ObjectSleepState.State.CanSleepThisFrame ||
this._object.getRuntimeScene().getFrameIndex() -
this._lastActivityFrameIndex >=
ObjectSleepState.framesBeforeSleep
);
}
isAwake(): boolean {
return this._state === gdjs.ObjectSleepState.State.AWake ||
this._state === gdjs.ObjectSleepState.State.CanSleepThisFrame;
}
wakeUp() {
const object = this._object;
this._lastActivityFrameIndex = object.getRuntimeScene().getFrameIndex();
if (this.isAwake()) {
return;
}
this._state = gdjs.ObjectSleepState.State.AWake;
for (const onWakingUp of this._onWakingUpCallbacks) {
onWakingUp(object);
}
}
registerOnWakingUp(onWakingUp: (object: RuntimeObject) => void) {
this._onWakingUpCallbacks.push(onWakingUp);
}
tryToSleep(): void {
if (this._isNeedingToBeAwake()) {
this._lastActivityFrameIndex = this._object
.getRuntimeScene()
.getFrameIndex();
}
}
static updateAwakeObjects(
awakeObjects: Array<RuntimeObject>,
getSleepState: (object: RuntimeObject) => ObjectSleepState,
onFallenAsleep: (object: RuntimeObject) => void,
onWakingUp: (object: RuntimeObject) => void
) {
let writeIndex = 0;
for (let readIndex = 0; readIndex < awakeObjects.length; readIndex++) {
const object = awakeObjects[readIndex];
const sleepState = getSleepState(object);
sleepState.tryToSleep();
if (sleepState.canSleep() || !sleepState.isAwake()) {
if (sleepState.isAwake()) {
// Avoid onWakingUp to be called if some managers didn't have time
// to update their awake object list.
sleepState._onWakingUpCallbacks.length = 0;
}
sleepState._state = gdjs.ObjectSleepState.State.ASleep;
onFallenAsleep(object);
sleepState.registerOnWakingUp(onWakingUp);
} else {
awakeObjects[writeIndex] = object;
writeIndex++;
}
}
awakeObjects.length = writeIndex;
return awakeObjects;
}
}
export namespace ObjectSleepState {
export enum State {
ASleep,
CanSleepThisFrame,
AWake,
Destroyed,
}
}
}

View File

@@ -15,6 +15,8 @@ namespace gdjs {
/** Contains the instances living on the container */ /** Contains the instances living on the container */
_instances: Hashtable<RuntimeObject[]>; _instances: Hashtable<RuntimeObject[]>;
_activeInstances: Array<RuntimeObject> = [];
/** /**
* An array used to create a list of all instance when necessary. * An array used to create a list of all instance when necessary.
* @see gdjs.RuntimeInstanceContainer#_constructListOfAllInstances} * @see gdjs.RuntimeInstanceContainer#_constructListOfAllInstances}
@@ -485,13 +487,23 @@ namespace gdjs {
return this._allInstancesList; return this._allInstancesList;
} }
getActiveInstances(): gdjs.RuntimeObject[] {
gdjs.ObjectSleepState.updateAwakeObjects(
this._activeInstances,
(object) => object.getLifecycleSleepState(),
(object) => {},
(object) => this._activeInstances.push(object)
);
return this._activeInstances;
}
/** /**
* Update the objects before launching the events. * Update the objects before launching the events.
*/ */
_updateObjectsPreEvents() { _updateObjectsPreEvents() {
// It is *mandatory* to create and iterate on a external list of all objects, as the behaviors // It is *mandatory* to create and iterate on a external list of all objects, as the behaviors
// may delete the objects. // may delete the objects.
const allInstancesList = this.getAdhocListOfAllInstances(); const allInstancesList = this.getActiveInstances();
for (let i = 0, len = allInstancesList.length; i < len; ++i) { for (let i = 0, len = allInstancesList.length; i < len; ++i) {
const obj = allInstancesList[i]; const obj = allInstancesList[i];
const elapsedTime = obj.getElapsedTime(); const elapsedTime = obj.getElapsedTime();
@@ -506,7 +518,7 @@ namespace gdjs {
obj.update(this); obj.update(this);
} }
obj.updateTimers(elapsedTime); obj.updateTimers(elapsedTime);
allInstancesList[i].stepBehaviorsPreEvents(this); obj.stepBehaviorsPreEvents(this);
} }
// Some behaviors may have request objects to be deleted. // Some behaviors may have request objects to be deleted.
@@ -521,7 +533,7 @@ namespace gdjs {
// It is *mandatory* to create and iterate on a external list of all objects, as the behaviors // It is *mandatory* to create and iterate on a external list of all objects, as the behaviors
// may delete the objects. // may delete the objects.
const allInstancesList = this.getAdhocListOfAllInstances(); const allInstancesList = this.getActiveInstances();
for (let i = 0, len = allInstancesList.length; i < len; ++i) { for (let i = 0, len = allInstancesList.length; i < len; ++i) {
allInstancesList[i].stepBehaviorsPostEvents(this); allInstancesList[i].stepBehaviorsPostEvents(this);
} }
@@ -535,11 +547,20 @@ namespace gdjs {
* @param obj The object to be added. * @param obj The object to be added.
*/ */
addObject(obj: gdjs.RuntimeObject) { addObject(obj: gdjs.RuntimeObject) {
if (!this._instances.containsKey(obj.name)) { let instances = this._instances.get(obj.name);
this._instances.put(obj.name, []); if (!instances) {
instances = [];
this._instances.put(obj.name, instances);
}
instances.push(obj);
this._allInstancesList.push(obj);
if (obj.getLifecycleSleepState().isAwake()) {
this._activeInstances.push(obj);
} else {
obj
.getLifecycleSleepState()
.registerOnWakingUp((object) => this._activeInstances.push(object));
} }
this._instances.get(obj.name).push(obj);
this._allInstancesListIsUpToDate = false;
} }
/** /**
@@ -717,19 +738,22 @@ namespace gdjs {
* Update the objects positions according to their forces * Update the objects positions according to their forces
*/ */
updateObjectsForces(): void { updateObjectsForces(): void {
for (const name in this._instances.items) { for (
if (this._instances.items.hasOwnProperty(name)) { let i = 0, listLen = this._activeInstances.length;
const list = this._instances.items[name]; i < listLen;
for (let j = 0, listLen = list.length; j < listLen; ++j) { ++i
const obj = list[j]; ) {
if (!obj.hasNoForces()) { const object = this._activeInstances[i];
const averageForce = obj.getAverageForce(); if (!object.hasNoForces()) {
const elapsedTimeInSeconds = obj.getElapsedTime() / 1000; const averageForce = object.getAverageForce();
obj.setX(obj.getX() + averageForce.getX() * elapsedTimeInSeconds); const elapsedTimeInSeconds = object.getElapsedTime() / 1000;
obj.setY(obj.getY() + averageForce.getY() * elapsedTimeInSeconds); object.setX(
obj.updateForces(elapsedTimeInSeconds); object.getX() + averageForce.getX() * elapsedTimeInSeconds
} );
} object.setY(
object.getY() + averageForce.getY() * elapsedTimeInSeconds
);
object.updateForces(elapsedTimeInSeconds);
} }
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -16,18 +16,18 @@ namespace gdjs {
minY: float = 0; minY: float = 0;
maxX: float = 0; maxX: float = 0;
maxY: float = 0; maxY: float = 0;
behavior: T; source: T;
constructor(behavior: T) { constructor(behavior: T) {
this.behavior = behavior; this.source = behavior;
this.updateAABBFromOwner(); this.updateAABBFromOwner();
} }
updateAABBFromOwner() { updateAABBFromOwner() {
this.minX = this.behavior.owner.getAABB().min[0]; this.minX = this.source.owner.getAABB().min[0];
this.minY = this.behavior.owner.getAABB().min[1]; this.minY = this.source.owner.getAABB().min[1];
this.maxX = this.behavior.owner.getAABB().max[0]; this.maxX = this.source.owner.getAABB().max[0];
this.maxY = this.behavior.owner.getAABB().max[1]; this.maxY = this.source.owner.getAABB().max[1];
} }
} }

View File

@@ -147,6 +147,8 @@ namespace gdjs {
return true; return true;
}; };
type RuntimeObjectCallback = (object: gdjs.RuntimeObject) => void;
/** /**
* RuntimeObject represents an object being used on a RuntimeScene. * RuntimeObject represents an object being used on a RuntimeScene.
* *
@@ -164,9 +166,12 @@ namespace gdjs {
layer: string = ''; layer: string = '';
protected _nameId: integer; protected _nameId: integer;
protected _livingOnScene: boolean = true; protected _livingOnScene: boolean = true;
protected _lifecycleSleepState: ObjectSleepState;
readonly id: integer; readonly id: integer;
private destroyCallbacks = new Set<() => void>(); private destroyCallbacks = new Set<() => void>();
// HitboxChanges happen a lot, an Array is faster to iterate.
private hitBoxChangedCallbacks: Array<RuntimeObjectCallback> = [];
_runtimeScene: gdjs.RuntimeInstanceContainer; _runtimeScene: gdjs.RuntimeInstanceContainer;
/** /**
@@ -181,12 +186,15 @@ namespace gdjs {
* not "thread safe" or "re-entrant algorithm" safe. * not "thread safe" or "re-entrant algorithm" safe.
*/ */
pick: boolean = false; pick: boolean = false;
pickingId: integer = 0;
//Hit boxes: //Hit boxes:
protected _defaultHitBoxes: gdjs.Polygon[] = []; protected _defaultHitBoxes: gdjs.Polygon[] = [];
protected hitBoxes: gdjs.Polygon[]; protected hitBoxes: gdjs.Polygon[];
protected hitBoxesDirty: boolean = true; protected hitBoxesDirty: boolean = true;
// TODO use a different AABB for collision mask and rendered image.
protected aabb: AABB = { min: [0, 0], max: [0, 0] }; protected aabb: AABB = { min: [0, 0], max: [0, 0] };
protected _isIncludedInParentCollisionMask = true; protected _isIncludedInParentCollisionMask = true;
//Variables: //Variables:
@@ -212,6 +220,7 @@ namespace gdjs {
* are never used. * are never used.
*/ */
protected _behaviors: gdjs.RuntimeBehavior[] = []; protected _behaviors: gdjs.RuntimeBehavior[] = [];
protected _activeBehaviors: gdjs.RuntimeBehavior[] = [];
/** /**
* Contains the behaviors of the object by name. * Contains the behaviors of the object by name.
* *
@@ -229,10 +238,11 @@ namespace gdjs {
instanceContainer: gdjs.RuntimeInstanceContainer, instanceContainer: gdjs.RuntimeInstanceContainer,
objectData: ObjectData & any objectData: ObjectData & any
) { ) {
const scene = instanceContainer.getScene();
this.name = objectData.name || ''; this.name = objectData.name || '';
this.type = objectData.type || ''; this.type = objectData.type || '';
this._nameId = RuntimeObject.getNameIdentifier(this.name); this._nameId = RuntimeObject.getNameIdentifier(this.name);
this.id = instanceContainer.getScene().createNewUniqueId(); this.id = scene.createNewUniqueId();
this._runtimeScene = instanceContainer; this._runtimeScene = instanceContainer;
this._defaultHitBoxes.push(gdjs.Polygon.createRectangle(0, 0)); this._defaultHitBoxes.push(gdjs.Polygon.createRectangle(0, 0));
this.hitBoxes = this._defaultHitBoxes; this.hitBoxes = this._defaultHitBoxes;
@@ -241,8 +251,14 @@ namespace gdjs {
); );
this._totalForce = new gdjs.Force(0, 0, 0); this._totalForce = new gdjs.Force(0, 0, 0);
this._behaviorsTable = new Hashtable(); this._behaviorsTable = new Hashtable();
this._timers = new Hashtable();
this._lifecycleSleepState = new gdjs.ObjectSleepState(
this,
() => this.isNeedingLifecycleFunctions(),
gdjs.ObjectSleepState.State.ASleep
);
for (let i = 0; i < objectData.effects.length; ++i) { for (let i = 0; i < objectData.effects.length; ++i) {
this._runtimeScene scene
.getGame() .getGame()
.getEffectsManager() .getEffectsManager()
.initializeEffect(objectData.effects[i], this._rendererEffects, this); .initializeEffect(objectData.effects[i], this._rendererEffects, this);
@@ -253,12 +269,13 @@ namespace gdjs {
const autoData = objectData.behaviors[i]; const autoData = objectData.behaviors[i];
const Ctor = gdjs.getBehaviorConstructor(autoData.type); const Ctor = gdjs.getBehaviorConstructor(autoData.type);
const behavior = new Ctor(instanceContainer, autoData, this); const behavior = new Ctor(instanceContainer, autoData, this);
this._behaviors.push(behavior);
if (behavior.usesLifecycleFunction()) { if (behavior.usesLifecycleFunction()) {
this._behaviors.push(behavior); this._activeBehaviors.push(behavior);
this._lifecycleSleepState.wakeUp();
} }
this._behaviorsTable.put(autoData.name, behavior); this._behaviorsTable.put(autoData.name, behavior);
} }
this._timers = new Hashtable();
} }
//Common members functions related to the object and its runtimeScene : //Common members functions related to the object and its runtimeScene :
@@ -322,10 +339,12 @@ namespace gdjs {
this.aabb.max[1] = 0; this.aabb.max[1] = 0;
this._variables = new gdjs.VariablesContainer(objectData.variables); this._variables = new gdjs.VariablesContainer(objectData.variables);
this.clearForces(); this.clearForces();
this._lifecycleSleepState._reinitialize(gdjs.ObjectSleepState.State.ASleep);
// Reinitialize behaviors. // Reinitialize behaviors.
this._behaviorsTable.clear(); this._behaviorsTable.clear();
const behaviorsDataCount = objectData.behaviors.length; const behaviorsDataCount = objectData.behaviors.length;
let behaviorsCount = 0;
let behaviorsUsingLifecycleFunctionCount = 0; let behaviorsUsingLifecycleFunctionCount = 0;
for ( for (
let behaviorDataIndex = 0; let behaviorDataIndex = 0;
@@ -336,17 +355,29 @@ namespace gdjs {
const Ctor = gdjs.getBehaviorConstructor(behaviorData.type); const Ctor = gdjs.getBehaviorConstructor(behaviorData.type);
// TODO: Add support for behavior recycling with a `reinitialize` method. // TODO: Add support for behavior recycling with a `reinitialize` method.
const behavior = new Ctor(runtimeScene, behaviorData, this); const behavior = new Ctor(runtimeScene, behaviorData, this);
if (behaviorsCount < this._behaviors.length) {
this._behaviors[behaviorsCount] = behavior;
} else {
this._behaviors.push(behavior);
}
behaviorsCount++;
if (behavior.usesLifecycleFunction()) { if (behavior.usesLifecycleFunction()) {
if (behaviorsUsingLifecycleFunctionCount < this._behaviors.length) { this._lifecycleSleepState.wakeUp();
this._behaviors[behaviorsUsingLifecycleFunctionCount] = behavior; if (
behaviorsUsingLifecycleFunctionCount < this._activeBehaviors.length
) {
this._activeBehaviors[
behaviorsUsingLifecycleFunctionCount
] = behavior;
} else { } else {
this._behaviors.push(behavior); this._activeBehaviors.push(behavior);
} }
behaviorsUsingLifecycleFunctionCount++; behaviorsUsingLifecycleFunctionCount++;
} }
this._behaviorsTable.put(behaviorData.name, behavior); this._behaviorsTable.put(behaviorData.name, behavior);
} }
this._behaviors.length = behaviorsUsingLifecycleFunctionCount; this._behaviors.length = behaviorsCount;
this._activeBehaviors.length = behaviorsUsingLifecycleFunctionCount;
// Reinitialize effects. // Reinitialize effects.
for (let i = 0; i < objectData.effects.length; ++i) { for (let i = 0; i < objectData.effects.length; ++i) {
@@ -439,6 +470,22 @@ namespace gdjs {
return false; return false;
} }
isNeedingLifecycleFunctions(): boolean {
return (
this._activeBehaviors.length > 0 ||
!this.hasNoForces() ||
!!this._timers.firstKey()
);
}
getLifecycleSleepState(): ObjectSleepState {
return this._lifecycleSleepState;
}
isAlive(): boolean {
return this._livingOnScene;
}
/** /**
* Remove an object from a scene. * Remove an object from a scene.
* *
@@ -449,6 +496,7 @@ namespace gdjs {
if (this._livingOnScene) { if (this._livingOnScene) {
instanceContainer.markObjectForDeletion(this); instanceContainer.markObjectForDeletion(this);
this._livingOnScene = false; this._livingOnScene = false;
this._lifecycleSleepState._destroy();
} }
} }
@@ -486,6 +534,30 @@ namespace gdjs {
onDestroyed(): void {} onDestroyed(): void {}
registerHitboxChangedCallback(callback: RuntimeObjectCallback) {
if (this.hitBoxChangedCallbacks.includes(callback)) {
return;
}
this.hitBoxChangedCallbacks.push(callback);
}
/**
* Send a signal that the object hitboxes are no longer up to date.
*
* The signal is propagated to parents so
* {@link gdjs.RuntimeObject.hitBoxesDirty} should never be modified
* directly.
*/
invalidateHitboxes(): void {
// TODO EBO Check that no community extension set hitBoxesDirty to true
// directly.
this.hitBoxesDirty = true;
this._runtimeScene.onChildrenLocationChanged();
for (const callback of this.hitBoxChangedCallbacks) {
callback(this);
}
}
/** /**
* Called whenever the scene owning the object is paused. * Called whenever the scene owning the object is paused.
* This should *not* impact objects, but some may need to inform their renderer. * This should *not* impact objects, but some may need to inform their renderer.
@@ -570,20 +642,6 @@ namespace gdjs {
this.invalidateHitboxes(); this.invalidateHitboxes();
} }
/**
* Send a signal that the object hitboxes are no longer up to date.
*
* The signal is propagated to parents so
* {@link gdjs.RuntimeObject.hitBoxesDirty} should never be modified
* directly.
*/
invalidateHitboxes(): void {
// TODO EBO Check that no community extension set hitBoxesDirty to true
// directly.
this.hitBoxesDirty = true;
this._runtimeScene.onChildrenLocationChanged();
}
/** /**
* Get the X position of the object. * Get the X position of the object.
* *
@@ -1363,6 +1421,7 @@ namespace gdjs {
// (or the 1st instant force). // (or the 1st instant force).
this._instantForces.push(this._getRecycledForce(x, y, multiplier)); this._instantForces.push(this._getRecycledForce(x, y, multiplier));
} }
this._lifecycleSleepState.wakeUp();
} }
/** /**
@@ -1781,8 +1840,8 @@ namespace gdjs {
stepBehaviorsPreEvents( stepBehaviorsPreEvents(
instanceContainer: gdjs.RuntimeInstanceContainer instanceContainer: gdjs.RuntimeInstanceContainer
): void { ): void {
for (let i = 0, len = this._behaviors.length; i < len; ++i) { for (let i = 0, len = this._activeBehaviors.length; i < len; ++i) {
this._behaviors[i].stepPreEvents(instanceContainer); this._activeBehaviors[i].stepPreEvents(instanceContainer);
} }
} }
@@ -1792,8 +1851,8 @@ namespace gdjs {
stepBehaviorsPostEvents( stepBehaviorsPostEvents(
instanceContainer: gdjs.RuntimeInstanceContainer instanceContainer: gdjs.RuntimeInstanceContainer
): void { ): void {
for (let i = 0, len = this._behaviors.length; i < len; ++i) { for (let i = 0, len = this._activeBehaviors.length; i < len; ++i) {
this._behaviors[i].stepPostEvents(instanceContainer); this._activeBehaviors[i].stepPostEvents(instanceContainer);
} }
} }
@@ -1870,9 +1929,17 @@ namespace gdjs {
return false; return false;
} }
behavior.onDestroy(); behavior.onDestroy();
const behaviorIndex = this._behaviors.indexOf(behavior); {
if (behaviorIndex !== -1) { const behaviorIndex = this._behaviors.indexOf(behavior);
this._behaviors.splice(behaviorIndex, 1); if (behaviorIndex !== -1) {
this._behaviors.splice(behaviorIndex, 1);
}
}
{
const behaviorIndex = this._activeBehaviors.indexOf(behavior);
if (behaviorIndex !== -1) {
this._activeBehaviors.splice(behaviorIndex, 1);
}
} }
this._behaviorsTable.remove(name); this._behaviorsTable.remove(name);
return true; return true;
@@ -1926,6 +1993,7 @@ namespace gdjs {
timerElapsedTime(timerName: string, timeInSeconds: float): boolean { timerElapsedTime(timerName: string, timeInSeconds: float): boolean {
if (!this._timers.containsKey(timerName)) { if (!this._timers.containsKey(timerName)) {
this._timers.put(timerName, new gdjs.Timer(timerName)); this._timers.put(timerName, new gdjs.Timer(timerName));
this._lifecycleSleepState.wakeUp();
return false; return false;
} }
return this.getTimerElapsedTimeInSeconds(timerName) >= timeInSeconds; return this.getTimerElapsedTimeInSeconds(timerName) >= timeInSeconds;
@@ -1950,6 +2018,7 @@ namespace gdjs {
resetTimer(timerName: string): void { resetTimer(timerName: string): void {
if (!this._timers.containsKey(timerName)) { if (!this._timers.containsKey(timerName)) {
this._timers.put(timerName, new gdjs.Timer(timerName)); this._timers.put(timerName, new gdjs.Timer(timerName));
this._lifecycleSleepState.wakeUp();
} }
this._timers.get(timerName).reset(); this._timers.get(timerName).reset();
} }
@@ -1961,6 +2030,7 @@ namespace gdjs {
pauseTimer(timerName: string): void { pauseTimer(timerName: string): void {
if (!this._timers.containsKey(timerName)) { if (!this._timers.containsKey(timerName)) {
this._timers.put(timerName, new gdjs.Timer(timerName)); this._timers.put(timerName, new gdjs.Timer(timerName));
this._lifecycleSleepState.wakeUp();
} }
this._timers.get(timerName).setPaused(true); this._timers.get(timerName).setPaused(true);
} }
@@ -1972,6 +2042,7 @@ namespace gdjs {
unpauseTimer(timerName: string): void { unpauseTimer(timerName: string): void {
if (!this._timers.containsKey(timerName)) { if (!this._timers.containsKey(timerName)) {
this._timers.put(timerName, new gdjs.Timer(timerName)); this._timers.put(timerName, new gdjs.Timer(timerName));
this._lifecycleSleepState.wakeUp();
} }
this._timers.get(timerName).setPaused(false); this._timers.get(timerName).setPaused(false);
} }

View File

@@ -42,6 +42,8 @@ namespace gdjs {
// Set to `new gdjs.Profiler()` to have profiling done on the scene. // Set to `new gdjs.Profiler()` to have profiling done on the scene.
_onProfilerStopped: null | ((oldProfiler: gdjs.Profiler) => void) = null; _onProfilerStopped: null | ((oldProfiler: gdjs.Profiler) => void) = null;
private _frameIndex: integer = 0;
_cachedGameResolutionWidth: integer; _cachedGameResolutionWidth: integer;
_cachedGameResolutionHeight: integer; _cachedGameResolutionHeight: integer;
@@ -427,6 +429,7 @@ namespace gdjs {
if (this._profiler) { if (this._profiler) {
this._profiler.endFrame(); this._profiler.endFrame();
} }
this._frameIndex++;
return !!this.getRequestedChange(); return !!this.getRequestedChange();
} }
@@ -734,6 +737,10 @@ namespace gdjs {
sceneJustResumed(): boolean { sceneJustResumed(): boolean {
return this._isJustResumed; return this._isJustResumed;
} }
getFrameIndex(): integer {
return this._frameIndex;
}
} }
//The flags to describe the change request by a scene: //The flags to describe the change request by a scene:

View File

@@ -348,6 +348,10 @@ namespace gdjs {
); );
this._updateAnimationFrame(); this._updateAnimationFrame();
if (this.isNeedingLifecycleFunctions()) {
this.getLifecycleSleepState().wakeUp();
}
// *ALWAYS* call `this.onCreated()` at the very end of your object constructor. // *ALWAYS* call `this.onCreated()` at the very end of your object constructor.
this.onCreated(); this.onCreated();
} }
@@ -452,6 +456,24 @@ namespace gdjs {
} }
} }
isNeedingLifecycleFunctions(): boolean {
if (super.isNeedingLifecycleFunctions()) {
return true;
}
if (
this.isAnimationPaused() ||
this.hasAnimationEnded() ||
this._currentAnimation >= this._animations.length
) {
return false;
}
const animation = this._animations[this._currentAnimation];
if (this._currentDirection > animation.directions.length) {
return false;
}
return animation.directions[this._currentDirection].frames.length > 1;
}
/** /**
* Update the current frame of the object according to the elapsed time on the scene. * Update the current frame of the object according to the elapsed time on the scene.
*/ */
@@ -617,6 +639,7 @@ namespace gdjs {
this._currentAnimation = newAnimation; this._currentAnimation = newAnimation;
this._currentFrame = 0; this._currentFrame = 0;
this._animationElapsedTime = 0; this._animationElapsedTime = 0;
this.getLifecycleSleepState().wakeUp();
//TODO: This may be unnecessary. //TODO: This may be unnecessary.
this._renderer.update(); this._renderer.update();
@@ -868,6 +891,7 @@ namespace gdjs {
resumeAnimation(): void { resumeAnimation(): void {
this._animationPaused = false; this._animationPaused = false;
this.getLifecycleSleepState().wakeUp();
} }
getAnimationSpeedScale() { getAnimationSpeedScale() {

19
GDJS/Runtime/types/rbush.d.ts vendored Normal file
View File

@@ -0,0 +1,19 @@
type SearchArea = { minX: float; minY: float; maxX: float; maxY: float };
type SearchedItem<T> = {
source: T;
minX: float;
minY: float;
maxX: float;
maxY: float;
};
declare class RBush<T> {
constructor(maxEntries?: number);
search(bbox: SearchArea, result?: Array<T>): Array<T>;
insert(item: SearchedItem<T>): RBush<T>;
clear(): RBush<T>;
remove(
item: SearchedItem<T>,
equalsFn?: (item: SearchedItem<T>, otherItem: SearchedItem<T>) => boolean
): RBush<T>;
}

View File

@@ -53,6 +53,7 @@ module.exports = function (config) {
'./newIDE/app/resources/GDJS/Runtime/ResourceCache.js', './newIDE/app/resources/GDJS/Runtime/ResourceCache.js',
'./newIDE/app/resources/GDJS/Runtime/timemanager.js', './newIDE/app/resources/GDJS/Runtime/timemanager.js',
'./newIDE/app/resources/GDJS/Runtime/polygon.js', './newIDE/app/resources/GDJS/Runtime/polygon.js',
'./newIDE/app/resources/GDJS/Runtime/ObjectSleepState.js',
'./newIDE/app/resources/GDJS/Runtime/runtimeobject.js', './newIDE/app/resources/GDJS/Runtime/runtimeobject.js',
'./newIDE/app/resources/GDJS/Runtime/RuntimeInstanceContainer.js', './newIDE/app/resources/GDJS/Runtime/RuntimeInstanceContainer.js',
'./newIDE/app/resources/GDJS/Runtime/runtimescene.js', './newIDE/app/resources/GDJS/Runtime/runtimescene.js',

View File

@@ -0,0 +1,78 @@
// @ts-check
describe.only('gdjs.RuntimeScene active objects tests', () => {
const spriteConfiguration = {
name: 'MySprite',
type: 'Sprite',
behaviors: [],
effects: [],
animations: [
{
name: 'animation',
directions: [
{
sprites: [
{
originPoint: { x: 0, y: 0 },
centerPoint: { x: 0, y: 0 },
points: [
{ name: 'Center', x: 0, y: 0 },
{ name: 'Origin', x: 0, y: 0 },
],
hasCustomCollisionMask: false,
},
],
},
],
},
],
}
it('can recycle a sprite without duplication in the active objects', () => {
const game = gdjs.getPixiRuntimeGame();
const scene = new gdjs.TestRuntimeScene(game);
scene.registerObject(spriteConfiguration);
let object = scene.createObject('MySprite');
object.resetTimer("MyTimer");
scene.renderAndStep(1000 / 60);
expect(scene._activeInstances.length).to.be(1);
object.deleteFromScene(scene);
scene.renderAndStep(1000 / 60);
expect(scene._activeInstances.length).to.be(0);
// The object is not destroyed because it is recycled.
expect(object.getLifecycleSleepState()._onWakingUpCallbacks.length).to.be(1);
let object2 = scene.createObject('MySprite');
expect(object === object2).to.be(true);
object.resetTimer("MyTimer");
expect(scene._activeInstances.length).to.be(1);
expect(object.getLifecycleSleepState()._onWakingUpCallbacks.length).to.be(1);
});
it('can keep an object awake to handle its timer', () => {
const game = gdjs.getPixiRuntimeGame();
const scene = new gdjs.TestRuntimeScene(game);
scene.registerObject(spriteConfiguration);
let object = scene.createObject('MySprite');
object.resetTimer("MyTimer");
for (let index = 0; index < 60; index++) {
scene.renderAndStep(1000 / 60);
expect(scene._activeInstances.length).to.be(1);
}
object.removeTimer("MyTimer");
for (let index = 0; index < 59; index++) {
scene.renderAndStep(1000 / 60);
expect(scene._activeInstances.length).to.be(1);
}
scene.renderAndStep(1000 / 60);
expect(scene._activeInstances.length).to.be(0);
});
});