Skip to content

Commit 20c94a0

Browse files
committed
Merge branch 'feature/variants' into dev
2 parents c29092c + e396c32 commit 20c94a0

4 files changed

Lines changed: 313 additions & 67 deletions

File tree

README.md

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -588,16 +588,22 @@ evolved.set(entity, fragment, 42)
588588

589589
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.
590590

591-
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.
591+
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), [`evolved.EXCLUDES`](#evolvedexcludes), and [`evolved.VARIANTS`](#evolvedvariants). These fragments expect a list of fragments as their components.
592+
593+
- [`evolved.INCLUDES`](#evolvedincludes) is used to specify fragments that must be present in the entity;
594+
- [`evolved.EXCLUDES`](#evolvedexcludes) is used to specify fragments that must not be present in the entity;
595+
- [`evolved.VARIANTS`](#evolvedvariants) is used to specify fragments where at least one must be present in the entity.
592596

593597
```lua
594598
local evolved = require 'evolved'
595599

596600
local health, poisoned, resistant = evolved.id(3)
601+
local alive, undead = evolved.id(2)
597602

598603
local query = evolved.id()
599604
evolved.set(query, evolved.INCLUDES, { health, poisoned })
600605
evolved.set(query, evolved.EXCLUDES, { resistant })
606+
evolved.set(query, evolved.VARIANTS, { alive, undead })
601607
```
602608

603609
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:
@@ -606,10 +612,11 @@ The builder interface can be used to create queries too. It is more convenient t
606612
local query = evolved.builder()
607613
:include(health, poisoned)
608614
:exclude(resistant)
615+
:variant(alive, undead)
609616
:build()
610617
```
611618

612-
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.
619+
We don't have to set all of [`evolved.INCLUDES`](#evolvedincludes), [`evolved.EXCLUDES`](#evolvedexcludes), and [`evolved.VARIANTS`](#evolvedvariants) fragments, we can even do it without filters at all, then the query will match all chunks in the world.
613620

614621
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.
615622

@@ -788,7 +795,7 @@ The [`evolved.process`](#evolvedprocess) function is used to process systems. It
788795
function evolved.process(...) end
789796
```
790797

791-
If you don't specify a query for the system, the system itself will be treated as a query. This means the system can contain `evolved.INCLUDES` and `evolved.EXCLUDES` fragments, and it will be processed according to them. This is useful for creating systems with unique queries that don't need to be reused in other systems.
798+
If you don't specify a query for the system, the system itself will be treated as a query. This means the system can contain `evolved.INCLUDES`, `evolved.EXCLUDES`, and `evolved.VARIANTS` fragments, and it will be processed according to them. This is useful for creating systems with unique queries that don't need to be reused in other systems.
792799

793800
```lua
794801
local evolved = require 'evolved'
@@ -1198,6 +1205,7 @@ DISABLED :: fragment
11981205
11991206
INCLUDES :: fragment
12001207
EXCLUDES :: fragment
1208+
VARIANTS :: fragment
12011209
REQUIRES :: fragment
12021210
12031211
ON_SET :: fragment
@@ -1332,6 +1340,7 @@ builder_mt:disabled :: builder
13321340
13331341
builder_mt:include :: fragment... -> builder
13341342
builder_mt:exclude :: fragment... -> builder
1343+
builder_mt:variant :: fragment... -> builder
13351344
builder_mt:require :: fragment... -> builder
13361345
13371346
builder_mt:on_set :: {entity, fragment, component, component} -> builder
@@ -1354,6 +1363,7 @@ builder_mt:destruction_policy :: id -> builder
13541363

13551364
### vX.Y.Z
13561365

1366+
- Added the new [`evolved.VARIANTS`](#evolvedvariants) query fragment that allows specifying any of multiple fragments in queries
13571367
- Added the new [`evolved.process_with`](#evolvedprocess_with) function that allows passing payloads to processing systems
13581368

13591369
### v1.6.0
@@ -1428,6 +1438,8 @@ builder_mt:destruction_policy :: id -> builder
14281438

14291439
### `evolved.EXCLUDES`
14301440

1441+
### `evolved.VARIANTS`
1442+
14311443
### `evolved.REQUIRES`
14321444

14331445
### `evolved.ON_SET`
@@ -2065,6 +2077,14 @@ function evolved.builder_mt:include(...) end
20652077
function evolved.builder_mt:exclude(...) end
20662078
```
20672079

2080+
#### `evolved.builder_mt:variant`
2081+
2082+
```lua
2083+
---@param ... evolved.fragment fragments
2084+
---@return evolved.builder builder
2085+
function evolved.builder_mt:variant(...) end
2086+
```
2087+
20682088
### `evolved.builder_mt:require`
20692089

20702090
```lua

develop/fuzzing/execute_fuzz.lua

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,31 @@ local function generate_query(query)
5656
end
5757
end
5858

59+
local variant_set = {}
60+
local variant_list = {}
61+
local variant_count = 0
62+
63+
for _ = 1, math.random(0, #all_fragment_list) do
64+
local variant = all_fragment_list[math.random(1, #all_fragment_list)]
65+
66+
if not variant_set[variant] then
67+
variant_count = variant_count + 1
68+
variant_set[variant] = variant_count
69+
variant_list[variant_count] = variant
70+
end
71+
end
72+
5973
if include_count > 0 then
6074
evo.set(query, evo.INCLUDES, include_list)
6175
end
6276

6377
if exclude_count > 0 then
6478
evo.set(query, evo.EXCLUDES, exclude_list)
6579
end
80+
81+
if variant_count > 0 then
82+
evo.set(query, evo.VARIANTS, variant_list)
83+
end
6684
end
6785

6886
---@param query_count integer
@@ -173,12 +191,22 @@ local function execute_query(query)
173191

174192
local query_include_list = evo.get(query, evo.INCLUDES) or {}
175193
local query_exclude_list = evo.get(query, evo.EXCLUDES) or {}
194+
local query_variant_list = evo.get(query, evo.VARIANTS) or {}
195+
196+
local query_include_count = #query_include_list
197+
local query_exclude_count = #query_exclude_list
198+
local query_variant_count = #query_variant_list
176199

177200
local query_include_set = {}
178201
for _, include in ipairs(query_include_list) do
179202
query_include_set[include] = true
180203
end
181204

205+
local query_variant_set = {}
206+
for _, variant in ipairs(query_variant_list) do
207+
query_variant_set[variant] = true
208+
end
209+
182210
for chunk, entity_list, entity_count in evo.execute(query) do
183211
assert(not query_chunk_set[chunk])
184212
query_chunk_set[chunk] = true
@@ -189,19 +217,29 @@ local function execute_query(query)
189217
query_entity_set[entity] = true
190218
end
191219

192-
assert(chunk:has_all(__table_unpack(query_include_list)))
193-
assert(not chunk:has_any(__table_unpack(query_exclude_list)))
220+
if query_include_count > 0 then
221+
assert(chunk:has_all(__table_unpack(query_include_list)))
222+
end
223+
224+
if query_exclude_count > 0 then
225+
assert(not chunk:has_any(__table_unpack(query_exclude_list)))
226+
end
227+
228+
if query_variant_count > 0 then
229+
assert(chunk:has_any(__table_unpack(query_variant_list)))
230+
end
194231
end
195232

196233
for i = 1, all_entity_count do
197234
local entity = all_entity_list[i]
198235

199236
local is_entity_matched =
200-
evo.has_all(entity, __table_unpack(query_include_list))
201-
and not evo.has_any(entity, __table_unpack(query_exclude_list))
237+
(query_variant_count == 0 or evo.has_any(entity, __table_unpack(query_variant_list))) and
238+
(query_include_count == 0 or evo.has_all(entity, __table_unpack(query_include_list))) and
239+
(query_exclude_count == 0 or not evo.has_any(entity, __table_unpack(query_exclude_list)))
202240

203241
for fragment in evo.each(entity) do
204-
if evo.has(fragment, evo.EXPLICIT) and not query_include_set[fragment] then
242+
if evo.has(fragment, evo.EXPLICIT) and not query_variant_set[fragment] and not query_include_set[fragment] then
205243
is_entity_matched = false
206244
end
207245
end
@@ -236,10 +274,13 @@ for _ = 1, math.random(1, 5) do
236274
if math.random(1, 2) == 1 then
237275
generate_query(query)
238276
else
239-
if math.random(1, 2) == 1 then
277+
local r = math.random(1, 3)
278+
if r == 1 then
240279
evo.remove(query, evo.INCLUDES)
241-
else
280+
elseif r == 2 then
242281
evo.remove(query, evo.EXCLUDES)
282+
else
283+
evo.remove(query, evo.VARIANTS)
243284
end
244285
end
245286
end

evolved.d.tl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969

7070
include: function(self: Builder, ...: Fragment): Builder
7171
exclude: function(self: Builder, ...: Fragment): Builder
72+
variant: function(self: Builder, ...: Fragment): Builder
7273
require: function(self: Builder, ...: Fragment): Builder
7374

7475
on_set: function<Component>(self: Builder, on_set: function(Entity, Fragment, ? Component, ? Component)): Builder
@@ -102,6 +103,7 @@
102103

103104
INCLUDES: Fragment
104105
EXCLUDES: Fragment
106+
VARIANTS: Fragment
105107
REQUIRES: Fragment
106108

107109
ON_SET: Fragment

0 commit comments

Comments
 (0)