2 Commits

Author SHA1 Message Date
BlackMATov
008df17ee6 fix readme formatting 2025-08-20 05:41:06 +07:00
BlackMATov
ba3018213e builder wildcard has/remove first impl 2025-08-20 05:16:18 +07:00
3 changed files with 360 additions and 54 deletions

View File

@@ -1149,6 +1149,7 @@ collect_garbage :: ()
### Relations
```
pair :: id, id -> id
unpair :: id -> id, id
@@ -1163,6 +1164,7 @@ secondaries :: entity, fragment -> {secondaries_state? -> fragment?, component?}
primary_count :: entity, fragment -> integer
secondary_count :: entity, fragment -> integer
```
### Classes

View File

@@ -1442,6 +1442,164 @@ do
end
end
do
local p1, p2, s1, s2 = evo.id(4)
do
local b = evo.builder()
b:set(evo.pair(p1, s1), 11)
b:set(evo.pair(p1, s2), 12)
b:set(evo.pair(p2, s1), 21)
b:set(evo.pair(p2, s2), 22)
b:remove(evo.pair(evo.ANY, evo.ANY))
assert(not b:has(evo.pair(p1, s1)))
assert(not b:has(evo.pair(p1, s2)))
assert(not b:has(evo.pair(p2, s1)))
assert(not b:has(evo.pair(p2, s2)))
assert(not b:has(evo.pair(p1, evo.ANY)))
assert(not b:has(evo.pair(p2, evo.ANY)))
assert(not b:has(evo.pair(evo.ANY, s1)))
assert(not b:has(evo.pair(evo.ANY, s2)))
assert(not b:has(evo.pair(evo.ANY, evo.ANY)))
end
do
local b = evo.builder()
b:set(evo.pair(p1, s1), 11)
b:set(evo.pair(p1, s2), 12)
b:set(evo.pair(p2, s1), 21)
b:set(evo.pair(p2, s2), 22)
b:remove(evo.pair(p2, evo.ANY))
assert(b:has(evo.pair(p1, s1)))
assert(b:has(evo.pair(p1, s2)))
assert(not b:has(evo.pair(p2, s1)))
assert(not b:has(evo.pair(p2, s2)))
assert(b:has(evo.pair(p1, evo.ANY)))
assert(not b:has(evo.pair(p2, evo.ANY)))
assert(b:has(evo.pair(evo.ANY, s1)))
assert(b:has(evo.pair(evo.ANY, s2)))
assert(b:has(evo.pair(evo.ANY, evo.ANY)))
end
do
local b = evo.builder()
b:set(evo.pair(p1, s1), 11)
b:set(evo.pair(p1, s2), 12)
b:set(evo.pair(p2, s1), 21)
b:set(evo.pair(p2, s2), 22)
b:remove(evo.pair(p2, evo.ANY))
assert(b:has_all(evo.pair(p1, s1)))
assert(b:has_all(evo.pair(p1, s1), evo.pair(p1, s2)))
assert(not b:has_all(evo.pair(p1, s1), evo.pair(p2, s1)))
assert(not b:has_all(evo.pair(p2, s1), evo.pair(p1, s2)))
assert(not b:has_all(evo.pair(p2, s1), evo.pair(p2, s2)))
assert(b:has_all(evo.pair(p1, evo.ANY)))
assert(b:has_all(evo.pair(p1, evo.ANY), evo.pair(evo.ANY, s1)))
assert(not b:has_all(evo.pair(p2, evo.ANY), evo.pair(evo.ANY, s1)))
assert(not b:has_all(evo.pair(p2, evo.ANY), evo.pair(evo.ANY, p1)))
assert(b:has_all(evo.pair(evo.ANY, evo.ANY)))
assert(b:has_all(
evo.pair(p1, s1),
evo.pair(p1, s2),
evo.pair(evo.ANY, s1),
evo.pair(p1, evo.ANY),
evo.pair(evo.ANY, evo.ANY)))
assert(not b:has_all(
evo.pair(p1, s1),
evo.pair(p1, s2),
evo.pair(evo.ANY, s1),
evo.pair(p1, evo.ANY),
evo.pair(evo.ANY, evo.ANY),
evo.pair(p2, evo.ANY)))
end
do
local b = evo.builder()
b:set(evo.pair(p1, s1), 11)
b:set(evo.pair(p1, s2), 12)
b:set(evo.pair(p2, s1), 21)
b:set(evo.pair(p2, s2), 22)
b:remove(evo.pair(p2, evo.ANY))
assert(b:has_any(evo.pair(p1, s1)))
assert(b:has_any(evo.pair(p1, s1), evo.pair(p1, s2)))
assert(b:has_any(evo.pair(p1, s1), evo.pair(p2, s1)))
assert(b:has_any(evo.pair(p2, s1), evo.pair(p1, s2)))
assert(not b:has_any(evo.pair(p2, s1), evo.pair(p2, s2)))
assert(b:has_any(evo.pair(p1, evo.ANY)))
assert(b:has_any(evo.pair(p1, evo.ANY), evo.pair(evo.ANY, s1)))
assert(b:has_any(evo.pair(p2, evo.ANY), evo.pair(evo.ANY, s1)))
assert(not b:has_any(evo.pair(p2, evo.ANY), evo.pair(evo.ANY, p1)))
assert(b:has_any(evo.pair(evo.ANY, evo.ANY)))
assert(b:has_any(
evo.pair(p1, s1),
evo.pair(p1, s2),
evo.pair(evo.ANY, s1),
evo.pair(p1, evo.ANY),
evo.pair(evo.ANY, evo.ANY)))
assert(not b:has_any(
evo.pair(p2, s1),
evo.pair(p2, s2),
evo.pair(p2, evo.ANY),
evo.pair(evo.ANY, p1),
evo.pair(evo.ANY, p2)))
assert(b:has_any(
evo.pair(p2, s1),
evo.pair(p2, s2),
evo.pair(p2, evo.ANY),
evo.pair(evo.ANY, p1),
evo.pair(evo.ANY, p2),
evo.pair(p1, evo.ANY)))
end
do
local b = evo.builder()
b:set(evo.pair(p1, s1), 11)
b:set(evo.pair(p1, s2), 12)
b:set(evo.pair(p2, s1), 21)
b:set(evo.pair(p2, s2), 22)
b:remove(evo.pair(p1, evo.ANY))
b:remove(evo.pair(p1, evo.ANY))
b:remove(evo.pair(p2, evo.ANY))
b:remove(evo.pair(p2, evo.ANY))
b:remove(evo.pair(evo.ANY, s1))
b:remove(evo.pair(evo.ANY, s1))
b:remove(evo.pair(evo.ANY, s2))
b:remove(evo.pair(evo.ANY, s2))
assert(not b:has(evo.pair(evo.ANY, evo.ANY)))
end
end
-- TODO
-- builder:has/has_all/has_any should work with wildcards / remove too?
-- should we provide wildcard support for get operations?

View File

@@ -214,6 +214,8 @@ __chunk_mt.__index = __chunk_mt
---@class evolved.builder
---@field package __components table<evolved.fragment, evolved.component>
---@field package __primary_pairs? table<integer, evolved.assoc_list>
---@field package __secondary_pairs? table<integer, evolved.assoc_list>
local __builder_mt = {}
__builder_mt.__index = __builder_mt
@@ -6562,7 +6564,35 @@ end
---@return boolean
---@nodiscard
function __builder_mt:has(fragment)
return self.__components[fragment] ~= nil
if self.__components[fragment] then
return true
end
local primary_pairs = self.__primary_pairs
local secondary_pairs = self.__secondary_pairs
local maybe_has_pairs = primary_pairs and secondary_pairs
if maybe_has_pairs and __evolved_is_wildcard(fragment) then
---@cast primary_pairs -?
---@cast secondary_pairs -?
local fragment_primary_index, fragment_secondary_index, fragment_opts =
__evolved_unpack(fragment)
if fragment_opts == __WILDCARD_OPTS then
return __lua_next(primary_pairs) ~= nil
and __lua_next(secondary_pairs) ~= nil
elseif fragment_opts == __PRIMARY_WILDCARD_OPTS then
local secondary_fragments = secondary_pairs[fragment_secondary_index]
return secondary_fragments ~= nil and secondary_fragments.__item_count > 0
elseif fragment_opts == __SECONDARY_WILDCARD_OPTS then
local primary_fragments = primary_pairs[fragment_primary_index]
return primary_fragments ~= nil and primary_fragments.__item_count > 0
end
end
return false
end
---@param ... evolved.fragment fragments
@@ -6577,30 +6607,41 @@ function __builder_mt:has_all(...)
local cs = self.__components
local has_f = self.has
local has_fs = self.has_all
local m_has_p = self.__primary_pairs and self.__secondary_pairs
if fragment_count == 1 then
local f1 = ...
return cs[f1] ~= nil
return (m_has_p and has_f(self, f1))
or (not m_has_p and cs[f1] ~= nil)
end
if fragment_count == 2 then
local f1, f2 = ...
return cs[f1] ~= nil and cs[f2] ~= nil
return (m_has_p and has_f(self, f1) and has_f(self, f2))
or (not m_has_p and cs[f1] ~= nil and cs[f2] ~= nil)
end
if fragment_count == 3 then
local f1, f2, f3 = ...
return cs[f1] ~= nil and cs[f2] ~= nil and cs[f3] ~= nil
return (m_has_p and has_f(self, f1) and has_f(self, f2) and has_f(self, f3))
or (not m_has_p and cs[f1] ~= nil and cs[f2] ~= nil and cs[f3] ~= nil)
end
if fragment_count == 4 then
local f1, f2, f3, f4 = ...
return cs[f1] ~= nil and cs[f2] ~= nil and cs[f3] ~= nil and cs[f4] ~= nil
return (m_has_p and has_f(self, f1) and has_f(self, f2) and has_f(self, f3) and has_f(self, f4))
or (not m_has_p and cs[f1] ~= nil and cs[f2] ~= nil and cs[f3] ~= nil and cs[f4] ~= nil)
end
do
local f1, f2, f3, f4 = ...
return cs[f1] ~= nil and cs[f2] ~= nil and cs[f3] ~= nil and cs[f4] ~= nil and
self:has_all(__lua_select(5, ...))
return (m_has_p and has_f(self, f1) and has_f(self, f2) and has_f(self, f3) and has_f(self, f4)
and has_fs(self, __lua_select(5, ...)))
or (not m_has_p and cs[f1] ~= nil and cs[f2] ~= nil and cs[f3] ~= nil and cs[f4] ~= nil
and has_fs(self, __lua_select(5, ...)))
end
end
@@ -6616,30 +6657,41 @@ function __builder_mt:has_any(...)
local cs = self.__components
local has_f = self.has
local has_fs = self.has_any
local m_has_p = self.__primary_pairs and self.__secondary_pairs
if fragment_count == 1 then
local f1 = ...
return cs[f1] ~= nil
return (m_has_p and has_f(self, f1))
or (not m_has_p and cs[f1] ~= nil)
end
if fragment_count == 2 then
local f1, f2 = ...
return cs[f1] ~= nil or cs[f2] ~= nil
return (m_has_p and (has_f(self, f1) or has_f(self, f2)))
or (not m_has_p and (cs[f1] ~= nil or cs[f2] ~= nil))
end
if fragment_count == 3 then
local f1, f2, f3 = ...
return cs[f1] ~= nil or cs[f2] ~= nil or cs[f3] ~= nil
return (m_has_p and (has_f(self, f1) or has_f(self, f2) or has_f(self, f3)))
or (not m_has_p and (cs[f1] ~= nil or cs[f2] ~= nil or cs[f3] ~= nil))
end
if fragment_count == 4 then
local f1, f2, f3, f4 = ...
return cs[f1] ~= nil or cs[f2] ~= nil or cs[f3] ~= nil or cs[f4] ~= nil
return (m_has_p and (has_f(self, f1) or has_f(self, f2) or has_f(self, f3) or has_f(self, f4)))
or (not m_has_p and (cs[f1] ~= nil or cs[f2] ~= nil or cs[f3] ~= nil or cs[f4] ~= nil))
end
do
local f1, f2, f3, f4 = ...
return cs[f1] ~= nil or cs[f2] ~= nil or cs[f3] ~= nil or cs[f4] ~= nil or
self:has_any(__lua_select(5, ...))
return (m_has_p and (has_f(self, f1) or has_f(self, f2) or has_f(self, f3) or has_f(self, f4)
or has_fs(self, __lua_select(5, ...))))
or (not m_has_p and (cs[f1] ~= nil or cs[f2] ~= nil or cs[f3] ~= nil or cs[f4] ~= nil
or has_fs(self, __lua_select(5, ...))))
end
end
@@ -6687,15 +6739,16 @@ end
---@return evolved.builder builder
function __builder_mt:set(fragment, component)
if __debug_mode then
if __evolved_is_wildcard(fragment) then
__error_fmt('the wildcard fragment (%s) cannot be used as a fragment', __id_name(fragment))
end
if not __evolved_alive(fragment) then
__error_fmt('the fragment (%s) is not alive and cannot be used', __id_name(fragment))
end
if __evolved_is_wildcard(fragment) then
__error_fmt('the wildcard fragment (%s) cannot be used as a fragment', __id_name(fragment))
end
end
do
---@type evolved.default?, evolved.duplicate?
local fragment_default, fragment_duplicate =
__evolved_get(fragment, __DEFAULT, __DUPLICATE)
@@ -6706,6 +6759,41 @@ function __builder_mt:set(fragment, component)
if new_component == nil then new_component = true end
self.__components[fragment] = new_component
end
if __evolved_is_pair(fragment) then
local fragment_primary_index, fragment_secondary_index =
__evolved_unpack(fragment)
local primary_pairs = self.__primary_pairs
local secondary_pairs = self.__secondary_pairs
if not primary_pairs then
primary_pairs = {}
self.__primary_pairs = primary_pairs
end
if not secondary_pairs then
secondary_pairs = {}
self.__secondary_pairs = secondary_pairs
end
local primary_fragments = primary_pairs[fragment_primary_index]
local secondary_fragments = secondary_pairs[fragment_secondary_index]
if not primary_fragments then
primary_fragments = __assoc_list_new(4)
primary_pairs[fragment_primary_index] = primary_fragments
end
if not secondary_fragments then
secondary_fragments = __assoc_list_new(4)
secondary_pairs[fragment_secondary_index] = secondary_fragments
end
__assoc_list_insert(primary_fragments, fragment)
__assoc_list_insert(secondary_fragments, fragment)
end
return self
end
@@ -6719,42 +6807,100 @@ function __builder_mt:remove(...)
return self
end
local cs = self.__components
local fragment = ...
if fragment_count == 1 then
local f1 = ...
cs[f1] = nil
return self
local components = self.__components
components[fragment] = nil
local primary_pairs = self.__primary_pairs
local secondary_pairs = self.__secondary_pairs
local maybe_has_pairs = primary_pairs and secondary_pairs
if maybe_has_pairs and __evolved_is_wildcard(fragment) then
---@cast primary_pairs -?
---@cast secondary_pairs -?
local fragment_primary_index, fragment_secondary_index, fragment_opts =
__evolved_unpack(fragment)
if fragment_opts == __WILDCARD_OPTS then
for primary_index, primary_fragments in __lua_next, primary_pairs do
local primary_fragment_list = primary_fragments.__item_list
local primary_fragment_count = primary_fragments.__item_count
for primary_fragment_index = 1, primary_fragment_count do
local primary_fragment = primary_fragment_list[primary_fragment_index]
components[primary_fragment] = nil
end
if fragment_count == 2 then
local f1, f2 = ...
cs[f1] = nil; cs[f2] = nil
return self
primary_pairs[primary_index] = nil
end
if fragment_count == 3 then
local f1, f2, f3 = ...
cs[f1] = nil; cs[f2] = nil; cs[f3] = nil
return self
for secondary_index, secondary_fragments in __lua_next, secondary_pairs do
local secondary_fragment_list = secondary_fragments.__item_list
local secondary_fragment_count = secondary_fragments.__item_count
for secondary_fragment_index = 1, secondary_fragment_count do
local secondary_fragment = secondary_fragment_list[secondary_fragment_index]
components[secondary_fragment] = nil
end
if fragment_count == 4 then
local f1, f2, f3, f4 = ...
cs[f1] = nil; cs[f2] = nil; cs[f3] = nil; cs[f4] = nil
return self
secondary_pairs[secondary_index] = nil
end
elseif fragment_opts == __PRIMARY_WILDCARD_OPTS then
local secondary_fragments = secondary_pairs[fragment_secondary_index]
local secondary_fragment_list = secondary_fragments and secondary_fragments.__item_list
local secondary_fragment_count = secondary_fragments and secondary_fragments.__item_count or 0
for secondary_fragment_index = 1, secondary_fragment_count do
local secondary_fragment = secondary_fragment_list[secondary_fragment_index]
components[secondary_fragment] = nil
local secondary_fragment_primary_index, _ = __evolved_unpack(secondary_fragment)
if __assoc_list_remove(primary_pairs[secondary_fragment_primary_index], secondary_fragment) == 0 then
primary_pairs[secondary_fragment_primary_index] = nil
end
end
do
local f1, f2, f3, f4 = ...
cs[f1] = nil; cs[f2] = nil; cs[f3] = nil; cs[f4] = nil
return self:remove(__lua_select(5, ...))
secondary_pairs[fragment_secondary_index] = nil
elseif fragment_opts == __SECONDARY_WILDCARD_OPTS then
local primary_fragments = primary_pairs[fragment_primary_index]
local primary_fragment_list = primary_fragments and primary_fragments.__item_list
local primary_fragment_count = primary_fragments and primary_fragments.__item_count or 0
for primary_fragment_index = 1, primary_fragment_count do
local primary_fragment = primary_fragment_list[primary_fragment_index]
components[primary_fragment] = nil
local _, primary_fragment_secondary_index = __evolved_unpack(primary_fragment)
if __assoc_list_remove(secondary_pairs[primary_fragment_secondary_index], primary_fragment) == 0 then
secondary_pairs[primary_fragment_secondary_index] = nil
end
end
primary_pairs[fragment_primary_index] = nil
end
end
return fragment_count > 1 and self:remove(__lua_select(2, ...)) or self
end
---@return evolved.builder builder
function __builder_mt:clear()
if self.__components then
__lua_table_clear(self.__components)
end
if self.__primary_pairs then
__lua_table_clear(self.__primary_pairs)
end
if self.__secondary_pairs then
__lua_table_clear(self.__secondary_pairs)
end
return self
end