siiky
2024/07/14
2024/07/15
en
On the previous log I wrote about token philosophy and how I made a bad implementation decision.
However, not all is lost if the option is given the the programmer: the same strategy that is implemented in gen_pnet can be offered as an alternative, on a transition-by-transition basis. It should be used only as a last resort, because it's slow and most often it'll only be used for "RPC"-related paths, in the input interface places and internally. Another alternative is to choose a field of a tuple (e.g. the 1st) to serve as the "key" to group tokens of different places.
The current PNEE:consume/1 function has a very simple interface, similar to a DNF: it must return a list of alternative sets of places to consume from (optionally with a multiplicity, and optionally with a token-predicate).
consume(a) -> [[coin_slot]]; consume(b) -> [[signal, storage]].
This option should be the most painless to use, and I believe it covers the vast majority of all transitions. The implementation is pretty simple, and the performance is linear. The other alternatives could be provided with a helper function, something like this:
consume(send_result) -> pnee:over_all( [get_res_reqs, done_reqs], % The tokens should be accepted only if they're of the same request fun(#{get_res_reqs:=[{ReqId, _Foo}], done_reqs:=[{ReqId, _Result}]}) -> true end ). % OR % Use erlang:element/2 to get each token's request ID consume(send_result) -> pnee:by_key(1, [get_res_reqs, done_reqs]).
Compared to the simplest (default) consume algorithm, pnee:by_key/2 shouldn't be much slower, and seems to cover a large number of common cases, although it still isn't generic enough to offer the full high-level Petri net experience. pnee:over_all/2 is there for that.
With this ease-of-use hierarchy I expect the programmer will be lead to the best performance-wise option on each case.