Callbacks 101 – ShiVa Engine

Callbacks 101

Nav Gupta: “This tutorial is actually for those who are more intermediate, HOWEVER keep in mind that once you know this, you will be able to move onto the tutorial after this that will teach you how to integrate Share options in Android such as Email, Facebook, etc. This will open you up to the world of true customization with ShiVa! So grab a coffee and a snack and let’s get down to it!”

What are hooks and callbacks?

A Hook/Callback is external code that connects up to ShiVa LUA code outside of ShiVa to complete additional functions that are not possible in ShiVa. These custom functions could be things such as In App Purchase, Sending Email with Attachments (My Next Tutorial will be on this topic), Posting to Facebook/Twitter and Many other neat functions that are more advanced or outside the scope of ShiVa. This current tutorial is for the ANDROID version of Hooks/Callbacks. I have yet to setup and create a tutorial for the iOS version of Hooks/Callbacks which I will do later when I figure that out.

ShiVa Steps

ASSUMPTION: I am assuming you are capable of creating HUD elements and connecting them to your ShiVa code and are able to do basic ShiVa programming.

  • 1. Create a HUD element for a button and name it “SHARE”
  • 2. Connect the new button via CopyTagToRegister and SendEventToUser functions (I’m assuming you know how to do this. This tutorial is specifically focused on Hooks/Callbacks so I am staying on topic for now.)
  • 3. Create an empty handler inside of ShiVa called onMyTestCall and leave it empty
  • 4. Create a second empty handler inside of ShiVa called onMyTestCallWithVar ( sText ) and leave it empty. This handler takes a single argument of type String.
  • 5. From the click button event, when a user clicks the Share button, execute the following code in ShiVa (Paste the code below into the function that handles the Share button press):
    user.sendEvent ( this.getUser ( ), "MainAI", "onMyTestCall" )
    user.sendEvent ( this.getUser ( ), "MainAI", "onMyTestCallWithVar", "Test" )
    log.message ( "Button Pressed" )
  • 6. Save and run the program first. If you watch the log in ShiVa, you should see the message “Button Pressed” every time you press the button. If you can see this then you have things setup correctly.
  • 7. Save, Compile and Export the STK file as an STK specifically (Not C++ source or anything like that).
  • 8. Open up ShiVa UAT for the next steps

ShiVa Authoring Tool Steps

  • 1. Select Android from the list on the left and hit “Or get started now”
  • 2. In step 1 select the ShiVa file and icons you want
  • 3. Click on Step 2 above
  • 4. For Authoring Type select PROJECT
  • 5. Enter your reverse domain bundle identifier ex. com.gamescorpion.melinasconquest
  • 6. Enter your version code and version number
  • 7. Click on Step 3 above
  • 8. Make sure you select DEVELOPMENT for build type and then select the settings you want to add such as Network or OpenAL sound.
  • 9. Set your OUTPUT folder to C: (Without the backslash)
  • 10. Hit Build and after it finishes building you should see your outputted zip file (YOURAPP_Android.zip) in the C:

You are now done with the UAT steps and can now move onto Eclipse steps.

Eclipse Steps

ASSUMPTION: We assume this is a new app and has not been made in Eclipse yet. If it has you’ll have to do these steps from a fresh copy to understand it. We also assume that you have Eclipse setup already with ANT. If not, go to the wiki to learn how to set up Eclipse properly with ANT.

  • 1. Open up eclipse
  • 2. Click on File->New->Java Project
  • 3. Type in a Project Name and hit FINISH
  • 4. In your Package Explorer window right click on your project and hit IMPORT
  • 5. Select “Archive File” under General and hit Next
  • 6. Click Browse and find the zip file we made in the previous step (Should be located at C:\\YOURAPP_Android.zip)
  • 7. Hit FINISH when done and it will import all the files
  • 8. Once imported, inside of your Package Explorer window click on the ARROW to the left of the folder to show the hierarchy of files. You should see a folder called “jni” so right click on the folder “jni” and hit IMPORT
  • 9. Select “File System” under General and hit Next
  • 10. Click Browse and Locate the S3DX directory in your system (Should be located at: C:\\Program Files(x86)\\Stonetrip\\Shiva Authoring Tool\\Data\\Windows\\Android\\Build\\S3DX)
  • 11. Click ‘Select All’ to make sure all the files from S3DX are selected for import and then hit the Finish button.

C++ and JAVA Hook/Callback

  • 1. Now in the Project Explorer under the jni folder (The folder where you just imported the files to) right click on the “S3DClient.cpp” file and click on Open With->Text Editor. You should now see the C++ source file in your window. (We will call this the C++ FILE for the rest of the tutorial)
  • 2. Now go back to the Project Explorer window and press the arrow on the left of the ‘src’ folder then again press the arrow next to the ‘com.yourcompany.yourapp’ folder and then double click on the file yourappname.java. You should now also see yourappname.java loaded into the main window. (We will call this the JAVA FILE for the rest of the tutorial)

C++ file

  • 1. At the top of the file AFTER “#include “S3DClient_Wrapper.h” (Line 11) add the following under the includes section:
    #include "S3DXAIVariable.h"
  • 2. If you have any CUSTOM GLOBAL variables you would like to use in the app you can add them to the “// @@BEGIN_JNI_GLOBALS@@” section on line 20. (Ex. static char YOURSTRING [255] = “” 😉
  • 3. Now for the juicy bit, on line 64 after you see the text “// @@BEGIN_JNI_CALLBACKS@@” is where we WILL add our C++ version of the callback code however before we do that we need to first navigate lower in the file to add our HOOKS. I am just writing this step in to draw reference to the location in the file so you can easily find the spot later again.
  • 4. Go down to approx. Line 624 where it says “// @@BEGIN_JNI_INSTALL_EVENT_HOOKS@@” and below the commented field you are going to now include two hooks by copying and pasting the code below:
    S3DClient_InstallCurrentUserEventHook ( "MainAI", "onMyTestCall", fun_CallCPP, NULL );
    S3DClient_InstallCurrentUserEventHook ( "MainAI", "onMyTestCallWithVar", fun_CallWithVarCPP, NULL );

    The above code basically says: WHEN “onMyTestCall” in the “MainAI” AI is called, re-route the call to the C++ function called “fun_CallCPP”.
    Same idea for the second line in the code above: WHEN “onMyTestCallWithVar” in the “MainAI” AI is called, re-route the call to the C++ function called “fun_CallWithVarCPP”

  • 5. Now that we have setup our hooks, we now need to setup the functions that those hooks call in C++. Thats where we must go back to the line in step 3 above and we will write 2 functions, one for each event hook. We have two events here to demonstrate how to have a basic event hook WITHOUT variables, and then a second hook to demonstrate how to pass variables from ShiVa (Such as an email address or a filename). Go back to line 61 and below the line ” // @@BEGIN_JNI_CALLBACKS@@” add the following functions:
  • void fun_CallCPP ( unsigned char _iArgumentCount, const void *_pArguments, void *_pUserData )
    {
        if ( pJavaVM )
            {
            JNIEnv *pEnv ;
            if    ( pJavaVM->GetEnv ( (void**) &pEnv, JNI_VERSION_1_4 ) >= 0 )
                {
                //USE REVERSE DOMAIN USING '/' instead of '.' ex. com/gamescorpion/intimatefireplace/IntimateFireplace
                //NOTE: THIS IS CASE SENSITIVE SO MAKE SURE YOU DO IT RIGHT!
                jclass pClass  = pEnv->FindClass ( "com/yourapp/yourapp/YourApp" );
                if   ( pClass != 0 )
                     {
                    //(ARGS)ReturnType -> (Z)I = (boolean)Function_Returns_Int
                    //(ARGS)ReturnType -> (I)V = (Int)Function_Returns_Void
                    //Google GetStaticMethodID for more return type examples
                    jmethodID pMethod = pEnv->GetStaticMethodID ( pClass, "MyTestCallJAVA", "()I");
                    if      ( pMethod )
                        {
                        int dummy = pEnv->CallStaticIntMethod ( pClass, pMethod ) ;
                        }
                    }
                }
            }
    }

    Ok, so right now that is a SINGLE function, we still have one more to write which takes in arguments, but I wanted to break it down because many may be lost as to what this is.
    Keep in mind when doing this that there is C++ code and JAVA code you are mixing. DONT MAKE THE MISTAKE OF USING JAVA CODE IN THE C++ FILE AND VICE VERSA! Your program will end up compiling perfectly and will even run, but when you come to that specific function hook/callback in your app, your app will CRASH!

    How does it work?

    void fun_CallCPP ( unsigned char _iArgumentCount, const void *_pArguments, void *_pUserData )	{ }

    This function basically is taking 3 arguments in. EVERY SINGLE CALLBACK FUNCTION YOU MAKE MUST HAVE THESE 3 ARGUMENTS OR IT WILL NOT WORK! The first one, _iArgumentCount gets the TOTAL number of arguments in the function. In this first case however we do not have any arguments we are passing from ShiVa, so this should return a 0 value.
    Second is the *_pArguments that holds the actual value in the arguments.
    Third is the *_pUserData which I honestly have no clue what its used for at this time however it is required and must be placed here for the function to work.

    if ( pJavaVM )
        {
        JNIEnv *pEnv ;
        if    ( pJavaVM->GetEnv ( (void**) &pEnv, JNI_VERSION_1_4 ) >= 0 )
            {
    //USE REVERSE DOMAIN USING '/' instead of '.' ex. com/gamescorpion/intimatefireplace/IntimateFireplace //NOTE: THIS IS CASE SENSITIVE SO MAKE SURE YOU DO IT RIGHT!
            jclass pClass  = pEnv->FindClass ( "com/yourapp/yourapp/YourApp" );
            if   ( pClass != 0 )
                {
                //(ARGS)ReturnType -> (Z)I = (boolean)Function_Returns_Int
                //(ARGS)ReturnType -> (I)V = (Int)Function_Returns_Void
                //Google GetStaticMethodID for more return type examples
                jmethodID pMethod = pEnv->GetStaticMethodID ( pClass, "MyTestCallJAVA", "()I");
                if      ( pMethod )
                    {
                        int dummy = pEnv->CallStaticIntMethod ( pClass, pMethod ) ;
                    }
                }
            }
        }

    Ok so now we define the above which is important:
    We can skip down to the actual points which you will be editing any time you create a callback which I have bolded and underlined in the code. The first bolded and underlined line shows you the location of the Java file you will be referring to. This is where the function will LOOK for the JAVA version of the function we are going to call. Basically its saying, “WHERE DO I FIND THE JAVA FUNCTION THAT RELATES TO THIS CALLBACK?”.
    In this case its the REVERSE DOMAIN notation followed by the Java filename (ReverseDomain.Filename), however the delimiter is no longer a period (.) but a forward slash (/). EXAMPLE: com.gamescorpion.intimatefireplace.IntimateFireplace => com/gamescorpion/intimatefireplace/IntimateFireplace
    Please take note that this is CASE SENSITIVE. If you are unsure of the filename for the java file, then simply find it in the Project Explorer window on your left under the src->com.yourcompany.yourapp folder.
    The next bolded and underlined part is a bit tricky but it is a C++ method call. It is basically connecting and calling the JAVA function to pass variables to and receive any final input from. In this case we use Static methods to access the items, but there is a very important part at the end of the line and that is HOW the JAVA function is defined.

    jmethodID pMethod = pEnv->GetStaticMethodID ( pClass, "MyTestCallJAVA", "()I");

    The last part there which is highlighted is basically saying that the JAVA function takes NO arguments and RETURNS an integer type. Based on what you want you can customize this for your needs. EX. ( ) I = No arguments and Returns Integer Type. ( Z ) V = JAVA Function Takes in a BOOLEAN and returns VOID ( I ) I = JAVA Function Takes in an integer and returns an integer ( ZI ) I = JAVA Function Takes in a BOOLEAN and an integer and returns an integer ( Ljava/lang/String; ) I = JAVA Function Takes in a String (MAKE SURE IT STARTS WITH “L” AND ENDS WITH SEMI-COLON, THIS IS NOT A TYPO!) and returns an integer
    NOTE: For more on this function please google GetStaticMethodID or you can also look at this link which I found helpful -> [url]http://journals.ecs.soton.ac.uk/java/tutorial/native1.1/implementing/method.html[/url]
    OK So the last highlighted part that you will usually edit is the CallStaticIntMethod which basically is the code that executes the call to the JAVA function. This can be edited based on the input types you have specified earlier such as Z, I or any of the other input arguments.
    Currently there is NO arguments going in, but if for example we had a string variable going in it would look like this:

    int dummy = pEnv->CallStaticIntMethod ( pClass, pMethod, pEnv->NewStringUTF ( YOURSTRING ) ) ;

    And if you were taking in a boolean for example it would look like this:

    int dummy = pEnv->CallStaticIntMethod ( pClass, pMethod, true ) ;

    NOW we will paste below this function the second callback function that takes in a string argument:

    void fun_CallWithVarCPP ( unsigned char _iArgumentCount, const void *_pArguments, void *_pUserData )
        {
        if ( _pArguments && ( _iArgumentCount == 1 ) )
            {
            const S3DX::AIVariable *pVariables = (const S3DX::AIVariable *)_pArguments ;
            if ( pVariables[0].GetType ( ) == S3DX::AIVariable::eTypeString )
                {
                    strncpy ( YOURSTRING, pVariables[0].GetStringValue ( ), 254 ) ;
                }
            }
        if ( pJavaVM )
            {
            JNIEnv *pEnv ;
            if ( pJavaVM->GetEnv ( (void**) &pEnv, JNI_VERSION_1_4 ) >= 0 )
                {
                jclass pClass  = pEnv->FindClass ( "com/gamescorpion/funtime3dedesignereaster/FunTime3DeDesignerEaster" );
                if ( pClass != 0 )
                    {
                    jmethodID pMethod = pEnv->GetStaticMethodID ( pClass, "MyTestCallWithVarJAVA", "(Ljava/lang/String;)I");
                    if ( pMethod )
                        {
                        int dummy = pEnv->CallStaticIntMethod ( pClass, pMethod, pEnv->NewStringUTF ( YOURSTRING ) ) ;
                        }
                   }
               }
           }
    }

    Ok so in this second version you will notice a NEW set of code that is added (when compared to the other function above) which I have outlined here with bold.
    This time we are taking in an argument and so now the _iArgumentCount comes into play and it is used to figure out the argument number in this case and then obtain the actual data which we then place into a variable (Assuming you have DEFINED THE GLOBAL VARIABLE CALLED “YOURSTRING” IN THE EARLIER SECTION in step 2 above: 2. If you have any CUSTOM GLOBAL variables you would like to use in the app you can add them to the “// @@BEGIN_JNI_GLOBALS@@” section on line 20. (Ex. static char YOURSTRING [255] = “” ;))
    We then using the same method can now pass the variable over to the JAVA function in the next step.
    YAAY WE ARE DONE WITH THE C++ STEPS, NOW WE GO TO OUR JAVA FILE TO CREATE THE JAVA FUNCTIONS WHICH CONNECT AND COMPLETE THE CODE.

    Java Code

    Open the JAVA FILE now in the main view and now we must go down to line 496 approx. where we see the code:

       @Override
       public void onLowMemory ( ) { }

    Before this line we need to now add our new functions which we created in the cpp file earlier.
    Copy and paste the two functions below into the lines above the override for onLowMemory:

       public static int MyTestCallJAVA( )
       {
    // JAVA CODE HERE
       	return 0;
       }
       public static int MyTestCallWithVarJAVA( String sBody )
       {
    // JAVA CODE HERE
       	return 0;
       }

    So there you can now start coding your java commands that you want to take place every time the function is called. This now allows you FULL customization such as being able to do special things like sending an email with an attachment, posting to facebook/twitter or even other cool advanced things.
    COMPILE and test, it should all work fine depending on what Java code you write in.
    NOTE: in the java code you may require certain function calls. These can only happen with the oThis object which is defined. Here is an example of calling an Intent for email:

           // Where sBody is the location of an image file
       	final Intent emailIntent = new Intent(android.content.Intent.ACTION_SEND);
       	emailIntent.setType("image/jpg");
       	emailIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse( sBody ) );
       	oThis.startActivity(Intent.createChooser(emailIntent, "Share:"));

    CONGRATULATIONS, you now know how to create Event Hooks and Callbacks!




    Need more answers?

    • slackBanner