My rules engine is written in pure Nim and is just about to reach its 1 year birthday. It is mainly meant for games but you could use it for anything. It overlaps with ECSs (entity component systems), but i think rules engines are way more powerful, and strangely overlooked.
https://github.com/paranim/pararules
The README is very long and there are example games linked at the top. Peace!
That looks really cool!
I have a couple of questions:
rule movePlayer(Fact):
what:
(Global, DeltaTime, dt)
(Global, PressedKeys, keys, then = false)
(Player, X, x, then = false)
then:
if keys.contains(263): # left arrow
session.insert(Player, X, x - 1.0)
elif keys.contains(262): # right arrow
session.insert(Player, X, x + 1.0)
here we don't update key state in then: block but then = false is present in (Global, PressedKeys, keys, then = false), not sure what to make of it
Here are 2 conflicting statements?
let chars = session.queryAll(rules.getCharacter)
for c in chars:
if c.id == Player:
echo c.x, " ", c.y
and
rule getCharacter(Fact):
what:
(id, X, x)
(id, Y, y)
cond:
id != Player # type mismatch: got <int, Id>
followed by: You need to write id != Player.ord instead, because pararules transforms all ids to normal integers and gives them back that way.
So my understanding is that it transforms all ids to integers but outside of rule blocks they are still usual enums? We can't do c.id == Player with integers, "gives them back that way" is a bit confusing
here we don't update key state in then: block but then = false is present in (Global, PressedKeys, keys, then = false), not sure what to make of it
That is true, but for rules that need to run every tick, I generally just put the DeltaTime at the top of the what block and set everything else as then = false. But I believe you're right that it isn't necessary for PressedKeys -- that is modified externally (by a GLFW callback in my example games).
Here are 2 conflicting statements?
Good catch, that was a typo and is fixed now.
From your readme
As mentioned before, it is highly recommended that you use reference types like ref seq, rather than value types like seq, to store collections in the session.
Can't system.move be used in order to avoid the copies? The Nim optimizers don't like ref all that much...
If system.move invalidates the original location then it seems like it wouldn't work, because the original location still needs to be used later. The RETE algorithm requires keeping partial matches for the rules, choosing to consume more memory for the sake of better performance.
For example, the movePlayer rule above has "memory nodes" storing just the first binding (dt), the first + second (dt, keys), and the first + second + third (dt, keys, x). Any time one of those is updated, it cascades down and is merged with any subsequent ones until it's a full match.