Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

Simple JS/UnityScript script for events

Discussion in 'Scripting' started by barinelg, Apr 8, 2014.

  1. barinelg

    barinelg

    Joined:
    Jun 1, 2010
    Posts:
    95
    Hello, everyone!

    I was looking around for possible JavaScript/UnityScript solutions for events that felt similar to C# events and delegates. Not finding much outside of a few mentions of frameworks (I didn't look hard), I decided to try to make something simple myself as a personal challenge. The following class is the result of that and I was just wanting to see what people thought of it, even if other options already exist. First you'll see the class, and then examples to how it compares to using C# events.

    Code (csharp):
    1.  
    2. #pragma strict
    3.  
    4. /*
    5.  * A simple JavaScript/UnityScript class that provides C#-style event functionality
    6.  * using reflection.
    7.  */
    8. public class SimpleJsEvent extends Object {
    9.     //A class (struct) to hold the information needed to register and fire an event
    10.     private class SimpleJsEventInfo extends System.ValueType {
    11.         var ObjectInstance:Object;
    12.         var Method:String;
    13.         var ObjectMethodInfo:System.Reflection.MethodInfo;
    14.     }
    15.     //The max number of arguments the registered function requires
    16.     private var maxNumberArguments:int = -1;
    17.     public function GetMaxNumberArguments() { return maxNumberArguments; }
    18.     //Storage for the registered functions
    19.     private var events:Array = new Array();
    20.    
    21.     /*
    22.      * Registers the function you wish to call when this event is fired. This will also
    23.      * ensure that the number of parameters for all functions registered match in length.
    24.      * The number of parameters is set when the first function is registered.
    25.      * PARAM: object (System.Object) - The object that contains the fuction you want to be called
    26.      * PARAM: method (String) - The name of themethod you want to be called
    27.      */
    28.     public function RegisterEvent(object:Object, method:String) {
    29.         //Get the method
    30.         var methodInfo:System.Reflection.MethodInfo = object.GetType().GetMethod(method);
    31.         //Get the number of parameters for the method.
    32.         var numOfArgs = methodInfo.GetParameters().length;
    33.         //Check to see if this is our first function registered. If so, assign the max number of params
    34.         //to the number of parameters of the passed in method. If not and the numbers don't match,
    35.         //throw and error.
    36.         if(maxNumberArguments == -1) {
    37.             maxNumberArguments = numOfArgs;
    38.         } else {
    39.             if(numOfArgs != maxNumberArguments) {
    40.                 throw "The number of parameters for " + object.GetType() + "." + method + " is invalid. Found " + numOfArgs + " where a number of " + maxNumberArguments + " is required.";
    41.             }
    42.         }
    43.         //Create the info to be stored for later calling.
    44.         var eventInfo:SimpleJsEventInfo;
    45.         eventInfo.ObjectInstance = object;
    46.         eventInfo.Method = method;
    47.         eventInfo.ObjectMethodInfo = methodInfo;
    48.         events.Add(eventInfo);
    49.     }
    50.    
    51.     /*
    52.      * Unegisters the function you wish to no longer be called when this event is fired.
    53.      * PARAM: object (System.Object) - The object that contains the fuction you want to be unregistered
    54.      * PARAM: method (String) - The name of the method you want to be removed
    55.      */
    56.     public function UnregisterEvent(object:Object, method:String) {
    57.         var eventArray:SimpleJsEventInfo[] = events.ToBuiltin(SimpleJsEventInfo) as SimpleJsEventInfo[];
    58.         for(var i=0; i < eventArray.length; ++i) {
    59.             //If the current registered function's object and method are the same as the one's passed in,
    60.             //remove it from the array.
    61.             if(eventArray[i].ObjectInstance == object  eventArray[i].Method == method) {
    62.                 events.RemoveAt(i);
    63.                 break;
    64.             }
    65.         }
    66.     }
    67.    
    68.     /*
    69.      * Fires the event if it requires no parameters.
    70.      */
    71.     public function FireEvent() {
    72.         this.fireEvent(null);
    73.     }
    74.    
    75.     /*
    76.      * Takes in a JavaScript/UnityScript array and fire's the event, passing the
    77.      * parameters to the called function.
    78.      * PARAM: params (Array) - the parameters to be passed to each registered event
    79.      */
    80.     public function FireEvent(params:Array) {
    81.         this.fireEvent(params.ToBuiltin(Object) as Object[]);
    82.     }
    83.    
    84.     /*
    85.      * Actually performs the event firing. Goes through all registered functions and
    86.      * invokes the method.
    87.      * PARAM: params (System.object[]) - The functions, as a "built in" array, to pass to Invoke
    88.      */
    89.     private function fireEvent(params:Object[]) {
    90.         var eventArray:SimpleJsEventInfo[] = events.ToBuiltin(SimpleJsEventInfo) as SimpleJsEventInfo[];
    91.         for(var i=0; i < eventArray.length; ++i) {
    92.             eventArray[i].ObjectMethodInfo.Invoke(eventArray[i].ObjectInstance, params);
    93.         }
    94.     }
    95. }
    96.  
    EXAMPLES

    C# using built-in events
    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4.  
    5. public class Example : MonoBehaviour {
    6.     public delegate void ExampleDelegate();
    7.     public event ExampleDelegate OnExample;
    8.     public delegate void ExampleDelegateWithParams(int param1, string param2);
    9.     public event ExampleDelegateWithParams OnExampleWithParams;
    10.  
    11.     void Update () {
    12.         //Register the events
    13.         if(Input.GetKeyDown(KeyCode.R)) {
    14.             OnExample += LogText;
    15.             OnExampleWithParams += LogTextWithParams;
    16.             Debug.Log("---EVENTS REGISTERED");
    17.         }
    18.         //Fire the no param event
    19.         if(Input.GetKeyDown(KeyCode.Space)) {
    20.             if(OnExample != null){
    21.                 OnExample();
    22.                 Debug.Log("---OnExample FIRED");
    23.             }
    24.         }
    25.         //Fire the event with params
    26.         if(Input.GetKeyDown(KeyCode.F)) {
    27.             if(OnExampleWithParams != null){
    28.                 OnExampleWithParams(42, "weeeeeeee!");
    29.                 Debug.Log("---OnExampleWithParams FIRED");
    30.             }
    31.         }
    32.         //Unregister the events
    33.         if(Input.GetKeyDown(KeyCode.U)) {
    34.             OnExample -= LogText;
    35.             OnExampleWithParams -= LogTextWithParams;
    36.             Debug.Log("---EVENTS UNREGISTERED");
    37.         }
    38.     }
    39.  
    40.     private void LogText() {
    41.         Debug.Log("Here is some text going into the logs!");
    42.     }
    43.  
    44.     private void LogTextWithParams(int intParam, string stringParam) {
    45.         Debug.Log("Here is some text going into the logs, but with some parameters! param1 = " + intParam + " param2 = " + stringParam);
    46.     }
    47. }
    48.  
    JS/US using SimpleJsEvent
    Code (csharp):
    1.  
    2. #pragma strict
    3.  
    4. var OnExample:SimpleJsEvent = new SimpleJsEvent();
    5. var OnExampleWithParams:SimpleJsEvent = new SimpleJsEvent();
    6.  
    7. function Update () {
    8.     //Fire the no param event
    9.     if(Input.GetKeyDown(KeyCode.Space)) {
    10.         OnExample.FireEvent();
    11.         Debug.Log("---OnExample FIRED");
    12.     }
    13.     //Fire the event with params
    14.     if(Input.GetKeyDown(KeyCode.F)) {
    15.         OnExampleWithParams.FireEvent(new Array(42, "weeeeeeee!"));
    16.         Debug.Log("---OnExampleWithParams FIRED");
    17.  
    18.     }
    19.     //Register the events
    20.     if(Input.GetKeyDown(KeyCode.R)) {
    21.         OnExample.RegisterEvent(this, "LogText");
    22.         OnExampleWithParams.RegisterEvent(this, "LogTextWithParams");
    23.         Debug.Log("---EVENTS REGISTERED");
    24.     }
    25.     //Unregister the events
    26.     if(Input.GetKeyDown(KeyCode.U)) {
    27.         OnExample.UnregisterEvent(this, "LogText");
    28.         OnExampleWithParams.UnregisterEvent(this, "LogTextWithParams");
    29.         Debug.Log("---EVENTS UNREGISTERED");
    30.     }
    31. }
    32.    
    33. function LogText() {
    34.     Debug.Log("Here is some text going into the logs!");
    35. }
    36.  
    37. function LogTextWithParams(intParam, stringParam) {
    38.     Debug.Log("Here is some text going into the logs, but with some parameters! param1 = " + intParam + " param2 = " + stringParam);
    39. }
    40.  
     
  2. Patico

    Patico

    Joined:
    May 21, 2013
    Posts:
    886
    Well done, I like it!
    One remark - in C# language, events are one of few way that could lead to memory leaks, so it will be good practice to clear events:Array variable when SimpleJsEvent object will be destroyed.
     
  3. User340

    User340

    Joined:
    Feb 28, 2007
    Posts:
    3,001
    us has seemed to evolved a bit since when i last used it. It seems to support delegates as proven by this code:

    Code (csharp):
    1. #pragma strict
    2.  
    3. function Start () {
    4.     var test : System.Action.<int> = Jajaja;
    5.    
    6.     test(5);
    7.     test(234);
    8. }
    9.  
    10. function Jajaja (hi : int)
    11. {
    12.     Debug.Log(hi.ToString());
    13. }
     
  4. Smooth-P

    Smooth-P

    Joined:
    Sep 15, 2012
    Posts:
    214
    Use C# to define generic event types.

    Code (csharp):
    1.  
    2. using System;
    3.  
    4. public struct SimpleEvent {
    5.     public event Action Handle;
    6.     public void Raise() { if (Handle != null) Handle(); }
    7. }
    8.  
    9. public struct SimpleEvent<T1> {
    10.     public event Action<T1> Handle;
    11.     public void Raise(T1 t1) { if (Handle != null) Handle(t1); }
    12. }
    13.  
    14. public struct SimpleEvent<T1, T2> {
    15.     public event Action<T1, T2> Handle;
    16.     public void Raise(T1 t1, T2 t2) { if (Handle != null) Handle(t1, t2); }
    17. }
    18.  
    19. // etc
    20.  

    Use from UnityScript if you're into that sort of thing.

    Code (csharp):
    1.  
    2. #pragma strict
    3.  
    4. var myEvent : SimpleEvent.<int, String>;
    5.  
    6. function Start() {
    7.     myEvent.Handle += DoStuff;
    8.     myEvent.Raise(0, "Yay!");
    9. }
    10.  
    11. function OnDestroy() {
    12.     // Remove handlers registered on other, non-destroyed objects, eg:
    13.     if (this) {
    14.         this.myEvent.Handle -= DoStuff;
    15.     }
    16. }
    17.  
    18. function DoStuff(i : int, s : String) {
    19. }
    20.  

    I'm actually planning to release a bunch of my utility code which includes events like these on the Unity store as free-to-use, free-to-distribute as soon as I get around to finishing the docs. If you want to download the semi-undocumented package you can find it here.
     
    Last edited: Apr 8, 2014
  5. barinelg

    barinelg

    Joined:
    Jun 1, 2010
    Posts:
    95
    Thanks for the compliments and ideas! I had no idea there was a memory leak in C# events, and I'm glad you made me aware of that.

    I also had no idea that you could do delegates like that in UnityScript, but thinking on how I'm doing my reflection it does make sense. After learning that I considered using it, but feel that the current approach kind of keeps to the idea of JS/US within Unity: flexibility. The only real drawback is if the coder isn't keeping up with their parameters, but it will error on an invoke attempt if there are issues. I might put this on the store just for having it there (free, of course).