Releases: rlipsc/polymorph
v0.3.1
Overview
This release focuses on features for organisation and ease of use.
Perhaps the biggest change is that system types and initialisation now defaults to being output by makeEcs
, rather than immediately in the module they're defined.
As a result, ECS code output is localised to a single module.
This leads to several improvements for larger projects where components, systems, and sealing could be in separate modules:
- A single import to use the sealed ECS (the module with
makeEcs
). - Easier scope management for supporting imports.
- No need to forward system types from where they're defined.
In particular, systems which use fields:
blocks to initialise values are now in the same scope as the ECS output, which is much more intuitive.
Previously, these initialisation were output in the module that created the system definition (with defineSystem
/makeSystem
), which might not be the same module the system execution procedures were output (from makeEcsCommit
/commitSystems
), leading to both modules needing their support imports and increasing module dependence complexity.
To further help with this, ecsImport
has been added. This instructs makeEcs
to output import
statements relative to the ecsImport
call site, letting you organise system code and support modules together without worrying about where makeEcs
is run.
There's also a lot of quality of life improvements, including aliasing components in systems, being able to register previously defined types as components, and better parsing of system bodies.
Changed
-
System registration and generation has been separated.
Systems types and instantiation are now deferred to
makeSystem
by default.This means systems always know the full ECS when generated, which can be important if systems defined later claim ownership to components.
Another advantage is easier use of imports, since systems are output in one place.
Breaking changes
- The new default is to defer system types and instantiation to
makeEcs
. - The previous behaviour of outputting systems when first defined can be obtained by setting
EcsSysOptions.commit
tosdcInPlace
.
Added
-
access
now works with the component type in systems and events so you can writeComponentType.access
. -
Component access/aliasing templates let you omit
item
when accessing components in systems and events:makeSystem "noItem", [MyComponent]: all: echo myComponent # Equivalent to 'item.myComponent'.
You can rename these components using a colon in the system's requirements:
makeSystem "noItem", [mc: MyComponent]: all: echo mc # Equivalent to 'item.myComponent'.
Aside from increasing readability, this can be useful for component libraries that use externally defined components:
template systemUsingComp*(userComponent: typedesc) {.dirty.} = makeSystem "useExternalComp", [uc: userComponent]: all: # As 'userComponent' is a type, to access its field in the # system's 'item' we'd have to either know the field name # already, or create a macro to generate the appropriate ident. # Now, we can just use the alias: echo uc
You can control whether access templates are output per system with
EcsSysOptions.itemTemplates
. -
Previously defined types can now be used as components:
type MyType = object register defaultCompOpts: MyType
-
Included an
addComponents
that operates on a whole system. -
defineToGroup
lets you wrap system definitions to a group. -
ecsImport
andecsImportFrom
replays imports inmakeEcs
using the call site as a path prefix. If this doesn't compile, the import is passed through verbatim.This allows systems deferred to
makeEcs
to naturally access other modules in a relative context, despite not being instantiated there.# subdir/module_1 var foo*: int
# subdir/module_2 register defaultCompOpts: type Bar* = object value*: int # Record import relative to our current path # to be output later by 'makeEcs'. ecsImport module_1 # This system uses our 'bar' variable from subdir/module_1. makeSystem "bar", [Bar]: all: echo foo + bar.value
# module_3.nim import module_2 # makeEcs now outputs 'import subdir/module_1'. makeEcsCommit "run"
-
System option for exporting fields by default.
-
onEcsCommitAll
,onEcsCommitGroups
, andonEcsNextCommit
events that are triggered on system commit. -
toTemplate
lets you produce templates of live entities for use withconstruct
. -
A string operator for
seq[ComponentTypeId]
. -
More examples.
Fixed
- Off-by-one in
componentCount
. - Component template options weren't stored.
- Inclusion test for multiple negated components.
- Check for assertions instead of 'defined(debug)'.
- Wrap 'addComponent' in a block for private ECS.
- Logging now properly handles identity.
- Removal of export markers for private scoped ECS now properly copies or passes through nodes instead of modifying in-place.
- System block parsing no longer skips recursion for call/command nodes that don't match iteration blocks. Iteration blocks are now correctly processed when not at the system body root.
Improved
- README documentation.
- Restructured existing examples and added more comments.
- Moved component removal to after events.
- Better entity to string output.
- Included {.line.} for 'item' templates for better line capture when debugging.
- The expression is now displayed when components can't be parsed.
- Better event spectrum coverage tests.
- Handle non-idents in stream command parsing.
- More line info logging for remove and delete.
- Update message for unsafe item access.
- Don't export ecsstatedb and pollute your namespace.
- Line info is now included for system definitions when logging.
- Handle nnkConv for findType.
ecsstatedb
is now pregenerated and doesn't need to be rebuilt each compilation.- Drop call site path from import uniqueness check.
- Mark 'curGroup' template as used.
- Use the base type for metadata lists.
- Use static analysis for unnested events.
- 'deExport' now handles 'when', better unused hints.
- Remove typetraits dependency.
- No seqs with 'cisArray' and no storage when owned.
- Remove imports and check within template.
- When parsing systems the nodes are now checked recursively, allowing things like
all
blocks to be placed within other blocks. - Improve event error messages.
- Add imports for
os
,times
andcpuinfo
only when used. - Allow the
instanceType
template withinregisterComponents
for easier creation of instance fields from component types. construct
can now be passed a count when building multiple entities from aConstructionTemplate
.
v0.3.0
Summary
This release introduces component negation for system requirements and improves events with compile time consistency checks.
Added
-
System requirements allow component negation with the
not
prefix.For example:
makeSystem "mySystem", [Comp1, not Comp2]: discard
Negating components expands the potential design space for systems and allows the creation of mutually exclusive systems.
-
Events now perform static analysis to ensure that embedded mutations
don't invalidate other events expected within a state change. This
means you can more safely mutate entities within events whilst having
your design checked when you compile.When event mutations clash with coherent state changes a reason is
output along with traces for both the calling event stack, and the
event that cause the clashing mutation.This means this is allowed:
MyComponent.onAdd: entity.remove MyComponent
But this will raise a compile time error:
MyComponent.onAdd: entity.remove MyComponent MyComponent.onAddCallback: echo "T"
To turn this feature off, compile with
-d:ecsPermissive
. -
System owned components can be declared with the
own
prefix.For example:
makeSystem "mySystem", [Comp1, own Comp2]
-
all
andstream
blocks can now be used at any level of code within
the system body and not just at the top level. -
all
andstream
blocks will raise a compile time error if they
have been embedded within themselves (this was not possible before
as only the top level was checked). -
Systems can now be defined with no components, allowing general
purpose code in the system workflow.These systems have the same control features, like pausing and
extracting to groups, but cannot use blocks that rely on entities
such asall
,stream
, or add/remove events, since they don't have
agroups
field to store component data. -
Events now allow accessing the current entity and system with
entity
andsys
, as well ascurEntity
andcurSystem
. -
The compile log (e.g.,
-d:ecsLog
) now includes the source file and
line of system definitions. -
Errors for field/option mismatch with
makeSystem
now show the
source file and line of the originaldefineSystem
. -
The error for attempting to redefine a system body with
makeSystem
now show the source file and line of the originalmakeSystem
. -
construct
is now{.discardable.}
. -
construct(quantity: int)
allows easier repeat construction of a
ComponentList
. -
hasAny
takes multiple component types and returns true when any of
them exist on the entity. -
fetch
now supports returning multiple components to a tuple like
theadd
operation. -
accessType
template returns the source component type from an
instance type. -
Improved error message when existing components are added.
-
Systems now have a type class for generic access to all systems.
-
register
can be used instead ofregisterComponents
. -
makeEcsCommit
performsmakeEcs
, thencommitSystems
. -
Comping with
-d:ecsLogDetails
is now much, much more detailed.
Operations now list their compile time invocation history, including
output of entity mutations by operation. -
EcsSysOptions
now includes athreading
field that, when set to
sthDistribute
, will set up athreads
seq
. This is set to the
number of cores when the system is instantiated. Spawning threads and
joining them is currently up to the user. Future versions will manage
threads for you when this option is enabled. -
All ECS operations are mangled with a unique identifier to avoid
variable collisions and allow better operation nesting.Pass
-d:ecsNoMangle
to output code without appending a signature -
Useful with-d:ecsLogCode
to make the logged code easier to read. -
Improvements and additions to test suites such as negation testing.
Changes
-
Streamlined and standardised events. More data access within events.
All relevant events now includeentity
access, and component events
involving systems now get access to the system row withitem
. -
Events that have access to the
item
template now include asserts
fromassertItem
and staticecsStrict
checks. -
Deleting the host/calling entity within an event is now a compile
time error. Other entities within events can still be deleted. -
has
can now take multiple arguments:echo entity.has(A, B, C)
. -
delete
now parses the entity's components for systems to query,
instead of querying all the systems.This makes destruction do work proportional to the number of run time
components, instead of the number of compile time systems. This
should meandelete
is faster. -
Only
delete
,construct
andclone
are now generated as procs, as
these state changes are fixed to the design. All other state changes
are lazily constructed through generics and macros.This offers two benefits: state changes can be interleaved in events,
and there's no time wasted on unused procs during compilation.The original assumption with generating procs for 'commonly used'
operations such as 'addComponent Type()' would save compilation time
over running a macro to generate. However, the opposite was often
true; compile times were increased by generating procedures that
weren't used. -
System compile time errors are checked before any macrocache updates.
This change means you can usewhen not compiles(...)
with defining
a system without create partial system entries. -
makeSystem
for a system previously defined withdefineSystem
now
ignores component order when checking matching requirements.
Note that the definition order is still semantically significant when
a system is first defined, as this informs the order of fields within
the system's item type. -
Component events that involve systems are announced as system events
whenechoRunning
is active. -
Component system events for missing systems create an
error
instead
of usingdoAssert
for a cleaner message. -
Strict checking outputs specific messages for removing and deleting.
-
addComponent
is now atemplate
and only constructed when used.
This reduces the need to build the code for every single combination
of singular adds to an entity which can constrain compilation for
some designs. -
EcsSysOptions.assertItem
fordefaultSystemOptions
is set to the
value ofcompileOptions("assertions")
. This means by default debug
builds will assert valid state withinitem
.
Fixed
-
The
onDelete
event is now properly exported. -
Multiple callback bodies such as
onAddCallback
,onRemoveCallback
,
now append to statements within a single callback proc instead of
trying to compile multiple procs with clashing names. -
onEcsBuilt
is now reset at the end ofmakeEcs
so that future
ECS outputs using the same identity don't output the code again.
Unfortunately this isn't possible withonEntityChange
because
state changes require access aftermakeEcs
has completed. -
Running systems at time intervals now applies to the entire system
body, and will process multipleall
orstream
blocks.
Previously, eachall
/stream
would reset timings, meaning only the
first block would be run.
Removed
-
Unused reference to
inSystemDeleteRow
macrocache entry. -
commit
has been removed from the intercept update event (see
breaking changes). -
Removed
newEntityTemplate
andinitComponents
as these are
better supported bycl()
.
Breaking changes
-
ecsStrict
is now the default. To disable strict item checking, pass
-d:ecsPermissive
.Strict checking will produce a compile time error if a system/event
context accessesitem
after doing something that has a possibility
to remove/invalidate the host context. However, this check doesn't
understand control flow or conditional statements, merely top to
bottom evaluation, and fully run time bound operations such as
construct
,clone
,transition
, anddelete
count as affecting
all components. -
Similarly, trying to
delete
theentity
that invokes an event will
now halt compilation with an error. This feature helps maintains
state integrity when events remove the calling entity. -
The
onUpdate
event has changed:- the
commit
proc has been removed. This event no longer allows
ignoring data updates whencommit
is not called.
The intention of the
commit
mechanism was to give the user explicit
control over whether to apply given component data. There is an
argument that this kind of mid-state data discarding invalidates the
transactional data integrity of state changes for seemingly little
value.The unique semantics of this event is also a potential for bugs,
since ifcommit
is not called, the data is dropped.Finally, the cancelling special case hasn't come up (anecdotally),
and is probably better served with a specific buffer mechanism at the
component level.- this event is now only called when
update
is used on a component
instance, and not in other contexts like new entities (for which you
should useonInit
oronAdd
/onAddCallback
).
- the
-
Events are now performed separately from system/entity state changes
and should be more lenient to embedding ECS state changes within
events. Event invocation order may differ slightly from previous
versions. -
Within
caseSystem
, theSystemTupleType
template was renamed
toItemType
. -
Generation history for components with
seq
storage are now lost
when the last i...
v0.2.2
v0.2.2 2021-8-13
Added
EcsEntityOptions
includes astrDefault
option to define the default behaviour of the$
operator for entities and components between displaying component data and just listing components.
Fixed
- Updated mention of
Container
toComponent
in theREADME.md
. - Updated link in
README.md
for the Polymersspaceshooter
demo.
Improved
- Expanded the ECS introduction in the
README.md
and some clean up of headings for options. - Nimble doesn't mention an invalid project structure.
v0.2.1
v0.2.1 2021-8-7
Added
- Private scope ECS generation.
sysProcessed
can be used withinstream
blocks to get the number of rows/entities the block has processed.
Fixed
defineSystemOwner
wasn't passing custom fields.stream multipass
andstream stochastic
no longer produce unknown identifier errors.stream
blocks starting at the last item no longer skip to the first item.- Streaming owner systems no longer process an extra row.
- Using non-component types in system requirements now produces a compile error.
useSet
now includes components to the set.- The correct ident is now used internally for
transition
. - The
onSystemAddTo
andonSystemRemoveFrom
events no longer fail to compile in rare cases.
Improved
- System stream parameters can be unordered.
- More features documented in the
README.md
.
Changed
- Owner systems now index from zero. Owned component instances are
valid
when uninitialised, andfetch
returns an instance set to-1
when it cannot find an owned component.
v0.2.0
This release is a complete internal restructuring to support system-owned components, new events, and a macrocache backend. Whilst effort has been made to maintain the current API, there are some significant changes.
Added
-
Ease of use:
-
Improved documentation to cover all library functionality.
-
Added code examples from documentation to the
examples
folder. -
More descriptive error messages.
-
onEcsBuilt
allows you to emit code aftermakeEcs
has finished. This code can use entities and ECS operations, which can be useful for utility functions and setup for systems and libraries. -
defaultComponentOptions
,defaultEntityOptions
, anddefaultSystemOptions
now have shorter aliases asdefaultCompOpts
,defaultEntOpts
, anddefaultSysOpts
. -
EcsEntityOptions
allows selection of error response between assertions and raising exceptions.
-
-
Components:
-
removeComponent
,addComponent
, andfetchComponent
can now be writtenremove
,add
, andfetch
. -
You can now remove multiple components in one state change with
remove
/removeComponents
. -
update
/updateComponents
supports updating multiple components with aComponentList
. Only existing components on the entity are updated, and others in theComponentList
are ignored. -
typeName
converts a run timeComponentTypeId
to the string of the type it represents. -
componentCount
returns the number of components in use for a component type or instance type. -
componentStorage
lets you access the component's storage list. -
registerComponents
will ignore types with the{.notComponent.}
pragma.
-
-
Systems:
-
You can now write code directly in the system body that is executed when the system is run. This means blocks are no longer a requirement of system bodies and you don't need to use
start
blocks to declare variables. Another benefit is you can write system code that respects thepaused
state even when not using any blocks. -
The
all
andstream
blocks are now expanded within the system body. This allows multiple passes and further processing such as running code in the body between these blocks. -
Systems now allow multiple blocks of the same type.
-
Systems passed the same component more than once will now throw an error at compile time.
-
System fields can be defined with the
fields:
block withinmakeSystem
. When a system has previously been defined withdefineSystem
, thefields:
block is checked to match this definition. -
Systems now support a
stream stochastic:
block to randomly select items to process. -
Systems can be grouped to run as separate procedures with
defineGroup
. These systems are removed from the pending output ofcommitSystems
and are instead emitted withcommitGroup
. It's a compile time error to emit groups with systems that don't have bodies defined. -
The system utility
clear
allows deleting all entities in a system. -
The system utility
remove
allows removing multiple components from all entities in a system. -
expectSystems
checks that an entity has a specific set of systems and willdoAssert
if the entity does not satisfy these systems. -
systemsUsed
returns a string containing the systems that would be used for entities with a particular set of components. -
analyseSystem
provides statistical analysis of the memory addresses accessed by a system. -
Systems include a
deleteList
seq
that will delete any entities added to it after the system has finished executing. -
Systems include an
id
field with the system'sSystemIndex
. -
caseSystem
allows generic operations based on a run timeSystemIndex
in a similar way tocaseComponent
. -
System options given to
defineSystem
are now checked to match options passed tomakeSystemOpts
.
-
-
ECS identities:
- Multiple ECS may be generated separately using
const
identity strings.
- Multiple ECS may be generated separately using
-
System owned components:
-
Systems can now own the storage and lifetime of components they use with
defineSystemOwner
. Owned components are inlined into the groups field of the owner system and depend on the system row to exist. When iterating, systems access their owned components in order as contiguous memory and without indirection. Access to owned components from other systems is a single indirection.- A component type can only be owned by one system.
- Adding owned components to entities without meeting the full component requirements of the owner systems will result in an error.
- Removing owned components will cascade system removal for other owned components in the system.
-
-
System events:
-
When defining a system, you can now use
added
andremoved
blocks to let you process state changes in a system context. These events let you work with sets of components usingitem
as if you were using anall
orstream
block. -
Actions in
added
andremoved
are performed immediately as part of a state change such asadd
orremove
, and not during the system's normal cycle.
-
-
onEntityChange
event:- This event is triggered for any entity that has components added or removed.
-
Run-time construction:
-
Creating entities with
construct
andclone
is now more efficient and flexible.- These operations are now generated as integrated state changes for dynamic sets of components.
construct
builds a map of types then updates all relevant systems with a single generated state change.- Previously
add
was called for each component, with significant overhead. clone
can elide some validity checks.
-
The
cl
macro makes building aComponentList
easy by allowing both source component types and container types to be mixed, whilst handling the internaltypeId
field for you. This macro also reduces boilerplate by handling a single component without having to typecast it toComponent
. -
construct
andclone
now call user events. -
Post construction events allow adding/removing components.
-
transition
allows state machine like switching of components between twoComponentList
parameters.
-
-
Misc utilities:
-
high(entityType: typedesc[EntityId] | typedesc[EntityRef])
returns the highestEntityId
currently in use. -
EntityRef
now has avalid
template.
-
-
New templates within
caseComponent
:-
isOwned
returns a bool constant indicating if the component type is owned. -
owningSystemIndex
returns theSystemIndex
of the owner system (orInvalidSystem
if none). -
owningSystem
lets you access the component's owner system variable. Note that this is only generated for components that are owned. -
componentData
lets you access the component's storage list.
-
-
Compile switches:
-
ecsLog
: output ECS generation progress. -
ecsLogDetails
: output detailed ECS generation information. -
ecsStrict
includes a check at compile time to catch accessingitem
after a remove or delete has affected the system (which can potentially change whatitem
refers to).
-
Changes
-
The library now uses the
macrocache
module to store the ECS state instead of{.compileTime.}
variables. -
The default stream count is now
1
, changed from10
. -
User events now run outside of state changes instead of inside them, and can rely on the state being fully defined when invoked.
-
SystemIndex
now starts from1
andSystemIndex(0) == InvalidSystemIndex
. PreviouslyInvalidSystemIndex
was equal toSystemIndex(-1)
. This change means default (zero) values are invalid, andSystemIndex
achieves parity withComponentTypeId
.
Breaking changes
-
Removed:
-
makeSystemOptFields
has been removed, as it is no longer needed. Fields inmakeSystem
andmakeSystemOpts
can be defined using thefields:
block. -
The
tmpl
andinit
macros generated for each component have been removed. Use thecl
macro or manually initialise component containers withmakeContainer
. -
The
deleteEntity
template withinall
andstream
blocks has been removed as it offers no value overentity.delete
. -
reprocessRow
has been removed. This is now handled automatically at compile time. -
addFetch
has been removed. This operation was ambiguous withaddComponent
and whilstaddComponent
returns a component instance,addFetch
returned aComponentRef
, which is less useful. To obtain aComponentRef
from an instance, use thetoRef
template. -
amFieldTemplates
inEcsCompOptions.accessMethod
has been removed as it did not offer any significant functionality overamDotOp
. -
Within
caseComponent
, thecomponentRefInit
template has been removed as these initialisers are no longer present.
-
-
Changed:
-
System execution order:
defineSystem
now sets the order that systems are run bycommitSystems
, where previously this was set when the system body was defined bymakeSystem
ormakeSystemBody
. When no matchingdefineSystem
exists,makeSystem
appends order as before when it invokesdefineSystem
. This means the order must be defined beforemakeEcs
is run, whereas before it could be defined aftermakeEcs
. -
commitSystems
will only output systems that have not already been output and are not grouped. -
The construction callback API has changed: constructor/clone callbacks now return a
seq[Component]
which is processed after the callback to ensure systems are correctly linked. Construction/clone callbacks should create the appropriateComponent
containers and add them to theseq
result. -
Within
caseComponent
, the templatecomponentInstanceIds
has been renamedcomponentGenerations
. -
The compile switch `debugSystemPerformance...
-
Initial release
v0.1.0 Replace readme with manual