:- module(notify_ports).
:- export foreachnotification_ / 8.
:- export foreachnotification_ / 7.
:- export foreachnotification / 7.
:- export receive_notifications / 4.
:- export open_receiver_init / 6.
:- export open_receiver / 4.
:- export send_notification / 3.
:- export close_sender / 2.
:- export open_sender / 2.
:- export foreachnotification / 6.
:- export receive_notifications / 3.
:- export open_receiver_init / 4.
:- export open_tagged_receiver / 3.
:- export open_receiver / 2.
:- export send_notification / 2.
:- export close_sender / 1.
:- export open_tagging_sender / 1.
:- export open_sender / 1.
:- tool(foreachnotification / 7,foreachnotification_ / 8).
:- tool(foreachnotification / 6,foreachnotification_ / 7).
:- comment(foreachnotification_ / 8, hidden).
:- comment(foreachnotification_ / 7, hidden).
:- comment(summary, "One-to-many and many-to-many notification ports").
:- comment(author, "Joachim Schimpf").
:- comment(date, "$Date: 2007/05/04 12:16:13 $").
:- comment(copyright, "Cisco Systems, Inc").
:- comment(desc, html("<P>\n    This library implements a nonlogical feature, called notification\n    ports.  They are a form of messaging, i.e. there is are send ports\n    and attached receive ports, and messages in the form of general terms\n    can be passed through these ports.  Both send and receive ports have\n    unique handles, which is the nonlogical bit.\n    </P><P>\n    There are two variants of this feature, one-to-many and many-to-many\n    ports.\n    </P>\n\n    <h3>One-To-Many</h3>\n    <P>\n    In the one-to-many variant, messages sent from a single send port\n    can be received independently by several receivers. In this setting,\n    the message stream is essentially an infinite list, with the sender\n    extending the list at the tail and the receivers each individually\n    progressing through the list.\n    </P><P>\n    Straightforward interface:\n    <PRE>\n\topen_sender(-Sender)\n\tclose_sender(+Sender)\n\tsend_notification(+Sender, +Message)\n\topen_receiver(+Sender, -Receiver)\n\topen_receiver_init(+Sender, +InitMsgs, -InitMsgsTail, -Receiver)\n\treceive_notifications(+Receiver, -Messages, -Status)\n\tforeachnotification(+BaseName, -Message, +Params, +Receiver, -Status, +Goals)\n    </PRE>\n    There is also a slightly more memory efficient API where sender and\n    receiver can be fields of larger structures rather than separate\n    substructures. These  larger structures must always be created\n    by the caller (in the case of the sender this is often an attribute\n    structure, in the case of the receiver it is sometimes advantageous\n    to package a suspension together with the receiver in order to kill\n    it at the end of all messages):\n    <PRE>\n\topen_sender(+SendPos, +SendStruct)\n\tclose_sender(+SendPos, +SendStruct)\n\tsend_notification(+SendPos, +SendStruct, +Message)\n\topen_receiver(+SendPos, +SendStruct, +ReceivePos, +ReceiveStruct)\n\topen_receiver_init(+SendPos, +SendStruct, +InitMsgs, -InitMsgsTail,\n\t\t\t\t+ReceivePos, +ReceiveStruct)\n\treceive_notifications(+ReceivePos, +ReceiveStruct, -Messages, -Status)\n\tforeachnotification(+BaseName, -Message, +Params, +ReceivePos, +ReceiveStruct, -Status, +Goals)\n    </PRE>\n\n    <h3>Many-To-Many</h3>\n    <P>\n    In the many-to-many variant, several send ports can be connected\n    to several receive ports in an arbitray manner.\n    To enable a receiver to distinguish messages from multiple senders,\n    the messages get tagged with a sender- and receiver-specific id as\n    they are received.\n    </P>\n    The corresponding predicates are the following. Note that sender and\n    receiver are opened with different predicates, but the send and receive\n    predicates are the same as for one-to-many ports:\n    <PRE>\n\topen_tagging_sender(-Sender)\n\topen_tagged_receiver(+Tag, +Sender, -Receiver)\n\n\tsend_notification(+Sender, +Message)\n\treceive_notifications(+Receiver, -Messages, -Status)\n\tforeachnotification(+BaseName, -Message, +Params, +Receiver, -Status, +Goals)\n    </PRE>\n    Note that closing of tagging senders is currently not implemented.\n    ")).
:- comment(eg, "\n    % One-to-many\n\n    % This example shows a typical use of notification ports.\n    % A notification port is used in addition to a waking list\n    % in order to transfer precise information about the reason for waking.\n\n    % We define a variable attribute (myattr) consisting of a send port\n    % and a waking list. \n\n\n    :- lib(notify_ports).\n\n    :- meta_attribute(myattr, []).\n    :- local struct(myattr(port,susplist)).\n    :- local struct(myrec(port,susp)).\n\n\n    test :-\n\t    init_var(X),\n\t    log_all_messages(X),\n\t    touch_var(X, hello),\n\t    touch_var(X, out),\n\t    touch_var(X, there),\n\t    fini_var(X).\n\n\n    % initialise and attach our attribute to the given variable\n    init_var(X) :-\n\t    Attr = myattr{},\n\t    open_sender(port of myattr, Attr),\n\t    init_suspension_list(susplist of myattr, Attr),\n\t    add_attribute(X, Attr, myattr).\n\n\n    % simulate an action on the variable: send a message and wake\n    touch_var(_X{myattr:Attr}, Message) ?-\n\t    send_notification(port of myattr, Attr, Message),\n\t    schedule_suspensions(susplist of myattr, Attr),\n\t    wake.\n\n    % finalise the attribute, e.g. before the variable gets instantiated\n    fini_var(_X{myattr:Attr}) ?-\n            close_sender(port of myattr, Attr),\n\t    schedule_suspensions(susplist of myattr, Attr),\n\t    wake.\n\n    % a sample demon that will report every time the variable is touched\n    log_all_messages(X{myattr:Attr}) ?-\n\t    Receiver = myrec{susp:Susp},\n\t    open_receiver(port of myattr, Attr, port of myrec, Receiver),\n\t    suspend(log_demon(Receiver), 2, X->myattr:(susplist of myattr), Susp).\n\n    :- demon log_demon/1.\n    log_demon(Receiver) :-\n\t    foreachnotification(log, Message, [], port of myrec, Receiver, Status, (\n\t\twriteln(received(Message))\n\t    )),\n\t    ( Status = closed ->\n\t\targ(susp of myrec, Receiver, Susp),\n\t\tkill_suspension(Susp),\n\t\twriteln(closed)\n\t    ;\n\t\ttrue\n\t    ).\n    \n\n    ( For a many-to-many example, see open_tagged_receiver/3 )\n    ").
:- comment(close_sender / 1, [summary : "Close a send port", amode : close_sender(+), args : ["SendPort" : "a send port structure"]]).
:- comment(close_sender / 2, [summary : "Close a send port on a structure field", amode : close_sender(+, +), args : ["Pos" : "positive integer, the send port's field number in Struct", "Struct" : "a structure whose Pos-th field is a send port"]]).
:- comment(foreachnotification / 6, [summary : "A control construct to iterate over received notifications", amode : foreachnotification(+, -, +, +, -, +), args : ["BaseName" : "an atom used as the basename for the generated auxiliary predicate", "Message" : "a variable", "Params" : "a list of global variables in the iteration body (as in do/2)", "ReceivePort" : "a receiver structure as created by open_receiver", "Status" : "a variable, will be bound to 'open' or 'closed'", "Goals" : "the goals that will be called for each iteration"], desc : html("\n    This is a control construct iterating over the currently available\n    messages on the given receive port. The purpose is to process the\n    received messages one by one without the need to create an auxiliary\n    list of received messages. The iteration terminates when there are\n    (currently) no more message on the receive port.\n    <P>\n    When the iteration terminates, the Status argument indicates whether\n    the associated sender is still open ('open') or has been closed\n    ('closed'). If closed, no more messages will arrive on this receive\n    port in the future.\n    <P>\n\n    "), see_also : [receive_notifications / 3], eg : "\n\tprocess_all_messages(ReceivePort, Log) :-\n\t    foreachnotification(sample, Message, [Log], ReceivePort, Status, (\n\t\twriteln(Log, received(Message)),\n\t\tdo_something(Message)\n\t    )),\n\t    ( Status = closed ->\n\t\twriteln(Log, end_of_messages)\n\t    ;\n\t\twriteln(Log, more_coming)\n\t    ).\n    "]).
:- comment(foreachnotification / 7, [summary : "A control construct to iterate over received notifications", amode : foreachnotification(+, -, +, +, +, -, +), args : ["BaseName" : "an atom used as the basename for the generated auxiliary predicate", "Message" : "a variable", "Params" : "a list of global variables in the iteration body (as in do/2)", "ReceivePos" : "positive integer, the send port's field number in ReceiveStruct", "ReceiveStruct" : "a structure", "Status" : "a variable, will be bound to 'open' or 'closed'", "Goals" : "the goals that will be called for each iteration"], desc : html("\n    This is a control construct iterating over the currently available\n    messages on the given receive port. The purpose is to process the\n    received messages one by one without the need to create an auxiliary\n    list of received messages. The iteration terminates when there are\n    (currently) no more message on the receive port.\n    <P>\n    When the iteration terminates, the Status argument indicates whether\n    the associated sender is still open ('open') or has been closed\n    ('closed'). If closed, no more messages will arrive on this receive\n    port in the future.\n    <P>\n\n    "), see_also : [receive_notifications / 4], eg : "see general example for the library"]).
:- comment(open_receiver / 2, [summary : "Create a receiver for a given notification sender", amode : open_receiver(+, -), args : ["SendPort" : "a send port structure", "ReceivePort" : "a variable, will be bound to a structure"], desc : html("\n    This predicate creates a receive port listening to messages sent\n    via the specified send port.  The new receive port will receive\n    all messages that are sent via the send port after the receiver\n    has been opened.  Messages that were sent before the receiver has\n    been opened will not be received by this receiver.\n    "), see_also : [open_receiver_init / 4]]).
:- comment(open_receiver / 4, [summary : "Create a receiver for a given notification sender", amode : open_receiver(+, +, +, +), args : ["SendPos" : "positive integer, the send port's field number in SendStruct", "SendStruct" : "a structure whose SendPos-th field is a send port", "ReceivePos" : "positive integer, the send port's field number in ReceiveStruct", "ReceiveStruct" : "a structure with free field ReceivePos"], desc : html("\n    SendPos and SendStruct identify a send port.  This predicate\n    creates a receive port listening to messages sent via the\n    specified send port.  The new receive port will receive all\n    messages that are sent via the send port after the receiver has\n    been opened.  Messages that were sent before the receiver has been\n    opened will not be received by this receiver.\n    <P>\n    The receiver will be installed in field ReceivePos of the structure\n    ReceiveStruct.\n    "), see_also : [open_receiver_init / 6]]).
:- comment(open_receiver_init / 4, [summary : "Create a receiver for a given notification sender", amode : open_receiver_init(+, +, -, -), args : ["SendPort" : "a send port structure", "InitialMessages" : "the head of a list of initial messages", "InitialMessagesTail" : "the tail of the list of initial messages", "ReceivePort" : "a variable, will be bound to a structure"], desc : html("\n    This predicate creates a receive port listening to messages sent\n    via the specified send port.  The new receive port will receive\n    all messages that are sent via the send port after the receiver\n    has been opened.  Messages that were sent before the receiver has\n    been opened will not be received by this receiver.\n    <P>\n    In addition to open_receiver/2, there is a difference list pair\n    (InitialMessages and InitialMessagesTail) which can be used to\n    fake a sequence of initial message that will be received on\n    the receive port without actually having been sent from the\n    associated send port. This feature can be used to bring the\n    message receiving agent into a particular starting state.\n    "), see_also : [open_receiver / 2]]).
:- comment(open_receiver_init / 6, [summary : "Create a receiver for a given notification sender", amode : open_receiver_init(+, +, +, -, +, +), args : ["SendPos" : "positive integer, the send port's field number in SendStruct", "SendStruct" : "a structure whose SendPos-th field is a send port", "InitialMessages" : "the head of a list of initial messages", "InitialMessagesTail" : "the tail of the list of initial messages", "ReceivePos" : "positive integer, the send port's field number in ReceiveStruct", "ReceiveStruct" : "a structure with free field ReceivePos"], desc : html("\n    SendPos and SendStruct identify a send port.  This predicate\n    creates a receive port listening to messages sent via the\n    specified send port.  The new receive port will receive all\n    messages that are sent via the send port after the receiver has\n    been opened.  Messages that were sent before the receiver has been\n    opened will not be received by this receiver.\n    <P>\n    The receiver will be installed in field ReceivePos of the structure\n    ReceiveStruct.\n    <P>\n    In addition to open_receiver/4, there is a difference list pair\n    (InitialMessages and InitialMessagesTail) which can be used to\n    fake a sequence of initial message that will be received on\n    the receive port without actually having been sent from the\n    associated send port. This feature can be used to bring the\n    message receiving agent into a particular starting state.\n    "), see_also : [open_receiver / 4]]).
:- comment(open_sender / 1, [summary : "Create a send port", amode : open_sender(-), args : ["SendPort" : "a variable, will be bound to a structure"]]).
:- comment(open_sender / 2, [summary : "Initialise a structure field as a send port", amode : open_sender(+, +), args : ["Pos" : "positive integer, the send port's field number in Struct", "Struct" : "a structure (with arity Pos or more)"]]).
:- comment(open_tagged_receiver / 3, [summary : "Create a receiver for one or more tagging senders", amode : open_tagged_receiver(+, +, -), amode : open_tagged_receiver(+, +, +), args : ["Tag" : "an arbitrary term", "SendPort" : "a tagged-send-port structure", "ReceivePort" : "a tagged-receive-port structure or a variable"], desc : html("\n    This predicate either creates a new receive port and connects it to an\n    existing tagged-send port, or connects an existing receive port to an\n    additional existing tagged-send port.  The new receive port will receive\n    all messages that are sent via the send port after the receiver\n    has been opened.  Any messages that were sent before the receiver was\n    opened will not be received by this receiver.\n    </P><P>\n    Messages that arrive on ReceivePort from SendPort will get tagged with\n    Tag, i.e. the received message will be a structure of the form\n    <PRE>\n    \tTag : Message\n    </PRE>\n    If several senders are connected to ReceivePort, the tag can thus\n    be used to identify the origin of the message.\n    </P>\n    "), eg : "\n    ?-\topen_tagging_sender(S1),\n\topen_tagging_sender(S2),\n\topen_tagged_receiver(r1s1, S1, R1),\n\topen_tagged_receiver(r1s2, S2, R1),\n\topen_tagged_receiver(r2s1, S1, R2),\n\topen_tagged_receiver(r2s2, S2, R2),\n\n\tsend_notification(S1, m1),\n\tsend_notification(S1, m2),\n\tsend_notification(S2, m3),\n\tsend_notification(S1, m4),\n\tsend_notification(S2, m5),\n\n\treceive_notifications(R1, R1M1, _),\n\treceive_notifications(R2, R2M1, _).\n\n    ...\n    R1M1 = [r1s1 : m1, r1s1 : m2, r1s2 : m3, r1s1 : m4, r1s2 : m5]\n    R2M1 = [r2s1 : m1, r2s1 : m2, r2s2 : m3, r2s1 : m4, r2s2 : m5]\n    Yes (0.00s cpu)\n    ", see_also : [open_tagging_sender / 1, send_notification / 2, receive_notifications / 3, foreachnotification / 6]]).
:- comment(open_tagging_sender / 1, [summary : "Create a many-to-many send port ", amode : open_tagging_sender(-), args : ["SendPort" : "a variable, will be bound to a structure"], desc : html("\n    Creates a send port that can be used in many-to-many communication,\n    i.e. several of these ports can be connected to a single receiver.\n    "), see_also : [open_tagged_receiver / 3, send_notification / 2, receive_notifications / 3, foreachnotification / 6]]).
:- comment(receive_notifications / 3, [summary : "Receive a list of currently available notification messages", amode : receive_notifications(+, -, -), args : ["ReceivePort" : "a receiver structure as created by open_receiver", "Messages" : "a variable, will be bound to a list", "Status" : "a variable, will be bound to 'open' or 'closed'"], desc : html("\n    This predicate retrieves all the messages that are currently\n    available at the given receive port.  This means all messages that\n    were sent via the associated send port but have not yet been\n    retrieved from this receive port. The messages are listed in the\n    order in which they were sent.\n    <P>\n    The Status argument indicates whether the associated sender is still\n    open ('open') or has been closed ('closed'). If closed, no more\n    messages will arrive on this receive port in the future.\n    "), see_also : [foreachnotification / 6]]).
:- comment(receive_notifications / 4, [summary : "Receive a list of currently available notification messages", amode : receive_notifications(+, +, -, -), args : ["ReceivePos" : "positive integer, the send port's field number in ReceiveStruct", "ReceiveStruct" : "a structure", "Messages" : "a variable", "Status" : "a variable"], desc : html("\n    This predicate retrieves all the messages that are currently\n    available at the given receive port.  This means all messages that\n    were sent via the associated send port but have not yet been\n    retrieved from this receive port.\n    <P>\n    The Status argument indicates whether the associated sender is still\n    open ('open') or has been closed ('closed'). If closed, no more\n    messages will arrive on this receive port in the future.\n    "), see_also : [foreachnotification / 7]]).
:- comment(send_notification / 2, [summary : "Send a notification message", amode : send_notification(+, +), args : ["SendPort" : "a send port structure", "Message" : "arbitrary term"], fail_if : "Fails if SendPort is closed", desc : html("\n    Messages is an arbitrary term that gets send via the send port SendPort.\n    All receivers that have connected to this send port at the time of\n    sending will be able to receive the message.\n    ")]).
:- comment(send_notification / 3, [summary : "Send a notification message", amode : send_notification(+, +, +), args : ["Pos" : "positive integer, the send port's field number in Struct", "Struct" : "a structure whose Pos-th field is a send port", "Message" : "arbitrary term"], fail_if : "Fails if send port is closed", desc : html("\n    Pos and Struct identify a send port. Messages is an arbitrary\n    term that gets send via this port. All receivers that have connected\n    to this send port at the time of sending will be able to receive\n    the message.\n    ")]).
