-
Notifications
You must be signed in to change notification settings - Fork 1
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
1 parent
ac3c380
commit 0462888
Showing
101 changed files
with
2,968 additions
and
657 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
# Pooling | ||
|
||
JamJar supports object pooling ([see the pooling documentation for an overview](../../documentation/pooling)), this | ||
page will outline how pooling works in the engine, alongside how pooling can be added to another object. | ||
|
||
Pooling in JamJar works with the following process: | ||
|
||
1. Pool is initialized, set to a maximum size and filled with empty/blank objects. | ||
2. An object is requested from the pool. | ||
|
||
- If the pool is not initialized the normal object constructor is used and a new instance is created. | ||
|
||
- If the pool does not have objects available the normal object constructor is used and a new instance is created. | ||
|
||
- If the pool has objects available, it shifts the first item out of the pool, it calls that pooled objects | ||
`Recycle` method, providing the required arguments. The object is marked as not in the pool anymore. | ||
|
||
3. Object is used. | ||
4. The object is freed. | ||
|
||
- If the object is marked as already in the pool this does nothing. | ||
|
||
- If the pool is not initialized this does nothing. | ||
|
||
- If the pool is already full (at maximum size) this does nothing. | ||
|
||
- If the pool has room the object is pushed into the pool and it is marked as in the pool to avoid duplicate | ||
entries. | ||
|
||
This approach means that the pool is a dynamic size, with the only limitation being the maximum size of the pool. This | ||
strategy also means that any object can be pushed into the pool and reused, it does not have to be provisioned from | ||
the pool in the first place. This allows a hybrid approach, with pooling used where available, and a fallback to | ||
standard garbage collection if the pool is full or pooling is disabled. | ||
|
||
## Making an Object Poolable | ||
|
||
An object can be made poolable by extending the [Pooled] base class, which provides helper functions for object pooling, | ||
and also implementing the [IPoolable] interface to add in `Recycle` and `Free` methods. The `Recycle` method should | ||
take in the same arguments as the constructor, allowing objects to be reused; for example in the [Vector] object it is: | ||
|
||
```typescript | ||
public Recycle(x: number, y: number): Vector { | ||
this.x = x; | ||
this.y = y; | ||
return this; | ||
} | ||
``` | ||
|
||
It is also good practice to provide some static helper methods for initialising the object pool, creating new objects | ||
from the pool and freeing objects into the pool, for example the [Vector] class provides `New`, `Free`, and `Init`: | ||
|
||
```typescript | ||
private static POOL_KEY = "jamjar_vector"; | ||
|
||
public static New(x: number, y: number): Vector { | ||
return this.new<Vector>(Vector.POOL_KEY, Vector, x, y); | ||
} | ||
|
||
public static Free(obj: Vector): void { | ||
this.free(Vector.POOL_KEY, obj); | ||
} | ||
|
||
public static Init(size: number): void { | ||
this.init(Vector.POOL_KEY, () => { | ||
return Vector.New(0, 0); | ||
}, size); | ||
} | ||
``` | ||
|
||
These static methods use the underlying methods provided by the [Pooled] base class. The `POOL_KEY` specifies the | ||
unique key that should be used when specifying the pool to use. | ||
|
||
## Object with Poolable Constituents/Subparts | ||
|
||
If an object contains constituent parts that are poolable should provide a `Free` method, if the object itself is not | ||
poolable it should implement the [IFreeable] interface to specify this. For example, [Component] objects are not | ||
themselves poolable, but can contain pooled data, so it implements [IFreeable] to allow any pooled constituent objects | ||
to be freed: | ||
|
||
```typescript | ||
abstract class Component implements IFreeable { | ||
... | ||
public Free(): void { | ||
return; | ||
} | ||
} | ||
``` | ||
|
||
The [Transform] class extends the [Component] class, it contains 3 poolable pieces of data (`position`, `scale` and | ||
`previous`) so it overrides the `Free` method and calls each of these poolable objects' `Free` methods: | ||
|
||
```typescript | ||
class Transform extends Component { | ||
... | ||
public Free(): void { | ||
this.position.Free(); | ||
this.previous.Free(); | ||
this.scale.Free(); | ||
} | ||
} | ||
|
||
``` | ||
|
||
[Pooled]: ../../reference/classes/pooled | ||
[IPoolable]: ../../reference/interfaces/ipoolable | ||
[IFreeable]: ../../reference/interfaces/ifreeable | ||
[Vector]: ../../reference/classes/vector | ||
[Component]: ../../reference/classes/component | ||
[Transform]: ../../reference/classes/transform |
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,101 @@ | ||
# Pooling | ||
|
||
Object pooling allows for reuse of objects, keeping free objects in a centralised *pool* of objects. Instead of | ||
creating a new instance of the object and requesting memory for it, instead the existing object is overwritten in place | ||
and used instead. | ||
|
||
## Benefits | ||
|
||
Object pooling is intended to reduce/avoid stuttering caused by garbage collection between frames. This can be a | ||
problem if there are many object being created and destroyed per frame, object pooling addresses this by using global | ||
static memory that allows objects to be created once and reused, rather than created, destroyed, and then created. | ||
|
||
## Drawbacks | ||
|
||
Object pooling can have some drawbacks: | ||
|
||
- If the initialised object pool is large the game will use a big chunk of memory, this could be inefficient. | ||
- Object pooling can come with a performance (CPU) hit, as new object creation requires more logic. | ||
|
||
## Considerations | ||
|
||
JamJar is designed to allow the optional use of object pools, they can be disabled and enabled easily - by choosing to | ||
initialise the pools or not. If an object pool is not initialised then object pooling is skipped and normal instance | ||
creation is used. | ||
|
||
With the design of the JamJar pooling architecture if some logic forgets to free the object back into the object pool | ||
this is not likely to cause a memory leak, the object will simply be garbage collected as normal. Since this fallback | ||
uses normal garbage collection it is still recommended to free poolable objects when finished with them. | ||
|
||
## Best Practices | ||
|
||
When designing systems/game logic to support object pooling make sure any objects that are no longer used are freed up | ||
and released into the object pool. This freeing of objects back into the pool is essential to allow object pooling to | ||
work without using up the entire object pool wastefully. | ||
|
||
When using pooled data inside a [Component] the pooled data can easily be released upon component removal/entity | ||
destruction by implementing and overriding the `Free` method to ensure all pooled data is freed. For example in the | ||
[Transform] component there are three poolable [Vector] data points (`position`, `previous`, and `scale`) which are | ||
freed up: | ||
|
||
```typescript | ||
public Free(): void { | ||
this.position.Free(); | ||
this.previous.Free(); | ||
this.scale.Free(); | ||
} | ||
``` | ||
|
||
When requesting new objects it is good practice to use the pool method to provision them rather than directly using | ||
the constructor - as this allows object pooling to be enabled or disabled by deciding to initialise the object pool or | ||
not, rather than having to manually switch between the two methods of initialising the objects. See the examples below | ||
for a more indepth explanation of creating objects using pooling vs using a constructor. | ||
|
||
## Examples | ||
|
||
Pooling is slightly abstract, and it mostly handled behind the scenes by the engine, so the interface for using object | ||
pooling in your game is quite simple. | ||
|
||
### Vector | ||
|
||
Instead of using the constructor for creating a [Vector] instead use the `Vector.New` static method: | ||
|
||
```typescript | ||
const unpooled = new Vector(5, 3); | ||
|
||
const pooled = Vector.New(5, 3); | ||
``` | ||
|
||
Using `Vector.New` is preferred in all instances, as this will work even if object pooling is disabled - this means | ||
that object pooling can be enabled or disabled in a single place by removing/adding pool initialization calls. | ||
|
||
To initialize the [Vector] object pool, use `Vector.Init`: | ||
|
||
```typescript | ||
Vector.Init(500); | ||
``` | ||
|
||
This initializes the [Vector] object pool with `500` blank objects, with the maximum pool size set to `500`. | ||
|
||
To free a [Vector] after it has been used, use `Vector.Free`: | ||
|
||
```typescript | ||
// Create | ||
const position = Vector.New(2, 1); | ||
|
||
// Use the vector in some way | ||
position.x += 5; | ||
|
||
// Free | ||
position.Free(); | ||
``` | ||
|
||
### Renderable | ||
|
||
[Renderable] objects are lower level objects that support pooling, they follow the same static methods as the `Vector` | ||
with `New`, `Free`, and `Init` all available. | ||
|
||
[Component]: ../../reference/classes/component | ||
[Transform]: ../../reference/classes/transform | ||
[Vector]: ../../reference/classes/vector | ||
[Renderable]: ../../reference/classes/renderable |
Oops, something went wrong.