Closed sinlerdev closed 6 months ago
Atomic updates in the form of a BatchWrite
public operator could be useful in certain cases where two or more dependencies could change at the same time, and most importantly, these dependencies have overlapping dependents but at the same time also have unique dependents.
How Vinum would implement it is entirely internal and shouldn't be a concern to the public, but since GNR (Generalized Node Reduction) algorithm is implemented into UpdateAll
, the simplest solution is to create a RNode that houses all of these dependencies as dependents, and then call the function on that RNode.
However, a very important question arises when dealing with BatchWrite
; how are we gonna typecheck this operator? Technically speaking, Luau lacks the ability to enable typechecking, so the "best" solution could be the introduction of a "Transaction" scope like where one could use DeferWrite
and these results are then applied after the Transaction has ended, preferably with an internal equivalent to BatchWrite, something like this:
Transaction(function(DeferWrite)
DeferWrite(state1, 1)
DeferWrite(state2, 2)
end) -- after this function is executed, all data is then applied with BatchWrite.
It could be useful to publicize both BatchWrite and Transaction, so that both act as different solutions for different problems.
It's important to note that Transactions introduce a lot of unnecessary code for their very simple job, which to update a bunch of state structs at the same time, so we should instead focus more on finding a reliable and type-safe function for batch writing.
A solution that requires recognition could be something like this:
SafeBatch({Info(State, 1), Info(State, "hi"})
Info here is just {_state: State, _value: T}
, so for Vinum, it's just a packing function that packs state and their desired new value into a spec that SafeBatch understands well.
Of course, tables returned from Info could be memoized and have their lifetime managed by the state's scope so that as long the state is alive, the cached table is alive as well, which means in worst case scenario, that there is a minor increase in memory footprint, however, in general and best case scenario, it means that there is only one BatchInfo alive being reused for lots of batch operations.
This isn't micro-optimization, and this statement must be understood very well. There could be thousands of BatchInfo being created for every SafeBatch call. This means both memory and performance costs, where memory is increased since those huge numbers of BatchInfos need to be housed somewhere, and performance speed is decreased since those objects put pressure on Luau's Garbage Collector.
Of course, we could also allow an unsafe UnsafeBatch
function where it disregards those Info objects and just accept types of any
. This function should matches the semantics of the previously proposed BatchWrite
function.
Final Design:
Barebones:
Batch(object, data, object2, data2)
Safe:
Batch(Info(object, data), Info(object2, data2))
Info
in this scenario is simply just (object: State<T>, data: T) -> (State<T>, data: T)
, as such, its is easily inline-able, and doesn't introduce any memory allocation, and as a bonus, is entirely optional.
A slight issue with the safe
option in the final design is that Luau is just.. kind of awkward with multiple return values, which results in values getting dropped. For now, I am pushing the barebones implementation, and will look at possible solutions later.
Upon further inspection, running into mistakes concerning Batch and typechecking is actually minimal due to the relatively simple API.
Atomic transactions' support is highly useful, and can provide an efficient method for Vinum users to update objects without accidentally triggering more than one update on a tree when not needed.
Perhaps this is the only issue I thought about its details for a lot of time, but I am not giving a lot of information about it for now because it can become irrelevant by the time I work on it.