From 33b34f3efb5b713319458a9c19b29a69a4a469e4 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Thu, 15 May 2025 07:16:47 +0700 Subject: [PATCH 01/16] manual wip --- MANUAL.md | 125 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 MANUAL.md diff --git a/MANUAL.md b/MANUAL.md new file mode 100644 index 0000000..f58ed3d --- /dev/null +++ b/MANUAL.md @@ -0,0 +1,125 @@ +# Manual + +## Identifiers + +Identifier is a packed 40-bit integer number. The first 20 bits are the index, and the last 20 bits are the version. To create a new identifier, use the `evolved.id` function. + +```lua +---@param count? integer +---@return evolved.id ... ids +function evolved.id(count) end +``` + +The `count` parameter is optional and defaults to `1`. The function returns one or more identifiers, depending on the `count` parameter. Maximum number of alive identifiers is `2^20-1` (1048575). After that, the function will throw an error `"| evolved.lua | id index overflow"`. + +Identifiers can be recycled. When an identifier is no longer needed, use the `evolved.destroy` function to destroy it. This will free up the identifier for reuse. + +```lua +---@param ... evolved.id ids +function evolved.destroy(...) end +``` + +The `destroy` function takes one or more identifiers as arguments. Destroyed identifiers will be added to the recycler free list. It is safe to destroy identifiers that are not alive (the function will just ignore them). + +After destroying an identifier, it can be reused by calling the `evolved.id` function again. The new identifier will have the same index as the destroyed one, but a different version. The version is incremented each time an identifier is destroyed. This mechanism allows us to reuse indices and know if an identifier is alive or not. + +The `evolved.alive` function set can be used to check if identifiers are alive or not. + +```lua +---@param id evolved.id +---@return boolean +function evolved.alive(id) end + +---@param ... evolved.id ids +---@return boolean +function evolved.alive_all(...) end + +---@param ... evolved.id ids +---@return boolean +function evolved.alive_any(...) end +``` + +Sometimes (for debugging purposes for example), it is necessary to extract the index and version from an identifier (or pack them back). The `evolved.pack` and `evolved.unpack` functions can be used for this purpose. + +```lua +---@param index integer +---@param version integer +---@return evolved.id id +function evolved.pack(index, version) end + +---@param id evolved.id +---@return integer index +---@return integer version +function evolved.unpack(id) end +``` + +Here is an little example of how to use identifiers: + +```lua +local evolved = require 'evolved' + +local id = evolved.id() -- create a new identifier +assert(evolved.alive(id)) -- check if the identifier is alive + +local index, version = evolved.unpack(id) -- unpack the identifier +assert(evolved.pack(index, version) == id) -- pack it back + +evolved.destroy(id) -- destroy the identifier +assert(not evolved.alive(id)) -- check if the identifier is not alive now +``` + +## Entities, Fragments, and Components + +First of all, we need to understand that entities and fragments are just identifiers. The differences between them are purely semantic. Entities are used to represent objects in the world, while fragments are used to represent types of components that can be attached to entities. Components, in the other hand, are any data that is attached to entities through fragments. + +Here is a simple example of how to attach a component to an entity: + +```lua +local evolved = require 'evolved' + +local entity, fragment = evolved.id(2) + +evolved.set(entity, fragment, 100) +assert(evolved.get(entity, fragment) == 100) +``` + +Yeah, I know, it's not very clear yet. But don't worry, we'll get there. In the next example, I'm going to name the entity and fragment, so it will be easier to understand what's going on here. + +```lua +local evolved = require 'evolved' + +local player = evolved.id() + +local health = evolved.id() +local stamina = evolved.id() + +evolved.set(player, health, 100) +evolved.set(player, stamina, 50) + +assert(evolved.get(player, health) == 100) +assert(evolved.get(player, stamina) == 50) +``` + +We have created an entity called `player` and two fragments called `health` and `stamina`. We have attached the components `100` and `50` to the entity through our fragments. After that, we can retrieve the components using the `evolved.get` function. + +We'll cover the `evolved.set` and `evolved.get` functions in more detail later in the section about modifying operations. For now, let's just say that they are used to setting and getting components from entities through fragments. + +The main thing to understand here is that we can attach any data to any identifiers using another identifiers. And yes, since fragments are just identifiers, we can use them as entities too! This very useful for marking fragments with some metadata, for example. + +```lua +local evolved = require 'evolved' + +local serializable = evolved.id() + +local position = evolved.id() +evolved.set(position, serializable, true) + +local velocity = evolved.id() +evolved.set(velocity, serializable, true) + +local player = evolved.id() +evolved.set(player, position, {x = 0, y = 0}) +evolved.set(player, velocity, {x = 0, y = 0}) +``` + +In this example, we have created a fragment called `serializable` and marked the fragments `position` and `velocity` as serializable. After that, we can write a function that will serialize entities. And this function will serialize only fragments that are marked as serializable. This is a very powerful feature of the library, and it allows us to create very flexible systems. Btw, fragments of fragments are usually called `traits`. From 0b95be99fb604b52abf48a1dd045edc1ec369575 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Thu, 15 May 2025 14:38:44 +0700 Subject: [PATCH 02/16] manual wip --- MANUAL.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/MANUAL.md b/MANUAL.md index f58ed3d..f338354 100644 --- a/MANUAL.md +++ b/MANUAL.md @@ -2,7 +2,7 @@ ## Identifiers -Identifier is a packed 40-bit integer number. The first 20 bits are the index, and the last 20 bits are the version. To create a new identifier, use the `evolved.id` function. +An identifier is a packed 40-bit integer. The first 20 bits represent the index, and the last 20 bits represent the version. To create a new identifier, use the `evolved.id` function. ```lua ---@param count? integer @@ -10,7 +10,7 @@ Identifier is a packed 40-bit integer number. The first 20 bits are the index, a function evolved.id(count) end ``` -The `count` parameter is optional and defaults to `1`. The function returns one or more identifiers, depending on the `count` parameter. Maximum number of alive identifiers is `2^20-1` (1048575). After that, the function will throw an error `"| evolved.lua | id index overflow"`. +The `count` parameter is optional and defaults to `1`. The function returns one or more identifiers, depending on the `count` parameter. The maximum number of alive identifiers is `2^20-1` (1048575). After that, the function will throw an error: `| evolved.lua | id index overflow`. Identifiers can be recycled. When an identifier is no longer needed, use the `evolved.destroy` function to destroy it. This will free up the identifier for reuse. @@ -19,11 +19,11 @@ Identifiers can be recycled. When an identifier is no longer needed, use the `ev function evolved.destroy(...) end ``` -The `destroy` function takes one or more identifiers as arguments. Destroyed identifiers will be added to the recycler free list. It is safe to destroy identifiers that are not alive (the function will just ignore them). +The `evolved.destroy` function takes one or more identifiers as arguments. Destroyed identifiers will be added to the recycler free list. It is safe to call `evolved.destroy` on identifiers that are not alive; the function will simply ignore them. -After destroying an identifier, it can be reused by calling the `evolved.id` function again. The new identifier will have the same index as the destroyed one, but a different version. The version is incremented each time an identifier is destroyed. This mechanism allows us to reuse indices and know if an identifier is alive or not. +After destroying an identifier, it can be reused by calling the `evolved.id` function again. The new identifier will have the same index as the destroyed one, but a different version. The version is incremented each time an identifier is destroyed. This mechanism allows us to reuse indices and to know whether an identifier is alive or not. -The `evolved.alive` function set can be used to check if identifiers are alive or not. +The set of `evolved.alive` functions can be used to check whether identifiers are alive. ```lua ---@param id evolved.id @@ -39,7 +39,7 @@ function evolved.alive_all(...) end function evolved.alive_any(...) end ``` -Sometimes (for debugging purposes for example), it is necessary to extract the index and version from an identifier (or pack them back). The `evolved.pack` and `evolved.unpack` functions can be used for this purpose. +Sometimes (for debugging purposes, for example), it is necessary to extract the index and version from an identifier, or to pack them back into an identifier. The `evolved.pack` and `evolved.unpack` functions can be used for this purpose. ```lua ---@param index integer @@ -53,24 +53,24 @@ function evolved.pack(index, version) end function evolved.unpack(id) end ``` -Here is an little example of how to use identifiers: +Here is a short example of how to use identifiers: ```lua local evolved = require 'evolved' local id = evolved.id() -- create a new identifier -assert(evolved.alive(id)) -- check if the identifier is alive +assert(evolved.alive(id)) -- check that the identifier is alive local index, version = evolved.unpack(id) -- unpack the identifier assert(evolved.pack(index, version) == id) -- pack it back evolved.destroy(id) -- destroy the identifier -assert(not evolved.alive(id)) -- check if the identifier is not alive now +assert(not evolved.alive(id)) -- check that the identifier is not alive now ``` ## Entities, Fragments, and Components -First of all, we need to understand that entities and fragments are just identifiers. The differences between them are purely semantic. Entities are used to represent objects in the world, while fragments are used to represent types of components that can be attached to entities. Components, in the other hand, are any data that is attached to entities through fragments. +First, we need to understand that entities and fragments are just identifiers. The difference between them is purely semantic. Entities are used to represent objects in the world, while fragments are used to represent types of components that can be attached to entities. Components, on the other hand, are any data that is attached to entities through fragments. Here is a simple example of how to attach a component to an entity: @@ -83,7 +83,7 @@ evolved.set(entity, fragment, 100) assert(evolved.get(entity, fragment) == 100) ``` -Yeah, I know, it's not very clear yet. But don't worry, we'll get there. In the next example, I'm going to name the entity and fragment, so it will be easier to understand what's going on here. +I know it's not very clear yet, but don't worry, we'll get there. In the next example, I'm going to name the entity and fragment, so it will be easier to understand what's going on here. ```lua local evolved = require 'evolved' @@ -100,11 +100,11 @@ assert(evolved.get(player, health) == 100) assert(evolved.get(player, stamina) == 50) ``` -We have created an entity called `player` and two fragments called `health` and `stamina`. We have attached the components `100` and `50` to the entity through our fragments. After that, we can retrieve the components using the `evolved.get` function. +We created an entity called `player` and two fragments called `health` and `stamina`. We attached the components `100` and `50` to the entity through these fragments. After that, we can retrieve the components using the `evolved.get` function. -We'll cover the `evolved.set` and `evolved.get` functions in more detail later in the section about modifying operations. For now, let's just say that they are used to setting and getting components from entities through fragments. +We'll cover the `evolved.set` and `evolved.get` functions in more detail later in the section about modifying operations. For now, let's just say that they are used to set and get components from entities through fragments. -The main thing to understand here is that we can attach any data to any identifiers using another identifiers. And yes, since fragments are just identifiers, we can use them as entities too! This very useful for marking fragments with some metadata, for example. +The main thing to understand here is that we can attach any data to any identifier using other identifiers. And yes, since fragments are just identifiers, we can use them as entities too! This is very useful for marking fragments with some metadata, for example. ```lua local evolved = require 'evolved' @@ -122,4 +122,4 @@ evolved.set(player, position, {x = 0, y = 0}) evolved.set(player, velocity, {x = 0, y = 0}) ``` -In this example, we have created a fragment called `serializable` and marked the fragments `position` and `velocity` as serializable. After that, we can write a function that will serialize entities. And this function will serialize only fragments that are marked as serializable. This is a very powerful feature of the library, and it allows us to create very flexible systems. Btw, fragments of fragments are usually called `traits`. +In this example, we created a fragment called `serializable` and marked the fragments `position` and `velocity` as serializable. After that, we can write a function that will serialize entities, and this function will serialize only fragments that are marked as serializable. This is a very powerful feature of the library, and it allows us to create very flexible systems. By the way, fragments of fragments are usually called `traits`. From 27bc31bd1c21aeabde33ce4bf06950ffbdb29d36 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Thu, 15 May 2025 17:03:37 +0700 Subject: [PATCH 03/16] manual wip --- MANUAL.md | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/MANUAL.md b/MANUAL.md index f338354..a5042f7 100644 --- a/MANUAL.md +++ b/MANUAL.md @@ -104,7 +104,11 @@ We created an entity called `player` and two fragments called `health` and `stam We'll cover the `evolved.set` and `evolved.get` functions in more detail later in the section about modifying operations. For now, let's just say that they are used to set and get components from entities through fragments. -The main thing to understand here is that we can attach any data to any identifier using other identifiers. And yes, since fragments are just identifiers, we can use them as entities too! This is very useful for marking fragments with some metadata, for example. +The main thing to understand here is that we can attach any data to any identifier using other identifiers. + +### Traits + +Since fragments are just identifiers, we can use them as entities too! Fragments of fragments are usually called `traits`. This is very useful for marking fragments with some metadata, for example. ```lua local evolved = require 'evolved' @@ -122,4 +126,17 @@ evolved.set(player, position, {x = 0, y = 0}) evolved.set(player, velocity, {x = 0, y = 0}) ``` -In this example, we created a fragment called `serializable` and marked the fragments `position` and `velocity` as serializable. After that, we can write a function that will serialize entities, and this function will serialize only fragments that are marked as serializable. This is a very powerful feature of the library, and it allows us to create very flexible systems. By the way, fragments of fragments are usually called `traits`. +In this example, we create a trait called `serializable` and mark the fragments `position` and `velocity` as serializable. After that, we can write a function that will serialize entities, and this function will serialize only fragments that are marked as serializable. This is a very powerful feature of the library, and it allows us to create very flexible systems. + +### Singletons + +Fragments can even be attached to themselves. This is called a singleton. Use this when you want to store some data without having a separate entity. For example, you can use it to store global data, like the game state or the current level. + +```lua +local evolved = require 'evolved' + +local gravity = evolved.id() +evolved.set(gravity, gravity, 10) + +assert(evolved.get(gravity, gravity) == 10) +``` From 36a5e6ac515364107914482199021a64d30601e2 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Fri, 16 May 2025 21:13:10 +0700 Subject: [PATCH 04/16] manual wip --- MANUAL.md | 175 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 167 insertions(+), 8 deletions(-) diff --git a/MANUAL.md b/MANUAL.md index a5042f7..019e1d0 100644 --- a/MANUAL.md +++ b/MANUAL.md @@ -10,9 +10,9 @@ An identifier is a packed 40-bit integer. The first 20 bits represent the index, function evolved.id(count) end ``` -The `count` parameter is optional and defaults to `1`. The function returns one or more identifiers, depending on the `count` parameter. The maximum number of alive identifiers is `2^20-1` (1048575). After that, the function will throw an error: `| evolved.lua | id index overflow`. +The `count` parameter is optional and defaults to `1`. The function returns one or more identifiers depending on the `count` parameter. The maximum number of alive identifiers is `2^20-1` (1048575). After that, the function will throw an error: `| evolved.lua | id index overflow`. -Identifiers can be recycled. When an identifier is no longer needed, use the `evolved.destroy` function to destroy it. This will free up the identifier for reuse. +Identifiers can be recycled. When an identifier is no longer needed, use the `evolved.destroy` function to destroy it. This will free the identifier for reuse. ```lua ---@param ... evolved.id ids @@ -39,7 +39,7 @@ function evolved.alive_all(...) end function evolved.alive_any(...) end ``` -Sometimes (for debugging purposes, for example), it is necessary to extract the index and version from an identifier, or to pack them back into an identifier. The `evolved.pack` and `evolved.unpack` functions can be used for this purpose. +Sometimes (for debugging purposes, for example), it is necessary to extract the index and version from an identifier or to pack them back into an identifier. The `evolved.pack` and `evolved.unpack` functions can be used for this purpose. ```lua ---@param index integer @@ -70,7 +70,13 @@ assert(not evolved.alive(id)) -- check that the identifier is not alive now ## Entities, Fragments, and Components -First, we need to understand that entities and fragments are just identifiers. The difference between them is purely semantic. Entities are used to represent objects in the world, while fragments are used to represent types of components that can be attached to entities. Components, on the other hand, are any data that is attached to entities through fragments. +First, you need to understand that entities and fragments are just identifiers. The difference between them is purely semantic. Entities are used to represent objects in the world, while fragments are used to represent types of components that can be attached to entities. Components, on the other hand, are any data that is attached to entities through fragments. + +```lua +---@alias evolved.entity evolved.id +---@alias evolved.fragment evolved.id +---@alias evolved.component any +``` Here is a simple example of how to attach a component to an entity: @@ -104,11 +110,11 @@ We created an entity called `player` and two fragments called `health` and `stam We'll cover the `evolved.set` and `evolved.get` functions in more detail later in the section about modifying operations. For now, let's just say that they are used to set and get components from entities through fragments. -The main thing to understand here is that we can attach any data to any identifier using other identifiers. +The main thing to understand here is that you can attach any data to any identifier by using other identifiers. ### Traits -Since fragments are just identifiers, we can use them as entities too! Fragments of fragments are usually called `traits`. This is very useful for marking fragments with some metadata, for example. +Since fragments are just identifiers, you can use them as entities too! Fragments of fragments are usually called `traits`. This is very useful, for example, for marking fragments with some metadata. ```lua local evolved = require 'evolved' @@ -126,11 +132,11 @@ evolved.set(player, position, {x = 0, y = 0}) evolved.set(player, velocity, {x = 0, y = 0}) ``` -In this example, we create a trait called `serializable` and mark the fragments `position` and `velocity` as serializable. After that, we can write a function that will serialize entities, and this function will serialize only fragments that are marked as serializable. This is a very powerful feature of the library, and it allows us to create very flexible systems. +In this example, we create a trait called `serializable` and mark the fragments `position` and `velocity` as serializable. After that, you can write a function that will serialize entities, and this function will serialize only fragments that are marked as serializable. This is a very powerful feature of the library, and it allows you to create very flexible systems. ### Singletons -Fragments can even be attached to themselves. This is called a singleton. Use this when you want to store some data without having a separate entity. For example, you can use it to store global data, like the game state or the current level. +Fragments can even be attached to themselves; this is called a singleton. Use this when you want to store some data without having a separate entity. For example, you can use it to store global data, like the game state or the current level. ```lua local evolved = require 'evolved' @@ -140,3 +146,156 @@ evolved.set(gravity, gravity, 10) assert(evolved.get(gravity, gravity) == 10) ``` + +## Chunks + +The next thing we need to understand is that all non-empty entities are stored in chunks. Chunks are just tables that store entities and their components together. Each unique combination of fragments is stored in a separate chunk. This means that if you have two entities with the same fragments, they will be stored in the `` chunk. If you have another entity with the fragments `health`, `stamina`, and `mana`, it will be stored in the `` chunk. This is very useful for performance reasons, as it allows us to store entities with the same fragments together, making it easier to iterate, filter, and process them. + +```lua +local evolved = require 'evolved' + +local health, stamina, mana = evolved.id(3) + +local entity1 = evolved.id() +evolved.set(entity1, health, 100) +evolved.set(entity1, stamina, 50) + +local entity2 = evolved.id() +evolved.set(entity2, health, 75) +evolved.set(entity2, stamina, 40) + +local entity3 = evolved.id() +evolved.set(entity3, health, 50) +evolved.set(entity3, stamina, 30) +evolved.set(entity3, mana, 20) +``` + +Here is what the chunks will look like after the code above has executed: + +| chunk | health | stamina | +| ------- | :----: | :-----: | +| entity1 | 100 | 50 | +| entity2 | 75 | 40 | + +| chunk | health | stamina | mana | +| ------- | :----: | :-----: | :---: | +| entity3 | 50 | 30 | 20 | + +Usually, you don't need to operate on chunks directly, but you can use the `evolved.chunk` function to get the specific chunk. + +```lua +---@param fragment evolved.fragment +---@param ... evolved.fragment fragments +---@return evolved.chunk chunk +function evolved.chunk(fragment, ...) end +``` + +The `evolved.chunk` function takes one or more fragments as arguments and returns the chunk for this combination. After that, you can use the chunk's methods to retrieve their entities, fragments, and components. + +```lua +---@param self evolved.chunk +---@return evolved.entity[] entity_list +---@return integer entity_count +function chunk_mt:entities() end + +---@param self evolved.chunk +---@return evolved.fragment[] fragment_list +---@return integer fragment_count +function chunk_mt:fragments() end + +---@param self evolved.chunk +---@param ... evolved.fragment fragments +---@return evolved.component[] ... component_lists +function chunk_mt:components(...) end +``` + +Full example: + +```lua +local evolved = require 'evolved' + +local health, stamina, mana = evolved.id(3) + +local entity1 = evolved.id() +evolved.set(entity1, health, 100) +evolved.set(entity1, stamina, 50) + +local entity2 = evolved.id() +evolved.set(entity2, health, 75) +evolved.set(entity2, stamina, 40) + +local entity3 = evolved.id() +evolved.set(entity3, health, 50) +evolved.set(entity3, stamina, 30) +evolved.set(entity3, mana, 20) + +-- get (or create if it doesn't exist) the chunk +local chunk = evolved.chunk(health, stamina) + +-- get the list of entities in the chunk and the number of them +local entity_list, entity_count = chunk:entities() + +-- get the columns of components in the chunk +local health_components = chunk:components(health) +local stamina_components = chunk:components(stamina) + +for i = 1, entity_count do + local entity = entity_list[i] + + local entity_health = health_components[i] + local entity_stamina = stamina_components[i] + + -- do something with the entity and its components + print(string.format( + 'Entity: %d, Health: %d, Stamina: %d', + entity, entity_health, entity_stamina)) +end + +-- Expected output: +-- Entity: 1048602, Health: 100, Stamina: 50 +-- Entity: 1048603, Health: 75, Stamina: 40 +``` + +## Structural Changes + +Every time we add or remove a fragment from an entity, the entity will be migrated to a new chunk. This is done automatically by the library, of course. However, you should be aware of this because it can affect performance, especially if you have many fragments on the entity. This is called a `structural change`. + +You should try to avoid structural changes, especially in performance-critical code. For example, you can spawn entities with all the fragments they will ever need and avoid changing them during the entity's lifetime. Overriding existing components is not a structural change, so you can do it freely. + +```lua +---@param components? table +---@return evolved.entity +function evolved.spawn(components) end + +---@param prefab evolved.entity +---@param components? table +---@return evolved.entity +function evolved.clone(prefab, components) end +``` + +The `evolved.spawn` function allows you to spawn an entity with all the necessary fragments. It takes a table of components as an argument, where the keys are fragments and the values are components. By the way, you don't need to create this `components` table every time; consider using a predefined table for maximum performance. + +You can also use the `evolved.clone` function to clone an existing entity. This is useful for creating entities with the same fragments as an existing entity but with different components. + +```lua +local evolved = require 'evolved' + +local health, stamina = evolved.id(2) + +-- spawn an entity with all the necessary fragments +local enemy1 = evolved.spawn { + [health] = 100, + [stamina] = 50, +} + +-- spawn another entity with the same fragments, +-- but with a different component for some of them +local enemy2 = evolved.clone(enemy1, { + [health] = 50, +}) + +-- there are no structural changes here, +-- we just override existing components +evolved.set(enemy1, health, 75) +evolved.set(enemy1, stamina, 42) +``` From 508bc57c670f94b488080ebe454880a77ec78574 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Sat, 17 May 2025 01:02:44 +0700 Subject: [PATCH 05/16] manual wip --- MANUAL.md | 110 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 108 insertions(+), 2 deletions(-) diff --git a/MANUAL.md b/MANUAL.md index 019e1d0..521dc2f 100644 --- a/MANUAL.md +++ b/MANUAL.md @@ -108,7 +108,7 @@ assert(evolved.get(player, stamina) == 50) We created an entity called `player` and two fragments called `health` and `stamina`. We attached the components `100` and `50` to the entity through these fragments. After that, we can retrieve the components using the `evolved.get` function. -We'll cover the `evolved.set` and `evolved.get` functions in more detail later in the section about modifying operations. For now, let's just say that they are used to set and get components from entities through fragments. +We'll cover the `evolved.set` and `evolved.get` functions in more detail later in the section about [modifying operations](#modifying-operations). For now, let's just say that they are used to set and get components from entities through fragments. The main thing to understand here is that you can attach any data to any identifier by using other identifiers. @@ -149,7 +149,7 @@ assert(evolved.get(gravity, gravity) == 10) ## Chunks -The next thing we need to understand is that all non-empty entities are stored in chunks. Chunks are just tables that store entities and their components together. Each unique combination of fragments is stored in a separate chunk. This means that if you have two entities with the same fragments, they will be stored in the `` chunk. If you have another entity with the fragments `health`, `stamina`, and `mana`, it will be stored in the `` chunk. This is very useful for performance reasons, as it allows us to store entities with the same fragments together, making it easier to iterate, filter, and process them. +The next thing we need to understand is that all non-empty entities are stored in chunks. Chunks are just tables that store entities and their components together. Each unique combination of fragments is stored in a separate chunk. This means that if you have two entities with `health` and `stamina` fragments, they will be stored in the `` chunk. If you have another entity with `health`, `stamina`, and `mana` fragments, it will be stored in the `` chunk. This is very useful for performance reasons, as it allows us to store entities with the same fragments together, making it easier to iterate, filter, and process them. ```lua local evolved = require 'evolved' @@ -262,6 +262,8 @@ Every time we add or remove a fragment from an entity, the entity will be migrat You should try to avoid structural changes, especially in performance-critical code. For example, you can spawn entities with all the fragments they will ever need and avoid changing them during the entity's lifetime. Overriding existing components is not a structural change, so you can do it freely. +### Spawning Entities + ```lua ---@param components? table ---@return evolved.entity @@ -299,3 +301,107 @@ local enemy2 = evolved.clone(enemy1, { evolved.set(enemy1, health, 75) evolved.set(enemy1, stamina, 42) ``` + +### Entity Builders + +Another way to avoid structural changes when spawning entities is to use the `evolved.builder` fluid interface. The `evolved.builder` function returns a builder object that allows you to spawn entities with a specific set of fragments and components without necessity setting them one by one with structural changes for each change. + +```lua +local evolved = require 'evolved' + +local health, stamina = evolved.id(2) + +local enemy = evolved.builder() + :set(health, 100) + :set(stamina, 50) + :spawn() +``` + +Builders can be reused, so you can create a builder with a specific set of fragments and components and then use it to spawn multiple entities with the same fragments and components. + +## Access Operations + +The library provides all the necessary functions to access entities and their components. I'm not going to cover all the accessor functions here, because they are pretty straightforward and self-explanatory. You can check the [API Reference](#api-reference) for all of them. Here are some of the most important ones: + +```lua +---@param entity evolved.entity +---@return boolean +function evolved.alive(entity) end + +---@param entity evolved.entity +---@return boolean +function evolved.empty(entity) end + +---@param entity evolved.entity +---@param fragment evolved.fragment +function evolved.has(entity, fragment) end + +---@param entity evolved.entity +---@param ... evolved.fragment fragments +---@return evolved.component ... components +function evolved.get(entity, ...) end +``` + +The `evolved.alive` function checks whether an entity is alive. The `evolved.empty` function checks whether an entity is empty (has no fragments). The `evolved.has` function checks whether an entity has a specific fragment. The `evolved.get` function retrieves the components of an entity for the specified fragments. If the entity doesn't have some of the fragments, the function will return `nil` for them. + +All of these functions can be safely called on non-alive entities and non-alive fragments. Also, they do not cause any structural changes, because they do not modify anything. + +## Modifying Operations + +The library provides a classic set of functions for modifying entities. These functions are used to set, get, remove, and check fragments on entities. + +```lua +---@param entity evolved.entity +---@param fragment evolved.fragment +---@param component evolved.component +function evolved.set(entity, fragment, component) end + +---@param entity evolved.entity +---@param ... evolved.fragment fragments +function evolved.remove(entity, ...) + +---@param ... evolved.entity entities +function evolved.clear(...) + +---@param ... evolved.entity entities +function evolved.destroy(...) +``` + +The `evolved.set` function is used to set a component on an entity. If the entity doesn't have this fragment, it will be added, with causing a structural change, of course. If the entity already has the fragment, the component will be overridden. The function should not be called on non-alive entities, because it is not possible to set any component on a destroyed entity, ignoring this can lead to errors. [Debug Mode](#debug-mode) can be used to check this kind of error. + +Use the `evolved.remove` function to remove fragments from an entity. If the entity doesn't have some of the fragments, they will be ignored. When one or more fragments are removed from an entity, the entity will be migrated to a new chunk, which is a structural change. When you want to remove more than one fragment, pass all of them as arguments. Do not remove fragments one by one, as this will cause a structural change for each fragment. The `evolved.remove` function will ignore non-alive entities, because post-conditions are satisfied (destroyed entities do not have any fragments, including those that we want to remove). + +To remove all fragments from an entity, use the `evolved.clear` function. This function will remove all fragments at once, with causing only one structural change. The `evolved.clear` function does not destroy the entity, it just removes all fragments from it. The entity after this operation will be empty, but it will be still alive. You can use this function to clear more than one entity at once, passing them as arguments. The function will ignore empty and non-alive entities. + +To destroy an entity, use the `evolved.destroy` function. This function will remove all fragments from the entity and free the identifier of the entity for reuse. The `evolved.destroy` function will ignore non-alive entities. To destroy more than one entity, pass them as arguments. + +## Debug Mode + +The library has a debug mode that can be enabled by the `evolved.debug_mode` function. When the debug mode is enabled, the library will check for incorrect usages of the API and throw errors when they are detected. This is very useful for debugging and development, but it can slow down performance a bit. + +```lua +---@param yesno boolean +function evolved.debug_mode(yesno) end +``` + +The debug mode is disabled by default, so you need to enable it manually. I strongly recommend doing this in the development environment. You can even leave it enabled in production, but only if you are sure the performance is acceptable for your case. + +```lua +local evolved = require 'evolved' + +evolved.debug_mode(true) + +local entity = evolved.id() + +local fragment = evolved.id() +evolved.destroy(fragment) + +-- try to use the destroyed fragment +evolved.set(entity, fragment, 42) + +-- [error] | evolved.lua | the fragment ($1048599#23:1) is not alive and cannot be used +``` + +## API Reference + +> coming soon... From 9d443abd2001497d2257ea3f4637ea41ac5ca545 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Sun, 18 May 2025 06:30:46 +0700 Subject: [PATCH 06/16] manual wip --- MANUAL.md | 139 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) diff --git a/MANUAL.md b/MANUAL.md index 521dc2f..4bfa80e 100644 --- a/MANUAL.md +++ b/MANUAL.md @@ -402,6 +402,145 @@ evolved.set(entity, fragment, 42) -- [error] | evolved.lua | the fragment ($1048599#23:1) is not alive and cannot be used ``` +## Queries + +One of the most important features of any ECS library is the ability to process entities by filters or queries. `evolved.lua` provides a simple and efficient way to do this. + +First, you need to create a query that describes which entities you want to process. You can specify fragments you want to include, and fragments you want to exclude. Queries are just identifiers with a special predefined fragments: `evolved.INCLUDES` and `evolved.EXCLUDES`. These fragments expect a list of fragments as their components. + +```lua +local evolved = require 'evolved' + +local health, poisoned, resistant = evolved.id(3) + +local query = evolved.id() +evolved.set(query, evolved.INCLUDES, { health, poisoned }) +evolved.set(query, evolved.EXCLUDES, { resistant }) +``` + +The builder interface can be used to create queries too. It is more convenient to use, because the builder has special methods for including and excluding fragments. Here is a simple example of this: + +```lua +local query = evolved.builder() + :include(health, poisoned) + :exclude(resistant) + :spawn() +``` + +We don't have to set both `evolved.INCLUDES` and `evolved.EXCLUDES` fragments, we can even do it without filters at all, then the query will match all chunks in the world. + +After the query is created, we are ready to process our filtered by this query entities. You can do this by using the `evolved.execute` function. This function takes a query as an argument and returns an iterator that can be used to iterate over all matching with the query chunks. + +```lua +---@param query evolved.query +---@return evolved.execute_iterator iterator +---@return evolved.execute_state? iterator_state +function evolved.execute(query) end +``` + +```lua +for chunk, entity_list, entity_count in evolved.execute(query) do + ---@type number[] + local health_components = chunk:components(health) + + for i = 1, entity_count do + health_components[i] = health_components[i] - 1 + end +end +``` + +As you can see, `evolved.execute_iterator` returns a chunk, a list of entities in the chunk, and the number of entities in this chunk. We [already know](#chunks) how to use chunks, so we can use the chunk's methods to retrieve the components of the entities in the chunk, change them, and so on. + +But I haven't mentioned one important thing yet: [structural changes](#structural-changes) are not allowed during any iteration over chunks. This means that you cannot add or remove fragments from entities while iterating. Also, you cannot destroy or spawn entities because this will cause structural changes too. This is done to avoid inconsistencies in the iteration process. If we allow structural changes here, we might skip some entities during iteration, or process the same entity multiple times. The [debug mode](#debug-mode) can catch this kind of error. + +### Deferred Operations + +Now we know that structural changes are not allowed during iteration, but what if we want to make some structural changes after the iteration is finished? For example, we might want to remove some fragments from entities after we have processed them, or we might want to spawn new entities while processing existing ones. To do all of this, we can use deferred operations. + +```lua +---@return boolean started +function evolved.defer() end + +---@return boolean committed +function evolved.commit() end +``` + +The `evolved.defer` function starts a deferred scope. This means that all changes made inside the scope will be queued and applied after leaving the scope. The `evolved.commit` function closes a last deferred scope and applies all queued changes. These functions can be nested, so you can start a new deferred scope inside an existing one. The `evolved.commit` function will apply all queued changes only when the last deferred scope is closed. + +```lua +local evolved = require 'evolved' + +local health, poisoned = evolved.id(2) + +local player = evolved.builder() + :set(health, 100) + :set(poisoned, true) + :spawn() + +-- start a deferred scope +evolved.defer() + +-- this removal will be queued, not applied immediately +evolved.remove(player, poisoned) + +-- the player still has the poisoned fragment inside the deferred scope +assert(evolved.has(player, poisoned)) + +-- commit the deferred operations +evolved.commit() + +-- now the poisoned fragment is removed +assert(not evolved.has(player, poisoned)) +``` + +### Batch Operations + +The library provides a set of functions for batch operations. These functions are used to perform modifying operations on multiple chunks at once. This is very useful for performance reasons. + +```lua +---@param query evolved.query +---@param fragment evolved.fragment +---@param component evolved.component +function evolved.batch_set(query, fragment, component) end + +---@param query evolved.query +---@param ... evolved.fragment fragments +function evolved.batch_remove(query, ...) end + +---@param ... evolved.query queries +function evolved.batch_clear(...) end + +---@param ... evolved.query queries +function evolved.batch_destroy(...) end +``` + +These functions are similar to the common [modifying operations](#modifying-operations), but they take a query as an argument instead of an entity. Here is a classic example that provides a huge performance boost when applied. + +```lua +local evolved = require 'evolved' + +local destroying_mark = evolved.id() + +local destroying_mark_query = evolved.builder() + :include(destroying_mark) + :spawn() + +-- destroy all entities with the destroying_mark fragment +evolved.batch_destroy(destroying_mark_query) +``` + +You should always prefer batch operations over common modifying operations when you need to perform a simple operation like destroying or removing fragments from multiple entities at once. Instead of applying the operation to each entity one by one, batch operations will apply the operation chunk by chunk. + +In all other respects, batch operations behave the same way as the common modifying operations that we have already covered. They can, of course, be used with [deferred operations](#deferred-operations) too. + +## Systems + +> coming soon... + +## Predefs + +> coming soon... + ## API Reference > coming soon... From f79778ee614f27d7c2213a947ba70093f19e0efb Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Tue, 20 May 2025 05:56:03 +0700 Subject: [PATCH 07/16] manual wip --- MANUAL.md | 107 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 104 insertions(+), 3 deletions(-) diff --git a/MANUAL.md b/MANUAL.md index 4bfa80e..80b6c87 100644 --- a/MANUAL.md +++ b/MANUAL.md @@ -529,13 +529,114 @@ local destroying_mark_query = evolved.builder() evolved.batch_destroy(destroying_mark_query) ``` -You should always prefer batch operations over common modifying operations when you need to perform a simple operation like destroying or removing fragments from multiple entities at once. Instead of applying the operation to each entity one by one, batch operations will apply the operation chunk by chunk. +You should always prefer batch operations over common modifying operations when you need to perform simple operations like destroying or removing fragments from multiple entities at once. Instead of applying the operation to each entity one by one, batch operations will apply the operation chunk by chunk. -In all other respects, batch operations behave the same way as the common modifying operations that we have already covered. They can, of course, be used with [deferred operations](#deferred-operations) too. +In all other respects, batch operations behave the same way as the common modifying operations that we have already covered. Of course, they can also be used with [deferred operations](#deferred-operations). ## Systems -> coming soon... +Usually, we want to organize our processing of entities into systems that will be executed in a specific order. The library has a way to do this using special `evolved.QUERY` and `evolved.EXECUTE` fragments that are used to specify the system's queries and execution callbacks. And yes, systems are just entities with special fragments. + +```lua +local evolved = require 'evolved' + +local health, max_health = evolved.id(2) + +local query = evolved.builder() + :include(health, max_health) + :spawn() + +local system = evolved.builder() + :query(query) + :execute(function(chunk, entity_list, entity_count) + local health_components = chunk:components(health) + local max_health_components = chunk:components(max_health) + + for i = 1, entity_count do + health_components[i] = math.min( + health_components[i] + 1, + max_health_components[i]) + end + end):spawn() +``` + +The `evolved.process` function is used to process systems. It takes systems as arguments and executes them in the order they were passed. + +```lua +---@param ... evolved.system systems +function evolved.process(...) end +``` + +To group systems together, you can use the `evolved.GROUP` fragment. Systems with a specified group will be processed when you call the `evolved.process` function with this group. For example, you can group all physics systems together and process them in one `evolved.process` call. + +```lua +local evolved = require 'evolved' + +local gravity_x = 0 +local gravity_y = -9.81 + +local position_x, position_y = evolved.id(2) +local velocity_x, velocity_y = evolved.id(2) + +local physical_body_query = evolved.builder() + :include(position_x, position_y) + :include(velocity_x, velocity_y) + :spawn() + +local physics_group = evolved.id() + +evolved.builder() + :group(physics_group) + :query(physical_body_query) + :execute(function(chunk, entity_list, entity_count) + local vx = chunk:components(velocity_x) + local vy = chunk:components(velocity_y) + + for i = 1, entity_count do + vx[i] = vx[i] + gravity_x + vy[i] = vy[i] + gravity_y + end + end):spawn() + +evolved.builder() + :group(physics_group) + :query(physical_body_query) + :execute(function(chunk, entity_list, entity_count) + local px = chunk:components(position_x) + local py = chunk:components(position_y) + + local vx = chunk:components(velocity_x) + local vy = chunk:components(velocity_y) + + for i = 1, entity_count do + px[i] = px[i] + vx[i] + py[i] = py[i] + vy[i] + end + end):spawn() + +evolved.process(physics_group) +``` + +Systems and groups also can have the `evolved.PROLOGUE` and `evolved.EPILOGUE` fragments. These fragments are used to specify callbacks that will be executed before and after the system or group is processed. This is useful for setting up and tearing down systems or groups, or for performing some additional processing before or after the main processing. + +```lua +local evolved = require 'evolved' + +local system = evolved.builder() + :prologue(function() + print('Prologue') + end) + :epilogue(function() + print('Epilogue') + end) + :spawn() + +evolved.process(system) +``` + +The prologue and epilogue fragments do not require an explicit query. They will be executed before and after the system is processed, regardless of the query. + +And one more thing about systems. Execution callbacks are called in the [deferred scope](#deferred-operations), this means that all modifying operations inside the callback will be queued and applied after the system processed all chunks. But prologue and epilogue callbacks are not called in the deferred scope, so all modifying operations inside them will be applied immediately. This is done to avoid confusion and to make it clear that prologue and epilogue callbacks are not part of the chunk processing. ## Predefs From 2105546eaabeea2b4332f8e033094f3fa87527a9 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Tue, 20 May 2025 06:27:40 +0700 Subject: [PATCH 08/16] manual wip --- MANUAL.md | 201 +++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 169 insertions(+), 32 deletions(-) diff --git a/MANUAL.md b/MANUAL.md index 80b6c87..4a55cb6 100644 --- a/MANUAL.md +++ b/MANUAL.md @@ -2,7 +2,7 @@ ## Identifiers -An identifier is a packed 40-bit integer. The first 20 bits represent the index, and the last 20 bits represent the version. To create a new identifier, use the `evolved.id` function. +An identifier is a packed 40-bit integer. The first 20 bits represent the index, and the last 20 bits represent the version. To create a new identifier, use the [`evolved.id`](#evolvedid) function. ```lua ---@param count? integer @@ -12,18 +12,18 @@ function evolved.id(count) end The `count` parameter is optional and defaults to `1`. The function returns one or more identifiers depending on the `count` parameter. The maximum number of alive identifiers is `2^20-1` (1048575). After that, the function will throw an error: `| evolved.lua | id index overflow`. -Identifiers can be recycled. When an identifier is no longer needed, use the `evolved.destroy` function to destroy it. This will free the identifier for reuse. +Identifiers can be recycled. When an identifier is no longer needed, use the [`evolved.destroy`](#evolveddestroy) function to destroy it. This will free the identifier for reuse. ```lua ---@param ... evolved.id ids function evolved.destroy(...) end ``` -The `evolved.destroy` function takes one or more identifiers as arguments. Destroyed identifiers will be added to the recycler free list. It is safe to call `evolved.destroy` on identifiers that are not alive; the function will simply ignore them. +The [`evolved.destroy`](#evolveddestroy) function takes one or more identifiers as arguments. Destroyed identifiers will be added to the recycler free list. It is safe to call [`evolved.destroy`](#evolveddestroy) on identifiers that are not alive; the function will simply ignore them. -After destroying an identifier, it can be reused by calling the `evolved.id` function again. The new identifier will have the same index as the destroyed one, but a different version. The version is incremented each time an identifier is destroyed. This mechanism allows us to reuse indices and to know whether an identifier is alive or not. +After destroying an identifier, it can be reused by calling the [`evolved.id`](#evolvedid) function again. The new identifier will have the same index as the destroyed one, but a different version. The version is incremented each time an identifier is destroyed. This mechanism allows us to reuse indices and to know whether an identifier is alive or not. -The set of `evolved.alive` functions can be used to check whether identifiers are alive. +The set of [`evolved.alive`](#evolvedalive) functions can be used to check whether identifiers are alive. ```lua ---@param id evolved.id @@ -39,7 +39,7 @@ function evolved.alive_all(...) end function evolved.alive_any(...) end ``` -Sometimes (for debugging purposes, for example), it is necessary to extract the index and version from an identifier or to pack them back into an identifier. The `evolved.pack` and `evolved.unpack` functions can be used for this purpose. +Sometimes (for debugging purposes, for example), it is necessary to extract the index and version from an identifier or to pack them back into an identifier. The [`evolved.pack`](#evolvedpack) and [`evolved.unpack`](#evolvedunpack) functions can be used for this purpose. ```lua ---@param index integer @@ -106,9 +106,9 @@ assert(evolved.get(player, health) == 100) assert(evolved.get(player, stamina) == 50) ``` -We created an entity called `player` and two fragments called `health` and `stamina`. We attached the components `100` and `50` to the entity through these fragments. After that, we can retrieve the components using the `evolved.get` function. +We created an entity called `player` and two fragments called `health` and `stamina`. We attached the components `100` and `50` to the entity through these fragments. After that, we can retrieve the components using the [`evolved.get`](#evolvedget) function. -We'll cover the `evolved.set` and `evolved.get` functions in more detail later in the section about [modifying operations](#modifying-operations). For now, let's just say that they are used to set and get components from entities through fragments. +We'll cover the [`evolved.set`](#evolvedset) and [`evolved.get`](#evolvedget) functions in more detail later in the section about [modifying operations](#modifying-operations). For now, let's just say that they are used to set and get components from entities through fragments. The main thing to understand here is that you can attach any data to any identifier by using other identifiers. @@ -181,7 +181,7 @@ Here is what the chunks will look like after the code above has executed: | ------- | :----: | :-----: | :---: | | entity3 | 50 | 30 | 20 | -Usually, you don't need to operate on chunks directly, but you can use the `evolved.chunk` function to get the specific chunk. +Usually, you don't need to operate on chunks directly, but you can use the [`evolved.chunk`](#evolvedchunk) function to get the specific chunk. ```lua ---@param fragment evolved.fragment @@ -190,7 +190,7 @@ Usually, you don't need to operate on chunks directly, but you can use the `evol function evolved.chunk(fragment, ...) end ``` -The `evolved.chunk` function takes one or more fragments as arguments and returns the chunk for this combination. After that, you can use the chunk's methods to retrieve their entities, fragments, and components. +The [`evolved.chunk`](#evolvedchunk) function takes one or more fragments as arguments and returns the chunk for this combination. After that, you can use the chunk's methods to retrieve their entities, fragments, and components. ```lua ---@param self evolved.chunk @@ -275,9 +275,9 @@ function evolved.spawn(components) end function evolved.clone(prefab, components) end ``` -The `evolved.spawn` function allows you to spawn an entity with all the necessary fragments. It takes a table of components as an argument, where the keys are fragments and the values are components. By the way, you don't need to create this `components` table every time; consider using a predefined table for maximum performance. +The [`evolved.spawn`](#evolvedspawn) function allows you to spawn an entity with all the necessary fragments. It takes a table of components as an argument, where the keys are fragments and the values are components. By the way, you don't need to create this `components` table every time; consider using a predefined table for maximum performance. -You can also use the `evolved.clone` function to clone an existing entity. This is useful for creating entities with the same fragments as an existing entity but with different components. +You can also use the [`evolved.clone`](#evolvedclone) function to clone an existing entity. This is useful for creating entities with the same fragments as an existing entity but with different components. ```lua local evolved = require 'evolved' @@ -304,7 +304,7 @@ evolved.set(enemy1, stamina, 42) ### Entity Builders -Another way to avoid structural changes when spawning entities is to use the `evolved.builder` fluid interface. The `evolved.builder` function returns a builder object that allows you to spawn entities with a specific set of fragments and components without necessity setting them one by one with structural changes for each change. +Another way to avoid structural changes when spawning entities is to use the [`evolved.builder`](#evolvedbuilder) fluid interface. The [`evolved.builder`](#evolvedbuilder) function returns a builder object that allows you to spawn entities with a specific set of fragments and components without necessity setting them one by one with structural changes for each change. ```lua local evolved = require 'evolved' @@ -342,7 +342,7 @@ function evolved.has(entity, fragment) end function evolved.get(entity, ...) end ``` -The `evolved.alive` function checks whether an entity is alive. The `evolved.empty` function checks whether an entity is empty (has no fragments). The `evolved.has` function checks whether an entity has a specific fragment. The `evolved.get` function retrieves the components of an entity for the specified fragments. If the entity doesn't have some of the fragments, the function will return `nil` for them. +The [`evolved.alive`](#evolvedalive) function checks whether an entity is alive. The [`evolved.empty`](#evolvedempty) function checks whether an entity is empty (has no fragments). The [`evolved.has`](#evolvedhas) function checks whether an entity has a specific fragment. The [`evolved.get`](#evolvedget) function retrieves the components of an entity for the specified fragments. If the entity doesn't have some of the fragments, the function will return `nil` for them. All of these functions can be safely called on non-alive entities and non-alive fragments. Also, they do not cause any structural changes, because they do not modify anything. @@ -367,17 +367,17 @@ function evolved.clear(...) function evolved.destroy(...) ``` -The `evolved.set` function is used to set a component on an entity. If the entity doesn't have this fragment, it will be added, with causing a structural change, of course. If the entity already has the fragment, the component will be overridden. The function should not be called on non-alive entities, because it is not possible to set any component on a destroyed entity, ignoring this can lead to errors. [Debug Mode](#debug-mode) can be used to check this kind of error. +The [`evolved.set`](#evolvedset) function is used to set a component on an entity. If the entity doesn't have this fragment, it will be added, with causing a structural change, of course. If the entity already has the fragment, the component will be overridden. The function should not be called on non-alive entities, because it is not possible to set any component on a destroyed entity, ignoring this can lead to errors. [Debug Mode](#debug-mode) can be used to check this kind of error. -Use the `evolved.remove` function to remove fragments from an entity. If the entity doesn't have some of the fragments, they will be ignored. When one or more fragments are removed from an entity, the entity will be migrated to a new chunk, which is a structural change. When you want to remove more than one fragment, pass all of them as arguments. Do not remove fragments one by one, as this will cause a structural change for each fragment. The `evolved.remove` function will ignore non-alive entities, because post-conditions are satisfied (destroyed entities do not have any fragments, including those that we want to remove). +Use the [`evolved.remove`](#evolvedremove) function to remove fragments from an entity. If the entity doesn't have some of the fragments, they will be ignored. When one or more fragments are removed from an entity, the entity will be migrated to a new chunk, which is a structural change. When you want to remove more than one fragment, pass all of them as arguments. Do not remove fragments one by one, as this will cause a structural change for each fragment. The [`evolved.remove`](#evolvedremove) function will ignore non-alive entities, because post-conditions are satisfied (destroyed entities do not have any fragments, including those that we want to remove). -To remove all fragments from an entity, use the `evolved.clear` function. This function will remove all fragments at once, with causing only one structural change. The `evolved.clear` function does not destroy the entity, it just removes all fragments from it. The entity after this operation will be empty, but it will be still alive. You can use this function to clear more than one entity at once, passing them as arguments. The function will ignore empty and non-alive entities. +To remove all fragments from an entity, use the [`evolved.clear`](#evolvedclear) function. This function will remove all fragments at once, with causing only one structural change. The [`evolved.clear`](#evolvedclear) function does not destroy the entity, it just removes all fragments from it. The entity after this operation will be empty, but it will be still alive. You can use this function to clear more than one entity at once, passing them as arguments. The function will ignore empty and non-alive entities. -To destroy an entity, use the `evolved.destroy` function. This function will remove all fragments from the entity and free the identifier of the entity for reuse. The `evolved.destroy` function will ignore non-alive entities. To destroy more than one entity, pass them as arguments. +To destroy an entity, use the [`evolved.destroy`](#evolveddestroy) function. This function will remove all fragments from the entity and free the identifier of the entity for reuse. The [`evolved.destroy`](#evolveddestroy) function will ignore non-alive entities. To destroy more than one entity, pass them as arguments. ## Debug Mode -The library has a debug mode that can be enabled by the `evolved.debug_mode` function. When the debug mode is enabled, the library will check for incorrect usages of the API and throw errors when they are detected. This is very useful for debugging and development, but it can slow down performance a bit. +The library has a debug mode that can be enabled by the [`evolved.debug_mode`](#evolveddebug_mode) function. When the debug mode is enabled, the library will check for incorrect usages of the API and throw errors when they are detected. This is very useful for debugging and development, but it can slow down performance a bit. ```lua ---@param yesno boolean @@ -406,7 +406,7 @@ evolved.set(entity, fragment, 42) One of the most important features of any ECS library is the ability to process entities by filters or queries. `evolved.lua` provides a simple and efficient way to do this. -First, you need to create a query that describes which entities you want to process. You can specify fragments you want to include, and fragments you want to exclude. Queries are just identifiers with a special predefined fragments: `evolved.INCLUDES` and `evolved.EXCLUDES`. These fragments expect a list of fragments as their components. +First, you need to create a query that describes which entities you want to process. You can specify fragments you want to include, and fragments you want to exclude. Queries are just identifiers with a special predefined fragments: [`evolved.INCLUDES`](#evolvedincludes) and [`evolved.EXCLUDES`](#evolvedexcludes). These fragments expect a list of fragments as their components. ```lua local evolved = require 'evolved' @@ -427,9 +427,9 @@ local query = evolved.builder() :spawn() ``` -We don't have to set both `evolved.INCLUDES` and `evolved.EXCLUDES` fragments, we can even do it without filters at all, then the query will match all chunks in the world. +We don't have to set both [`evolved.INCLUDES`](#evolvedincludes) and [`evolved.EXCLUDES`](#evolvedexcludes) fragments, we can even do it without filters at all, then the query will match all chunks in the world. -After the query is created, we are ready to process our filtered by this query entities. You can do this by using the `evolved.execute` function. This function takes a query as an argument and returns an iterator that can be used to iterate over all matching with the query chunks. +After the query is created, we are ready to process our filtered by this query entities. You can do this by using the [`evolved.execute`](#evolvedexecute) function. This function takes a query as an argument and returns an iterator that can be used to iterate over all matching with the query chunks. ```lua ---@param query evolved.query @@ -465,7 +465,7 @@ function evolved.defer() end function evolved.commit() end ``` -The `evolved.defer` function starts a deferred scope. This means that all changes made inside the scope will be queued and applied after leaving the scope. The `evolved.commit` function closes a last deferred scope and applies all queued changes. These functions can be nested, so you can start a new deferred scope inside an existing one. The `evolved.commit` function will apply all queued changes only when the last deferred scope is closed. +The [`evolved.defer`](#evolveddefer) function starts a deferred scope. This means that all changes made inside the scope will be queued and applied after leaving the scope. The [`evolved.commit`](#evolvedcommit) function closes a last deferred scope and applies all queued changes. These functions can be nested, so you can start a new deferred scope inside an existing one. The [`evolved.commit`](#evolvedcommit) function will apply all queued changes only when the last deferred scope is closed. ```lua local evolved = require 'evolved' @@ -535,7 +535,7 @@ In all other respects, batch operations behave the same way as the common modify ## Systems -Usually, we want to organize our processing of entities into systems that will be executed in a specific order. The library has a way to do this using special `evolved.QUERY` and `evolved.EXECUTE` fragments that are used to specify the system's queries and execution callbacks. And yes, systems are just entities with special fragments. +Usually, we want to organize our processing of entities into systems that will be executed in a specific order. The library has a way to do this using special [`evolved.QUERY`](#evolvedquery) and [`evolved.EXECUTE`](#evolvedexecute) fragments that are used to specify the system's queries and execution callbacks. And yes, systems are just entities with special fragments. ```lua local evolved = require 'evolved' @@ -560,14 +560,14 @@ local system = evolved.builder() end):spawn() ``` -The `evolved.process` function is used to process systems. It takes systems as arguments and executes them in the order they were passed. +The [`evolved.process`](#evolvedprocess) function is used to process systems. It takes systems as arguments and executes them in the order they were passed. ```lua ---@param ... evolved.system systems function evolved.process(...) end ``` -To group systems together, you can use the `evolved.GROUP` fragment. Systems with a specified group will be processed when you call the `evolved.process` function with this group. For example, you can group all physics systems together and process them in one `evolved.process` call. +To group systems together, you can use the [`evolved.GROUP`](#evolvedgroup) fragment. Systems with a specified group will be processed when you call the [`evolved.process`](#evolvedprocess) function with this group. For example, you can group all physics systems together and process them in one [`evolved.process`](#evolvedprocess) call. ```lua local evolved = require 'evolved' @@ -617,7 +617,7 @@ evolved.builder() evolved.process(physics_group) ``` -Systems and groups also can have the `evolved.PROLOGUE` and `evolved.EPILOGUE` fragments. These fragments are used to specify callbacks that will be executed before and after the system or group is processed. This is useful for setting up and tearing down systems or groups, or for performing some additional processing before or after the main processing. +Systems and groups also can have the [`evolved.PROLOGUE`](#evolvedprologue) and [`evolved.EPILOGUE`](#evolvedepilogue) fragments. These fragments are used to specify callbacks that will be executed before and after the system or group is processed. This is useful for setting up and tearing down systems or groups, or for performing some additional processing before or after the main processing. ```lua local evolved = require 'evolved' @@ -638,10 +638,147 @@ The prologue and epilogue fragments do not require an explicit query. They will And one more thing about systems. Execution callbacks are called in the [deferred scope](#deferred-operations), this means that all modifying operations inside the callback will be queued and applied after the system processed all chunks. But prologue and epilogue callbacks are not called in the deferred scope, so all modifying operations inside them will be applied immediately. This is done to avoid confusion and to make it clear that prologue and epilogue callbacks are not part of the chunk processing. -## Predefs - -> coming soon... - ## API Reference -> coming soon... +### Predefs + +#### `evolved.TAG` + +#### `evolved.NAME` + +#### `evolved.UNIQUE` + +#### `evolved.EXPLICIT` + +#### `evolved.DEFAULT` + +#### `evolved.DUPLICATE` + +#### `evolved.PREFAB` + +#### `evolved.DISABLED` + +#### `evolved.INCLUDES` + +#### `evolved.EXCLUDES` + +#### `evolved.ON_SET` + +#### `evolved.ON_ASSIGN` + +#### `evolved.ON_INSERT` + +#### `evolved.ON_REMOVE` + +#### `evolved.GROUP` + +#### `evolved.QUERY` + +#### `evolved.EXECUTE` + +#### `evolved.PROLOGUE` + +#### `evolved.EPILOGUE` + +#### `evolved.DESTROY_POLICY` + +### Functions + +#### `evolved.id` + +#### `evolved.pack` +#### `evolved.unpack` + +#### `evolved.defer` +#### `evolved.commit` + +#### `evolved.spawn` +#### `evolved.clone` + +#### `evolved.alive` +#### `evolved.alive_all` +#### `evolved.alive_any` + +#### `evolved.empty` +#### `evolved.empty_all` +#### `evolved.empty_any` + +#### `evolved.has` +#### `evolved.has_all` +#### `evolved.has_any` + +#### `evolved.get` + +#### `evolved.set` +#### `evolved.remove` +#### `evolved.clear` +#### `evolved.destroy` + +#### `evolved.batch_set` +#### `evolved.batch_remove` +#### `evolved.batch_clear` +#### `evolved.batch_destroy` + +#### `evolved.each` +#### `evolved.execute` + +#### `evolved.process` + +#### `evolved.debug_mode` +#### `evolved.collect_garbage` + +#### `evolved.chunk` + +#### `evolved.chunk_mt:alive` +#### `evolved.chunk_mt:empty` + +#### `evolved.chunk_mt:has` +#### `evolved.chunk_mt:has_all` +#### `evolved.chunk_mt:has_any` + +#### `evolved.chunk_mt:entities` +#### `evolved.chunk_mt:fragments` +#### `evolved.chunk_mt:components` + +#### `evolved.builder` + +#### `evolved.builder_mt:spawn` +#### `evolved.builder_mt:clone` + +#### `evolved.builder_mt:has` +#### `evolved.builder_mt:has_all` +#### `evolved.builder_mt:has_any` + +#### `evolved.builder_mt:set` +#### `evolved.builder_mt:remove` +#### `evolved.builder_mt:clear` + +#### `evolved.builder_mt:tag` +#### `evolved.builder_mt:name` + +#### `evolved.builder_mt:unique` +#### `evolved.builder_mt:explicit` + +#### `evolved.builder_mt:default` +#### `evolved.builder_mt:duplicate` + +#### `evolved.builder_mt:prefab` +#### `evolved.builder_mt:disabled` + +#### `evolved.builder_mt:include` +#### `evolved.builder_mt:exclude` + +#### `evolved.builder_mt:on_set` +#### `evolved.builder_mt:on_assign` +#### `evolved.builder_mt:on_insert` +#### `evolved.builder_mt:on_remove` + +#### `evolved.builder_mt:group` + +#### `evolved.builder_mt:query` +#### `evolved.builder_mt:execute` + +#### `evolved.builder_mt:prologue` +#### `evolved.builder_mt:epilogue` + +#### `evolved.builder_mt:destroy_policy` From 429554685e136439026808908dd6ce66119bf18c Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Tue, 20 May 2025 06:45:44 +0700 Subject: [PATCH 09/16] manual wip --- MANUAL.md | 478 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 478 insertions(+) diff --git a/MANUAL.md b/MANUAL.md index 4a55cb6..dcd1051 100644 --- a/MANUAL.md +++ b/MANUAL.md @@ -686,99 +686,577 @@ And one more thing about systems. Execution callbacks are called in the [deferre #### `evolved.id` +```lua +---@param count? integer +---@return evolved.id ... ids +---@nodiscard +function evolved.id(count) end +``` + #### `evolved.pack` + +```lua +---@param index integer +---@param version integer +---@return evolved.id id +---@nodiscard +function evolved.pack(index, version) end +``` + #### `evolved.unpack` +```lua +---@param id evolved.id +---@return integer index +---@return integer version +---@nodiscard +function evolved.unpack(id) end +``` + #### `evolved.defer` + +```lua +---@return boolean started +function evolved.defer() end +``` + #### `evolved.commit` +```lua +---@return boolean committed +function evolved.commit() end +``` + #### `evolved.spawn` + +```lua +---@param components? table +---@return evolved.entity +function evolved.spawn(components) end +``` + #### `evolved.clone` +```lua +---@param prefab evolved.entity +---@param components? table +---@return evolved.entity +function evolved.clone(prefab, components) end +``` + #### `evolved.alive` + +```lua +---@param entity evolved.entity +---@return boolean +---@nodiscard +function evolved.alive(entity) end +``` + #### `evolved.alive_all` + +```lua +---@param ... evolved.entity entities +---@return boolean +---@nodiscard +function evolved.alive_all(...) end +``` + #### `evolved.alive_any` +```lua +---@param ... evolved.entity entities +---@return boolean +---@nodiscard +function evolved.alive_any(...) end +``` + #### `evolved.empty` + +```lua +---@param entity evolved.entity +---@return boolean +---@nodiscard +function evolved.empty(entity) end +``` + #### `evolved.empty_all` + +```lua +---@param ... evolved.entity entities +---@return boolean +---@nodiscard +function evolved.empty_all(...) end +``` + #### `evolved.empty_any` +```lua +---@param ... evolved.entity entities +---@return boolean +---@nodiscard +function evolved.empty_any(...) end +``` + #### `evolved.has` + +```lua +---@param entity evolved.entity +---@param fragment evolved.fragment +---@return boolean +---@nodiscard +function evolved.has(entity, fragment) end +``` + #### `evolved.has_all` + +```lua +---@param entity evolved.entity +---@param ... evolved.fragment fragments +---@return boolean +---@nodiscard +function evolved.has_all(entity, ...) end +``` + #### `evolved.has_any` +```lua +---@param entity evolved.entity +---@param ... evolved.fragment fragments +---@return boolean +---@nodiscard +function evolved.has_any(entity, ...) end +``` + #### `evolved.get` +```lua +---@param entity evolved.entity +---@param ... evolved.fragment fragments +---@return evolved.component ... components +---@nodiscard +function evolved.get(entity, ...) end +``` + #### `evolved.set` + +```lua +---@param entity evolved.entity +---@param fragment evolved.fragment +---@param component evolved.component +function evolved.set(entity, fragment, component) end +``` + #### `evolved.remove` + +```lua +---@param entity evolved.entity +---@param ... evolved.fragment fragments +function evolved.remove(entity, ...) end +``` + #### `evolved.clear` + +```lua +---@param ... evolved.entity entities +function evolved.clear(...) end +``` + #### `evolved.destroy` +```lua +---@param ... evolved.entity entities +function evolved.destroy(...) end +``` + #### `evolved.batch_set` + +```lua +---@param query evolved.query +---@param fragment evolved.fragment +---@param component evolved.component +function evolved.batch_set(query, fragment, component) end +``` + #### `evolved.batch_remove` + +```lua +---@param query evolved.query +---@param ... evolved.fragment fragments +function evolved.batch_remove(query, ...) end +``` + #### `evolved.batch_clear` + +```lua +---@param ... evolved.query queries +function evolved.batch_clear(...) end +``` + #### `evolved.batch_destroy` +```lua +---@param ... evolved.query queries +function evolved.batch_destroy(...) end +``` + #### `evolved.each` + +```lua +---@param entity evolved.entity +---@return evolved.each_iterator iterator +---@return evolved.each_state? iterator_state +---@nodiscard +function evolved.each(entity) end +``` + #### `evolved.execute` +```lua +---@param query evolved.query +---@return evolved.execute_iterator iterator +---@return evolved.execute_state? iterator_state +---@nodiscard +function evolved.execute(query) end +``` + #### `evolved.process` +```lua +---@param ... evolved.system systems +function evolved.process(...) end +``` + #### `evolved.debug_mode` + +```lua +---@param yesno boolean +function evolved.debug_mode(yesno) end +``` + #### `evolved.collect_garbage` +```lua +function evolved.collect_garbage() end +``` + + #### `evolved.chunk` +```lua +---@param fragment evolved.fragment +---@param ... evolved.fragment fragments +---@return evolved.chunk chunk +---@return evolved.entity[] entity_list +---@return integer entity_count +---@nodiscard +function evolved.chunk(fragment, ...) end +``` + #### `evolved.chunk_mt:alive` + +```lua +---@return boolean +---@nodiscard +function __chunk_mt:alive() end +``` + #### `evolved.chunk_mt:empty` +```lua +---@return boolean +---@nodiscard +function __chunk_mt:empty() end +``` + #### `evolved.chunk_mt:has` + +```lua +---@param fragment evolved.fragment +---@return boolean +---@nodiscard +function __chunk_mt:has(fragment) end +``` + #### `evolved.chunk_mt:has_all` + +```lua +---@param ... evolved.fragment fragments +---@return boolean +---@nodiscard +function __chunk_mt:has_all(...) end +``` + #### `evolved.chunk_mt:has_any` +```lua +---@param ... evolved.fragment fragments +---@return boolean +---@nodiscard +function __chunk_mt:has_any(...) end +``` + #### `evolved.chunk_mt:entities` + +```lua +---@return evolved.entity[] entity_list +---@return integer entity_count +---@nodiscard +function __chunk_mt:entities() end +``` + #### `evolved.chunk_mt:fragments` + +```lua +---@return evolved.fragment[] fragment_list +---@return integer fragment_count +---@nodiscard +function __chunk_mt:fragments() end +``` + #### `evolved.chunk_mt:components` +```lua +---@param ... evolved.fragment fragments +---@return evolved.storage ... storages +---@nodiscard +function __chunk_mt:components(...) end +``` + #### `evolved.builder` +```lua +---@return evolved.builder builder +---@nodiscard +function evolved.builder() end +``` + #### `evolved.builder_mt:spawn` + +```lua +---@return evolved.entity +function __builder_mt:spawn() end +``` + #### `evolved.builder_mt:clone` +```lua +---@param prefab evolved.entity +---@return evolved.entity +function __builder_mt:clone(prefab) end +``` + #### `evolved.builder_mt:has` + +```lua +---@param fragment evolved.fragment +---@return boolean +---@nodiscard +function __builder_mt:has(fragment) end +``` + #### `evolved.builder_mt:has_all` + +```lua +---@param ... evolved.fragment fragments +---@return boolean +---@nodiscard +function __builder_mt:has_all(...) end +``` + #### `evolved.builder_mt:has_any` +```lua +---@param ... evolved.fragment fragments +---@return boolean +---@nodiscard +function __builder_mt:has_any(...) end +``` + +#### `evolved.builder_mt:get` + +```lua +---@param ... evolved.fragment fragments +---@return evolved.component ... components +---@nodiscard +function __builder_mt:get(...) end +``` + #### `evolved.builder_mt:set` + +```lua +---@param fragment evolved.fragment +---@param component evolved.component +---@return evolved.builder builder +function __builder_mt:set(fragment, component) end +``` + #### `evolved.builder_mt:remove` + +```lua +---@param ... evolved.fragment fragments +---@return evolved.builder builder +function __builder_mt:remove(...) end +``` + #### `evolved.builder_mt:clear` +```lua +---@return evolved.builder builder +function __builder_mt:clear() end +``` + #### `evolved.builder_mt:tag` + +```lua +---@return evolved.builder builder +function __builder_mt:tag() end +``` + #### `evolved.builder_mt:name` +```lua +---@param name string +---@return evolved.builder builder +function __builder_mt:name(name) end +``` + #### `evolved.builder_mt:unique` + +```lua +---@return evolved.builder builder +function __builder_mt:unique() end +``` + #### `evolved.builder_mt:explicit` +```lua +---@return evolved.builder builder +function __builder_mt:explicit() end +``` + #### `evolved.builder_mt:default` + +```lua +---@param default evolved.component +---@return evolved.builder builder +function __builder_mt:default(default) end +``` + #### `evolved.builder_mt:duplicate` +```lua +---@param duplicate evolved.duplicate +---@return evolved.builder builder +function __builder_mt:duplicate(duplicate) end +``` + #### `evolved.builder_mt:prefab` + +```lua +---@return evolved.builder builder +function __builder_mt:prefab() end +``` + #### `evolved.builder_mt:disabled` +```lua +---@return evolved.builder builder +function __builder_mt:disabled() end +``` + #### `evolved.builder_mt:include` + +```lua +---@param ... evolved.fragment fragments +---@return evolved.builder builder +function __builder_mt:include(...) end +``` + #### `evolved.builder_mt:exclude` +```lua +---@param ... evolved.fragment fragments +---@return evolved.builder builder +function __builder_mt:exclude(...) end +``` + #### `evolved.builder_mt:on_set` + +```lua +---@param on_set evolved.set_hook +---@return evolved.builder builder +function __builder_mt:on_set(on_set) end +``` + #### `evolved.builder_mt:on_assign` + +```lua +---@param on_assign evolved.assign_hook +---@return evolved.builder builder +function __builder_mt:on_assign(on_assign) end +``` + #### `evolved.builder_mt:on_insert` + +```lua +---@param on_insert evolved.insert_hook +---@return evolved.builder builder +function __builder_mt:on_insert(on_insert) end +``` + #### `evolved.builder_mt:on_remove` +```lua +---@param on_remove evolved.remove_hook +---@return evolved.builder builder +function __builder_mt:on_remove(on_remove) end +``` + #### `evolved.builder_mt:group` +```lua +---@param group evolved.system +---@return evolved.builder builder +function __builder_mt:group(group) end +``` + #### `evolved.builder_mt:query` + +```lua +---@param query evolved.query +---@return evolved.builder builder +function __builder_mt:query(query) end +``` + #### `evolved.builder_mt:execute` +```lua +---@param execute evolved.execute +---@return evolved.builder builder +function __builder_mt:execute(execute) end +``` + #### `evolved.builder_mt:prologue` + +```lua +---@param prologue evolved.prologue +---@return evolved.builder builder +function __builder_mt:prologue(prologue) end +``` + #### `evolved.builder_mt:epilogue` +```lua +---@param epilogue evolved.epilogue +---@return evolved.builder builder +function __builder_mt:epilogue(epilogue) end +``` + #### `evolved.builder_mt:destroy_policy` + +```lua +---@param destroy_policy evolved.id +---@return evolved.builder builder +function __builder_mt:destroy_policy(destroy_policy) end +``` From 3b89b85b22e021875a7e3f89dce7e5bd8f1cf871 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Tue, 20 May 2025 06:59:23 +0700 Subject: [PATCH 10/16] manual wip --- MANUAL.md | 261 +++++++++++++++++++++++++++--------------------------- 1 file changed, 130 insertions(+), 131 deletions(-) diff --git a/MANUAL.md b/MANUAL.md index dcd1051..e881183 100644 --- a/MANUAL.md +++ b/MANUAL.md @@ -1,4 +1,4 @@ -# Manual +# Overview ## Identifiers @@ -638,53 +638,53 @@ The prologue and epilogue fragments do not require an explicit query. They will And one more thing about systems. Execution callbacks are called in the [deferred scope](#deferred-operations), this means that all modifying operations inside the callback will be queued and applied after the system processed all chunks. But prologue and epilogue callbacks are not called in the deferred scope, so all modifying operations inside them will be applied immediately. This is done to avoid confusion and to make it clear that prologue and epilogue callbacks are not part of the chunk processing. -## API Reference +# API Reference -### Predefs +## Predefs -#### `evolved.TAG` +### `evolved.TAG` -#### `evolved.NAME` +### `evolved.NAME` -#### `evolved.UNIQUE` +### `evolved.UNIQUE` -#### `evolved.EXPLICIT` +### `evolved.EXPLICIT` -#### `evolved.DEFAULT` +### `evolved.DEFAULT` -#### `evolved.DUPLICATE` +### `evolved.DUPLICATE` -#### `evolved.PREFAB` +### `evolved.PREFAB` -#### `evolved.DISABLED` +### `evolved.DISABLED` -#### `evolved.INCLUDES` +### `evolved.INCLUDES` -#### `evolved.EXCLUDES` +### `evolved.EXCLUDES` -#### `evolved.ON_SET` +### `evolved.ON_SET` -#### `evolved.ON_ASSIGN` +### `evolved.ON_ASSIGN` -#### `evolved.ON_INSERT` +### `evolved.ON_INSERT` -#### `evolved.ON_REMOVE` +### `evolved.ON_REMOVE` -#### `evolved.GROUP` +### `evolved.GROUP` -#### `evolved.QUERY` +### `evolved.QUERY` -#### `evolved.EXECUTE` +### `evolved.EXECUTE` -#### `evolved.PROLOGUE` +### `evolved.PROLOGUE` -#### `evolved.EPILOGUE` +### `evolved.EPILOGUE` -#### `evolved.DESTROY_POLICY` +### `evolved.DESTROY_POLICY` -### Functions +## Functions -#### `evolved.id` +### `evolved.id` ```lua ---@param count? integer @@ -693,7 +693,7 @@ And one more thing about systems. Execution callbacks are called in the [deferre function evolved.id(count) end ``` -#### `evolved.pack` +### `evolved.pack` ```lua ---@param index integer @@ -703,7 +703,7 @@ function evolved.id(count) end function evolved.pack(index, version) end ``` -#### `evolved.unpack` +### `evolved.unpack` ```lua ---@param id evolved.id @@ -713,21 +713,21 @@ function evolved.pack(index, version) end function evolved.unpack(id) end ``` -#### `evolved.defer` +### `evolved.defer` ```lua ---@return boolean started function evolved.defer() end ``` -#### `evolved.commit` +### `evolved.commit` ```lua ---@return boolean committed function evolved.commit() end ``` -#### `evolved.spawn` +### `evolved.spawn` ```lua ---@param components? table @@ -735,7 +735,7 @@ function evolved.commit() end function evolved.spawn(components) end ``` -#### `evolved.clone` +### `evolved.clone` ```lua ---@param prefab evolved.entity @@ -744,7 +744,7 @@ function evolved.spawn(components) end function evolved.clone(prefab, components) end ``` -#### `evolved.alive` +### `evolved.alive` ```lua ---@param entity evolved.entity @@ -753,7 +753,7 @@ function evolved.clone(prefab, components) end function evolved.alive(entity) end ``` -#### `evolved.alive_all` +### `evolved.alive_all` ```lua ---@param ... evolved.entity entities @@ -762,7 +762,7 @@ function evolved.alive(entity) end function evolved.alive_all(...) end ``` -#### `evolved.alive_any` +### `evolved.alive_any` ```lua ---@param ... evolved.entity entities @@ -771,7 +771,7 @@ function evolved.alive_all(...) end function evolved.alive_any(...) end ``` -#### `evolved.empty` +### `evolved.empty` ```lua ---@param entity evolved.entity @@ -780,7 +780,7 @@ function evolved.alive_any(...) end function evolved.empty(entity) end ``` -#### `evolved.empty_all` +### `evolved.empty_all` ```lua ---@param ... evolved.entity entities @@ -789,7 +789,7 @@ function evolved.empty(entity) end function evolved.empty_all(...) end ``` -#### `evolved.empty_any` +### `evolved.empty_any` ```lua ---@param ... evolved.entity entities @@ -798,7 +798,7 @@ function evolved.empty_all(...) end function evolved.empty_any(...) end ``` -#### `evolved.has` +### `evolved.has` ```lua ---@param entity evolved.entity @@ -808,7 +808,7 @@ function evolved.empty_any(...) end function evolved.has(entity, fragment) end ``` -#### `evolved.has_all` +### `evolved.has_all` ```lua ---@param entity evolved.entity @@ -818,7 +818,7 @@ function evolved.has(entity, fragment) end function evolved.has_all(entity, ...) end ``` -#### `evolved.has_any` +### `evolved.has_any` ```lua ---@param entity evolved.entity @@ -828,7 +828,7 @@ function evolved.has_all(entity, ...) end function evolved.has_any(entity, ...) end ``` -#### `evolved.get` +### `evolved.get` ```lua ---@param entity evolved.entity @@ -838,7 +838,7 @@ function evolved.has_any(entity, ...) end function evolved.get(entity, ...) end ``` -#### `evolved.set` +### `evolved.set` ```lua ---@param entity evolved.entity @@ -847,7 +847,7 @@ function evolved.get(entity, ...) end function evolved.set(entity, fragment, component) end ``` -#### `evolved.remove` +### `evolved.remove` ```lua ---@param entity evolved.entity @@ -855,21 +855,21 @@ function evolved.set(entity, fragment, component) end function evolved.remove(entity, ...) end ``` -#### `evolved.clear` +### `evolved.clear` ```lua ---@param ... evolved.entity entities function evolved.clear(...) end ``` -#### `evolved.destroy` +### `evolved.destroy` ```lua ---@param ... evolved.entity entities function evolved.destroy(...) end ``` -#### `evolved.batch_set` +### `evolved.batch_set` ```lua ---@param query evolved.query @@ -878,7 +878,7 @@ function evolved.destroy(...) end function evolved.batch_set(query, fragment, component) end ``` -#### `evolved.batch_remove` +### `evolved.batch_remove` ```lua ---@param query evolved.query @@ -886,21 +886,21 @@ function evolved.batch_set(query, fragment, component) end function evolved.batch_remove(query, ...) end ``` -#### `evolved.batch_clear` +### `evolved.batch_clear` ```lua ---@param ... evolved.query queries function evolved.batch_clear(...) end ``` -#### `evolved.batch_destroy` +### `evolved.batch_destroy` ```lua ---@param ... evolved.query queries function evolved.batch_destroy(...) end ``` -#### `evolved.each` +### `evolved.each` ```lua ---@param entity evolved.entity @@ -910,7 +910,7 @@ function evolved.batch_destroy(...) end function evolved.each(entity) end ``` -#### `evolved.execute` +### `evolved.execute` ```lua ---@param query evolved.query @@ -920,28 +920,27 @@ function evolved.each(entity) end function evolved.execute(query) end ``` -#### `evolved.process` +### `evolved.process` ```lua ---@param ... evolved.system systems function evolved.process(...) end ``` -#### `evolved.debug_mode` +### `evolved.debug_mode` ```lua ---@param yesno boolean function evolved.debug_mode(yesno) end ``` -#### `evolved.collect_garbage` +### `evolved.collect_garbage` ```lua function evolved.collect_garbage() end ``` - -#### `evolved.chunk` +### `evolved.chunk` ```lua ---@param fragment evolved.fragment @@ -953,77 +952,77 @@ function evolved.collect_garbage() end function evolved.chunk(fragment, ...) end ``` -#### `evolved.chunk_mt:alive` +### `evolved.chunk_mt:alive` ```lua ---@return boolean ---@nodiscard -function __chunk_mt:alive() end +function evolved.chunk_mt:alive() end ``` -#### `evolved.chunk_mt:empty` +### `evolved.chunk_mt:empty` ```lua ---@return boolean ---@nodiscard -function __chunk_mt:empty() end +function evolved.chunk_mt:empty() end ``` -#### `evolved.chunk_mt:has` +### `evolved.chunk_mt:has` ```lua ---@param fragment evolved.fragment ---@return boolean ---@nodiscard -function __chunk_mt:has(fragment) end +function evolved.chunk_mt:has(fragment) end ``` -#### `evolved.chunk_mt:has_all` +### `evolved.chunk_mt:has_all` ```lua ---@param ... evolved.fragment fragments ---@return boolean ---@nodiscard -function __chunk_mt:has_all(...) end +function evolved.chunk_mt:has_all(...) end ``` -#### `evolved.chunk_mt:has_any` +### `evolved.chunk_mt:has_any` ```lua ---@param ... evolved.fragment fragments ---@return boolean ---@nodiscard -function __chunk_mt:has_any(...) end +function evolved.chunk_mt:has_any(...) end ``` -#### `evolved.chunk_mt:entities` +### `evolved.chunk_mt:entities` ```lua ---@return evolved.entity[] entity_list ---@return integer entity_count ---@nodiscard -function __chunk_mt:entities() end +function evolved.chunk_mt:entities() end ``` -#### `evolved.chunk_mt:fragments` +### `evolved.chunk_mt:fragments` ```lua ---@return evolved.fragment[] fragment_list ---@return integer fragment_count ---@nodiscard -function __chunk_mt:fragments() end +function evolved.chunk_mt:fragments() end ``` -#### `evolved.chunk_mt:components` +### `evolved.chunk_mt:components` ```lua ---@param ... evolved.fragment fragments ---@return evolved.storage ... storages ---@nodiscard -function __chunk_mt:components(...) end +function evolved.chunk_mt:components(...) end ``` -#### `evolved.builder` +### `evolved.builder` ```lua ---@return evolved.builder builder @@ -1031,232 +1030,232 @@ function __chunk_mt:components(...) end function evolved.builder() end ``` -#### `evolved.builder_mt:spawn` +### `evolved.builder_mt:spawn` ```lua ---@return evolved.entity -function __builder_mt:spawn() end +function evolved.builder_mt:spawn() end ``` -#### `evolved.builder_mt:clone` +### `evolved.builder_mt:clone` ```lua ---@param prefab evolved.entity ---@return evolved.entity -function __builder_mt:clone(prefab) end +function evolved.builder_mt:clone(prefab) end ``` -#### `evolved.builder_mt:has` +### `evolved.builder_mt:has` ```lua ---@param fragment evolved.fragment ---@return boolean ---@nodiscard -function __builder_mt:has(fragment) end +function evolved.builder_mt:has(fragment) end ``` -#### `evolved.builder_mt:has_all` +### `evolved.builder_mt:has_all` ```lua ---@param ... evolved.fragment fragments ---@return boolean ---@nodiscard -function __builder_mt:has_all(...) end +function evolved.builder_mt:has_all(...) end ``` -#### `evolved.builder_mt:has_any` +### `evolved.builder_mt:has_any` ```lua ---@param ... evolved.fragment fragments ---@return boolean ---@nodiscard -function __builder_mt:has_any(...) end +function evolved.builder_mt:has_any(...) end ``` -#### `evolved.builder_mt:get` +### `evolved.builder_mt:get` ```lua ---@param ... evolved.fragment fragments ---@return evolved.component ... components ---@nodiscard -function __builder_mt:get(...) end +function evolved.builder_mt:get(...) end ``` -#### `evolved.builder_mt:set` +### `evolved.builder_mt:set` ```lua ---@param fragment evolved.fragment ---@param component evolved.component ---@return evolved.builder builder -function __builder_mt:set(fragment, component) end +function evolved.builder_mt:set(fragment, component) end ``` -#### `evolved.builder_mt:remove` +### `evolved.builder_mt:remove` ```lua ---@param ... evolved.fragment fragments ---@return evolved.builder builder -function __builder_mt:remove(...) end +function evolved.builder_mt:remove(...) end ``` -#### `evolved.builder_mt:clear` +### `evolved.builder_mt:clear` ```lua ---@return evolved.builder builder -function __builder_mt:clear() end +function evolved.builder_mt:clear() end ``` -#### `evolved.builder_mt:tag` +### `evolved.builder_mt:tag` ```lua ---@return evolved.builder builder -function __builder_mt:tag() end +function evolved.builder_mt:tag() end ``` -#### `evolved.builder_mt:name` +### `evolved.builder_mt:name` ```lua ---@param name string ---@return evolved.builder builder -function __builder_mt:name(name) end +function evolved.builder_mt:name(name) end ``` -#### `evolved.builder_mt:unique` +### `evolved.builder_mt:unique` ```lua ---@return evolved.builder builder -function __builder_mt:unique() end +function evolved.builder_mt:unique() end ``` -#### `evolved.builder_mt:explicit` +### `evolved.builder_mt:explicit` ```lua ---@return evolved.builder builder -function __builder_mt:explicit() end +function evolved.builder_mt:explicit() end ``` -#### `evolved.builder_mt:default` +### `evolved.builder_mt:default` ```lua ---@param default evolved.component ---@return evolved.builder builder -function __builder_mt:default(default) end +function evolved.builder_mt:default(default) end ``` -#### `evolved.builder_mt:duplicate` +### `evolved.builder_mt:duplicate` ```lua ---@param duplicate evolved.duplicate ---@return evolved.builder builder -function __builder_mt:duplicate(duplicate) end +function evolved.builder_mt:duplicate(duplicate) end ``` -#### `evolved.builder_mt:prefab` +### `evolved.builder_mt:prefab` ```lua ---@return evolved.builder builder -function __builder_mt:prefab() end +function evolved.builder_mt:prefab() end ``` -#### `evolved.builder_mt:disabled` +### `evolved.builder_mt:disabled` ```lua ---@return evolved.builder builder -function __builder_mt:disabled() end +function evolved.builder_mt:disabled() end ``` -#### `evolved.builder_mt:include` +### `evolved.builder_mt:include` ```lua ---@param ... evolved.fragment fragments ---@return evolved.builder builder -function __builder_mt:include(...) end +function evolved.builder_mt:include(...) end ``` -#### `evolved.builder_mt:exclude` +### `evolved.builder_mt:exclude` ```lua ---@param ... evolved.fragment fragments ---@return evolved.builder builder -function __builder_mt:exclude(...) end +function evolved.builder_mt:exclude(...) end ``` -#### `evolved.builder_mt:on_set` +### `evolved.builder_mt:on_set` ```lua ---@param on_set evolved.set_hook ---@return evolved.builder builder -function __builder_mt:on_set(on_set) end +function evolved.builder_mt:on_set(on_set) end ``` -#### `evolved.builder_mt:on_assign` +### `evolved.builder_mt:on_assign` ```lua ---@param on_assign evolved.assign_hook ---@return evolved.builder builder -function __builder_mt:on_assign(on_assign) end +function evolved.builder_mt:on_assign(on_assign) end ``` -#### `evolved.builder_mt:on_insert` +### `evolved.builder_mt:on_insert` ```lua ---@param on_insert evolved.insert_hook ---@return evolved.builder builder -function __builder_mt:on_insert(on_insert) end +function evolved.builder_mt:on_insert(on_insert) end ``` -#### `evolved.builder_mt:on_remove` +### `evolved.builder_mt:on_remove` ```lua ---@param on_remove evolved.remove_hook ---@return evolved.builder builder -function __builder_mt:on_remove(on_remove) end +function evolved.builder_mt:on_remove(on_remove) end ``` -#### `evolved.builder_mt:group` +### `evolved.builder_mt:group` ```lua ---@param group evolved.system ---@return evolved.builder builder -function __builder_mt:group(group) end +function evolved.builder_mt:group(group) end ``` -#### `evolved.builder_mt:query` +### `evolved.builder_mt:query` ```lua ---@param query evolved.query ---@return evolved.builder builder -function __builder_mt:query(query) end +function evolved.builder_mt:query(query) end ``` -#### `evolved.builder_mt:execute` +### `evolved.builder_mt:execute` ```lua ---@param execute evolved.execute ---@return evolved.builder builder -function __builder_mt:execute(execute) end +function evolved.builder_mt:execute(execute) end ``` -#### `evolved.builder_mt:prologue` +### `evolved.builder_mt:prologue` ```lua ---@param prologue evolved.prologue ---@return evolved.builder builder -function __builder_mt:prologue(prologue) end +function evolved.builder_mt:prologue(prologue) end ``` -#### `evolved.builder_mt:epilogue` +### `evolved.builder_mt:epilogue` ```lua ---@param epilogue evolved.epilogue ---@return evolved.builder builder -function __builder_mt:epilogue(epilogue) end +function evolved.builder_mt:epilogue(epilogue) end ``` -#### `evolved.builder_mt:destroy_policy` +### `evolved.builder_mt:destroy_policy` ```lua ---@param destroy_policy evolved.id ---@return evolved.builder builder -function __builder_mt:destroy_policy(destroy_policy) end +function evolved.builder_mt:destroy_policy(destroy_policy) end ``` From 9e4e3440bc9135a425df668f48dd3652b34f3c06 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Tue, 20 May 2025 17:47:20 +0700 Subject: [PATCH 11/16] manual wip --- MANUAL.md | 54 ++++++++++++++++++++++++---- README.md | 106 +++++++++++++++++++++++++++++++++++------------------- 2 files changed, 117 insertions(+), 43 deletions(-) diff --git a/MANUAL.md b/MANUAL.md index e881183..6d5f22a 100644 --- a/MANUAL.md +++ b/MANUAL.md @@ -193,20 +193,17 @@ function evolved.chunk(fragment, ...) end The [`evolved.chunk`](#evolvedchunk) function takes one or more fragments as arguments and returns the chunk for this combination. After that, you can use the chunk's methods to retrieve their entities, fragments, and components. ```lua ----@param self evolved.chunk ---@return evolved.entity[] entity_list ---@return integer entity_count function chunk_mt:entities() end ----@param self evolved.chunk ---@return evolved.fragment[] fragment_list ---@return integer fragment_count function chunk_mt:fragments() end ----@param self evolved.chunk ---@param ... evolved.fragment fragments ----@return evolved.component[] ... component_lists -function chunk_mt:components(...) end +---@return evolved.storage ... storages +function chunk_mt:components(...) ``` Full example: @@ -348,7 +345,7 @@ All of these functions can be safely called on non-alive entities and non-alive ## Modifying Operations -The library provides a classic set of functions for modifying entities. These functions are used to set, get, remove, and check fragments on entities. +The library provides a classic set of functions for modifying entities. These functions are used to add, override, and remove fragments from entities. ```lua ---@param entity evolved.entity @@ -638,6 +635,47 @@ The prologue and epilogue fragments do not require an explicit query. They will And one more thing about systems. Execution callbacks are called in the [deferred scope](#deferred-operations), this means that all modifying operations inside the callback will be queued and applied after the system processed all chunks. But prologue and epilogue callbacks are not called in the deferred scope, so all modifying operations inside them will be applied immediately. This is done to avoid confusion and to make it clear that prologue and epilogue callbacks are not part of the chunk processing. +## Advanced Topics + +### Fragment Tags + +Sometimes you want to have a fragment without a component. For example, you might want to have some marks that will be used to mark entities for processing. Fragments without components are called `tags`. Such fragments take up less memory, because they do not require any components to be stored. Migration of entities with tags is faster, because the library does not need to migrate components, only the tags themselves. To create a tag, mark the fragment with the [`evolved.TAG`](#evolvedtag) fragment. + +```lua +local evolved = require 'evolved' + +local player_tag = evolved.id() +evolved.set(player_tag, evolved.TAG) + +local player = evolved.id() +evolved.set(player, player_tag) + +-- player has the player_tag fragment +assert(evolved.has(player, player_tag)) + +-- player_tag is a tag, so it doesn't have a component +assert(evolved.get(player, player_tag) == nil) +``` + +### Fragment Hooks + +The library provides a way to execute callbacks when fragments are set, assigned, inserted, or removed from entities. This is done using special fragments: [`evolved.ON_SET`](#evolvedon_set), [`evolved.ON_ASSIGN`](#evolvedon_assign), [`evolved.ON_INSERT`](#evolvedon_insert), and [`evolved.ON_REMOVE`](#evolvedon_remove). These fragments are used to specify the callbacks that will be executed when the corresponding operation is performed on the fragment. + +```lua +local evolved = require 'evolved' + +local health = evolved.builder() + :on_set(function(entity, fragment, component) + print('health set to ' .. component) + end):spawn() + +local player = evolved.id() +evolved.set(player, health, 100) -- prints "health set to 100" +evolved.set(player, health, 200) -- prints "health set to 200" +``` + +Use [`evolved.ON_SET`](#evolvedon_set) for callbacks on fragment insert or override, [`evolved.ON_ASSIGN`](#evolvedon_assign) for overrides, and [`evolved.ON_INSERT`](#evolvedon_insert)/[`evolved.ON_REMOVE`](#evolvedon_remove) for insertions or removals. + # API Reference ## Predefs @@ -940,6 +978,8 @@ function evolved.debug_mode(yesno) end function evolved.collect_garbage() end ``` +## Chunk + ### `evolved.chunk` ```lua @@ -1022,6 +1062,8 @@ function evolved.chunk_mt:fragments() end function evolved.chunk_mt:components(...) end ``` +## Builder + ### `evolved.builder` ```lua diff --git a/README.md b/README.md index d765344..8871b02 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,38 @@ - [lua](https://www.lua.org/) **>= 5.1** - [luajit](https://luajit.org/) **>= 2.0** +## Aliases + +``` +id :: implementation-specific + +entity :: id +fragment :: id +query :: id +system :: id + +component :: any +storage :: component[] + +default :: component +duplicate :: {component -> component} + +execute :: {chunk, entity[], integer} +prologue :: {} +epilogue :: {} + +set_hook :: {entity, fragment, component, component?} +assign_hook :: {entity, fragment, component, component} +insert_hook :: {entity, fragment, component} +remove_hook :: {entity, fragment, component} + +each_state :: implementation-specific +execute_state :: implementation-specific + +each_iterator :: {each_state? -> fragment?, component?} +execute_iterator :: {execute_state? -> chunk?, entity[]?, integer?} +``` + ## Predefs ``` @@ -112,16 +144,16 @@ collect_garbage :: () ``` chunk :: fragment, fragment... -> chunk, entity[], integer -chunk:alive :: boolean -chunk:empty :: boolean +chunk_mt:alive :: boolean +chunk_mt:empty :: boolean -chunk:has :: fragment -> boolean -chunk:has_all :: fragment... -> boolean -chunk:has_any :: fragment... -> boolean +chunk_mt:has :: fragment -> boolean +chunk_mt:has_all :: fragment... -> boolean +chunk_mt:has_any :: fragment... -> boolean -chunk:entities :: entity[], integer -chunk:fragments :: fragment[], integer -chunk:components :: fragment... -> component[]... +chunk_mt:entities :: entity[], integer +chunk_mt:fragments :: fragment[], integer +chunk_mt:components :: fragment... -> storage... ``` ## Builder @@ -129,48 +161,48 @@ chunk:components :: fragment... -> component[]... ``` builder :: builder -builder:spawn :: entity -builder:clone :: entity -> entity +builder_mt:spawn :: entity +builder_mt:clone :: entity -> entity -builder:has :: fragment -> boolean -builder:has_all :: fragment... -> boolean -builder:has_any :: fragment... -> boolean +builder_mt:has :: fragment -> boolean +builder_mt:has_all :: fragment... -> boolean +builder_mt:has_any :: fragment... -> boolean -builder:get :: fragment... -> component... +builder_mt:get :: fragment... -> component... -builder:set :: fragment, component -> builder -builder:remove :: fragment... -> builder -builder:clear :: builder +builder_mt:set :: fragment, component -> builder +builder_mt:remove :: fragment... -> builder +builder_mt:clear :: builder -builder:tag :: builder -builder:name :: string -> builder +builder_mt:tag :: builder +builder_mt:name :: string -> builder -builder:unique :: builder -builder:explicit :: builder +builder_mt:unique :: builder +builder_mt:explicit :: builder -builder:default :: component -> builder -builder:duplicate :: {component -> component} -> builder +builder_mt:default :: component -> builder +builder_mt:duplicate :: {component -> component} -> builder -builder:prefab :: builder -builder:disabled :: builder +builder_mt:prefab :: builder +builder_mt:disabled :: builder -builder:include :: fragment... -> builder -builder:exclude :: fragment... -> builder +builder_mt:include :: fragment... -> builder +builder_mt:exclude :: fragment... -> builder -builder:on_set :: {entity, fragment, component, component?} -> builder -builder:on_assign :: {entity, fragment, component, component} -> builder -builder:on_insert :: {entity, fragment, component} -> builder -builder:on_remove :: {entity, fragment} -> builder +builder_mt:on_set :: {entity, fragment, component, component?} -> builder +builder_mt:on_assign :: {entity, fragment, component, component} -> builder +builder_mt:on_insert :: {entity, fragment, component} -> builder +builder_mt:on_remove :: {entity, fragment} -> builder -builder:group :: system -> builder +builder_mt:group :: system -> builder -builder:query :: query -> builder -builder:execute :: {chunk, entity[], integer} -> builder +builder_mt:query :: query -> builder +builder_mt:execute :: {chunk, entity[], integer} -> builder -builder:prologue :: {} -> builder -builder:epilogue :: {} -> builder +builder_mt:prologue :: {} -> builder +builder_mt:epilogue :: {} -> builder -builder:destroy_policy :: id -> builder +builder_mt:destroy_policy :: id -> builder ``` ## [License (MIT)](./LICENSE.md) From 0d93842e52e4ea7b9e195b66629bb04eaeb7f3f5 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Tue, 20 May 2025 19:49:27 +0700 Subject: [PATCH 12/16] manual wip --- MANUAL.md | 1303 --------------------------------------------------- README.md | 1335 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 1330 insertions(+), 1308 deletions(-) delete mode 100644 MANUAL.md diff --git a/MANUAL.md b/MANUAL.md deleted file mode 100644 index 6d5f22a..0000000 --- a/MANUAL.md +++ /dev/null @@ -1,1303 +0,0 @@ -# Overview - -## Identifiers - -An identifier is a packed 40-bit integer. The first 20 bits represent the index, and the last 20 bits represent the version. To create a new identifier, use the [`evolved.id`](#evolvedid) function. - -```lua ----@param count? integer ----@return evolved.id ... ids -function evolved.id(count) end -``` - -The `count` parameter is optional and defaults to `1`. The function returns one or more identifiers depending on the `count` parameter. The maximum number of alive identifiers is `2^20-1` (1048575). After that, the function will throw an error: `| evolved.lua | id index overflow`. - -Identifiers can be recycled. When an identifier is no longer needed, use the [`evolved.destroy`](#evolveddestroy) function to destroy it. This will free the identifier for reuse. - -```lua ----@param ... evolved.id ids -function evolved.destroy(...) end -``` - -The [`evolved.destroy`](#evolveddestroy) function takes one or more identifiers as arguments. Destroyed identifiers will be added to the recycler free list. It is safe to call [`evolved.destroy`](#evolveddestroy) on identifiers that are not alive; the function will simply ignore them. - -After destroying an identifier, it can be reused by calling the [`evolved.id`](#evolvedid) function again. The new identifier will have the same index as the destroyed one, but a different version. The version is incremented each time an identifier is destroyed. This mechanism allows us to reuse indices and to know whether an identifier is alive or not. - -The set of [`evolved.alive`](#evolvedalive) functions can be used to check whether identifiers are alive. - -```lua ----@param id evolved.id ----@return boolean -function evolved.alive(id) end - ----@param ... evolved.id ids ----@return boolean -function evolved.alive_all(...) end - ----@param ... evolved.id ids ----@return boolean -function evolved.alive_any(...) end -``` - -Sometimes (for debugging purposes, for example), it is necessary to extract the index and version from an identifier or to pack them back into an identifier. The [`evolved.pack`](#evolvedpack) and [`evolved.unpack`](#evolvedunpack) functions can be used for this purpose. - -```lua ----@param index integer ----@param version integer ----@return evolved.id id -function evolved.pack(index, version) end - ----@param id evolved.id ----@return integer index ----@return integer version -function evolved.unpack(id) end -``` - -Here is a short example of how to use identifiers: - -```lua -local evolved = require 'evolved' - -local id = evolved.id() -- create a new identifier -assert(evolved.alive(id)) -- check that the identifier is alive - -local index, version = evolved.unpack(id) -- unpack the identifier -assert(evolved.pack(index, version) == id) -- pack it back - -evolved.destroy(id) -- destroy the identifier -assert(not evolved.alive(id)) -- check that the identifier is not alive now -``` - -## Entities, Fragments, and Components - -First, you need to understand that entities and fragments are just identifiers. The difference between them is purely semantic. Entities are used to represent objects in the world, while fragments are used to represent types of components that can be attached to entities. Components, on the other hand, are any data that is attached to entities through fragments. - -```lua ----@alias evolved.entity evolved.id ----@alias evolved.fragment evolved.id ----@alias evolved.component any -``` - -Here is a simple example of how to attach a component to an entity: - -```lua -local evolved = require 'evolved' - -local entity, fragment = evolved.id(2) - -evolved.set(entity, fragment, 100) -assert(evolved.get(entity, fragment) == 100) -``` - -I know it's not very clear yet, but don't worry, we'll get there. In the next example, I'm going to name the entity and fragment, so it will be easier to understand what's going on here. - -```lua -local evolved = require 'evolved' - -local player = evolved.id() - -local health = evolved.id() -local stamina = evolved.id() - -evolved.set(player, health, 100) -evolved.set(player, stamina, 50) - -assert(evolved.get(player, health) == 100) -assert(evolved.get(player, stamina) == 50) -``` - -We created an entity called `player` and two fragments called `health` and `stamina`. We attached the components `100` and `50` to the entity through these fragments. After that, we can retrieve the components using the [`evolved.get`](#evolvedget) function. - -We'll cover the [`evolved.set`](#evolvedset) and [`evolved.get`](#evolvedget) functions in more detail later in the section about [modifying operations](#modifying-operations). For now, let's just say that they are used to set and get components from entities through fragments. - -The main thing to understand here is that you can attach any data to any identifier by using other identifiers. - -### Traits - -Since fragments are just identifiers, you can use them as entities too! Fragments of fragments are usually called `traits`. This is very useful, for example, for marking fragments with some metadata. - -```lua -local evolved = require 'evolved' - -local serializable = evolved.id() - -local position = evolved.id() -evolved.set(position, serializable, true) - -local velocity = evolved.id() -evolved.set(velocity, serializable, true) - -local player = evolved.id() -evolved.set(player, position, {x = 0, y = 0}) -evolved.set(player, velocity, {x = 0, y = 0}) -``` - -In this example, we create a trait called `serializable` and mark the fragments `position` and `velocity` as serializable. After that, you can write a function that will serialize entities, and this function will serialize only fragments that are marked as serializable. This is a very powerful feature of the library, and it allows you to create very flexible systems. - -### Singletons - -Fragments can even be attached to themselves; this is called a singleton. Use this when you want to store some data without having a separate entity. For example, you can use it to store global data, like the game state or the current level. - -```lua -local evolved = require 'evolved' - -local gravity = evolved.id() -evolved.set(gravity, gravity, 10) - -assert(evolved.get(gravity, gravity) == 10) -``` - -## Chunks - -The next thing we need to understand is that all non-empty entities are stored in chunks. Chunks are just tables that store entities and their components together. Each unique combination of fragments is stored in a separate chunk. This means that if you have two entities with `health` and `stamina` fragments, they will be stored in the `` chunk. If you have another entity with `health`, `stamina`, and `mana` fragments, it will be stored in the `` chunk. This is very useful for performance reasons, as it allows us to store entities with the same fragments together, making it easier to iterate, filter, and process them. - -```lua -local evolved = require 'evolved' - -local health, stamina, mana = evolved.id(3) - -local entity1 = evolved.id() -evolved.set(entity1, health, 100) -evolved.set(entity1, stamina, 50) - -local entity2 = evolved.id() -evolved.set(entity2, health, 75) -evolved.set(entity2, stamina, 40) - -local entity3 = evolved.id() -evolved.set(entity3, health, 50) -evolved.set(entity3, stamina, 30) -evolved.set(entity3, mana, 20) -``` - -Here is what the chunks will look like after the code above has executed: - -| chunk | health | stamina | -| ------- | :----: | :-----: | -| entity1 | 100 | 50 | -| entity2 | 75 | 40 | - -| chunk | health | stamina | mana | -| ------- | :----: | :-----: | :---: | -| entity3 | 50 | 30 | 20 | - -Usually, you don't need to operate on chunks directly, but you can use the [`evolved.chunk`](#evolvedchunk) function to get the specific chunk. - -```lua ----@param fragment evolved.fragment ----@param ... evolved.fragment fragments ----@return evolved.chunk chunk -function evolved.chunk(fragment, ...) end -``` - -The [`evolved.chunk`](#evolvedchunk) function takes one or more fragments as arguments and returns the chunk for this combination. After that, you can use the chunk's methods to retrieve their entities, fragments, and components. - -```lua ----@return evolved.entity[] entity_list ----@return integer entity_count -function chunk_mt:entities() end - ----@return evolved.fragment[] fragment_list ----@return integer fragment_count -function chunk_mt:fragments() end - ----@param ... evolved.fragment fragments ----@return evolved.storage ... storages -function chunk_mt:components(...) -``` - -Full example: - -```lua -local evolved = require 'evolved' - -local health, stamina, mana = evolved.id(3) - -local entity1 = evolved.id() -evolved.set(entity1, health, 100) -evolved.set(entity1, stamina, 50) - -local entity2 = evolved.id() -evolved.set(entity2, health, 75) -evolved.set(entity2, stamina, 40) - -local entity3 = evolved.id() -evolved.set(entity3, health, 50) -evolved.set(entity3, stamina, 30) -evolved.set(entity3, mana, 20) - --- get (or create if it doesn't exist) the chunk -local chunk = evolved.chunk(health, stamina) - --- get the list of entities in the chunk and the number of them -local entity_list, entity_count = chunk:entities() - --- get the columns of components in the chunk -local health_components = chunk:components(health) -local stamina_components = chunk:components(stamina) - -for i = 1, entity_count do - local entity = entity_list[i] - - local entity_health = health_components[i] - local entity_stamina = stamina_components[i] - - -- do something with the entity and its components - print(string.format( - 'Entity: %d, Health: %d, Stamina: %d', - entity, entity_health, entity_stamina)) -end - --- Expected output: --- Entity: 1048602, Health: 100, Stamina: 50 --- Entity: 1048603, Health: 75, Stamina: 40 -``` - -## Structural Changes - -Every time we add or remove a fragment from an entity, the entity will be migrated to a new chunk. This is done automatically by the library, of course. However, you should be aware of this because it can affect performance, especially if you have many fragments on the entity. This is called a `structural change`. - -You should try to avoid structural changes, especially in performance-critical code. For example, you can spawn entities with all the fragments they will ever need and avoid changing them during the entity's lifetime. Overriding existing components is not a structural change, so you can do it freely. - -### Spawning Entities - -```lua ----@param components? table ----@return evolved.entity -function evolved.spawn(components) end - ----@param prefab evolved.entity ----@param components? table ----@return evolved.entity -function evolved.clone(prefab, components) end -``` - -The [`evolved.spawn`](#evolvedspawn) function allows you to spawn an entity with all the necessary fragments. It takes a table of components as an argument, where the keys are fragments and the values are components. By the way, you don't need to create this `components` table every time; consider using a predefined table for maximum performance. - -You can also use the [`evolved.clone`](#evolvedclone) function to clone an existing entity. This is useful for creating entities with the same fragments as an existing entity but with different components. - -```lua -local evolved = require 'evolved' - -local health, stamina = evolved.id(2) - --- spawn an entity with all the necessary fragments -local enemy1 = evolved.spawn { - [health] = 100, - [stamina] = 50, -} - --- spawn another entity with the same fragments, --- but with a different component for some of them -local enemy2 = evolved.clone(enemy1, { - [health] = 50, -}) - --- there are no structural changes here, --- we just override existing components -evolved.set(enemy1, health, 75) -evolved.set(enemy1, stamina, 42) -``` - -### Entity Builders - -Another way to avoid structural changes when spawning entities is to use the [`evolved.builder`](#evolvedbuilder) fluid interface. The [`evolved.builder`](#evolvedbuilder) function returns a builder object that allows you to spawn entities with a specific set of fragments and components without necessity setting them one by one with structural changes for each change. - -```lua -local evolved = require 'evolved' - -local health, stamina = evolved.id(2) - -local enemy = evolved.builder() - :set(health, 100) - :set(stamina, 50) - :spawn() -``` - -Builders can be reused, so you can create a builder with a specific set of fragments and components and then use it to spawn multiple entities with the same fragments and components. - -## Access Operations - -The library provides all the necessary functions to access entities and their components. I'm not going to cover all the accessor functions here, because they are pretty straightforward and self-explanatory. You can check the [API Reference](#api-reference) for all of them. Here are some of the most important ones: - -```lua ----@param entity evolved.entity ----@return boolean -function evolved.alive(entity) end - ----@param entity evolved.entity ----@return boolean -function evolved.empty(entity) end - ----@param entity evolved.entity ----@param fragment evolved.fragment -function evolved.has(entity, fragment) end - ----@param entity evolved.entity ----@param ... evolved.fragment fragments ----@return evolved.component ... components -function evolved.get(entity, ...) end -``` - -The [`evolved.alive`](#evolvedalive) function checks whether an entity is alive. The [`evolved.empty`](#evolvedempty) function checks whether an entity is empty (has no fragments). The [`evolved.has`](#evolvedhas) function checks whether an entity has a specific fragment. The [`evolved.get`](#evolvedget) function retrieves the components of an entity for the specified fragments. If the entity doesn't have some of the fragments, the function will return `nil` for them. - -All of these functions can be safely called on non-alive entities and non-alive fragments. Also, they do not cause any structural changes, because they do not modify anything. - -## Modifying Operations - -The library provides a classic set of functions for modifying entities. These functions are used to add, override, and remove fragments from entities. - -```lua ----@param entity evolved.entity ----@param fragment evolved.fragment ----@param component evolved.component -function evolved.set(entity, fragment, component) end - ----@param entity evolved.entity ----@param ... evolved.fragment fragments -function evolved.remove(entity, ...) - ----@param ... evolved.entity entities -function evolved.clear(...) - ----@param ... evolved.entity entities -function evolved.destroy(...) -``` - -The [`evolved.set`](#evolvedset) function is used to set a component on an entity. If the entity doesn't have this fragment, it will be added, with causing a structural change, of course. If the entity already has the fragment, the component will be overridden. The function should not be called on non-alive entities, because it is not possible to set any component on a destroyed entity, ignoring this can lead to errors. [Debug Mode](#debug-mode) can be used to check this kind of error. - -Use the [`evolved.remove`](#evolvedremove) function to remove fragments from an entity. If the entity doesn't have some of the fragments, they will be ignored. When one or more fragments are removed from an entity, the entity will be migrated to a new chunk, which is a structural change. When you want to remove more than one fragment, pass all of them as arguments. Do not remove fragments one by one, as this will cause a structural change for each fragment. The [`evolved.remove`](#evolvedremove) function will ignore non-alive entities, because post-conditions are satisfied (destroyed entities do not have any fragments, including those that we want to remove). - -To remove all fragments from an entity, use the [`evolved.clear`](#evolvedclear) function. This function will remove all fragments at once, with causing only one structural change. The [`evolved.clear`](#evolvedclear) function does not destroy the entity, it just removes all fragments from it. The entity after this operation will be empty, but it will be still alive. You can use this function to clear more than one entity at once, passing them as arguments. The function will ignore empty and non-alive entities. - -To destroy an entity, use the [`evolved.destroy`](#evolveddestroy) function. This function will remove all fragments from the entity and free the identifier of the entity for reuse. The [`evolved.destroy`](#evolveddestroy) function will ignore non-alive entities. To destroy more than one entity, pass them as arguments. - -## Debug Mode - -The library has a debug mode that can be enabled by the [`evolved.debug_mode`](#evolveddebug_mode) function. When the debug mode is enabled, the library will check for incorrect usages of the API and throw errors when they are detected. This is very useful for debugging and development, but it can slow down performance a bit. - -```lua ----@param yesno boolean -function evolved.debug_mode(yesno) end -``` - -The debug mode is disabled by default, so you need to enable it manually. I strongly recommend doing this in the development environment. You can even leave it enabled in production, but only if you are sure the performance is acceptable for your case. - -```lua -local evolved = require 'evolved' - -evolved.debug_mode(true) - -local entity = evolved.id() - -local fragment = evolved.id() -evolved.destroy(fragment) - --- try to use the destroyed fragment -evolved.set(entity, fragment, 42) - --- [error] | evolved.lua | the fragment ($1048599#23:1) is not alive and cannot be used -``` - -## Queries - -One of the most important features of any ECS library is the ability to process entities by filters or queries. `evolved.lua` provides a simple and efficient way to do this. - -First, you need to create a query that describes which entities you want to process. You can specify fragments you want to include, and fragments you want to exclude. Queries are just identifiers with a special predefined fragments: [`evolved.INCLUDES`](#evolvedincludes) and [`evolved.EXCLUDES`](#evolvedexcludes). These fragments expect a list of fragments as their components. - -```lua -local evolved = require 'evolved' - -local health, poisoned, resistant = evolved.id(3) - -local query = evolved.id() -evolved.set(query, evolved.INCLUDES, { health, poisoned }) -evolved.set(query, evolved.EXCLUDES, { resistant }) -``` - -The builder interface can be used to create queries too. It is more convenient to use, because the builder has special methods for including and excluding fragments. Here is a simple example of this: - -```lua -local query = evolved.builder() - :include(health, poisoned) - :exclude(resistant) - :spawn() -``` - -We don't have to set both [`evolved.INCLUDES`](#evolvedincludes) and [`evolved.EXCLUDES`](#evolvedexcludes) fragments, we can even do it without filters at all, then the query will match all chunks in the world. - -After the query is created, we are ready to process our filtered by this query entities. You can do this by using the [`evolved.execute`](#evolvedexecute) function. This function takes a query as an argument and returns an iterator that can be used to iterate over all matching with the query chunks. - -```lua ----@param query evolved.query ----@return evolved.execute_iterator iterator ----@return evolved.execute_state? iterator_state -function evolved.execute(query) end -``` - -```lua -for chunk, entity_list, entity_count in evolved.execute(query) do - ---@type number[] - local health_components = chunk:components(health) - - for i = 1, entity_count do - health_components[i] = health_components[i] - 1 - end -end -``` - -As you can see, `evolved.execute_iterator` returns a chunk, a list of entities in the chunk, and the number of entities in this chunk. We [already know](#chunks) how to use chunks, so we can use the chunk's methods to retrieve the components of the entities in the chunk, change them, and so on. - -But I haven't mentioned one important thing yet: [structural changes](#structural-changes) are not allowed during any iteration over chunks. This means that you cannot add or remove fragments from entities while iterating. Also, you cannot destroy or spawn entities because this will cause structural changes too. This is done to avoid inconsistencies in the iteration process. If we allow structural changes here, we might skip some entities during iteration, or process the same entity multiple times. The [debug mode](#debug-mode) can catch this kind of error. - -### Deferred Operations - -Now we know that structural changes are not allowed during iteration, but what if we want to make some structural changes after the iteration is finished? For example, we might want to remove some fragments from entities after we have processed them, or we might want to spawn new entities while processing existing ones. To do all of this, we can use deferred operations. - -```lua ----@return boolean started -function evolved.defer() end - ----@return boolean committed -function evolved.commit() end -``` - -The [`evolved.defer`](#evolveddefer) function starts a deferred scope. This means that all changes made inside the scope will be queued and applied after leaving the scope. The [`evolved.commit`](#evolvedcommit) function closes a last deferred scope and applies all queued changes. These functions can be nested, so you can start a new deferred scope inside an existing one. The [`evolved.commit`](#evolvedcommit) function will apply all queued changes only when the last deferred scope is closed. - -```lua -local evolved = require 'evolved' - -local health, poisoned = evolved.id(2) - -local player = evolved.builder() - :set(health, 100) - :set(poisoned, true) - :spawn() - --- start a deferred scope -evolved.defer() - --- this removal will be queued, not applied immediately -evolved.remove(player, poisoned) - --- the player still has the poisoned fragment inside the deferred scope -assert(evolved.has(player, poisoned)) - --- commit the deferred operations -evolved.commit() - --- now the poisoned fragment is removed -assert(not evolved.has(player, poisoned)) -``` - -### Batch Operations - -The library provides a set of functions for batch operations. These functions are used to perform modifying operations on multiple chunks at once. This is very useful for performance reasons. - -```lua ----@param query evolved.query ----@param fragment evolved.fragment ----@param component evolved.component -function evolved.batch_set(query, fragment, component) end - ----@param query evolved.query ----@param ... evolved.fragment fragments -function evolved.batch_remove(query, ...) end - ----@param ... evolved.query queries -function evolved.batch_clear(...) end - ----@param ... evolved.query queries -function evolved.batch_destroy(...) end -``` - -These functions are similar to the common [modifying operations](#modifying-operations), but they take a query as an argument instead of an entity. Here is a classic example that provides a huge performance boost when applied. - -```lua -local evolved = require 'evolved' - -local destroying_mark = evolved.id() - -local destroying_mark_query = evolved.builder() - :include(destroying_mark) - :spawn() - --- destroy all entities with the destroying_mark fragment -evolved.batch_destroy(destroying_mark_query) -``` - -You should always prefer batch operations over common modifying operations when you need to perform simple operations like destroying or removing fragments from multiple entities at once. Instead of applying the operation to each entity one by one, batch operations will apply the operation chunk by chunk. - -In all other respects, batch operations behave the same way as the common modifying operations that we have already covered. Of course, they can also be used with [deferred operations](#deferred-operations). - -## Systems - -Usually, we want to organize our processing of entities into systems that will be executed in a specific order. The library has a way to do this using special [`evolved.QUERY`](#evolvedquery) and [`evolved.EXECUTE`](#evolvedexecute) fragments that are used to specify the system's queries and execution callbacks. And yes, systems are just entities with special fragments. - -```lua -local evolved = require 'evolved' - -local health, max_health = evolved.id(2) - -local query = evolved.builder() - :include(health, max_health) - :spawn() - -local system = evolved.builder() - :query(query) - :execute(function(chunk, entity_list, entity_count) - local health_components = chunk:components(health) - local max_health_components = chunk:components(max_health) - - for i = 1, entity_count do - health_components[i] = math.min( - health_components[i] + 1, - max_health_components[i]) - end - end):spawn() -``` - -The [`evolved.process`](#evolvedprocess) function is used to process systems. It takes systems as arguments and executes them in the order they were passed. - -```lua ----@param ... evolved.system systems -function evolved.process(...) end -``` - -To group systems together, you can use the [`evolved.GROUP`](#evolvedgroup) fragment. Systems with a specified group will be processed when you call the [`evolved.process`](#evolvedprocess) function with this group. For example, you can group all physics systems together and process them in one [`evolved.process`](#evolvedprocess) call. - -```lua -local evolved = require 'evolved' - -local gravity_x = 0 -local gravity_y = -9.81 - -local position_x, position_y = evolved.id(2) -local velocity_x, velocity_y = evolved.id(2) - -local physical_body_query = evolved.builder() - :include(position_x, position_y) - :include(velocity_x, velocity_y) - :spawn() - -local physics_group = evolved.id() - -evolved.builder() - :group(physics_group) - :query(physical_body_query) - :execute(function(chunk, entity_list, entity_count) - local vx = chunk:components(velocity_x) - local vy = chunk:components(velocity_y) - - for i = 1, entity_count do - vx[i] = vx[i] + gravity_x - vy[i] = vy[i] + gravity_y - end - end):spawn() - -evolved.builder() - :group(physics_group) - :query(physical_body_query) - :execute(function(chunk, entity_list, entity_count) - local px = chunk:components(position_x) - local py = chunk:components(position_y) - - local vx = chunk:components(velocity_x) - local vy = chunk:components(velocity_y) - - for i = 1, entity_count do - px[i] = px[i] + vx[i] - py[i] = py[i] + vy[i] - end - end):spawn() - -evolved.process(physics_group) -``` - -Systems and groups also can have the [`evolved.PROLOGUE`](#evolvedprologue) and [`evolved.EPILOGUE`](#evolvedepilogue) fragments. These fragments are used to specify callbacks that will be executed before and after the system or group is processed. This is useful for setting up and tearing down systems or groups, or for performing some additional processing before or after the main processing. - -```lua -local evolved = require 'evolved' - -local system = evolved.builder() - :prologue(function() - print('Prologue') - end) - :epilogue(function() - print('Epilogue') - end) - :spawn() - -evolved.process(system) -``` - -The prologue and epilogue fragments do not require an explicit query. They will be executed before and after the system is processed, regardless of the query. - -And one more thing about systems. Execution callbacks are called in the [deferred scope](#deferred-operations), this means that all modifying operations inside the callback will be queued and applied after the system processed all chunks. But prologue and epilogue callbacks are not called in the deferred scope, so all modifying operations inside them will be applied immediately. This is done to avoid confusion and to make it clear that prologue and epilogue callbacks are not part of the chunk processing. - -## Advanced Topics - -### Fragment Tags - -Sometimes you want to have a fragment without a component. For example, you might want to have some marks that will be used to mark entities for processing. Fragments without components are called `tags`. Such fragments take up less memory, because they do not require any components to be stored. Migration of entities with tags is faster, because the library does not need to migrate components, only the tags themselves. To create a tag, mark the fragment with the [`evolved.TAG`](#evolvedtag) fragment. - -```lua -local evolved = require 'evolved' - -local player_tag = evolved.id() -evolved.set(player_tag, evolved.TAG) - -local player = evolved.id() -evolved.set(player, player_tag) - --- player has the player_tag fragment -assert(evolved.has(player, player_tag)) - --- player_tag is a tag, so it doesn't have a component -assert(evolved.get(player, player_tag) == nil) -``` - -### Fragment Hooks - -The library provides a way to execute callbacks when fragments are set, assigned, inserted, or removed from entities. This is done using special fragments: [`evolved.ON_SET`](#evolvedon_set), [`evolved.ON_ASSIGN`](#evolvedon_assign), [`evolved.ON_INSERT`](#evolvedon_insert), and [`evolved.ON_REMOVE`](#evolvedon_remove). These fragments are used to specify the callbacks that will be executed when the corresponding operation is performed on the fragment. - -```lua -local evolved = require 'evolved' - -local health = evolved.builder() - :on_set(function(entity, fragment, component) - print('health set to ' .. component) - end):spawn() - -local player = evolved.id() -evolved.set(player, health, 100) -- prints "health set to 100" -evolved.set(player, health, 200) -- prints "health set to 200" -``` - -Use [`evolved.ON_SET`](#evolvedon_set) for callbacks on fragment insert or override, [`evolved.ON_ASSIGN`](#evolvedon_assign) for overrides, and [`evolved.ON_INSERT`](#evolvedon_insert)/[`evolved.ON_REMOVE`](#evolvedon_remove) for insertions or removals. - -# API Reference - -## Predefs - -### `evolved.TAG` - -### `evolved.NAME` - -### `evolved.UNIQUE` - -### `evolved.EXPLICIT` - -### `evolved.DEFAULT` - -### `evolved.DUPLICATE` - -### `evolved.PREFAB` - -### `evolved.DISABLED` - -### `evolved.INCLUDES` - -### `evolved.EXCLUDES` - -### `evolved.ON_SET` - -### `evolved.ON_ASSIGN` - -### `evolved.ON_INSERT` - -### `evolved.ON_REMOVE` - -### `evolved.GROUP` - -### `evolved.QUERY` - -### `evolved.EXECUTE` - -### `evolved.PROLOGUE` - -### `evolved.EPILOGUE` - -### `evolved.DESTROY_POLICY` - -## Functions - -### `evolved.id` - -```lua ----@param count? integer ----@return evolved.id ... ids ----@nodiscard -function evolved.id(count) end -``` - -### `evolved.pack` - -```lua ----@param index integer ----@param version integer ----@return evolved.id id ----@nodiscard -function evolved.pack(index, version) end -``` - -### `evolved.unpack` - -```lua ----@param id evolved.id ----@return integer index ----@return integer version ----@nodiscard -function evolved.unpack(id) end -``` - -### `evolved.defer` - -```lua ----@return boolean started -function evolved.defer() end -``` - -### `evolved.commit` - -```lua ----@return boolean committed -function evolved.commit() end -``` - -### `evolved.spawn` - -```lua ----@param components? table ----@return evolved.entity -function evolved.spawn(components) end -``` - -### `evolved.clone` - -```lua ----@param prefab evolved.entity ----@param components? table ----@return evolved.entity -function evolved.clone(prefab, components) end -``` - -### `evolved.alive` - -```lua ----@param entity evolved.entity ----@return boolean ----@nodiscard -function evolved.alive(entity) end -``` - -### `evolved.alive_all` - -```lua ----@param ... evolved.entity entities ----@return boolean ----@nodiscard -function evolved.alive_all(...) end -``` - -### `evolved.alive_any` - -```lua ----@param ... evolved.entity entities ----@return boolean ----@nodiscard -function evolved.alive_any(...) end -``` - -### `evolved.empty` - -```lua ----@param entity evolved.entity ----@return boolean ----@nodiscard -function evolved.empty(entity) end -``` - -### `evolved.empty_all` - -```lua ----@param ... evolved.entity entities ----@return boolean ----@nodiscard -function evolved.empty_all(...) end -``` - -### `evolved.empty_any` - -```lua ----@param ... evolved.entity entities ----@return boolean ----@nodiscard -function evolved.empty_any(...) end -``` - -### `evolved.has` - -```lua ----@param entity evolved.entity ----@param fragment evolved.fragment ----@return boolean ----@nodiscard -function evolved.has(entity, fragment) end -``` - -### `evolved.has_all` - -```lua ----@param entity evolved.entity ----@param ... evolved.fragment fragments ----@return boolean ----@nodiscard -function evolved.has_all(entity, ...) end -``` - -### `evolved.has_any` - -```lua ----@param entity evolved.entity ----@param ... evolved.fragment fragments ----@return boolean ----@nodiscard -function evolved.has_any(entity, ...) end -``` - -### `evolved.get` - -```lua ----@param entity evolved.entity ----@param ... evolved.fragment fragments ----@return evolved.component ... components ----@nodiscard -function evolved.get(entity, ...) end -``` - -### `evolved.set` - -```lua ----@param entity evolved.entity ----@param fragment evolved.fragment ----@param component evolved.component -function evolved.set(entity, fragment, component) end -``` - -### `evolved.remove` - -```lua ----@param entity evolved.entity ----@param ... evolved.fragment fragments -function evolved.remove(entity, ...) end -``` - -### `evolved.clear` - -```lua ----@param ... evolved.entity entities -function evolved.clear(...) end -``` - -### `evolved.destroy` - -```lua ----@param ... evolved.entity entities -function evolved.destroy(...) end -``` - -### `evolved.batch_set` - -```lua ----@param query evolved.query ----@param fragment evolved.fragment ----@param component evolved.component -function evolved.batch_set(query, fragment, component) end -``` - -### `evolved.batch_remove` - -```lua ----@param query evolved.query ----@param ... evolved.fragment fragments -function evolved.batch_remove(query, ...) end -``` - -### `evolved.batch_clear` - -```lua ----@param ... evolved.query queries -function evolved.batch_clear(...) end -``` - -### `evolved.batch_destroy` - -```lua ----@param ... evolved.query queries -function evolved.batch_destroy(...) end -``` - -### `evolved.each` - -```lua ----@param entity evolved.entity ----@return evolved.each_iterator iterator ----@return evolved.each_state? iterator_state ----@nodiscard -function evolved.each(entity) end -``` - -### `evolved.execute` - -```lua ----@param query evolved.query ----@return evolved.execute_iterator iterator ----@return evolved.execute_state? iterator_state ----@nodiscard -function evolved.execute(query) end -``` - -### `evolved.process` - -```lua ----@param ... evolved.system systems -function evolved.process(...) end -``` - -### `evolved.debug_mode` - -```lua ----@param yesno boolean -function evolved.debug_mode(yesno) end -``` - -### `evolved.collect_garbage` - -```lua -function evolved.collect_garbage() end -``` - -## Chunk - -### `evolved.chunk` - -```lua ----@param fragment evolved.fragment ----@param ... evolved.fragment fragments ----@return evolved.chunk chunk ----@return evolved.entity[] entity_list ----@return integer entity_count ----@nodiscard -function evolved.chunk(fragment, ...) end -``` - -### `evolved.chunk_mt:alive` - -```lua ----@return boolean ----@nodiscard -function evolved.chunk_mt:alive() end -``` - -### `evolved.chunk_mt:empty` - -```lua ----@return boolean ----@nodiscard -function evolved.chunk_mt:empty() end -``` - -### `evolved.chunk_mt:has` - -```lua ----@param fragment evolved.fragment ----@return boolean ----@nodiscard -function evolved.chunk_mt:has(fragment) end -``` - -### `evolved.chunk_mt:has_all` - -```lua ----@param ... evolved.fragment fragments ----@return boolean ----@nodiscard -function evolved.chunk_mt:has_all(...) end -``` - -### `evolved.chunk_mt:has_any` - -```lua ----@param ... evolved.fragment fragments ----@return boolean ----@nodiscard -function evolved.chunk_mt:has_any(...) end -``` - -### `evolved.chunk_mt:entities` - -```lua ----@return evolved.entity[] entity_list ----@return integer entity_count ----@nodiscard -function evolved.chunk_mt:entities() end -``` - -### `evolved.chunk_mt:fragments` - -```lua ----@return evolved.fragment[] fragment_list ----@return integer fragment_count ----@nodiscard -function evolved.chunk_mt:fragments() end -``` - -### `evolved.chunk_mt:components` - -```lua ----@param ... evolved.fragment fragments ----@return evolved.storage ... storages ----@nodiscard -function evolved.chunk_mt:components(...) end -``` - -## Builder - -### `evolved.builder` - -```lua ----@return evolved.builder builder ----@nodiscard -function evolved.builder() end -``` - -### `evolved.builder_mt:spawn` - -```lua ----@return evolved.entity -function evolved.builder_mt:spawn() end -``` - -### `evolved.builder_mt:clone` - -```lua ----@param prefab evolved.entity ----@return evolved.entity -function evolved.builder_mt:clone(prefab) end -``` - -### `evolved.builder_mt:has` - -```lua ----@param fragment evolved.fragment ----@return boolean ----@nodiscard -function evolved.builder_mt:has(fragment) end -``` - -### `evolved.builder_mt:has_all` - -```lua ----@param ... evolved.fragment fragments ----@return boolean ----@nodiscard -function evolved.builder_mt:has_all(...) end -``` - -### `evolved.builder_mt:has_any` - -```lua ----@param ... evolved.fragment fragments ----@return boolean ----@nodiscard -function evolved.builder_mt:has_any(...) end -``` - -### `evolved.builder_mt:get` - -```lua ----@param ... evolved.fragment fragments ----@return evolved.component ... components ----@nodiscard -function evolved.builder_mt:get(...) end -``` - -### `evolved.builder_mt:set` - -```lua ----@param fragment evolved.fragment ----@param component evolved.component ----@return evolved.builder builder -function evolved.builder_mt:set(fragment, component) end -``` - -### `evolved.builder_mt:remove` - -```lua ----@param ... evolved.fragment fragments ----@return evolved.builder builder -function evolved.builder_mt:remove(...) end -``` - -### `evolved.builder_mt:clear` - -```lua ----@return evolved.builder builder -function evolved.builder_mt:clear() end -``` - -### `evolved.builder_mt:tag` - -```lua ----@return evolved.builder builder -function evolved.builder_mt:tag() end -``` - -### `evolved.builder_mt:name` - -```lua ----@param name string ----@return evolved.builder builder -function evolved.builder_mt:name(name) end -``` - -### `evolved.builder_mt:unique` - -```lua ----@return evolved.builder builder -function evolved.builder_mt:unique() end -``` - -### `evolved.builder_mt:explicit` - -```lua ----@return evolved.builder builder -function evolved.builder_mt:explicit() end -``` - -### `evolved.builder_mt:default` - -```lua ----@param default evolved.component ----@return evolved.builder builder -function evolved.builder_mt:default(default) end -``` - -### `evolved.builder_mt:duplicate` - -```lua ----@param duplicate evolved.duplicate ----@return evolved.builder builder -function evolved.builder_mt:duplicate(duplicate) end -``` - -### `evolved.builder_mt:prefab` - -```lua ----@return evolved.builder builder -function evolved.builder_mt:prefab() end -``` - -### `evolved.builder_mt:disabled` - -```lua ----@return evolved.builder builder -function evolved.builder_mt:disabled() end -``` - -### `evolved.builder_mt:include` - -```lua ----@param ... evolved.fragment fragments ----@return evolved.builder builder -function evolved.builder_mt:include(...) end -``` - -### `evolved.builder_mt:exclude` - -```lua ----@param ... evolved.fragment fragments ----@return evolved.builder builder -function evolved.builder_mt:exclude(...) end -``` - -### `evolved.builder_mt:on_set` - -```lua ----@param on_set evolved.set_hook ----@return evolved.builder builder -function evolved.builder_mt:on_set(on_set) end -``` - -### `evolved.builder_mt:on_assign` - -```lua ----@param on_assign evolved.assign_hook ----@return evolved.builder builder -function evolved.builder_mt:on_assign(on_assign) end -``` - -### `evolved.builder_mt:on_insert` - -```lua ----@param on_insert evolved.insert_hook ----@return evolved.builder builder -function evolved.builder_mt:on_insert(on_insert) end -``` - -### `evolved.builder_mt:on_remove` - -```lua ----@param on_remove evolved.remove_hook ----@return evolved.builder builder -function evolved.builder_mt:on_remove(on_remove) end -``` - -### `evolved.builder_mt:group` - -```lua ----@param group evolved.system ----@return evolved.builder builder -function evolved.builder_mt:group(group) end -``` - -### `evolved.builder_mt:query` - -```lua ----@param query evolved.query ----@return evolved.builder builder -function evolved.builder_mt:query(query) end -``` - -### `evolved.builder_mt:execute` - -```lua ----@param execute evolved.execute ----@return evolved.builder builder -function evolved.builder_mt:execute(execute) end -``` - -### `evolved.builder_mt:prologue` - -```lua ----@param prologue evolved.prologue ----@return evolved.builder builder -function evolved.builder_mt:prologue(prologue) end -``` - -### `evolved.builder_mt:epilogue` - -```lua ----@param epilogue evolved.epilogue ----@return evolved.builder builder -function evolved.builder_mt:epilogue(epilogue) end -``` - -### `evolved.builder_mt:destroy_policy` - -```lua ----@param destroy_policy evolved.id ----@return evolved.builder builder -function evolved.builder_mt:destroy_policy(destroy_policy) end -``` diff --git a/README.md b/README.md index 8871b02..1f7ce75 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,19 @@ - [lua](https://www.lua.org/) **>= 5.1** - [luajit](https://luajit.org/) **>= 2.0** -## Aliases +## Installation + +You can install `evolved.lua` using [luarocks](https://luarocks.org/) with the following command: + +```bash +luarocks install evolved.lua +``` + +Or just clone the [repository](https://github.com/BlackMATov/evolved.lua) and copy the [evolved.lua](evolved.lua) file to your project. + +## Cheat Sheet + +### Aliases ``` id :: implementation-specific @@ -56,7 +68,7 @@ each_iterator :: {each_state? -> fragment?, component?} execute_iterator :: {execute_state? -> chunk?, entity[]?, integer?} ``` -## Predefs +### Predefs ``` TAG :: fragment @@ -92,7 +104,7 @@ DESTROY_POLICY_DESTROY_ENTITY :: id DESTROY_POLICY_REMOVE_FRAGMENT :: id ``` -## Functions +### Functions ``` id :: integer? -> id... @@ -139,7 +151,9 @@ debug_mode :: boolean -> () collect_garbage :: () ``` -## Chunk +### Classes + +#### Chunk ``` chunk :: fragment, fragment... -> chunk, entity[], integer @@ -156,7 +170,7 @@ chunk_mt:fragments :: fragment[], integer chunk_mt:components :: fragment... -> storage... ``` -## Builder +#### Builder ``` builder :: builder @@ -205,4 +219,1315 @@ builder_mt:epilogue :: {} -> builder builder_mt:destroy_policy :: id -> builder ``` +## Overview + +### Identifiers + +An identifier is a packed 40-bit integer. The first 20 bits represent the index, and the last 20 bits represent the version. To create a new identifier, use the [`evolved.id`](#evolvedid) function. + +```lua +---@param count? integer +---@return evolved.id ... ids +function evolved.id(count) end +``` + +The `count` parameter is optional and defaults to `1`. The function returns one or more identifiers depending on the `count` parameter. The maximum number of alive identifiers is `2^20-1` (1048575). After that, the function will throw an error: `| evolved.lua | id index overflow`. + +Identifiers can be recycled. When an identifier is no longer needed, use the [`evolved.destroy`](#evolveddestroy) function to destroy it. This will free the identifier for reuse. + +```lua +---@param ... evolved.id ids +function evolved.destroy(...) end +``` + +The [`evolved.destroy`](#evolveddestroy) function takes one or more identifiers as arguments. Destroyed identifiers will be added to the recycler free list. It is safe to call [`evolved.destroy`](#evolveddestroy) on identifiers that are not alive; the function will simply ignore them. + +After destroying an identifier, it can be reused by calling the [`evolved.id`](#evolvedid) function again. The new identifier will have the same index as the destroyed one, but a different version. The version is incremented each time an identifier is destroyed. This mechanism allows us to reuse indices and to know whether an identifier is alive or not. + +The set of [`evolved.alive`](#evolvedalive) functions can be used to check whether identifiers are alive. + +```lua +---@param id evolved.id +---@return boolean +function evolved.alive(id) end + +---@param ... evolved.id ids +---@return boolean +function evolved.alive_all(...) end + +---@param ... evolved.id ids +---@return boolean +function evolved.alive_any(...) end +``` + +Sometimes (for debugging purposes, for example), it is necessary to extract the index and version from an identifier or to pack them back into an identifier. The [`evolved.pack`](#evolvedpack) and [`evolved.unpack`](#evolvedunpack) functions can be used for this purpose. + +```lua +---@param index integer +---@param version integer +---@return evolved.id id +function evolved.pack(index, version) end + +---@param id evolved.id +---@return integer index +---@return integer version +function evolved.unpack(id) end +``` + +Here is a short example of how to use identifiers: + +```lua +local evolved = require 'evolved' + +local id = evolved.id() -- create a new identifier +assert(evolved.alive(id)) -- check that the identifier is alive + +local index, version = evolved.unpack(id) -- unpack the identifier +assert(evolved.pack(index, version) == id) -- pack it back + +evolved.destroy(id) -- destroy the identifier +assert(not evolved.alive(id)) -- check that the identifier is not alive now +``` + +### Entities, Fragments, and Components + +First, you need to understand that entities and fragments are just identifiers. The difference between them is purely semantic. Entities are used to represent objects in the world, while fragments are used to represent types of components that can be attached to entities. Components, on the other hand, are any data that is attached to entities through fragments. + +```lua +---@alias evolved.entity evolved.id +---@alias evolved.fragment evolved.id +---@alias evolved.component any +``` + +Here is a simple example of how to attach a component to an entity: + +```lua +local evolved = require 'evolved' + +local entity, fragment = evolved.id(2) + +evolved.set(entity, fragment, 100) +assert(evolved.get(entity, fragment) == 100) +``` + +I know it's not very clear yet, but don't worry, we'll get there. In the next example, I'm going to name the entity and fragment, so it will be easier to understand what's going on here. + +```lua +local evolved = require 'evolved' + +local player = evolved.id() + +local health = evolved.id() +local stamina = evolved.id() + +evolved.set(player, health, 100) +evolved.set(player, stamina, 50) + +assert(evolved.get(player, health) == 100) +assert(evolved.get(player, stamina) == 50) +``` + +We created an entity called `player` and two fragments called `health` and `stamina`. We attached the components `100` and `50` to the entity through these fragments. After that, we can retrieve the components using the [`evolved.get`](#evolvedget) function. + +We'll cover the [`evolved.set`](#evolvedset) and [`evolved.get`](#evolvedget) functions in more detail later in the section about [modifying operations](#modifying-operations). For now, let's just say that they are used to set and get components from entities through fragments. + +The main thing to understand here is that you can attach any data to any identifier by using other identifiers. + +#### Traits + +Since fragments are just identifiers, you can use them as entities too! Fragments of fragments are usually called `traits`. This is very useful, for example, for marking fragments with some metadata. + +```lua +local evolved = require 'evolved' + +local serializable = evolved.id() + +local position = evolved.id() +evolved.set(position, serializable, true) + +local velocity = evolved.id() +evolved.set(velocity, serializable, true) + +local player = evolved.id() +evolved.set(player, position, {x = 0, y = 0}) +evolved.set(player, velocity, {x = 0, y = 0}) +``` + +In this example, we create a trait called `serializable` and mark the fragments `position` and `velocity` as serializable. After that, you can write a function that will serialize entities, and this function will serialize only fragments that are marked as serializable. This is a very powerful feature of the library, and it allows you to create very flexible systems. + +#### Singletons + +Fragments can even be attached to themselves; this is called a singleton. Use this when you want to store some data without having a separate entity. For example, you can use it to store global data, like the game state or the current level. + +```lua +local evolved = require 'evolved' + +local gravity = evolved.id() +evolved.set(gravity, gravity, 10) + +assert(evolved.get(gravity, gravity) == 10) +``` + +### Chunks + +The next thing we need to understand is that all non-empty entities are stored in chunks. Chunks are just tables that store entities and their components together. Each unique combination of fragments is stored in a separate chunk. This means that if you have two entities with `health` and `stamina` fragments, they will be stored in the `` chunk. If you have another entity with `health`, `stamina`, and `mana` fragments, it will be stored in the `` chunk. This is very useful for performance reasons, as it allows us to store entities with the same fragments together, making it easier to iterate, filter, and process them. + +```lua +local evolved = require 'evolved' + +local health, stamina, mana = evolved.id(3) + +local entity1 = evolved.id() +evolved.set(entity1, health, 100) +evolved.set(entity1, stamina, 50) + +local entity2 = evolved.id() +evolved.set(entity2, health, 75) +evolved.set(entity2, stamina, 40) + +local entity3 = evolved.id() +evolved.set(entity3, health, 50) +evolved.set(entity3, stamina, 30) +evolved.set(entity3, mana, 20) +``` + +Here is what the chunks will look like after the code above has executed: + +| chunk | health | stamina | +| ------- | :----: | :-----: | +| entity1 | 100 | 50 | +| entity2 | 75 | 40 | + +| chunk | health | stamina | mana | +| ------- | :----: | :-----: | :---: | +| entity3 | 50 | 30 | 20 | + +Usually, you don't need to operate on chunks directly, but you can use the [`evolved.chunk`](#evolvedchunk) function to get the specific chunk. + +```lua +---@param fragment evolved.fragment +---@param ... evolved.fragment fragments +---@return evolved.chunk chunk +function evolved.chunk(fragment, ...) end +``` + +The [`evolved.chunk`](#evolvedchunk) function takes one or more fragments as arguments and returns the chunk for this combination. After that, you can use the chunk's methods to retrieve their entities, fragments, and components. + +```lua +---@return evolved.entity[] entity_list +---@return integer entity_count +function chunk_mt:entities() end + +---@return evolved.fragment[] fragment_list +---@return integer fragment_count +function chunk_mt:fragments() end + +---@param ... evolved.fragment fragments +---@return evolved.storage ... storages +function chunk_mt:components(...) +``` + +Full example: + +```lua +local evolved = require 'evolved' + +local health, stamina, mana = evolved.id(3) + +local entity1 = evolved.id() +evolved.set(entity1, health, 100) +evolved.set(entity1, stamina, 50) + +local entity2 = evolved.id() +evolved.set(entity2, health, 75) +evolved.set(entity2, stamina, 40) + +local entity3 = evolved.id() +evolved.set(entity3, health, 50) +evolved.set(entity3, stamina, 30) +evolved.set(entity3, mana, 20) + +-- get (or create if it doesn't exist) the chunk +local chunk = evolved.chunk(health, stamina) + +-- get the list of entities in the chunk and the number of them +local entity_list, entity_count = chunk:entities() + +-- get the columns of components in the chunk +local health_components = chunk:components(health) +local stamina_components = chunk:components(stamina) + +for i = 1, entity_count do + local entity = entity_list[i] + + local entity_health = health_components[i] + local entity_stamina = stamina_components[i] + + -- do something with the entity and its components + print(string.format( + 'Entity: %d, Health: %d, Stamina: %d', + entity, entity_health, entity_stamina)) +end + +-- Expected output: +-- Entity: 1048602, Health: 100, Stamina: 50 +-- Entity: 1048603, Health: 75, Stamina: 40 +``` + +### Structural Changes + +Every time we insert or remove a fragment from an entity, the entity will be migrated to a new chunk. This is done automatically by the library, of course. However, you should be aware of this because it can affect performance, especially if you have many fragments on the entity. This is called a `structural change`. + +You should try to avoid structural changes, especially in performance-critical code. For example, you can spawn entities with all the fragments they will ever need and avoid changing them during the entity's lifetime. Overriding existing components is not a structural change, so you can do it freely. + +#### Spawning Entities + +```lua +---@param components? table +---@return evolved.entity +function evolved.spawn(components) end + +---@param prefab evolved.entity +---@param components? table +---@return evolved.entity +function evolved.clone(prefab, components) end +``` + +The [`evolved.spawn`](#evolvedspawn) function allows you to spawn an entity with all the necessary fragments. It takes a table of components as an argument, where the keys are fragments and the values are components. By the way, you don't need to create this `components` table every time; consider using a predefined table for maximum performance. + +You can also use the [`evolved.clone`](#evolvedclone) function to clone an existing entity. This is useful for creating entities with the same fragments as an existing entity but with different components. + +```lua +local evolved = require 'evolved' + +local health, stamina = evolved.id(2) + +-- spawn an entity with all the necessary fragments +local enemy1 = evolved.spawn { + [health] = 100, + [stamina] = 50, +} + +-- spawn another entity with the same fragments, +-- but with a different component for some of them +local enemy2 = evolved.clone(enemy1, { + [health] = 50, +}) + +-- there are no structural changes here, +-- we just override existing components +evolved.set(enemy1, health, 75) +evolved.set(enemy1, stamina, 42) +``` + +#### Entity Builders + +Another way to avoid structural changes when spawning entities is to use the [`evolved.builder`](#evolvedbuilder) fluid interface. The [`evolved.builder`](#evolvedbuilder) function returns a builder object that allows you to spawn entities with a specific set of fragments and components without necessity setting them one by one with structural changes for each change. + +```lua +local evolved = require 'evolved' + +local health, stamina = evolved.id(2) + +local enemy = evolved.builder() + :set(health, 100) + :set(stamina, 50) + :spawn() +``` + +Builders can be reused, so you can create a builder with a specific set of fragments and components and then use it to spawn multiple entities with the same fragments and components. + +### Access Operations + +The library provides all the necessary functions to access entities and their components. I'm not going to cover all the accessor functions here, because they are pretty straightforward and self-explanatory. You can check the [API Reference](#api-reference) for all of them. Here are some of the most important ones: + +```lua +---@param entity evolved.entity +---@return boolean +function evolved.alive(entity) end + +---@param entity evolved.entity +---@return boolean +function evolved.empty(entity) end + +---@param entity evolved.entity +---@param fragment evolved.fragment +function evolved.has(entity, fragment) end + +---@param entity evolved.entity +---@param ... evolved.fragment fragments +---@return evolved.component ... components +function evolved.get(entity, ...) end +``` + +The [`evolved.alive`](#evolvedalive) function checks whether an entity is alive. The [`evolved.empty`](#evolvedempty) function checks whether an entity is empty (has no fragments). The [`evolved.has`](#evolvedhas) function checks whether an entity has a specific fragment. The [`evolved.get`](#evolvedget) function retrieves the components of an entity for the specified fragments. If the entity doesn't have some of the fragments or if the fragments are marked with the [`evolved.TAG`](#evolvedtag), the function will return `nil` for them. + +All of these functions can be safely called on non-alive entities and non-alive fragments. Also, they do not cause any structural changes, because they do not modify anything. + +### Modifying Operations + +The library provides a classic set of functions for modifying entities. These functions are used to insert, override, and remove fragments from entities. + +```lua +---@param entity evolved.entity +---@param fragment evolved.fragment +---@param component evolved.component +function evolved.set(entity, fragment, component) end + +---@param entity evolved.entity +---@param ... evolved.fragment fragments +function evolved.remove(entity, ...) + +---@param ... evolved.entity entities +function evolved.clear(...) + +---@param ... evolved.entity entities +function evolved.destroy(...) +``` + +The [`evolved.set`](#evolvedset) function is used to set a component on an entity. If the entity doesn't have this fragment, it will be inserted, with causing a structural change, of course. If the entity already has the fragment, the component will be overridden. The function should not be called on non-alive entities, because it is not possible to set any component on a destroyed entity, ignoring this can lead to errors. The [Debug Mode](#debug-mode) can be used to check this kind of error. + +Use the [`evolved.remove`](#evolvedremove) function to remove fragments from an entity. If the entity doesn't have some of the fragments, they will be ignored. When one or more fragments are removed from an entity, the entity will be migrated to a new chunk, which is a structural change. When you want to remove more than one fragment, pass all of them as arguments. Do not remove fragments one by one, as this will cause a structural change for each fragment. The [`evolved.remove`](#evolvedremove) function will ignore non-alive entities, because post-conditions are satisfied (destroyed entities do not have any fragments, including those that we want to remove). + +To remove all fragments from an entity, use the [`evolved.clear`](#evolvedclear) function. This function will remove all fragments at once, with causing only one structural change. The [`evolved.clear`](#evolvedclear) function does not destroy the entity, it just removes all fragments from it. The entity after this operation will be empty, but it will be still alive. You can use this function to clear more than one entity at once, passing them as arguments. The function will ignore empty and non-alive entities. + +To destroy an entity, use the [`evolved.destroy`](#evolveddestroy) function. This function will remove all fragments from the entity and free the identifier of the entity for reuse. The [`evolved.destroy`](#evolveddestroy) function will ignore non-alive entities. To destroy more than one entity, pass them as arguments. + +### Debug Mode + +The library has a debug mode that can be enabled by the [`evolved.debug_mode`](#evolveddebug_mode) function. When the debug mode is enabled, the library will check for incorrect usages of the API and throw errors when they are detected. This is very useful for debugging and development, but it can slow down performance a bit. + +```lua +---@param yesno boolean +function evolved.debug_mode(yesno) end +``` + +The debug mode is disabled by default, so you need to enable it manually. I strongly recommend doing this in the development environment. You can even leave it enabled in production, but only if you are sure the performance is acceptable for your case. + +```lua +local evolved = require 'evolved' + +evolved.debug_mode(true) + +local entity = evolved.id() + +local fragment = evolved.id() +evolved.destroy(fragment) + +-- try to use the destroyed fragment +evolved.set(entity, fragment, 42) + +-- [error] | evolved.lua | the fragment ($1048599#23:1) is not alive and cannot be used +``` + +### Queries + +One of the most important features of any ECS library is the ability to process entities by filters or queries. `evolved.lua` provides a simple and efficient way to do this. + +First, you need to create a query that describes which entities you want to process. You can specify fragments you want to include, and fragments you want to exclude. Queries are just identifiers with a special predefined fragments: [`evolved.INCLUDES`](#evolvedincludes) and [`evolved.EXCLUDES`](#evolvedexcludes). These fragments expect a list of fragments as their components. + +```lua +local evolved = require 'evolved' + +local health, poisoned, resistant = evolved.id(3) + +local query = evolved.id() +evolved.set(query, evolved.INCLUDES, { health, poisoned }) +evolved.set(query, evolved.EXCLUDES, { resistant }) +``` + +The builder interface can be used to create queries too. It is more convenient to use, because the builder has special methods for including and excluding fragments. Here is a simple example of this: + +```lua +local query = evolved.builder() + :include(health, poisoned) + :exclude(resistant) + :spawn() +``` + +We don't have to set both [`evolved.INCLUDES`](#evolvedincludes) and [`evolved.EXCLUDES`](#evolvedexcludes) fragments, we can even do it without filters at all, then the query will match all chunks in the world. + +After the query is created, we are ready to process our filtered by this query entities. You can do this by using the [`evolved.execute`](#evolvedexecute) function. This function takes a query as an argument and returns an iterator that can be used to iterate over all matching with the query chunks. + +```lua +---@param query evolved.query +---@return evolved.execute_iterator iterator +---@return evolved.execute_state? iterator_state +function evolved.execute(query) end +``` + +```lua +for chunk, entity_list, entity_count in evolved.execute(query) do + ---@type number[] + local health_components = chunk:components(health) + + for i = 1, entity_count do + health_components[i] = math.max( + health_components[i] - 1, + 0) + end +end +``` + +As you can see, `evolved.execute_iterator` returns a chunk, a list of entities in the chunk, and the number of entities in this chunk. We [already know](#chunks) how to use chunks, so we can use the chunk's methods to retrieve the components of the entities in the chunk, change them, and so on. + +> [!NOTE] +> But I haven't mentioned one important thing yet: [structural changes](#structural-changes) are not allowed during any iteration over chunks. This means that you cannot insert or remove fragments from entities while iterating. Also, you cannot destroy or spawn entities because this will cause structural changes too. This is done to avoid inconsistencies in the iteration process. If we allow structural changes here, we might skip some entities during iteration, or process the same entity multiple times. The [debug mode](#debug-mode) can catch this kind of error. + +#### Deferred Operations + +Now we know that structural changes are not allowed during iteration, but what if we want to make some structural changes after the iteration is finished? For example, we might want to remove some fragments from entities after we have processed them, or we might want to spawn new entities while processing existing ones. To do all of this, we can use deferred operations. + +```lua +---@return boolean started +function evolved.defer() end + +---@return boolean committed +function evolved.commit() end +``` + +The [`evolved.defer`](#evolveddefer) function starts a deferred scope. This means that all changes made inside the scope will be queued and applied after leaving the scope. The [`evolved.commit`](#evolvedcommit) function closes a last deferred scope and applies all queued changes. These functions can be nested, so you can start a new deferred scope inside an existing one. The [`evolved.commit`](#evolvedcommit) function will apply all queued changes only when the last deferred scope is closed. + +```lua +local evolved = require 'evolved' + +local health, poisoned = evolved.id(2) + +local player = evolved.builder() + :set(health, 100) + :set(poisoned, true) + :spawn() + +-- start a deferred scope +evolved.defer() + +-- this removal will be queued, not applied immediately +evolved.remove(player, poisoned) + +-- the player still has the poisoned fragment inside the deferred scope +assert(evolved.has(player, poisoned)) + +-- commit the deferred operations +evolved.commit() + +-- now the poisoned fragment is removed +assert(not evolved.has(player, poisoned)) +``` + +#### Batch Operations + +The library provides a set of functions for batch operations. These functions are used to perform modifying operations on multiple chunks at once. This is very useful for performance reasons. + +```lua +---@param query evolved.query +---@param fragment evolved.fragment +---@param component evolved.component +function evolved.batch_set(query, fragment, component) end + +---@param query evolved.query +---@param ... evolved.fragment fragments +function evolved.batch_remove(query, ...) end + +---@param ... evolved.query queries +function evolved.batch_clear(...) end + +---@param ... evolved.query queries +function evolved.batch_destroy(...) end +``` + +These functions are similar to the common [modifying operations](#modifying-operations), but they take a query as an argument instead of an entity. Here is a classic example that provides a huge performance boost when applied. + +```lua +local evolved = require 'evolved' + +local destroying_mark = evolved.id() + +local destroying_mark_query = evolved.builder() + :include(destroying_mark) + :spawn() + +-- destroy all entities with the destroying_mark fragment +evolved.batch_destroy(destroying_mark_query) +``` + +> [!TIP] +> You should always prefer batch operations over common modifying operations when you need to perform simple operations like destroying or removing fragments from multiple entities at once. Instead of applying the operation to each entity one by one, batch operations will apply the operation chunk by chunk. + +In all other respects, batch operations behave the same way as the common modifying operations that we have already covered. Of course, they can also be used with [deferred operations](#deferred-operations). + +### Systems + +Usually, we want to organize our processing of entities into systems that will be executed in a specific order. The library has a way to do this using special [`evolved.QUERY`](#evolvedquery) and [`evolved.EXECUTE`](#evolvedexecute) fragments that are used to specify the system's queries and execution callbacks. And yes, systems are just entities with special fragments. + +```lua +local evolved = require 'evolved' + +local health, max_health = evolved.id(2) + +local query = evolved.builder() + :include(health, max_health) + :spawn() + +local system = evolved.builder() + :query(query) + :execute(function(chunk, entity_list, entity_count) + local health_components = chunk:components(health) + local max_health_components = chunk:components(max_health) + + for i = 1, entity_count do + health_components[i] = math.min( + health_components[i] + 1, + max_health_components[i]) + end + end):spawn() +``` + +The [`evolved.process`](#evolvedprocess) function is used to process systems. It takes systems as arguments and executes them in the order they were passed. + +```lua +---@param ... evolved.system systems +function evolved.process(...) end +``` + +To group systems together, you can use the [`evolved.GROUP`](#evolvedgroup) fragment. Systems with a specified group will be processed when you call the [`evolved.process`](#evolvedprocess) function with this group. For example, you can group all physics systems together and process them in one [`evolved.process`](#evolvedprocess) call. + +```lua +local evolved = require 'evolved' + +local gravity_x = 0 +local gravity_y = -9.81 + +local position_x, position_y = evolved.id(2) +local velocity_x, velocity_y = evolved.id(2) + +local physical_body_query = evolved.builder() + :include(position_x, position_y) + :include(velocity_x, velocity_y) + :spawn() + +local physics_group = evolved.id() + +evolved.builder() + :group(physics_group) + :query(physical_body_query) + :execute(function(chunk, entity_list, entity_count) + local vx = chunk:components(velocity_x) + local vy = chunk:components(velocity_y) + + for i = 1, entity_count do + vx[i] = vx[i] + gravity_x + vy[i] = vy[i] + gravity_y + end + end):spawn() + +evolved.builder() + :group(physics_group) + :query(physical_body_query) + :execute(function(chunk, entity_list, entity_count) + local px = chunk:components(position_x) + local py = chunk:components(position_y) + + local vx = chunk:components(velocity_x) + local vy = chunk:components(velocity_y) + + for i = 1, entity_count do + px[i] = px[i] + vx[i] + py[i] = py[i] + vy[i] + end + end):spawn() + +evolved.process(physics_group) +``` + +Systems and groups also can have the [`evolved.PROLOGUE`](#evolvedprologue) and [`evolved.EPILOGUE`](#evolvedepilogue) fragments. These fragments are used to specify callbacks that will be executed before and after the system or group is processed. This is useful for setting up and tearing down systems or groups, or for performing some additional processing before or after the main processing. + +```lua +local evolved = require 'evolved' + +local system = evolved.builder() + :prologue(function() + print('Prologue') + end) + :epilogue(function() + print('Epilogue') + end) + :spawn() + +evolved.process(system) +``` + +The prologue and epilogue fragments do not require an explicit query. They will be executed before and after the system is processed, regardless of the query. + +> [!NOTE] +> And one more thing about systems. Execution callbacks are called in the [deferred scope](#deferred-operations), this means that all modifying operations inside the callback will be queued and applied after the system processed all chunks. But prologue and epilogue callbacks are not called in the deferred scope, so all modifying operations inside them will be applied immediately. This is done to avoid confusion and to make it clear that prologue and epilogue callbacks are not part of the chunk processing. + +### Advanced Topics + +#### Fragment Tags + +Sometimes you want to have a fragment without a component. For example, you might want to have some marks that will be used to mark entities for processing. Fragments without components are called `tags`. Such fragments take up less memory, because they do not require any components to be stored. Migration of entities with tags is faster, because the library does not need to migrate components, only the tags themselves. To create a tag, mark the fragment with the [`evolved.TAG`](#evolvedtag) fragment. + +```lua +local evolved = require 'evolved' + +local player_tag = evolved.id() +evolved.set(player_tag, evolved.TAG) + +local player = evolved.id() +evolved.set(player, player_tag) + +-- player has the player_tag fragment +assert(evolved.has(player, player_tag)) + +-- player_tag is a tag, so it doesn't have a component +assert(evolved.get(player, player_tag) == nil) +``` + +#### Fragment Hooks + +The library provides a way to execute callbacks when fragments are set, assigned, inserted, or removed from entities. This is done using special fragments: [`evolved.ON_SET`](#evolvedon_set), [`evolved.ON_ASSIGN`](#evolvedon_assign), [`evolved.ON_INSERT`](#evolvedon_insert), and [`evolved.ON_REMOVE`](#evolvedon_remove). These fragments are used to specify the callbacks that will be executed when the corresponding operation is performed on the fragment. + +```lua +local evolved = require 'evolved' + +local health = evolved.builder() + :on_set(function(entity, fragment, component) + print('health set to ' .. component) + end):spawn() + +local player = evolved.id() +evolved.set(player, health, 100) -- prints "health set to 100" +evolved.set(player, health, 200) -- prints "health set to 200" +``` + +Use [`evolved.ON_SET`](#evolvedon_set) for callbacks on fragment insert or override, [`evolved.ON_ASSIGN`](#evolvedon_assign) for overrides, and [`evolved.ON_INSERT`](#evolvedon_insert)/[`evolved.ON_REMOVE`](#evolvedon_remove) for insertions or removals. + +# API Reference + +## Predefs + +### `evolved.TAG` + +### `evolved.NAME` + +### `evolved.UNIQUE` + +### `evolved.EXPLICIT` + +### `evolved.DEFAULT` + +### `evolved.DUPLICATE` + +### `evolved.PREFAB` + +### `evolved.DISABLED` + +### `evolved.INCLUDES` + +### `evolved.EXCLUDES` + +### `evolved.ON_SET` + +### `evolved.ON_ASSIGN` + +### `evolved.ON_INSERT` + +### `evolved.ON_REMOVE` + +### `evolved.GROUP` + +### `evolved.QUERY` + +### `evolved.EXECUTE` + +### `evolved.PROLOGUE` + +### `evolved.EPILOGUE` + +### `evolved.DESTROY_POLICY` + +## Functions + +### `evolved.id` + +```lua +---@param count? integer +---@return evolved.id ... ids +---@nodiscard +function evolved.id(count) end +``` + +### `evolved.pack` + +```lua +---@param index integer +---@param version integer +---@return evolved.id id +---@nodiscard +function evolved.pack(index, version) end +``` + +### `evolved.unpack` + +```lua +---@param id evolved.id +---@return integer index +---@return integer version +---@nodiscard +function evolved.unpack(id) end +``` + +### `evolved.defer` + +```lua +---@return boolean started +function evolved.defer() end +``` + +### `evolved.commit` + +```lua +---@return boolean committed +function evolved.commit() end +``` + +### `evolved.spawn` + +```lua +---@param components? table +---@return evolved.entity +function evolved.spawn(components) end +``` + +### `evolved.clone` + +```lua +---@param prefab evolved.entity +---@param components? table +---@return evolved.entity +function evolved.clone(prefab, components) end +``` + +### `evolved.alive` + +```lua +---@param entity evolved.entity +---@return boolean +---@nodiscard +function evolved.alive(entity) end +``` + +### `evolved.alive_all` + +```lua +---@param ... evolved.entity entities +---@return boolean +---@nodiscard +function evolved.alive_all(...) end +``` + +### `evolved.alive_any` + +```lua +---@param ... evolved.entity entities +---@return boolean +---@nodiscard +function evolved.alive_any(...) end +``` + +### `evolved.empty` + +```lua +---@param entity evolved.entity +---@return boolean +---@nodiscard +function evolved.empty(entity) end +``` + +### `evolved.empty_all` + +```lua +---@param ... evolved.entity entities +---@return boolean +---@nodiscard +function evolved.empty_all(...) end +``` + +### `evolved.empty_any` + +```lua +---@param ... evolved.entity entities +---@return boolean +---@nodiscard +function evolved.empty_any(...) end +``` + +### `evolved.has` + +```lua +---@param entity evolved.entity +---@param fragment evolved.fragment +---@return boolean +---@nodiscard +function evolved.has(entity, fragment) end +``` + +### `evolved.has_all` + +```lua +---@param entity evolved.entity +---@param ... evolved.fragment fragments +---@return boolean +---@nodiscard +function evolved.has_all(entity, ...) end +``` + +### `evolved.has_any` + +```lua +---@param entity evolved.entity +---@param ... evolved.fragment fragments +---@return boolean +---@nodiscard +function evolved.has_any(entity, ...) end +``` + +### `evolved.get` + +```lua +---@param entity evolved.entity +---@param ... evolved.fragment fragments +---@return evolved.component ... components +---@nodiscard +function evolved.get(entity, ...) end +``` + +### `evolved.set` + +```lua +---@param entity evolved.entity +---@param fragment evolved.fragment +---@param component evolved.component +function evolved.set(entity, fragment, component) end +``` + +### `evolved.remove` + +```lua +---@param entity evolved.entity +---@param ... evolved.fragment fragments +function evolved.remove(entity, ...) end +``` + +### `evolved.clear` + +```lua +---@param ... evolved.entity entities +function evolved.clear(...) end +``` + +### `evolved.destroy` + +```lua +---@param ... evolved.entity entities +function evolved.destroy(...) end +``` + +### `evolved.batch_set` + +```lua +---@param query evolved.query +---@param fragment evolved.fragment +---@param component evolved.component +function evolved.batch_set(query, fragment, component) end +``` + +### `evolved.batch_remove` + +```lua +---@param query evolved.query +---@param ... evolved.fragment fragments +function evolved.batch_remove(query, ...) end +``` + +### `evolved.batch_clear` + +```lua +---@param ... evolved.query queries +function evolved.batch_clear(...) end +``` + +### `evolved.batch_destroy` + +```lua +---@param ... evolved.query queries +function evolved.batch_destroy(...) end +``` + +### `evolved.each` + +```lua +---@param entity evolved.entity +---@return evolved.each_iterator iterator +---@return evolved.each_state? iterator_state +---@nodiscard +function evolved.each(entity) end +``` + +### `evolved.execute` + +```lua +---@param query evolved.query +---@return evolved.execute_iterator iterator +---@return evolved.execute_state? iterator_state +---@nodiscard +function evolved.execute(query) end +``` + +### `evolved.process` + +```lua +---@param ... evolved.system systems +function evolved.process(...) end +``` + +### `evolved.debug_mode` + +```lua +---@param yesno boolean +function evolved.debug_mode(yesno) end +``` + +### `evolved.collect_garbage` + +```lua +function evolved.collect_garbage() end +``` + +## Classes + +### Chunk + +#### `evolved.chunk` + +```lua +---@param fragment evolved.fragment +---@param ... evolved.fragment fragments +---@return evolved.chunk chunk +---@return evolved.entity[] entity_list +---@return integer entity_count +---@nodiscard +function evolved.chunk(fragment, ...) end +``` + +#### `evolved.chunk_mt:alive` + +```lua +---@return boolean +---@nodiscard +function evolved.chunk_mt:alive() end +``` + +#### `evolved.chunk_mt:empty` + +```lua +---@return boolean +---@nodiscard +function evolved.chunk_mt:empty() end +``` + +#### `evolved.chunk_mt:has` + +```lua +---@param fragment evolved.fragment +---@return boolean +---@nodiscard +function evolved.chunk_mt:has(fragment) end +``` + +#### `evolved.chunk_mt:has_all` + +```lua +---@param ... evolved.fragment fragments +---@return boolean +---@nodiscard +function evolved.chunk_mt:has_all(...) end +``` + +#### `evolved.chunk_mt:has_any` + +```lua +---@param ... evolved.fragment fragments +---@return boolean +---@nodiscard +function evolved.chunk_mt:has_any(...) end +``` + +#### `evolved.chunk_mt:entities` + +```lua +---@return evolved.entity[] entity_list +---@return integer entity_count +---@nodiscard +function evolved.chunk_mt:entities() end +``` + +#### `evolved.chunk_mt:fragments` + +```lua +---@return evolved.fragment[] fragment_list +---@return integer fragment_count +---@nodiscard +function evolved.chunk_mt:fragments() end +``` + +#### `evolved.chunk_mt:components` + +```lua +---@param ... evolved.fragment fragments +---@return evolved.storage ... storages +---@nodiscard +function evolved.chunk_mt:components(...) end +``` + +### Builder + +#### `evolved.builder` + +```lua +---@return evolved.builder builder +---@nodiscard +function evolved.builder() end +``` + +#### `evolved.builder_mt:spawn` + +```lua +---@return evolved.entity +function evolved.builder_mt:spawn() end +``` + +#### `evolved.builder_mt:clone` + +```lua +---@param prefab evolved.entity +---@return evolved.entity +function evolved.builder_mt:clone(prefab) end +``` + +#### `evolved.builder_mt:has` + +```lua +---@param fragment evolved.fragment +---@return boolean +---@nodiscard +function evolved.builder_mt:has(fragment) end +``` + +#### `evolved.builder_mt:has_all` + +```lua +---@param ... evolved.fragment fragments +---@return boolean +---@nodiscard +function evolved.builder_mt:has_all(...) end +``` + +#### `evolved.builder_mt:has_any` + +```lua +---@param ... evolved.fragment fragments +---@return boolean +---@nodiscard +function evolved.builder_mt:has_any(...) end +``` + +#### `evolved.builder_mt:get` + +```lua +---@param ... evolved.fragment fragments +---@return evolved.component ... components +---@nodiscard +function evolved.builder_mt:get(...) end +``` + +#### `evolved.builder_mt:set` + +```lua +---@param fragment evolved.fragment +---@param component evolved.component +---@return evolved.builder builder +function evolved.builder_mt:set(fragment, component) end +``` + +#### `evolved.builder_mt:remove` + +```lua +---@param ... evolved.fragment fragments +---@return evolved.builder builder +function evolved.builder_mt:remove(...) end +``` + +#### `evolved.builder_mt:clear` + +```lua +---@return evolved.builder builder +function evolved.builder_mt:clear() end +``` + +#### `evolved.builder_mt:tag` + +```lua +---@return evolved.builder builder +function evolved.builder_mt:tag() end +``` + +#### `evolved.builder_mt:name` + +```lua +---@param name string +---@return evolved.builder builder +function evolved.builder_mt:name(name) end +``` + +#### `evolved.builder_mt:unique` + +```lua +---@return evolved.builder builder +function evolved.builder_mt:unique() end +``` + +#### `evolved.builder_mt:explicit` + +```lua +---@return evolved.builder builder +function evolved.builder_mt:explicit() end +``` + +#### `evolved.builder_mt:default` + +```lua +---@param default evolved.component +---@return evolved.builder builder +function evolved.builder_mt:default(default) end +``` + +#### `evolved.builder_mt:duplicate` + +```lua +---@param duplicate evolved.duplicate +---@return evolved.builder builder +function evolved.builder_mt:duplicate(duplicate) end +``` + +#### `evolved.builder_mt:prefab` + +```lua +---@return evolved.builder builder +function evolved.builder_mt:prefab() end +``` + +#### `evolved.builder_mt:disabled` + +```lua +---@return evolved.builder builder +function evolved.builder_mt:disabled() end +``` + +#### `evolved.builder_mt:include` + +```lua +---@param ... evolved.fragment fragments +---@return evolved.builder builder +function evolved.builder_mt:include(...) end +``` + +#### `evolved.builder_mt:exclude` + +```lua +---@param ... evolved.fragment fragments +---@return evolved.builder builder +function evolved.builder_mt:exclude(...) end +``` + +#### `evolved.builder_mt:on_set` + +```lua +---@param on_set evolved.set_hook +---@return evolved.builder builder +function evolved.builder_mt:on_set(on_set) end +``` + +#### `evolved.builder_mt:on_assign` + +```lua +---@param on_assign evolved.assign_hook +---@return evolved.builder builder +function evolved.builder_mt:on_assign(on_assign) end +``` + +#### `evolved.builder_mt:on_insert` + +```lua +---@param on_insert evolved.insert_hook +---@return evolved.builder builder +function evolved.builder_mt:on_insert(on_insert) end +``` + +#### `evolved.builder_mt:on_remove` + +```lua +---@param on_remove evolved.remove_hook +---@return evolved.builder builder +function evolved.builder_mt:on_remove(on_remove) end +``` + +#### `evolved.builder_mt:group` + +```lua +---@param group evolved.system +---@return evolved.builder builder +function evolved.builder_mt:group(group) end +``` + +#### `evolved.builder_mt:query` + +```lua +---@param query evolved.query +---@return evolved.builder builder +function evolved.builder_mt:query(query) end +``` + +#### `evolved.builder_mt:execute` + +```lua +---@param execute evolved.execute +---@return evolved.builder builder +function evolved.builder_mt:execute(execute) end +``` + +#### `evolved.builder_mt:prologue` + +```lua +---@param prologue evolved.prologue +---@return evolved.builder builder +function evolved.builder_mt:prologue(prologue) end +``` + +#### `evolved.builder_mt:epilogue` + +```lua +---@param epilogue evolved.epilogue +---@return evolved.builder builder +function evolved.builder_mt:epilogue(epilogue) end +``` + +#### `evolved.builder_mt:destroy_policy` + +```lua +---@param destroy_policy evolved.id +---@return evolved.builder builder +function evolved.builder_mt:destroy_policy(destroy_policy) end +``` + ## [License (MIT)](./LICENSE.md) From ebef3dce167c177f230b6e7741db704b6a87bac0 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Tue, 20 May 2025 20:17:03 +0700 Subject: [PATCH 13/16] manual wip --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index 1f7ce75..dccb96a 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,12 @@ luarocks install evolved.lua Or just clone the [repository](https://github.com/BlackMATov/evolved.lua) and copy the [evolved.lua](evolved.lua) file to your project. +## Quick Start + +To start using `evolved.lua`, read the [Overview](#overview) section first. It will give you a basic understanding of how the library works and how to use it. After that, check the full-featured [Example](develop/example.lua), which demonstrates complex usage of the library. Finally, refer to the [Cheat Sheet](#cheat-sheet) for a quick reference of all the functions and classes provided by the library. + +Enjoy! :suspect: + ## Cheat Sheet ### Aliases @@ -221,6 +227,12 @@ builder_mt:destroy_policy :: id -> builder ## Overview +The library is designed to be simple and highly efficient. It uses an archetype-based approach to store entities and their components. This allows you to filter and process your entities very efficiently, especially when you have many of them. + +If you are familiar with the ECS (Entity-Component-System) pattern, you will feel right at home. If not, I highly recommend reading about it first. Here is a good starting point: [Entity Component System FAQ](https://github.com/SanderMertens/ecs-faq). + +Let's get started! :godmode: + ### Identifiers An identifier is a packed 40-bit integer. The first 20 bits represent the index, and the last 20 bits represent the version. To create a new identifier, use the [`evolved.id`](#evolvedid) function. From 2062de9c773ce0ade543cdb48dc729b671f4fb52 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Tue, 20 May 2025 20:44:43 +0700 Subject: [PATCH 14/16] manual wip --- README.md | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/README.md b/README.md index dccb96a..820ffd2 100644 --- a/README.md +++ b/README.md @@ -914,6 +914,53 @@ evolved.set(player, health, 200) -- prints "health set to 200" Use [`evolved.ON_SET`](#evolvedon_set) for callbacks on fragment insert or override, [`evolved.ON_ASSIGN`](#evolvedon_assign) for overrides, and [`evolved.ON_INSERT`](#evolvedon_insert)/[`evolved.ON_REMOVE`](#evolvedon_remove) for insertions or removals. +#### Unique Fragments + +Some fragments should not be cloned when cloning entities. For example, `evolved.lua` has a special fragment called `evolved.PREFAB`, which marks entities used as sources for cloning. This fragment should not be present on the cloned entities. To prevent a fragment from being cloned, mark it as unique using the [`evolved.UNIQUE`](#evolvedunique) fragment trait. This ensures the fragment will not be copied when cloning entities. + +```lua +local evolved = require 'evolved' + +local health, stamina = evolved.id(2) + +local enemy_prefab = evolved.builder() + :prefab() + :set(health, 100) + :set(stamina, 50) + :spawn() + +local enemy_clone = evolved.clone(enemy_prefab) + +-- the enemy_prefab has the evolved.PREFAB fragment +assert(evolved.has(enemy_prefab, evolved.PREFAB)) + +-- but the enemy_clone doesn't have it because it is marked as unique +assert(not evolved.has(enemy_clone, evolved.PREFAB)) +``` + +#### Explicit Fragments + +In some cases, you might want to hide chunks with certain fragments from queries by default. For example, the library has a special fragment called `evolved.DISABLED` that behaves this way. This fragment is marked with the [`evolved.EXPLICIT`](#evolvedexplicit) fragment trait, which means it will be hidden from queries unless you explicitly include it. This is useful for fragments that are used for internal or editor purposes and should not be exposed to queries by default. + +Additionally, the [`evolved.PREFAB`](#evolvedprefab) fragment is also marked with the [`evolved.EXPLICIT`](#evolvedexplicit) fragment trait. This prevents prefabs from being processed in queries at runtime. Prefabs are used only for cloning entities, so they should not be processed by default. + +```lua +local evolved = require 'evolved' + +local enemy_tag = evolved.builder() + :tag() + :spawn() + +local only_enabled_enemies = evolved.builder() + :include(enemy_tag) + :spawn() + +local all_enemies_including_disabled = evolved.builder() + :include(enemy_tag) + :include(evolved.DISABLED) + :spawn() +``` + # API Reference ## Predefs From a8012c75cf07cbbb919c7f268f861d13d24a864c Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Tue, 20 May 2025 21:07:34 +0700 Subject: [PATCH 15/16] manual wip --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 820ffd2..5bc3078 100644 --- a/README.md +++ b/README.md @@ -940,7 +940,7 @@ assert(not evolved.has(enemy_clone, evolved.PREFAB)) #### Explicit Fragments -In some cases, you might want to hide chunks with certain fragments from queries by default. For example, the library has a special fragment called `evolved.DISABLED` that behaves this way. This fragment is marked with the [`evolved.EXPLICIT`](#evolvedexplicit) fragment trait, which means it will be hidden from queries unless you explicitly include it. This is useful for fragments that are used for internal or editor purposes and should not be exposed to queries by default. +In some cases, you might want to hide chunks with certain fragments from queries by default. For example, the library has a special fragment called [`evolved.DISABLED`](#evolveddisabled) that behaves this way. This fragment is marked with the [`evolved.EXPLICIT`](#evolvedexplicit) fragment trait, which means it will be hidden from queries unless you explicitly include it. This is useful for fragments that are used for internal or editor purposes and should not be exposed to queries by default. Additionally, the [`evolved.PREFAB`](#evolvedprefab) fragment is also marked with the [`evolved.EXPLICIT`](#evolvedexplicit) fragment trait. This prevents prefabs from being processed in queries at runtime. Prefabs are used only for cloning entities, so they should not be processed by default. From 0108d2b039f1b21679881bdc2483878d30970d4d Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Tue, 20 May 2025 23:43:55 +0700 Subject: [PATCH 16/16] manual wip --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 5bc3078..08bc335 100644 --- a/README.md +++ b/README.md @@ -534,7 +534,7 @@ evolved.set(enemy1, stamina, 42) #### Entity Builders -Another way to avoid structural changes when spawning entities is to use the [`evolved.builder`](#evolvedbuilder) fluid interface. The [`evolved.builder`](#evolvedbuilder) function returns a builder object that allows you to spawn entities with a specific set of fragments and components without necessity setting them one by one with structural changes for each change. +Another way to avoid structural changes when spawning entities is to use the [`evolved.builder`](#evolvedbuilder) fluid interface. The [`evolved.builder`](#evolvedbuilder) function returns a builder object that allows you to spawn entities with a specific set of fragments and components without the necessity of setting them one by one with structural changes for each change. ```lua local evolved = require 'evolved' @@ -601,7 +601,7 @@ The [`evolved.set`](#evolvedset) function is used to set a component on an entit Use the [`evolved.remove`](#evolvedremove) function to remove fragments from an entity. If the entity doesn't have some of the fragments, they will be ignored. When one or more fragments are removed from an entity, the entity will be migrated to a new chunk, which is a structural change. When you want to remove more than one fragment, pass all of them as arguments. Do not remove fragments one by one, as this will cause a structural change for each fragment. The [`evolved.remove`](#evolvedremove) function will ignore non-alive entities, because post-conditions are satisfied (destroyed entities do not have any fragments, including those that we want to remove). -To remove all fragments from an entity, use the [`evolved.clear`](#evolvedclear) function. This function will remove all fragments at once, with causing only one structural change. The [`evolved.clear`](#evolvedclear) function does not destroy the entity, it just removes all fragments from it. The entity after this operation will be empty, but it will be still alive. You can use this function to clear more than one entity at once, passing them as arguments. The function will ignore empty and non-alive entities. +To remove all fragments from an entity, use the [`evolved.clear`](#evolvedclear) function. This function will remove all fragments at once, causing only one structural change. The [`evolved.clear`](#evolvedclear) function does not destroy the entity, it just removes all fragments from it. The entity after this operation will be empty, but it will still be alive. You can use this function to clear more than one entity at once, passing them as arguments. The function will ignore empty and non-alive entities. To destroy an entity, use the [`evolved.destroy`](#evolveddestroy) function. This function will remove all fragments from the entity and free the identifier of the entity for reuse. The [`evolved.destroy`](#evolveddestroy) function will ignore non-alive entities. To destroy more than one entity, pass them as arguments. @@ -684,7 +684,7 @@ end As you can see, `evolved.execute_iterator` returns a chunk, a list of entities in the chunk, and the number of entities in this chunk. We [already know](#chunks) how to use chunks, so we can use the chunk's methods to retrieve the components of the entities in the chunk, change them, and so on. > [!NOTE] -> But I haven't mentioned one important thing yet: [structural changes](#structural-changes) are not allowed during any iteration over chunks. This means that you cannot insert or remove fragments from entities while iterating. Also, you cannot destroy or spawn entities because this will cause structural changes too. This is done to avoid inconsistencies in the iteration process. If we allow structural changes here, we might skip some entities during iteration, or process the same entity multiple times. The [debug mode](#debug-mode) can catch this kind of error. +> But I haven't mentioned one important thing yet: [structural changes](#structural-changes) are not allowed during any iteration over chunks. This means that you cannot insert or remove fragments from entities while iterating. Also, you cannot destroy or spawn entities because this will cause structural changes too. This is done to avoid inconsistencies in the iteration process. If we allowed structural changes here, we might skip some entities during iteration, or process the same entity multiple times. The [debug mode](#debug-mode) can catch this kind of error. #### Deferred Operations @@ -698,7 +698,7 @@ function evolved.defer() end function evolved.commit() end ``` -The [`evolved.defer`](#evolveddefer) function starts a deferred scope. This means that all changes made inside the scope will be queued and applied after leaving the scope. The [`evolved.commit`](#evolvedcommit) function closes a last deferred scope and applies all queued changes. These functions can be nested, so you can start a new deferred scope inside an existing one. The [`evolved.commit`](#evolvedcommit) function will apply all queued changes only when the last deferred scope is closed. +The [`evolved.defer`](#evolveddefer) function starts a deferred scope. This means that all changes made inside the scope will be queued and applied after leaving the scope. The [`evolved.commit`](#evolvedcommit) function closes the last deferred scope and applies all queued changes. These functions can be nested, so you can start a new deferred scope inside an existing one. The [`evolved.commit`](#evolvedcommit) function will apply all queued changes only when the last deferred scope is closed. ```lua local evolved = require 'evolved' @@ -871,7 +871,7 @@ evolved.process(system) The prologue and epilogue fragments do not require an explicit query. They will be executed before and after the system is processed, regardless of the query. > [!NOTE] -> And one more thing about systems. Execution callbacks are called in the [deferred scope](#deferred-operations), this means that all modifying operations inside the callback will be queued and applied after the system processed all chunks. But prologue and epilogue callbacks are not called in the deferred scope, so all modifying operations inside them will be applied immediately. This is done to avoid confusion and to make it clear that prologue and epilogue callbacks are not part of the chunk processing. +> And one more thing about systems. Execution callbacks are called in the [deferred scope](#deferred-operations), which means that all modifying operations inside the callback will be queued and applied after the system has processed all chunks. But prologue and epilogue callbacks are not called in the deferred scope, so all modifying operations inside them will be applied immediately. This is done to avoid confusion and to make it clear that prologue and epilogue callbacks are not part of the chunk processing. ### Advanced Topics @@ -934,7 +934,7 @@ local enemy_clone = evolved.clone(enemy_prefab) -- the enemy_prefab has the evolved.PREFAB fragment assert(evolved.has(enemy_prefab, evolved.PREFAB)) --- but the enemy_clone doesn't have it because it is marked as unique +-- but the enemy_clone doesn't have it, because it is marked as unique assert(not evolved.has(enemy_clone, evolved.PREFAB)) ```