Skip to content

Commit

Permalink
[lang][core] Event source should be specified when firing in EventSpace.
Browse files Browse the repository at this point in the history
see #78

Signed-off-by: Stéphane Galland <galland@arakhne.org>
  • Loading branch information
gallandarakhneorg committed Sep 2, 2017
1 parent 07115ac commit e5aa681
Show file tree
Hide file tree
Showing 17 changed files with 343 additions and 103 deletions.
12 changes: 12 additions & 0 deletions main/apiplugins/io.sarl.core/src/io/sarl/core/bic.sarl
Expand Up @@ -34,6 +34,7 @@ import io.sarl.lang.core.AgentContext
import io.sarl.lang.core.Behavior
import io.sarl.lang.core.Event
import io.sarl.lang.core.EventListener
import io.sarl.lang.core.EventSpace
import io.sarl.lang.core.Scope
import io.sarl.lang.core.Space
import io.sarl.lang.core.SpaceID
Expand Down Expand Up @@ -133,6 +134,17 @@ capacity ExternalContextAccess {
@Pure
def isInSpace(^event : Event, spaceID : UUID) : boolean

/**
* Emits a given event with the provided scope in the given space.
* Equivalent to <code>space.emit(getID,event,scope)</code>
*
* @param space the space in which the event should be fired.
* @param event the event to emit.
* @param scope the definition of the scope that will be used for selected the receivers of the events. If <code>null</code>, all the agents in the space will receive the event.
* @since 0.6
*/
def emit(^space : EventSpace, ^event : Event, scope : Scope<Address> = null)

}

/**
Expand Down
Expand Up @@ -30,6 +30,7 @@
* Event driven Interaction {@link Space} for agents.
*
* @author $Author: srodriguez$
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
Expand All @@ -49,19 +50,73 @@ public interface EventSpace extends Space {
* Emits the event inside this space with the given scope. Only agents
* matching the scope will receive the event.
*
* <p>This function does not change the source of the event if it was set.
*
* <p>If the given event has no specified source, the caller of the emit function is assumed
* to set the source's address to the address of the "current" agent. The concept of "current"
* agent depends on the capabilities of the run-time platform. Usually, it is the
* agent that is the cause of the emit.
*
* @param event - the event to emit in the space.
* @param scope - the definition of the list of receiviers of the event.
* @param scope - the definition of the list of receivers of the event.
* @deprecated see {@link #emit(UUID, Event, Scope)}, since 0.6
*/
void emit(Event event, Scope<Address> scope);
@Deprecated
@Inline(value = "emit(null, $1, $2)")
default void emit(Event event, Scope<Address> scope) {
emit(null, event, scope);
}

/**
* Emits the event inside this space. All registered agents will receive the event.
*
* <p>This function does not change the source of the event if it was set.
*
* <p>If the given event has no specified source, the caller of the emit function is assumed
* to set the source's address to the address of the "current" agent. The concept of "current"
* agent depends on the capabilities of the run-time platform. Usually, it is the
* agent that is the cause of the emit.
*
* @param event - the event to emit in the space.
* @deprecated see {@link #emit(UUID, Event)}, since 0.6
*/
@Inline(value = "emit($1, null)")
@Inline(value = "emit(null, $1, null)")
@Deprecated
default void emit(Event event) {
emit(event, null);
emit(null, event, null);
}

/**
* Emits the event inside this space with the given scope. Only agents
* matching the scope will receive the event.
*
* <p>This function does not change the source of the event if it was set.
*
* <p>If the given event has no specified source, the emit function uses the
* {@code eventSource} parameter to set the source's address.
*
* @param eventSource - the sender of the event.
* @param event - the event to emit in the space.
* @param scope - the definition of the list of receivers of the event.
* @since 0.6
*/
void emit(UUID eventSource, Event event, Scope<Address> scope);

/**
* Emits the event inside this space. All registered agents will receive the event.
*
* <p>This function does not change the source of the event if it was set.
*
* <p>If the given event has no specified source, the emit function uses the
* {@code eventSource} parameter to set the source's address.
*
* @param eventSource - the sender of the event.
* @param event - the event to emit in the space.
* @since 0.6
*/
@Inline(value = "emit($1, $2, null)")
default void emit(UUID eventSource, Event event) {
emit(eventSource, event, null);
}

}
Expand Up @@ -160,8 +160,7 @@ public synchronized void wake(Event evt, Scope<Address> scope) {

if ((!(context instanceof InnerContextSkill)) || ((InnerContextSkill) context).hasInnerContext()) {
final EventSpace defSpace = context.getInnerContext().getDefaultSpace();
evt.setSource(defSpace.getAddress(getOwner().getID()));
defSpace.emit(evt, scope);
defSpace.emit(getID(), evt, scope);
} else {
// Do not call getInnerContext(), which is creating the inner context automatically.
// In place, try to send the event inside the agent only (and its behaviors).
Expand Down
Expand Up @@ -101,14 +101,12 @@ protected void install() {

@Override
public void emit(Event event) {
event.setSource(getDefaultAddress());
this.defaultSpace.emit(event);
this.defaultSpace.emit(getOwner().getID(), event, null);
}

@Override
public void emit(Event event, Scope<Address> scope) {
event.setSource(getDefaultAddress());
this.defaultSpace.emit(event, scope);
this.defaultSpace.emit(getOwner().getID(), event, scope);
}

@Override
Expand Down
Expand Up @@ -37,10 +37,12 @@
import io.sarl.core.ExternalContextAccess;
import io.sarl.core.MemberJoined;
import io.sarl.core.MemberLeft;
import io.sarl.lang.core.Address;
import io.sarl.lang.core.Agent;
import io.sarl.lang.core.AgentContext;
import io.sarl.lang.core.Event;
import io.sarl.lang.core.EventSpace;
import io.sarl.lang.core.Scope;
import io.sarl.lang.core.Skill;
import io.sarl.lang.core.Space;
import io.sarl.lang.core.SpaceID;
Expand Down Expand Up @@ -189,7 +191,10 @@ protected final void fireContextJoined(UUID futureContext, UUID futureContextDef
*/
protected final void fireMemberJoined(AgentContext newJoinedContext) {
final EventSpace defSpace = newJoinedContext.getDefaultSpace();
defSpace.emit(new MemberJoined(defSpace.getAddress(getOwner().getID()), newJoinedContext.getID(), getOwner().getID(),
defSpace.emit(
// No need to give an event source because the event's source is explicitly set below.
null,
new MemberJoined(defSpace.getAddress(getOwner().getID()), newJoinedContext.getID(), getOwner().getID(),
getOwner().getClass().getName()));
}

Expand Down Expand Up @@ -233,6 +238,8 @@ protected final void fireContextLeft(UUID contextID) {
protected final void fireMemberLeft(AgentContext leftContext) {
final EventSpace defSpace = leftContext.getDefaultSpace();
defSpace.emit(
// No need to give an event source because the event's source is explicitly set below.
null,
new MemberLeft(defSpace.getAddress(getOwner().getID()), getOwner().getID(), getOwner().getClass().getName()));
}

Expand All @@ -251,4 +258,9 @@ public boolean isInSpace(Event event, UUID spaceID) {
return spaceID.equals(event.getSource().getSpaceId().getID());
}

@Override
public void emit(EventSpace space, Event event, Scope<Address> scope) {
space.emit(getID(), event, scope);
}

}
Expand Up @@ -291,21 +291,25 @@ public <T> int getRegisteredEventListeners(Class<T> type, Collection<? super T>

@Override
public void selfEvent(Event event) {
// Ensure that the event source is the agent itself!
event.setSource(getInnerDefaultSpaceAddress());
// If the event must be fired only by the
// agent itself, it is treated in this function.
// Otherwise, it is given to the asynchronous
// listener.
final Class<? extends Event> eventType = event.getClass();
if (Initialize.class.equals(eventType)) {
// Ensure that the event source is the agent itself!
event.setSource(getInnerDefaultSpaceAddress());
runInitializationStage(event);
} else if (Destroy.class.equals(eventType)) {
// Ensure that the event source is the agent itself!
event.setSource(getInnerDefaultSpaceAddress());
runDestructionStage(event);
} else if (AsynchronousAgentKillingEvent.class.equals(eventType)) {
// Asynchronous kill of the event.
this.agentAsEventListener.killOrMarkAsKilled();
} else if (getOwnerState().isEventHandling()) {
// Ensure that the event source is the agent itself!
event.setSource(getInnerDefaultSpaceAddress());
// Asynchronous parallel dispatching of this event
this.agentAsEventListener.receiveEvent(event);
}
Expand Down
Expand Up @@ -218,7 +218,10 @@ public void spaceCreated(Space space, boolean isLocalCreation) {
final EventSpace defSpace = this.context.getDefaultSpace();
// defSpace may be null if the created space is the default space.
if (defSpace != null) {
defSpace.emit(new SpaceCreated(new Address(defSpace.getSpaceID(), this.context.getID()), space.getSpaceID()));
defSpace.emit(
// No need to give an event source because it is explicitly set below.
null,
new SpaceCreated(new Address(defSpace.getSpaceID(), this.context.getID()), space.getSpaceID()));
}
}
}
Expand All @@ -231,7 +234,10 @@ public void spaceDestroyed(Space space, boolean isLocalDestruction) {
final EventSpace defSpace = this.context.getDefaultSpace();
// defSpace may be null if the created space is the default space.
if (defSpace != null) {
defSpace.emit(new SpaceDestroyed(new Address(defSpace.getSpaceID(), this.context.getID()), space.getSpaceID()));
defSpace.emit(
// No need to give an event source because it is explicitly set below.
null,
new SpaceDestroyed(new Address(defSpace.getSpaceID(), this.context.getID()), space.getSpaceID()));
}
}
// Notify the relays (other services)
Expand Down
Expand Up @@ -248,7 +248,10 @@ protected void fireAgentSpawnedOutsideAgent(UUID spawningAgent, AgentContext con
assert source != null;
final AgentSpawned event = new AgentSpawned(source, agentClazz.getName(),
Collections2.transform(agents, (it) -> it.getID()));
defSpace.emit(event);
defSpace.emit(
// No need to give an event source because it is explicitly set above.
null,
event);
}

/** Notify the agent's listeners about its spawning.
Expand Down Expand Up @@ -471,7 +474,10 @@ protected void fireAgentDestroyed(Agent agent) {
synchronized (sc.mutex()) {
for (final AgentContext context : sc) {
final EventSpace defSpace = context.getDefaultSpace();
defSpace.emit(new AgentKilled(defSpace.getAddress(agent.getID()), agent.getID(), agent.getClass().getName()));
defSpace.emit(
// No need to give an event source because it is explicitly set below.
null,
new AgentKilled(defSpace.getAddress(agent.getID()), agent.getID(), agent.getClass().getName()));
}
}
} catch (RuntimeException e) {
Expand Down
Expand Up @@ -121,13 +121,15 @@ public Address getAddress(UUID id) {
*
* <p>This function emits on the internal event bus of the agent (call to {@link #doEmit(Event, Scope)}), and on the network.
*
* @param eventSource - the source of the event.
* @param event - the event to emit.
* @param scope - description of the scope of the event, i.e. the receivers of the event.
* @since 2.0.6.0
*/
public final void emit(Event event, Scope<Address> scope) {
public final void emit(UUID eventSource, Event event, Scope<Address> scope) {
assert event != null;
assert event.getSource() != null : "Every event must have a source"; //$NON-NLS-1$
assert this.getSpaceID().equals(event.getSource().getSpaceId()) : "The source address must belong to this space"; //$NON-NLS-1$
ensureEventSource(eventSource, event);
assert getSpaceID().equals(event.getSource().getSpaceId()) : "The source address must belong to this space"; //$NON-NLS-1$
try {
final Scope<Address> scopeInstance = (scope == null) ? Scopes.<Address>allParticipants() : scope;
this.network.publish(scopeInstance, event);
Expand All @@ -138,6 +140,22 @@ public final void emit(Event event, Scope<Address> scope) {

}

/** Ensure that the given event has a source.
*
* @param eventSource - the source of the event.
* @param event - the event to emit.
* @since 2.0.6.0
*/
protected void ensureEventSource(UUID eventSource, Event event) {
if (event.getSource() == null) {
if (eventSource != null) {
event.setSource(new Address(getSpaceID(), eventSource));
} else {
throw new AssertionError("Every event must have a source"); //$NON-NLS-1$
}
}
}

/**
* Do the emission of the event.
*
Expand Down
Expand Up @@ -154,7 +154,8 @@ public RegisteredInInitializeAgent(BuiltinCapacitiesProvider provider, UUID pare
protected boolean runAgentTest() {
addResult(this);
getSkill(Behaviors.class).registerBehavior(this.behavior);
getSkill(Schedules.class).in(TIMEOUT, (agent) -> getSkill(Lifecycle.class).killMe());
getSkill(Schedules.class).in(TIMEOUT, (agent) ->
getSkill(Lifecycle.class).killMe());
return false;
}

Expand Down
Expand Up @@ -221,29 +221,27 @@ public void getRegisteredBehaviors() {
public void wake_noScope() {
Event event = mock(Event.class);
this.skill.wake(event);
ArgumentCaptor<Event> argument1 = ArgumentCaptor.forClass(Event.class);
ArgumentCaptor<Scope<Address>> argument2 = ArgumentCaptor.forClass(Scope.class);
Mockito.verify(this.innerSpace).emit(argument1.capture(), argument2.capture());
assertSame(event, argument1.getValue());
assertNull(argument2.getValue());
ArgumentCaptor<Address> argument3 = ArgumentCaptor.forClass(Address.class);
Mockito.verify(event).setSource(argument3.capture());
assertEquals(this.address, argument3.getValue());
ArgumentCaptor<UUID> argument1 = ArgumentCaptor.forClass(UUID.class);
ArgumentCaptor<Event> argument2 = ArgumentCaptor.forClass(Event.class);
ArgumentCaptor<Scope<Address>> argument3 = ArgumentCaptor.forClass(Scope.class);
Mockito.verify(this.innerSpace).emit(argument1.capture(), argument2.capture(), argument3.capture());
assertEquals(this.agent.getID(), argument1.getValue());
assertSame(event, argument2.getValue());
assertNull(argument3.getValue());
}

@Test
public void wake_scope_all() {
Event event = mock(Event.class);
this.skill.wake(event, Scopes.allParticipants());
ArgumentCaptor<Event> argument1 = ArgumentCaptor.forClass(Event.class);
ArgumentCaptor<Scope<Address>> argument2 = ArgumentCaptor.forClass(Scope.class);
Mockito.verify(this.innerSpace).emit(argument1.capture(), argument2.capture());
assertSame(event, argument1.getValue());
assertNotNull(argument2.getValue());
assertSame(Scopes.allParticipants(), argument2.getValue());
ArgumentCaptor<Address> argument3 = ArgumentCaptor.forClass(Address.class);
Mockito.verify(event).setSource(argument3.capture());
assertEquals(this.address, argument3.getValue());
ArgumentCaptor<UUID> argument1 = ArgumentCaptor.forClass(UUID.class);
ArgumentCaptor<Event> argument2 = ArgumentCaptor.forClass(Event.class);
ArgumentCaptor<Scope<Address>> argument3 = ArgumentCaptor.forClass(Scope.class);
Mockito.verify(this.innerSpace).emit(argument1.capture(), argument2.capture(), argument3.capture());
assertEquals(this.agent.getID(), argument1.getValue());
assertSame(event, argument2.getValue());
assertNotNull(argument3.getValue());
assertSame(Scopes.allParticipants(), argument3.getValue());
}

@Test
Expand All @@ -252,15 +250,14 @@ public void wake_scope_any() {
SpaceID spaceID = new SpaceID(UUID.randomUUID(), UUID.randomUUID(), null);
Scope<Address> scope = Scopes.addresses(new Address(spaceID, UUID.randomUUID()));
this.skill.wake(event, scope);
ArgumentCaptor<Event> argument1 = ArgumentCaptor.forClass(Event.class);
ArgumentCaptor<Scope<Address>> argument2 = ArgumentCaptor.forClass(Scope.class);
Mockito.verify(this.innerSpace).emit(argument1.capture(), argument2.capture());
assertSame(event, argument1.getValue());
assertNotNull(argument2.getValue());
assertSame(scope, argument2.getValue());
ArgumentCaptor<Address> argument3 = ArgumentCaptor.forClass(Address.class);
Mockito.verify(event).setSource(argument3.capture());
assertEquals(this.address, argument3.getValue());
ArgumentCaptor<UUID> argument1 = ArgumentCaptor.forClass(UUID.class);
ArgumentCaptor<Event> argument2 = ArgumentCaptor.forClass(Event.class);
ArgumentCaptor<Scope<Address>> argument3 = ArgumentCaptor.forClass(Scope.class);
Mockito.verify(this.innerSpace).emit(argument1.capture(), argument2.capture(), argument3.capture());
assertEquals(this.agent.getID(), argument1.getValue());
assertSame(event, argument2.getValue());
assertNotNull(argument3.getValue());
assertSame(scope, argument3.getValue());
}

@Test
Expand Down

1 comment on commit e5aa681

@ssardina
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just found this new thing in 0.6 which is causing problems with my old code, now it does not work.

Examples are still using old emit it seems: http://www.sarl.io/docs/official/index.html

I would strongly recommend that when these non-backward compatible changes are done, they are specially highlighted in a separate part and suggestions how to upgrade old code is included.

Please sign in to comment.