Did you know ... | Search Documentation: |
Pack logtalk -- logtalk-3.86.0/manuals/_sources/userman/events.rst.txt |
.. This file is part of Logtalk https://logtalk.org/ SPDX-FileCopyrightText: 1998-2024 Paulo Moura <pmoura@logtalk.org> SPDX-License-Identifier: Apache-2.0
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
.. _events_events:
The addition of event-driven programming capacities to the Logtalk language [Moura94]_ is based on a simple but powerful idea:
The computations must result not only from message-sending but also from the observation of message-sending.
The need to associate computations to the occurrence of events was very early recognized in knowledge representation languages, programming languages [Stefik_et_al_86]_, [Moon86]_, operative systems [Tanenbaum87]_, and graphical user interfaces.
With the integration between object-oriented and event-driven programming, we intend to achieve the following goals:
.. _events_definitions:
The words event and monitor have multiple meanings in computer science. To avoid misunderstandings, we start by defining them in the Logtalk context.
.. _events_event:
Event ~~~~~
In an object-oriented system, all computations start through message
sending. It thus becomes quite natural to declare that the only event
that can occur in this kind of system is precisely the sending of a
message. An event can thus be represented by the ordered tuple
(Object, Message, Sender)
.
If we consider message processing an indivisible activity, we can
interpret the sending of a message and the return of the control to the
object that has sent the message as two distinct events. This
distinction allows us to have more precise control over a system's
dynamic behavior. In Logtalk, these two types of events have been named
before
and after
, respectively for sending a message and for
returning control to the sender. Therefore, we refine our event
representation using the ordered tuple (Event, Object, Message, Sender)
.
The implementation of events in Logtalk enjoys the following properties:
Independence between the two types of events We can choose to watch only one event type or to process each one of the events associated with a message-sending goal in an independent way.
All events are automatically generated by the message-sending mechanism The task of generating events is transparently accomplished by the message-sending mechanism. The user only needs to define the events that will be monitored.
The events watched at any moment can be dynamically changed during program execution The notion of event allows the user not only to have the possibility of observing but also of controlling and modifying an application behavior, namely by dynamically changing the observed events during program execution. It is our goal to provide the user with the possibility of modeling the largest number of situations.
.. _events_monitor:
Monitor ~~~~~~~
Complementary to the notion of event is the notion of monitor. A monitor is an object that is automatically notified by the message-sending mechanism whenever a registered event occurs. Any object that defines the event-handling predicates can play the role of a monitor.
The implementation of monitors in Logtalk enjoys the following properties:
Any object can act as a monitor The monitor status is a role that any object can perform during its existence. The minimum protocol necessary is declared in the built-in :ref:`monitoring apis:monitoring/0` protocol. Strictly speaking, the reference to this protocol is only needed when specializing event handlers. Nevertheless, it is considered good programming practice to always refer to the protocol when defining event handlers.
Unlimited number of monitors for each event Several monitors can observe the same event for distinct reasons. Therefore, the number of monitors per event is bounded only by the available computing resources.
The monitor status of an object can be dynamically changed at runtime This property does not imply that an object must be dynamic to act as a monitor (the monitor status of an object is not stored in the object).
Event handlers cannot modify the event arguments Notably, if the message contains unbound variables, these cannot be bound by the calls to the monitor event handlers.
.. _events_generation:
Assuming that the :ref:`events <flag_events>` flag is set to allow
for
the object (or category) sending the messages we want to observe, for each
message that is sent using the :ref:control_send_to_object_2
control
construct, the runtime system automatically generates two events.
The first â before event â is generated when the message is sent. The
second â after event â is generated after the message has successfully
been executed.
Note that self messages (using the :ref:control_send_to_self_1
control
construct) and super calls (using the :ref:control_call_super_1
control
construct) don't generate events.
.. _events_communicating:
Whenever a spied event occurs, the message-sending mechanism calls the corresponding event handlers directly for all registered monitors. These calls are internally made, thus bypassing the message-sending primitives in order to avoid potential endless loops. The event handlers consist of user definitions for the public predicates declared in the built-in :ref:`monitoring apis:monitoring/0` protocol (see below for more details).
.. _events_performance:
Ideally, the existence of monitored messages should not affect the processing of the remaining messages. On the other hand, for each message that has been sent, the system must verify if its respective event is monitored. Whenever possible, this verification should be performed in constant time and independently of the number of monitored events. The representation of events takes advantage of the first argument indexing performed by most Prolog compilers, which ensure â in the general case â access in constant time.
Event support can be turned off on a per-object (or per-category) basis using the :ref:`events <flag_events>` compiler flag. With event support turned off, Logtalk uses optimized code for processing message-sending calls that skips the checking of monitored events, resulting in a small but measurable performance improvement.
.. _events_semantics:
The established semantics for monitor actions consists of considering its success as a necessary condition so that a message can succeed:
before
must succeed
so that the message processing can start.after
also have to
succeed so that the message itself succeeds. The failure of any
action associated with an event of type after
forces backtracking
over the message execution (the failure of a monitor never causes
backtracking over the preceding monitor actions).
Note that this is the most general choice. If we require a transparent presence of monitors in a message processing, we just have to define the monitor actions in such a way that they never fail (which is very simple to accomplish).
.. _events_order:
Ideally, whenever there are several monitors defined for the same event,
the calling order should not interfere with the result. However, this is
not always possible. In the case of an event of type before
, the
failure of a monitor prevents a message from being sent and prevents the
execution of the remaining monitors. In the case of an event of type
after
, a monitor failure will force backtracking over message
execution. Different orders of monitor activation can therefore lead to
different results if the monitor actions imply object modifications
unrecoverable in case of backtracking. Therefore, the order for monitor
activation should be assumed as arbitrary. In effect, to assume or to
try to impose a specific sequence requires a global knowledge of an
application dynamics, which is not always possible. Furthermore, that
knowledge can reveal itself as incorrect if there is any change in the
execution conditions. Note that, given the independence between
monitors, it does not make sense that a failure forces backtracking over
the actions previously executed.
.. _events_handling:
Logtalk provides three built-in predicates for event handling. These predicates support defining, enumerating, and abolishing events. Applications that use events extensively usually define a set of objects that use these built-in predicates to implement more sophisticated and higher-level behavior.
.. _events_defining:
Defining new events ~~~~~~~~~~~~~~~~~~~
New events can be defined using the :ref:predicates_define_events_5
built-in predicate:
.. code-block:: text
| ?- define_events(Event, Object, Message, Sender, Monitor)
.
Note that if any of the Event
, Object
, Message
, and
Sender
arguments is a free variable or contains free variables, this
call will define a set of matching events.
.. _events_abolishing:
Abolishing defined events ~~~~~~~~~~~~~~~~~~~~~~~~~
Events that are no longer needed may be abolished using the
:ref:predicates_abolish_events_5
built-in predicate:
.. code-block:: text
| ?- abolish_events(Event, Object, Message, Sender, Monitor)
.
If called with free variables, this goal will remove all matching events.
.. _events_finding:
Finding defined events ~~~~~~~~~~~~~~~~~~~~~~
The events that are currently defined can be retrieved using the
:ref:predicates_current_event_5
built-in predicate:
.. code-block:: text
| ?- current_event(Event, Object, Message, Sender, Monitor)
.
Note that this predicate will return sets of matching events if some of the returned arguments are free variables or contain free variables.
.. _events_handlers:
Defining event handlers ~~~~~~~~~~~~~~~~~~~~~~~
The :ref:`monitoring apis:monitoring/0` built-in protocol declares two
public predicates, :ref:methods_before_3
and :ref:methods_after_3
, that
are automatically called to handle before
and after
events. Any
object that plays the role of monitor must define one or both of these
event handler methods:
::
before(Object, Message, Sender)
:-
... .
after(Object, Message, Sender)
:-
... .
The arguments in both methods are instantiated by the message-sending
mechanism when a monitored event occurs. For example, assume that we
want to define a monitor called tracer
that will track any message
sent to an object by printing a descriptive text to the standard output.
Its definition could be something like:
::
:- object(tracer,
% built-in protocol for event handler methods
implements(monitoring))
.
before(Object, Message, Sender)
:-
write('call: ')
, writeq(Object)
,
write(' <-- ')
, writeq(Message)
,
write(' from ')
, writeq(Sender)
, nl.
after(Object, Message, Sender)
:-
write('exit: ')
, writeq(Object)
,
write(' <-- ')
, writeq(Message)
,
write(' from ')
, writeq(Sender)
, nl.
:- end_object.
Assume that we also have the following object:
::
:- object(any)
.
:- public(bar/1). bar(bar). :- public(foo/1). foo(foo).
:- end_object.
After compiling and loading both objects and setting the
:ref:`events <flag_events>` flag to allow
, we can start tracing
every message sent to any object by calling the
:ref:predicates_define_events_5
built-in predicate:
.. code-block:: text
| ?- set_logtalk_flag(events, allow)
.
yes
| ?- define_events(_, _, _, _, tracer)
.
yes
From now on, every message sent from user
to any object will be
traced to the standard output stream:
.. code-block:: text
| ?- any::bar(X)
.
call: any <-- bar(X)
from user
exit: any <-- bar(bar)
from user
X = bar
yes
To stop tracing, we can use the :ref:predicates_abolish_events_5
built-in predicate:
.. code-block:: text
| ?- abolish_events(_, _, _, _, tracer)
.
yes
The :ref:`monitoring apis:monitoring/0` protocol declares the event handlers as public predicates. If necessary, :ref:`protected or private implementation of the protocol <protocols_implementing>` may be used in order to change the scope of the event handler predicates. Note that the message-sending processing mechanism is able to call the event handlers irrespective of their scope. Nevertheless, the scope of the event handlers may be restricted in order to prevent other objects from calling them.
The pseudo-object :ref:`user <objects_user>` can also act as a monitor.
This object expects the before/3 and after/3 predicates to be
defined in the plain Prolog database. To avoid predicate existence errors
when setting user
as a monitor, this object declares the predicates
multifile. Thus, any plain Prolog code defining the predicates should
include the directives:
::
:- multifile(before/3)
.
:- multifile(after/3)
.