diff --git a/README.md b/README.md index 76736da..7896926 100644 --- a/README.md +++ b/README.md @@ -599,6 +599,9 @@ function evolved.defer() end ---@return boolean committed function evolved.commit() end + +---@return boolean cancelled +function evolved.cancel() 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 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. @@ -629,6 +632,34 @@ evolved.commit() assert(not evolved.has(player, poisoned)) ``` +The [`evolved.cancel`](#evolvedcancel) function can be used to cancel all queued changes in the current deferred scope. This is useful if you want to discard all changes made inside the scope and revert to the previous state on an error or some other condition. + +```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)) + +-- cancel the deferred operations +evolved.cancel() + +-- the poisoned fragment is still there +assert(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. @@ -1101,6 +1132,7 @@ unpack :: id -> integer, integer defer :: boolean commit :: boolean +cancel :: boolean spawn :: ? -> entity multi_spawn :: integer, ? -> entity[] @@ -1223,6 +1255,7 @@ builder_mt:destruction_policy :: id -> builder ## vX.X.X - The internal garbage collector now collects more garbage +- Added the new [`evolved.cancel`](#evolvedcancel) function ## v1.2.0 @@ -1345,6 +1378,13 @@ function evolved.defer() end function evolved.commit() end ``` +### `evolved.cancel` + +```lua +---@return boolean cancelled +function evolved.cancel() end +``` + ### `evolved.spawn` ```lua diff --git a/develop/all.lua b/develop/all.lua index b2cc3b6..fca6cf7 100644 --- a/develop/all.lua +++ b/develop/all.lua @@ -1,5 +1,6 @@ require 'develop.samples.systems' +require 'develop.testing.cancel_tests' require 'develop.testing.multi_spawn_tests' require 'develop.testing.name_tests' require 'develop.testing.requires_fragment_tests' diff --git a/develop/testing/cancel_tests.lua b/develop/testing/cancel_tests.lua new file mode 100644 index 0000000..43d8b11 --- /dev/null +++ b/develop/testing/cancel_tests.lua @@ -0,0 +1,105 @@ +local evo = require 'evolved' + +do + assert(evo.defer()) + assert(evo.cancel()) +end + +do + assert(evo.defer()) + assert(not evo.defer()) + assert(not evo.cancel()) + assert(evo.commit()) +end + +do + assert(evo.defer()) + assert(not evo.defer()) + assert(not evo.cancel()) + assert(evo.cancel()) +end + +do + assert(evo.defer()) + assert(not evo.defer()) + assert(not evo.cancel()) + assert(not evo.defer()) + assert(not evo.cancel()) + assert(evo.commit()) +end + +do + local e, f = evo.id(2) + + assert(evo.defer()) + do + evo.set(e, f) + assert(not evo.has(e, f)) + end + assert(evo.cancel()) + + assert(not evo.has(e, f)) +end + +do + local e, f1, f2 = evo.id(3) + + assert(evo.defer()) + do + evo.set(e, f1) + assert(not evo.has(e, f1)) + + assert(not evo.defer()) + do + evo.set(e, f2) + assert(not evo.has(e, f2)) + end + assert(not evo.cancel()) + end + assert(evo.commit()) + + assert(evo.has(e, f1)) + assert(not evo.has(e, f2)) +end + +do + local e, f1, f2 = evo.id(3) + + assert(evo.defer()) + do + evo.set(e, f1) + assert(not evo.has(e, f1)) + + assert(not evo.defer()) + do + evo.set(e, f2) + assert(not evo.has(e, f2)) + end + assert(not evo.cancel()) + end + assert(evo.cancel()) + + assert(not evo.has(e, f1)) + assert(not evo.has(e, f2)) +end + +do + local e, f1, f2 = evo.id(3) + + assert(evo.defer()) + do + evo.set(e, f1) + assert(not evo.has(e, f1)) + + assert(not evo.defer()) + do + evo.set(e, f2) + assert(not evo.has(e, f2)) + end + assert(not evo.commit()) + end + assert(evo.cancel()) + + assert(not evo.has(e, f1)) + assert(not evo.has(e, f2)) +end diff --git a/evolved.lua b/evolved.lua index 04e0f98..ebd0318 100644 --- a/evolved.lua +++ b/evolved.lua @@ -118,6 +118,7 @@ local __acquired_count = 0 ---@type integer local __available_primary = 0 ---@type integer local __defer_depth = 0 ---@type integer +local __defer_points = {} ---@type integer[] local __defer_length = 0 ---@type integer local __defer_bytecode = {} ---@type any[] @@ -790,6 +791,7 @@ local __evolved_unpack local __evolved_defer local __evolved_commit +local __evolved_cancel local __evolved_spawn local __evolved_multi_spawn @@ -4606,13 +4608,14 @@ end ---@return boolean started function __evolved_defer() __defer_depth = __defer_depth + 1 + __defer_points[__defer_depth] = __defer_length return __defer_depth == 1 end ---@return boolean committed function __evolved_commit() if __defer_depth <= 0 then - __error_fmt('unbalanced defer/commit') + __error_fmt('unbalanced defer/commit/cancel') end __defer_depth = __defer_depth - 1 @@ -4641,6 +4644,17 @@ function __evolved_commit() return true end +---@return boolean cancelled +function __evolved_cancel() + if __defer_depth <= 0 then + __error_fmt('unbalanced defer/commit/cancel') + end + + __defer_length = __defer_points[__defer_depth] + + return __evolved_commit() +end + ---@param components? table ---@return evolved.entity entity function __evolved_spawn(components) @@ -5951,6 +5965,17 @@ function __evolved_collect_garbage() __entity_places = new_entity_places end + do + ---@type integer[] + local new_defer_points = __lua_table_new(__defer_depth, 0) + + __lua_table_move( + __defer_points, 1, __defer_depth, + 1, new_defer_points) + + __defer_points = new_defer_points + end + do ---@type any[] local new_defer_bytecode = __lua_table_new(__defer_length, 0) @@ -6894,6 +6919,7 @@ evolved.unpack = __evolved_unpack evolved.defer = __evolved_defer evolved.commit = __evolved_commit +evolved.cancel = __evolved_cancel evolved.spawn = __evolved_spawn evolved.multi_spawn = __evolved_multi_spawn