/** DLP multi-threaded (bounded) buffer + producer + consumer example featuring (1) active, i.e. multi-threaded objects, (2) communication by rendez-vous, and (3) non-logical variables.
Execution starts at method main in object pxbuff. Method main redirects the output of this program to a browser text area and creates three active objects: active_buffer , term_consumer , and term_producer . The invocations of new/1 in main result in the creation of three independently running objects. These objects will start their execution at the specified object constructors in the corresponding new/1 goals :

:-object pxbuff.

	main :-

		AB := new(active_buffer(10)),
		_C := new(term_consumer(5,AB)),
		_P := new(term_producer(5,AB)).

:-end_object pxbuff.

When an active instance of a term_producer object is created, execution starts at the term_producer object constructor. The constructor prints a message and invokes method loop/2. The loop method executes N times and sends a Prolog term (in this example an integer) to the active_buffer thread by means of the goal "B<-put_term(I)" :

:-object term_producer.

	term_producer(N,B) :-
		format('P ~w term_producer main/2 running~n', [this]),

	loop(0,_) :-
		format('P ~w end of loop ...~n', [this]).
	loop(I,B) :-
		format('P ~w sending ~w~n', [this, I]),
		B <- put_term(I),
		N is I-1,

:-end_object term_producer.

The creation of an active term_consumer object in main/0 of object pxbuff results in the execution of the term_consumer/2 constructor. The constructor outputs a message and starts loop/2. The loop/2 method executes N times and retrieves its data from the active buffer thread by means of the "B<-get_term(T)" goal :

:-object term_consumer.

	term_consumer(N,B) :-
		format('C ~w term_consumer main/2 running~n', [this]),

	loop(0,_) :-
		format('C ~w end of loop ...~n', [this]).
	loop(I,B) :-
		B <- get_term(T),
		format('C ~w receiving ~w~n', [this, T]),
		N is I-1,

:-end_object term_consumer.

Object active_buffer contains four non-logical variables: head, tail, size, and count. As opposed to logical (Prolog like) variables, non-logical variables can be updated destructively. Execution starts at the active_buffer/1 object constructor. The constructor outputs a message and invokes loop/1. Method loop/1 accepts either a put_term/1 or get_term/1 method invocation request of an independently running term_producer or term_consumer object, depending on the internal state of this active_buffer object as specified by the guards: in case the current number of data items in the buffer is less than size or greater than zero, the first matching method request in the accept queue will be accepted for execution by active_buffer. If only one guard holds, the corresponding method entry will be accepted. In case there is no matching method request the thread will be blocked until such a method message arrives. A term_producer or term_consumer thread will block until active_buffer accepts a particular method invocation request and has returned its answer. When either a get_term/1 or put_term/1 method is accepted, the corresponding method in active_buffer will be executed : Method get_term/1 retrieves the first entry in the linked list and returns the corresponding term after the non-logical variable head of the linked list has been updated. Method put_term/1 will store the term in the linked list and updates the non-logical variable tail or both the head and the tail of the list. After a get_term/1 or put_term/1 method "rendez-vous", loop/1 in active_buffer prints the current state (see out_list/1) and starts the next loop iteration.

:-object active_buffer.

	var head=null, tail=null, size=3, count=0.

	active_buffer(N) :-
		format('B ~w active_buffer main/1 running~n', [this]),

	loop(0) :-
		format('B ~w end of loop ...~n', [this]).
	loop(I) :-
			put_term(_) <== [count < size],
			get_term(_) <== [count > 0]
		N is I-1,

	get_term(Term) :-
		-- count,
		head <- get_node_term(Term),
		head <- get_node_next(Next),
		head := Next,
		format('B ~w get term ~w~n', [this, Term]).

	put_term(Term) :-
		Node := new(buffer_node),
		Node <- set_node_term(Term),
		add_node(count, Node),
		format('B ~w put term ~w~n', [this, Term]),
		++ count.

	add_node(0, Node) :-
		head := Node,
		tail := Node.
	add_node(_, Node) :-
		tail <- set_node_next(Node),
		tail := Node.

	out_list(Node) :-
		format('B~tcurrent nodes:~n'),
		out_list(0, Node).

	out_list(Curr, _) :-
		Curr = count, !,
		format('B~tend node list.~n').
	out_list(I, Node) :-
		N is I + 1,
		Node <- get_node_term(Term),
		Node <- get_node_next(Link),
		format('B~tnode no. ~w, term = ~w~n', [N, Term]),
		out_list(N, Link).

:-end_object active_buffer.

Object buffer_node is a passive object (no constructor involved). It has two non-logical variables: term and next. These variables are destructively updated by the set_node_term/1 and set_node_next/1 methods, respectively. Object buffer_node is used by
active_buffer to construct a linked list of buffered terms :

:-object buffer_node.

	var term, next=null.

	set_node_term(Term) :-
		term := Term.
	set_node_next(Next) :-
		next := Next.

	get_node_term(Term) :-
		Term := term.
	get_node_next(Next) :-
		Next := next.

:-end_object buffer_node.