From 046288843217497a5fa051cda51822f63204b679 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Sun, 18 Oct 2020 13:43:58 +0100 Subject: [PATCH] Add Object Pooling (#162) --- CHANGELOG.md | 19 ++ docs/architecture/pooling.md | 109 +++++++ docs/documentation/camera.md | 12 +- docs/documentation/custom-shaders.md | 20 +- docs/documentation/image-loading.md | 14 +- docs/documentation/pooling.md | 101 +++++++ docs/documentation/primitives.md | 26 +- docs/documentation/scripting.md | 2 +- docs/documentation/shapes.md | 20 +- docs/documentation/sprite-animation.md | 12 +- docs/documentation/sprites.md | 22 +- docs/documentation/text.md | 4 +- docs/documentation/ui.md | 6 +- docs/reference/README.md | 4 + docs/reference/classes/aabb.md | 13 +- docs/reference/classes/audiosource.md | 17 ++ docs/reference/classes/camera.md | 23 +- docs/reference/classes/collider.md | 40 ++- docs/reference/classes/component.md | 18 ++ docs/reference/classes/componentmanager.md | 6 +- docs/reference/classes/ellipse.md | 18 +- docs/reference/classes/fakecomponent.md | 20 ++ docs/reference/classes/fakemessagebus.md | 9 + docs/reference/classes/material.md | 15 + docs/reference/classes/messagebus.md | 46 ++- docs/reference/classes/motion.md | 24 +- docs/reference/classes/polygon.md | 26 +- docs/reference/classes/pooled.md | 129 ++++++++ docs/reference/classes/primitive.md | 20 ++ docs/reference/classes/renderable.md | 219 +++++++++++++- docs/reference/classes/script.md | 20 ++ docs/reference/classes/sprite.md | 20 ++ docs/reference/classes/spriteanimator.md | 20 ++ docs/reference/classes/testpooledobject.md | 249 +++++++++++++++ docs/reference/classes/text.md | 22 +- docs/reference/classes/texture.md | 17 +- docs/reference/classes/transform.md | 21 +- docs/reference/classes/ui.md | 20 ++ docs/reference/classes/vector.md | 210 ++++++++++++- docs/reference/interfaces/ifreeable.md | 47 +++ docs/reference/interfaces/imessagebus.md | 53 +++- docs/reference/interfaces/ipoolable.md | 64 ++++ docs/reference/interfaces/irenderable.md | 51 +++- docs/reference/interfaces/ishape.md | 17 +- example/animation/src/index.ts | 26 +- example/audio/src/index.ts | 10 +- example/custom_fragment_shader/src/index.ts | 8 +- example/ellipse_approximation/src/index.ts | 14 +- example/scripting/src/index.ts | 68 +++-- example/shooter/src/index.ts | 118 ++++---- example/texture/src/index.ts | 21 +- example/z_order/src/index.ts | 16 +- src/component/component.ts | 11 +- src/component/component_manager.test.ts | 114 +++---- src/component/component_manager.ts | 16 +- src/entity/entity.ts | 14 +- src/entity/entity_manager.test.ts | 44 +-- src/entity/entity_manager.ts | 16 +- src/fake/message_bus.ts | 6 +- src/game.ts | 4 +- src/geometry/matrix_4d.ts | 2 +- src/geometry/vector.ts | 48 ++- src/index.ts | 8 + src/message/imessage.ts | 4 +- src/message/imessage_bus.ts | 47 ++- src/message/message.ts | 4 +- src/message/message_bus.ts | 35 +-- src/pooling/ifreeable.ts | 28 ++ src/pooling/ipoolable.ts | 36 +++ src/pooling/pooled.test.ts | 283 ++++++++++++++++++ src/pooling/pooled.ts | 124 ++++++++ src/rendering/irenderable.ts | 5 +- src/rendering/material/material.ts | 15 +- src/rendering/renderable.ts | 43 ++- src/rendering/texture/texture.ts | 23 +- src/shape/aabb.ts | 15 +- src/shape/ellipse.test.ts | 2 +- src/shape/ellipse.ts | 15 +- src/shape/ishape.ts | 9 +- src/shape/polygon.test.ts | 26 +- src/shape/polygon.ts | 82 +++-- src/standard/camera/camera.ts | 14 +- .../algorithm/aabb_algorithm.test.ts | 36 +-- .../collision/algorithm/aabb_algorithm.ts | 8 +- .../collision/algorithm/gjk_algorithm.test.ts | 37 ++- .../collision/algorithm/gjk_algorithm.ts | 6 +- src/standard/collision/collider.ts | 10 +- .../collision/collision_system.test.ts | 120 ++++---- .../interpolation/interpolation_system.ts | 12 +- src/standard/motion/motion.ts | 12 +- src/standard/motion/motion_system.ts | 7 +- src/standard/pointer/pointer_system.ts | 14 +- src/standard/primitive/primitive.ts | 6 +- src/standard/primitive/primitive_system.ts | 8 +- src/standard/sprite/sprite.ts | 8 +- src/standard/sprite/sprite_system.ts | 18 +- src/standard/text/text.ts | 10 +- src/standard/text/text_system.ts | 44 ++- src/standard/transform/transform.ts | 16 +- src/standard/webgl/webgl_system.test.ts | 26 +- src/standard/webgl/webgl_system.ts | 8 +- 101 files changed, 2968 insertions(+), 657 deletions(-) create mode 100644 docs/architecture/pooling.md create mode 100644 docs/documentation/pooling.md create mode 100644 docs/reference/classes/pooled.md create mode 100644 docs/reference/classes/testpooledobject.md create mode 100644 docs/reference/interfaces/ifreeable.md create mode 100644 docs/reference/interfaces/ipoolable.md create mode 100644 src/pooling/ifreeable.ts create mode 100644 src/pooling/ipoolable.ts create mode 100644 src/pooling/pooled.test.ts create mode 100644 src/pooling/pooled.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 89a953f..a02ea38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added +- Object pooling, allows reusing objects in memory to avoid the garbage collection churn of create -> delete -> create. +This can help prevent stuttering due to minor and major garbage collection occurring between frames by reducing the +volume of objects that need garbage collected. +- `Vector` object is now poolable, with helper static functions added to `Vector` + - `New` -> provisions a `Vector` from the object pool if available, if not it creates a new instance. + - `Free` -> releases a `Vector` back into the object pool if available. + - `Init` -> initializes the `Vector` object pool to a specified size. +- `Renderable` object is now poolable, with similar helper static functions as `Vector`. +- `DispatchUntilEmpty` method added to `MessageBus`, allows repeated dispatching until the message queue is empty. +### Changed +- In `Polygon` -> `RectangleByDimensions`, `QuadByDimensions`, and `EllipseEstimation` all now represent center/origin +using two X and Y numbers rather than a vector object to avoid unneeded object creation. +- In `Ellipse` -> `Circle` represnts center using two X and Y numbers rather than a vector object to avoid unneeded +object creation. +- `Component` implements `IFreeable`, allowing component values to be freed upon removal/entity destruction. This +must be implemented on a per `Component` basis by overriding this function. +- The game loop now ensures all game logic messages are processed using `DispatchUntilEmpty` before executing any +render logic. ## [v0.9.0] - 2020-09-05 ### Added diff --git a/docs/architecture/pooling.md b/docs/architecture/pooling.md new file mode 100644 index 0000000..268c5db --- /dev/null +++ b/docs/architecture/pooling.md @@ -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.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 diff --git a/docs/documentation/camera.md b/docs/documentation/camera.md index a9f675f..30a22b5 100644 --- a/docs/documentation/camera.md +++ b/docs/documentation/camera.md @@ -3,7 +3,7 @@ Cameras are important parts of the JamJar game engine, they define how the game world is viewed and rendered. A camera is a [`Component`](../../reference/classes/component), any entity can be made into a -camera by simply adding a [`Camera`](../../reference/classes/camera) to it. +camera by simply adding a [`Camera`](../../reference/classes/camera) to it. ## Camera World Position @@ -32,7 +32,7 @@ rendered. This is defined with two properties of the [`viewportScale`](../../reference/classes/camera#viewportscale). The [`viewportPosition`](../../reference/classes/camera#viewportposition) is the position of the camera's viewport on the screen, with from the bottom left -`(-1,-1)` to top right `(1,1)` with `(0,0)` as the center. +`(-1,-1)` to top right `(1,1)` with `(0,0)` as the center. The [`viewportScale`](../../reference/classes/camera#viewportscale) is the scale of the camera's viewport, relative to the canvas/rendering surface. A viewport scale of `(1,1)` would take up the entire canvas, while a scale of `(0.5, 0.5)` @@ -44,11 +44,11 @@ would only take up half of the screen (width and height). ```typescript const cameraEntity = new Entity(messageBus); -cameraEntity.Add(new Transform(new Vector(0, 0), new Vector(5, 5))); +cameraEntity.Add(new Transform(Vector.New(0, 0), Vector.New(5, 5))); cameraEntity.Add(new Camera( new Color(1, 0, 0, 1), // Red - new Vector(0.5, 0.5), // Top right - new Vector(0.5, 0.5), // Half width and height of canvas/screen - new Vector(160, 90) // 16*9 screen, show 160 * 90 world units + Vector.New(0.5, 0.5), // Top right + Vector.New(0.5, 0.5), // Half width and height of canvas/screen + Vector.New(160, 90) // 16*9 screen, show 160 * 90 world units )); ``` diff --git a/docs/documentation/custom-shaders.md b/docs/documentation/custom-shaders.md index 76188fd..8c5cdb4 100644 --- a/docs/documentation/custom-shaders.md +++ b/docs/documentation/custom-shaders.md @@ -73,11 +73,11 @@ class ShaderGame extends Game { ShaderAsset.FRAGMENT_TYPE, `#version 300 es precision mediump float; - + in vec2 vTextureCoordinate; - + out vec4 outColor; - + void main() { outColor = vec4(0,1,0,1); } @@ -111,11 +111,11 @@ class ShaderGame extends Game { ShaderAsset.FRAGMENT_TYPE, `#version 300 es precision mediump float; - + in vec2 vTextureCoordinate; - + out vec4 outColor; - + void main() { outColor = vec4(0,1,0,1); } @@ -139,7 +139,7 @@ class ShaderGame extends Game { // Create example entity const example = new Entity(this.messageBus); - example.Add(new Transform(new Vector(0,0), new Vector(10,10))); + example.Add(new Transform(Vector.New(0,0), Vector.New(10,10))); // Create sprite using a material with the previously loaded texture, // alongside using our custom fragment shader with the default @@ -148,8 +148,8 @@ class ShaderGame extends Game { new Material({ texture: new Texture("example", Polygon.RectangleByDimensions(1,1)), shaders: [ "example-shader", ShaderAsset.DEFAULT_TEXTURE_VERTEX_SHADER_NAME ] - }), - 0, + }), + 0, Polygon.RectangleByDimensions(1,1) )); } @@ -162,4 +162,4 @@ class ShaderGame extends Game { The full code for this guide can be found in [`/example/custom_fragment_shader`](https://github.com/jamjarlabs/JamJar/tree/master/example/custom_fragment_shader) -in the main JamJar repository. \ No newline at end of file +in the main JamJar repository. diff --git a/docs/documentation/image-loading.md b/docs/documentation/image-loading.md index a420fd5..c14e3f7 100644 --- a/docs/documentation/image-loading.md +++ b/docs/documentation/image-loading.md @@ -16,7 +16,7 @@ attached [ImageRequest] to specify the image. ```typescript this.messageBus.Publish(new Message( - ImageRequest.MESSAGE_REQUEST_LOAD, + ImageRequest.MESSAGE_REQUEST_LOAD, new ImageRequest("bullet", "assets/bullet.png") )); ``` @@ -31,11 +31,11 @@ such as in a sprite: ```typescript const bullet = new Entity(); -bullet.Add(new Transform(new Vector(0,0), new Vector(5,5))); +bullet.Add(new Transform(Vector.New(0,0), Vector.New(5,5))); bullet.Add(new Sprite(new Material( new Texture( - "bullet", - Polygon.RectangleByPoints(new Vector(0,0), new Vector(1,1)).GetFloat32Array() + "bullet", + Polygon.RectangleByPoints(Vector.New(0,0), Vector.New(1,1)).GetFloat32Array() )), 1) ); ``` @@ -49,9 +49,9 @@ options provided to the [ImageRequest] in the form of [ITextureOptions]. ```typescript this.messageBus.Publish(new Message( - ImageRequest.MESSAGE_REQUEST_LOAD, + ImageRequest.MESSAGE_REQUEST_LOAD, new ImageRequest( - "bullet", + "bullet", "assets/bullet.png", { minFilter: TextureFiltering.NEAREST, @@ -71,4 +71,4 @@ on texture customisation choices. [Sprite]:../../reference/classes/sprite [ITextureOptions]:../../reference/interfaces/itextureoptions [TextureFiltering]:../../reference/classes/texturefiltering -[Texture Options]:../texture-options \ No newline at end of file +[Texture Options]:../texture-options diff --git a/docs/documentation/pooling.md b/docs/documentation/pooling.md new file mode 100644 index 0000000..a8e7d37 --- /dev/null +++ b/docs/documentation/pooling.md @@ -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 diff --git a/docs/documentation/primitives.md b/docs/documentation/primitives.md index fbfb076..fb46156 100644 --- a/docs/documentation/primitives.md +++ b/docs/documentation/primitives.md @@ -29,15 +29,15 @@ entity. ```typescript const primitiveEntity = new Entity(messageBus); -primitiveEntity.Add(new Transform(new Vector(0, 0), new Vector(5,5))); +primitiveEntity.Add(new Transform(Vector.New(0, 0), Vector.New(5,5))); primitiveEntity.Add(new Primitive( new Material(), 0, new Polygon([ - new Vector(0,0.5), - new Vector(0.5, -0.5), - new Vector(-0.5, -0.5), - new Vector(0,0.5) + Vector.New(0,0.5), + Vector.New(0.5, -0.5), + Vector.New(-0.5, -0.5), + Vector.New(0,0.5) ]), DrawMode.LINE_STRIP )); @@ -49,7 +49,7 @@ to render. Primitives contain a field [zOrder], which determines the precendence with which primitives will appear on the screen. Objects with a higher z order will appear -ahead of objects with a lower z order. +ahead of objects with a lower z order. ![Example Z Order](../assets/z_order.svg) @@ -71,19 +71,19 @@ required [Transform] and [Primitive] components. ```typescript const gameCamera = new Entity(messageBus); -gameCamera.Add(new Transform(new Vector(0, 0), new Vector(5, 5))); +gameCamera.Add(new Transform(Vector.New(0, 0), Vector.New(5, 5))); gameCamera.Add(new Camera()); const uiElement = new Entity(messageBus); -uiElement.Add(new Transform(new Vector(0, 0), new Vector(0.2,0.2))); +uiElement.Add(new Transform(Vector.New(0, 0), Vector.New(0.2,0.2))); uiElement.Add(new Primitive( new Material(), 0, new Polygon([ - new Vector(0,0.5), - new Vector(0.5, -0.5), - new Vector(-0.5, -0.5), - new Vector(0,0.5) + Vector.New(0,0.5), + Vector.New(0.5, -0.5), + Vector.New(-0.5, -0.5), + Vector.New(0,0.5) ]), DrawMode.LINE_STRIP )); @@ -102,4 +102,4 @@ in the center of the camera. [zOrder]:../../reference/classes/sprite#zorder [Material]:../../reference/classes/material [UI]:../../reference/classes/ui -[Transform]:../../reference/classes/transform \ No newline at end of file +[Transform]:../../reference/classes/transform diff --git a/docs/documentation/scripting.md b/docs/documentation/scripting.md index 2fbc17a..334598d 100644 --- a/docs/documentation/scripting.md +++ b/docs/documentation/scripting.md @@ -98,7 +98,7 @@ referenced. ```typescript const collidable = new Entity(this.messageBus); -collidable.Add(new Transform(new Vector(0, 0), new Vector(1, 1))); +collidable.Add(new Transform(Vector.New(0, 0), Vector.New(1, 1))); collidable.Add(new Collider(Polygon.RectangleByDimensions(1, 1), "test-script")); ``` diff --git a/docs/documentation/shapes.md b/docs/documentation/shapes.md index b613891..0f0ed29 100644 --- a/docs/documentation/shapes.md +++ b/docs/documentation/shapes.md @@ -27,9 +27,9 @@ For example: ```typescript const triangle = new Polygon([ - new Vector(-1, -1), - new Vector(1, -1), - new Vector(0, 1) + Vector.New(-1, -1), + Vector.New(1, -1), + Vector.New(0, 1) ]); ``` @@ -40,9 +40,9 @@ To wrap a [Polygon]: ```typescript const triangle = new Polygon([ - new Vector(-1, -1), - new Vector(1, -1), - new Vector(0, 1) + Vector.New(-1, -1), + Vector.New(1, -1), + Vector.New(0, 1) ], true); ``` @@ -64,7 +64,7 @@ a circle. It is represented by a center position, dimensions, and a rotation. For example: ```typescript -const oval = new Ellipse(new Vector(4, 2), Math.PI / 4, new Vector(2, 2)); +const oval = new Ellipse(Vector.New(4, 2), Math.PI / 4, Vector.New(2, 2)); ``` ## AABB @@ -79,9 +79,9 @@ less calculations than a fully defined polygon. For example: ```typescript -const square = new AABB(new Vector(2,2)); -const squareAroundPoint = new AABB(new Vector(2,2), new Vector(5,3)); -const rectangle = new AABB(new Vector(2,4)); +const square = new AABB(Vector.New(2,2)); +const squareAroundPoint = new AABB(Vector.New(2,2), Vector.New(5,3)); +const rectangle = new AABB(Vector.New(2,4)); ``` [IShape]: ../../reference/interfaces/ishape diff --git a/docs/documentation/sprite-animation.md b/docs/documentation/sprite-animation.md index a32e5af..6447ad4 100644 --- a/docs/documentation/sprite-animation.md +++ b/docs/documentation/sprite-animation.md @@ -14,7 +14,7 @@ new SpriteAnimatorSystem(messageBus); ## Creating an Entity with Sprite Animation To set up an [Entity] with Sprite Animation, there are three components -required, a [Transform], a [Sprite], and a [SpriteAnimator]. +required, a [Transform], a [Sprite], and a [SpriteAnimator]. The following sample creates an entity with Sprite Animation, using a 2x2 sprite sheet. The sprite sheet is accessed uing [Texture.GenerateSpritesheetIndex] @@ -28,12 +28,12 @@ The animation is set to loop infinitely. const spriteSheetIndices = Texture.GenerateSpritesheetIndex(2, 2); const animationEntity = new Entity(messageBus); -animationEntity.Add(new Transform(new Vector(0, 0), new Vector(5,5))); +animationEntity.Add(new Transform(Vector.New(0, 0), Vector.New(5,5))); animationEntity.Add(new Sprite( new Material({ texture: new Texture("example_spritesheet", spriteSheetIndices[0]), - }), - 0, + }), + 0, Polygon.RectangleByDimensions(1,1) )); animationEntity.Add(new SpriteAnimator( @@ -68,7 +68,7 @@ animationEntity.Add(new SpriteAnimator( 10, -1 )], - ]), + ]), "example" )); ``` @@ -79,4 +79,4 @@ animationEntity.Add(new SpriteAnimator( [Transform]:../../reference/classes/transform [Sprite]:../../reference/classes/sprite [SpriteAnimator]:../../reference/classes/spriteanimator -[Texture.GenerateSpritesheetIndex]:../../reference/classes/texture#static-generatespritesheetindex \ No newline at end of file +[Texture.GenerateSpritesheetIndex]:../../reference/classes/texture#static-generatespritesheetindex diff --git a/docs/documentation/sprites.md b/docs/documentation/sprites.md index 69678b3..49d5bef 100644 --- a/docs/documentation/sprites.md +++ b/docs/documentation/sprites.md @@ -1,7 +1,7 @@ # Sprites Sprites are simple renderable components, allowing shapes to be rendered with -materials applied to them to specify texture, color and shaders. +materials applied to them to specify texture, color and shaders. The [SpriteSystem] allows rendering [Sprite] components - by tracking [Sprite] components and generating a list of [IRenderable] for render systems to use. @@ -26,12 +26,12 @@ An [Entity] can be given a sprite by adding the [Sprite] component to an entity. ```typescript const spriteEntity = new Entity(messageBus); -spriteEntity.Add(new Transform(new Vector(0, 0), new Vector(5,5))); +spriteEntity.Add(new Transform(Vector.New(0, 0), Vector.New(5,5))); spriteEntity.Add(new Sprite( new Material({ - texture: new Texture("example_texture", Polygon.RectangleByPoints(new Vector(0,0), new Vector(1,1))), - }), - 0, + texture: new Texture("example_texture", Polygon.RectangleByPoints(Vector.New(0,0), Vector.New(1,1))), + }), + 0, Polygon.RectangleByDimensions(1,1) )); ``` @@ -59,16 +59,16 @@ required [Transform] and [Sprite] components. ```typescript const gameCamera = new Entity(messageBus); -gameCamera.Add(new Transform(new Vector(0, 0), new Vector(5, 5))); +gameCamera.Add(new Transform(Vector.New(0, 0), Vector.New(5, 5))); gameCamera.Add(new Camera()); const uiElement = new Entity(messageBus); -uiElement.Add(new Transform(new Vector(0, 0), new Vector(0.2,0.2))); +uiElement.Add(new Transform(Vector.New(0, 0), Vector.New(0.2,0.2))); uiElement.Add(new Sprite( new Material({ - texture: new Texture("example_texture", Polygon.RectangleByPoints(new Vector(0,0), new Vector(1,1))), - }), - 0, + texture: new Texture("example_texture", Polygon.RectangleByPoints(Vector.New(0,0), Vector.New(1,1))), + }), + 0, Polygon.RectangleByDimensions(1,1) )); uiElement.Add(new UI(gameCamera)); @@ -85,4 +85,4 @@ the center of the camera - with the `example_texture` texture. [zOrder]:../../reference/classes/sprite#zorder [Material]:../../reference/classes/material [UI]:../../reference/classes/ui -[Transform]:../../reference/classes/transform \ No newline at end of file +[Transform]:../../reference/classes/transform diff --git a/docs/documentation/text.md b/docs/documentation/text.md index f59b29a..8e88ddc 100644 --- a/docs/documentation/text.md +++ b/docs/documentation/text.md @@ -68,7 +68,7 @@ process it before sending it to a [RenderSystem]. ```typescript const textEntity = new Entity(messageBus); -textEntity.Add(new Transform(new Vector(0, 0), new Vector(5,5))); +textEntity.Add(new Transform(Vector.New(0, 0), Vector.New(5,5))); textEntity.Add(new Text(1, "hello world", "example_font", TextAlignment.Center, 2.5, new Color(0,1,0))); ``` @@ -109,4 +109,4 @@ camera viewport, and `0.5,0.5` being half of it's width and height. [Text]: ../../reference/classes/text [Transform]: ../../reference/classes/transform [TextAlignment]: ../../reference/enums/textalignment -[UI]: ../../reference/classes/ui \ No newline at end of file +[UI]: ../../reference/classes/ui diff --git a/docs/documentation/ui.md b/docs/documentation/ui.md index 00494b9..5a6d310 100644 --- a/docs/documentation/ui.md +++ b/docs/documentation/ui.md @@ -10,7 +10,7 @@ than world space. The UI uses a coordinate system relative to the camera it is assigned to. All of the UI layout is through the [Transform] component, which is interpreted -differently for UI elements. +differently for UI elements. ### Position The coordinates go from `(-1,-1)` (bottom left) to `(1,1)` (top right). This @@ -22,7 +22,7 @@ right](../assets/ui_example_position.svg) This UI element would be at position `(0.5,0)`. ```typescript -new Transform(new Vector(0.5, 0)); +new Transform(Vector.New(0.5, 0)); ``` ### Scale @@ -47,4 +47,4 @@ rendered on. The UI element will exclusively be rendered on this camera, and all calculations of scale and positions will be relative to it. [UI]:../../reference/classes/ui -[Transform]:../../reference/classes/transform \ No newline at end of file +[Transform]:../../reference/classes/transform diff --git a/docs/reference/README.md b/docs/reference/README.md index 2af966c..f4b35e1 100644 --- a/docs/reference/README.md +++ b/docs/reference/README.md @@ -76,6 +76,7 @@ * [PointerCameraInfo](classes/pointercamerainfo.md) * [PointerSystem](classes/pointersystem.md) * [Polygon](classes/polygon.md) +* [Pooled](classes/pooled.md) * [Primitive](classes/primitive.md) * [PrimitiveSystem](classes/primitivesystem.md) * [Reactor](classes/reactor.md) @@ -107,6 +108,7 @@ * [TestHTTPScriptSystem](classes/testhttpscriptsystem.md) * [TestKeyboardSystem](classes/testkeyboardsystem.md) * [TestPointerSystem](classes/testpointersystem.md) +* [TestPooledObject](classes/testpooledobject.md) * [TestRenderSystem](classes/testrendersystem.md) * [TestScene](classes/testscene.md) * [TestShader](classes/testshader.md) @@ -127,11 +129,13 @@ * [ICollisionAlgorithm](interfaces/icollisionalgorithm.md) * [IEntity](interfaces/ientity.md) * [IFontOptions](interfaces/ifontoptions.md) +* [IFreeable](interfaces/ifreeable.md) * [IFrustumCuller](interfaces/ifrustumculler.md) * [IGame](interfaces/igame.md) * [IMaterialOptions](interfaces/imaterialoptions.md) * [IMessage](interfaces/imessage.md) * [IMessageBus](interfaces/imessagebus.md) +* [IPoolable](interfaces/ipoolable.md) * [IRenderable](interfaces/irenderable.md) * [IScene](interfaces/iscene.md) * [IShader](interfaces/ishader.md) diff --git a/docs/reference/classes/aabb.md b/docs/reference/classes/aabb.md index 3bbce0b..4734e30 100644 --- a/docs/reference/classes/aabb.md +++ b/docs/reference/classes/aabb.md @@ -30,6 +30,7 @@ requiring less calculations than a fully defined polygon. * [Center](aabb.md#center) * [FarthestPointInDirection](aabb.md#farthestpointindirection) +* [Free](aabb.md#free) * [PointInside](aabb.md#pointinside) * [Transform](aabb.md#transform) @@ -44,7 +45,7 @@ requiring less calculations than a fully defined polygon. Name | Type | Default | ------ | ------ | ------ | `size` | [Vector](vector.md) | - | -`center` | [Vector](vector.md) | new Vector(0,0) | +`center` | [Vector](vector.md) | Vector.New(0,0) | **Returns:** *[AABB](aabb.md)* @@ -88,6 +89,16 @@ Name | Type | ___ +### Free + +▸ **Free**(): *void* + +*Implementation of [IShape](../interfaces/ishape.md)* + +**Returns:** *void* + +___ + ### PointInside ▸ **PointInside**(`point`: [Vector](vector.md)): *boolean* diff --git a/docs/reference/classes/audiosource.md b/docs/reference/classes/audiosource.md index 7ddf557..f377346 100644 --- a/docs/reference/classes/audiosource.md +++ b/docs/reference/classes/audiosource.md @@ -10,6 +10,10 @@ should be played. ↳ **AudioSource** +## Implements + +* [IFreeable](../interfaces/ifreeable.md) + ## Index ### Constructors @@ -32,6 +36,7 @@ should be played. ### Methods +* [Free](audiosource.md#free) * [Play](audiosource.md#play) * [Stop](audiosource.md#stop) @@ -151,6 +156,18 @@ ___ ## Methods +### Free + +▸ **Free**(): *void* + +*Implementation of [IFreeable](../interfaces/ifreeable.md)* + +*Inherited from [Component](component.md).[Free](component.md#free)* + +**Returns:** *void* + +___ + ### Play ▸ **Play**(): *void* diff --git a/docs/reference/classes/camera.md b/docs/reference/classes/camera.md index 58fba36..875f4f8 100644 --- a/docs/reference/classes/camera.md +++ b/docs/reference/classes/camera.md @@ -11,6 +11,10 @@ In-game camera position should be managed in the transform. ↳ **Camera** +## Implements + +* [IFreeable](../interfaces/ifreeable.md) + ## Index ### Constructors @@ -30,6 +34,7 @@ In-game camera position should be managed in the transform. ### Methods +* [Free](camera.md#free) * [GetProjectionMatrix](camera.md#getprojectionmatrix) ## Constructors @@ -45,9 +50,9 @@ In-game camera position should be managed in the transform. Name | Type | Default | ------ | ------ | ------ | `backgroundColor` | [Color](color.md) | new Color(0,0,0,1) | -`viewportPosition` | [Vector](vector.md) | new Vector(0,0) | -`viewportScale` | [Vector](vector.md) | new Vector(1,1) | -`virtualScale` | [Vector](vector.md) | new Vector(160,90) | +`viewportPosition` | [Vector](vector.md) | Vector.New(0,0) | +`viewportScale` | [Vector](vector.md) | Vector.New(1,1) | +`virtualScale` | [Vector](vector.md) | Vector.New(160,90) | **Returns:** *[Camera](camera.md)* @@ -120,6 +125,18 @@ ___ ## Methods +### Free + +▸ **Free**(): *void* + +*Implementation of [IFreeable](../interfaces/ifreeable.md)* + +*Overrides [Component](component.md).[Free](component.md#free)* + +**Returns:** *void* + +___ + ### GetProjectionMatrix ▸ **GetProjectionMatrix**(): *[Matrix4D](matrix4d.md)* diff --git a/docs/reference/classes/collider.md b/docs/reference/classes/collider.md index 7edbcbb..77a1cda 100644 --- a/docs/reference/classes/collider.md +++ b/docs/reference/classes/collider.md @@ -10,6 +10,10 @@ with other Colliders. ↳ **Collider** +## Implements + +* [IFreeable](../interfaces/ifreeable.md) + ## Index ### Constructors @@ -18,6 +22,7 @@ with other Colliders. ### Properties +* [currentlyCollidingWith](collider.md#currentlycollidingwith) * [enterScript](collider.md#optional-enterscript) * [exitScript](collider.md#optional-exitscript) * [key](collider.md#key) @@ -26,26 +31,37 @@ with other Colliders. * [MESSAGE_ADD](collider.md#static-message_add) * [MESSAGE_REMOVE](collider.md#static-message_remove) +### Methods + +* [Free](collider.md#free) + ## Constructors ### constructor -\+ **new Collider**(`shape`: [IShape](../interfaces/ishape.md), `enterScript?`: undefined | string, `exitScript?`: undefined | string): *[Collider](collider.md)* +\+ **new Collider**(`shape`: [IShape](../interfaces/ishape.md), `enterScript?`: undefined | string, `exitScript?`: undefined | string, `currentlyCollidingWith`: [IEntity](../interfaces/ientity.md)[]): *[Collider](collider.md)* *Overrides [Component](component.md).[constructor](component.md#constructor)* **Parameters:** -Name | Type | ------- | ------ | -`shape` | [IShape](../interfaces/ishape.md) | -`enterScript?` | undefined | string | -`exitScript?` | undefined | string | +Name | Type | Default | +------ | ------ | ------ | +`shape` | [IShape](../interfaces/ishape.md) | - | +`enterScript?` | undefined | string | - | +`exitScript?` | undefined | string | - | +`currentlyCollidingWith` | [IEntity](../interfaces/ientity.md)[] | [] | **Returns:** *[Collider](collider.md)* ## Properties +### currentlyCollidingWith + +• **currentlyCollidingWith**: *[IEntity](../interfaces/ientity.md)[]* + +___ + ### `Optional` enterScript • **enterScript**? : *undefined | string* @@ -91,3 +107,15 @@ ___ ▪ **MESSAGE_REMOVE**: *"component_remove"* = "component_remove" *Inherited from [Component](component.md).[MESSAGE_REMOVE](component.md#static-message_remove)* + +## Methods + +### Free + +▸ **Free**(): *void* + +*Implementation of [IFreeable](../interfaces/ifreeable.md)* + +*Overrides [Component](component.md).[Free](component.md#free)* + +**Returns:** *void* diff --git a/docs/reference/classes/component.md b/docs/reference/classes/component.md index 23f41ce..5c53104 100644 --- a/docs/reference/classes/component.md +++ b/docs/reference/classes/component.md @@ -35,6 +35,10 @@ Each entity can only have 1 component of each type. ↳ [Text](text.md) +## Implements + +* [IFreeable](../interfaces/ifreeable.md) + ## Index ### Constructors @@ -47,6 +51,10 @@ Each entity can only have 1 component of each type. * [MESSAGE_ADD](component.md#static-message_add) * [MESSAGE_REMOVE](component.md#static-message_remove) +### Methods + +* [Free](component.md#free) + ## Constructors ### constructor @@ -78,3 +86,13 @@ ___ ### `Static` MESSAGE_REMOVE ▪ **MESSAGE_REMOVE**: *"component_remove"* = "component_remove" + +## Methods + +### Free + +▸ **Free**(): *void* + +*Implementation of [IFreeable](../interfaces/ifreeable.md)* + +**Returns:** *void* diff --git a/docs/reference/classes/componentmanager.md b/docs/reference/classes/componentmanager.md index 7c3d074..15b5cb0 100644 --- a/docs/reference/classes/componentmanager.md +++ b/docs/reference/classes/componentmanager.md @@ -30,14 +30,14 @@ Used in conjunction with the EntityManager for managing Entities/Components. ### constructor -\+ **new ComponentManager**(`key`: string, `components`: Record‹number, [Component](component.md)›): *[ComponentManager](componentmanager.md)* +\+ **new ComponentManager**(`key`: string, `components`: Map‹number, [Component](component.md)›): *[ComponentManager](componentmanager.md)* **Parameters:** Name | Type | Default | ------ | ------ | ------ | `key` | string | - | -`components` | Record‹number, [Component](component.md)› | {} | +`components` | Map‹number, [Component](component.md)› | new Map() | **Returns:** *[ComponentManager](componentmanager.md)* @@ -45,7 +45,7 @@ Name | Type | Default | ### `Private` components -• **components**: *Record‹number, [Component](component.md)›* +• **components**: *Map‹number, [Component](component.md)›* ___ diff --git a/docs/reference/classes/ellipse.md b/docs/reference/classes/ellipse.md index df562d6..9267c96 100644 --- a/docs/reference/classes/ellipse.md +++ b/docs/reference/classes/ellipse.md @@ -27,6 +27,7 @@ Ellipse is the representation of a 2D Ellipse shape. Can be used for collision d * [Center](ellipse.md#center) * [FarthestPointInDirection](ellipse.md#farthestpointindirection) +* [Free](ellipse.md#free) * [PointInside](ellipse.md#pointinside) * [Transform](ellipse.md#transform) * [Circle](ellipse.md#static-circle) @@ -43,7 +44,7 @@ Name | Type | Default | ------ | ------ | ------ | `dimensions` | [Vector](vector.md) | - | `orientation` | number | 0 | -`center` | [Vector](vector.md) | new Vector(0, 0) | +`center` | [Vector](vector.md) | Vector.New(0, 0) | **Returns:** *[Ellipse](ellipse.md)* @@ -93,6 +94,16 @@ Name | Type | ___ +### Free + +▸ **Free**(): *void* + +*Implementation of [IShape](../interfaces/ishape.md)* + +**Returns:** *void* + +___ + ### PointInside ▸ **PointInside**(`point`: [Vector](vector.md)): *boolean* @@ -127,7 +138,7 @@ ___ ### `Static` Circle -▸ **Circle**(`radius`: number, `center`: [Vector](vector.md)): *[Ellipse](ellipse.md)* +▸ **Circle**(`radius`: number, `centerX`: number, `centerY`: number): *[Ellipse](ellipse.md)* Circle returns a new Ellipse in the shape of a circle. @@ -136,6 +147,7 @@ Circle returns a new Ellipse in the shape of a circle. Name | Type | Default | Description | ------ | ------ | ------ | ------ | `radius` | number | - | Radius of the circle | -`center` | [Vector](vector.md) | new Vector(0, 0) | Centre of the circle | +`centerX` | number | 0 | - | +`centerY` | number | 0 | - | **Returns:** *[Ellipse](ellipse.md)* diff --git a/docs/reference/classes/fakecomponent.md b/docs/reference/classes/fakecomponent.md index fee0399..88be8a6 100644 --- a/docs/reference/classes/fakecomponent.md +++ b/docs/reference/classes/fakecomponent.md @@ -7,6 +7,10 @@ ↳ **FakeComponent** +## Implements + +* [IFreeable](../interfaces/ifreeable.md) + ## Index ### Constructors @@ -19,6 +23,10 @@ * [MESSAGE_ADD](fakecomponent.md#static-message_add) * [MESSAGE_REMOVE](fakecomponent.md#static-message_remove) +### Methods + +* [Free](fakecomponent.md#free) + ## Constructors ### constructor @@ -58,3 +66,15 @@ ___ ▪ **MESSAGE_REMOVE**: *"component_remove"* = "component_remove" *Inherited from [Component](component.md).[MESSAGE_REMOVE](component.md#static-message_remove)* + +## Methods + +### Free + +▸ **Free**(): *void* + +*Implementation of [IFreeable](../interfaces/ifreeable.md)* + +*Inherited from [Component](component.md).[Free](component.md#free)* + +**Returns:** *void* diff --git a/docs/reference/classes/fakemessagebus.md b/docs/reference/classes/fakemessagebus.md index 4c2d1fc..42290d9 100644 --- a/docs/reference/classes/fakemessagebus.md +++ b/docs/reference/classes/fakemessagebus.md @@ -20,6 +20,7 @@ ### Methods * [Dispatch](fakemessagebus.md#dispatch) +* [DispatchUntilEmpty](fakemessagebus.md#dispatchuntilempty) * [Publish](fakemessagebus.md#publish) * [Subscribe](fakemessagebus.md#subscribe) * [Unsubscribe](fakemessagebus.md#unsubscribe) @@ -51,6 +52,14 @@ Name | Type | Default | ___ +### DispatchUntilEmpty + +▸ **DispatchUntilEmpty**(): *void* + +**Returns:** *void* + +___ + ### Publish ▸ **Publish**(`message`: [IMessage](../interfaces/imessage.md)): *void* diff --git a/docs/reference/classes/material.md b/docs/reference/classes/material.md index 1da4ae0..22d0857 100644 --- a/docs/reference/classes/material.md +++ b/docs/reference/classes/material.md @@ -8,6 +8,10 @@ shaders, textures and colors. * **Material** +## Implements + +* [IFreeable](../interfaces/ifreeable.md) + ## Index ### Constructors @@ -24,6 +28,7 @@ shaders, textures and colors. ### Methods * [Copy](material.md#copy) +* [Free](material.md#free) ## Constructors @@ -79,3 +84,13 @@ ___ Makes a value copy of the material. **Returns:** *[Material](material.md)* + +___ + +### Free + +▸ **Free**(): *void* + +*Implementation of [IFreeable](../interfaces/ifreeable.md)* + +**Returns:** *void* diff --git a/docs/reference/classes/messagebus.md b/docs/reference/classes/messagebus.md index a26c161..dc0b81c 100644 --- a/docs/reference/classes/messagebus.md +++ b/docs/reference/classes/messagebus.md @@ -33,6 +33,7 @@ The dispatch should probably be left to the core game loop for triggering. ### Methods * [Dispatch](messagebus.md#dispatch) +* [DispatchUntilEmpty](messagebus.md#dispatchuntilempty) * [Publish](messagebus.md#publish) * [Subscribe](messagebus.md#subscribe) * [Unsubscribe](messagebus.md#unsubscribe) @@ -71,8 +72,13 @@ ___ ▸ **Dispatch**(): *void* -Processes the message bus queue and forwards the messages to the subscribers. -who have subscribed to each message type. +**Returns:** *void* + +___ + +### DispatchUntilEmpty + +▸ **DispatchUntilEmpty**(): *void* **Returns:** *void* @@ -82,13 +88,11 @@ ___ ▸ **Publish**(`message`: [IMessage](../interfaces/imessage.md)): *void* -Publish adds a message to the message bus queue to be dispatched. - **Parameters:** -Name | Type | Description | ------- | ------ | ------ | -`message` | [IMessage](../interfaces/imessage.md) | The message to send | +Name | Type | +------ | ------ | +`message` | [IMessage](../interfaces/imessage.md) | **Returns:** *void* @@ -98,14 +102,12 @@ ___ ▸ **Subscribe**(`subscriber`: [ISubscriber](../interfaces/isubscriber.md), `types`: string | string[]): *void* -Subscribe subscibes a subscriber to a particular message type or types. - **Parameters:** -Name | Type | Description | ------- | ------ | ------ | -`subscriber` | [ISubscriber](../interfaces/isubscriber.md) | The subscriber to the message type(s) | -`types` | string | string[] | The message type(s) to subscribe to | +Name | Type | +------ | ------ | +`subscriber` | [ISubscriber](../interfaces/isubscriber.md) | +`types` | string | string[] | **Returns:** *void* @@ -115,14 +117,12 @@ ___ ▸ **Unsubscribe**(`subscriber`: [ISubscriber](../interfaces/isubscriber.md), `types`: string | string[]): *void* -Unsubscribe unsubscribes a subscriber from a specific message type or types. - **Parameters:** -Name | Type | Description | ------- | ------ | ------ | -`subscriber` | [ISubscriber](../interfaces/isubscriber.md) | The subscriber to unsubscribe | -`types` | string | string[] | The message type(s) to unsubscribe from | +Name | Type | +------ | ------ | +`subscriber` | [ISubscriber](../interfaces/isubscriber.md) | +`types` | string | string[] | **Returns:** *void* @@ -132,12 +132,10 @@ ___ ▸ **UnsubscribeAll**(`subscriber`: [ISubscriber](../interfaces/isubscriber.md)): *void* -UnsubscribeAll unsubscribes a Subscriber from all messages. - **Parameters:** -Name | Type | Description | ------- | ------ | ------ | -`subscriber` | [ISubscriber](../interfaces/isubscriber.md) | The subscriber to unsubscribe | +Name | Type | +------ | ------ | +`subscriber` | [ISubscriber](../interfaces/isubscriber.md) | **Returns:** *void* diff --git a/docs/reference/classes/motion.md b/docs/reference/classes/motion.md index 8405180..d9bd72c 100644 --- a/docs/reference/classes/motion.md +++ b/docs/reference/classes/motion.md @@ -10,6 +10,10 @@ Holds info such as velocity, acceleration, angular velocity and angular accelera ↳ **Motion** +## Implements + +* [IFreeable](../interfaces/ifreeable.md) + ## Index ### Constructors @@ -27,6 +31,10 @@ Holds info such as velocity, acceleration, angular velocity and angular accelera * [MESSAGE_ADD](motion.md#static-message_add) * [MESSAGE_REMOVE](motion.md#static-message_remove) +### Methods + +* [Free](motion.md#free) + ## Constructors ### constructor @@ -39,8 +47,8 @@ Holds info such as velocity, acceleration, angular velocity and angular accelera Name | Type | Default | ------ | ------ | ------ | -`velocity` | [Vector](vector.md)‹› | new Vector(0,0) | -`acceleration` | [Vector](vector.md)‹› | new Vector(0,0) | +`velocity` | [Vector](vector.md)‹› | Vector.New(0, 0) | +`acceleration` | [Vector](vector.md)‹› | Vector.New(0, 0) | `angularVelocity` | number | 0 | `angularAcceleration` | number | 0 | @@ -109,3 +117,15 @@ ___ ▪ **MESSAGE_REMOVE**: *"component_remove"* = "component_remove" *Inherited from [Component](component.md).[MESSAGE_REMOVE](component.md#static-message_remove)* + +## Methods + +### Free + +▸ **Free**(): *void* + +*Implementation of [IFreeable](../interfaces/ifreeable.md)* + +*Overrides [Component](component.md).[Free](component.md#free)* + +**Returns:** *void* diff --git a/docs/reference/classes/polygon.md b/docs/reference/classes/polygon.md index fcf2602..37a949d 100644 --- a/docs/reference/classes/polygon.md +++ b/docs/reference/classes/polygon.md @@ -28,6 +28,7 @@ Can be used in collision detection and rendering. * [Center](polygon.md#center) * [Copy](polygon.md#copy) * [FarthestPointInDirection](polygon.md#farthestpointindirection) +* [Free](polygon.md#free) * [GetFloat32Array](polygon.md#getfloat32array) * [PointInside](polygon.md#pointinside) * [Transform](polygon.md#transform) @@ -110,6 +111,16 @@ Name | Type | ___ +### Free + +▸ **Free**(): *void* + +*Implementation of [IShape](../interfaces/ishape.md)* + +**Returns:** *void* + +___ + ### GetFloat32Array ▸ **GetFloat32Array**(): *Float32Array* @@ -156,7 +167,7 @@ ___ ### `Static` EllipseEstimation -▸ **EllipseEstimation**(`numOfPoints`: number, `dimensions`: [Vector](vector.md), `center`: [Vector](vector.md), `wrap`: boolean): *[Polygon](polygon.md)* +▸ **EllipseEstimation**(`numOfPoints`: number, `dimensions`: [Vector](vector.md), `centerX`: number, `centerY`: number, `wrap`: boolean): *[Polygon](polygon.md)* EllipseEstimation provides a new polygon that estimates the shape of an ellipse. @@ -166,7 +177,8 @@ Name | Type | Default | Description | ------ | ------ | ------ | ------ | `numOfPoints` | number | - | Number of points the estimation should have | `dimensions` | [Vector](vector.md) | - | Ellipse dimensions | -`center` | [Vector](vector.md) | new Vector(0, 0) | Ellipse center | +`centerX` | number | 0 | - | +`centerY` | number | 0 | - | `wrap` | boolean | false | If the polygon should wrap on itself (first point == last point) | **Returns:** *[Polygon](polygon.md)* @@ -175,7 +187,7 @@ ___ ### `Static` QuadByDimensions -▸ **QuadByDimensions**(`width`: number, `height`: number, `origin`: [Vector](vector.md)): *[Polygon](polygon.md)* +▸ **QuadByDimensions**(`width`: number, `height`: number, `originX`: number, `originY`: number): *[Polygon](polygon.md)* QuadByDimensions returns a new polygon in a quad shape with the width and height provided, optionally around an origin point @@ -186,7 +198,8 @@ Name | Type | Default | Description | ------ | ------ | ------ | ------ | `width` | number | - | Width of the quad | `height` | number | - | Height of the quad | -`origin` | [Vector](vector.md) | new Vector(0,0) | Center point of the quad | +`originX` | number | 0 | - | +`originY` | number | 0 | - | **Returns:** *[Polygon](polygon.md)* @@ -212,7 +225,7 @@ ___ ### `Static` RectangleByDimensions -▸ **RectangleByDimensions**(`width`: number, `height`: number, `origin`: [Vector](vector.md), `wrap`: boolean): *[Polygon](polygon.md)* +▸ **RectangleByDimensions**(`width`: number, `height`: number, `originX`: number, `originY`: number, `wrap`: boolean): *[Polygon](polygon.md)* RectangleByDimensions returns a new polygon in a rectangle shape with the width and height provided, optionally around an origin point. @@ -223,7 +236,8 @@ Name | Type | Default | Description | ------ | ------ | ------ | ------ | `width` | number | - | Width of the rectangle | `height` | number | - | Height of the rectangle | -`origin` | [Vector](vector.md) | new Vector(0,0) | Center point of the rectangle | +`originX` | number | 0 | - | +`originY` | number | 0 | - | `wrap` | boolean | false | - | **Returns:** *[Polygon](polygon.md)* diff --git a/docs/reference/classes/pooled.md b/docs/reference/classes/pooled.md new file mode 100644 index 0000000..79c95e7 --- /dev/null +++ b/docs/reference/classes/pooled.md @@ -0,0 +1,129 @@ + +# Class: Pooled + +Pooled is the base class for any object that needs to implement object pooling. +This base class keeps track of all object pools through static global memory, providing generic methods for +requesting objects from the pools, freeing memory up for the pools to use, and initializing the pools to a certain +size. +The pooled class also provides a required objectInPool variable to allow instances to be marked as available in the +pool, used to avoid duplicating objects in the same pool (multiple free calls on the same object). + +## Hierarchy + +* **Pooled** + + ↳ [Vector](vector.md) + + ↳ [Renderable](renderable.md) + + ↳ [TestPooledObject](testpooledobject.md) + +## Index + +### Properties + +* [objectInPool](pooled.md#objectinpool) +* [pools](pooled.md#static-protected-pools) + +### Methods + +* [free](pooled.md#static-protected-free) +* [init](pooled.md#static-protected-init) +* [new](pooled.md#static-protected-new) + +## Properties + +### objectInPool + +• **objectInPool**: *boolean* = false + +objectInPool is true if an object is made available in the object pool. If it is false it is not +currently available in the object pool. +This is used to avoid adding the same object to the same object pool multiple times if there are successive +calls to free the the same object. + +___ + +### `Static` `Protected` pools + +▪ **pools**: *Map‹string, [number, [IPoolable](../interfaces/ipoolable.md)[]]›* = new Map() + +pools is the global, static mapping of string keys to object pools. +An object pool contains two pieces of data, the maximum size of the pool (first value), and the objects that +make up the pool as an array (second value). + +## Methods + +### `Static` `Protected` free + +▸ **free**(`poolKey`: string, `obj`: [IPoolable](../interfaces/ipoolable.md)): *void* + +free is used to mark a provided object as free in the pool provided. This method can be called multiple times +with the same object, it will only add one entry to the pool. + +**Parameters:** + +Name | Type | Description | +------ | ------ | ------ | +`poolKey` | string | The key of the pool to add the object to. | +`obj` | [IPoolable](../interfaces/ipoolable.md) | The object to add to the pool. | + +**Returns:** *void* + +___ + +### `Static` `Protected` init + +▸ **init**(`poolKey`: string, `emptyGenerator`: function, `size`: number): *void* + +init is used to initialize an object pool to a certain size. This method takes a key of the pool to initialize, +an 'empty generator' which is a function that should return an empty/blank instance of the object being pooled +which can be overwritten at a later point, and the maximum size of the pool (which it will be initialized to +at the start using the empty generator). + +**Parameters:** + +▪ **poolKey**: *string* + +▪ **emptyGenerator**: *function* + +▸ (): *[IPoolable](../interfaces/ipoolable.md)* + +▪ **size**: *number* + +**Returns:** *void* + +___ + +### `Static` `Protected` new + +▸ **new**<**T**>(`poolKey`: string, `type`: object, ...`args`: any): *T* + +new is used to request a new object from the pool specified, if the pool is unavailable or empty it will use +the type to provision a new object through a constructor. +This is a generic method, it includes a cast to the generic type provided - this cast can fail if the objects +returned from the pool are not the type expected. + +**Type parameters:** + +▪ **T**: *[IPoolable](../interfaces/ipoolable.md)* + +**Parameters:** + +▪ **poolKey**: *string* + +The key of the pool to retrieve from. + +▪ **type**: *object* + +The fallback constructor to use if the pool is not initialized/empty. + +Name | Type | +------ | ------ | +`constructor` | | + +▪... **args**: *any* + +The args to use when creating/recycling the object. + +**Returns:** *T* diff --git a/docs/reference/classes/primitive.md b/docs/reference/classes/primitive.md index c0e6b8f..57a10f5 100644 --- a/docs/reference/classes/primitive.md +++ b/docs/reference/classes/primitive.md @@ -12,6 +12,10 @@ modification. ↳ **Primitive** +## Implements + +* [IFreeable](../interfaces/ifreeable.md) + ## Index ### Constructors @@ -29,6 +33,10 @@ modification. * [MESSAGE_ADD](primitive.md#static-message_add) * [MESSAGE_REMOVE](primitive.md#static-message_remove) +### Methods + +* [Free](primitive.md#free) + ## Constructors ### constructor @@ -115,3 +123,15 @@ ___ ▪ **MESSAGE_REMOVE**: *"component_remove"* = "component_remove" *Inherited from [Component](component.md).[MESSAGE_REMOVE](component.md#static-message_remove)* + +## Methods + +### Free + +▸ **Free**(): *void* + +*Implementation of [IFreeable](../interfaces/ifreeable.md)* + +*Overrides [Component](component.md).[Free](component.md#free)* + +**Returns:** *void* diff --git a/docs/reference/classes/renderable.md b/docs/reference/classes/renderable.md index 7464813..faebb00 100644 --- a/docs/reference/classes/renderable.md +++ b/docs/reference/classes/renderable.md @@ -10,7 +10,9 @@ Contains information for rendering. ## Hierarchy -* **Renderable** +* [Pooled](pooled.md) + + ↳ **Renderable** ## Implements @@ -28,9 +30,23 @@ Contains information for rendering. * [drawMode](renderable.md#drawmode) * [material](renderable.md#material) * [modelMatrix](renderable.md#modelmatrix) +* [objectInPool](renderable.md#objectinpool) * [payload](renderable.md#optional-payload) * [vertices](renderable.md#vertices) * [zOrder](renderable.md#zorder) +* [POOL_KEY](renderable.md#static-private-pool_key) +* [pools](renderable.md#static-protected-pools) + +### Methods + +* [Free](renderable.md#free) +* [Recycle](renderable.md#recycle) +* [Free](renderable.md#static-free) +* [Init](renderable.md#static-init) +* [New](renderable.md#static-new) +* [free](renderable.md#static-protected-free) +* [init](renderable.md#static-protected-init) +* [new](renderable.md#static-protected-new) ## Constructors @@ -96,6 +112,21 @@ The model matrix (position, scale, rotation) of the object to render. ___ +### objectInPool + +• **objectInPool**: *boolean* = false + +*Implementation of [IRenderable](../interfaces/irenderable.md).[objectInPool](../interfaces/irenderable.md#objectinpool)* + +*Inherited from [Pooled](pooled.md).[objectInPool](pooled.md#objectinpool)* + +objectInPool is true if an object is made available in the object pool. If it is false it is not +currently available in the object pool. +This is used to avoid adding the same object to the same object pool multiple times if there are successive +calls to free the the same object. + +___ + ### `Optional` payload • **payload**? : *T* @@ -123,3 +154,189 @@ ___ The Z-Order of the object, the order at which the object will appear infront or behind other objects. A higher Z-Order means in front, a lower Z-Order means behind. + +___ + +### `Static` `Private` POOL_KEY + +▪ **POOL_KEY**: *string* = "jamjar_renderable" + +___ + +### `Static` `Protected` pools + +▪ **pools**: *Map‹string, [number, [IPoolable](../interfaces/ipoolable.md)[]]›* = new Map() + +*Inherited from [Pooled](pooled.md).[pools](pooled.md#static-protected-pools)* + +pools is the global, static mapping of string keys to object pools. +An object pool contains two pieces of data, the maximum size of the pool (first value), and the objects that +make up the pool as an array (second value). + +## Methods + +### Free + +▸ **Free**(): *void* + +*Implementation of [IRenderable](../interfaces/irenderable.md)* + +**Returns:** *void* + +___ + +### Recycle + +▸ **Recycle**(`zOrder`: number, `vertices`: [Polygon](polygon.md), `modelMatrix`: [Matrix4D](matrix4d.md), `material`: [Material](material.md), `drawMode`: [DrawMode](../enums/drawmode.md), `payload?`: T, `camera?`: [IEntity](../interfaces/ientity.md)): *[Renderable](renderable.md)‹T›* + +**Parameters:** + +Name | Type | +------ | ------ | +`zOrder` | number | +`vertices` | [Polygon](polygon.md) | +`modelMatrix` | [Matrix4D](matrix4d.md) | +`material` | [Material](material.md) | +`drawMode` | [DrawMode](../enums/drawmode.md) | +`payload?` | T | +`camera?` | [IEntity](../interfaces/ientity.md) | + +**Returns:** *[Renderable](renderable.md)‹T›* + +___ + +### `Static` Free + +▸ **Free**<**T**>(`obj`: [Renderable](renderable.md)‹T›): *void* + +**Type parameters:** + +▪ **T** + +**Parameters:** + +Name | Type | +------ | ------ | +`obj` | [Renderable](renderable.md)‹T› | + +**Returns:** *void* + +___ + +### `Static` Init + +▸ **Init**(`size`: number): *void* + +**Parameters:** + +Name | Type | +------ | ------ | +`size` | number | + +**Returns:** *void* + +___ + +### `Static` New + +▸ **New**<**T**>(`zOrder`: number, `vertices`: [Polygon](polygon.md), `modelMatrix`: [Matrix4D](matrix4d.md), `material`: [Material](material.md), `drawMode`: [DrawMode](../enums/drawmode.md), `payload?`: T, `camera?`: [IEntity](../interfaces/ientity.md)): *[Renderable](renderable.md)‹T›* + +**Type parameters:** + +▪ **T** + +**Parameters:** + +Name | Type | +------ | ------ | +`zOrder` | number | +`vertices` | [Polygon](polygon.md) | +`modelMatrix` | [Matrix4D](matrix4d.md) | +`material` | [Material](material.md) | +`drawMode` | [DrawMode](../enums/drawmode.md) | +`payload?` | T | +`camera?` | [IEntity](../interfaces/ientity.md) | + +**Returns:** *[Renderable](renderable.md)‹T›* + +___ + +### `Static` `Protected` free + +▸ **free**(`poolKey`: string, `obj`: [IPoolable](../interfaces/ipoolable.md)): *void* + +*Inherited from [Pooled](pooled.md).[free](pooled.md#static-protected-free)* + +free is used to mark a provided object as free in the pool provided. This method can be called multiple times +with the same object, it will only add one entry to the pool. + +**Parameters:** + +Name | Type | Description | +------ | ------ | ------ | +`poolKey` | string | The key of the pool to add the object to. | +`obj` | [IPoolable](../interfaces/ipoolable.md) | The object to add to the pool. | + +**Returns:** *void* + +___ + +### `Static` `Protected` init + +▸ **init**(`poolKey`: string, `emptyGenerator`: function, `size`: number): *void* + +*Inherited from [Pooled](pooled.md).[init](pooled.md#static-protected-init)* + +init is used to initialize an object pool to a certain size. This method takes a key of the pool to initialize, +an 'empty generator' which is a function that should return an empty/blank instance of the object being pooled +which can be overwritten at a later point, and the maximum size of the pool (which it will be initialized to +at the start using the empty generator). + +**Parameters:** + +▪ **poolKey**: *string* + +▪ **emptyGenerator**: *function* + +▸ (): *[IPoolable](../interfaces/ipoolable.md)* + +▪ **size**: *number* + +**Returns:** *void* + +___ + +### `Static` `Protected` new + +▸ **new**<**T**>(`poolKey`: string, `type`: object, ...`args`: any): *T* + +*Inherited from [Pooled](pooled.md).[new](pooled.md#static-protected-new)* + +new is used to request a new object from the pool specified, if the pool is unavailable or empty it will use +the type to provision a new object through a constructor. +This is a generic method, it includes a cast to the generic type provided - this cast can fail if the objects +returned from the pool are not the type expected. + +**Type parameters:** + +▪ **T**: *[IPoolable](../interfaces/ipoolable.md)* + +**Parameters:** + +▪ **poolKey**: *string* + +The key of the pool to retrieve from. + +▪ **type**: *object* + +The fallback constructor to use if the pool is not initialized/empty. + +Name | Type | +------ | ------ | +`constructor` | | + +▪... **args**: *any* + +The args to use when creating/recycling the object. + +**Returns:** *T* diff --git a/docs/reference/classes/script.md b/docs/reference/classes/script.md index 987ff76..3d242af 100644 --- a/docs/reference/classes/script.md +++ b/docs/reference/classes/script.md @@ -10,6 +10,10 @@ to an entity that will be available to the script at runtime. ↳ **Script** +## Implements + +* [IFreeable](../interfaces/ifreeable.md) + ## Index ### Constructors @@ -25,6 +29,10 @@ to an entity that will be available to the script at runtime. * [MESSAGE_ADD](script.md#static-message_add) * [MESSAGE_REMOVE](script.md#static-message_remove) +### Methods + +* [Free](script.md#free) + ## Constructors ### constructor @@ -87,3 +95,15 @@ ___ ▪ **MESSAGE_REMOVE**: *"component_remove"* = "component_remove" *Inherited from [Component](component.md).[MESSAGE_REMOVE](component.md#static-message_remove)* + +## Methods + +### Free + +▸ **Free**(): *void* + +*Implementation of [IFreeable](../interfaces/ifreeable.md)* + +*Inherited from [Component](component.md).[Free](component.md#free)* + +**Returns:** *void* diff --git a/docs/reference/classes/sprite.md b/docs/reference/classes/sprite.md index 8983e1b..de2e2c3 100644 --- a/docs/reference/classes/sprite.md +++ b/docs/reference/classes/sprite.md @@ -13,6 +13,10 @@ Can contain texture information such as bounds and a texture. ↳ **Sprite** +## Implements + +* [IFreeable](../interfaces/ifreeable.md) + ## Index ### Constructors @@ -28,6 +32,10 @@ Can contain texture information such as bounds and a texture. * [MESSAGE_ADD](sprite.md#static-message_add) * [MESSAGE_REMOVE](sprite.md#static-message_remove) +### Methods + +* [Free](sprite.md#free) + ## Constructors ### constructor @@ -94,3 +102,15 @@ ___ ▪ **MESSAGE_REMOVE**: *"component_remove"* = "component_remove" *Inherited from [Component](component.md).[MESSAGE_REMOVE](component.md#static-message_remove)* + +## Methods + +### Free + +▸ **Free**(): *void* + +*Implementation of [IFreeable](../interfaces/ifreeable.md)* + +*Overrides [Component](component.md).[Free](component.md#free)* + +**Returns:** *void* diff --git a/docs/reference/classes/spriteanimator.md b/docs/reference/classes/spriteanimator.md index a91e581..fc075f4 100644 --- a/docs/reference/classes/spriteanimator.md +++ b/docs/reference/classes/spriteanimator.md @@ -14,6 +14,10 @@ SpriteAnimator should be used with a Sprite component in conjunction. ↳ **SpriteAnimator** +## Implements + +* [IFreeable](../interfaces/ifreeable.md) + ## Index ### Constructors @@ -29,6 +33,10 @@ SpriteAnimator should be used with a Sprite component in conjunction. * [MESSAGE_ADD](spriteanimator.md#static-message_add) * [MESSAGE_REMOVE](spriteanimator.md#static-message_remove) +### Methods + +* [Free](spriteanimator.md#free) + ## Constructors ### constructor @@ -95,3 +103,15 @@ ___ ▪ **MESSAGE_REMOVE**: *"component_remove"* = "component_remove" *Inherited from [Component](component.md).[MESSAGE_REMOVE](component.md#static-message_remove)* + +## Methods + +### Free + +▸ **Free**(): *void* + +*Implementation of [IFreeable](../interfaces/ifreeable.md)* + +*Inherited from [Component](component.md).[Free](component.md#free)* + +**Returns:** *void* diff --git a/docs/reference/classes/testpooledobject.md b/docs/reference/classes/testpooledobject.md new file mode 100644 index 0000000..32b9db3 --- /dev/null +++ b/docs/reference/classes/testpooledobject.md @@ -0,0 +1,249 @@ + +# Class: TestPooledObject + +## Hierarchy + +* [Pooled](pooled.md) + + ↳ **TestPooledObject** + +## Index + +### Constructors + +* [constructor](testpooledobject.md#constructor) + +### Properties + +* [id](testpooledobject.md#id) +* [objectInPool](testpooledobject.md#objectinpool) +* [CURRENT_ID](testpooledobject.md#static-current_id) +* [POOL_KEY](testpooledobject.md#static-pool_key) +* [pools](testpooledobject.md#static-protected-pools) + +### Methods + +* [Free](testpooledobject.md#free) +* [Recycle](testpooledobject.md#recycle) +* [GetPools](testpooledobject.md#static-getpools) +* [SetPools](testpooledobject.md#static-setpools) +* [SimulateFree](testpooledobject.md#static-simulatefree) +* [SimulateInit](testpooledobject.md#static-simulateinit) +* [SimulateNew](testpooledobject.md#static-simulatenew) +* [free](testpooledobject.md#static-protected-free) +* [init](testpooledobject.md#static-protected-init) +* [new](testpooledobject.md#static-protected-new) + +## Constructors + +### constructor + +\+ **new TestPooledObject**(`id?`: undefined | number, `inPool`: boolean): *[TestPooledObject](testpooledobject.md)* + +**Parameters:** + +Name | Type | Default | +------ | ------ | ------ | +`id?` | undefined | number | - | +`inPool` | boolean | false | + +**Returns:** *[TestPooledObject](testpooledobject.md)* + +## Properties + +### id + +• **id**: *number* + +___ + +### objectInPool + +• **objectInPool**: *boolean* = false + +*Inherited from [Pooled](pooled.md).[objectInPool](pooled.md#objectinpool)* + +objectInPool is true if an object is made available in the object pool. If it is false it is not +currently available in the object pool. +This is used to avoid adding the same object to the same object pool multiple times if there are successive +calls to free the the same object. + +___ + +### `Static` CURRENT_ID + +▪ **CURRENT_ID**: *number* = 0 + +___ + +### `Static` POOL_KEY + +▪ **POOL_KEY**: *"test_pooled_object_pool"* = "test_pooled_object_pool" + +___ + +### `Static` `Protected` pools + +▪ **pools**: *Map‹string, [number, [IPoolable](../interfaces/ipoolable.md)[]]›* = new Map() + +*Inherited from [Pooled](pooled.md).[pools](pooled.md#static-protected-pools)* + +pools is the global, static mapping of string keys to object pools. +An object pool contains two pieces of data, the maximum size of the pool (first value), and the objects that +make up the pool as an array (second value). + +## Methods + +### Free + +▸ **Free**(): *void* + +**Returns:** *void* + +___ + +### Recycle + +▸ **Recycle**(): *[TestPooledObject](testpooledobject.md)* + +**Returns:** *[TestPooledObject](testpooledobject.md)* + +___ + +### `Static` GetPools + +▸ **GetPools**(): *Map‹string, [number, [IPoolable](../interfaces/ipoolable.md)[]]›* + +**Returns:** *Map‹string, [number, [IPoolable](../interfaces/ipoolable.md)[]]›* + +___ + +### `Static` SetPools + +▸ **SetPools**(`pools`: Map‹string, [number, [IPoolable](../interfaces/ipoolable.md)[]]›): *void* + +**Parameters:** + +Name | Type | +------ | ------ | +`pools` | Map‹string, [number, [IPoolable](../interfaces/ipoolable.md)[]]› | + +**Returns:** *void* + +___ + +### `Static` SimulateFree + +▸ **SimulateFree**(`obj`: [TestPooledObject](testpooledobject.md)): *void* + +**Parameters:** + +Name | Type | +------ | ------ | +`obj` | [TestPooledObject](testpooledobject.md) | + +**Returns:** *void* + +___ + +### `Static` SimulateInit + +▸ **SimulateInit**(`size`: number): *void* + +**Parameters:** + +Name | Type | +------ | ------ | +`size` | number | + +**Returns:** *void* + +___ + +### `Static` SimulateNew + +▸ **SimulateNew**(): *[TestPooledObject](testpooledobject.md)* + +**Returns:** *[TestPooledObject](testpooledobject.md)* + +___ + +### `Static` `Protected` free + +▸ **free**(`poolKey`: string, `obj`: [IPoolable](../interfaces/ipoolable.md)): *void* + +*Inherited from [Pooled](pooled.md).[free](pooled.md#static-protected-free)* + +free is used to mark a provided object as free in the pool provided. This method can be called multiple times +with the same object, it will only add one entry to the pool. + +**Parameters:** + +Name | Type | Description | +------ | ------ | ------ | +`poolKey` | string | The key of the pool to add the object to. | +`obj` | [IPoolable](../interfaces/ipoolable.md) | The object to add to the pool. | + +**Returns:** *void* + +___ + +### `Static` `Protected` init + +▸ **init**(`poolKey`: string, `emptyGenerator`: function, `size`: number): *void* + +*Inherited from [Pooled](pooled.md).[init](pooled.md#static-protected-init)* + +init is used to initialize an object pool to a certain size. This method takes a key of the pool to initialize, +an 'empty generator' which is a function that should return an empty/blank instance of the object being pooled +which can be overwritten at a later point, and the maximum size of the pool (which it will be initialized to +at the start using the empty generator). + +**Parameters:** + +▪ **poolKey**: *string* + +▪ **emptyGenerator**: *function* + +▸ (): *[IPoolable](../interfaces/ipoolable.md)* + +▪ **size**: *number* + +**Returns:** *void* + +___ + +### `Static` `Protected` new + +▸ **new**<**T**>(`poolKey`: string, `type`: object, ...`args`: any): *T* + +*Inherited from [Pooled](pooled.md).[new](pooled.md#static-protected-new)* + +new is used to request a new object from the pool specified, if the pool is unavailable or empty it will use +the type to provision a new object through a constructor. +This is a generic method, it includes a cast to the generic type provided - this cast can fail if the objects +returned from the pool are not the type expected. + +**Type parameters:** + +▪ **T**: *[IPoolable](../interfaces/ipoolable.md)* + +**Parameters:** + +▪ **poolKey**: *string* + +The key of the pool to retrieve from. + +▪ **type**: *object* + +The fallback constructor to use if the pool is not initialized/empty. + +Name | Type | +------ | ------ | +`constructor` | | + +▪... **args**: *any* + +The args to use when creating/recycling the object. + +**Returns:** *T* diff --git a/docs/reference/classes/text.md b/docs/reference/classes/text.md index 8e2c59b..34ca4ff 100644 --- a/docs/reference/classes/text.md +++ b/docs/reference/classes/text.md @@ -10,6 +10,10 @@ rendering it such as font, alignment, color and shaders. ↳ **Text** +## Implements + +* [IFreeable](../interfaces/ifreeable.md) + ## Index ### Constructors @@ -32,6 +36,10 @@ rendering it such as font, alignment, color and shaders. * [MESSAGE_ADD](text.md#static-message_add) * [MESSAGE_REMOVE](text.md#static-message_remove) +### Methods + +* [Free](text.md#free) + ## Constructors ### constructor @@ -49,7 +57,7 @@ Name | Type | Default | `font` | string | - | `align` | [TextAlignment](../enums/textalignment.md) | TextAlignment.Left | `spacing` | number | Text.DEFAULT_SPACING | -`offset` | [Vector](vector.md) | new Vector(0,0) | +`offset` | [Vector](vector.md) | Vector.New(0,0) | `color` | [Color](color.md) | new Color(0, 0, 0, 1) | `shaders` | string[] | [ ShaderAsset.DEFAULT_TEXTURE_VERTEX_SHADER_NAME, @@ -165,3 +173,15 @@ ___ ▪ **MESSAGE_REMOVE**: *"component_remove"* = "component_remove" *Inherited from [Component](component.md).[MESSAGE_REMOVE](component.md#static-message_remove)* + +## Methods + +### Free + +▸ **Free**(): *void* + +*Implementation of [IFreeable](../interfaces/ifreeable.md)* + +*Overrides [Component](component.md).[Free](component.md#free)* + +**Returns:** *void* diff --git a/docs/reference/classes/texture.md b/docs/reference/classes/texture.md index 54118e8..ac83f80 100644 --- a/docs/reference/classes/texture.md +++ b/docs/reference/classes/texture.md @@ -8,6 +8,10 @@ how the texture should be drawn and represented. * **Texture** +## Implements + +* [IFreeable](../interfaces/ifreeable.md) + ## Index ### Constructors @@ -22,6 +26,7 @@ how the texture should be drawn and represented. ### Methods * [Copy](texture.md#copy) +* [Free](texture.md#free) * [GenerateSpritesheetIndex](texture.md#static-generatespritesheetindex) ## Constructors @@ -35,7 +40,7 @@ how the texture should be drawn and represented. Name | Type | Default | ------ | ------ | ------ | `image` | string | - | -`points` | [Polygon](polygon.md) | Polygon.QuadByPoints(new Vector(0,0), new Vector(1,1)) | +`points` | [Polygon](polygon.md) | Polygon.QuadByPoints(Vector.New(0,0), Vector.New(1,1)) | **Returns:** *[Texture](texture.md)* @@ -67,6 +72,16 @@ Make a value copy of the texture. ___ +### Free + +▸ **Free**(): *void* + +*Implementation of [IFreeable](../interfaces/ifreeable.md)* + +**Returns:** *void* + +___ + ### `Static` GenerateSpritesheetIndex ▸ **GenerateSpritesheetIndex**(`rowCount`: number, `columnCount`: number): *[Polygon](polygon.md)[]* diff --git a/docs/reference/classes/transform.md b/docs/reference/classes/transform.md index a3460ee..4f86005 100644 --- a/docs/reference/classes/transform.md +++ b/docs/reference/classes/transform.md @@ -11,6 +11,10 @@ Frequently used in rendering, collisions and physics. ↳ **Transform** +## Implements + +* [IFreeable](../interfaces/ifreeable.md) + ## Index ### Constructors @@ -30,6 +34,7 @@ Frequently used in rendering, collisions and physics. ### Methods +* [Free](transform.md#free) * [InterpolatedMatrix4D](transform.md#interpolatedmatrix4d) * [Matrix3D](transform.md#matrix3d) * [Matrix4D](transform.md#matrix4d) @@ -46,8 +51,8 @@ Frequently used in rendering, collisions and physics. Name | Type | Default | ------ | ------ | ------ | -`position` | [Vector](vector.md)‹› | new Vector(0,0) | -`scale` | [Vector](vector.md)‹› | new Vector(1,1) | +`position` | [Vector](vector.md)‹› | Vector.New(0, 0) | +`scale` | [Vector](vector.md)‹› | Vector.New(1, 1) | `angle` | number | 0 | **Returns:** *[Transform](transform.md)* @@ -118,6 +123,18 @@ ___ ## Methods +### Free + +▸ **Free**(): *void* + +*Implementation of [IFreeable](../interfaces/ifreeable.md)* + +*Overrides [Component](component.md).[Free](component.md#free)* + +**Returns:** *void* + +___ + ### InterpolatedMatrix4D ▸ **InterpolatedMatrix4D**(`alpha`: number): *[Matrix4D](matrix4d.md)* diff --git a/docs/reference/classes/ui.md b/docs/reference/classes/ui.md index 59b2955..01be1e0 100644 --- a/docs/reference/classes/ui.md +++ b/docs/reference/classes/ui.md @@ -13,6 +13,10 @@ than in world space. ↳ **UI** +## Implements + +* [IFreeable](../interfaces/ifreeable.md) + ## Index ### Constructors @@ -27,6 +31,10 @@ than in world space. * [MESSAGE_ADD](ui.md#static-message_add) * [MESSAGE_REMOVE](ui.md#static-message_remove) +### Methods + +* [Free](ui.md#free) + ## Constructors ### constructor @@ -82,3 +90,15 @@ ___ ▪ **MESSAGE_REMOVE**: *"component_remove"* = "component_remove" *Inherited from [Component](component.md).[MESSAGE_REMOVE](component.md#static-message_remove)* + +## Methods + +### Free + +▸ **Free**(): *void* + +*Implementation of [IFreeable](../interfaces/ifreeable.md)* + +*Inherited from [Component](component.md).[Free](component.md#free)* + +**Returns:** *void* diff --git a/docs/reference/classes/vector.md b/docs/reference/classes/vector.md index 7cc71ac..1fb0bc1 100644 --- a/docs/reference/classes/vector.md +++ b/docs/reference/classes/vector.md @@ -6,7 +6,13 @@ This is a mutable data structure, operations on Vector objects will affect the o ## Hierarchy -* **Vector** +* [Pooled](pooled.md) + + ↳ **Vector** + +## Implements + +* [IPoolable](../interfaces/ipoolable.md) ## Index @@ -17,6 +23,9 @@ This is a mutable data structure, operations on Vector objects will affect the o ### Properties * [data](vector.md#private-data) +* [objectInPool](vector.md#objectinpool) +* [POOL_KEY](vector.md#static-private-pool_key) +* [pools](vector.md#static-protected-pools) ### Accessors @@ -31,14 +40,22 @@ This is a mutable data structure, operations on Vector objects will affect the o * [Copy](vector.md#copy) * [Dot](vector.md#dot) * [Equals](vector.md#equals) +* [Free](vector.md#free) * [Invert](vector.md#invert) * [Magnitude](vector.md#magnitude) * [Multiply](vector.md#multiply) * [Normalize](vector.md#normalize) +* [Recycle](vector.md#recycle) * [Rotate](vector.md#rotate) * [RotateDeg](vector.md#rotatedeg) * [Scale](vector.md#scale) * [Sub](vector.md#sub) +* [Free](vector.md#static-free) +* [Init](vector.md#static-init) +* [New](vector.md#static-new) +* [free](vector.md#static-protected-free) +* [init](vector.md#static-protected-init) +* [new](vector.md#static-protected-new) ## Constructors @@ -61,6 +78,41 @@ Name | Type | • **data**: *Float32Array* +___ + +### objectInPool + +• **objectInPool**: *boolean* = false + +*Implementation of [IPoolable](../interfaces/ipoolable.md).[objectInPool](../interfaces/ipoolable.md#objectinpool)* + +*Inherited from [Pooled](pooled.md).[objectInPool](pooled.md#objectinpool)* + +objectInPool is true if an object is made available in the object pool. If it is false it is not +currently available in the object pool. +This is used to avoid adding the same object to the same object pool multiple times if there are successive +calls to free the the same object. + +___ + +### `Static` `Private` POOL_KEY + +▪ **POOL_KEY**: *string* = "jamjar_vector" + +Value of the Vector object pool. + +___ + +### `Static` `Protected` pools + +▪ **pools**: *Map‹string, [number, [IPoolable](../interfaces/ipoolable.md)[]]›* = new Map() + +*Inherited from [Pooled](pooled.md).[pools](pooled.md#static-protected-pools)* + +pools is the global, static mapping of string keys to object pools. +An object pool contains two pieces of data, the maximum size of the pool (first value), and the objects that +make up the pool as an array (second value). + ## Accessors ### x @@ -199,6 +251,16 @@ Name | Type | Description | ___ +### Free + +▸ **Free**(): *void* + +*Implementation of [IPoolable](../interfaces/ipoolable.md)* + +**Returns:** *void* + +___ + ### Invert ▸ **Invert**(): *[Vector](vector.md)* @@ -254,6 +316,21 @@ This vector to allow chaining, the normalized vector ___ +### Recycle + +▸ **Recycle**(`x`: number, `y`: number): *[Vector](vector.md)* + +**Parameters:** + +Name | Type | +------ | ------ | +`x` | number | +`y` | number | + +**Returns:** *[Vector](vector.md)* + +___ + ### Rotate ▸ **Rotate**(`center`: [Vector](vector.md), `angle`: number): *[Vector](vector.md)* @@ -327,3 +404,134 @@ Name | Type | Description | **Returns:** *[Vector](vector.md)* This vector to allow chaining, the result result of the subtraction + +___ + +### `Static` Free + +▸ **Free**(`obj`: [Vector](vector.md)): *void* + +Free the provided vector. + +**Parameters:** + +Name | Type | +------ | ------ | +`obj` | [Vector](vector.md) | + +**Returns:** *void* + +___ + +### `Static` Init + +▸ **Init**(`size`: number): *void* + +Initialize the Vector pool to the size provided. + +**Parameters:** + +Name | Type | +------ | ------ | +`size` | number | + +**Returns:** *void* + +___ + +### `Static` New + +▸ **New**(`x`: number, `y`: number): *[Vector](vector.md)* + +Create a Vector.New, using pooling if available. + +**Parameters:** + +Name | Type | +------ | ------ | +`x` | number | +`y` | number | + +**Returns:** *[Vector](vector.md)* + +___ + +### `Static` `Protected` free + +▸ **free**(`poolKey`: string, `obj`: [IPoolable](../interfaces/ipoolable.md)): *void* + +*Inherited from [Pooled](pooled.md).[free](pooled.md#static-protected-free)* + +free is used to mark a provided object as free in the pool provided. This method can be called multiple times +with the same object, it will only add one entry to the pool. + +**Parameters:** + +Name | Type | Description | +------ | ------ | ------ | +`poolKey` | string | The key of the pool to add the object to. | +`obj` | [IPoolable](../interfaces/ipoolable.md) | The object to add to the pool. | + +**Returns:** *void* + +___ + +### `Static` `Protected` init + +▸ **init**(`poolKey`: string, `emptyGenerator`: function, `size`: number): *void* + +*Inherited from [Pooled](pooled.md).[init](pooled.md#static-protected-init)* + +init is used to initialize an object pool to a certain size. This method takes a key of the pool to initialize, +an 'empty generator' which is a function that should return an empty/blank instance of the object being pooled +which can be overwritten at a later point, and the maximum size of the pool (which it will be initialized to +at the start using the empty generator). + +**Parameters:** + +▪ **poolKey**: *string* + +▪ **emptyGenerator**: *function* + +▸ (): *[IPoolable](../interfaces/ipoolable.md)* + +▪ **size**: *number* + +**Returns:** *void* + +___ + +### `Static` `Protected` new + +▸ **new**<**T**>(`poolKey`: string, `type`: object, ...`args`: any): *T* + +*Inherited from [Pooled](pooled.md).[new](pooled.md#static-protected-new)* + +new is used to request a new object from the pool specified, if the pool is unavailable or empty it will use +the type to provision a new object through a constructor. +This is a generic method, it includes a cast to the generic type provided - this cast can fail if the objects +returned from the pool are not the type expected. + +**Type parameters:** + +▪ **T**: *[IPoolable](../interfaces/ipoolable.md)* + +**Parameters:** + +▪ **poolKey**: *string* + +The key of the pool to retrieve from. + +▪ **type**: *object* + +The fallback constructor to use if the pool is not initialized/empty. + +Name | Type | +------ | ------ | +`constructor` | | + +▪... **args**: *any* + +The args to use when creating/recycling the object. + +**Returns:** *T* diff --git a/docs/reference/interfaces/ifreeable.md b/docs/reference/interfaces/ifreeable.md new file mode 100644 index 0000000..ec06382 --- /dev/null +++ b/docs/reference/interfaces/ifreeable.md @@ -0,0 +1,47 @@ + +# Interface: IFreeable + +IFreeable defines the contract for an object that either is poolable or contains poolable elements, allowing the +object to be freed/it's constituent parts freed back to object pools. + +## Hierarchy + +* **IFreeable** + + ↳ [IPoolable](ipoolable.md) + + ↳ [IShape](ishape.md) + +## Implemented by + +* [AudioSource](../classes/audiosource.md) +* [Camera](../classes/camera.md) +* [Collider](../classes/collider.md) +* [Component](../classes/component.md) +* [FakeComponent](../classes/fakecomponent.md) +* [Material](../classes/material.md) +* [Motion](../classes/motion.md) +* [Primitive](../classes/primitive.md) +* [Script](../classes/script.md) +* [Sprite](../classes/sprite.md) +* [SpriteAnimator](../classes/spriteanimator.md) +* [Text](../classes/text.md) +* [Texture](../classes/texture.md) +* [Transform](../classes/transform.md) +* [UI](../classes/ui.md) + +## Index + +### Methods + +* [Free](ifreeable.md#free) + +## Methods + +### Free + +▸ **Free**(): *void* + +Free releases an object or it's constituent parts back into any available object pools. + +**Returns:** *void* diff --git a/docs/reference/interfaces/imessagebus.md b/docs/reference/interfaces/imessagebus.md index 99822f7..5ed1300 100644 --- a/docs/reference/interfaces/imessagebus.md +++ b/docs/reference/interfaces/imessagebus.md @@ -1,6 +1,9 @@ # Interface: IMessageBus +IMessageBus defines the contract for a message bus, handling receiver subscription/unsubcription, message publishing, +and message dispatching. + ## Hierarchy * **IMessageBus** @@ -15,6 +18,7 @@ ### Properties * [Dispatch](imessagebus.md#dispatch) +* [DispatchUntilEmpty](imessagebus.md#dispatchuntilempty) * [Publish](imessagebus.md#publish) * [Subscribe](imessagebus.md#subscribe) * [Unsubscribe](imessagebus.md#unsubscribe) @@ -26,6 +30,23 @@ • **Dispatch**: *function* +Dispatch processes the current message bus queue and forwards the messages to the subscribers who have +subscribed to each message type. + +#### Type declaration: + +▸ (): *void* + +___ + +### DispatchUntilEmpty + +• **DispatchUntilEmpty**: *function* + +DispatchUntilEmpty repeatedly dispatches until the message queue is empty, used to make sure everything is +processed, e.g. if there is a message that causes a new message to be added, it will ensure that all recursive +messages are processed. + #### Type declaration: ▸ (): *void* @@ -36,6 +57,10 @@ ___ • **Publish**: *function* +Publish adds a message to the message bus queue to be dispatched. + +**`param`** The message to send + #### Type declaration: ▸ (`message`: [IMessage](imessage.md)): *void* @@ -52,15 +77,21 @@ ___ • **Subscribe**: *function* +Subscribe subscibes a subscriber to a particular message type or types. + +**`param`** The subscriber to the message type(s) + +**`param`** The message type(s) to subscribe to + #### Type declaration: -▸ (`subscriber`: [Subscriber](../classes/subscriber.md), `types`: string | string[]): *void* +▸ (`subscriber`: [ISubscriber](isubscriber.md), `types`: string | string[]): *void* **Parameters:** Name | Type | ------ | ------ | -`subscriber` | [Subscriber](../classes/subscriber.md) | +`subscriber` | [ISubscriber](isubscriber.md) | `types` | string | string[] | ___ @@ -69,15 +100,21 @@ ___ • **Unsubscribe**: *function* +Unsubscribe unsubscribes a subscriber from a specific message type or types. + +**`param`** The subscriber to unsubscribe + +**`param`** The message type(s) to unsubscribe from + #### Type declaration: -▸ (`subscriber`: [Subscriber](../classes/subscriber.md), `types`: string | string[]): *void* +▸ (`subscriber`: [ISubscriber](isubscriber.md), `types`: string | string[]): *void* **Parameters:** Name | Type | ------ | ------ | -`subscriber` | [Subscriber](../classes/subscriber.md) | +`subscriber` | [ISubscriber](isubscriber.md) | `types` | string | string[] | ___ @@ -86,12 +123,16 @@ ___ • **UnsubscribeAll**: *function* +UnsubscribeAll unsubscribes a Subscriber from all messages. + +**`param`** The subscriber to unsubscribe + #### Type declaration: -▸ (`subscriber`: [Subscriber](../classes/subscriber.md)): *void* +▸ (`subscriber`: [ISubscriber](isubscriber.md)): *void* **Parameters:** Name | Type | ------ | ------ | -`subscriber` | [Subscriber](../classes/subscriber.md) | +`subscriber` | [ISubscriber](isubscriber.md) | diff --git a/docs/reference/interfaces/ipoolable.md b/docs/reference/interfaces/ipoolable.md new file mode 100644 index 0000000..d1a859a --- /dev/null +++ b/docs/reference/interfaces/ipoolable.md @@ -0,0 +1,64 @@ + +# Interface: IPoolable + +IPoolable defines the required properties of an object that is able to be pooled using an object pool. + +## Hierarchy + +* [IFreeable](ifreeable.md) + + ↳ **IPoolable** + + ↳ [IRenderable](irenderable.md) + +## Implemented by + +* [Vector](../classes/vector.md) + +## Index + +### Properties + +* [objectInPool](ipoolable.md#objectinpool) + +### Methods + +* [Free](ipoolable.md#free) +* [Recycle](ipoolable.md#recycle) + +## Properties + +### objectInPool + +• **objectInPool**: *boolean* + +objectInPool is used to mark if the instance of the object is currently pooled. + +## Methods + +### Free + +▸ **Free**(): *void* + +*Inherited from [IFreeable](ifreeable.md).[Free](ifreeable.md#free)* + +Free releases an object or it's constituent parts back into any available object pools. + +**Returns:** *void* + +___ + +### Recycle + +▸ **Recycle**(...`args`: any): *[IPoolable](ipoolable.md)* + +Recycle is used to reuse an existing object instance, using the arguments provided - similar to a constructor, +but must be repeatable. + +**Parameters:** + +Name | Type | Description | +------ | ------ | ------ | +`...args` | any | The arguments to use when recycling the object instance | + +**Returns:** *[IPoolable](ipoolable.md)* diff --git a/docs/reference/interfaces/irenderable.md b/docs/reference/interfaces/irenderable.md index b6ff394..e6f2360 100644 --- a/docs/reference/interfaces/irenderable.md +++ b/docs/reference/interfaces/irenderable.md @@ -6,7 +6,9 @@ Contains information for rendering. ## Hierarchy -* **IRenderable** + ↳ [IPoolable](ipoolable.md) + + ↳ **IRenderable** ## Implemented by @@ -20,9 +22,15 @@ Contains information for rendering. * [drawMode](irenderable.md#drawmode) * [material](irenderable.md#material) * [modelMatrix](irenderable.md#modelmatrix) +* [objectInPool](irenderable.md#objectinpool) * [vertices](irenderable.md#vertices) * [zOrder](irenderable.md#zorder) +### Methods + +* [Free](irenderable.md#free) +* [Recycle](irenderable.md#recycle) + ## Properties ### `Optional` camera @@ -59,6 +67,16 @@ The model matrix (position, scale, rotation) of the object to render. ___ +### objectInPool + +• **objectInPool**: *boolean* + +*Inherited from [IPoolable](ipoolable.md).[objectInPool](ipoolable.md#objectinpool)* + +objectInPool is used to mark if the instance of the object is currently pooled. + +___ + ### vertices • **vertices**: *[Polygon](../classes/polygon.md)* @@ -74,3 +92,34 @@ ___ The Z-Order of the object, the order at which the object will appear infront or behind other objects. A higher Z-Order means in front, a lower Z-Order means behind. + +## Methods + +### Free + +▸ **Free**(): *void* + +*Inherited from [IFreeable](ifreeable.md).[Free](ifreeable.md#free)* + +Free releases an object or it's constituent parts back into any available object pools. + +**Returns:** *void* + +___ + +### Recycle + +▸ **Recycle**(...`args`: any): *[IPoolable](ipoolable.md)* + +*Inherited from [IPoolable](ipoolable.md).[Recycle](ipoolable.md#recycle)* + +Recycle is used to reuse an existing object instance, using the arguments provided - similar to a constructor, +but must be repeatable. + +**Parameters:** + +Name | Type | Description | +------ | ------ | ------ | +`...args` | any | The arguments to use when recycling the object instance | + +**Returns:** *[IPoolable](ipoolable.md)* diff --git a/docs/reference/interfaces/ishape.md b/docs/reference/interfaces/ishape.md index 95d3c40..e0adcf7 100644 --- a/docs/reference/interfaces/ishape.md +++ b/docs/reference/interfaces/ishape.md @@ -6,7 +6,9 @@ for the shape to be used with collision detection. ## Hierarchy -* **IShape** +* [IFreeable](ifreeable.md) + + ↳ **IShape** ## Implemented by @@ -20,6 +22,7 @@ for the shape to be used with collision detection. * [Center](ishape.md#center) * [FarthestPointInDirection](ishape.md#farthestpointindirection) +* [Free](ishape.md#free) * [PointInside](ishape.md#pointinside) * [Transform](ishape.md#transform) @@ -56,6 +59,18 @@ The farthest point in the direction provided ___ +### Free + +▸ **Free**(): *void* + +*Inherited from [IFreeable](ifreeable.md).[Free](ifreeable.md#free)* + +Free releases an object or it's constituent parts back into any available object pools. + +**Returns:** *void* + +___ + ### PointInside ▸ **PointInside**(`point`: [Vector](../classes/vector.md)): *boolean* diff --git a/example/animation/src/index.ts b/example/animation/src/index.ts index 6b12682..90990ae 100644 --- a/example/animation/src/index.ts +++ b/example/animation/src/index.ts @@ -46,7 +46,7 @@ import { SystemEntity, Motion, MotionSystem, - InterpolationSystem, + InterpolationSystem,, Renderable } from "jamjar"; class TextureGame extends Game { @@ -73,7 +73,7 @@ class TextureGame extends Game { // Create player const player = new Entity(this.messageBus, ["player"]); - player.Add(new Transform(new Vector(0, 0), new Vector(20,20))); + player.Add(new Transform(Vector.New(0, 0), Vector.New(20,20))); player.Add(new Sprite( new Material({ texture: new Texture("animation_sheet", spriteSheetIndices[0]), @@ -224,22 +224,22 @@ class PlayerSystem extends System { const player = entity.Get(Player.KEY) as Player; const animator = entity.Get(SpriteAnimator.KEY) as SpriteAnimator; if (keyMessage.payload === "KeyW") { - player.direction = new Vector(0, 1); + player.direction = Vector.New(0, 1); if (animator.current !== undefined) { animator.current = "up"; } } else if (keyMessage.payload === "KeyS") { - player.direction = new Vector(0, -1); + player.direction = Vector.New(0, -1); if (animator.current !== undefined) { animator.current = "down"; } } else if (keyMessage.payload === "KeyA") { - player.direction = new Vector(-1, 0); + player.direction = Vector.New(-1, 0); if (animator.current !== undefined) { animator.current = "left"; } } else if (keyMessage.payload === "KeyD") { - player.direction = new Vector(1, 0); + player.direction = Vector.New(1, 0); if (animator.current !== undefined) { animator.current = "right"; } @@ -262,22 +262,22 @@ class PlayerSystem extends System { const player = entity.Get(Player.KEY) as Player; const animator = entity.Get(SpriteAnimator.KEY) as SpriteAnimator; if (keyMessage.payload === "KeyW" && player.direction.y === 1) { - player.direction = new Vector(0, 0); + player.direction = Vector.New(0, 0); if (animator.current !== undefined) { animator.current = "idle"; } } else if (keyMessage.payload === "KeyS" && player.direction.y === -1) { - player.direction = new Vector(0, 0); + player.direction = Vector.New(0, 0); if (animator.current !== undefined) { animator.current = "idle"; } } else if (keyMessage.payload === "KeyA" && player.direction.x === -1) { - player.direction = new Vector(0, 0); + player.direction = Vector.New(0, 0); if (animator.current !== undefined) { animator.current = "idle"; } } else if (keyMessage.payload === "KeyD" && player.direction.x === 1) { - player.direction = new Vector(0, 0); + player.direction = Vector.New(0, 0); if (animator.current !== undefined) { animator.current = "idle"; } @@ -304,7 +304,7 @@ class Player extends Component { public speed: number; public direction: Vector; - constructor(speed: number, direction: Vector = new Vector(0,0)) { + constructor(speed: number, direction: Vector = Vector.New(0,0)) { super(Player.KEY); this.speed = speed; this.direction = direction; @@ -320,6 +320,10 @@ if (!gl) { throw ("WebGL2 not supported in this browser") } +// Set up object pools +Vector.Init(200); +Renderable.Init(200); + // Create message bus and entity manager const messageBus = new MessageBus(); new EntityManager(messageBus); diff --git a/example/audio/src/index.ts b/example/audio/src/index.ts index 08702c7..eeca2c9 100644 --- a/example/audio/src/index.ts +++ b/example/audio/src/index.ts @@ -45,7 +45,7 @@ import { IMessage, Collider, Pointer, - PointerSystem, + PointerSystem,, Renderable } from "jamjar"; class AudioButtonSystem extends System { @@ -145,7 +145,7 @@ class AudioGame extends Game { // Create entities const stopButton = new Entity(this.messageBus, ["stop"]); - stopButton.Add(new Transform(new Vector(-40, 0), new Vector(40,40))); + stopButton.Add(new Transform(Vector.New(-40, 0), Vector.New(40,40))); stopButton.Add(new Sprite( new Material({ texture: new Texture("button", buttonSpriteSheet[0]), @@ -157,7 +157,7 @@ class AudioGame extends Game { // Create entities const playButton = new Entity(this.messageBus, ["play"]); - playButton.Add(new Transform(new Vector(40, 0), new Vector(40,40))); + playButton.Add(new Transform(Vector.New(40, 0), Vector.New(40,40))); playButton.Add(new Sprite( new Material({ texture: new Texture("button", buttonSpriteSheet[1]), @@ -184,6 +184,10 @@ if (startButton === null) { } startButton.onclick = () => { + // Set up pooling + Vector.Init(200); + Renderable.Init(200); + // Create message bus and entity manager const messageBus = new MessageBus(); new EntityManager(messageBus); diff --git a/example/custom_fragment_shader/src/index.ts b/example/custom_fragment_shader/src/index.ts index ac4ab85..8578ee0 100644 --- a/example/custom_fragment_shader/src/index.ts +++ b/example/custom_fragment_shader/src/index.ts @@ -33,7 +33,7 @@ import { Texture, Vector, ImageRequest, - ShaderAsset, + ShaderAsset,, Renderable } from "jamjar"; // Game definition @@ -76,7 +76,7 @@ class ShaderGame extends Game { // Create example entity const example = new Entity(this.messageBus); - example.Add(new Transform(new Vector(0,0), new Vector(10,10))); + example.Add(new Transform(Vector.New(0,0), Vector.New(10,10))); // Create sprite using a material with the previously loaded texture, // alongside using our custom fragment shader with the default @@ -100,6 +100,10 @@ if (!gl) { throw ("WebGL2 not supported in this browser") } +// Set up pooling +Vector.Init(200); +Renderable.Init(200); + // Create message bus and entity manager const messageBus = new MessageBus(); new EntityManager(messageBus); diff --git a/example/ellipse_approximation/src/index.ts b/example/ellipse_approximation/src/index.ts index 25c41f9..33972fa 100644 --- a/example/ellipse_approximation/src/index.ts +++ b/example/ellipse_approximation/src/index.ts @@ -15,7 +15,7 @@ import { MotionSystem, Material, Polygon, - Color + Color, Renderable } from "jamjar"; const OBJ_COUNT = 40; @@ -30,7 +30,6 @@ const MAX_Y = 45; const MAX_POINTS = 40; const MIN_POINTS = 4; - class EllipseApproximation extends Game { constructor(messageBus: IMessageBus) { super(messageBus, "ellipse-approximation"); @@ -48,8 +47,8 @@ class EllipseApproximation extends Game { // Create entities const nearest = new Entity(this.messageBus); nearest.Add(new Transform( - new Vector(randomBetweenInts(MIN_X, MAX_X), randomBetweenInts(MIN_Y, MAX_Y)), - new Vector(randomBetweenInts(MIN_SCALE, MAX_SCALE), randomBetweenInts(MIN_SCALE, MAX_SCALE)) + Vector.New(randomBetweenInts(MIN_X, MAX_X), randomBetweenInts(MIN_Y, MAX_Y)), + Vector.New(randomBetweenInts(MIN_SCALE, MAX_SCALE), randomBetweenInts(MIN_SCALE, MAX_SCALE)) )); nearest.Add(new Primitive( new Material({ @@ -58,7 +57,8 @@ class EllipseApproximation extends Game { 1, Polygon.EllipseEstimation( randomBetweenInts(MIN_POINTS, MAX_POINTS), - new Vector(xDimension, yDimension), + Vector.New(xDimension, yDimension), + undefined, undefined, true ) @@ -77,6 +77,10 @@ if (!gl) { throw ("WebGL2 not supported in this browser") } +// Set up pooling +Vector.Init(400); +Renderable.Init(200); + const messageBus = new MessageBus(); new EntityManager(messageBus); diff --git a/example/scripting/src/index.ts b/example/scripting/src/index.ts index aa246d9..ff4061f 100644 --- a/example/scripting/src/index.ts +++ b/example/scripting/src/index.ts @@ -38,7 +38,7 @@ import { InterpolationSystem, Camera, CollisionSystem, - Motion + Motion, Renderable } from "jamjar"; class ScriptingGame extends Game { @@ -57,75 +57,75 @@ class ScriptingGame extends Game { "assets/collision_exit.js" ))); - const virtualSize = new Vector(160, 90); - const viewportPosition = new Vector(0, 0); - const viewportScale = new Vector(1, 1); + const virtualSize = Vector.New(160, 90); + const viewportPosition = Vector.New(0, 0); + const viewportScale = Vector.New(1, 1); const backgroundColor = new Color(0, 0, 0, 1); const camera = new Entity(this.messageBus); - camera.Add(new Transform(new Vector(0, 0))); + camera.Add(new Transform(Vector.New(0, 0))); camera.Add(new Camera(backgroundColor, viewportPosition, viewportScale, virtualSize)); const a = new Entity(this.messageBus); - a.Add(new Transform(new Vector(-70, 0), new Vector(12, 12))); + a.Add(new Transform(Vector.New(-70, 0), Vector.New(12, 12))); a.Add(new Collider(Polygon.RectangleByDimensions(1, 1), "enter-script", "exit-script")); - a.Add(new Motion(new Vector(20,0))); + a.Add(new Motion(Vector.New(20,0))); a.Add(new Primitive( new Material({ color: new Color(0,1,0,1) }), 0, new Polygon([ - new Vector(-0.5,-0.5), - new Vector(-0.5, 0.5), - new Vector(0.5, 0.5), - new Vector(0.5,-0.5), - new Vector(-0.5,-0.5) + Vector.New(-0.5,-0.5), + Vector.New(-0.5, 0.5), + Vector.New(0.5, 0.5), + Vector.New(0.5,-0.5), + Vector.New(-0.5,-0.5) ]), DrawMode.LINE_STRIP )); const b = new Entity(this.messageBus); - b.Add(new Transform(new Vector(10, 30), new Vector(12, 12))); + b.Add(new Transform(Vector.New(10, 30), Vector.New(12, 12))); b.Add(new Collider(Polygon.RectangleByDimensions(1, 1), "enter-script", "exit-script")); - b.Add(new Motion(new Vector(0,-20))); + b.Add(new Motion(Vector.New(0,-20))); b.Add(new Primitive( new Material({ color: new Color(0,1,0,1) }), 0, new Polygon([ - new Vector(-0.5,-0.5), - new Vector(-0.5, 0.5), - new Vector(0.5, 0.5), - new Vector(0.5,-0.5), - new Vector(-0.5,-0.5) + Vector.New(-0.5,-0.5), + Vector.New(-0.5, 0.5), + Vector.New(0.5, 0.5), + Vector.New(0.5,-0.5), + Vector.New(-0.5,-0.5) ]), DrawMode.LINE_STRIP )); const c = new Entity(this.messageBus); - c.Add(new Transform(new Vector(70, 20), new Vector(12, 12))); + c.Add(new Transform(Vector.New(70, 20), Vector.New(12, 12))); c.Add(new Collider(Polygon.RectangleByDimensions(1, 1), "enter-script", "exit-script")); - c.Add(new Motion(new Vector(-30,0))); + c.Add(new Motion(Vector.New(-30,0))); c.Add(new Primitive( new Material({ color: new Color(0,1,0,1) }), 0, new Polygon([ - new Vector(-0.5,-0.5), - new Vector(-0.5, 0.5), - new Vector(0.5, 0.5), - new Vector(0.5,-0.5), - new Vector(-0.5,-0.5) + Vector.New(-0.5,-0.5), + Vector.New(-0.5, 0.5), + Vector.New(0.5, 0.5), + Vector.New(0.5,-0.5), + Vector.New(-0.5,-0.5) ]), DrawMode.LINE_STRIP )); const obstacle = new Entity(this.messageBus); - obstacle.Add(new Transform(new Vector(0, 10), new Vector(12, 12))); + obstacle.Add(new Transform(Vector.New(0, 10), Vector.New(12, 12))); obstacle.Add(new Collider(Polygon.RectangleByDimensions(1, 1))); obstacle.Add(new Primitive( new Material({ @@ -133,11 +133,11 @@ class ScriptingGame extends Game { }), 0, new Polygon([ - new Vector(-0.5,-0.5), - new Vector(-0.5, 0.5), - new Vector(0.5, 0.5), - new Vector(0.5,-0.5), - new Vector(-0.5,-0.5) + Vector.New(-0.5,-0.5), + Vector.New(-0.5, 0.5), + Vector.New(0.5, 0.5), + Vector.New(0.5,-0.5), + Vector.New(-0.5,-0.5) ]), DrawMode.LINE_STRIP )); @@ -154,6 +154,10 @@ if (!gl) { throw ("WebGL2 not supported in this browser") } +// Set up pooling +Vector.Init(200); +Renderable.Init(200); + // Create message bus and entity manager const messageBus = new MessageBus(); new EntityManager(messageBus); diff --git a/example/shooter/src/index.ts b/example/shooter/src/index.ts index 55a0f40..8f37225 100644 --- a/example/shooter/src/index.ts +++ b/example/shooter/src/index.ts @@ -56,7 +56,8 @@ import { TextAlignment, TextureFiltering, FontRequest, - DrawMode + DrawMode, + Renderable } from "jamjar" class ScoreCounter extends Component { @@ -89,7 +90,7 @@ class ScoreSystem extends System { public OnMessage(message: IMessage): void { super.OnMessage(message); - switch(message.type) { + switch (message.type) { case ScoreSystem.MESSAGE_SCORE_INCREMENT: { for (const entity of this.entities.values()) { const counter = entity.Get(ScoreCounter.KEY) as ScoreCounter; @@ -106,7 +107,7 @@ class ScoreSystem extends System { class AsteroidSystem extends System { public static readonly ASTEROID_TAG = "asteroid"; - public static readonly ASTEROID_LAYER= "asteroid"; + public static readonly ASTEROID_LAYER = "asteroid"; public static readonly BASE_SPAWN_INTERVAL: number = 3000; private static readonly MIN_SPEED = 10; @@ -128,7 +129,6 @@ class AsteroidSystem extends System { private lastSpawnTime: number; private spawnInterval: number; - private destroyed: number[]; constructor(messageBus: IMessageBus, scene?: IScene, @@ -136,12 +136,11 @@ class AsteroidSystem extends System { spawnInterval: number = AsteroidSystem.BASE_SPAWN_INTERVAL, entities?: Map, subscriberID?: number, - ) { + ) { super(messageBus, scene, AsteroidSystem.EVALUATOR, entities, subscriberID); this.messageBus.Subscribe(this, CollisionSystem.MESSAGE_COLLISION_ENTER); this.lastSpawnTime = lastSpawnTime; this.spawnInterval = spawnInterval; - this.destroyed = []; } protected Update(dt: number): void { @@ -153,12 +152,12 @@ class AsteroidSystem extends System { } this.lastSpawnTime = time; - const startPosition = new Vector(0, 1) - .RotateDeg(new Vector(0, 0), randomBetweenInts(0, 359)) + const startPosition = Vector.New(0, 1) + .RotateDeg(Vector.New(0, 0), randomBetweenInts(0, 359)) .Normalize() .Scale(AsteroidSystem.SPAWN_DISTANCE); - const size = new Vector(randomBetweenInts(1, 15), randomBetweenInts(1, 15)); + const size = Vector.New(randomBetweenInts(1, 15), randomBetweenInts(1, 15)); this.createAsteroid(startPosition, size, AsteroidSystem.MAX_SPEED); } @@ -204,16 +203,16 @@ class AsteroidSystem extends System { destroyEntity = collisionMessage.payload.a; } - if (destroyEntity === undefined || this.destroyed.includes(destroyEntity.id)) { + if (destroyEntity === undefined) { continue; } + for (const entity of asteroids) { if (entity === undefined || entity.entity.id != destroyEntity.id) { continue; } entity.Destroy(); this.messageBus.Publish(new Message(ScoreSystem.MESSAGE_SCORE_INCREMENT)); - this.destroyed.push(entity.entity.id); return; } } @@ -246,13 +245,13 @@ class AsteroidSystem extends System { private createAsteroid(position: Vector, scale: Vector, maxSpeed: number): void { const asteroid = new Entity(this.messageBus, [AsteroidSystem.ASTEROID_TAG], [AsteroidSystem.ASTEROID_LAYER]); - const towardsVector = new Vector(0, 0).Sub(position).Normalize(); + const towardsVector = Vector.New(0, 0).Sub(position).Normalize(); const numberOfSides = randomBetweenInts(6, 10); let points: Vector[] = []; for (let i = 0; i < numberOfSides; i++) { - points.push(new Vector(Math.cos(2 * Math.PI * i / numberOfSides), Math.sin(2 * Math.PI * i / numberOfSides)).Scale(Math.random() * (0.5 - 1.5) + 1.5)); + points.push(Vector.New(Math.cos(2 * Math.PI * i / numberOfSides), Math.sin(2 * Math.PI * i / numberOfSides)).Scale(Math.random() * (0.5 - 1.5) + 1.5)); } points.push(points[0]); const shape = new Polygon(points); @@ -263,7 +262,7 @@ class AsteroidSystem extends System { Math.random() * (0 - Math.PI * 2) + Math.PI * 2)); asteroid.Add(new Primitive( new Material({ - color: new Color(1,1,1,1) + color: new Color(1, 1, 1, 1) }), 0, shape, @@ -377,7 +376,7 @@ class PlayerSystem extends System { scene?: IScene, entities?: Map, subscriberID?: number, - targetedPosition: Vector = new Vector(0,0)) { + targetedPosition: Vector = Vector.New(0, 0)) { super(messageBus, scene, PlayerSystem.EVALUATOR, entities, subscriberID); this.messageBus.Subscribe(this, ["pointermove", "pointerdown"]); this.targetedPosition = targetedPosition; @@ -417,22 +416,22 @@ class PlayerSystem extends System { const bullet = new Entity(this.messageBus, [BulletSystem.BULLET_TAG], [BulletSystem.BULLET_LAYER]); const towardsVector = this.targetedPosition.Copy().Sub(transform.position).Normalize(); - bullet.Add(new Transform(towardsVector.Copy().Scale(6), new Vector(0.4, 3), orientation)); + bullet.Add(new Transform(towardsVector.Copy().Scale(6), Vector.New(0.4, 3), orientation)); bullet.Add(new Primitive( new Material({ - color: new Color(0.54,1,0.54,1) + color: new Color(0.54, 1, 0.54, 1) }), 1, new Polygon([ - new Vector(0, -0.5), - new Vector(0, 0.5), + Vector.New(0, -0.5), + Vector.New(0, 0.5), ]), DrawMode.LINES )) bullet.Add(new Sprite(new Material({ texture: new Texture( "bullet", - Polygon.RectangleByPoints(new Vector(0,0), new Vector(1,1)) + Polygon.RectangleByPoints(Vector.New(0, 0), Vector.New(1, 1)) ) }), 1)); bullet.Add(new Collider(Polygon.RectangleByDimensions(1, 1))) @@ -470,21 +469,21 @@ class GameOverScene extends Scene { new PrimitiveSystem(this.messageBus, this); new InterpolationSystem(this.messageBus, this); - const virtualSize = new Vector(160, 90); - const viewportPosition = new Vector(0, 0); - const viewportScale = new Vector(1, 1); + const virtualSize = Vector.New(160, 90); + const viewportPosition = Vector.New(0, 0); + const viewportScale = Vector.New(1, 1); const backgroundColor = new Color(0, 0, 0, 1); const camera = new Entity(this.messageBus); - camera.Add(new Transform(new Vector(0, 0))); + camera.Add(new Transform(Vector.New(0, 0))); camera.Add(new Camera(backgroundColor, viewportPosition, viewportScale, virtualSize)); this.AddEntity(camera); const gameOverHeight = 0.1; const gameOverWidth = gameOverHeight * (virtualSize.y / virtualSize.x); const gameOver = new Entity(this.messageBus); - gameOver.Add(new Transform(new Vector(0, 0), new Vector(gameOverWidth, gameOverHeight))); - gameOver.Add(new Text(2, "GAMEOVER", "test", TextAlignment.Center, 0.3, undefined, new Color(1,1,1,1))); + gameOver.Add(new Transform(Vector.New(0, 0), Vector.New(gameOverWidth, gameOverHeight))); + gameOver.Add(new Text(2, "GAMEOVER", "test", TextAlignment.Center, 0.3, undefined, new Color(1, 1, 1, 1))); gameOver.Add(new UI(camera)); this.AddEntity(gameOver); @@ -492,24 +491,24 @@ class GameOverScene extends Scene { const playAgainHeight = 0.05; const playAgainWidth = playAgainHeight * (virtualSize.y / virtualSize.x); const playAgain = new Entity(this.messageBus); - playAgain.Add(new Transform(new Vector(0, -0.2), new Vector(playAgainWidth, playAgainHeight))); - playAgain.Add(new Text(2, "REFRESH TO REPLAY", "test", TextAlignment.Center, 0.3, undefined, new Color(1,1,1,1))); + playAgain.Add(new Transform(Vector.New(0, -0.2), Vector.New(playAgainWidth, playAgainHeight))); + playAgain.Add(new Text(2, "REFRESH TO REPLAY", "test", TextAlignment.Center, 0.3, undefined, new Color(1, 1, 1, 1))); playAgain.Add(new UI(camera)); this.AddEntity(playAgain); const crosshair = new Entity(this.messageBus, [CrosshairSystem.CROSSHAIR_TAG]); - crosshair.Add(new Transform(new Vector(0, 0), new Vector(0.03, 0.053))); + crosshair.Add(new Transform(Vector.New(0, 0), Vector.New(0.03, 0.053))); crosshair.Add(new Primitive( new Material({ - color: new Color(1,1,1,1) + color: new Color(1, 1, 1, 1) }), 1, new Polygon([ - new Vector(-0.25,-0.25), - new Vector(0.25,0.25), - new Vector(0,0), - new Vector(-0.25,0.25), - new Vector(0.25,-0.25), + Vector.New(-0.25, -0.25), + Vector.New(0.25, 0.25), + Vector.New(0, 0), + Vector.New(-0.25, 0.25), + Vector.New(0.25, -0.25), ]), DrawMode.LINE_STRIP )) @@ -529,7 +528,7 @@ class MainScene extends Scene { OnMessage(message: IMessage): void { super.OnMessage(message); - switch(message.type) { + switch (message.type) { case MainScene.MESSAGE_GAME_OVER: { new GameOverScene(this.messageBus).Start(); this.Destroy(); @@ -568,51 +567,51 @@ class MainScene extends Scene { } ))); - const virtualSize = new Vector(160, 90); - const viewportPosition = new Vector(0, 0); - const viewportScale = new Vector(1, 1); + const virtualSize = Vector.New(160, 90); + const viewportPosition = Vector.New(0, 0); + const viewportScale = Vector.New(1, 1); const backgroundColor = new Color(0, 0, 0, 1); const camera = new Entity(this.messageBus); - camera.Add(new Transform(new Vector(0, 0))); + camera.Add(new Transform(Vector.New(0, 0))); camera.Add(new Camera(backgroundColor, viewportPosition, viewportScale, virtualSize)); this.AddEntity(camera); const player = new Entity(this.messageBus, [PlayerSystem.PLAYER_TAG], [PlayerSystem.PLAYER_LAYER]); - player.Add(new Transform(new Vector(0, 0), new Vector(2, 2))); + player.Add(new Transform(Vector.New(0, 0), Vector.New(2, 2))); player.Add(new Primitive( new Material({ - color: new Color(1,1,1,1) + color: new Color(1, 1, 1, 1) }), 0, new Polygon([ - new Vector(0,0.5), - new Vector(0.5, -0.5), - new Vector(-0.5, -0.5), - new Vector(0,0.5) + Vector.New(0, 0.5), + Vector.New(0.5, -0.5), + Vector.New(-0.5, -0.5), + Vector.New(0, 0.5) ]), DrawMode.LINE_STRIP )); player.Add(new Collider(new Polygon([ - new Vector(0,0.5), - new Vector(0.5, -0.5), - new Vector(-0.5, -0.5), + Vector.New(0, 0.5), + Vector.New(0.5, -0.5), + Vector.New(-0.5, -0.5), ]))); this.AddEntity(player); const crosshair = new Entity(this.messageBus, [CrosshairSystem.CROSSHAIR_TAG]); - crosshair.Add(new Transform(new Vector(0, 0), new Vector(0.03, 0.053))); + crosshair.Add(new Transform(Vector.New(0, 0), Vector.New(0.03, 0.053))); crosshair.Add(new Primitive( new Material({ - color: new Color(1,1,1,1) + color: new Color(1, 1, 1, 1) }), 1, new Polygon([ - new Vector(-0.25,-0.25), - new Vector(0.25,0.25), - new Vector(0,0), - new Vector(-0.25,0.25), - new Vector(0.25,-0.25), + Vector.New(-0.25, -0.25), + Vector.New(0.25, 0.25), + Vector.New(0, 0), + Vector.New(-0.25, 0.25), + Vector.New(0.25, -0.25), ]), DrawMode.LINE_STRIP )) @@ -622,8 +621,8 @@ class MainScene extends Scene { const scoreHeight = 0.06; const scoreCharWidth = scoreHeight * (virtualSize.y / virtualSize.x); const scoreCounter = new Entity(this.messageBus); - scoreCounter.Add(new Transform(new Vector(0, 0.9), new Vector(scoreCharWidth, scoreHeight))); - scoreCounter.Add(new Text(2, "SCORE:0", "test", TextAlignment.Center, 0.3, undefined, new Color(1,1,1,1))); + scoreCounter.Add(new Transform(Vector.New(0, 0.9), Vector.New(scoreCharWidth, scoreHeight))); + scoreCounter.Add(new Text(2, "SCORE:0", "test", TextAlignment.Center, 0.3, undefined, new Color(1, 1, 1, 1))); scoreCounter.Add(new ScoreCounter()); scoreCounter.Add(new UI(camera)); this.AddEntity(scoreCounter); @@ -650,6 +649,9 @@ if (!gl) { throw ("WebGL2 not supported in this browser") } +Vector.Init(5000); +Renderable.Init(500); + const messageBus = new MessageBus(); new EntityManager(messageBus); diff --git a/example/texture/src/index.ts b/example/texture/src/index.ts index 4dc7fbd..103713b 100644 --- a/example/texture/src/index.ts +++ b/example/texture/src/index.ts @@ -35,6 +35,7 @@ import { Vector, Color, TextureWrapping, + Renderable } from "jamjar"; class TextureGame extends Game { @@ -95,7 +96,7 @@ class TextureGame extends Game { // Create entities const nearest = new Entity(this.messageBus); - nearest.Add(new Transform(new Vector(-40, 20), new Vector(20,20))); + nearest.Add(new Transform(Vector.New(-40, 20), Vector.New(20,20))); nearest.Add(new Sprite( new Material({ texture: new Texture("nearest"), @@ -103,7 +104,7 @@ class TextureGame extends Game { )); const bilinear = new Entity(this.messageBus); - bilinear.Add(new Transform(new Vector(0, 20), new Vector(20,20))); + bilinear.Add(new Transform(Vector.New(0, 20), Vector.New(20,20))); bilinear.Add(new Sprite( new Material({ texture: new Texture("bilinear"), @@ -111,7 +112,7 @@ class TextureGame extends Game { )); const trilinear = new Entity(this.messageBus); - trilinear.Add(new Transform(new Vector(40, 20), new Vector(20,20))); + trilinear.Add(new Transform(Vector.New(40, 20), Vector.New(20,20))); trilinear.Add(new Sprite( new Material({ texture: new Texture("trilinear"), @@ -119,7 +120,7 @@ class TextureGame extends Game { )); const nearestRed = new Entity(this.messageBus); - nearestRed.Add(new Transform(new Vector(-40, -20), new Vector(20,20))); + nearestRed.Add(new Transform(Vector.New(-40, -20), Vector.New(20,20))); nearestRed.Add(new Sprite( new Material({ texture: new Texture("nearest"), @@ -128,19 +129,19 @@ class TextureGame extends Game { )); const bilinearMirroRepeat = new Entity(this.messageBus); - bilinearMirroRepeat.Add(new Transform(new Vector(0, -20), new Vector(20,20))); + bilinearMirroRepeat.Add(new Transform(Vector.New(0, -20), Vector.New(20,20))); bilinearMirroRepeat.Add(new Sprite( new Material({ - texture: new Texture("bilinear_mirror_repeat", Polygon.QuadByPoints(new Vector(0,0), new Vector(5,5))), + texture: new Texture("bilinear_mirror_repeat", Polygon.QuadByPoints(Vector.New(0,0), Vector.New(5,5))), color: new Color(1,1,1,0.5) }), )); const trilinearRepeat = new Entity(this.messageBus); - trilinearRepeat.Add(new Transform(new Vector(40, -20), new Vector(20,20))); + trilinearRepeat.Add(new Transform(Vector.New(40, -20), Vector.New(20,20))); trilinearRepeat.Add(new Sprite( new Material({ - texture: new Texture("trilinear_repeat", Polygon.QuadByPoints(new Vector(0,0), new Vector(5,5))), + texture: new Texture("trilinear_repeat", Polygon.QuadByPoints(Vector.New(0,0), Vector.New(5,5))), color: new Color(0,1,0,1) }), 0, @@ -157,6 +158,10 @@ if (!gl) { throw ("WebGL2 not supported in this browser") } +// Set up vector and renderable pooling +Vector.Init(200); +Renderable.Init(100); + // Create message bus and entity manager const messageBus = new MessageBus(); new EntityManager(messageBus); diff --git a/example/z_order/src/index.ts b/example/z_order/src/index.ts index 1ca6be6..c0ed0bf 100644 --- a/example/z_order/src/index.ts +++ b/example/z_order/src/index.ts @@ -31,7 +31,7 @@ import { Camera, Material, Texture, - Vector, + Vector,, Renderable } from "jamjar"; class ZOrderGame extends Game { @@ -46,7 +46,7 @@ class ZOrderGame extends Game { "assets/example.png", { minFilter: TextureFiltering.NEAREST, - magFilter: TextureFiltering.NEAREST, + magFilter: TextureFiltering.NEARESRenderableT, } ))); @@ -60,7 +60,7 @@ class ZOrderGame extends Game { // Create entities const near = new Entity(this.messageBus); - near.Add(new Transform(new Vector(-11, 0), new Vector(20,20))); + near.Add(new Transform(Vector.New(-11, 0), Vector.New(20,20))); near.Add(new Sprite( new Material({ texture: new Texture("example_sheet", exampleSpriteSheet[3]), @@ -68,7 +68,7 @@ class ZOrderGame extends Game { )); const medium = new Entity(this.messageBus); - medium.Add(new Transform(new Vector(0, 11), new Vector(20,20))); + medium.Add(new Transform(Vector.New(0, 11), Vector.New(20,20))); medium.Add(new Sprite( new Material({ texture: new Texture("example_sheet", exampleSpriteSheet[2]), @@ -76,7 +76,7 @@ class ZOrderGame extends Game { )); const far = new Entity(this.messageBus); - far.Add(new Transform(new Vector(11, 0), new Vector(20,20))); + far.Add(new Transform(Vector.New(11, 0), Vector.New(20,20))); far.Add(new Sprite( new Material({ texture: new Texture("example_sheet", exampleSpriteSheet[1]), @@ -84,7 +84,7 @@ class ZOrderGame extends Game { )); const evenFurther = new Entity(this.messageBus); - evenFurther.Add(new Transform(new Vector(0, -11), new Vector(20,20))); + evenFurther.Add(new Transform(Vector.New(0, -11), Vector.New(20,20))); evenFurther.Add(new Sprite( new Material({ texture: new Texture("example_sheet", exampleSpriteSheet[0]), @@ -102,6 +102,10 @@ if (!gl) { throw ("WebGL2 not supported in this browser") } +// Set up pooling +Vector.Init(400); +Renderable.Init(200); + // Create message bus and entity manager const messageBus = new MessageBus(); new EntityManager(messageBus); diff --git a/src/component/component.ts b/src/component/component.ts index b382875..21b466b 100644 --- a/src/component/component.ts +++ b/src/component/component.ts @@ -1,5 +1,5 @@ /* -Copyright 2019 JamJar Authors +Copyright 2020 JamJar Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +import IFreeable from "../pooling/ifreeable"; + /** * Component is one of the key elements of the Entity-Component-System architecture. * A component is there to store data, logic shouldn't exist within @@ -21,10 +23,13 @@ limitations under the License. * component data. * Each entity can only have 1 component of each type. */ -abstract class Component { +abstract class Component implements IFreeable { public static readonly MESSAGE_ADD = "component_add"; public static readonly MESSAGE_REMOVE = "component_remove"; constructor(public key: string) {} + public Free(): void { + return; + } } -export default Component; \ No newline at end of file +export default Component; diff --git a/src/component/component_manager.test.ts b/src/component/component_manager.test.ts index 2a12c1e..473c736 100644 --- a/src/component/component_manager.test.ts +++ b/src/component/component_manager.test.ts @@ -37,21 +37,21 @@ describe("ComponentManager - Get", () => { [ "3 components, not found", undefined, - new ComponentManager("test", { - 0: new FakeComponent("test"), - 1: new FakeComponent("test"), - 2: new FakeComponent("test"), - }), + new ComponentManager("test", new Map([ + [0, new FakeComponent("test")], + [1, new FakeComponent("test")], + [2, new FakeComponent("test")], + ])), new FakeEntity(3) ], [ "3 components, found", new FakeComponent("test"), - new ComponentManager("test", { - 0: new FakeComponent("test"), - 1: new FakeComponent("test"), - 2: new FakeComponent("test"), - }), + new ComponentManager("test", new Map([ + [0, new FakeComponent("test")], + [1, new FakeComponent("test")], + [2, new FakeComponent("test")], + ])), new FakeEntity(0) ], ])("%p", (description: string, expected: Component | undefined, componentManager: ComponentManager, entity: IEntity) => { @@ -71,41 +71,41 @@ describe("ComponentManager - Add", () => { test.each([ [ "No components", - new ComponentManager("test", { - 0: new FakeComponent("test") - }), + new ComponentManager("test", new Map([ + [0, new FakeComponent("test")], + ])), new ComponentManager("test"), new FakeEntity(0), new FakeComponent("test") ], [ "3 components, add new", - new ComponentManager("test", { - 0: new FakeComponent("test"), - 1: new FakeComponent("test"), - 2: new FakeComponent("test"), - 3: new FakeComponent("test"), - }), - new ComponentManager("test", { - 0: new FakeComponent("test"), - 1: new FakeComponent("test"), - 2: new FakeComponent("test"), - }), + new ComponentManager("test", new Map([ + [0, new FakeComponent("test")], + [1, new FakeComponent("test")], + [2, new FakeComponent("test")], + [3, new FakeComponent("test")], + ])), + new ComponentManager("test", new Map([ + [0, new FakeComponent("test")], + [1, new FakeComponent("test")], + [2, new FakeComponent("test")], + ])), new FakeEntity(3), new FakeComponent("test") ], [ "3 components, update existing", - new ComponentManager("test", { - 0: new FakeComponent("test"), - 1: new FakeComponent("test"), - 2: new FakeComponent("test"), - }), - new ComponentManager("test", { - 0: new FakeComponent("REPLACE_ME"), - 1: new FakeComponent("test"), - 2: new FakeComponent("test"), - }), + new ComponentManager("test", new Map([ + [0, new FakeComponent("test")], + [1, new FakeComponent("test")], + [2, new FakeComponent("test")], + ])), + new ComponentManager("test", new Map([ + [0, new FakeComponent("REPLACE_ME")], + [1, new FakeComponent("test")], + [2, new FakeComponent("test")], + ])), new FakeEntity(0), new FakeComponent("test") ], @@ -128,40 +128,40 @@ describe("ComponentManager - Remove", () => { [ "No components", new ComponentManager("test"), - new ComponentManager("test", { - 0: new FakeComponent("test") - }), + new ComponentManager("test", new Map([ + [0, new FakeComponent("test")], + ])), new FakeEntity(0), ], [ "3 components, not found", - new ComponentManager("test", { - 0: new FakeComponent("test"), - 1: new FakeComponent("test"), - 2: new FakeComponent("test"), - }), - new ComponentManager("test", { - 0: new FakeComponent("test"), - 1: new FakeComponent("test"), - 2: new FakeComponent("test"), - }), + new ComponentManager("test", new Map([ + [0, new FakeComponent("test")], + [1, new FakeComponent("test")], + [2, new FakeComponent("test")], + ])), + new ComponentManager("test", new Map([ + [0, new FakeComponent("test")], + [1, new FakeComponent("test")], + [2, new FakeComponent("test")], + ])), new FakeEntity(3), ], [ "3 components, found", - new ComponentManager("test", { - 1: new FakeComponent("test"), - 2: new FakeComponent("test"), - }), - new ComponentManager("test", { - 0: new FakeComponent("test"), - 1: new FakeComponent("test"), - 2: new FakeComponent("test"), - }), + new ComponentManager("test", new Map([ + [1, new FakeComponent("test")], + [2, new FakeComponent("test")], + ])), + new ComponentManager("test", new Map([ + [0, new FakeComponent("test")], + [1, new FakeComponent("test")], + [2, new FakeComponent("test")], + ])), new FakeEntity(0), ], ])("%p", (description: string, expectedState: ComponentManager, componentManager: ComponentManager, entity: IEntity) => { componentManager.Remove(entity); expect(componentManager).toEqual(expectedState); }); -}); \ No newline at end of file +}); diff --git a/src/component/component_manager.ts b/src/component/component_manager.ts index 4c3e823..1771bc2 100644 --- a/src/component/component_manager.ts +++ b/src/component/component_manager.ts @@ -24,9 +24,9 @@ import IEntity from "../entity/ientity"; */ class ComponentManager { public key: string; - private components: Record; + private components: Map; - constructor(key: string, components: Record = {}) { + constructor(key: string, components: Map = new Map()) { this.key = key; this.components = components; } @@ -38,7 +38,7 @@ class ComponentManager { * @returns {Component|undefined} Component retrieved, if doesn't exist, undefined */ public Get(entity: IEntity): Component | undefined { - return this.components[entity.id]; + return this.components.get(entity.id); } /** @@ -48,7 +48,7 @@ class ComponentManager { * @param {Component} component Component to add */ public Add(entity: IEntity, component: Component): void { - this.components[entity.id] = component; + this.components.set(entity.id, component); } /** @@ -57,8 +57,12 @@ class ComponentManager { * @param {IEntity} entity Entity of the component to remove */ public Remove(entity: IEntity): void { - delete this.components[entity.id]; + const component = this.components.get(entity.id); + if (component !== undefined) { + this.components.delete(entity.id); + component.Free(); + } } } -export default ComponentManager; \ No newline at end of file +export default ComponentManager; diff --git a/src/entity/entity.ts b/src/entity/entity.ts index 6f09f46..3173f78 100644 --- a/src/entity/entity.ts +++ b/src/entity/entity.ts @@ -41,9 +41,9 @@ class Entity implements IEntity { private messageBus: IMessageBus - constructor(messageBus: IMessageBus, - tags: string[] = [], - layers: string[] = [], + constructor(messageBus: IMessageBus, + tags: string[] = [], + layers: string[] = [], id: number = Entity.ID++) { this.messageBus = messageBus; this.tags = tags; @@ -51,17 +51,17 @@ class Entity implements IEntity { this.id = id; } - Add(component: Component): void { + public Add(component: Component): void { this.messageBus.Publish(new Message<[Entity, Component]>(Component.MESSAGE_ADD, [this, component])); } - Remove(key: string): void { + public Remove(key: string): void { this.messageBus.Publish(new Message<[Entity, string]>(Component.MESSAGE_REMOVE, [this, key])); } - Destroy(): void { + public Destroy(): void { this.messageBus.Publish(new Message(Entity.MESSAGE_DESTROY, this)); } } -export default Entity; \ No newline at end of file +export default Entity; diff --git a/src/entity/entity_manager.test.ts b/src/entity/entity_manager.test.ts index a8729ba..f8c89b4 100644 --- a/src/entity/entity_manager.test.ts +++ b/src/entity/entity_manager.test.ts @@ -60,14 +60,14 @@ describe("EntityManager - OnMessage", () => { "Destroy success, remove from three component managers", undefined, new EntityManager(new FakeMessageBus(), [ - new ComponentManager("test1", { 1: new FakeComponent("test1") }), - new ComponentManager("test2", {}), - new ComponentManager("test3", {}) + new ComponentManager("test1", new Map([[1, new FakeComponent("test1")]])), + new ComponentManager("test2", new Map()), + new ComponentManager("test3", new Map()) ], 0), new EntityManager(new FakeMessageBus(), [ - new ComponentManager("test1", { 0: new FakeComponent("test1"), 1: new FakeComponent("test1") }), - new ComponentManager("test2", { 0: new FakeComponent("test2") }), - new ComponentManager("test3", { 0: new FakeComponent("test3") }) + new ComponentManager("test1", new Map([[0, new FakeComponent("test1")], [1, new FakeComponent("test1")]])), + new ComponentManager("test2", new Map([[0, new FakeComponent("test2")]])), + new ComponentManager("test3", new Map([[0, new FakeComponent("test3")]])) ], 0), new Message(Entity.MESSAGE_DESTROY, new FakeEntity(0)) ], @@ -89,7 +89,7 @@ describe("EntityManager - OnMessage", () => { "Add, success new component manager", undefined, new EntityManager(new FakeMessageBus(), [ - new ComponentManager("test", { 0: new FakeComponent("test") }) + new ComponentManager("test", new Map([[0, new FakeComponent("test")]])) ], 0), new EntityManager(new FakeMessageBus(), [], 0), new Message<[IEntity, Component]>(Component.MESSAGE_ADD, [new FakeEntity(0), new FakeComponent("test")]) @@ -98,10 +98,10 @@ describe("EntityManager - OnMessage", () => { "Add, success existing component manager", undefined, new EntityManager(new FakeMessageBus(), [ - new ComponentManager("test", { 0: new FakeComponent("test"), 1: new FakeComponent("test"), 2: new FakeComponent("test") }) + new ComponentManager("test", new Map([[0, new FakeComponent("test")],[1, new FakeComponent("test")],[2, new FakeComponent("test") ]])) ], 0), new EntityManager(new FakeMessageBus(), [ - new ComponentManager("test", { 1: new FakeComponent("test"), 2: new FakeComponent("test") }) + new ComponentManager("test", new Map([[1, new FakeComponent("test")],[2, new FakeComponent("test") ]])) ], 0), new Message<[IEntity, Component]>(Component.MESSAGE_ADD, [new FakeEntity(0), new FakeComponent("test")]) ], @@ -136,14 +136,14 @@ describe("EntityManager - OnMessage", () => { "Remove, found matching", undefined, new EntityManager(new FakeMessageBus(), [ - new ComponentManager("test1", {}), - new ComponentManager("test2", { 0: new FakeComponent("test2") }), - new ComponentManager("test3", { 0: new FakeComponent("test3") }) + new ComponentManager("test1", new Map()), + new ComponentManager("test2", new Map([[0, new FakeComponent("test2")]])), + new ComponentManager("test3", new Map([[0, new FakeComponent("test3")]])) ], 0), new EntityManager(new FakeMessageBus(), [ - new ComponentManager("test1", { 0: new FakeComponent("test1") }), - new ComponentManager("test2", { 0: new FakeComponent("test2") }), - new ComponentManager("test3", { 0: new FakeComponent("test3") }) + new ComponentManager("test1", new Map([[0, new FakeComponent("test1")]])), + new ComponentManager("test2", new Map([[0, new FakeComponent("test2")]])), + new ComponentManager("test3", new Map([[0, new FakeComponent("test3")]])) ], 0), new Message<[IEntity, string]>(Component.MESSAGE_REMOVE, [new FakeEntity(0), "test1"]) ], @@ -151,14 +151,14 @@ describe("EntityManager - OnMessage", () => { "Remove, found matching", undefined, new EntityManager(new FakeMessageBus(), [ - new ComponentManager("test1", {}), - new ComponentManager("test2", { 0: new FakeComponent("test2") }), - new ComponentManager("test3", { 0: new FakeComponent("test3") }) + new ComponentManager("test1", new Map()), + new ComponentManager("test2", new Map([[0, new FakeComponent("test2")]])), + new ComponentManager("test3", new Map([[0, new FakeComponent("test3")]])) ], 0), new EntityManager(new FakeMessageBus(), [ - new ComponentManager("test1", { 0: new FakeComponent("test1") }), - new ComponentManager("test2", { 0: new FakeComponent("test2") }), - new ComponentManager("test3", { 0: new FakeComponent("test3") }) + new ComponentManager("test1", new Map([[0, new FakeComponent("test1")]])), + new ComponentManager("test2", new Map([[0, new FakeComponent("test2")]])), + new ComponentManager("test3", new Map([[0, new FakeComponent("test3")]])) ], 0), new Message<[IEntity, string]>(Component.MESSAGE_REMOVE, [new FakeEntity(0), "test1"]) ], @@ -172,4 +172,4 @@ describe("EntityManager - OnMessage", () => { expect(entityManager).toEqual(expectedState); } }); -}); \ No newline at end of file +}); diff --git a/src/entity/entity_manager.ts b/src/entity/entity_manager.ts index dfa3e43..f47f033 100644 --- a/src/entity/entity_manager.ts +++ b/src/entity/entity_manager.ts @@ -1,5 +1,5 @@ /* -Copyright 2019 JamJar Authors +Copyright 2020 JamJar Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -26,10 +26,10 @@ import IEntity from "./ientity"; /** * EntityManager keeps tracks of all entities and their components/changes in their components. - * The EntityManager watches for changes in which components belong to an entity (add/remove), and + * The EntityManager watches for changes in which components belong to an entity (add/remove), and * when a change is detected it will broadcast that a change has been detected in the entity and the * entity's new list of components. - * The EntityManager also watches for entities being deleted and removes the deleted entity's + * The EntityManager also watches for entities being deleted and removes the deleted entity's * components. */ class EntityManager extends Subscriber { @@ -45,8 +45,8 @@ class EntityManager extends Subscriber { this.messageBus = messageBus; this.componentManagers = componentManagers; this.messageBus.Subscribe(this, [ - Component.MESSAGE_ADD, - Component.MESSAGE_REMOVE, + Component.MESSAGE_ADD, + Component.MESSAGE_REMOVE, Entity.MESSAGE_DESTROY ]); } @@ -104,7 +104,7 @@ class EntityManager extends Subscriber { } this.messageBus.Publish(new Message(System.MESSAGE_DEREGISTER, entity)); } - + /** * removeComponent removes an entity's component by the component key provided. * @param {IEntity} entity Entity to remove the component from @@ -152,7 +152,7 @@ class EntityManager extends Subscriber { /** * getComponents gets all components belonging to an entity. * @param {IEntity} entity The entity to get the components of - * @returns {Component[]} The list of components for the entity + * @returns {Component[]} The list of components for the entity */ private getComponents(entity: IEntity): Component[] { const components: Component[] = []; @@ -166,4 +166,4 @@ class EntityManager extends Subscriber { } } -export default EntityManager; \ No newline at end of file +export default EntityManager; diff --git a/src/fake/message_bus.ts b/src/fake/message_bus.ts index f146e30..0354291 100644 --- a/src/fake/message_bus.ts +++ b/src/fake/message_bus.ts @@ -24,6 +24,10 @@ class FakeMessageBus extends Fake implements IMessageBus { return; } + public DispatchUntilEmpty(): void { + return; + } + public Publish(message: IMessage): void { return; } @@ -42,4 +46,4 @@ class FakeMessageBus extends Fake implements IMessageBus { } -export default FakeMessageBus; \ No newline at end of file +export default FakeMessageBus; diff --git a/src/game.ts b/src/game.ts index 9c91908..47e7f72 100644 --- a/src/game.ts +++ b/src/game.ts @@ -94,6 +94,8 @@ abstract class Game implements IGame { this.accumulator -= Game.TIME_STEP; } + this.messageBus.DispatchUntilEmpty(); + // Alpha constant for interpolation calculations const alpha = this.accumulator / Game.TIME_STEP; // Pre-render and dispatch, must be immediately dispatched to allow pre-render systems to @@ -105,7 +107,7 @@ abstract class Game implements IGame { // Post render this.messageBus.Publish(new Message(Game.MESSAGE_POST_RENDER, alpha)); this.messageBus.Dispatch(); - this.frameRequestCallback((timestamp: number) => { this.loop(timestamp); }); + this.frameRequestCallback(this.loop.bind(this)); } } diff --git a/src/geometry/matrix_4d.ts b/src/geometry/matrix_4d.ts index 93883cd..2b4997a 100644 --- a/src/geometry/matrix_4d.ts +++ b/src/geometry/matrix_4d.ts @@ -1,5 +1,5 @@ /* -Copyright 2019 JamJar Authors +Copyright 2020 JamJar Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/src/geometry/vector.ts b/src/geometry/vector.ts index d79b1b7..78cf0ea 100644 --- a/src/geometry/vector.ts +++ b/src/geometry/vector.ts @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +import IPoolable from "../pooling/ipoolable"; +import Pooled from "../pooling/pooled"; import Matrix3D from "./matrix_3d"; import Matrix4D from "./matrix_4d"; @@ -21,10 +23,40 @@ import Matrix4D from "./matrix_4d"; * Vector is the 2 dimensional representation of a vector, with two values (x,y). * This is a mutable data structure, operations on Vector objects will affect the original object. */ -class Vector { +class Vector extends Pooled implements IPoolable { + + /** + * Value of the Vector object pool. + */ + private static POOL_KEY = "jamjar_vector"; + + /** + * Create a Vector.New, using pooling if available. + */ + public static New(x: number, y: number): Vector { + return this.new(Vector.POOL_KEY, Vector, x, y); + } + + /** + * Free the provided vector. + */ + public static Free(obj: Vector): void { + this.free(Vector.POOL_KEY, obj); + } + + /** + * Initialize the Vector pool to the size provided. + */ + public static Init(size: number): void { + this.init(Vector.POOL_KEY, () => { + return Vector.New(0, 0); + }, size); + } + private data: Float32Array; constructor(x: number, y: number) { + super(); this.data = new Float32Array(2); this.data[0] = x; this.data[1] = y; @@ -161,7 +193,7 @@ class Vector { * @returns {Vector} This vector to allow chaining, the result of the rotation */ public RotateDeg(center: Vector, angle: number): Vector { - return this.Rotate(center, angle * (Math.PI/180)); + return this.Rotate(center, angle * (Math.PI / 180)); } /** @@ -200,11 +232,21 @@ class Vector { * @returns {Vector} The copy of this vector */ public Copy(): Vector { - return new Vector( + return Vector.New( this.x, this.y ); } + + public Recycle(x: number, y: number): Vector { + this.x = x; + this.y = y; + return this; + } + + public Free(): void { + Vector.Free(this); + } } export default Vector; diff --git a/src/index.ts b/src/index.ts index e459f67..52c5e09 100644 --- a/src/index.ts +++ b/src/index.ts @@ -128,6 +128,9 @@ import DefaultTextFragmentShader from "./standard/webgl/default_text_fragment_sh import DefaultTextureFragmentShader from "./standard/webgl/default_texture_fragment_shader"; import DefaultTextureVertexShader from "./standard/webgl/default_texture_vertex_shader"; import WebGLSystem from "./standard/webgl/webgl_system"; +import Pooled from "./pooling/pooled"; +import IPoolable from "./pooling/ipoolable"; +import IFreeable from "./pooling/ifreeable"; export { // Core @@ -179,6 +182,11 @@ export { System, SystemEntity, + // Pooling + Pooled, + IPoolable, + IFreeable, + // Fake FakeAudioBufferSourceNode, FakeAudioContext, diff --git a/src/message/imessage.ts b/src/message/imessage.ts index 1d298f9..08be7be 100644 --- a/src/message/imessage.ts +++ b/src/message/imessage.ts @@ -1,5 +1,5 @@ /* -Copyright 2019 JamJar Authors +Copyright 2020 JamJar Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -23,4 +23,4 @@ interface IMessage { type: string; } -export default IMessage; \ No newline at end of file +export default IMessage; diff --git a/src/message/imessage_bus.ts b/src/message/imessage_bus.ts index f7be769..3aa28df 100644 --- a/src/message/imessage_bus.ts +++ b/src/message/imessage_bus.ts @@ -14,15 +14,52 @@ See the License for the specific language governing permissions and limitations under the License. */ -import Subscriber from "./subscriber"; import IMessage from "./imessage"; +import ISubscriber from "./isubscriber"; +/** + * IMessageBus defines the contract for a message bus, handling receiver subscription/unsubcription, message publishing, + * and message dispatching. + */ interface IMessageBus { + /** + * Dispatch processes the current message bus queue and forwards the messages to the subscribers who have + * subscribed to each message type. + */ Dispatch: () => void; + + /** + * DispatchUntilEmpty repeatedly dispatches until the message queue is empty, used to make sure everything is + * processed, e.g. if there is a message that causes a new message to be added, it will ensure that all recursive + * messages are processed. + */ + DispatchUntilEmpty: () => void; + + /** + * Publish adds a message to the message bus queue to be dispatched. + * @param {IMessage} message The message to send + */ Publish: (message: IMessage) => void; - Subscribe: (subscriber: Subscriber, types: string | string[]) => void; - UnsubscribeAll: (subscriber: Subscriber) => void; - Unsubscribe: (subscriber: Subscriber, types: string | string[]) => void; + + /** + * Subscribe subscibes a subscriber to a particular message type or types. + * @param {ISubscriber} subscriber The subscriber to the message type(s) + * @param {string|string[]} types The message type(s) to subscribe to + */ + Subscribe: (subscriber: ISubscriber, types: string | string[]) => void; + + /** + * UnsubscribeAll unsubscribes a Subscriber from all messages. + * @param {ISubscriber} subscriber The subscriber to unsubscribe + */ + UnsubscribeAll: (subscriber: ISubscriber) => void; + + /** + * Unsubscribe unsubscribes a subscriber from a specific message type or types. + * @param {ISubscriber} subscriber The subscriber to unsubscribe + * @param {string|strings} types The message type(s) to unsubscribe from + */ + Unsubscribe: (subscriber: ISubscriber, types: string | string[]) => void; } -export default IMessageBus; \ No newline at end of file +export default IMessageBus; diff --git a/src/message/message.ts b/src/message/message.ts index fa8ca2e..086bf9c 100644 --- a/src/message/message.ts +++ b/src/message/message.ts @@ -1,5 +1,5 @@ /* -Copyright 2019 JamJar Authors +Copyright 2020 JamJar Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -24,4 +24,4 @@ class Message implements IMessage { constructor(public type: string, public payload?: T) {} } -export default Message; \ No newline at end of file +export default Message; diff --git a/src/message/message_bus.ts b/src/message/message_bus.ts index 2210f9b..29763f4 100644 --- a/src/message/message_bus.ts +++ b/src/message/message_bus.ts @@ -38,20 +38,19 @@ class MessageBus implements IMessageBus { this.messageQueue = messageQueue; } - /** - * Processes the message bus queue and forwards the messages to the subscribers. - * who have subscribed to each message type. - */ public Dispatch(): void { - const queueLength = this.messageQueue.length; + const messageQueue = [...this.messageQueue]; + this.messageQueue = []; + const queueLength = messageQueue.length; for (let i = 0; i < queueLength; i++) { // Will always be non null /* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */ - const message = this.messageQueue.shift()!; + const message = messageQueue.shift()!; const messageSubs = this.subscribers[message.type]; if (!messageSubs) { continue; } + // Take a copy of the array, otherwise subscribers might unsubscribe // as part of this message, mixing the dispatch order and causing // other subscribers to not recieve a message @@ -62,19 +61,16 @@ class MessageBus implements IMessageBus { } } - /** - * Publish adds a message to the message bus queue to be dispatched. - * @param {IMessage} message The message to send - */ + public DispatchUntilEmpty(): void { + while (this.messageQueue.length > 0) { + this.Dispatch(); + } + } + public Publish(message: IMessage): void { this.messageQueue.push(message); } - /** - * Subscribe subscibes a subscriber to a particular message type or types. - * @param {ISubscriber} subscriber The subscriber to the message type(s) - * @param {string|string[]} types The message type(s) to subscribe to - */ public Subscribe(subscriber: ISubscriber, types: string | string[]): void { if (types instanceof Array) { for (const type of types) { @@ -90,10 +86,6 @@ class MessageBus implements IMessageBus { this.subscribers[types] = typeSubs; } - /** - * UnsubscribeAll unsubscribes a Subscriber from all messages. - * @param {ISubscriber} subscriber The subscriber to unsubscribe - */ public UnsubscribeAll(subscriber: ISubscriber): void { for (const key in this.subscribers) { const subscribers = this.subscribers[key]; @@ -106,11 +98,6 @@ class MessageBus implements IMessageBus { } } - /** - * Unsubscribe unsubscribes a subscriber from a specific message type or types. - * @param {ISubscriber} subscriber The subscriber to unsubscribe - * @param {string|strings} types The message type(s) to unsubscribe from - */ public Unsubscribe(subscriber: ISubscriber, types: string | string[]): void { if (types instanceof Array) { for (const type of types) { diff --git a/src/pooling/ifreeable.ts b/src/pooling/ifreeable.ts new file mode 100644 index 0000000..3c1668a --- /dev/null +++ b/src/pooling/ifreeable.ts @@ -0,0 +1,28 @@ +/* +Copyright 2020 JamJar Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/** + * IFreeable defines the contract for an object that either is poolable or contains poolable elements, allowing the + * object to be freed/it's constituent parts freed back to object pools. + */ +interface IFreeable { + /** + * Free releases an object or it's constituent parts back into any available object pools. + */ + Free(): void; +} + +export default IFreeable; diff --git a/src/pooling/ipoolable.ts b/src/pooling/ipoolable.ts new file mode 100644 index 0000000..da9f8e7 --- /dev/null +++ b/src/pooling/ipoolable.ts @@ -0,0 +1,36 @@ +/* +Copyright 2020 JamJar Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import IFreeable from "./ifreeable"; + +/** + * IPoolable defines the required properties of an object that is able to be pooled using an object pool. + */ +interface IPoolable extends IFreeable { + /** + * objectInPool is used to mark if the instance of the object is currently pooled. + */ + objectInPool: boolean; + /** + * Recycle is used to reuse an existing object instance, using the arguments provided - similar to a constructor, + * but must be repeatable. + * @param args The arguments to use when recycling the object instance + */ + /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ + Recycle(...args: any): IPoolable; +} + +export default IPoolable; diff --git a/src/pooling/pooled.test.ts b/src/pooling/pooled.test.ts new file mode 100644 index 0000000..1118193 --- /dev/null +++ b/src/pooling/pooled.test.ts @@ -0,0 +1,283 @@ +/* +Copyright 2020 JamJar Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import Pooled from "./pooled"; +import IPoolable from "./ipoolable"; + +class TestPooledObject extends Pooled { + + public static readonly POOL_KEY = "test_pooled_object_pool"; + public static CURRENT_ID = 0; + public id: number; + + public static SimulateNew(): TestPooledObject { + return this.new(TestPooledObject.POOL_KEY, TestPooledObject); + } + + public static SimulateFree(obj: TestPooledObject): void { + return this.free(TestPooledObject.POOL_KEY, obj); + } + + public static SimulateInit(size: number): void { + return this.init(TestPooledObject.POOL_KEY, () => new TestPooledObject(), size); + } + + public static SetPools(pools: Map): void { + Pooled.pools = pools; + } + + public static GetPools(): Map { + return Pooled.pools; + } + + constructor(id?: number, inPool = false) { + super(); + if (id === undefined) { + this.id = TestPooledObject.CURRENT_ID++; + } else { + this.id = id; + } + this.objectInPool = inPool; + } + + public Recycle(): TestPooledObject { + return this; + } + + public Free(): void { + return; + } +} + +describe("Pooled - new", () => { + type TestTuple = [string, TestPooledObject, Map, Map]; + test.each([ + [ + "No pool initialized", + new TestPooledObject(0, false), + new Map(), + new Map(), + ], + [ + "Empty pool", + new TestPooledObject(1, false), + new Map([ + [TestPooledObject.POOL_KEY, [5, []]] + ]), + new Map([ + [TestPooledObject.POOL_KEY, [5, []]] + ]), + ], + [ + "Pool with 5 entries", + new TestPooledObject(10, false), + new Map([ + [TestPooledObject.POOL_KEY, [5, [ + new TestPooledObject(10, true), + new TestPooledObject(11, true), + new TestPooledObject(12, true), + new TestPooledObject(13, true), + new TestPooledObject(14, true), + ]]] + ]), + new Map([ + [TestPooledObject.POOL_KEY, [5, [ + new TestPooledObject(11, true), + new TestPooledObject(12, true), + new TestPooledObject(13, true), + new TestPooledObject(14, true), + ]]] + ]), + ], + [ + "Pool with 1 entry", + new TestPooledObject(14, false), + new Map([ + [TestPooledObject.POOL_KEY, [5, [ + new TestPooledObject(14, true), + ]]] + ]), + new Map([ + [TestPooledObject.POOL_KEY, [5, []]] + ]), + ], + ])("%p", (description: string, expected: TestPooledObject, beforePools: Map, + afterPools: Map) => { + TestPooledObject.SetPools(beforePools); + expect(TestPooledObject.SimulateNew()).toEqual(expected); + expect(TestPooledObject.GetPools()).toEqual(afterPools); + }); +}); + +describe("Pooled - free", () => { + type TestTuple = [string, Map, Map, TestPooledObject]; + test.each([ + [ + "Object already pooled", + new Map(), + new Map(), + new TestPooledObject(0, true), + ], + [ + "No pool initialized", + new Map(), + new Map(), + new TestPooledObject(0, false), + ], + [ + "Pool full", + new Map([ + [TestPooledObject.POOL_KEY, [5, [ + new TestPooledObject(10, true), + new TestPooledObject(11, true), + new TestPooledObject(12, true), + new TestPooledObject(13, true), + new TestPooledObject(14, true), + ]]] + ]), + new Map([ + [TestPooledObject.POOL_KEY, [5, [ + new TestPooledObject(10, true), + new TestPooledObject(11, true), + new TestPooledObject(12, true), + new TestPooledObject(13, true), + new TestPooledObject(14, true), + ]]] + ]), + new TestPooledObject(0, false) + ], + [ + "Pool with 3 entries, maxmimum 4", + new Map([ + [TestPooledObject.POOL_KEY, [5, [ + new TestPooledObject(10, true), + new TestPooledObject(11, true), + new TestPooledObject(12, true), + ]]] + ]), + new Map([ + [TestPooledObject.POOL_KEY, [5, [ + new TestPooledObject(10, true), + new TestPooledObject(11, true), + new TestPooledObject(12, true), + new TestPooledObject(0, true) + ]]] + ]), + new TestPooledObject(0, false) + ], + [ + "Empty pool", + new Map([ + [TestPooledObject.POOL_KEY, [5, [ + ]]] + ]), + new Map([ + [TestPooledObject.POOL_KEY, [5, [ + new TestPooledObject(0, true), + ]]] + ]), + new TestPooledObject(0, false), + ], + ])("%p", (description: string, beforePools: Map, + afterPools: Map, obj: TestPooledObject) => { + TestPooledObject.SetPools(beforePools); + TestPooledObject.SimulateFree(obj); + expect(TestPooledObject.GetPools()).toEqual(afterPools); + }); +}); + + +describe("Pooled - init", () => { + type TestTuple = [string, Map, Map, number]; + test.each([ + [ + "Pool of size 0", + new Map(), + new Map([ + [TestPooledObject.POOL_KEY, [0, []]] + ]), + 0, + ], + [ + "Pool of size 5", + new Map(), + new Map([ + [TestPooledObject.POOL_KEY, [5, [ + new TestPooledObject(2, true), + new TestPooledObject(3, true), + new TestPooledObject(4, true), + new TestPooledObject(5, true), + new TestPooledObject(6, true), + ]]] + ]), + 5, + ], + [ + "Pool of size 3, overwrite pool of size 4", + new Map([ + [TestPooledObject.POOL_KEY, [4, [ + new TestPooledObject(0, true), + new TestPooledObject(1, true), + new TestPooledObject(2, true), + new TestPooledObject(3, true), + ]]] + ]), + new Map([ + [TestPooledObject.POOL_KEY, [3, [ + new TestPooledObject(7, true), + new TestPooledObject(8, true), + new TestPooledObject(9, true), + ]]] + ]), + 3, + ], + [ + "Pool of size 2, 2 other pools", + new Map([ + ["test-a", [4, [ + new TestPooledObject(0, true), + new TestPooledObject(1, true), + ]]], + ["test-b", [10, [ + new TestPooledObject(5, true), + new TestPooledObject(7, true), + new TestPooledObject(9, true), + ]]] + ]), + new Map([ + ["test-a", [4, [ + new TestPooledObject(0, true), + new TestPooledObject(1, true), + ]]], + ["test-b", [10, [ + new TestPooledObject(5, true), + new TestPooledObject(7, true), + new TestPooledObject(9, true), + ]]], + [TestPooledObject.POOL_KEY, [2, [ + new TestPooledObject(10, true), + new TestPooledObject(11, true), + ]]] + ]), + 2, + ], + ])("%p", (description: string, beforePools: Map, + afterPools: Map, size: number) => { + TestPooledObject.SetPools(beforePools); + TestPooledObject.SimulateInit(size); + expect(TestPooledObject.GetPools()).toEqual(afterPools); + }); +}); diff --git a/src/pooling/pooled.ts b/src/pooling/pooled.ts new file mode 100644 index 0000000..9048583 --- /dev/null +++ b/src/pooling/pooled.ts @@ -0,0 +1,124 @@ +/* +Copyright 2020 JamJar Authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import IPoolable from "./ipoolable"; + +/** + * Pooled is the base class for any object that needs to implement object pooling. + * This base class keeps track of all object pools through static global memory, providing generic methods for + * requesting objects from the pools, freeing memory up for the pools to use, and initializing the pools to a certain + * size. + * The pooled class also provides a required objectInPool variable to allow instances to be marked as available in the + * pool, used to avoid duplicating objects in the same pool (multiple free calls on the same object). + */ +abstract class Pooled { + /** + * objectInPool is true if an object is made available in the object pool. If it is false it is not + * currently available in the object pool. + * This is used to avoid adding the same object to the same object pool multiple times if there are successive + * calls to free the the same object. + */ + public objectInPool = false; + + /** + * pools is the global, static mapping of string keys to object pools. + * An object pool contains two pieces of data, the maximum size of the pool (first value), and the objects that + * make up the pool as an array (second value). + */ + protected static pools: Map = new Map(); + + /** + * new is used to request a new object from the pool specified, if the pool is unavailable or empty it will use + * the type to provision a new object through a constructor. + * This is a generic method, it includes a cast to the generic type provided - this cast can fail if the objects + * returned from the pool are not the type expected. + * @param poolKey The key of the pool to retrieve from. + * @param type The fallback constructor to use if the pool is not initialized/empty. + * @param args The args to use when creating/recycling the object. + */ + /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ + protected static new(poolKey: string, type: { new(...args: any): T }, ...args: any): T { + // Get any object pool with matching key, if no pool just create unpooled object + const pool = Pooled.pools.get(poolKey); + + if (pool === undefined) { + // No pool initialized, create unpooled object + return new type(...args); + } + + // Check for a free entry in the pool, if one is free recycle and use it + if (pool[1].length > 0) { + // Will always be non-undefined as the length is > 0 + /* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */ + const recycled = pool[1].shift()!.Recycle(...args) as T; + recycled.objectInPool = false; + return recycled; + } + + // No free entry, create unpooled object + return new type(...args); + } + + /** + * free is used to mark a provided object as free in the pool provided. This method can be called multiple times + * with the same object, it will only add one entry to the pool. + * @param poolKey The key of the pool to add the object to. + * @param obj The object to add to the pool. + */ + protected static free(poolKey: string, obj: IPoolable): void { + // Object hasn't already been added to the pool + if (obj.objectInPool) { + return; + } + + // Get pool, if it doesn't exist just return + const pool = Pooled.pools.get(poolKey); + if (pool === undefined) { + return; + } + + // Check if the pool is smaller than it's maximum size (first value is pool max size, second value is objects + // in the pool. + if (pool[0] > pool[1].length) { + // Pool is not full, add the new object to the pool, mark that the object has been added to the pool + pool[1].push(obj); + obj.objectInPool = true; + } + } + + /** + * init is used to initialize an object pool to a certain size. This method takes a key of the pool to initialize, + * an 'empty generator' which is a function that should return an empty/blank instance of the object being pooled + * which can be overwritten at a later point, and the maximum size of the pool (which it will be initialized to + * at the start using the empty generator). + * @param poolKey + * @param emptyGenerator + * @param size + */ + protected static init(poolKey: string, emptyGenerator: () => IPoolable, size: number): void { + // Create an empty pool of maximum size provided + const pool: [number, IPoolable[]] = [size, []]; + // Generate enough empty/blank objects to fill up to the maximum pool size and add to the pool + for (let i = 0; i < size; i++) { + const empty = emptyGenerator(); + empty.objectInPool = true; + pool[1].push(empty); + } + Pooled.pools.set(poolKey, pool); + } +} + +export default Pooled; diff --git a/src/rendering/irenderable.ts b/src/rendering/irenderable.ts index b09f557..0dd4e09 100644 --- a/src/rendering/irenderable.ts +++ b/src/rendering/irenderable.ts @@ -19,12 +19,13 @@ import Material from "./material/material"; import Matrix4D from "../geometry/matrix_4d"; import Polygon from "../shape/polygon"; import DrawMode from "./draw_mode"; +import IPoolable from "../pooling/ipoolable"; /** * Renderable represents something that can be rendered. * Contains information for rendering. */ -interface IRenderable { +interface IRenderable extends IPoolable { /** * The Z-Order of the object, the order at which the object will appear * infront or behind other objects. A higher Z-Order means in front, a @@ -55,4 +56,4 @@ interface IRenderable { camera?: IEntity; } -export default IRenderable; \ No newline at end of file +export default IRenderable; diff --git a/src/rendering/material/material.ts b/src/rendering/material/material.ts index a2befb4..1186416 100644 --- a/src/rendering/material/material.ts +++ b/src/rendering/material/material.ts @@ -18,12 +18,13 @@ import Texture from "../texture/texture"; import ShaderAsset from "../shader/shader_asset"; import Color from "../color"; import IMaterialOptions from "./imaterial_options"; +import IFreeable from "../../pooling/ifreeable"; /** * Material represents how something is displayed and rendered, specifying * shaders, textures and colors. */ -class Material { +class Material implements IFreeable { private static readonly NO_TEXTURE_COLOR = new Color(0.54, 0, 0.54, 1); /** * List of shaders to apply. @@ -42,14 +43,14 @@ class Material { constructor(options: IMaterialOptions = {}) { // Default shaders if a texture is present let defaultShaders = [ - ShaderAsset.DEFAULT_TEXTURE_VERTEX_SHADER_NAME, + ShaderAsset.DEFAULT_TEXTURE_VERTEX_SHADER_NAME, ShaderAsset.DEFAULT_TEXTURE_FRAGMENT_SHADER_NAME ]; let defaultColor = new Color(1,1,1,1); if (options.texture === undefined) { // Default shaders for no texture defaultShaders = [ - ShaderAsset.DEFAULT_PRIMITIVE_VERTEX_SHADER_NAME, + ShaderAsset.DEFAULT_PRIMITIVE_VERTEX_SHADER_NAME, ShaderAsset.DEFAULT_PRIMITIVE_FRAGMENT_SHADER_NAME ]; defaultColor = Material.NO_TEXTURE_COLOR; @@ -86,6 +87,12 @@ class Material { color: color }); } + + public Free(): void { + if (this.texture !== undefined) { + this.texture.Free(); + } + } } -export default Material; \ No newline at end of file +export default Material; diff --git a/src/rendering/renderable.ts b/src/rendering/renderable.ts index f01d621..2f660ab 100644 --- a/src/rendering/renderable.ts +++ b/src/rendering/renderable.ts @@ -20,12 +20,32 @@ import IRenderable from "./irenderable"; import Matrix4D from "../geometry/matrix_4d"; import Polygon from "../shape/polygon"; import DrawMode from "./draw_mode"; +import Pooled from "../pooling/pooled"; /** * Renderable represents something that can be rendered. * Contains information for rendering. */ -class Renderable implements IRenderable { +class Renderable extends Pooled implements IRenderable { + + public static New(zOrder: number, vertices: Polygon, modelMatrix: Matrix4D, material: Material, drawMode: DrawMode, + payload?: T, camera?: IEntity): Renderable { + return this.new>(Renderable.POOL_KEY, Renderable, zOrder, vertices, modelMatrix, material, + drawMode, payload, camera); + } + + public static Free(obj: Renderable): void { + this.free(Renderable.POOL_KEY, obj); + } + + public static Init(size: number): void { + this.init(Renderable.POOL_KEY, () => { + return new Renderable(0, new Polygon([]), new Matrix4D(), new Material(), DrawMode.POINTS); + }, size); + } + + private static POOL_KEY = "jamjar_renderable"; + /** * The Z-Order of the object, the order at which the object will appear * infront or behind other objects. A higher Z-Order means in front, a @@ -60,6 +80,7 @@ class Renderable implements IRenderable { public camera?: IEntity; constructor(zOrder: number, vertices: Polygon, modelMatrix: Matrix4D, material: Material, drawMode: DrawMode, payload?: T, camera?: IEntity) { + super(); this.zOrder = zOrder; this.vertices = vertices; this.modelMatrix = modelMatrix; @@ -68,6 +89,24 @@ class Renderable implements IRenderable { this.payload = payload; this.camera = camera; } + + public Recycle(zOrder: number, vertices: Polygon, modelMatrix: Matrix4D, material: Material, drawMode: DrawMode, + payload?: T, camera?: IEntity): Renderable { + this.zOrder = zOrder; + this.vertices = vertices; + this.modelMatrix = modelMatrix; + this.material = material; + this.drawMode = drawMode; + this.payload = payload; + this.camera = camera; + return this; + } + + public Free(): void { + this.vertices.Free(); + this.material.Free(); + Renderable.Free(this); + } } -export default Renderable; \ No newline at end of file +export default Renderable; diff --git a/src/rendering/texture/texture.ts b/src/rendering/texture/texture.ts index d3665b5..f195ffe 100644 --- a/src/rendering/texture/texture.ts +++ b/src/rendering/texture/texture.ts @@ -16,12 +16,13 @@ limitations under the License. import Polygon from "../../shape/polygon"; import Vector from "../../geometry/vector"; +import IFreeable from "../../pooling/ifreeable"; /** * Texture is the mapping from an image that has been loaded, deciding * how the texture should be drawn and represented. */ -class Texture { +class Texture implements IFreeable { /** * Name of the image the texture refers to. */ @@ -31,7 +32,7 @@ class Texture { */ public points: Polygon; - constructor(image: string, points: Polygon = Polygon.QuadByPoints(new Vector(0,0), new Vector(1,1))) { + constructor(image: string, points: Polygon = Polygon.QuadByPoints(Vector.New(0,0), Vector.New(1,1))) { this.image = image; this.points = points; } @@ -40,10 +41,12 @@ class Texture { * Make a value copy of the texture. */ public Copy(): Texture { - return new Texture( + const tex = new Texture( this.image, this.points.Copy() ); + + return tex; } /** @@ -53,13 +56,13 @@ class Texture { * The indexed sprite sheet operates from left to right, bottom to top. * For example, the following shows the indexes of each position in the * sprite sheet: - * + * * |---------| * | 0 1 2 | * | 3 4 5 | * | 6 7 8 | * |---------| - * + * * @param {number} rowCount - number of rows in the sprite sheet (vertically). * @param {number} columnCount - number of columns in the sprite sheet (horizontally). * @returns {Polygon[]} - An indexed array of shapes to access each sprite. @@ -79,13 +82,17 @@ class Texture { const xStart = j * spriteWidth; const yStart = i * spriteHeight; spriteSheetIndex.push(Polygon.QuadByPoints( - new Vector(xStart, yStart), - new Vector(xStart + spriteWidth, yStart + spriteHeight) + Vector.New(xStart, yStart), + Vector.New(xStart + spriteWidth, yStart + spriteHeight) )); } } return spriteSheetIndex; } + + public Free(): void { + this.points.Free(); + } } -export default Texture; \ No newline at end of file +export default Texture; diff --git a/src/shape/aabb.ts b/src/shape/aabb.ts index f70f6eb..07550f8 100644 --- a/src/shape/aabb.ts +++ b/src/shape/aabb.ts @@ -29,7 +29,7 @@ class AABB implements IShape { public center: Vector; public size: Vector; - constructor(size: Vector, center: Vector = new Vector(0,0)) { + constructor(size: Vector, center: Vector = Vector.New(0,0)) { this.center = center; this.size = size; } @@ -52,10 +52,10 @@ class AABB implements IShape { const bottom = this.center.y - this.size.y / 2; const points = [ - new Vector(left, top), - new Vector(right, top), - new Vector(left, bottom), - new Vector(right, bottom) + Vector.New(left, top), + Vector.New(right, top), + Vector.New(left, bottom), + Vector.New(right, bottom) ]; let farthestDistance = points[0].Dot(direction); @@ -79,6 +79,11 @@ class AABB implements IShape { const bottom = this.center.y - this.size.y / 2; return point.x < right && point.x > left && point.y < top && point.y > bottom; } + + public Free(): void { + this.center.Free(); + this.size.Free(); + } } export default AABB; diff --git a/src/shape/ellipse.test.ts b/src/shape/ellipse.test.ts index fa74ca3..c5123b5 100644 --- a/src/shape/ellipse.test.ts +++ b/src/shape/ellipse.test.ts @@ -123,7 +123,7 @@ describe("Polygon - Circle", () => { [ "2r circle around origin", new Ellipse(new Vector(2, 2), 0, new Vector(0, 0)), - Ellipse.Circle(2, new Vector(0, 0)) + Ellipse.Circle(2, 0, 0) ], ])("%p", (description: string, expected: Ellipse, ellipse: Ellipse) => { expect(ellipse).toEqual(expected); diff --git a/src/shape/ellipse.ts b/src/shape/ellipse.ts index c7a1658..91537a4 100644 --- a/src/shape/ellipse.ts +++ b/src/shape/ellipse.ts @@ -26,7 +26,7 @@ class Ellipse implements IShape { public dimensions: Vector; public orientation: number; - constructor(dimensions: Vector, orientation = 0, center: Vector = new Vector(0, 0)) { + constructor(dimensions: Vector, orientation = 0, center: Vector = Vector.New(0, 0)) { this.center = center; this.dimensions = dimensions; this.orientation = orientation; @@ -52,20 +52,25 @@ class Ellipse implements IShape { public FarthestPointInDirection(direction: Vector): Vector { const angle = Math.atan2(direction.y, direction.x); - const angledDimensions = this.dimensions.Rotate(new Vector(0, 0), this.orientation); - return new Vector( + const angledDimensions = this.dimensions.Rotate(Vector.New(0, 0), this.orientation); + return Vector.New( this.center.x + (angledDimensions.x * Math.cos(angle)), this.center.y + (angledDimensions.y * Math.sin(angle)) ); } + public Free(): void { + this.center.Free(); + this.dimensions.Free(); + } + /** * Circle returns a new Ellipse in the shape of a circle. * @param {number} radius Radius of the circle * @param {Vector} center Centre of the circle */ - public static Circle(radius: number, center: Vector = new Vector(0, 0)): Ellipse { - return new Ellipse(new Vector(radius, radius), 0, center); + public static Circle(radius: number, centerX = 0, centerY = 0): Ellipse { + return new Ellipse(Vector.New(radius, radius), 0, Vector.New(centerX, centerY)); } } diff --git a/src/shape/ishape.ts b/src/shape/ishape.ts index c54c309..14bfd3c 100644 --- a/src/shape/ishape.ts +++ b/src/shape/ishape.ts @@ -15,13 +15,18 @@ limitations under the License. */ import Vector from "../geometry/vector"; +import IFreeable from "../pooling/ifreeable"; import Transform from "../standard/transform/transform"; /** * IShape is the interface for a shape, defining all methods that need implemented in order * for the shape to be used with collision detection. */ -interface IShape { +interface IShape extends IFreeable { + /** + * Center calculates/retrieves the center of a shape. + * @returns {Vector} The center point of the shape + */ /** * Center calculates/retrieves the center of a shape. * @returns {Vector} The center point of the shape @@ -48,4 +53,4 @@ interface IShape { PointInside(point: Vector): boolean; } -export default IShape; \ No newline at end of file +export default IShape; diff --git a/src/shape/polygon.test.ts b/src/shape/polygon.test.ts index d4123ae..afa981f 100644 --- a/src/shape/polygon.test.ts +++ b/src/shape/polygon.test.ts @@ -196,17 +196,17 @@ describe("Polygon - Center", () => { [ "Square around origin", new Vector(0,0), - Polygon.RectangleByDimensions(1, 1, new Vector(0,0)) + Polygon.RectangleByDimensions(1, 1, 0, 0) ], [ "Rectangle around origin", new Vector(0,0), - Polygon.RectangleByDimensions(10, 1, new Vector(0,0)) + Polygon.RectangleByDimensions(10, 1, 0, 0) ], [ "Rectangle around point", new Vector(10,5), - Polygon.RectangleByDimensions(10, 3, new Vector(10,5)) + Polygon.RectangleByDimensions(10, 3, 10, 5) ], ])("%p", (description: string, expected: Vector, polygon: Polygon) => { expect(polygon.Center()).toEqual(expected); @@ -224,8 +224,8 @@ describe("Polygon - Apply4D", () => { ], [ "Square, move up 3, scale 2", - Polygon.RectangleByDimensions(2, 2, new Vector(0,3)), - Polygon.RectangleByDimensions(1, 1, new Vector(0,0)), + Polygon.RectangleByDimensions(2, 2, 0, 3), + Polygon.RectangleByDimensions(1, 1, 0, 0), ((): Matrix4D => { const mat = new Matrix4D(); mat.Translate(new Vector(0,3)); @@ -295,7 +295,7 @@ describe("Polygon - RectangleByDimensions", () => { true ], ])("%p", (description: string, expected: Polygon, width: number, height: number, origin: Vector, wrap: boolean) => { - expect(Polygon.RectangleByDimensions(width, height, origin, wrap)).toEqual(expected); + expect(Polygon.RectangleByDimensions(width, height, origin.x, origin.y, wrap)).toEqual(expected); }); }); @@ -369,7 +369,7 @@ describe("Polygon - RectangleByPoints", () => { }); describe("Polygon - QuadByDimensions", () => { - type TestTuple = [string, Polygon, number, number, Vector | undefined]; + type TestTuple = [string, Polygon, number, number, number | undefined, number | undefined]; test.each([ [ "0*0", @@ -383,6 +383,7 @@ describe("Polygon - QuadByDimensions", () => { ]), 0, 0, + undefined, undefined ], [ @@ -398,6 +399,7 @@ describe("Polygon - QuadByDimensions", () => { ]), 2, 2, + undefined, undefined ], [ @@ -413,6 +415,7 @@ describe("Polygon - QuadByDimensions", () => { ]), 3, 2, + undefined, undefined ], [ @@ -428,10 +431,11 @@ describe("Polygon - QuadByDimensions", () => { ]), 2, 2, - new Vector(4,4) + 4, + 4 ], - ])("%p", (description: string, expected: Polygon, width: number, height: number, origin: Vector | undefined) => { - expect(Polygon.QuadByDimensions(width, height, origin)).toEqual(expected); + ])("%p", (description: string, expected: Polygon, width: number, height: number, originX: number | undefined, originY: number | undefined) => { + expect(Polygon.QuadByDimensions(width, height, originX, originY)).toEqual(expected); }); }); @@ -531,6 +535,6 @@ describe("Polygon - EllipseEstimation", () => { ], ])("%p", (description: string, expected: Polygon, numOfEdges: number, dimensions: Vector, center: Vector, wrap: boolean) => { - expect(Polygon.EllipseEstimation(numOfEdges, dimensions, center, wrap)).toEqual(expected); + expect(Polygon.EllipseEstimation(numOfEdges, dimensions, center.x, center.y, wrap)).toEqual(expected); }); }); diff --git a/src/shape/polygon.ts b/src/shape/polygon.ts index 380fbc4..2ade60f 100644 --- a/src/shape/polygon.ts +++ b/src/shape/polygon.ts @@ -57,7 +57,7 @@ class Polygon implements IShape { public FarthestPointInDirection(direction: Vector): Vector { let farthestDistance = -Infinity; // If there are no points, just return point 0,0 - let farthestPoint: Vector = new Vector(0,0); + let farthestPoint: Vector = Vector.New(0, 0); for (const point of this.points) { const distanceInDirection = point.Dot(direction); if (distanceInDirection > farthestDistance) { @@ -75,7 +75,7 @@ class Polygon implements IShape { xSum += point.x; ySum += point.y; } - return new Vector( + return Vector.New( xSum / this.points.length, ySum / this.points.length ); @@ -106,15 +106,21 @@ class Polygon implements IShape { if ((cornerA.y < point.y && cornerB.y >= point.y || cornerB.y < point.y && cornerA.y >= point.y) && (cornerA.x <= point.x || cornerB.x <= point.x)) { - if (cornerA.x + (point.y - cornerA.y)/(cornerB.y-cornerA.y)*(cornerB.x-cornerA.x) < point.x) { + if (cornerA.x + (point.y - cornerA.y) / (cornerB.y - cornerA.y) * (cornerB.x - cornerA.x) < point.x) { inPolygon = !inPolygon; } } - j=i; + j = i; } return inPolygon; } + public Free(): void { + for (const point of this.points) { + point.Free(); + } + } + /** * GetFloat32Array converts the polygon to a WebGL/glMatrix compatible Float32Array * @returns {Float32Array} The array representation of the polygon @@ -135,14 +141,14 @@ class Polygon implements IShape { * @param {number} height Height of the rectangle * @param {origin} origin Center point of the rectangle */ - public static RectangleByDimensions(width: number, height: number, origin: Vector = new Vector(0,0), wrap = false): Polygon { - const halfWidth = width/2; - const halfHeight = height/2; + public static RectangleByDimensions(width: number, height: number, originX = 0, originY = 0, wrap = false): Polygon { + const halfWidth = width / 2; + const halfHeight = height / 2; return new Polygon([ - new Vector(origin.x - halfWidth, origin.y + halfHeight), // top left - new Vector(origin.x + halfWidth, origin.y + halfHeight), // top right - new Vector(origin.x + halfWidth, origin.y - halfHeight), // bottom right - new Vector(origin.x - halfWidth, origin.y - halfHeight), // bottom left + Vector.New(originX - halfWidth, originY + halfHeight), // top left + Vector.New(originX + halfWidth, originY + halfHeight), // top right + Vector.New(originX + halfWidth, originY - halfHeight), // bottom right + Vector.New(originX - halfWidth, originY - halfHeight), // bottom left ], wrap); } @@ -153,11 +159,15 @@ class Polygon implements IShape { * @param {Vector} topRight Top right of the rectangle */ public static RectangleByPoints(bottomLeft: Vector, topRight: Vector, wrap = false): Polygon { + const bottomRight = bottomLeft.Copy(); + bottomRight.x += topRight.x - bottomLeft.x; + const topLeft = topRight.Copy(); + topLeft.x -= topRight.x - bottomLeft.x; return new Polygon([ bottomLeft.Copy(), - bottomLeft.Copy().Add(new Vector(topRight.x - bottomLeft.x, 0)), // bottom right + bottomRight, topRight.Copy(), - topRight.Copy().Sub(new Vector(topRight.x - bottomLeft.x, 0)), // top left + topLeft ], wrap); } @@ -168,17 +178,23 @@ class Polygon implements IShape { * @param {number} height Height of the quad * @param {Vector} origin Center point of the quad */ - public static QuadByDimensions(width: number, height: number, origin: Vector = new Vector(0,0)): Polygon { - const halfWidth = width/2; - const halfHeight = height/2; + public static QuadByDimensions(width: number, height: number, originX = 0, originY = 0): Polygon { + const halfWidth = width / 2; + const halfHeight = height / 2; + + const bottomRight = Vector.New(originX + halfWidth, originY - halfHeight); + const bottomLeft = Vector.New(originX - halfWidth, originY - halfHeight); + const topLeft = Vector.New(originX - halfWidth, originY + halfHeight); + const topRight = Vector.New(originX + halfWidth, originY + halfHeight); + return new Polygon([ - new Vector(origin.x + halfWidth, origin.y - halfHeight), // bottom right - new Vector(origin.x - halfWidth, origin.y - halfHeight), // bottom left - new Vector(origin.x - halfWidth, origin.y + halfHeight), // top left + bottomRight, + bottomLeft, + topLeft, - new Vector(origin.x - halfWidth, origin.y + halfHeight), // top left - new Vector(origin.x + halfWidth, origin.y + halfHeight), // top right - new Vector(origin.x + halfWidth, origin.y - halfHeight), // bottom right + topLeft.Copy(), + topRight, + bottomRight.Copy(), ]); } @@ -189,13 +205,17 @@ class Polygon implements IShape { * @param {Vector} topRight Top right of the quad */ public static QuadByPoints(bottomLeft: Vector, topRight: Vector): Polygon { + const bottomRight = bottomLeft.Copy(); + bottomRight.x += topRight.x - bottomLeft.x; + const topLeft = topRight.Copy(); + topLeft.x -= topRight.x - bottomLeft.x; return new Polygon([ - bottomLeft.Copy().Add(new Vector(topRight.x - bottomLeft.x, 0)), // bottom right + bottomRight, bottomLeft.Copy(), - topRight.Copy().Sub(new Vector(topRight.x - bottomLeft.x, 0)), // top left - topRight.Copy().Sub(new Vector(topRight.x - bottomLeft.x, 0)), // top left + topLeft, + topLeft.Copy(), topRight.Copy(), - bottomLeft.Copy().Add(new Vector(topRight.x - bottomLeft.x, 0)), // bottom right + bottomRight.Copy() ]); } @@ -206,15 +226,15 @@ class Polygon implements IShape { * @param center Ellipse center * @param wrap If the polygon should wrap on itself (first point == last point) */ - public static EllipseEstimation(numOfPoints: number, dimensions: Vector, - center: Vector = new Vector(0, 0), wrap = false): Polygon { + public static EllipseEstimation(numOfPoints: number, dimensions: Vector, centerX = 0, centerY = 0, + wrap = false): Polygon { const points: Vector[] = []; for (let i = 0; i < numOfPoints; i++) { const done = i / numOfPoints; const angle = done * 2 * Math.PI; - points.push(new Vector( - dimensions.x * Math.cos(angle) + center.x, - dimensions.y * Math.sin(angle) + center.y, + points.push(Vector.New( + dimensions.x * Math.cos(angle) + centerX, + dimensions.y * Math.sin(angle) + centerY, )); } return new Polygon(points, wrap); diff --git a/src/standard/camera/camera.ts b/src/standard/camera/camera.ts index cf89e50..17253c3 100644 --- a/src/standard/camera/camera.ts +++ b/src/standard/camera/camera.ts @@ -1,5 +1,5 @@ /* -Copyright 2019 JamJar Authors +Copyright 2020 JamJar Authors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -49,9 +49,9 @@ class Camera extends Component { public virtualScale: Vector; constructor(backgroundColor: Color = new Color(0,0,0,1), - viewportPosition: Vector = new Vector(0,0), - viewportScale: Vector = new Vector(1,1), - virtualScale: Vector = new Vector(160,90)) { + viewportPosition: Vector = Vector.New(0,0), + viewportScale: Vector = Vector.New(1,1), + virtualScale: Vector = Vector.New(160,90)) { super(Camera.KEY); this.backgroundColor = backgroundColor; this.viewportPosition = viewportPosition; @@ -77,6 +77,12 @@ class Camera extends Component { 100 ); } + + public Free(): void { + this.viewportPosition.Free(); + this.viewportScale.Free(); + this.virtualScale.Free(); + } } export default Camera; diff --git a/src/standard/collision/algorithm/aabb_algorithm.test.ts b/src/standard/collision/algorithm/aabb_algorithm.test.ts index cae9800..b9f8c40 100644 --- a/src/standard/collision/algorithm/aabb_algorithm.test.ts +++ b/src/standard/collision/algorithm/aabb_algorithm.test.ts @@ -29,20 +29,20 @@ describe("AABBAlgorithm - CalculateCollision", () => { "Squares (one AABB, one Polygon), no collision", [], [ - new AABB(new Vector(1,1)), - Polygon.RectangleByDimensions(1, 1, new Vector(3, 3)) + new AABB(new Vector(1, 1)), + Polygon.RectangleByDimensions(1, 1, 3, 3) ], new AABBAlgorithm() ], [ "Squares, collision", [new CollisionInfo( - Polygon.RectangleByDimensions(3, 3, new Vector(0, 0)), - Polygon.RectangleByDimensions(3, 3, new Vector(1, 1)) + Polygon.RectangleByDimensions(3, 3, 0, 0), + Polygon.RectangleByDimensions(3, 3, 1, 1) )], [ - Polygon.RectangleByDimensions(3, 3, new Vector(0, 0)), - Polygon.RectangleByDimensions(3, 3, new Vector(1, 1)) + Polygon.RectangleByDimensions(3, 3, 0, 0), + Polygon.RectangleByDimensions(3, 3, 1, 1) ], new AABBAlgorithm() ], @@ -50,20 +50,20 @@ describe("AABBAlgorithm - CalculateCollision", () => { "Squares, edge no collision", [], [ - Polygon.RectangleByDimensions(1, 1, new Vector(0, 0)), - Polygon.RectangleByDimensions(1, 1, new Vector(1, 1)) + Polygon.RectangleByDimensions(1, 1, 0, 0), + Polygon.RectangleByDimensions(1, 1, 1, 1) ], new AABBAlgorithm() ], [ "Squares, edge collision", [new CollisionInfo( - Polygon.RectangleByDimensions(1, 1, new Vector(0, 0)), - Polygon.RectangleByDimensions(1, 1, new Vector(0.999, 0.999)) + Polygon.RectangleByDimensions(1, 1, 0, 0), + Polygon.RectangleByDimensions(1, 1, 0.999, 0.999) )], [ - Polygon.RectangleByDimensions(1, 1, new Vector(0, 0)), - Polygon.RectangleByDimensions(1, 1, new Vector(0.999, 0.999)) + Polygon.RectangleByDimensions(1, 1, 0, 0), + Polygon.RectangleByDimensions(1, 1, 0.999, 0.999) ], new AABBAlgorithm() ], @@ -71,20 +71,20 @@ describe("AABBAlgorithm - CalculateCollision", () => { "Ellipse and rectangle, no collision", [], [ - Polygon.RectangleByDimensions(1, 1, new Vector(0, 0)), - Ellipse.Circle(2, new Vector(5, 5)) + Polygon.RectangleByDimensions(1, 1, 0, 0), + Ellipse.Circle(2, 5, 5) ], new AABBAlgorithm() ], [ "Ellipse and rectangle, collision", [new CollisionInfo( - Polygon.RectangleByDimensions(1, 1, new Vector(0, 0)), - Ellipse.Circle(2, new Vector(-1, 0)), + Polygon.RectangleByDimensions(1, 1, 0, 0), + Ellipse.Circle(2, -1, 0), )], [ - Polygon.RectangleByDimensions(1, 1, new Vector(0, 0)), - Ellipse.Circle(2, new Vector(-1, 0)) + Polygon.RectangleByDimensions(1, 1, 0, 0), + Ellipse.Circle(2, -1, 0) ], new AABBAlgorithm() ], diff --git a/src/standard/collision/algorithm/aabb_algorithm.ts b/src/standard/collision/algorithm/aabb_algorithm.ts index d3d796f..da618a2 100644 --- a/src/standard/collision/algorithm/aabb_algorithm.ts +++ b/src/standard/collision/algorithm/aabb_algorithm.ts @@ -43,10 +43,10 @@ class AABBAlgorithm implements ICollisionAlgorithm { } private aabb(a: IShape, b: IShape): CollisionInfo | undefined { - const aTopLeft = a.FarthestPointInDirection(new Vector(-1, 1)); - const aBottomRight = a.FarthestPointInDirection(new Vector(1, -1)); - const bTopLeft = b.FarthestPointInDirection(new Vector(-1, 1)); - const bBottomRight = b.FarthestPointInDirection(new Vector(1, -1)); + const aTopLeft = a.FarthestPointInDirection(Vector.New(-1, 1)); + const aBottomRight = a.FarthestPointInDirection(Vector.New(1, -1)); + const bTopLeft = b.FarthestPointInDirection(Vector.New(-1, 1)); + const bBottomRight = b.FarthestPointInDirection(Vector.New(1, -1)); if (aTopLeft.x < bBottomRight.x && aBottomRight.x > bTopLeft.x && aBottomRight.y < bTopLeft.y && diff --git a/src/standard/collision/algorithm/gjk_algorithm.test.ts b/src/standard/collision/algorithm/gjk_algorithm.test.ts index 23d3160..5ebf3d3 100644 --- a/src/standard/collision/algorithm/gjk_algorithm.test.ts +++ b/src/standard/collision/algorithm/gjk_algorithm.test.ts @@ -18,7 +18,6 @@ import IShape from "../../../shape/ishape"; import GJKAlgorithm from "./gjk_algorithm"; import CollisionInfo from "../collision_info"; import Polygon from "../../../shape/polygon"; -import Vector from "../../../geometry/vector"; import Ellipse from "../../../shape/ellipse"; describe("GJKAlgorithm - CalculateCollision", () => { @@ -28,20 +27,20 @@ describe("GJKAlgorithm - CalculateCollision", () => { "Squares, no collision", [], [ - Polygon.RectangleByDimensions(1, 1, new Vector(0, 0)), - Polygon.RectangleByDimensions(1, 1, new Vector(3, 3)) + Polygon.RectangleByDimensions(1, 1, 0, 0), + Polygon.RectangleByDimensions(1, 1, 3, 3) ], new GJKAlgorithm() ], [ "Squares, collision", [new CollisionInfo( - Polygon.RectangleByDimensions(3, 3, new Vector(0, 0)), - Polygon.RectangleByDimensions(3, 3, new Vector(1, 1)) + Polygon.RectangleByDimensions(3, 3, 0, 0), + Polygon.RectangleByDimensions(3, 3, 1, 1) )], [ - Polygon.RectangleByDimensions(3, 3, new Vector(0, 0)), - Polygon.RectangleByDimensions(3, 3, new Vector(1, 1)) + Polygon.RectangleByDimensions(3, 3, 0, 0), + Polygon.RectangleByDimensions(3, 3, 1, 1) ], new GJKAlgorithm() ], @@ -49,20 +48,20 @@ describe("GJKAlgorithm - CalculateCollision", () => { "Squares, edge no collision", [], [ - Polygon.RectangleByDimensions(1, 1, new Vector(0, 0)), - Polygon.RectangleByDimensions(1, 1, new Vector(1, 1)) + Polygon.RectangleByDimensions(1, 1, 0, 0), + Polygon.RectangleByDimensions(1, 1, 1, 1) ], new GJKAlgorithm() ], [ "Squares, edge collision", [new CollisionInfo( - Polygon.RectangleByDimensions(1, 1, new Vector(0, 0)), - Polygon.RectangleByDimensions(1, 1, new Vector(0.999, 0.999)) + Polygon.RectangleByDimensions(1, 1, 0, 0), + Polygon.RectangleByDimensions(1, 1, 0.999, 0.999) )], [ - Polygon.RectangleByDimensions(1, 1, new Vector(0, 0)), - Polygon.RectangleByDimensions(1, 1, new Vector(0.999, 0.999)) + Polygon.RectangleByDimensions(1, 1, 0, 0), + Polygon.RectangleByDimensions(1, 1, 0.999, 0.999) ], new GJKAlgorithm() ], @@ -70,20 +69,20 @@ describe("GJKAlgorithm - CalculateCollision", () => { "Ellipse and rectangle, no collision", [], [ - Polygon.RectangleByDimensions(1, 1, new Vector(0, 0)), - Ellipse.Circle(2, new Vector(5, 5)) + Polygon.RectangleByDimensions(1, 1, 0, 0), + Ellipse.Circle(2, 5, 5) ], new GJKAlgorithm() ], [ "Ellipse and rectangle, collision", [new CollisionInfo( - Polygon.RectangleByDimensions(1, 1, new Vector(0, 0)), - Ellipse.Circle(2, new Vector(-1, 0)), + Polygon.RectangleByDimensions(1, 1, 0, 0), + Ellipse.Circle(2, -1, 0), )], [ - Polygon.RectangleByDimensions(1, 1, new Vector(0, 0)), - Ellipse.Circle(2, new Vector(-1, 0)) + Polygon.RectangleByDimensions(1, 1, 0, 0), + Ellipse.Circle(2, -1, 0) ], new GJKAlgorithm() ], diff --git a/src/standard/collision/algorithm/gjk_algorithm.ts b/src/standard/collision/algorithm/gjk_algorithm.ts index f1ea5b4..f130a9a 100644 --- a/src/standard/collision/algorithm/gjk_algorithm.ts +++ b/src/standard/collision/algorithm/gjk_algorithm.ts @@ -100,7 +100,7 @@ class GJKAlgorithm implements ICollisionAlgorithm { const ac = c.Copy().Sub(a); // Determine perpendicular of the a->b line - let abPerp = new Vector(ab.y, -ab.x); + let abPerp = Vector.New(ab.y, -ab.x); // Check the handedness of the perpendicular, it should // face AWAY from the simplex @@ -118,7 +118,7 @@ class GJKAlgorithm implements ICollisionAlgorithm { } // Determine perpendicular of the a->c line - let acPerp = new Vector(ac.y, -ac.x); + let acPerp = Vector.New(ac.y, -ac.x); // Check the handedness of the perpendicular, it should // face AWAY from the simplex @@ -144,7 +144,7 @@ class GJKAlgorithm implements ICollisionAlgorithm { const ab = b.Copy().Sub(a); // Get the perpendicular of the a->b line - let abPerp = new Vector(ab.y, -ab.x); + let abPerp = Vector.New(ab.y, -ab.x); // Check the handedness of the perpendicular, it should // face TOWARDS the origin diff --git a/src/standard/collision/collider.ts b/src/standard/collision/collider.ts index 9831d47..213818a 100644 --- a/src/standard/collision/collider.ts +++ b/src/standard/collision/collider.ts @@ -15,6 +15,7 @@ limitations under the License. */ import Component from "../../component/component"; +import IEntity from "../../entity/ientity"; import IShape from "../../shape/ishape"; /** @@ -26,12 +27,19 @@ class Collider extends Component { public shape: IShape; public enterScript?: string; public exitScript?: string; + public currentlyCollidingWith: IEntity[]; - constructor(shape: IShape, enterScript?: string, exitScript?: string) { + constructor(shape: IShape, enterScript?: string, exitScript?: string, currentlyCollidingWith: IEntity[] = []) { super(Collider.KEY); this.shape = shape; this.enterScript = enterScript; this.exitScript = exitScript; + this.currentlyCollidingWith = currentlyCollidingWith; + } + + public Free(): void { + this.currentlyCollidingWith = []; + this.shape.Free(); } } diff --git a/src/standard/collision/collision_system.test.ts b/src/standard/collision/collision_system.test.ts index e791648..64b1b47 100644 --- a/src/standard/collision/collision_system.test.ts +++ b/src/standard/collision/collision_system.test.ts @@ -345,39 +345,39 @@ describe("CollisionSystem - Update", () => { new FakeEntity(0), new FakeEntity(1), new CollisionInfo( - Ellipse.Circle(1, new Vector(0, 0)), - Ellipse.Circle(1, new Vector(1, 0)) + Ellipse.Circle(1, 0, 0), + Ellipse.Circle(1, 1, 0) ) ), new Collision( new FakeEntity(3), new FakeEntity(4), new CollisionInfo( - Ellipse.Circle(1, new Vector(3, 0)), - Ellipse.Circle(1, new Vector(4, 0)) + Ellipse.Circle(1, 3, 0), + Ellipse.Circle(1, 4, 0) ) ) ], new Map([ [0, new SystemEntity(new FakeEntity(0), [ new Transform(), - new Collider(Ellipse.Circle(1, new Vector(0, 0))) + new Collider(Ellipse.Circle(1, 0, 0)) ])], [1, new SystemEntity(new FakeEntity(1), [ new Transform(), - new Collider(Ellipse.Circle(1, new Vector(1, 0))) + new Collider(Ellipse.Circle(1, 1, 0)) ])], [2, new SystemEntity(new FakeEntity(2), [ new Transform(), - new Collider(Ellipse.Circle(1, new Vector(2, 0))) + new Collider(Ellipse.Circle(1, 2, 0)) ])], [3, new SystemEntity(new FakeEntity(3), [ new Transform(), - new Collider(Ellipse.Circle(1, new Vector(3, 0))) + new Collider(Ellipse.Circle(1, 3, 0)) ])], [4, new SystemEntity(new FakeEntity(4), [ new Transform(), - new Collider(Ellipse.Circle(1, new Vector(4, 0))) + new Collider(Ellipse.Circle(1, 4, 0)) ])] ]), 0 @@ -400,23 +400,23 @@ describe("CollisionSystem - Update", () => { new Map([ [0, new SystemEntity(new FakeEntity(0), [ new Transform(), - new Collider(Ellipse.Circle(1, new Vector(0, 0))) + new Collider(Ellipse.Circle(1, 0, 0)) ])], [1, new SystemEntity(new FakeEntity(1), [ new Transform(), - new Collider(Ellipse.Circle(1, new Vector(1, 0))) + new Collider(Ellipse.Circle(1, 1, 0)) ])], [2, new SystemEntity(new FakeEntity(2), [ new Transform(), - new Collider(Ellipse.Circle(1, new Vector(2, 0))) + new Collider(Ellipse.Circle(1, 2, 0)) ])], [3, new SystemEntity(new FakeEntity(3), [ new Transform(), - new Collider(Ellipse.Circle(1, new Vector(3, 0))) + new Collider(Ellipse.Circle(1, 3, 0)) ])], [4, new SystemEntity(new FakeEntity(4), [ new Transform(), - new Collider(Ellipse.Circle(1, new Vector(4, 0))) + new Collider(Ellipse.Circle(1, 4, 0)) ])] ]), 0 @@ -437,55 +437,55 @@ describe("CollisionSystem - Update", () => { new FakeEntity(0, undefined, ["test1"]), new FakeEntity(1, undefined, ["test2"]), new CollisionInfo( - Ellipse.Circle(1, new Vector(0, 0)), - Ellipse.Circle(1, new Vector(1, 0)) + Ellipse.Circle(1, 0, 0), + Ellipse.Circle(1, 1, 0) ) ), new Collision( new FakeEntity(0, undefined, ["test1"]), new FakeEntity(3, undefined, ["test2"]), new CollisionInfo( - Ellipse.Circle(1, new Vector(0, 0)), - Ellipse.Circle(1, new Vector(3, 0)) + Ellipse.Circle(1, 0, 0), + Ellipse.Circle(1, 3, 0) ) ), new Collision( new FakeEntity(1, undefined, ["test2"]), new FakeEntity(2, undefined, ["test1"]), new CollisionInfo( - Ellipse.Circle(1, new Vector(1, 0)), - Ellipse.Circle(1, new Vector(2, 0)) + Ellipse.Circle(1, 1, 0), + Ellipse.Circle(1, 2, 0) ) ), new Collision( new FakeEntity(2, undefined, ["test1"]), new FakeEntity(3, undefined, ["test2"]), new CollisionInfo( - Ellipse.Circle(1, new Vector(2, 0)), - Ellipse.Circle(1, new Vector(3, 0)) + Ellipse.Circle(1, 2, 0), + Ellipse.Circle(1, 3, 0) ) ), ], new Map([ [0, new SystemEntity(new FakeEntity(0, undefined, ["test1"]), [ new Transform(), - new Collider(Ellipse.Circle(1, new Vector(0, 0))) + new Collider(Ellipse.Circle(1, 0, 0)) ])], [1, new SystemEntity(new FakeEntity(1, undefined, ["test2"]), [ new Transform(), - new Collider(Ellipse.Circle(1, new Vector(1, 0))) + new Collider(Ellipse.Circle(1, 1, 0)) ])], [2, new SystemEntity(new FakeEntity(2, undefined, ["test1"]), [ new Transform(), - new Collider(Ellipse.Circle(1, new Vector(2, 0))) + new Collider(Ellipse.Circle(1, 2, 0)) ])], [3, new SystemEntity(new FakeEntity(3, undefined, ["test2"]), [ new Transform(), - new Collider(Ellipse.Circle(1, new Vector(3, 0))) + new Collider(Ellipse.Circle(1, 3, 0)) ])], [4, new SystemEntity(new FakeEntity(4, undefined, []), [ new Transform(), - new Collider(Ellipse.Circle(1, new Vector(4, 0))) + new Collider(Ellipse.Circle(1, 4, 0)) ])] ]), ), @@ -499,23 +499,23 @@ describe("CollisionSystem - Update", () => { new Map([ [0, new SystemEntity(new FakeEntity(0, undefined, ["test1"]), [ new Transform(), - new Collider(Ellipse.Circle(1, new Vector(0, 0))) + new Collider(Ellipse.Circle(1, 0, 0)) ])], [1, new SystemEntity(new FakeEntity(1, undefined, ["test2"]), [ new Transform(), - new Collider(Ellipse.Circle(1, new Vector(1, 0))) + new Collider(Ellipse.Circle(1, 1, 0)) ])], [2, new SystemEntity(new FakeEntity(2, undefined, ["test1"]), [ new Transform(), - new Collider(Ellipse.Circle(1, new Vector(2, 0))) + new Collider(Ellipse.Circle(1, 2, 0)) ])], [3, new SystemEntity(new FakeEntity(3, undefined, ["test2"]), [ new Transform(), - new Collider(Ellipse.Circle(1, new Vector(3, 0))) + new Collider(Ellipse.Circle(1, 3, 0)) ])], [4, new SystemEntity(new FakeEntity(4, undefined, []), [ new Transform(), - new Collider(Ellipse.Circle(1, new Vector(4, 0))) + new Collider(Ellipse.Circle(1, 4, 0)) ])] ]), 0 @@ -536,19 +536,19 @@ describe("CollisionSystem - Update", () => { new FakeEntity(0, undefined, undefined), new FakeEntity(1, undefined, undefined), new CollisionInfo( - Ellipse.Circle(1, new Vector(0, 0)), - Ellipse.Circle(1, new Vector(1, 0)) + Ellipse.Circle(1, 0, 0), + Ellipse.Circle(1, 1, 0) ) ), ], new Map([ [0, new SystemEntity(new FakeEntity(0, undefined, undefined), [ new Transform(), - new Collider(Ellipse.Circle(1, new Vector(0, 0))) + new Collider(Ellipse.Circle(1, 0, 0)) ])], [1, new SystemEntity(new FakeEntity(1, undefined, undefined), [ new Transform(), - new Collider(Ellipse.Circle(1, new Vector(1, 0))) + new Collider(Ellipse.Circle(1, 1, 0)) ])], ]), 0 @@ -564,19 +564,19 @@ describe("CollisionSystem - Update", () => { new FakeEntity(0, undefined, undefined), new FakeEntity(1, undefined, undefined), new CollisionInfo( - Ellipse.Circle(1, new Vector(0, 0)), - Ellipse.Circle(1, new Vector(1, 0)) + Ellipse.Circle(1, 0, 0), + Ellipse.Circle(1, 1, 0) ) ), ], new Map([ [0, new SystemEntity(new FakeEntity(0, undefined, undefined), [ new Transform(), - new Collider(Ellipse.Circle(1, new Vector(0, 0))) + new Collider(Ellipse.Circle(1, 0, 0)) ])], [1, new SystemEntity(new FakeEntity(1, undefined, undefined), [ new Transform(), - new Collider(Ellipse.Circle(1, new Vector(1, 0))) + new Collider(Ellipse.Circle(1, 1, 0)) ])], ]), 0 @@ -596,11 +596,11 @@ describe("CollisionSystem - Update", () => { new Map([ [0, new SystemEntity(new FakeEntity(0, undefined, undefined), [ new Transform(), - new Collider(Ellipse.Circle(1, new Vector(0, 0))) + new Collider(Ellipse.Circle(1, 0, 0)) ])], [1, new SystemEntity(new FakeEntity(1, undefined, undefined), [ new Transform(), - new Collider(Ellipse.Circle(1, new Vector(1, 0))) + new Collider(Ellipse.Circle(1, 1, 0)) ])], ]), 0 @@ -616,19 +616,19 @@ describe("CollisionSystem - Update", () => { new FakeEntity(0, undefined, undefined), new FakeEntity(1, undefined, undefined), new CollisionInfo( - Ellipse.Circle(1, new Vector(0, 0)), - Ellipse.Circle(1, new Vector(1, 0)) + Ellipse.Circle(1, 0, 0), + Ellipse.Circle(1, 1, 0) ) ), ], new Map([ [0, new SystemEntity(new FakeEntity(0, undefined, undefined), [ new Transform(), - new Collider(Ellipse.Circle(1, new Vector(0, 0))) + new Collider(Ellipse.Circle(1, 0, 0)) ])], [1, new SystemEntity(new FakeEntity(1, undefined, undefined), [ new Transform(), - new Collider(Ellipse.Circle(1, new Vector(1, 0))) + new Collider(Ellipse.Circle(1, 1, 0)) ])], ]), 0 @@ -651,23 +651,23 @@ describe("CollisionSystem - Update", () => { new FakeEntity(0, undefined, undefined), new FakeEntity(1, undefined, undefined), new CollisionInfo( - Ellipse.Circle(1, new Vector(0, 0)), - Ellipse.Circle(1, new Vector(1, 0)) + Ellipse.Circle(1, 0, 0), + Ellipse.Circle(1, 1, 0) ) ), ], new Map([ [0, new SystemEntity(new FakeEntity(0, undefined, undefined), [ new Transform(), - new Collider(Ellipse.Circle(1, new Vector(0, 0))) + new Collider(Ellipse.Circle(1, 0, 0)) ])], [1, new SystemEntity(new FakeEntity(1, undefined, undefined), [ new Transform(), - new Collider(Ellipse.Circle(1, new Vector(1, 0)), undefined, "exit-script") + new Collider(Ellipse.Circle(1, 1, 0), undefined, "exit-script") ])], [2, new SystemEntity(new FakeEntity(2, undefined, undefined), [ new Transform(), - new Collider(Ellipse.Circle(1, new Vector(2, 0)), undefined, "exit-script") + new Collider(Ellipse.Circle(1, 2, 0), undefined, "exit-script") ])], ]), 0 @@ -685,31 +685,31 @@ describe("CollisionSystem - Update", () => { new FakeEntity(0, undefined, undefined), new FakeEntity(1, undefined, undefined), new CollisionInfo( - Ellipse.Circle(1, new Vector(0, 0)), - Ellipse.Circle(1, new Vector(1, 0)) + Ellipse.Circle(1, 0, 0), + Ellipse.Circle(1, 1, 0) ) ), new Collision( new FakeEntity(1, undefined, undefined), new FakeEntity(2, undefined, undefined), new CollisionInfo( - Ellipse.Circle(1, new Vector(1, 0)), - Ellipse.Circle(1, new Vector(2, 0)) + Ellipse.Circle(1, 1, 0), + Ellipse.Circle(1, 2, 0) ) ), ], new Map([ [0, new SystemEntity(new FakeEntity(0, undefined, undefined), [ new Transform(), - new Collider(Ellipse.Circle(1, new Vector(0, 0))) + new Collider(Ellipse.Circle(1, 0, 0)) ])], [1, new SystemEntity(new FakeEntity(1, undefined, undefined), [ new Transform(), - new Collider(Ellipse.Circle(1, new Vector(1, 0)), undefined, "exit-script") + new Collider(Ellipse.Circle(1, 1, 0), undefined, "exit-script") ])], [2, new SystemEntity(new FakeEntity(2, undefined, undefined), [ new Transform(), - new Collider(Ellipse.Circle(1, new Vector(2, 0)), undefined, "exit-script") + new Collider(Ellipse.Circle(1, 2, 0), undefined, "exit-script") ])], ]), 0 @@ -740,8 +740,8 @@ describe("CollisionSystem - Update", () => { new FakeEntity(0, undefined, undefined), new FakeEntity(1, undefined, undefined), new CollisionInfo( - Ellipse.Circle(1, new Vector(0, 0)), - Ellipse.Circle(1, new Vector(1, 0)) + Ellipse.Circle(1, 0, 0), + Ellipse.Circle(1, 1, 0) ) ), ], diff --git a/src/standard/interpolation/interpolation_system.ts b/src/standard/interpolation/interpolation_system.ts index 49d66b9..c45d57f 100644 --- a/src/standard/interpolation/interpolation_system.ts +++ b/src/standard/interpolation/interpolation_system.ts @@ -39,9 +39,9 @@ class InterpolationSystem extends System { ); }; - constructor(messageBus: IMessageBus, - scene?: IScene, - entities?: Map, + constructor(messageBus: IMessageBus, + scene?: IScene, + entities?: Map, subscriberID?: number) { super(messageBus, scene, InterpolationSystem.EVALUATOR, entities, subscriberID); this.messageBus.Subscribe(this, Game.MESSAGE_POST_RENDER); @@ -61,14 +61,14 @@ class InterpolationSystem extends System { * interpolateTransforms updates the `previous` member to be the current position of the transform. * This is used in rendering, allowing render systems to use the previous and current position to * interpolate its position when drawing. - * @param {SystemEntity[]} entities The entities to update the interpolation positions of + * @param {SystemEntity[]} entities The entities to update the interpolation positions of */ private interpolateTransforms(): void { for (const entity of this.entities.values()) { const transform = entity.Get(Transform.KEY) as Transform; - transform.previous = transform.position.Copy(); + transform.previous = transform.previous.Recycle(transform.position.x, transform.position.y); } } } -export default InterpolationSystem; \ No newline at end of file +export default InterpolationSystem; diff --git a/src/standard/motion/motion.ts b/src/standard/motion/motion.ts index c996b37..8fec671 100644 --- a/src/standard/motion/motion.ts +++ b/src/standard/motion/motion.ts @@ -43,13 +43,21 @@ class Motion extends Component { */ public angularAcceleration: number; - constructor(velocity = new Vector(0,0), acceleration = new Vector(0,0), angularVelocity = 0, angularAcceleration = 0) { + constructor(velocity = Vector.New(0, 0), + acceleration = Vector.New(0, 0), + angularVelocity = 0, + angularAcceleration = 0) { super(Motion.KEY); this.velocity = velocity; this.acceleration = acceleration; this.angularVelocity = angularVelocity; this.angularAcceleration = angularAcceleration; } + + public Free(): void { + this.velocity.Free(); + this.acceleration.Free(); + } } -export default Motion; \ No newline at end of file +export default Motion; diff --git a/src/standard/motion/motion_system.ts b/src/standard/motion/motion_system.ts index 93a540b..aef342c 100644 --- a/src/standard/motion/motion_system.ts +++ b/src/standard/motion/motion_system.ts @@ -50,9 +50,12 @@ class MotionSystem extends System { const motion = entity.Get(Motion.KEY) as Motion; // v += a * dt - motion.velocity.Add(motion.acceleration.Copy().Scale(dt)); + motion.velocity.x += motion.acceleration.x * dt; + motion.velocity.y += motion.acceleration.y * dt; + // p += v * dt - transform.position.Add(motion.velocity.Copy().Scale(dt)); + transform.position.x += motion.velocity.x * dt; + transform.position.y += motion.velocity.y * dt; // v += a * dt motion.angularVelocity += motion.angularAcceleration * dt; diff --git a/src/standard/pointer/pointer_system.ts b/src/standard/pointer/pointer_system.ts index 25d5050..8585404 100644 --- a/src/standard/pointer/pointer_system.ts +++ b/src/standard/pointer/pointer_system.ts @@ -110,7 +110,7 @@ class PointerSystem extends System { * When a Wheel Event occurs; used to store the last wheel event to be * dispatched at the next update. This is to throttle this event which can * be fired many times. - * @param {WheelEvent} event Fired wheel event + * @param {WheelEvent} event Fired wheel event */ protected wheelEvent(event: WheelEvent): void { this.lastWheelEvent = event; @@ -137,14 +137,14 @@ class PointerSystem extends System { // lockedPointerPosition is undefined when not fullscreen, when fullscreen it is used to // keep track of the last pointer position if (this.lockedPointerPosition !== undefined) { - this.lockedPointerPosition = this.lockedPointerPosition.Add(new Vector(event.movementX, event.movementY)); + this.lockedPointerPosition = this.lockedPointerPosition.Add(Vector.New(event.movementX, event.movementY)); pointerX = this.lockedPointerPosition.x; pointerY = this.lockedPointerPosition.y; } else { - this.lockedPointerPosition = new Vector(event.clientX, event.clientY); + this.lockedPointerPosition = Vector.New(event.clientX, event.clientY); } } - const elementPosition = new Vector( + const elementPosition = Vector.New( ((pointerX - rect.left) - (rect.width / 2)) / (rect.width / 2), -((pointerY - rect.top) - (rect.height / 2)) / (rect.height / 2) ); @@ -168,13 +168,13 @@ class PointerSystem extends System { elementPosition.y > viewportPosition.y - viewportScale.y; // Position relative to camera viewport - const cameraPosition = new Vector( + const cameraPosition = Vector.New( (elementPosition.x - viewportPosition.x) / viewportScale.x, (elementPosition.y - viewportPosition.y) / viewportScale.y ); // Position in the world according to the camera - const worldPosition = new Vector( + const worldPosition = Vector.New( cameraWorldPosition.x + virtualScale.x * (cameraPosition.x / 2), cameraWorldPosition.y + virtualScale.y * (cameraPosition.y / 2) ); @@ -190,4 +190,4 @@ class PointerSystem extends System { } } -export default PointerSystem; \ No newline at end of file +export default PointerSystem; diff --git a/src/standard/primitive/primitive.ts b/src/standard/primitive/primitive.ts index 3dc0437..c8d59e9 100644 --- a/src/standard/primitive/primitive.ts +++ b/src/standard/primitive/primitive.ts @@ -62,6 +62,10 @@ class Primitive extends Component { this.points = points; this.drawMode = drawMode; } + + public Free(): void { + this.points.Free(); + } } -export default Primitive; \ No newline at end of file +export default Primitive; diff --git a/src/standard/primitive/primitive_system.ts b/src/standard/primitive/primitive_system.ts index 5529c87..c2f548e 100644 --- a/src/standard/primitive/primitive_system.ts +++ b/src/standard/primitive/primitive_system.ts @@ -82,9 +82,9 @@ class PrimitiveSystem extends System { if (ui === undefined) { // Not UI - renderables.push(new Renderable( + renderables.push(Renderable.New( primitive.zOrder, - primitive.points, + primitive.points.Copy(), transform.InterpolatedMatrix4D(alpha), primitive.material, primitive.drawMode, @@ -117,9 +117,9 @@ class PrimitiveSystem extends System { ); // Create the renderable for use by rendering systems - renderables.push(new Renderable( + renderables.push(Renderable.New( primitive.zOrder, - primitive.points, + primitive.points.Copy(), relativeTransform.InterpolatedMatrix4D(alpha), primitive.material, primitive.drawMode, diff --git a/src/standard/sprite/sprite.ts b/src/standard/sprite/sprite.ts index b1710b4..4d8db2a 100644 --- a/src/standard/sprite/sprite.ts +++ b/src/standard/sprite/sprite.ts @@ -30,7 +30,7 @@ class Sprite extends Component { */ public static readonly KEY = "sprite"; /** - * Order which the sprite should appear, if it should appear infront/behind other + * Order which the sprite should appear, if it should appear infront/behind other * objects, the higher the value the more precedence it is given and will * appear in front of objects with a lower value. */ @@ -45,6 +45,10 @@ class Sprite extends Component { this.material = material; this.zOrder = zOrder; } + + public Free(): void { + this.material.Free(); + } } -export default Sprite; \ No newline at end of file +export default Sprite; diff --git a/src/standard/sprite/sprite_system.ts b/src/standard/sprite/sprite_system.ts index ac23297..2be54c6 100644 --- a/src/standard/sprite/sprite_system.ts +++ b/src/standard/sprite/sprite_system.ts @@ -85,11 +85,11 @@ class SpriteSystem extends System { if (ui === undefined) { // Not UI - renderables.push(new Renderable( + renderables.push(Renderable.New( sprite.zOrder, - Polygon.QuadByDimensions(1,1), + Polygon.QuadByDimensions(1, 1, 0, 0), transform.InterpolatedMatrix4D(alpha), - sprite.material, + sprite.material.Copy(), DrawMode.TRIANGLES, undefined, )); @@ -120,12 +120,16 @@ class SpriteSystem extends System { transform.angle ); + const matrix = relativeTransform.InterpolatedMatrix4D(alpha); + + relativeTransform.Free(); + // Create the renderable for use by rendering systems - renderables.push(new Renderable( + renderables.push(Renderable.New( sprite.zOrder, - Polygon.QuadByDimensions(1,1), - relativeTransform.InterpolatedMatrix4D(alpha), - sprite.material, + Polygon.QuadByDimensions(1, 1, 0, 0), + matrix, + sprite.material.Copy(), DrawMode.TRIANGLES, ui.camera, )); diff --git a/src/standard/text/text.ts b/src/standard/text/text.ts index ff4a599..c527faa 100644 --- a/src/standard/text/text.ts +++ b/src/standard/text/text.ts @@ -34,7 +34,7 @@ class Text extends Component { */ private static readonly DEFAULT_SPACING = 0.3; /** - * Order which the text should appear, if it should appear infront/behind other + * Order which the text should appear, if it should appear infront/behind other * objects, the higher the value the more precedence it is given and will * appear in front of objects with a lower value. */ @@ -76,7 +76,7 @@ class Text extends Component { font: string, align: TextAlignment = TextAlignment.Left, spacing: number = Text.DEFAULT_SPACING, - offset: Vector = new Vector(0,0), + offset: Vector = Vector.New(0,0), color: Color = new Color(0, 0, 0, 1), shaders: string[] = [ ShaderAsset.DEFAULT_TEXTURE_VERTEX_SHADER_NAME, @@ -92,6 +92,10 @@ class Text extends Component { this.color = color; this.shaders = shaders; } + + public Free(): void { + this.offset.Free(); + } } -export default Text; \ No newline at end of file +export default Text; diff --git a/src/standard/text/text_system.ts b/src/standard/text/text_system.ts index eaff5af..267bc64 100644 --- a/src/standard/text/text_system.ts +++ b/src/standard/text/text_system.ts @@ -42,6 +42,7 @@ import UI from "../ui/ui"; import FontRequest from "../../rendering/font/font_request"; import FontAsset from "../../rendering/font/font_asset"; import DrawMode from "../../rendering/draw_mode"; +import Matrix4D from "../../geometry/matrix_4d"; /** * TextSystem is a pre-rendering system, taking in text components and @@ -242,14 +243,14 @@ class TextSystem extends System { // Get the character's mapping value, if it doesn't // exist skip rendering this character const position = mapping.characters.get(char); - if(position === undefined) { + if (position === undefined) { continue; } // Determine the x alignment of the text, based // on the text alignment options provided let xAlign; - switch(text.align) { + switch (text.align) { case TextAlignment.Left: { // +0.5 to shift first character from being centered on // transform position to the right of it @@ -257,22 +258,22 @@ class TextSystem extends System { break; } case TextAlignment.Center: { - xAlign = i - (text.value.length-1)/2; + xAlign = i - (text.value.length - 1) / 2; break; } case TextAlignment.Right: { // Inverse of left align, with transform position set to // right of the last character - xAlign = i - (text.value.length-1) - 0.5; + xAlign = i - (text.value.length - 1) - 0.5; break; } default: { - throw(`Invalid text alignment: ${text.align}`); + throw (`Invalid text alignment: ${text.align}`); } } // Transform for character - let charTransform: Transform; + const interpolatedMatrix = new Matrix4D(); if (ui === undefined) { // Not part of the UI /** @@ -281,14 +282,10 @@ class TextSystem extends System { * (xAlign * text.spacing * transform.scale.x/2) -> * determines spacing between characters */ - charTransform = new Transform( - transform.position.Copy() - .Add(text.offset) - .Add(new Vector((xAlign * transform.scale.x/2) + (xAlign * text.spacing * transform.scale.x/2), 0)), - transform.scale.Copy(), - transform.angle - ); - + const translation = transform.position.Copy(); + translation.x += text.offset.x + (xAlign * transform.scale.x / 2) + (xAlign * text.spacing * transform.scale.x / 2); + translation.y += text.offset.y; + interpolatedMatrix.Translate(translation).Scale(transform.scale).Rotate(transform.angle); } else { // Part of the UI // Get the camera entity this is assigned to, if no camera @@ -320,12 +317,9 @@ class TextSystem extends System { * (xAlign * text.spacing * charScale.x/2) -> * determines spacing between characters */ - charTransform = new Transform( - textPosition.Copy() - .Add(new Vector((xAlign * charScale.x / 2) + (xAlign * text.spacing * charScale.x/2), 0)), - charScale.Copy(), - transform.angle - ); + const translation = textPosition.Copy(); + translation.x += (xAlign * charScale.x / 2) + (xAlign * text.spacing * charScale.x / 2); + interpolatedMatrix.Translate(translation).Scale(charScale).Rotate(transform.angle); } // Determine the position in the glyph to extract the text glyph @@ -339,17 +333,17 @@ class TextSystem extends System { const charSize = 1 / mapping.width; // Create renderable for the character, include extra TextRender // information for shaders to use - renderables.push(new Renderable( + renderables.push(Renderable.New( text.zOrder, - Polygon.QuadByDimensions(1,1), - charTransform.InterpolatedMatrix4D(alpha), + Polygon.QuadByDimensions(1, 1, 0, 0), + interpolatedMatrix, new Material( { texture: new Texture( `font_${text.font}`, Polygon.QuadByPoints( - new Vector(x * charSize, y * charSize), - new Vector(x * charSize + charSize, y * charSize + charSize) + Vector.New(x * charSize, y * charSize), + Vector.New(x * charSize + charSize, y * charSize + charSize) ) ), shaders: text.shaders, diff --git a/src/standard/transform/transform.ts b/src/standard/transform/transform.ts index 026b376..ed0e999 100644 --- a/src/standard/transform/transform.ts +++ b/src/standard/transform/transform.ts @@ -46,7 +46,7 @@ class Transform extends Component { */ public angle: number; - constructor(position = new Vector(0,0), scale = new Vector(1,1), angle = 0) { + constructor(position = Vector.New(0, 0), scale = Vector.New(1, 1), angle = 0) { super(Transform.KEY); this.previous = position; this.position = position; @@ -87,13 +87,17 @@ class Transform extends Component { * @returns {Matrix4D} Matrix of the interpolated transform. */ public InterpolatedMatrix4D(alpha: number): Matrix4D { - return new Matrix4D() - .Translate(new Vector( + return new Matrix4D().Translate( + Vector.New( this.previous.x * alpha + this.position.x * (1 - alpha), this.previous.y * alpha + this.position.y * (1 - alpha) - )) - .Rotate(this.angle) - .Scale(this.scale); + )).Rotate(this.angle).Scale(this.scale); + } + + public Free(): void { + this.position.Free(); + this.previous.Free(); + this.scale.Free(); } } diff --git a/src/standard/webgl/webgl_system.test.ts b/src/standard/webgl/webgl_system.test.ts index c16df79..6ea2d91 100644 --- a/src/standard/webgl/webgl_system.test.ts +++ b/src/standard/webgl/webgl_system.test.ts @@ -1780,7 +1780,7 @@ describe("WebGLSystem - Render", () => { undefined, [ new Renderable(0, - Polygon.RectangleByDimensions(1, 1, new Vector(0, 0)), + Polygon.RectangleByDimensions(1, 1, 0, 0), new Matrix4D(), new Material({ texture: new Texture("test", Polygon.RectangleByDimensions(1, 1)), @@ -1789,7 +1789,7 @@ describe("WebGLSystem - Render", () => { DrawMode.TRIANGLE_STRIP ), new Renderable(0, - Polygon.RectangleByDimensions(1, 1, new Vector(1, 0)), + Polygon.RectangleByDimensions(1, 1, 1, 0), new Matrix4D(), new Material({ texture: new Texture("test", Polygon.RectangleByDimensions(1, 1)), @@ -1798,7 +1798,7 @@ describe("WebGLSystem - Render", () => { DrawMode.TRIANGLE_STRIP ), new Renderable(0, - Polygon.RectangleByDimensions(1, 1, new Vector(2, 0)), + Polygon.RectangleByDimensions(1, 1, 2, 0), new Matrix4D(), new Material({ texture: new Texture("test", Polygon.RectangleByDimensions(1, 1)), @@ -1807,7 +1807,7 @@ describe("WebGLSystem - Render", () => { DrawMode.TRIANGLE_STRIP ), new Renderable(0, - Polygon.RectangleByDimensions(1, 1, new Vector(3, 0)), + Polygon.RectangleByDimensions(1, 1, 3, 0), new Matrix4D(), new Material({ texture: new Texture("test", Polygon.RectangleByDimensions(1, 1)), @@ -1816,7 +1816,7 @@ describe("WebGLSystem - Render", () => { DrawMode.TRIANGLE_STRIP ), new Renderable(0, - Polygon.RectangleByDimensions(1, 1, new Vector(4, 0)), + Polygon.RectangleByDimensions(1, 1, 4, 0), new Matrix4D(), new Material({ texture: new Texture("test", Polygon.RectangleByDimensions(1, 1)), @@ -1924,7 +1924,7 @@ describe("WebGLSystem - Render", () => { undefined, [ new Renderable(0, - Polygon.RectangleByDimensions(1, 1, new Vector(0, 0)), + Polygon.RectangleByDimensions(1, 1, 0, 0), new Matrix4D(), new Material({ texture: new Texture("test", Polygon.RectangleByDimensions(1, 1)), @@ -1935,7 +1935,7 @@ describe("WebGLSystem - Render", () => { new FakeEntity(0) ), new Renderable(0, - Polygon.RectangleByDimensions(1, 1, new Vector(1, 0)), + Polygon.RectangleByDimensions(1, 1, 1, 0), new Matrix4D(), new Material({ texture: new Texture("test", Polygon.RectangleByDimensions(1, 1)), @@ -1946,7 +1946,7 @@ describe("WebGLSystem - Render", () => { new FakeEntity(0) ), new Renderable(0, - Polygon.RectangleByDimensions(1, 1, new Vector(2, 0)), + Polygon.RectangleByDimensions(1, 1, 2, 0), new Matrix4D(), new Material({ shaders: ["test_vert", "test_frag"] @@ -2130,7 +2130,7 @@ describe("WebGLSystem - Render", () => { undefined, [ new Renderable(0, - Polygon.RectangleByDimensions(1, 1, new Vector(0, 0)), + Polygon.RectangleByDimensions(1, 1, 0, 0), new Matrix4D(), new Material({ texture: new Texture("test", Polygon.RectangleByDimensions(1, 1)), @@ -2139,7 +2139,7 @@ describe("WebGLSystem - Render", () => { DrawMode.TRIANGLE_STRIP ), new Renderable(1, - Polygon.RectangleByDimensions(1, 1, new Vector(1, 0)), + Polygon.RectangleByDimensions(1, 1, 1, 0), new Matrix4D(), new Material({ texture: new Texture("test", Polygon.RectangleByDimensions(1, 1)), @@ -2148,7 +2148,7 @@ describe("WebGLSystem - Render", () => { DrawMode.TRIANGLE_STRIP ), new Renderable(0, - Polygon.RectangleByDimensions(1, 1, new Vector(2, 0)), + Polygon.RectangleByDimensions(1, 1, 2, 0), new Matrix4D(), new Material({ texture: new Texture("test", Polygon.RectangleByDimensions(1, 1)), @@ -2157,7 +2157,7 @@ describe("WebGLSystem - Render", () => { DrawMode.TRIANGLE_STRIP ), new Renderable(3, - Polygon.RectangleByDimensions(1, 1, new Vector(3, 0)), + Polygon.RectangleByDimensions(1, 1, 3, 0), new Matrix4D(), new Material({ texture: new Texture("test", Polygon.RectangleByDimensions(1, 1)), @@ -2166,7 +2166,7 @@ describe("WebGLSystem - Render", () => { DrawMode.TRIANGLE_STRIP ), new Renderable(5, - Polygon.RectangleByDimensions(1, 1, new Vector(4, 0)), + Polygon.RectangleByDimensions(1, 1, 4, 0), new Matrix4D(), new Material({ texture: new Texture("test", Polygon.RectangleByDimensions(1, 1)), diff --git a/src/standard/webgl/webgl_system.ts b/src/standard/webgl/webgl_system.ts index 9272918..5c500f8 100644 --- a/src/standard/webgl/webgl_system.ts +++ b/src/standard/webgl/webgl_system.ts @@ -276,7 +276,7 @@ class WebGLSystem extends RenderSystem { // the canvas converted from the -1 to +1 coordinates of the viewportPosition // combined with the real width and height to make sure it is in the center // of the viewport. - const realPosition = new Vector( + const realPosition = Vector.New( (canvasWidth / 2 + (camera.viewportPosition.x / 2) * canvasWidth) - realWidth / 2, (canvasHeight / 2 + (camera.viewportPosition.y / 2) * canvasHeight) - realHeight / 2 ); @@ -459,6 +459,7 @@ class WebGLSystem extends RenderSystem { shader[1].perRenderable(glslContext, renderable, texture); } } + gl.drawArrays(drawMode, 0, renderable.vertices.GetFloat32Array().length / 2); } } @@ -466,6 +467,11 @@ class WebGLSystem extends RenderSystem { } } + + for (const renderable of this.renderables) { + renderable.Free(); + } + this.renderables = []; } }