Releases: JaimeGensler/thyseus
v0.4.0
💥 Breaking Changes
- Some elements of
WorldBuilder
andWorld
have changed.components
on World is now aComponentType[]
.queries
on World is now aQuery[]
.queries
andregisterQuery
on WorldBuilder have been removed. At
the moment, queries do not need to be pre-registered.
- Some properties previously available on
WorldCommands
no longer exist. All
methods remain the same.- It is strongly suggested that you do not access properties on
WorldCommands
and only use its methods, as the internal implementation
ofWorldCommands
is likely to continue to change (pre-1.0).
- It is strongly suggested that you do not access properties on
- Some properties previously available on
Query
no longer exist. Iteration
remains the same.- Namely, queries no longer need to track which entities match them, and
so have noentities
field. - As above, it is strongly suggested that you simply iterate over query
instances, as the rest of the properties are subject to change.
- Namely, queries no longer need to track which entities match them, and
threads
onWorld
have been changed fromThread[]
toThreadGroup
.- Some of the functionality of ThreadGroup will be expanded to be
consumer-facing in a future update.
- Some of the functionality of ThreadGroup will be expanded to be
✨ Features
- Now uses archetypes for component storage! As a result, memory usage is significantly
reduced, and iterating queries should be more cache-friendly.- Archetypes can be accessed with
world.archetypes
(Map<bigint, Table>
). - Bear in mind that archetypes may be somewhat slower at the moment than
the previous storage mechanism - especially adding/removing Components -
but have the potential to be much faster - more benchmarking around the
bottlenecks of the current implementation is needed, first.
- Archetypes can be accessed with
- Entities are now a generational index.
- Every entity has an associated generation. Whenever an entity is
despawned, the generation is incremented. - Entity data can be queried for with the
Entity
component (all living
entities have this component, so it does not affect what a query
matches). - The
Entity
component has no setters and includes a few methods that
can be used to queue components for add/remove, or to despawn the
entity -WorldCommands.spawn()
returns an Entity component! These
methods do not require a lock, and so Entity can (and should) be
accessed immutably.class Entity { get id(): bigint; // uint64 get generation(): number; // uint32 get entityIndex(): number; // uint32 insert(Component: ComponentType<any>): this; remove(Component: ComponentType): this; despawn(): void; }
- Every entity has an associated generation. Whenever an entity is
- Added
getNewTableSize: (prev: number) => number
to world config, which
determines how archetype tables grow. By default, 8 entities are allocated
for a table, and table size is doubled each grow.- When creating a new table, the
prev
argument will be 0. - Returned values must be multiples of 8 - failure to do so will
result in undefined behavior!
- When creating a new table, the
- Systems can now return promises, which will be
await
ed before continuing
system execution on that thread. This should not be utilized often, as any
data a system locks will remain locked until the promise is resolved!
🔧 Maintenance
- Bumped dev dependency versions.
v0.3.0
💥 Breaking Changes
- Single-threaded worlds use the same executor as multithreaded worlds. As a
result, they will respect passed dependencies. Unfortunately, they now
requireAtomics.waitAsync
- this will be resolved in a future release. - Components can no longer be used as Resources (i.e.,
class MyResource extends Component({ ... }) {}
no longer works as
expected). TheResource()
function is now provided to serve this purpose.
Resource()
is nearly identical toComponent()
, but requires a schema. EntityManager
has been replaced withWorldCommands
- see below for
details. Systems usingP.Entities()
must be updated toP.Commands()
and
the new Commands API.- The
Thread
class is no longer exposed (not intended for consumer use). To
access theSend
andReceive
symbols, you can instead
import { ThreadProtocol } from 'thyseus'
.
✨ Features
- New
Commands
API!- Commands can be accessed on any thread, and is disjoint with all other
parameters - including itself. - Commands functionally queues modifications to entities - updates to
queries occurs in a system that intersects with all other systems. If
you need systems to run after entity modification, you can
import { applyCommands } from 'thyseus'
and specify it as a
dependency. Be aware that for multithreaded worlds, doing so will create
a hard sync point. Last, WorldBuilders always add theapplyCommands
system - you don't need to add it yourself.
- Commands can be accessed on any thread, and is disjoint with all other
- New minimal plugin API!
- Plugins are just functions that accept a WorldBuilder instance. They can
add systems to the world just as normal, and are useful for creating
shared groups of systems that may depend on eachother. - Add plugins to a world with the
addPlugin
method. - The
definePlugin
function is provided for type help.
- Plugins are just functions that accept a WorldBuilder instance. They can
- New
World
system parameter.- This provides direct access to the World and all of its data. It
intersects with all other system parameters (creating a sync point for
multithreaded worlds). Systems that accessWorld
always run on the
main thread, and can therefore access main-thread-only resources if
needed.
- This provides direct access to the World and all of its data. It
- New methods for
WorldBuilder
, and some previously internal properties on
World
andWorldBuilder
are exposed thru getters! Most of these are
designed for internal use, but could be useful for advanced use cases.- As a result of this and other refactors, user-defined system parameter
descriptors are now possible. This is a more advanced pattern, and more
documentation around this will be available in the future.
- As a result of this and other refactors, user-defined system parameter
🔧 Maintenance
- Improved annotations on classes/methods.
- Cleaned up type names - Component Classes are now
ComponentType
, Resource
Classes are nowResourceType
. - Major behind-the-scenes refactoring.
- Improved test coverage.
- Bumped dev dependency versions.
v0.2.0
💥 Breaking Changes
defineSystem()
now returns an object rather adding aparameters
property
to the provided function. As a result, the same function can be re-used for
multiple systems.
✨ Features
-
You can now specify dependencies between systems to force a specific
execution order! TheaddSystem()
method now accepts an optional second
argument, which can be used to specify if a system must execute before/after
other systems.const updateTime = defineSystem(/* ... */); const handleInput = defineSystem(/* ... */); const mover = defineSystem(/* ... */); const draw = defineSystem(/* ... */); const myWorld = World.new() // Mover will not run until after handleInput has finished. .addSystem(mover, { after: [handleInput] }) // A matching before/after pair is permitted but not required. .addSystem(handleInput) // draw will not run until all intersecting systems have finished. .addSystem(draw, { afterAll: true }) // updateTime must run before any intersecting systems may run. .addSystem(updateTime, { beforeAll: true }); // Dependencies look like this: interface Dependencies { before?: SystemDefinition[]; after?: SystemDefinition[]; beforeAll?: boolean; afterAll?: boolean; }
A few notes on dependencies:
- Both explicit (
before
,after
) and implicit (beforeAll
,afterAll
)
dependencies apply only to systems that intersect. For example, if
systemsA
andB
are disjoint, thenA before B
will have no effect.
Similarly,A beforeAll
will not guarantee thatA
runs before
B
(but will guarantee thatA
runs before any other systems thatA
intersects with). - Explicit dependencies take precedence over implicit dependencies. Given
intersectingA
andB
,(B beforeAll), (A before B)
will guarantee
that A runs before B. - If no explicit resolution is provided, implicit dependencies are
evaluated in the order they are passed in.- Given
(A beforeAll), (B beforeAll)
,A
will be guaranteed to
execute beforeB
. - Given
(A afterAll), (B afterAll)
,A
will be guaranteed to
execute afterB
.
- Given
- Directly contradictory dependencies - such as
A after A
or
(A before B), (B before A)
- will cause an error to be thrown when the
world is built. - Dependencies apply only to multithreaded worlds for the moment. In the
future, single threaded worlds will also use dependencies.
- Both explicit (
-
Resources can now also be made thread-friendly by implementing Thyseus's
internal Thread Send/Receive protocol (i.e., classes that implement
static [Thread.Receive]() {}
and[Thread.Send]() {}
methods).- Constructors for classes implementing
Send
/Receive
will be called
with the world config. - This is the recommended method of defining resources.
- Constructors for classes implementing
-
Resources that are bound to the main thread for initialization, but are
readable and/or writeable from multiple threads (for example, aKeyboard
resource that needs to register listeners forkeydown
/keyup
events) can
implement a staticcreate()
method that will be used rather than its
constructor. This method receives the same arguments its constructor would
be called with (WorldConfig
for resources implementing Thread protocol, a
store & index for Component-based resources), and must return an instance of
the class.
🔧 Maintenance
- Improved test coverage
- Bump Vite/Vitest version
- Use vite library mode to build