Backtracking cannot see predicates changes when retract/assertz itself

I did an experiment about retract/1 or assertz/1.

See following program:

:- dynamic test/1.

update(X) :-
  test(X),
  format("update ~w~n", [X]),  
  (   X=2
  ->  format("update X=2 ~n"), retract(test(3))
  ;   format("update X=/=2 ~n"), true
  ),
  listing(test/1).

and query:

?- assertz(test(1)),
   assertz(test(2)),
   assertz(test(3)),
   update(X).
update 1
update X=/=2 
:- dynamic test/1.

test(1).
test(2).
test(3).

X = 1 ;
update 2
update X=2 
:- dynamic test/1.

test(1).
test(2).

X = 2 ;
update 3
update X=/=2 
:- dynamic test/1.

test(1).
test(2).

X = 3.

Intuitively, the fact test(3) should be retracted when X=2, but why it still do backtracking for test(3) ? It should terminate when X=2, is that right?

I found some clues in SWI-Prolog Manual, but I’m not sure if it’s related:

Traditionally, Prolog systems used the immediate update view : new clauses became visible to predicates backtracking over dynamic predicates immediately, and retracted clauses became invisible immediately.

Starting with SWI-Prolog 3.3.0 we adhere to the logical update view , where backtrackable predicates that enter the definition of a predicate will not see any changes (either caused by assert/1 or retract/1) to the predicate.

Does it mean that in old Prolog system the above example would terminate when X=2? If so, is it possible to get the old behavior in current SWI-Prolog?

Thanks.

It is true that the result you see is the consequence of the logical update view. And no, there is no way to get the old immediate update view using the dynamic database.

You’ll need to reconsider your design at a somewhat higher level. If you want help it would help if you share your overall aim.

2 Likes

The aim is that I’d like to simulate Entity_component_system (ECS) in Prolog.

My current workaround is that

  1. Introduce the iter_entity/1, the game loop traverse iter_entity/1 instead of entity/1.

  2. During a frame, structural changes impact entity/1 instead of iter_entity/1. P.s. The structural changes are creating/deleting entities, adding/removing components to an entity.

  3. At the end of frame, clean iter_entity(EID) and copy entity(EID) to iter_entity(EID).

See the following program.

:- dynamic iter_entity/1, entity/1, component/3.

entity(1).
entity(2).
entity(3).
entity(4).

component(1, position, vec(1,1)).
component(1, health, 100).
component(1, speed, 5).
component(1, bounds, 1).

component(2, position, vec(2,2)).
component(2, health, 200).
component(2, speed, 10).
component(2, bounds, 2).

component(3, position, vec(3,3)).
component(3, health, 300).
component(3, speed, 15).
component(3, bounds, 3).

component(4, position, vec(4,4)).
component(4, health, 400).
component(4, speed, 20).
component(4, bounds, 4).

iter_entity(1).
iter_entity(2).
iter_entity(3).
iter_entity(4).

query_entity_components(EID) :-  
 format("query_entity_components ~w~n", [EID]),
 forall(component(EID, K, V), format("component(~w, ~w, ~w)~n", [EID, K, V])).

query_all :-  
 format("query_all~n"),
 forall(entity(EID), format("entity(~w)~n", [EID])),
 forall(component(EID, K, V), format("component(~w,~w,~w)~n", [EID, K, V])).

game_update(FrameID) :-   
 system_A_update(FrameID),
 retractall(iter_entity(_)),
 forall(entity(EID), assertz(iter_entity(EID))).

system_A_update(FrameID) :-
 forall(iter_entity(EID), ignore((entity(EID), system_A_update_entity(FrameID, EID)))).

system_A_update_entity(FrameID, EID) :- 
 format("system_A_update_entity FrameID=~w EID=~w~n", [FrameID, EID]),           
 query_all,
 %% In frame 1, when entity(2) update, remove entity(3), 
 %% then the entity(3) will become invisible immediately!
 %% For example, unit_2 kill unit_3.
 (   FrameID=1, EID=2
 ->  retract(entity(3)),
     retractall(component(3, _)),
     query_all     
 ;   true
 ).

The entity/1 represents entities, the component/3 represents components, system_A_update represents a system, the game_update will be called in the game main loop.

The design is that

  1. When adding an entity during a frame, the entity can be only queried and modified in the current frame, but it cannot be called in system_A_update_entity/2. It will be called in the next frame.
  2. When removing an entity during a frame, the entity will become invisible immediately.

It seems reasonable.

Do you have any suggestions? Thanks.

Some tests:

?- game_update(1).
?- game_update(1).
system_A_update_entity FrameID=1 EID=1
query_all
entity(1)
entity(2)
entity(3)
entity(4)
component(1,position,vec(1,1))
component(1,health,100)
component(1,speed,5)
component(1,bounds,1)
component(2,position,vec(2,2))
component(2,health,200)
component(2,speed,10)
component(2,bounds,2)
component(3,position,vec(3,3))
component(3,health,300)
component(3,speed,15)
component(3,bounds,3)
component(4,position,vec(4,4))
component(4,health,400)
component(4,speed,20)
component(4,bounds,4)
system_A_update_entity FrameID=1 EID=2
query_all
entity(1)
entity(2)
entity(3)
entity(4)
component(1,position,vec(1,1))
component(1,health,100)
component(1,speed,5)
component(1,bounds,1)
component(2,position,vec(2,2))
component(2,health,200)
component(2,speed,10)
component(2,bounds,2)
component(3,position,vec(3,3))
component(3,health,300)
component(3,speed,15)
component(3,bounds,3)
component(4,position,vec(4,4))
component(4,health,400)
component(4,speed,20)
component(4,bounds,4)
query_all
entity(1)
entity(2)
entity(4)
component(1,position,vec(1,1))
component(1,health,100)
component(1,speed,5)
component(1,bounds,1)
component(2,position,vec(2,2))
component(2,health,200)
component(2,speed,10)
component(2,bounds,2)
component(3,position,vec(3,3))
component(3,health,300)
component(3,speed,15)
component(3,bounds,3)
component(4,position,vec(4,4))
component(4,health,400)
component(4,speed,20)
component(4,bounds,4)
system_A_update_entity FrameID=1 EID=4
query_all
entity(1)
entity(2)
entity(4)
component(1,position,vec(1,1))
component(1,health,100)
component(1,speed,5)
component(1,bounds,1)
component(2,position,vec(2,2))
component(2,health,200)
component(2,speed,10)
component(2,bounds,2)
component(3,position,vec(3,3))
component(3,health,300)
component(3,speed,15)
component(3,bounds,3)
component(4,position,vec(4,4))
component(4,health,400)
component(4,speed,20)
component(4,bounds,4)
true.
?- game_update(1), game_update(2),.
?- game_update(1), game_update(2).
system_A_update_entity FrameID=1 EID=1
query_all
entity(1)
entity(2)
entity(3)
entity(4)
component(1,position,vec(1,1))
component(1,health,100)
component(1,speed,5)
component(1,bounds,1)
component(2,position,vec(2,2))
component(2,health,200)
component(2,speed,10)
component(2,bounds,2)
component(3,position,vec(3,3))
component(3,health,300)
component(3,speed,15)
component(3,bounds,3)
component(4,position,vec(4,4))
component(4,health,400)
component(4,speed,20)
component(4,bounds,4)
system_A_update_entity FrameID=1 EID=2
query_all
entity(1)
entity(2)
entity(3)
entity(4)
component(1,position,vec(1,1))
component(1,health,100)
component(1,speed,5)
component(1,bounds,1)
component(2,position,vec(2,2))
component(2,health,200)
component(2,speed,10)
component(2,bounds,2)
component(3,position,vec(3,3))
component(3,health,300)
component(3,speed,15)
component(3,bounds,3)
component(4,position,vec(4,4))
component(4,health,400)
component(4,speed,20)
component(4,bounds,4)
query_all
entity(1)
entity(2)
entity(4)
component(1,position,vec(1,1))
component(1,health,100)
component(1,speed,5)
component(1,bounds,1)
component(2,position,vec(2,2))
component(2,health,200)
component(2,speed,10)
component(2,bounds,2)
component(3,position,vec(3,3))
component(3,health,300)
component(3,speed,15)
component(3,bounds,3)
component(4,position,vec(4,4))
component(4,health,400)
component(4,speed,20)
component(4,bounds,4)
system_A_update_entity FrameID=1 EID=4
query_all
entity(1)
entity(2)
entity(4)
component(1,position,vec(1,1))
component(1,health,100)
component(1,speed,5)
component(1,bounds,1)
component(2,position,vec(2,2))
component(2,health,200)
component(2,speed,10)
component(2,bounds,2)
component(3,position,vec(3,3))
component(3,health,300)
component(3,speed,15)
component(3,bounds,3)
component(4,position,vec(4,4))
component(4,health,400)
component(4,speed,20)
component(4,bounds,4)
system_A_update_entity FrameID=2 EID=1
query_all
entity(1)
entity(2)
entity(4)
component(1,position,vec(1,1))
component(1,health,100)
component(1,speed,5)
component(1,bounds,1)
component(2,position,vec(2,2))
component(2,health,200)
component(2,speed,10)
component(2,bounds,2)
component(3,position,vec(3,3))
component(3,health,300)
component(3,speed,15)
component(3,bounds,3)
component(4,position,vec(4,4))
component(4,health,400)
component(4,speed,20)
component(4,bounds,4)
system_A_update_entity FrameID=2 EID=2
query_all
entity(1)
entity(2)
entity(4)
component(1,position,vec(1,1))
component(1,health,100)
component(1,speed,5)
component(1,bounds,1)
component(2,position,vec(2,2))
component(2,health,200)
component(2,speed,10)
component(2,bounds,2)
component(3,position,vec(3,3))
component(3,health,300)
component(3,speed,15)
component(3,bounds,3)
component(4,position,vec(4,4))
component(4,health,400)
component(4,speed,20)
component(4,bounds,4)
system_A_update_entity FrameID=2 EID=4
query_all
entity(1)
entity(2)
entity(4)
component(1,position,vec(1,1))
component(1,health,100)
component(1,speed,5)
component(1,bounds,1)
component(2,position,vec(2,2))
component(2,health,200)
component(2,speed,10)
component(2,bounds,2)
component(3,position,vec(3,3))
component(3,health,300)
component(3,speed,15)
component(3,bounds,3)
component(4,position,vec(4,4))
component(4,health,400)
component(4,speed,20)
component(4,bounds,4)
true.

Glad you think so :slight_smile: Update semantics can be tricky. Making clauses visible on a backtracking predicate would require any call to a dynamic predicate to succeed with a choice point as it is always possible the user adds a clause. SWI-Prolog does allow you to check whether a predicate was modified using predicate_property/2 with the last_modified_generation property. You can also use prolog_listen/2 to listen to changes of a predicate and act on it immediately.

This seems reasonable, although I’d write it as below. The ignore/1 will cause calls to system_A_update_entity/2 to fail without the whole thing failing.

   forall(iter_entity(EID),
          (   iter_entity(EID)
          ->  system_A_update_entity(FrameID, EID)
          ;   true
          ))

Note that in recent versions you can declare a predicate should succeed exactly once using det/1. That makes debugging a lot quicker :slight_smile:

1 Like

Thanks :heart:.

I read the paper “A framework to specify database update views for Prolog” by Egon Boerger and Bart Demoen today. In that paper the authors introduced 4 different update views (i.e. minimal, logical, immemediate and maximal) and argued that the most attractive view is the minimal view, which is exactly the entity update semantics used in my game. That is interesting!

BTW, the authors also mentioned the following papers:

  1. Efficient Implementation of a Defensible Semantics for Dynamic PROLOG Code by T. Lindholm, R.A. O’Keefe.

  2. Cut and Paste - Defining the impure primitives of Prolog by C. Moss.

  3. An analysis of Prolog database views and their uniform implementation by E. Boerger, D. Rosenzweig.

but I cannot find them on the Internet.

Do you (or anyone) have these papers?

I’d like to take a look at them.

Thanks.