Skip to content

Commit

Permalink
Add basic Lua interoperability
Browse files Browse the repository at this point in the history
  • Loading branch information
samdze committed Nov 16, 2023
1 parent c76cdbc commit e4be6eb
Show file tree
Hide file tree
Showing 6 changed files with 243 additions and 5 deletions.
32 changes: 31 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion playdate.nimble
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
4 changes: 2 additions & 2 deletions src/playdate/api.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
3 changes: 2 additions & 1 deletion src/playdate/bindings/api.nim
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand Down
84 changes: 84 additions & 0 deletions src/playdate/bindings/lua.nim
Original file line number Diff line number Diff line change
@@ -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: [].}

123 changes: 123 additions & 0 deletions src/playdate/lua.nim
Original file line number Diff line number Diff line change
@@ -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)

0 comments on commit e4be6eb

Please sign in to comment.