robotlegs / robotlegs-framework

An ActionScript 3 application framework for Flash and Flex
https://robotlegs.tenderapp.com/
MIT License
967 stars 261 forks source link

ModuleConnector: allow module instances communicate with each other #141

Open creynders opened 11 years ago

creynders commented 11 years ago

If you try to have several instance of the same module communicate with each other through the module connector, you run into an infinite loop:

moduleConnector.onChannel('A-and-B')
.receiveEvent(ModularConnectorEvent.SOME_TYPE);

moduleConnector.onChannel('A-and-B')
.relayEvent (ModularConnectorEvent.SOME_TYPE);

see http://knowledge.robotlegs.org/discussions/robotlegs-2/3327-go-modular-for-multiple-games

creynders commented 11 years ago

cc @ondina

Ondina commented 11 years ago

Have you seen this comment too? http://knowledge.robotlegs.org/discussions/robotlegs-2/3327-go-modular-for-multiple-games#comment_27433210

creynders commented 11 years ago

Yeah I saw it, but though that's ok as a temporary solution, this is something that needs to be fixed. Need to think on how to fix it though.

Ondina commented 11 years ago

Of course.

creynders commented 11 years ago

Wow, you're totally losing me :)

How does that exactly help? You'd still need to do this:

[Inject (name='A-and-B')]
public var eventDispatcher:IEventDispatcher;

eventDispatcher.dispatchEvent(ModularConnectorEvent.A_TO_B_MESSAGE);

Right? We're still talking about a module instance sending events to other instances of the same module, right?

Ondina commented 11 years ago

Yeah, I deleted the bs. My intention was to have a dispatcher for the inter-instances communication that doesn’t relay any events and kind of a local interceptor that would decide what to do with the received event, maybe made clear in the mappings or a guard ...weird, I know.

As I understand (still studying the code), the infinite loop is caused by having the same dispatcher as a receiver and as a relayer, listening to and dispatching the same event. The moment it receives the event, it relays it again.

Anyway, I’ll continue studying the ModuleConnector & Co and keep my mouth shut ;)

creynders commented 11 years ago

Anyway, I’ll continue studying the ModuleConnector & Co and keep my mouth shut ;)

No, no, that's how solutions are found. 99% BS and 1% good ideas. At least that's how it is for me. :)

darscan commented 11 years ago

I remember worrying about circular relays.. and even possibly coming up with solution.. and then totally forgetting. The fix probably needs to live in the EventRelay.

Ondina commented 11 years ago

I made another attempt to break the infinite loop. BS or not, but it works.

I couldn't create a gist for some reasons, so here is the code: https://github.com/Ondina/robotlegs-bender-modular-air/wiki/ModuleConnectionConfigurator---breaking-the-infinite-loop

[1] localDispatcher dispatches [2] channelDispatcher listens [3] channelDispatcher dispatches [4] localDispatcher listens [5] localDispatcher dispatches [6] channelDispatcher listens [7] channelDispatcher dispatches and so on

Here is the loop breaker:


public function onEventDispatched(event:*):void
{
    if (_channelDispatcher == event.target)
    {
        _localDispatcher.removeEventListener(event.type, _channelDispatcher.dispatchEvent);
    }
    else
    {
        _localDispatcher.addEventListener(event.type, _channelDispatcher.dispatchEvent);
    }
}

I looked at the EventRelay and I think it's not the right place to make changes, since all it does is adding and removing listeners and it shouldn't have to know about events being dispatched. In fact, the ModuleConnectionConfigurator should not be burdened with that either, but where else do we have access to the dispatchers and the event types?

I also thought about using event's useCapture and phases or event.stopPropagation() inside of EventRelay , but I couldn't work it out, maybe because it doesn't make sense at all? Sorry for the ramblings, but infinite loops are dizzying...

I'm sure you'll come up with something better, so I'm really very curious about your solution to that ouroboric situation :)

@darscan could you, at least, give me a hint about the solution you had in mind

darscan commented 11 years ago

Interesting solution! I honestly can't remember where my thinking was at regarding this stuff. I'll have another dabble this w/e, and give more thought to your solution as well.

Ondina commented 11 years ago

@darscan alright, there is no rush

Another thing I've tried was to have a callback function for each dispatcher, where they'd let the other dispatcher dispatch the event only if it wasn't already dispatched, depending on a counter. I could make it work only for instances of the same module. Somehow the counting was messed up for other modules and I gave up on this approach. I don't know what's better, relaying events manually depending on some condition, or adding/removing event listeners as I showed in my previous message. Anyway, adding/removing listeners was easier for me to implement while keeping the original logic of all the involved classes.

Btw, I changed the code a bit:


public function onEventDispatched(event:Event):void
{
   if (_channelDispatcher == event.target)
         _localToChannelRelay.removeType(event.type);
   else
        _localToChannelRelay.addType(event.type);
}

and added the type (EventRelay.addType) to the _types only if the type is not in _types.

[EDIT] I made it work (relaying events manually based on a counter) for all modules - different instances of same module *and different modules as well. I had to reset the counter in case the module did not have a mapping for receiving an event type. Still, not convinced it's a good approach.

Ondina commented 11 years ago

Progress report

https://github.com/Ondina/robotlegs-bender-modular-air/wiki/ModuleConnectionConfigurator---take-2

It's just a draft.

I add listeners to the parentDispatcher:


_parentDispatcher.addEventListener(ModularRouterEvent.LOCAL_TO_PARENT, parentToLocalRouter);

and to the localDispatcher:


public function addListener(eventType:String):void
{
    _eventTypeDispatched[eventType] = false;
    _localDispatcher.addEventListener(eventType, localToParentRouter);
}

The handlers:


private function localToParentRouter(event:Event):void
{
    if (!_eventTypeDispatched[event.type])
    {
        _eventTypeDispatched[event.type] = true;
        _parentDispatcher.dispatchEvent(new ModularRouterEvent(ModularRouterEvent.LOCAL_TO_PARENT, _context, event, event.type));
    }
    _eventTypeDispatched[event.type] = false;
}
private function parentToLocalRouter(event:ModularRouterEvent):void
{
    if (_context != event.context)
    {
        _eventTypeDispatched[event.eventType] = true;
        _localDispatcher.dispatchEvent(event.eventClass);
    }
}

Ondina commented 11 years ago

What about creating a lifecycle for the modular events?

https://gist.github.com/Ondina/6138055 (https://github.com/Ondina/robotlegs-bender-modular-air/wiki/ModuleConnectionConfigurator---ModularRouter)