Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Implementing Addon's for beginners!

First off a quick disclaimer - I'm still fairly new to iOS internals and so please take this code as a starting point and be aware that it MAY contain bugs. Feel free to comment, extend, advise as required but as Addon's are required to get iAd's, IAP, Analytics etc working I thought I'd document my experiments.

Note - the ultimate aim is to be able to implement IAP and Google Analytics (as discussed at http://www.raywenderlich.com/53459/google-analytics-ios) unless someone offers up a better solution.

Firstly here is an example program that we're going to use to test our simple Addon.

-- Addon Testbed
---[[

-- These stub functions will be replaced by their "real" counterparts in the exported xCode project
-- Addon functions are grouped via a table

local cb = nil

myFirstAddon = {
    extGetValue                = function()    return "INTERNAL" end,
    extSendOneParamToDouble    = function(a)   return a end,
    extSendTwoParamsToMultiply = function(a,b) return a+b end,
    extSetCountBase            = function(c)   print("setCountBase "..c); cb = 9999 end,
    extIncCount                = function ()   cb = cb-1 end,
    extGetCount                = function ()   return cb end,
}
--]]

-- Use this function to perform your initial setup
function setup()
    print("AddonTestbed")
    print("getExtValue = "..myFirstAddon.extGetValue())
    print("Internal 2 doubled = "..2*2)
    print("sendOneParamToDouble(2) = "..myFirstAddon.extSendOneParamToDouble(2))
    print("sendTwoParamsToMultiply(3,4) = "..myFirstAddon.extSendTwoParamsToMultiply(3,4))
    print("setCountBase(0)")
    myFirstAddon.extSetCountBase(0)
    print("getCount = "..myFirstAddon.extGetCount())
end

-- This function gets called once every frame
function draw()
    -- This sets a dark background color 
    background(40, 40, 50)

    -- This sets the line thickness
    strokeWidth(5)

    -- Do your drawing here
    textMode(CORNER)
    fontSize(20)
    text("extGetCount = "..myFirstAddon.extGetCount(),40,400)
    myFirstAddon.extIncCount()
end

Paste this into a new Codea project called Addon Testbed and run it to make sure all is ok. Note the use of the ---[[ at the top - this is so I can block out the code when it's run via xCode.

What I've discovered is that the best (only?) way to access functions is to wrap them up inside a table and then the table get's registered in your project later on.

Assuming you've got all this working then export Addon Testbed as an xCode project (long press on the project tile) and just accept the defaults.
(see you in part 2)

Comments

  • edited January 2015 Posts: 688

    Ok Part 2.
    Open the xCode project and inside the Addons folder create two files (a blank header file called MyFirstAddon.h and a blank objective-C class called MyFirstAddon.m) - being sure to add them to the AddonTestbed target).

    Copy the following code into their respective files...

    MyFirstAddon.h

    #ifndef AddonTestbed_MyFirstAddon_h
    #define AddonTestbed_MyFirstAddon_h
    
    #import <Foundation/Foundation.h>
    
    #import "CodeaAddon.h"
    
    @interface MyFirstAddon : CodeaAddon        // Addon extends the base CodeaAddon class
    
    + (instancetype) sharedInstance;
    
    @end
    
    #endif
    

    MyFirstAddon.m

    //
    //  MyFirstAddon.m
    //  AddonTestbed
    //
    
    #import <Foundation/Foundation.h>
    #import "MyFirstAddon.h"
    
    #import "StandaloneCodeaViewController.h"
    
    #import "lua.h"
    #import "lauxlib.h"
    
    //----------------------------------------------------------------------------------
    // Prototypes for the exported functions...
    
    #define MYFIRSTADDON_LIB_NAME "myFirstAddon"
    
    #pragma mark - Lua Functions
    
    static int myFirstAddon_extGetValue(struct lua_State* L);
    static int myFirstAddon_extGetTableOfXAndY(struct lua_State* L);
    static int myFirstAddon_extSendOneParamToDouble(struct lua_State* L);
    static int myFirstAddon_extSendTwoParamsToMultiply(struct lua_State* L);
    static int myFirstAddon_extSetCountBase(struct lua_State* L);
    static int myFirstAddon_extIncCount(struct lua_State* L);
    static int myFirstAddon_extGetCount(struct lua_State* L);
    
    //----------------------------------------------------------------------------------
    // Map each of the C functions to their Codea equivalents...
    
    #pragma mark - Lua Function Mappings
    
    static const luaL_Reg myFirstAddonLibs[] =
    {
        // Name as it's known in Codea, C function to actually perform the function
        {"extGetValue",                 myFirstAddon_extGetValue},
        {"extSendOneParamToDouble",     myFirstAddon_extSendOneParamToDouble},
        {"extSendTwoParamsToMultiply",  myFirstAddon_extSendTwoParamsToMultiply},
        {"extSetCountBase",             myFirstAddon_extSetCountBase},
        {"extIncCount",                 myFirstAddon_extIncCount},
        {"extGetCount",                 myFirstAddon_extGetCount},
        {NULL, NULL}                    // Terminate the list!
    };
    
    //----------------------------------------------------------------------------------
    // Objective C code to interface with the Codea runtime...
    
    #pragma mark - My First Addon
    
    @interface MyFirstAddon ()  // <GKGameCenterControllerDelegate>
    
    @property (nonatomic, weak) StandaloneCodeaViewController *currentCodeaController;
    
    @end
    
    
    //----------------------------------------------------------------------------------
    // Actual implementation of the above...
    
    @implementation MyFirstAddon
    
    #pragma mark - Singleton
    
    + (instancetype) sharedInstance
    {
        static id _sharedObject = nil;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            _sharedObject = [[self alloc] init];
        });
        return _sharedObject;
    }
    
    //----------------------------------------------------------------------------------
    
    #pragma mark - Codea Addon Protocol Implementation
    
    - (void) codeaDidRegisterAddon:(StandaloneCodeaViewController *)controller
    {
        NSLog(@"Registered MyFirstAddon Addon");
    }
    
    - (void) codea:(StandaloneCodeaViewController*)codeaController didCreateLuaState:(struct lua_State*)L
    {
        NSLog(@"didCreateLuaState");
        // Register Library functions with Lua
        luaL_openlib(L, MYFIRSTADDON_LIB_NAME, myFirstAddonLibs, 0);
    }
    
    
    //----------------------------------------------------------------------------------
    // C Code to actually perform the required functions...
    
    
    #pragma mark - Lua Function Implementations
    
    static int myFirstAddon_extGetValue(struct lua_State* L)
    {
        NSLog(@"myFirstAddon_extGetValue"); // Return a fixed value
        lua_pushfstring(L, "HELLO FROM XCODE!");
    
        return 1;   // Return value is the number of results
    }
    
    static int myFirstAddon_extSendOneParamToDouble(struct lua_State* L)
    {
        NSLog(@"myFirstAddon_extSendOneParamToDouble"); // return $p1 * 2
        int n = lua_gettop(L);      // return the number of arguments on the stack
    
        NSLog(@"Found %d args",n);
    
        if( n > 0 )
        {
            int val = lua_tointeger(L,1) * 2;   // read the value off the stack (should really check type)
            NSLog(@"val = %d",val);
            lua_pushinteger(L,val);             // push the return value on to the stack
            return 1;
        }
    
        return 0;
    }
    
    static int myFirstAddon_extSendTwoParamsToMultiply(struct lua_State* L)
    {
        NSLog(@"myFirstAddon_extSendTwoParamsToMultiply"); // return $p1 * $p2
        int n = lua_gettop(L);
    
        NSLog(@"Found %d args",n);
    
        if( n > 1 )
        {
            int val = lua_tointeger(L,1) * lua_tointeger(L,2);
            NSLog(@"val = %d",val);
            lua_pushinteger(L,val);
            return 1;
        }
    
        return 0;
    }
    
    static int localBase = -456;
    
    static int myFirstAddon_extSetCountBase(struct lua_State* L)
    {
        NSLog(@"myFirstAddon_extSetCountBase"); // set internal value to $p1
        int n = lua_gettop(L);
    
        if(n > 0)
        {
            localBase = lua_tointeger(L,1);
        }
        return 0;
    }
    
    static int myFirstAddon_extIncCount(struct lua_State* L)
    {
        // NSLog(@"myFirstAddon_extIncCount"); // inc internal value
        localBase += 1;
        return 0;
    }
    
    static int myFirstAddon_extGetCount(struct lua_State* L)
    {
        // NSLog(@"myFirstAddon_extGetCount"); // return external value
        lua_pushinteger(L,localBase);
        return 1;
    }
    
    @end
    
    

    Also don't forget to comment out the simulated functions at the top - in fact I'd suggest running the code now to make sure the "myFirstAddon" table is reported as being nil.

  • edited January 2015 Posts: 688

    Part 3.
    Hopefully the above code should be fairly self explanatory - the key thing to get is that firstly there is a C function that maps to each of the lua functions in the table, the "myFirstAddonLibs" table is a list of entries mapping the lua function name to a pointer to it's matching C function. Two important points here, firstly the function parameter list is the same for each one - a pointer to the lua stack structure that's used to send and receive values and the fact that the list is terminated by two NULL entries otherwise the register function will fall off the end of the list.

    I'm not going to document each of the functions as the code is already commented - other that to say that the return value is the number of values that are returned (0 will cause the function to return nil back to lua) and the function lua_gettop() wil return the number of arguments passed so you can do some basic error checking.

    As this is just a simple example (and I know what the params and return values should be) I've not bothered to check the type of the arguments and for the numeric ones I've assumed I've passed int's - you can extend this as much as you need - obviously code meant for public distribution should be a lot more failure tolerant than this... :)

    To actually get the two sides to talk to each other you need to look at the implementation of the MyFirstAddon class which is a subclass of the CodeaAddon class that @Simeon has provided. The CodeaAddon class will at some point call the "didCreateLuaState" method and it's in here that the

    luaL_openlib(L, MYFIRSTADDON_LIB_NAME, myFirstAddonLibs, 0);
    

    function is called - which basically makes the methods available in the specified table to your code (again note the naming conventions - the _LIB_NAME macro (parameter 2) is the name of the table in your lua file and the table functions are the first entry in the list of values passed in as parameter 3.

    (I MIGHT BE WRONG AT THIS POINT) The singleton sharedInstance is a reference to CodeaAddon class so that any / all instances are all tied together so it's possible to have many different addon's (GameCentre, iAD, IAP, Analytics etc) all working together
    (FEEL FREE TO COMMENT ON THIS IF I'M NOT QUITE RIGHT AND I'LL EDIT THIS BIT)

    Lastly - to get the addon working you need to edit the AppDelegate.mm file and send the Obj-C message that will actually call the register function - find the line that say's

    // Uncomment the following line...
    

    and add in the following...

        [self.viewController registerAddon:[MyFirstAddon sharedInstance]];
    

    Where MyFirstAddon is the name of your class.

    Also in here are the event handlers for when your app get's notified about important system events (like being sent to the background or being made active again) - it would be nice to be able to hook into these and maybe send a message through to Codea - but that's a topic for another day,

  • Posts: 688

    One last point - I did notice that for some reason things seem to get messed up if you press the restart button from within the codea app - I'm not sure why other than I noticed that the "willCloseLuaState" method is called at this point and seeing that in a published app the Codea overlay shouldn't be visible I don't think this is much of a problem.

    As I said - these are just my first experiments but hopefully they'll point others in the right direction and if I have made any mistakes or missed something out then please feel free to post here (or PM me) and I'll edit the above posts.

    Jon...

  • Jmv38Jmv38 Mod
    Posts: 3,295

    @techDojo not tried yet, but THANK SO MUCH for this tuto! ^:)^ ^:)^

  • @TechDojo Thanks for making this. Im a total noob at objective-c so most of this doesn't make much sense but once I gain a good grip, this should be pretty helpful.

    Can you please explain what the use of this is? Its lua code that you can call from the objective-c code?

  • Posts: 688

    The use of this is to allow you to write custom functions in C that (normally) interfaces to iOS functionality that isn't available in Codea. You can then call these Obj-C functions from within Codea to basically Add On extra functionality.

    So for things like being able to post scores to Game Center, being able to use iAd or in app purchase, being able to log usage data with Google analytics or to be able to provide extended share functionality, or bluetooth multiplayer etc etc etc - none of these are available directly within Codea, but they can be added in using the custom AddOn interface.

    Obviously these functions will only work properly when you've actually exported your project to xCode and added in the required libraries, but to allow you to continue to develop your game in Codea it's necessary to provide some fake stub functions that you can call and then return values to pretend that they've worked (or not depending on your testing), then when it's time to export the project you can just comment out the stubs and if you've done everything correct then the real functions will be used instead.

    So basically it's C / Objective-C code you can call from Lua not the other way round.

  • Good job! I will try it on my project soon!

  • Posts: 688

    Just a quick note - I've gone back to actually try and implement the Google Analytics addon as promised and I noticed that the above code is no longer valid for Codea 2.3 :(

    Specifically the bit that registers the functions

        // Register Library functions with Lua
        luaL_openlib(L, MYFIRSTADDON_LIB_NAME, myFirstAddonLibs, 0);
    

    needs to be replaced with the following...

        CODEA_ADDON_REGISTER(MYFIRSTADDON_LIB_NAME, luaopen_myFirstAddonLibs);
    

    and a new function added

    static int luaopen_myFirstAddonLibs(lua_State *L)
    {
        // Register functions with Lua
        lua_newtable(L);
        luaL_setfuncs(L, myFirstAddonLibs, 0);
    
        return 1;
    }
    

    The rest seems to be the same - however I'd look at the GameCentre example for more details - when I get the analytics addon working I'll make a more detailed post.

  • edited April 2015 Posts: 289

    marvy,i have no idea if its C code's running,editor could show C syntax?

  • Posts: 688

    The above is C code which is added to the exported Codea project

Sign In or Register to comment.