Closed nivekuil closed 1 year ago
You mean retract facts right? Rules cannot be retracted.
To remove all the facts for a given entity you could do it out-of-band like you mention, or make a rule that triggers when the entities dies and manually retract all of the facts. It will throw an exception if the fact doesn't exist, but you can use o/contains?
to check if it exists first.
The above approach requires you to know in advance all of the attributes you want to remove, though. Right now there isn't a way to query all facts with a given id. One possibility would be to allow attributes to have binding symbols, so you could write a rule like this:
::player-dies
[:what
[id ::dead? true]
[id attr value]
:then
(o/retract! id attr)]
Right now this will not work because the attribute isn't allowed to have a symbol there, but this would make it somewhat convenient to make catch-all rules that retract all the facts with a given id. Would that help in your use case?
You mean retract facts right? Rules cannot be retracted.
oops, yes
Would that help in your use case?
That looks helpful but I'm not sure I understand your example. Is [id ::dead? true]
querying and checking at the same time? So ::player-dies
would be triggered multiple times per fire-rules
right, is that an inefficient way to modify the session? I was thinking garbage collection-like tasks should be fired separately from the game loop for better frame pacing, so I guess you could insert a ::gc?
true fact somewhere but then how would you know when all the gc related rules are done?
I assumed that ::dead?
was an attribute you give to entities. So, when you want to mark an entity as dead, you would do something like (o/insert! :my-entity-id ::dead? true)
. Is that the case?
If so, then the rule I wrote above would only trigger one time, when you set ::dead?
to true. It would immediately remove all facts that have :my-entity-id
as the id. If that is what you're looking for, I can try implementing it and see if it works for you.
ah, that makes sense. The way I have it right now is the entity has all possible attributes already added for denormalization/derived fact purposes, since those all have to exist for the derived fact rule to fire, so it starts with ::dead? false
. It probably makes more sense to insert the ::dead?
fact only when it matters. It sounds like it could work.
OK I pushed a quick change that allows binding symbols to be used on the attribute column. If you're using clojure deps, just change your deps.edn to bring odoyle in via git:
net.sekao/odoyle-rules {:git/url "https://github.com/oakes/odoyle-rules.git"
:git/sha "20788178fb9cfd7cc28742a58016898d133b6a61"}
With that, you should be able to write a rule like I made above. Try it out whenever and let me know how it goes. If it works well for you I can cut a release with it.
BTW you can initialize ::dead?
to false
if you want. The rule will still only be triggered once, because its what block says that it has to be true
.
The rule will still only be triggered once, because its what block says that it has to be true.
oh, I did not realize that the right side could be a value acting as a match constraint, not just a symbol acting as a binding. Does the README mention this somewhere?
The new attribute matching feature does work, but I realized I had a further problem: in my ::attack
rule, I'm modifying the targets ::hp
directly, so depending on the execution order it could add some fact about a dead entity like [1 ::hp -5]
that never gets cleaned up. I think I should break that logic out into a ::take-damage
rule instead. The lesson is that rules should only modify entities they query for?
In the process of doing this I've noticed that RETE(/UL?) seems to scale pretty poorly with # of facts, even if nothing is explicitly happening with them, so in general something like odoyle would be better suited for complex interactions over a few entities (e.g. Path of Exile) and not something like Vampire Survivors? I wonder if you could spatially partition the set of facts and get things like efficient collision detection for free.
It looks like I didn't specifically have an example of a literal value, so I just added a bit to the end of the Conditions section to explain it.
Regarding "rules should only modify entities they query for", that sounds right, but what do you mean by query for? Are you calling o/query-all
in your :then
block, or are you talking about your :what
block? I talked about the former a bit in the readme (search for "Important rule of thumb").
Yeah it's not going to scale like a typical ECS, I think. It is an open design question about how to get the benefits of both, and I don't know of anyone that has tried it. See #21.
I'll test the attribute matching a bit more and if all looks good, I will cut a release later today.
I released 1.1.0 with this feature so I'll close this.
When an entity dies, what's a good way to retract all facts related to it? My thought was to make a
::graveyard
rule that queries for::dead?
entities then query that out-of-band to retract rules but it seems to leave behind some rules occasionally.