Skip to content

Commit e4be6eb

Browse files
committed
Add basic Lua interoperability
1 parent c76cdbc commit e4be6eb

6 files changed

Lines changed: 243 additions & 5 deletions

File tree

README.md

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,9 +140,39 @@ else:
140140
switch("passL", "-lchipmunk")
141141
```
142142

143+
## Interoperability with Lua
144+
145+
Nim can be used together with Lua.
146+
There are two ways you can use Nim and Lua in the same project:
147+
1. The main loop is defined in Nim, but you want to call a few Lua functions.
148+
2. The main loop is defined in Lua, but you want to call Nim functions.
149+
150+
Either way, you can provide Lua with your Nim functions during Lua initialization:
151+
```nim
152+
proc nimInsideLua(state: LuaStatePtr): cint {.cdecl, raises: [].} = ...
153+
154+
# Application entrypoint and event handler
155+
proc handler(event: PDSystemEvent, keycode: uint) {.raises: [].} =
156+
if event == kEventInitLua: # Lua initialization event
157+
# Add a function `nimInsideLua` to the Lua environment
158+
playdate.lua.addFunction(nimInsideLua, "nimInsideLua")
159+
# If you want to use Nim to define the main loop, set the update callback
160+
playdate.system.setUpdateCallback(update)
161+
```
162+
163+
Calling a Lua function from Nim:
164+
```nim
165+
try:
166+
# Push the argument first
167+
playdate.lua.pushInt(5)
168+
playdate.lua.callFunction("funcWithOneArgument", 1)
169+
except:
170+
playdate.system.logToConsole(getCurrentExceptionMsg())
171+
```
172+
143173
---
144174
This project is a work in progress, here's what is missing right now:
145175
- various playdate.sound funcionalities (but FilePlayer and SamplePlayer are available)
146176
- playdate.json, but you can use Nim std/json, which is very convenient
147-
- playdate.lua, interfacing with Lua and providing classes/functions
177+
- advanced playdate.lua features, but basic Lua interop is available
148178
- playdate.scoreboards, undocumented even in the official C API docs

playdate.nimble

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Package
22

3-
version = "0.10.0"
3+
version = "0.11.0"
44
author = "Samuele Zolfanelli"
55
description = "Playdate Nim bindings with extra features."
66
license = "MIT"

src/playdate/api.nim

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import bindings/utils {.all.} as memory
77
import bindings/api
88
export api
99

10-
import graphics, system, file, sprite, display, sound, json, utils, types
11-
export graphics, system, file, sprite, display, sound, json, utils, types
10+
import graphics, system, file, sprite, display, sound, lua, json, utils, types
11+
export graphics, system, file, sprite, display, sound, lua, json, utils, types
1212

1313
macro initSDK*() =
1414
return quote do:

src/playdate/bindings/api.nim

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{.push raises: [].}
22

3-
import graphics, system, file, display, sprite, sound
3+
import graphics, system, file, display, sprite, sound, lua
44

55
type PlaydateAPI* {.importc: "PlaydateAPI", header: "pd_api.h".} = object
66
system* {.importc: "system".}: ptr PlaydateSys
@@ -9,6 +9,7 @@ type PlaydateAPI* {.importc: "PlaydateAPI", header: "pd_api.h".} = object
99
sprite* {.importc: "sprite".}: ptr PlaydateSprite
1010
display* {.importc: "display".}: ptr PlaydateDisplay
1111
sound* {.importc: "sound".}: ptr PlaydateSound
12+
lua* {.importc: "lua".}: ptr PlaydateLua
1213
# json* {.importc: "json".}: ptr PlaydateJSON # Unavailable, use std/json
1314

1415
type PDSystemEvent* {.importc: "PDSystemEvent", header: "pd_api.h".} = enum

src/playdate/bindings/lua.nim

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
{.push raises: [].}
2+
3+
import sprite {.all.}
4+
import types
5+
6+
type
7+
LuaStatePtr* = pointer
8+
LuaNimFunction* = proc (L: LuaStatePtr): cint {.cdecl, raises: [].}
9+
LuaUDObject* {.importc: "LuaUDObject", header: "pd_api.h", bycopy.} = object
10+
11+
LValType* {.importc: "l_valtype", header: "pd_api.h".} = enum
12+
kInt, kFloat, kStr
13+
14+
# LuaReg* {.importc: "lua_reg", header: "pd_api.h", bycopy.} = object
15+
# name* {.importc: "name".}: cstring
16+
# `func`* {.importc: "func".}: LuaNimFunction
17+
18+
# LuaType* {.size: sizeof(cint).} = enum
19+
# kTypeNil, kTypeBool, kTypeInt, kTypeFloat, kTypeString, kTypeTable,
20+
# kTypeFunction,
21+
# kTypeThread, kTypeObject
22+
23+
LuaType* {.importc: "enum LuaType", header: "pd_api.h", bycopy.} = enum
24+
kTypeNil, kTypeBool, kTypeInt, kTypeFloat, kTypeString, kTypeTable,
25+
kTypeFunction,
26+
kTypeThread, kTypeObject
27+
28+
29+
type
30+
# INNER_C_UNION_pd_api_lua_1* {.importc: "lua_val::no_name",
31+
# header: "pd_api.h", bycopy, union.} = object
32+
# intval* {.importc: "intval".}: cuint
33+
# floatval* {.importc: "floatval".}: cfloat
34+
# strval* {.importc: "strval".}: cstring
35+
36+
# LuaVal* {.importc: "lua_val", header: "pd_api.h", bycopy.} = object
37+
# name* {.importc: "name".}: cstring
38+
# `type`* {.importc: "type".}: LValType
39+
# v* {.importc: "v".}: INNER_C_UNION_pd_api_lua_1
40+
41+
PlaydateLua* {.importc: "const struct playdate_lua", header: "pd_api.h",
42+
bycopy.} = object
43+
## these two return 1 on success, else 0 with an error message in outErr
44+
addFunction {.importc: "addFunction".}: proc (f: LuaNimFunction;
45+
name: cstring; outErr: ptr cstring): cint {.cdecl, raises: [].}
46+
# registerClass {.importc: "registerClass".}: proc (name: cstring;
47+
# reg: ptr LuaReg; vals: ptr LuaVal;
48+
# isstatic: cint; outErr: ptr cstring): cint {.cdecl.}
49+
pushFunction* {.importc: "pushFunction".}: proc (f: LuaNimFunction) {.cdecl.}
50+
indexMetatable {.importc: "indexMetatable".}: proc (): cint {.cdecl.}
51+
stop* {.importc: "stop".}: proc () {.cdecl, raises: [].}
52+
start* {.importc: "start".}: proc () {.cdecl, raises: [].}
53+
## stack operations
54+
getArgCount {.importc: "getArgCount".}: proc (): cint {.cdecl, raises: [].}
55+
getArgType {.importc: "getArgType".}: proc (pos: cint; outClass: ptr cstring): LuaType {.cdecl, raises: [].}
56+
argIsNil {.importc: "argIsNil".}: proc (pos: cint): cint {.cdecl, raises: [].}
57+
getArgBool {.importc: "getArgBool".}: proc (pos: cint): cint {.cdecl, raises: [].}
58+
getArgInt {.importc: "getArgInt".}: proc (pos: cint): cint {.cdecl, raises: [].}
59+
getArgFloat {.importc: "getArgFloat".}: proc (pos: cint): cfloat {.cdecl, raises: [].}
60+
getArgString {.importc: "getArgString".}: proc (pos: cint): cstring {.cdecl, raises: [].}
61+
getArgBytes {.importc: "getArgBytes".}: proc (pos: cint; outlen: ptr csize_t): cstring {.cdecl, raises: [].}
62+
getArgObject {.importc: "getArgObject".}: proc (pos: cint; `type`: cstring; outud: ptr ptr LuaUDObject): pointer {.cdecl.}
63+
getBitmap {.importc: "getBitmap".}: proc (pos: cint): LCDBitmapPtr {.cdecl.}
64+
getSprite {.importc: "getSprite".}: proc (pos: cint): LCDSpritePtr {.cdecl.}
65+
## for returning values back to Lua
66+
pushNil* {.importc: "pushNil".}: proc () {.cdecl, raises: [].}
67+
pushBool {.importc: "pushBool".}: proc (val: cint) {.cdecl, raises: [].}
68+
pushInt {.importc: "pushInt".}: proc (val: cint) {.cdecl, raises: [].}
69+
pushFloat {.importc: "pushFloat".}: proc (val: cfloat) {.cdecl, raises: [].}
70+
pushString {.importc: "pushString".}: proc (str: cstring) {.cdecl, raises: [].}
71+
pushBytes {.importc: "pushBytes".}: proc (str: cstring; len: csize_t) {.cdecl, raises: [].}
72+
pushBitmap {.importc: "pushBitmap".}: proc (bitmap: LCDBitmapPtr) {.cdecl.}
73+
pushSprite {.importc: "pushSprite".}: proc (sprite: LCDSpritePtr) {.cdecl.}
74+
pushObject {.importc: "pushObject".}: proc (obj: pointer; `type`: cstring; nValues: cint): ptr LuaUDObject {.cdecl.}
75+
retainObject {.importc: "retainObject".}: proc (obj: ptr LuaUDObject): ptr LuaUDObject {.cdecl.}
76+
releaseObject {.importc: "releaseObject".}: proc (obj: ptr LuaUDObject) {.cdecl.}
77+
setUserValue {.importc: "setUserValue".}: proc (obj: ptr LuaUDObject; slot: cuint) {.cdecl.}
78+
## sets item on top of stack and pops it
79+
getUserValue {.importc: "getUserValue".}: proc (obj: ptr LuaUDObject; slot: cuint): cint {.cdecl.}
80+
## pushes item at slot to top of stack, returns stack position
81+
## calling lua from C has some overhead. use sparingly!
82+
callFunction {.importc: "callFunction".}: proc (name: cstring; nargs: cint;
83+
outerr: ptr cstring): cint {.cdecl, raises: [].}
84+

src/playdate/lua.nim

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
{.push raises: [].}
2+
3+
import std/importutils
4+
5+
import bindings/[api, system]
6+
import bindings/[types]
7+
import bindings/lua
8+
9+
# Only export public symbols, then import all
10+
export lua
11+
{.hint[DuplicateModuleImport]: off.}
12+
import bindings/lua {.all.}
13+
14+
type LuaError* = object of CatchableError
15+
16+
proc addFunction*(this: ptr PlaydateLua, function: LuaNimFunction, name: string) {.raises: [LuaError]} =
17+
privateAccess(PlaydateLua)
18+
var err: ConstChar = nil
19+
var success = this.addFunction(function, name.cstring, addr(err))
20+
if success == 0:
21+
raise newException(LuaError, $err)
22+
23+
# registerClass
24+
25+
# indexMetatable
26+
27+
proc getArgCount*(this: ptr PlaydateLua): int =
28+
privateAccess(PlaydateLua)
29+
return this.getArgCount().int
30+
31+
proc getArgType*(this: ptr PlaydateLua, position: int): LuaType {.raises: [LuaError]} =
32+
privateAccess(PlaydateLua)
33+
if position < 1 or position > this.getArgCount():
34+
raise newException(LuaError, "Invalid argument index " & $position & ".")
35+
var cls: ConstChar = nil
36+
return this.getArgType(position.cint, addr(cls))
37+
38+
proc getArgClass*(this: ptr PlaydateLua, position: int): string {.raises: [LuaError]} =
39+
privateAccess(PlaydateLua)
40+
if position < 1 or position > this.getArgCount():
41+
raise newException(LuaError, "Invalid argument index " & $position & ".")
42+
var cls: ConstChar = nil
43+
discard this.getArgType(position.cint, addr(cls))
44+
return $cls
45+
46+
proc argIsNil*(this: ptr PlaydateLua, position: int): bool {.raises: [LuaError]} =
47+
privateAccess(PlaydateLua)
48+
if position < 1 or position > this.getArgCount():
49+
raise newException(LuaError, "Invalid argument index " & $position & ".")
50+
return this.argIsNil(position.cint) > 0
51+
52+
proc getArgBool*(this: ptr PlaydateLua, position: int): bool {.raises: [LuaError]} =
53+
privateAccess(PlaydateLua)
54+
if position < 1 or position > this.getArgCount():
55+
raise newException(LuaError, "Invalid argument index " & $position & ".")
56+
return this.getArgBool(position.cint) > 0
57+
58+
proc getArgFloat*(this: ptr PlaydateLua, position: int): float {.raises: [LuaError]} =
59+
privateAccess(PlaydateLua)
60+
if position < 1 or position > this.getArgCount():
61+
raise newException(LuaError, "Invalid argument index " & $position & ".")
62+
return this.getArgFloat(position.cint).float
63+
64+
proc getArgInt*(this: ptr PlaydateLua, position: int): int {.raises: [LuaError]} =
65+
privateAccess(PlaydateLua)
66+
if position < 1 or position > this.getArgCount():
67+
raise newException(LuaError, "Invalid argument index " & $position & ".")
68+
return this.getArgInt(position.cint).int
69+
70+
proc getArgString*(this: ptr PlaydateLua, position: int): string {.raises: [LuaError]} =
71+
privateAccess(PlaydateLua)
72+
if position < 1 or position > this.getArgCount():
73+
raise newException(LuaError, "Invalid argument index " & $position & ".")
74+
return $this.getArgString(position.cint)
75+
76+
# getArgBytes
77+
78+
# getArgObject
79+
80+
# getBitmap
81+
82+
# getSprite
83+
84+
proc pushBool*(this: ptr PlaydateLua, value: bool) =
85+
privateAccess(PlaydateLua)
86+
this.pushBool(if value: 1 else: 0)
87+
88+
proc pushInt*(this: ptr PlaydateLua, value: int) =
89+
privateAccess(PlaydateLua)
90+
this.pushInt(value.cint)
91+
92+
proc pushFloat*(this: ptr PlaydateLua, value: float) =
93+
privateAccess(PlaydateLua)
94+
this.pushFloat(value.cfloat)
95+
96+
proc pushString*(this: ptr PlaydateLua, value: string) =
97+
privateAccess(PlaydateLua)
98+
this.pushString(value.cstring)
99+
100+
# pushBytes
101+
102+
# pushBitmap
103+
104+
# pushSprite
105+
106+
# pushObject
107+
108+
# retainObject
109+
110+
# releaseObject
111+
112+
# setUserValue
113+
114+
# getUserValue
115+
116+
proc callFunction*(this: ptr PlaydateLua, name: string, argsCount: int = 0) {.raises: [LuaError]} =
117+
privateAccess(PlaydateLua)
118+
privateAccess(PlaydateSys)
119+
var err: ConstChar = nil
120+
var success = this.callFunction(name.cstring, argsCount.cint, addr(err))
121+
if success == 0:
122+
playdate.system.logToConsole(err)
123+
raise newException(LuaError, $err)

0 commit comments

Comments
 (0)