diff --git a/README.md b/README.md index 5ebd234..1be5eed 100644 --- a/README.md +++ b/README.md @@ -140,9 +140,39 @@ else: switch("passL", "-lchipmunk") ``` +## Interoperability with Lua + +Nim can be used together with Lua. +There are two ways you can use Nim and Lua in the same project: +1. The main loop is defined in Nim, but you want to call a few Lua functions. +2. The main loop is defined in Lua, but you want to call Nim functions. + +Either way, you can provide Lua with your Nim functions during Lua initialization: +```nim +proc nimInsideLua(state: LuaStatePtr): cint {.cdecl, raises: [].} = ... + +# Application entrypoint and event handler +proc handler(event: PDSystemEvent, keycode: uint) {.raises: [].} = + if event == kEventInitLua: # Lua initialization event + # Add a function `nimInsideLua` to the Lua environment + playdate.lua.addFunction(nimInsideLua, "nimInsideLua") + # If you want to use Nim to define the main loop, set the update callback + playdate.system.setUpdateCallback(update) +``` + +Calling a Lua function from Nim: +```nim +try: + # Push the argument first + playdate.lua.pushInt(5) + playdate.lua.callFunction("funcWithOneArgument", 1) +except: + playdate.system.logToConsole(getCurrentExceptionMsg()) +``` + --- This project is a work in progress, here's what is missing right now: - various playdate.sound funcionalities (but FilePlayer and SamplePlayer are available) - playdate.json, but you can use Nim std/json, which is very convenient -- playdate.lua, interfacing with Lua and providing classes/functions +- advanced playdate.lua features, but basic Lua interop is available - playdate.scoreboards, undocumented even in the official C API docs diff --git a/playdate.nimble b/playdate.nimble index d46a4e9..5564bef 100644 --- a/playdate.nimble +++ b/playdate.nimble @@ -1,6 +1,6 @@ # Package -version = "0.10.0" +version = "0.11.0" author = "Samuele Zolfanelli" description = "Playdate Nim bindings with extra features." license = "MIT" diff --git a/src/playdate/api.nim b/src/playdate/api.nim index d8fc949..984a644 100644 --- a/src/playdate/api.nim +++ b/src/playdate/api.nim @@ -7,8 +7,8 @@ import bindings/utils {.all.} as memory import bindings/api export api -import graphics, system, file, sprite, display, sound, json, utils, types -export graphics, system, file, sprite, display, sound, json, utils, types +import graphics, system, file, sprite, display, sound, lua, json, utils, types +export graphics, system, file, sprite, display, sound, lua, json, utils, types macro initSDK*() = return quote do: diff --git a/src/playdate/bindings/api.nim b/src/playdate/bindings/api.nim index 2b301c5..a86f0d1 100644 --- a/src/playdate/bindings/api.nim +++ b/src/playdate/bindings/api.nim @@ -1,6 +1,6 @@ {.push raises: [].} -import graphics, system, file, display, sprite, sound +import graphics, system, file, display, sprite, sound, lua type PlaydateAPI* {.importc: "PlaydateAPI", header: "pd_api.h".} = object system* {.importc: "system".}: ptr PlaydateSys @@ -9,6 +9,7 @@ type PlaydateAPI* {.importc: "PlaydateAPI", header: "pd_api.h".} = object sprite* {.importc: "sprite".}: ptr PlaydateSprite display* {.importc: "display".}: ptr PlaydateDisplay sound* {.importc: "sound".}: ptr PlaydateSound + lua* {.importc: "lua".}: ptr PlaydateLua # json* {.importc: "json".}: ptr PlaydateJSON # Unavailable, use std/json type PDSystemEvent* {.importc: "PDSystemEvent", header: "pd_api.h".} = enum diff --git a/src/playdate/bindings/lua.nim b/src/playdate/bindings/lua.nim new file mode 100644 index 0000000..4355421 --- /dev/null +++ b/src/playdate/bindings/lua.nim @@ -0,0 +1,84 @@ +{.push raises: [].} + +import sprite {.all.} +import types + +type + LuaStatePtr* = pointer + LuaNimFunction* = proc (L: LuaStatePtr): cint {.cdecl, raises: [].} + LuaUDObject* {.importc: "LuaUDObject", header: "pd_api.h", bycopy.} = object + + LValType* {.importc: "l_valtype", header: "pd_api.h".} = enum + kInt, kFloat, kStr + + # LuaReg* {.importc: "lua_reg", header: "pd_api.h", bycopy.} = object + # name* {.importc: "name".}: cstring + # `func`* {.importc: "func".}: LuaNimFunction + + # LuaType* {.size: sizeof(cint).} = enum + # kTypeNil, kTypeBool, kTypeInt, kTypeFloat, kTypeString, kTypeTable, + # kTypeFunction, + # kTypeThread, kTypeObject + + LuaType* {.importc: "enum LuaType", header: "pd_api.h", bycopy.} = enum + kTypeNil, kTypeBool, kTypeInt, kTypeFloat, kTypeString, kTypeTable, + kTypeFunction, + kTypeThread, kTypeObject + + +type + # INNER_C_UNION_pd_api_lua_1* {.importc: "lua_val::no_name", + # header: "pd_api.h", bycopy, union.} = object + # intval* {.importc: "intval".}: cuint + # floatval* {.importc: "floatval".}: cfloat + # strval* {.importc: "strval".}: cstring + + # LuaVal* {.importc: "lua_val", header: "pd_api.h", bycopy.} = object + # name* {.importc: "name".}: cstring + # `type`* {.importc: "type".}: LValType + # v* {.importc: "v".}: INNER_C_UNION_pd_api_lua_1 + + PlaydateLua* {.importc: "const struct playdate_lua", header: "pd_api.h", + bycopy.} = object + ## these two return 1 on success, else 0 with an error message in outErr + addFunction {.importc: "addFunction".}: proc (f: LuaNimFunction; + name: cstring; outErr: ptr cstring): cint {.cdecl, raises: [].} + # registerClass {.importc: "registerClass".}: proc (name: cstring; + # reg: ptr LuaReg; vals: ptr LuaVal; + # isstatic: cint; outErr: ptr cstring): cint {.cdecl.} + pushFunction* {.importc: "pushFunction".}: proc (f: LuaNimFunction) {.cdecl.} + indexMetatable {.importc: "indexMetatable".}: proc (): cint {.cdecl.} + stop* {.importc: "stop".}: proc () {.cdecl, raises: [].} + start* {.importc: "start".}: proc () {.cdecl, raises: [].} + ## stack operations + getArgCount {.importc: "getArgCount".}: proc (): cint {.cdecl, raises: [].} + getArgType {.importc: "getArgType".}: proc (pos: cint; outClass: ptr cstring): LuaType {.cdecl, raises: [].} + argIsNil {.importc: "argIsNil".}: proc (pos: cint): cint {.cdecl, raises: [].} + getArgBool {.importc: "getArgBool".}: proc (pos: cint): cint {.cdecl, raises: [].} + getArgInt {.importc: "getArgInt".}: proc (pos: cint): cint {.cdecl, raises: [].} + getArgFloat {.importc: "getArgFloat".}: proc (pos: cint): cfloat {.cdecl, raises: [].} + getArgString {.importc: "getArgString".}: proc (pos: cint): cstring {.cdecl, raises: [].} + getArgBytes {.importc: "getArgBytes".}: proc (pos: cint; outlen: ptr csize_t): cstring {.cdecl, raises: [].} + getArgObject {.importc: "getArgObject".}: proc (pos: cint; `type`: cstring; outud: ptr ptr LuaUDObject): pointer {.cdecl.} + getBitmap {.importc: "getBitmap".}: proc (pos: cint): LCDBitmapPtr {.cdecl.} + getSprite {.importc: "getSprite".}: proc (pos: cint): LCDSpritePtr {.cdecl.} + ## for returning values back to Lua + pushNil* {.importc: "pushNil".}: proc () {.cdecl, raises: [].} + pushBool {.importc: "pushBool".}: proc (val: cint) {.cdecl, raises: [].} + pushInt {.importc: "pushInt".}: proc (val: cint) {.cdecl, raises: [].} + pushFloat {.importc: "pushFloat".}: proc (val: cfloat) {.cdecl, raises: [].} + pushString {.importc: "pushString".}: proc (str: cstring) {.cdecl, raises: [].} + pushBytes {.importc: "pushBytes".}: proc (str: cstring; len: csize_t) {.cdecl, raises: [].} + pushBitmap {.importc: "pushBitmap".}: proc (bitmap: LCDBitmapPtr) {.cdecl.} + pushSprite {.importc: "pushSprite".}: proc (sprite: LCDSpritePtr) {.cdecl.} + pushObject {.importc: "pushObject".}: proc (obj: pointer; `type`: cstring; nValues: cint): ptr LuaUDObject {.cdecl.} + retainObject {.importc: "retainObject".}: proc (obj: ptr LuaUDObject): ptr LuaUDObject {.cdecl.} + releaseObject {.importc: "releaseObject".}: proc (obj: ptr LuaUDObject) {.cdecl.} + setUserValue {.importc: "setUserValue".}: proc (obj: ptr LuaUDObject; slot: cuint) {.cdecl.} + ## sets item on top of stack and pops it + getUserValue {.importc: "getUserValue".}: proc (obj: ptr LuaUDObject; slot: cuint): cint {.cdecl.} + ## pushes item at slot to top of stack, returns stack position + ## calling lua from C has some overhead. use sparingly! + callFunction {.importc: "callFunction".}: proc (name: cstring; nargs: cint; + outerr: ptr cstring): cint {.cdecl, raises: [].} + diff --git a/src/playdate/lua.nim b/src/playdate/lua.nim new file mode 100644 index 0000000..332cb3f --- /dev/null +++ b/src/playdate/lua.nim @@ -0,0 +1,123 @@ +{.push raises: [].} + +import std/importutils + +import bindings/[api, system] +import bindings/[types] +import bindings/lua + +# Only export public symbols, then import all +export lua +{.hint[DuplicateModuleImport]: off.} +import bindings/lua {.all.} + +type LuaError* = object of CatchableError + +proc addFunction*(this: ptr PlaydateLua, function: LuaNimFunction, name: string) {.raises: [LuaError]} = + privateAccess(PlaydateLua) + var err: ConstChar = nil + var success = this.addFunction(function, name.cstring, addr(err)) + if success == 0: + raise newException(LuaError, $err) + +# registerClass + +# indexMetatable + +proc getArgCount*(this: ptr PlaydateLua): int = + privateAccess(PlaydateLua) + return this.getArgCount().int + +proc getArgType*(this: ptr PlaydateLua, position: int): LuaType {.raises: [LuaError]} = + privateAccess(PlaydateLua) + if position < 1 or position > this.getArgCount(): + raise newException(LuaError, "Invalid argument index " & $position & ".") + var cls: ConstChar = nil + return this.getArgType(position.cint, addr(cls)) + +proc getArgClass*(this: ptr PlaydateLua, position: int): string {.raises: [LuaError]} = + privateAccess(PlaydateLua) + if position < 1 or position > this.getArgCount(): + raise newException(LuaError, "Invalid argument index " & $position & ".") + var cls: ConstChar = nil + discard this.getArgType(position.cint, addr(cls)) + return $cls + +proc argIsNil*(this: ptr PlaydateLua, position: int): bool {.raises: [LuaError]} = + privateAccess(PlaydateLua) + if position < 1 or position > this.getArgCount(): + raise newException(LuaError, "Invalid argument index " & $position & ".") + return this.argIsNil(position.cint) > 0 + +proc getArgBool*(this: ptr PlaydateLua, position: int): bool {.raises: [LuaError]} = + privateAccess(PlaydateLua) + if position < 1 or position > this.getArgCount(): + raise newException(LuaError, "Invalid argument index " & $position & ".") + return this.getArgBool(position.cint) > 0 + +proc getArgFloat*(this: ptr PlaydateLua, position: int): float {.raises: [LuaError]} = + privateAccess(PlaydateLua) + if position < 1 or position > this.getArgCount(): + raise newException(LuaError, "Invalid argument index " & $position & ".") + return this.getArgFloat(position.cint).float + +proc getArgInt*(this: ptr PlaydateLua, position: int): int {.raises: [LuaError]} = + privateAccess(PlaydateLua) + if position < 1 or position > this.getArgCount(): + raise newException(LuaError, "Invalid argument index " & $position & ".") + return this.getArgInt(position.cint).int + +proc getArgString*(this: ptr PlaydateLua, position: int): string {.raises: [LuaError]} = + privateAccess(PlaydateLua) + if position < 1 or position > this.getArgCount(): + raise newException(LuaError, "Invalid argument index " & $position & ".") + return $this.getArgString(position.cint) + +# getArgBytes + +# getArgObject + +# getBitmap + +# getSprite + +proc pushBool*(this: ptr PlaydateLua, value: bool) = + privateAccess(PlaydateLua) + this.pushBool(if value: 1 else: 0) + +proc pushInt*(this: ptr PlaydateLua, value: int) = + privateAccess(PlaydateLua) + this.pushInt(value.cint) + +proc pushFloat*(this: ptr PlaydateLua, value: float) = + privateAccess(PlaydateLua) + this.pushFloat(value.cfloat) + +proc pushString*(this: ptr PlaydateLua, value: string) = + privateAccess(PlaydateLua) + this.pushString(value.cstring) + +# pushBytes + +# pushBitmap + +# pushSprite + +# pushObject + +# retainObject + +# releaseObject + +# setUserValue + +# getUserValue + +proc callFunction*(this: ptr PlaydateLua, name: string, argsCount: int = 0) {.raises: [LuaError]} = + privateAccess(PlaydateLua) + privateAccess(PlaydateSys) + var err: ConstChar = nil + var success = this.callFunction(name.cstring, argsCount.cint, addr(err)) + if success == 0: + playdate.system.logToConsole(err) + raise newException(LuaError, $err) \ No newline at end of file