Showing posts with label event. Show all posts
Showing posts with label event. Show all posts

Thursday, September 9, 2010

Message Oriented Architecture (Code 2/2)

Second and final part of the code.
private function _addEventListener(idChannel:String, type:String, listener:iEventListener):void {
   if(!(idChannel in hashChannel)) { // first add channel check
      // there is no hashType for that sender so we create one
      hashChannel[idChannel] = new Object();
   }
   if (!(type in hashChannel[idChannel])) { // first add type check
      // there is no such event listener registred
      hashChannel[idChannel][type] = new Array();
   }
   var array:Array = hashChannel[idChannel][type] as Array;
   if(array.indexOf(listener) != -1) { // second add listener check
      // the array does contain "listener", so we don't add twice
      return;
   } else {
      // the only really useful code
      array.push(listener);
   }
}
 
Be careful with all those checks, without them, you could have problems. By example if you put the EventCenter as an external project like me, you don't have to change for each project the type of channels, types etc. So keep in mind that you need to check each time if the channel exist, if the type exist etc. The code itself is not complex but the number of case could me it hard to understand. Now you know of to add a listener. Let's see of to remove on, that is the last part.
private function _removeEventListener(idChannel:String, type:String, listener:iEventListener):void {
   if(!(idChannel in hashChannel)) {
      // there is no hashType for that sender so we return
      return;
   }
   var hashType:Object = hashChannel[idChannel];
   if (!(type in hashType)) {
      // You try to remove a listener that has not the good type
   } else {
      var array:Array = hashType[type] as Array;
      var index:int = array.indexOf(listener);
      if(index != -1) {
         // remove the listener from the array
         array.splice(index, 1);
         if (array.length == 0) {
            // the array is empty, so we delete it
            delete hashType[type];
         } else {
            hashType[type] = array;
         }
      } else {
         // the listener is not in the array
      }
   }
}
 
The splice method of Array, permit the deletion when we specify the index of the item and 1 as the number of item we want to remove. When we need Array.remove(item), we need to use Array.splice(Array.indexOf(item), 1), but be careful, if you forget to write the "1" all the array from the index will be removed, quite hard to find in debug.
I think we are done with the subject of EventCenter, good luck using this new way of messaging.

Message Oriented Architecture (Code 1/2)

Here I present you the code of the EventCenter. I show your parts of code incrementally, to be able to add some comments at each step.
public class EventCenter {
   private static var instance:EventCenter = new EventCenter();
   private var hashChannel:Object;
   
   public function EventCenter():void {
      this.hashChannel = new Object();
   }
 
You have here the initial code of the class. We use the singleton pattern without any protection, you can improve that if you want but it's not the goal of this tutorial. The only one variable of this class is the hashChannel that is the HashMap that contains the different channels. In ActionScript 3, we don't have native HashMap like in Java, but we have the Object class that can contains dynamical property like an HashMap.
public static function reset():void {
      instance = new EventCenter();
   }

   public static function dispatchEvent(idChannel:String, type:String, args:Object = null):void {
      instance._dispatchEvent(idChannel, type, args);
   }

   public static function addEventListener(idChannel:String, listener:iEventListener, ...type):void {
      for each(var t:String in type) {
         instance._addEventListener(idChannel, t, listener);
      }
   }

   public static function removeEventListener(idChannel:String, listener:iEventListener, ...type):void {
      for each(var t:String in type) {
         instance._removeEventListener(idChannel, t, listener);
      }
   }
 
This part of the code is actually the interface we show to the world. All those methods are public static, we could also add final but it's out of the bounds of this tutorial. All the real code is hidden in the instance field. The reset method only serves if you want to reinitialize the EventCenter. The idChannel is the identifier of the channel on/from which we send/receive messages. The iEventListener listener is the object that will receives the message, we call its method onEvent with the correct arguments. Finally the ...type allows us to add as many type as we want thanks to the "..." syntax that will transform type into an Array.
public interface iEventListener {
   function onEvent(type:String, args:Object):void;
}
 
Here is the simple code for the interface. One iEventListener can listens to as many Channel as it wants. It is the time to show the concrete code:
private function _dispatchEvent(idChannel:String, type:String, args:Object):void {
   if(idChannel in hashChannel) {
      var hashType:Object = hashChannel[idChannel];
      if (type in hashType) {
         this._dispatch(hashType, type, args);
      } else {
         // No such listener
      }
   } else {
      // there is no listener for this sender
   }
}
 
We use the properties of objects to store elements in the pseudo HashMap. You can notice the "in" operator that is quite faster than the "hasProperty...". With this, we know if there is an entrance with key=idChannel, if so, we know there are some listener for that channel. Then, we search into the HashMap corresponding to the channel if there is the type we look for, in this case we can call the _dispatch sub-function.
private function _dispatch(hashType:Object, type:String, args:Object):void {
   var array:Array = hashType[arrayType] as Array;
   if (!array) { // first assert
      // The array is null
      delete hashType[type];
   } else {
      if (array.length == 0) { // second assert
         // Array is empty => property removed
         delete hashType[arrayType];
      } else {
         for each(var listener:iEventListener in array) {
            listener.onEvent(type, args);
         }
      }
   }
}
 
As you will see in other function, especially in "_removeEventListener" we never keep an Array of listeners empty or null, so I call the two conditions "assert". Normally we never came into their braces.
This function allow us to dispatch / trigger / fire an event to all the corresponding listeners. The args contains also an HashMap.
Here is some examples of use:
EventCenter.dispatchEvent("Score_Channel", "Reset_Score");
EventCenter.dispatchEvent("Score_Channel", "Add_Score", {incr:13});
EventCenter.dispatchEvent("Score_Channel", "Decrease_Score", {decr:11});
EventCenter.dispatchEvent("Score_Channel", "Change_Score", {old:12, new:39, forPlayer:'Bob'});
 
The args is optionnal and moreover could have as many item as you want, identified by the keys. I think this is the most flexible we can do. Remember how complex it is with the native system of event. You have to create a new type of Event for each... new type you want. But with my solution, you just have to change the name. For my use, I always make constants representing the events with in commentary the format of the keys, with that when I code a dispatch, I always see with the doc, what is the format. Next part of the code, in next message.

Tuesday, September 7, 2010

Message Oriented Architecture (Performance)

Second part of the tutorial about MOA. After a night of test, I can show you the results. They are convincing. I compared the native event system with my implementation. The three main performance indices are the time for sending an event without any listener, the cost to add a listener to an empty list of listener and finally the marginal cost for adding a listener to an existing list of listeners.

Type Native Custom Performance 
without listener 2674.2 ms 1545.4 ms ~175%
first listener 2342.6 ms 1264.5 ms ~185%
add a listener 883.6 ms 145.6 ms ~605%

All tests were done with 1'000'000 send actions. The second and third experiment result are only the additional cost. So we can observe that we gain a lot of time using our custom version. Additionally, the custom version gives you more flexibility for its purpose and throw away the useless things like bubbling, etc. We can set a channel in which we speak, give the number of arguments we want and select a event type. All actions are done in a static manner, so we don't have to bother with EventDispatcher, we only use "iEventListener". As you will see.

Example of structure for the EventCenter
As you can see on the example schema, we use different level of classification. The first level contains the channels. When an event is dispatch, we first select the correct channel. Then the second level is the type of event, each channel has different type of event. You can notice the "Kill_Channel" has no type in the schema, it's because it has no listener. When a listener comes, we will add the types it wants to wait for. With this technique we don't waste neither memory space for each unused event types nor computation time when we dispatch an event that nobody are interested by.
The third level is a list of all current listeners. For the moment, we use a simple ArrayList, but we could also implement a LinkedList to store the listeners and allow us to have constant time insert/delete. But to do that, we need to add some code in each listener. Because we use only an interface to have the biggest flexibility. Using a class like EventListener that contains the next/prev links needed by the linked list, we must use composition instead of simple inheritance.
All the code you need will come with the next tutorial...

Monday, September 6, 2010

Message Oriented Architecture (Intro)

After this article, I hope, you will make some nice architecture for your future games. Using a message oriented architecture, inspired by message oriented middleware (MOM), permit the complete separation of the modules. Without a static module managing the message, all your modules needed to be connected to allow any kind of communication. Without wire, we can't communicate. But this article will show you how to reinvent the WiFi in your architecture, i.e. wireless communication. Actually the wires still exist but are hidden behind the communication module.
Let's call our communication module "EventCenter". First reason is because I use it instead of the natives events of ActionScript 3. Secondly because all the principles are based on events, that encapsulate the messages in some sense.
Some notions are needed before starting. First notion is the synchronization: a synchronous message is blocking, i.e., when we call "send", the receiver sees his method "receive" called and only when this method finishes, the sender has the hand back; an asynchronous message is sent and after a given time, the receiver really receives the message, i.e. it's not blocking. Too theoretical ? Let's have two examples.
Synchronous Messages:
We can use synchronous messages when we want to simulate direct function call like "enemy.kill()" by sending the message "EnemyKillMessage". In this case, just after the send of the message, the enemy is dead.
Asynchronous Messages:
The example for asynchronous message is quite more complex. Imagine you have a loop, during a pass you could send 0,1, or n message(s). The only relevant information is "do I have received a message during the pass or not ?" Imagine the number of messages is irrelevant and we want to make a treatment only once, imagine a long compuation. If we had synchronous message, we should use a boolean to keep the information "alreadyComputed" and a message will be send after each pass to indicate we need to reset the boolean. We can also use asynchronous messages and store them into an array like a buffer. Cleaning the buffer in one time when making the computation can speedup the execution. (If you find a better example, not too complex, don't hesitate).

Next message will be about the structure of our communication center, here was just an introduction. (Will be published tomorrow)