-
Notifications
You must be signed in to change notification settings - Fork 863
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
13 changed files
with
1,001 additions
and
665 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
namespace gdjs { | ||
/** | ||
* Allow to do spacial searches on objects as fast as possible. | ||
* | ||
* Objects are put in an R-Tree only if they didn't move recently to avoid to | ||
* update the R-Tree too often. | ||
*/ | ||
export class ObjectManager { | ||
private _allInstances: Array<RuntimeObject> = []; | ||
private _awakeInstances: Array<RuntimeObject> = []; | ||
private _rbush: RBush<RuntimeObject>; | ||
|
||
constructor() { | ||
this._rbush = new RBush<RuntimeObject>(); | ||
} | ||
|
||
_destroy(): void { | ||
this._allInstances = []; | ||
this._awakeInstances = []; | ||
this._rbush.clear(); | ||
} | ||
|
||
search( | ||
searchArea: SearchArea, | ||
results: Array<RuntimeObject> | ||
): Array<RuntimeObject> { | ||
let instances = this._allInstances; | ||
if (instances.length >= 8) { | ||
this._rbush.search(searchArea, results); | ||
instances = this._awakeInstances; | ||
} | ||
for (const instance of instances) { | ||
// TODO Allow to use getAABB to optimize collision conditions | ||
const aabb = instance.getVisibilityAABB(); | ||
if ( | ||
!aabb || | ||
(aabb.min[0] <= searchArea.maxX && | ||
aabb.max[0] >= searchArea.minX && | ||
aabb.min[1] <= searchArea.maxY && | ||
aabb.max[1] >= searchArea.minY) | ||
) { | ||
results.push(instance); | ||
} | ||
} | ||
return results; | ||
} | ||
|
||
private _onWakingUp(object: RuntimeObject): void { | ||
this._rbush.remove(object._rtreeAABB); | ||
this._awakeInstances.push(object); | ||
} | ||
|
||
private _onFallenAsleep(object: RuntimeObject): void { | ||
// TODO Allow to use getAABB to optimize collision conditions | ||
const objectAABB = object.getVisibilityAABB(); | ||
if (!objectAABB) { | ||
return; | ||
} | ||
this._rbush.remove(object._rtreeAABB); | ||
object._rtreeAABB.minX = objectAABB.min[0]; | ||
object._rtreeAABB.minY = objectAABB.min[1]; | ||
object._rtreeAABB.maxX = objectAABB.max[0]; | ||
object._rtreeAABB.maxY = objectAABB.max[1]; | ||
this._rbush.insert(object._rtreeAABB); | ||
} | ||
|
||
updateAwakeObjects(): void { | ||
gdjs.ObjectSleepState.updateAwakeObjects( | ||
this._awakeInstances, | ||
(object) => object.getSpatialSearchSleepState(), | ||
(object) => this._onFallenAsleep(object), | ||
(object) => this._onWakingUp(object) | ||
); | ||
} | ||
|
||
getAllInstances(): Array<RuntimeObject> { | ||
return this._allInstances; | ||
} | ||
|
||
getAwakeInstances(): Array<RuntimeObject> { | ||
return this._awakeInstances; | ||
} | ||
|
||
/** | ||
* Add an object to the instances living in the container. | ||
* @param obj The object to be added. | ||
*/ | ||
addObject(object: gdjs.RuntimeObject): void { | ||
this._allInstances.push(object); | ||
this._awakeInstances.push(object); | ||
} | ||
|
||
/** | ||
* Must be called whenever an object must be removed from the container. | ||
* @param object The object to be removed. | ||
*/ | ||
deleteObject(object: gdjs.RuntimeObject): boolean { | ||
const objId = object.id; | ||
let isObjectDeleted = false; | ||
for (let i = 0, len = this._allInstances.length; i < len; ++i) { | ||
if (this._allInstances[i].id == objId) { | ||
this._allInstances.splice(i, 1); | ||
isObjectDeleted = true; | ||
break; | ||
} | ||
} | ||
// TODO Maybe the state could be used but it would be more prone to errors. | ||
let isAwake = false; | ||
for (let i = 0, len = this._awakeInstances.length; i < len; ++i) { | ||
if (this._awakeInstances[i].id == objId) { | ||
this._awakeInstances.splice(i, 1); | ||
isAwake = true; | ||
break; | ||
} | ||
} | ||
if (!isAwake) { | ||
this._rbush.remove(object._rtreeAABB); | ||
} | ||
return isObjectDeleted; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
/* | ||
* GDevelop JS Platform | ||
* Copyright 2023-2023 Florian Rival ([email protected]). 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; | ||
private _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(); | ||
} | ||
|
||
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.ASleep; | ||
} | ||
|
||
_forceToSleep(): void { | ||
if (!this.isAwake()) { | ||
return; | ||
} | ||
this._lastActivityFrameIndex = Number.NEGATIVE_INFINITY; | ||
} | ||
|
||
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._lastActivityFrameIndex !== Number.NEGATIVE_INFINITY && | ||
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._onWakingUpCallbacks.push(onWakingUp); | ||
} else { | ||
awakeObjects[writeIndex] = object; | ||
writeIndex++; | ||
} | ||
} | ||
awakeObjects.length = writeIndex; | ||
return awakeObjects; | ||
} | ||
} | ||
|
||
export namespace ObjectSleepState { | ||
export enum State { | ||
ASleep, | ||
CanSleepThisFrame, | ||
AWake, | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.