1
0
Fork 0
mirror of https://github.com/Reuh/ubiquitousse.git synced 2025-10-27 09:09:30 +00:00

ecs: implement skip lists

This commit is contained in:
Étienne Fildadut 2022-10-11 19:14:48 +09:00
parent 0ea6117af9
commit bd28610ff4
14 changed files with 936 additions and 639 deletions

View file

@ -113,7 +113,7 @@
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2022-10-07 12:40:59 </i> <i style="float:right;">Last updated 2022-10-11 19:12:05 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -337,7 +337,7 @@
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2022-10-07 12:40:59 </i> <i style="float:right;">Last updated 2022-10-11 19:12:05 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -70,7 +70,8 @@ Main differences include:</p>
<li>ability to nest systems (more organisation potential);</li> <li>ability to nest systems (more organisation potential);</li>
<li>instanciation of systems for each world (no shared state) (several worlds can coexist at the same time easily);</li> <li>instanciation of systems for each world (no shared state) (several worlds can coexist at the same time easily);</li>
<li>adding and removing entities is done instantaneously (no going isane over tiny-ecs cache issues);</li> <li>adding and removing entities is done instantaneously (no going isane over tiny-ecs cache issues);</li>
<li>ability to add and remove components from entities after they were added to the world (more dynamic entities).</li> <li>ability to add and remove components from entities after they were added to the world (more dynamic entities);</li>
<li>much better performance for ordered systems (entities are stored in a skip list internally).</li>
</ul> </ul>
@ -83,6 +84,8 @@ if you don&rsquo;t use them.</p>
<p>The module returns a table that contains several functions, <a href="../modules/ecs.html#world">world</a> or <a href="../modules/scene.html#">scene</a> are starting points <p>The module returns a table that contains several functions, <a href="../modules/ecs.html#world">world</a> or <a href="../modules/scene.html#">scene</a> are starting points
to create your world.</p> to create your world.</p>
<p>This library was designed to be reasonably fast; on my machine using LuaJIT, in the duration of a frame (1/60 seconds) about 40000 entities can be added to an unordered system or 8000 to an ordered system. Complexities are documented for each function.</p>
<p>No mandatory dependency. <p>No mandatory dependency.
Optional dependency: <a href="../modules/ubiquitousse.html#scene">ubiquitousse.scene</a>, to allow quick creation of ECS-based scenes (<a href="../modules/ecs.html#scene">ecs.scene</a>).</p> Optional dependency: <a href="../modules/ubiquitousse.html#scene">ubiquitousse.scene</a>, to allow quick creation of ECS-based scenes (<a href="../modules/ecs.html#scene">ecs.scene</a>).</p>
<h3>Usage:</h3> <h3>Usage:</h3>
@ -140,6 +143,26 @@ end
</table> </table>
<h3 class="doc-title"><a href="#Entity.Component">Components.</a></h3> <h3 class="doc-title"><a href="#Entity.Component">Components.</a></h3>
<table class="function_list"> <table class="function_list">
<tr>
<td class="name" nowrap><a href="#Entity.first">Entity.first</a></td>
<td class="summary">First element of the highest layer linked list of entities: { entity, next_element, element_in_lower_layer }.</td>
</tr>
<tr>
<td class="name" nowrap><a href="#Entity.firstBase">Entity.firstBase</a></td>
<td class="summary">First element of the base layer.</td>
</tr>
<tr>
<td class="name" nowrap><a href="#Entity.previous">Entity.previous</a></td>
<td class="summary">List of hash map (one per skip listlayer) of entities in the system and their previous linked list element.</td>
</tr>
<tr>
<td class="name" nowrap><a href="#Entity.nLayers">Entity.nLayers</a></td>
<td class="summary">Number of layers in the skip list.</td>
</tr>
<tr>
<td class="name" nowrap><a href="#Entity.n">Entity.n</a></td>
<td class="summary">Number of elements in the skip list.</td>
</tr>
</table> </table>
<h2><a href="#System_objects">System objects </a></h2> <h2><a href="#System_objects">System objects </a></h2>
<table class="function_list"> <table class="function_list">
@ -519,6 +542,117 @@ component or if it doesn&rsquo;t exist in the entity.
}</pre> }</pre>
</ul> </ul>
</dd>
<dt>
<a name = "Entity.first"></a>
<strong>Entity.first</strong>
</dt>
<dd>
First element of the highest layer linked list of entities: { entity, next_element, element_in_lower_layer }.
The default entity <code>head</code> is always added as a first element to simplify algorithms; remember to skip it.
</ul>
</ul>
</ul>
</ul>
<h3>Fields:</h3>
<ul>
<li><span class="parameter">head</span>
</li>
<li><span class="parameter">nil</span>
</li>
</ul>
</dd>
<dt>
<a name = "Entity.firstBase"></a>
<strong>Entity.firstBase</strong>
</dt>
<dd>
First element of the base layer.
</ul>
</ul>
</ul>
</ul>
</dd>
<dt>
<a name = "Entity.previous"></a>
<strong>Entity.previous</strong>
</dt>
<dd>
List of hash map (one per skip listlayer) of entities in the system and their previous linked list element.
Does not contain a key for the <code>head</code> entity.
This make each linked list layer effectively a doubly linked list, but with fast access to the previous element using this map (and therefore O(1) deletion).
</ul>
</ul>
</ul>
</ul>
<h3>Fields:</h3>
<ul>
<li><span class="parameter">{</span>
</li>
</ul>
</dd>
<dt>
<a name = "Entity.nLayers"></a>
<strong>Entity.nLayers</strong>
</dt>
<dd>
Number of layers in the skip list.
</ul>
</ul>
</ul>
</ul>
</dd>
<dt>
<a name = "Entity.n"></a>
<strong>Entity.n</strong>
</dt>
<dd>
Number of elements in the skip list.
</ul>
</ul>
</ul>
</ul>
</dd> </dd>
</dl> </dl>
<h2 class="section-header has-description"><a name="System_objects"></a>System objects </h2> <h2 class="section-header has-description"><a name="System_objects"></a>System objects </h2>
@ -1306,7 +1440,7 @@ avoid repeating your filters or allow controlling several system from a single p
If you do that, since <a href="../modules/ecs.html#System:remove">System:remove</a> will not search for entities in systems where they should have been filtered out, the added entities will not be removed If you do that, since <a href="../modules/ecs.html#System:remove">System:remove</a> will not search for entities in systems where they should have been filtered out, the added entities will not be removed
when calling <a href="../modules/ecs.html#System:remove">System:remove</a> on a parent system or the world. The entity can be removed by calling <a href="../modules/ecs.html#System:remove">System:remove</a> on the system <a href="../modules/ecs.html#System:add">System:add</a> was called on.</p> when calling <a href="../modules/ecs.html#System:remove">System:remove</a> on a parent system or the world. The entity can be removed by calling <a href="../modules/ecs.html#System:remove">System:remove</a> on the system <a href="../modules/ecs.html#System:add">System:add</a> was called on.</p>
<p> Complexity: O(1) per unordered system, O(entityCount) per ordered system. <p> Complexity: O(1) per unordered system, O(log2(entityCount)) per ordered system.
</ul> </ul>
</ul> </ul>
@ -1349,7 +1483,7 @@ avoid repeating your filters or allow controlling several system from a single p
<p> If you intend to call this on a subsystem instead of the world, please read the warning in <a href="../modules/ecs.html#System:add">System:add</a>.</p> <p> If you intend to call this on a subsystem instead of the world, please read the warning in <a href="../modules/ecs.html#System:add">System:add</a>.</p>
<p> Complexity: O(1) per system. <p> Complexity: O(1) per unordered system, O(log2(entityCount)) per ordered system.
</ul> </ul>
</ul> </ul>
@ -1429,7 +1563,7 @@ avoid repeating your filters or allow controlling several system from a single p
<p> Will recalculate the entity position in the entity list for this system and its subsystems. <p> Will recalculate the entity position in the entity list for this system and its subsystems.
Will skip entities that are not in the system.</p> Will skip entities that are not in the system.</p>
<p> Complexity: O(entityCount) per system. <p> Complexity: O(1) per unordered system, O(log2(entityCount)) per ordered system.
</ul> </ul>
</ul> </ul>
@ -1501,7 +1635,9 @@ avoid repeating your filters or allow controlling several system from a single p
<strong>System:iter ()</strong> <strong>System:iter ()</strong>
</dt> </dt>
<dd> <dd>
Returns an iterator that iterate through the entties in this system, in order. Returns an iterator that iterate through the entties in this system, in order. </p>
<p> Complexity: O(1) per iteration; O(entityCount) for the full iteration
</ul> </ul>
</ul> </ul>
@ -1525,9 +1661,9 @@ avoid repeating your filters or allow controlling several system from a single p
<strong>System:get (i)</strong> <strong>System:get (i)</strong>
</dt> </dt>
<dd> <dd>
Get the <code>i</code>th entity in the system. Get the <code>i</code>th entity in the system. </p>
This is a simple wrapper around <a href="../modules/ecs.html#System:iter">iter</a>; it <em>will</em> iterate over all the entities in the system in order until we reach the desired one.
Complexity: O(i) <p> Complexity: O(i)
</ul> </ul>
</ul> </ul>
@ -1558,7 +1694,9 @@ avoid repeating your filters or allow controlling several system from a single p
<strong>System:clear ()</strong> <strong>System:clear ()</strong>
</dt> </dt>
<dd> <dd>
Remove every entity from the system and its subsystems. Remove every entity from the system and its subsystems. </p>
<p> Complexity: O(entityCount) per system
</ul> </ul>
</ul> </ul>
@ -1578,7 +1716,9 @@ avoid repeating your filters or allow controlling several system from a single p
<dd> <dd>
Try to update the system and its subsystems. Should be called on every game update.</p> Try to update the system and its subsystems. Should be called on every game update.</p>
<p> Subsystems are updated after their parent system. <p> Subsystems are updated after their parent system.</p>
<p> Complexity: O(entityCount) per system if system:process is defined; O(1) per system otherwise.
</ul> </ul>
</ul> </ul>
@ -1605,7 +1745,9 @@ avoid repeating your filters or allow controlling several system from a single p
<dd> <dd>
Try to draw the system and its subsystems. Should be called on every game draw.</p> Try to draw the system and its subsystems. Should be called on every game draw.</p>
<p> Subsystems are drawn after their parent system. <p> Subsystems are drawn after their parent system.</p>
<p> &mdash; Complexity: O(entityCount) per system if system:render is defined; O(1) per system otherwise.
</ul> </ul>
</ul> </ul>
@ -1629,7 +1771,9 @@ avoid repeating your filters or allow controlling several system from a single p
if the method exists and the entity is in the system. <code>c</code> is the system <a href="#Entity.Component">component</a> if the method exists and the entity is in the system. <code>c</code> is the system <a href="#Entity.Component">component</a>
associated with the current system, and <code>e</code> is the <a href="../modules/ecs.html#Entity_objects">Entity</a>.</p> associated with the current system, and <code>e</code> is the <a href="../modules/ecs.html#Entity_objects">Entity</a>.</p>
<p> Think of it as a way to perform custom callbacks issued from an entity event, similar to <a href="../modules/ecs.html#System:onAdd">System:onAdd</a>. <p> Think of it as a way to perform custom callbacks issued from an entity event, similar to <a href="../modules/ecs.html#System:onAdd">System:onAdd</a>.</p>
<p> Complexity: O(1) per system
</ul> </ul>
</ul> </ul>
@ -1681,7 +1825,9 @@ its sibling systems (i.e. completely stop the propagation of the event).</li>
disable <a href="../modules/ecs.html#System:onUpdate">System:onUpdate</a> behaviour on the system and its subsystems).</p> disable <a href="../modules/ecs.html#System:onUpdate">System:onUpdate</a> behaviour on the system and its subsystems).</p>
<p> <code>&quot;capture&quot;</code> would be for example used to prevent other systems from handling the event (for example to make sure an <p> <code>&quot;capture&quot;</code> would be for example used to prevent other systems from handling the event (for example to make sure an
input event is handled only once by a single system). input event is handled only once by a single system).</p>
<p> Complexity: O(1) per system
</ul> </ul>
</ul> </ul>
@ -1710,6 +1856,7 @@ its sibling systems (i.e. completely stop the propagation of the event).</li>
</dt> </dt>
<dd> <dd>
Remove all the entities and subsystems in this system. Remove all the entities and subsystems in this system.
Complexity: O(entityCount) per system
</ul> </ul>
</ul> </ul>
@ -1729,7 +1876,7 @@ its sibling systems (i.e. completely stop the propagation of the event).</li>
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2022-10-07 12:40:59 </i> <i style="float:right;">Last updated 2022-10-11 19:12:05 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -238,6 +238,10 @@ end
<td class="summary">Set the state of this input and its children to a neutral position (i.e.</td> <td class="summary">Set the state of this input and its children to a neutral position (i.e.</td>
</tr> </tr>
<tr> <tr>
<td class="name" nowrap><a href="#Input:set">Input:set (...)</a></td>
<td class="summary">Manually set the state of this input.</td>
</tr>
<tr>
<td class="name" nowrap><a href="#Input:setJoystick">Input:setJoystick (joystick)</a></td> <td class="name" nowrap><a href="#Input:setJoystick">Input:setJoystick (joystick)</a></td>
<td class="summary">Set the joystick associated with this input.</td> <td class="summary">Set the joystick associated with this input.</td>
</tr> </tr>
@ -1181,6 +1185,31 @@ player.fire.event:bind(<span class="string">"pressed"</span>, <span class="keywo
</dd>
<dt>
<a name = "Input:set"></a>
<strong>Input:set (...)</strong>
</dt>
<dd>
Manually set the state of this input.
</ul>
</ul>
</ul>
</ul>
<h3>Parameters:</h3>
<ul>
<li><span class="parameter">...</span>
</li>
</ul>
</dd> </dd>
<dt> <dt>
<a name = "Input:setJoystick"></a> <a name = "Input:setJoystick"></a>
@ -1694,7 +1723,7 @@ player.fire.event:bind(<span class="string">"pressed"</span>, <span class="keywo
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2022-10-07 12:40:59 </i> <i style="float:right;">Last updated 2022-10-11 19:12:05 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -2066,7 +2066,7 @@ end
Level background. </p> Level background. </p>
<p> If there is a background image, <code>background.image</code> contains a table <code>{image=image, x=number, y=number, sx=number, sy=number}</code> <p> If there is a background image, <code>background.image</code> contains a table <code>{image=image, x=number, y=number, sx=number, sy=number}</code>
where <a href="../modules/ldtk.html#Tileset.image">image</a> is the LÖVE image (or image filepath if LÖVE not available) <a href="../modules/ldtk.html#Tile.x">x</a> and <a href="../modules/ldtk.html#Tile.y">y</a> are the top-left position, where <a href="../modules/ldtk.html#Tileset.image">image</a> is the LÖVE image (or image filepath if LÖVE not available) <a href="../modules/ldtk.html#Entity.x">x</a> and <a href="../modules/ldtk.html#Tile.y">y</a> are the top-left position,
and <a href="../modules/ldtk.html#Entity.sx">sx</a> and <a href="../modules/ldtk.html#Entity.sy">sy</a> the horizontal and vertical scale factors. and <a href="../modules/ldtk.html#Entity.sx">sx</a> and <a href="../modules/ldtk.html#Entity.sy">sy</a> the horizontal and vertical scale factors.
</ul> </ul>
@ -2142,7 +2142,7 @@ end
<li>Enum are converted into a Lua string giving the currently selected enum value.</li> <li>Enum are converted into a Lua string giving the currently selected enum value.</li>
<li>Filepath are converted into a Lua string giving the file path.</li> <li>Filepath are converted into a Lua string giving the file path.</li>
<li>Arrays are converted into a Lua table with the elements in it as a list.</li> <li>Arrays are converted into a Lua table with the elements in it as a list.</li>
<li>Points are converted into a Lua table with the fields <a href="../modules/ldtk.html#Tile.x">x</a> and <a href="../modules/ldtk.html#Tile.y">y</a>, in pixels: <code>{ x=number, y=number }</code>.</li> <li>Points are converted into a Lua table with the fields <a href="../modules/ldtk.html#Entity.x">x</a> and <a href="../modules/ldtk.html#Tile.y">y</a>, in pixels: <code>{ x=number, y=number }</code>.</li>
<li>Colors are converted into a Lua table with the red, green and blue components in [0-1] as a list: <code>{r,g,b}</code>.</li> <li>Colors are converted into a Lua table with the red, green and blue components in [0-1] as a list: <code>{r,g,b}</code>.</li>
<li>Tiles are converted into a Lua table { tileset = associated tileset object, quad = associated quad } where <a href="../modules/ldtk.html#Tile.quad">quad</a> is a LÖVE Quad if LÖVE is available, otherwise a table <code>{ x, y, width, height }</code>.</li> <li>Tiles are converted into a Lua table { tileset = associated tileset object, quad = associated quad } where <a href="../modules/ldtk.html#Tile.quad">quad</a> is a LÖVE Quad if LÖVE is available, otherwise a table <code>{ x, y, width, height }</code>.</li>
<li>EntityRef are converted into a Lua table { level = level, layerIid = layer IID, entityIid = entity IID, entity = see explanation }. If the entity being refernced belongs to another level and this level is not loaded, <code>entity</code> will be nil; otherwise (same level or the other level is also loaded), it will contain the entity.</li> <li>EntityRef are converted into a Lua table { level = level, layerIid = layer IID, entityIid = entity IID, entity = see explanation }. If the entity being refernced belongs to another level and this level is not loaded, <code>entity</code> will be nil; otherwise (same level or the other level is also loaded), it will contain the entity.</li>
@ -2170,7 +2170,7 @@ end
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2022-10-07 12:40:59 </i> <i style="float:right;">Last updated 2022-10-11 19:12:05 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -703,7 +703,7 @@
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2022-10-07 12:40:59 </i> <i style="float:right;">Last updated 2022-10-11 19:12:05 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -788,7 +788,7 @@ signal.event:bind(&quot;keypressed&quot;, function(key, scancode) print(&quot;pr
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2022-10-07 12:40:59 </i> <i style="float:right;">Last updated 2022-10-11 19:12:05 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -1154,7 +1154,7 @@
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2022-10-07 12:40:59 </i> <i style="float:right;">Last updated 2022-10-11 19:12:05 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -394,7 +394,7 @@ the repository to save you a few seconds.</p>
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2022-10-07 12:40:59 </i> <i style="float:right;">Last updated 2022-10-11 19:12:05 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -785,7 +785,7 @@
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2022-10-07 12:40:59 </i> <i style="float:right;">Last updated 2022-10-11 19:12:05 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -65,7 +65,7 @@
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2022-10-07 12:40:59 </i> <i style="float:right;">Last updated 2022-10-11 19:12:05 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -87,7 +87,7 @@
</div> <!-- id="main" --> </div> <!-- id="main" -->
<div id="about"> <div id="about">
<i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i> <i>generated by <a href="http://github.com/stevedonovan/LDoc">LDoc 1.4.6</a></i>
<i style="float:right;">Last updated 2022-10-07 12:40:59 </i> <i style="float:right;">Last updated 2022-10-11 19:12:05 </i>
</div> <!-- id="about" --> </div> <!-- id="about" -->
</div> <!-- id="container" --> </div> <!-- id="container" -->
</body> </body>

View file

@ -6,7 +6,8 @@ Main differences include:
* ability to nest systems (more organisation potential); * ability to nest systems (more organisation potential);
* instanciation of systems for each world (no shared state) (several worlds can coexist at the same time easily); * instanciation of systems for each world (no shared state) (several worlds can coexist at the same time easily);
* adding and removing entities is done instantaneously (no going isane over tiny-ecs cache issues); * adding and removing entities is done instantaneously (no going isane over tiny-ecs cache issues);
* ability to add and remove components from entities after they were added to the world (more dynamic entities). * ability to add and remove components from entities after they were added to the world (more dynamic entities);
* much better performance for ordered systems (entities are stored in a skip list internally).
And a fair amount of other quality-of-life features. And a fair amount of other quality-of-life features.
@ -17,6 +18,8 @@ if you don't use them.
The module returns a table that contains several functions, `world` or `scene` are starting points The module returns a table that contains several functions, `world` or `scene` are starting points
to create your world. to create your world.
This library was designed to be reasonably fast; on my machine using LuaJIT, in the duration of a frame (1/60 seconds) about 40000 entities can be added to an unordered system or 8000 to an ordered system. Complexities are documented for each function.
No mandatory dependency. No mandatory dependency.
Optional dependency: `ubiquitousse.scene`, to allow quick creation of ECS-based scenes (`ecs.scene`). Optional dependency: `ubiquitousse.scene`, to allow quick creation of ECS-based scenes (`ecs.scene`).
@ -54,8 +57,7 @@ if not loaded then scene = nil end
let ecs let ecs
-- TODO: Implement a skip list for faster search. -- TODO: better control over system order: process, draw, methods? (for lag reasons and dependencies)
-- better control over system order: process, draw, methods? (for lag reasons and dependencies)
--- Entities are regular tables that get processed by `System`s. --- Entities are regular tables that get processed by `System`s.
-- --
@ -98,9 +100,6 @@ local sprite = {
} }
]]-- ]]--
--- Special value used as the first element of each system's linked list of entities.
let head = {}
--- Recursively remove subsystems from a system. --- Recursively remove subsystems from a system.
let recDestroySystems = (system) let recDestroySystems = (system)
for i=#system.systems, 1, -1 do for i=#system.systems, 1, -1 do
@ -122,17 +121,6 @@ let recCallOnRemoveFromWorld = (world, systems)
end end
end end
--- Iterate through the next entity, based on state s: { previousLinkedListItem }
let nextEntity = (s)
if s[1] then
let var = s[1][1]
s[1] = s[1][2]
return var
else
return nil
end
end
--- Recursively copy content of a into b if it isn't already present. --- Recursively copy content of a into b if it isn't already present.
-- Don't copy keys, will preserve metatable but not copy them. -- Don't copy keys, will preserve metatable but not copy them.
let copy = (a, b, cache={}) let copy = (a, b, cache={})
@ -156,6 +144,149 @@ let copy = (a, b, cache={})
end end
end end
--- Skip list implementation ---
-- Well technically it's a conbination of a skip list (for ordering) and a hash map (for that sweet O(1) search). Takes more memory but oh so efficient.
--- Special value used as the first element of each linked list.
let head = {}
--- Create a new linked list.
let skipNew = ()
let s = {
--- First element of the highest layer linked list of entities: { entity, next_element, element_in_lower_layer }.
-- The default entity `head` is always added as a first element to simplify algorithms; remember to skip it.
first = { head, nil },
--- First element of the base layer.
firstBase = nil,
--- List of hash map (one per skip listlayer) of entities in the system and their previous linked list element.
-- Does not contain a key for the `head` entity.
-- This make each linked list layer effectively a doubly linked list, but with fast access to the previous element using this map (and therefore O(1) deletion).
previous = { {} },
--- Number of layers in the skip list.
nLayers = 1,
--- Number of elements in the skip list.
n = 0
}
s.firstBase = s.first
return s
end
--- Iterate through the next entity in a linked list, based on state s: { previousLinkedListItem }
let nextEntity = (s)
if s[1] then
let var = s[1][1]
s[1] = s[1][2]
return var
else
return nil
end
end
--- Returns an iterator over all the elements of the skip list.
-- Complexity: O(n) like expected
let skipIter = :()
return nextEntity, { @firstBase[2] }
end
--- Add new layers (if needed) in order to target O(log2(n)) complexity for skip list operations.
-- i.e. add layers until we have log2(n) layers
-- Complexity: O(log2(n)) if you never called it before, but you should call it with every insert for O(1)
let skipAddLayers = :()
while @n > 2^@nLayers do
@first = { head, nil, @first }
table.insert(@previous, {})
@nLayers += 1
end
end
--- 1/2 chance of being true, 1/2 of being false! How exciting!
let coinFlip = ()
return math.random(0,1) == 1
end
--- Insert an element into the skip list using system:compare for ordering.
-- Behavior undefined if e is already in the skip list.
-- Complexity: if luck is on your side, O(log2(n)); O(n) if the universe hates you; O(1) if compare always returns true and nLayers = 1
let skipInsert = :(system, e)
-- find previous entity in each layer
let prevLayer = {}
let prev = @first -- no need to process first entity as it is the special `head` entity
for i=@nLayers, 1, -1 do
while true do
-- next is end of layer or greater entity: select this prev entity
if prev[2] == nil or system:compare(e, prev[2][1]) then
prevLayer[i] = prev
-- not on base layer: go down a layer, for loop will continue
if prev[3] then
prev = prev[3] -- same entity on lower layer
break
end
break
-- next entity on current layer
else
prev = prev[2]
end
end
end
-- add to each layer
let inLowerLayer
for i=1, @nLayers do
prev = prevLayer[i]
if i == 1 or coinFlip() then -- always present in base layer, otherwise 1/2 chance
let nxt = prev[2]
prev[2] = { e, nxt, inLowerLayer }
@previous[i][e] = prev
if nxt then
@previous[i][nxt[1]] = prev[2]
end
inLowerLayer = prev[2]
else
break
end
end
@n += 1
end
--- Remove an element from the skip list.
-- Behavior undefined if e is not in the skip list.
-- Complexity: O(nLayers) at most, which should be O(log2(n)) if you called skipAddLayers often enough
let skipDelete = :(e)
-- remove from each layer
for i=1, @nLayers do
let previous = @previous[i]
if previous[e] then
let prev = previous[e]
prev[2] = prev[2][2]
previous[e] = nil
if prev[2] then
previous[prev[2][1]] = prev
end
else
break -- won't appear on higher layers either
end
end
@n -= 1
end
--- Reorder an element into the skip list.
-- Behavior undefined if e is not in the skip list.
-- Complexity: if luck is on your side, O(log2(n)); O(n) if the universe hates you; O(1) if compare always returns true and nLayers = 1
let skipReorder = :(system, e)
skipDelete(@, e)
skipInsert(@, system, e)
end
--- Returns the ith element of the skip list.
-- Complexity: O(n)
let skipIndex = :(i)
local n = 1
for e in skipIter(@) do
if n == i then
return e
end
n += 1
end
return nil
end
--[[-- Systems and Worlds. --[[-- Systems and Worlds.
Systems are what do the processing on your entities. A system contains a list of entities; the entities in this list are selected Systems are what do the processing on your entities. A system contains a list of entities; the entities in this list are selected
using a `filter`, and the system will only operate on those filtered entities. using a `filter`, and the system will only operate on those filtered entities.
@ -385,14 +516,12 @@ let system_mt = {
--- Private fields --- --- Private fields ---
--- First element of the linked list of entities: { entity, next_element }. --- Hash map of the entities currently in the system.
-- The default entity `head` is always added as a first element to simplify algorithms; remember to skip it. -- Used to quickly check if an entity is present of not in this system.
-- This is actually the _skiplist.previous[1] table.
-- @local -- @local
_first = nil, _has = nil,
--- Hash map of entities in the system and their previous linked list element. Does not contain a key for the `head` entity.
-- This make the list effectively a doubly linked list, but with fast access to the previous element using this map (and therefore O(1) deletion).
-- @local
_previous = nil,
--- Amount of time waited since last update (if interval is set). --- Amount of time waited since last update (if interval is set).
-- @local -- @local
_waited = 0, _waited = 0,
@ -412,37 +541,27 @@ let system_mt = {
-- If you do that, since `System:remove` will not search for entities in systems where they should have been filtered out, the added entities will not be removed -- If you do that, since `System:remove` will not search for entities in systems where they should have been filtered out, the added entities will not be removed
-- when calling `System:remove` on a parent system or the world. The entity can be removed by calling `System:remove` on the system `System:add` was called on. -- when calling `System:remove` on a parent system or the world. The entity can be removed by calling `System:remove` on the system `System:add` was called on.
-- --
-- Complexity: O(1) per unordered system, O(entityCount) per ordered system. -- Complexity: O(1) per unordered system, O(log2(entityCount)) per ordered system.
-- @tparam Entity e entity to add -- @tparam Entity e entity to add
-- @tparam Entity... ... other entities to add -- @tparam Entity... ... other entities to add
-- @treturn Entity,... `e,...` the function arguments -- @treturn Entity,... `e,...` the function arguments
add = :(e, ...) add = :(e, ...)
if e ~= nil and not @_previous[e] and @filter(e) then if e ~= nil and not @_has[e] and @filter(e) then
-- copy default system component -- copy default system component
if @component and @default then if @component and @default then
copy({ [@component] = @default }, e) copy({ [@component] = @default }, e)
end end
-- add to linked list -- ordered system: add new layer if needed
let entity = @_first if @compare ~= system_mt.compare then
while entity[2] ~= nil do -- no need to process first entity as it is the special `head` entity skipAddLayers(@_skiplist)
if @compare(e, entity[2][1]) then
let nxt = entity[2]
entity[2] = { e, nxt }
@_previous[e] = entity
@_previous[nxt[1]] = entity[2]
break
end
entity = entity[2]
end
if entity[2] == nil then
entity[2] = { e, nil }
@_previous[e] = entity
end end
-- add to skip list
skipInsert(@_skiplist, @, e)
-- notify addition -- notify addition
@entityCount += 1 @entityCount += 1
@onAdd(e, e[@component]) @onAdd(e, e[@component])
-- add to subsystems (if it wasn't immediately removed in onAdd) -- add to subsystems (if it wasn't immediately removed in onAdd)
if @_previous[e] then if @_has[e] then
for _, s in ipairs(@systems) do for _, s in ipairs(@systems) do
s:add(e) s:add(e)
end end
@ -462,27 +581,21 @@ let system_mt = {
-- --
-- If you intend to call this on a subsystem instead of the world, please read the warning in `System:add`. -- If you intend to call this on a subsystem instead of the world, please read the warning in `System:add`.
-- --
-- Complexity: O(1) per system. -- Complexity: O(1) per unordered system, O(log2(entityCount)) per ordered system.
-- @tparam Entity e entity to remove -- @tparam Entity e entity to remove
-- @tparam Entity... ... other entities to remove -- @tparam Entity... ... other entities to remove
-- @treturn Entity,... `e,...` the function arguments -- @treturn Entity,... `e,...` the function arguments
remove = :(e, ...) remove = :(e, ...)
if e ~= nil then if e ~= nil then
if @_previous[e] then if @_has[e] then
-- remove from subsystems -- remove from subsystems
for _, s in ipairs(@systems) do for _, s in ipairs(@systems) do
s:remove(e) s:remove(e)
end end
end end
if @_previous[e] then -- recheck in case it was removed already from a subsystem onRemove callback if @_has[e] then -- recheck in case it was removed already from a subsystem onRemove callback
-- remove from linked list skipDelete(@_skiplist, e)
let prev = @_previous[e]
prev[2] = prev[2][2]
if prev[2] then
@_previous[prev[2][1]] = prev
end
-- notify removal -- notify removal
@_previous[e] = nil
@entityCount -= 1 @entityCount -= 1
@onRemove(e, e[@component]) @onRemove(e, e[@component])
end end
@ -504,9 +617,9 @@ let system_mt = {
-- @treturn Entity,... `e,...` the function arguments -- @treturn Entity,... `e,...` the function arguments
refresh = :(e, ...) refresh = :(e, ...)
if e ~= nil then if e ~= nil then
if not @_previous[e] then if not @_has[e] then
@add(e) @add(e)
elseif @_previous[e] then else
if not @filter(e) then if not @filter(e) then
@remove(e) @remove(e)
else else
@ -527,35 +640,13 @@ let system_mt = {
-- Will recalculate the entity position in the entity list for this system and its subsystems. -- Will recalculate the entity position in the entity list for this system and its subsystems.
-- Will skip entities that are not in the system. -- Will skip entities that are not in the system.
-- --
-- Complexity: O(entityCount) per system. -- Complexity: O(1) per unordered system, O(log2(entityCount)) per ordered system.
-- @tparam Entity e entity to reorder -- @tparam Entity e entity to reorder
-- @tparam Entity... ... other entities to reorder -- @tparam Entity... ... other entities to reorder
-- @treturn Entity,... `e,...` the function arguments -- @treturn Entity,... `e,...` the function arguments
reorder = :(e, ...) reorder = :(e, ...)
if e ~= nil and @_previous[e] then if e ~= nil and @_has[e] then
let prev = @_previous[e] -- { prev, { e, next } } skipReorder(@_skiplist, @, e)
let next = prev[2][2]
-- remove e from linked list
prev[2] = next
if next then
@_previous[next[1]] = prev
end
-- find position so that prev < e <= next
while prev[1] ~= head and @compare(e, prev[1]) do -- ensure prev < e
next = prev
prev = @_previous[prev[1]]
end
while next ~= nil and not @compare(e, next[1]) do -- ensure e <= next
prev = next
next = next[2]
end
-- reinsert e in linked list
let new = { e, next }
@_previous[e] = prev
if next then
@_previous[next[1]] = new
end
prev[2] = new
-- Reorder in subsystems -- Reorder in subsystems
for _, s in ipairs(@systems) do for _, s in ipairs(@systems) do
s:reorder(e) s:reorder(e)
@ -574,7 +665,7 @@ let system_mt = {
-- @tparam Entity... ... other entities that may be in the system -- @tparam Entity... ... other entities that may be in the system
-- @treturn boolean `true` if every entity is in the system -- @treturn boolean `true` if every entity is in the system
has = :(e, ...) has = :(e, ...)
let has = e == nil or not not @_previous[e] let has = e == nil or not not @_has[e]
if ... then if ... then
return has and @has(...) return has and @has(...)
else else
@ -582,28 +673,25 @@ let system_mt = {
end end
end, end,
--- Returns an iterator that iterate through the entties in this system, in order. --- Returns an iterator that iterate through the entties in this system, in order.
--
-- Complexity: O(1) per iteration; O(entityCount) for the full iteration
-- @treturn iterator iterator over the entities in this system -- @treturn iterator iterator over the entities in this system
iter = :() iter = :()
return nextEntity, { @_first[2] } return skipIter(@_skiplist)
end, end,
--- Get the `i`th entity in the system. --- Get the `i`th entity in the system.
-- This is a simple wrapper around `iter`; it _will_ iterate over all the entities in the system in order until we reach the desired one. --
-- Complexity: O(i) -- Complexity: O(i)
-- @tparam number i the index of the entity -- @tparam number i the index of the entity
-- @treturn Entity the entity; `nil` if there is no such entity in the system -- @treturn Entity the entity; `nil` if there is no such entity in the system
get = :(i) get = :(i)
local n = 1 return skipIndex(@_skiplist, i)
for e in @iter() do
if n == i then
return e
end
n += 1
end
return nil
end, end,
--- Remove every entity from the system and its subsystems. --- Remove every entity from the system and its subsystems.
--
-- Complexity: O(entityCount) per system
clear = :() clear = :()
for e in @iter() do for e in skipIter(@_skiplist) do
@remove(e) @remove(e)
end end
for _, s in ipairs(@systems) do for _, s in ipairs(@systems) do
@ -613,6 +701,8 @@ let system_mt = {
--- Try to update the system and its subsystems. Should be called on every game update. --- Try to update the system and its subsystems. Should be called on every game update.
-- --
-- Subsystems are updated after their parent system. -- Subsystems are updated after their parent system.
--
-- Complexity: O(entityCount) per system if system:process is defined; O(1) per system otherwise.
-- @number dt delta-time since last update -- @number dt delta-time since last update
update = :(dt) update = :(dt)
if @active then if @active then
@ -624,7 +714,7 @@ let system_mt = {
end end
@onUpdate(dt) @onUpdate(dt)
if @process ~= system_mt.process then if @process ~= system_mt.process then
for e in @iter() do for e in skipIter(@_skiplist) do
@process(e, e[@component], dt) @process(e, e[@component], dt)
end end
end end
@ -640,11 +730,13 @@ let system_mt = {
--- Try to draw the system and its subsystems. Should be called on every game draw. --- Try to draw the system and its subsystems. Should be called on every game draw.
-- --
-- Subsystems are drawn after their parent system. -- Subsystems are drawn after their parent system.
--
-- -- Complexity: O(entityCount) per system if system:render is defined; O(1) per system otherwise.
draw = :() draw = :()
if @visible then if @visible then
@onDraw() @onDraw()
if @render ~= system_mt.render then if @render ~= system_mt.render then
for e in @iter() do for e in skipIter(@_skiplist) do
@render(e, e[@component]) @render(e, e[@component])
end end
end end
@ -661,16 +753,18 @@ let system_mt = {
-- associated with the current system, and `e` is the `Entity`. -- associated with the current system, and `e` is the `Entity`.
-- --
-- Think of it as a way to perform custom callbacks issued from an entity event, similar to `System:onAdd`. -- Think of it as a way to perform custom callbacks issued from an entity event, similar to `System:onAdd`.
--
-- Complexity: O(1) per system
-- @tparam string name name of the callback -- @tparam string name name of the callback
-- @tparam Entity e the entity to perform the callback on -- @tparam Entity e the entity to perform the callback on
-- @param ... other arguments to pass to the callback -- @param ... other arguments to pass to the callback
callback = :(name, e, ...) callback = :(name, e, ...)
-- call callback -- call callback
if @_previous[e] and @[name] then if @_has[e] and @[name] then
@[name](@, e, e[@component], ...) @[name](@, e, e[@component], ...)
end end
-- callback on subsystems (if it wasn't removed during the callback) -- callback on subsystems (if it wasn't removed during the callback)
if @_previous[e] then if @_has[e] then
for _, ss in ipairs(@systems) do for _, ss in ipairs(@systems) do
ss:callback(name, e, ...) ss:callback(name, e, ...)
end end
@ -695,6 +789,7 @@ let system_mt = {
-- `"capture"` would be for example used to prevent other systems from handling the event (for example to make sure an -- `"capture"` would be for example used to prevent other systems from handling the event (for example to make sure an
-- input event is handled only once by a single system). -- input event is handled only once by a single system).
-- --
-- Complexity: O(1) per system
-- @tparam string name name of the callback -- @tparam string name name of the callback
-- @param ... other arguments to pass to the callback -- @param ... other arguments to pass to the callback
emit = :(name, ...) emit = :(name, ...)
@ -713,6 +808,7 @@ let system_mt = {
return status return status
end, end,
--- Remove all the entities and subsystems in this system. --- Remove all the entities and subsystems in this system.
-- Complexity: O(entityCount) per system
destroy = :() destroy = :()
recCallOnRemoveFromWorld(@world, { @ }) recCallOnRemoveFromWorld(@world, { @ })
recDestroySystems({ systems = { @ } }) recDestroySystems({ systems = { @ } })
@ -736,8 +832,7 @@ let recInstanciateSystems = (world, systems)
world = world, world = world,
w = world, w = world,
s = world.s, s = world.s,
_first = { head }, _skiplist = skipNew()
_previous = {},
}, { }, {
__index = :(k) __index = :(k)
if s[k] ~= nil then if s[k] ~= nil then
@ -747,6 +842,7 @@ let recInstanciateSystems = (world, systems)
end end
end end
}) })
system._has = system._skiplist.previous[1]
-- create filter -- create filter
if type(s.filter) == "string" then if type(s.filter) == "string" then
system.filter = (_, e) return e[s.filter] ~= nil end system.filter = (_, e) return e[s.filter] ~= nil end
@ -791,9 +887,9 @@ ecs = {
let world = setmetatable({ let world = setmetatable({
filter = ecs.all(), filter = ecs.all(),
s = {}, s = {},
_first = { head }, _skiplist = skipNew(),
_previous = {}
}, { __index = system_mt }) }, { __index = system_mt })
world._has = world._skiplist.previous[1]
world.world = world world.world = world
world.w = world world.w = world
world.systems = recInstanciateSystems(world, {...}) world.systems = recInstanciateSystems(world, {...})

File diff suppressed because it is too large Load diff