Initial commit

main
guilevi 2023-08-27 14:32:38 +02:00
commit a9fcca4246
42 changed files with 4972 additions and 0 deletions

BIN
.DS_Store vendored 100644

Binary file not shown.

1
.alsoftrc 100644
View File

@ -0,0 +1 @@
hrtf = false

3
.gitmodules vendored 100644
View File

@ -0,0 +1,3 @@
[submodule "love2talk"]
path = love2talk
url = https://github.com/pitermach/love2talk

27
Love2talk.lua 100644
View File

@ -0,0 +1,27 @@
local os=love.system.getOS()
if os =="Windows" then
backend=require "Tolk"
backend.trySAPI(true)
elseif os=="OS X" then
backend=require "macspeech"
end
local function say(text, interrupt)
interrupt=interrupt or false
if os=="Windows" then
backend.output(text, interrupt)
else
backend.output(text)
end
end
local function isSpeaking()
return backend.isSpeaking()
end
return {say=say, isSpeaking=isSpeaking}

1
alsoft.conf 100644
View File

@ -0,0 +1 @@
hrtf = false

BIN
audio/.DS_Store vendored 100644

Binary file not shown.

BIN
audio/beep.flac 100644

Binary file not shown.

BIN
audio/enemies/.DS_Store vendored 100644

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
audio/meh.wav 100644

Binary file not shown.

BIN
audio/tracks/.DS_Store vendored 100644

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

68
classic.lua 100644
View File

@ -0,0 +1,68 @@
--
-- classic
--
-- Copyright (c) 2014, rxi
--
-- This module is free software; you can redistribute it and/or modify it under
-- the terms of the MIT license. See LICENSE for details.
--
local Object = {}
Object.__index = Object
function Object:new()
end
function Object:extend()
local cls = {}
for k, v in pairs(self) do
if k:find("__") == 1 then
cls[k] = v
end
end
cls.__index = cls
cls.super = self
setmetatable(cls, self)
return cls
end
function Object:implement(...)
for _, cls in pairs({...}) do
for k, v in pairs(cls) do
if self[k] == nil and type(v) == "function" then
self[k] = v
end
end
end
end
function Object:is(T)
local mt = getmetatable(self)
while mt do
if mt == T then
return true
end
mt = getmetatable(mt)
end
return false
end
function Object:__tostring()
return "Object"
end
function Object:__call(...)
local obj = setmetatable({}, self)
obj:new(...)
return obj
end
return Object

12
conf.lua 100644
View File

@ -0,0 +1,12 @@
function love.conf(t)
t.identity="rsi"
t.window.title="rsi"
t.window.vsync=0
t.modules.font=true
--t.modules.graphics=false
t.modules.image=false
t.modules.mouse=false
t.modules.touch=false
end

40
encoding.lua 100644
View File

@ -0,0 +1,40 @@
local ffi = require "ffi"
local kernel32 = ffi.load("kernel32")
ffi.cdef[[
int Beep(int x, int y);
typedef unsigned int UINT;
typedef unsigned int DWORD;
typedef const char * LPCSTR;
typedef char * LPSTR;
typedef wchar_t * LPWSTR;
typedef const wchar_t *LPCWSTR;
typedef int *LPBOOL;
int WideCharToMultiByte(UINT CodePage,
DWORD dwFlags,
LPCWSTR lpWideCharStr, int cchWideChar,
LPSTR lpMultiByteStr, int cbMultiByte,
LPCSTR lpDefaultChar,
LPBOOL lpUsedDefaultChar
);
int MultiByteToWideChar(UINT CodePage,
DWORD dwFlags,
LPCSTR lpMultiByteStr, int cbMultiByte,
LPWSTR lpWideCharStr, int cchWideChar);
]]
local CP_UTF8 = 65001
local function to_utf16(s)
local needed = kernel32.MultiByteToWideChar(CP_UTF8, 0, s, -1, NULL, 0)
local buf = ffi.new("wchar_t[?]", needed)
local written = kernel32.MultiByteToWideChar(CP_UTF8, 0, s, -1, buf, needed)
return ffi.string(buf, written*2)
end
local function to_utf8(s)
local needed = kernel32.WideCharToMultiByte(CP_UTF8, 0, s, -1, nil, 0, nil, nil)
local buf = ffi.new("char[?]", needed)
local written = kernel32.WideCharToMultiByte(CP_UTF8, 0, s, -1, buf, needed, nil, nil)
return ffi.string(buf, written - 1)
end
return {to_utf8=to_utf8, to_utf16=to_utf16}

BIN
enemies/.DS_Store vendored 100644

Binary file not shown.

View File

@ -0,0 +1,9 @@
enemyBasic=enemy:extend()
function enemyBasic:new(x,y)
sounds=group("audio/enemies/1")
sounds.loop:setLooping(true)
enemyBasic.super.new(self,x,y,sounds)
sounds.loop:play()
end

25
enemy.lua 100644
View File

@ -0,0 +1,25 @@
enemy=object:extend()
function enemy:new(x,y,sounds)
self.x=x
self.y=y
self.dx=0
self.dy=-1
self.sounds=sounds;
sounds.loop:setPosition(x,y,0)
self.lastMoveTick=0
self.rate=game.currentTrack.info.beatDivisions
end -- new
function enemy:update(dt)
if game.ticker.ticks-self.lastMoveTick>=self.rate then
self:move()
end -- rate
end -- update
function enemy:move()
self.lastMoveTick=game.ticker.ticks
self.x=self.x+self.dx
self.y=self.y+self.dy
self.sounds.loop:setPosition(self.x,self.y,0)
end

13
field.lua 100644
View File

@ -0,0 +1,13 @@
field=object:extend()
function field:new(width,height)
self.width=width
self.height=height
self.contents={}
end -- new
function field:update(dt)
for k,v in pairs(self.contents) do
v:update(dt)
end -- for field
end

69
game.lua 100644
View File

@ -0,0 +1,69 @@
require "field"
game={}
game.field=field(40,20)
game.player=player
game.ticker=ticker(0.125)
game.currentTrack={}
game.trackRunning=false
game.useTrack=true
game.events={} -- events are tables that contain recurring (bool), i (period or counter), and func
function game.update(dt)
if game.useTrack then
if not game.trackRunning and c.music:tell()>=c.info.startTime then
game.ticker:calibrate(love.timer.getTime()-(game.currentTrack.music:tell()-game.currentTrack.info.startTime))
game.trackRunning=true
end -- if calibration
end -- if usetrack
local ticked=game.ticker:update(dt)
player.update(dt)
game.field:update(dt)
end -- update
function game.loadTrack(t)
game.currentTrack=t
game.ticker.tickTime=t.timeStep
t.music:setVolume(t.info.volumeBase)
game.trackRunning=false
end -- loadtrack
function game.init()
table.insert(game.field.contents, enemyBasic(5,20))
function game.ticker:tick()
for k,event in pairs(game.events) do
local adjustedTicks=game.ticker.ticks-1+(event.shift or 0)
if event.recurring then
if adjustedTicks%event.i==0 then
event.func()
end -- if fire recurring event
else
event.i=event.i-1
if event.i==0 then
event.func()
event=nil
end -- if fire one-shot event
end -- if recurring
end -- for events
end
local click={
i=2,
recurring=true,
func=function()
aud.meh:stop()
aud.meh:play()
end
}
local beep={
i=4,
recurring=true,
func=function()
aud.beep:stop()
aud.beep:play()
end
}
-- game.events.click=click
-- game.events.beep=beep
end

1
love2talk 160000

@ -0,0 +1 @@
Subproject commit a67c4f273a5325e654d7b6dc86547bf8a28fee97

19
macspeech.lua 100644
View File

@ -0,0 +1,19 @@
objc=require "objc/objc"
local synth=objc.NSSpeechSynthesizer:alloc():init()
local function output(text)
if type(text) ~="string" then
text=tostring(text)
end
synth:startSpeakingString(text)
end
local function isSpeaking()
if synth:isSpeaking()==1 then
return true
else
return false
end
end
return {output=output, isSpeaking=isSpeaking}

51
main.lua 100644
View File

@ -0,0 +1,51 @@
div=0.2
curtime=0.
lastdiv=0
calib=false
function love.load()
tts=require "love2talk"
object=require "classic"
local tick=require "tick"
tick.rate=.001
require "soundgroups"
require "ticker"
require "track"
require "utils"
require "player"
require "game"
require "enemy"
require "enemies/basic"
aud=group("audio")
c=require "tracks/gourmet"
love.timer.sleep(1)
game.loadTrack(c)
game.init()
game.currentTrack.music:play()
end -- load
function love.update(dt)
game.update()
end -- update
function love.keypressed(key)
if key=="right" then
player.direction=1
elseif key=="left" then
player.direction=-1
elseif key=="c" then
tts.say(player.x)
elseif key=="d" then
print(love.audio.getOrientation())
love.audio.setPosition(100,0,30)
end
end -- keyPressed
function love.keyreleased(key)
if key=="left" or key=="right" then
player.direction=0
end
end -- keyReleased

View File

@ -0,0 +1,9 @@
# Copy bridgesupport files from an OSX older than 10.13 to luapower/bridgesupport directory.
# NOTE: Not sure if *.bridgesupport files are copyrightable/redistributable. Use at your own risk.
usage() { echo "Usage: mgit bridgesupport copy VOLUME_DIR"; exit 1; }
[ "$1" == copy ] || usage
[ -d "$2" ] || usage
echo "Copying $2/System/Library/Frameworks/*.bridgesupport to bridgesupport ..."
find "$2/System/Library/Frameworks" -name '*.bridgesupport' -exec cp '{}' bridgesupport \;

2403
objc/objc.lua 100644

File diff suppressed because it is too large Load Diff

562
objc/objc.md 100644
View File

@ -0,0 +1,562 @@
---
tagline: Obj-C & Cocoa bridge
platforms: osx64
---
## `local objc = require'objc'`
## Features
* Coverage
* full access to Cocoa classes, protocols, C functions, structs, enums, constants
* access to methods, properties and ivars
* creating classes and overriding methods
* exploring and searching the Objective-C runtime
* Platforms
* tested with __OSX 10.7 to 10.12__ (__32bit__ and __64bit__)
* Dependencies
* none for Cocoa (XML parser included), [expat] for non-standard bridgesupport files
* Type Bridging
* methods and functions return Lua booleans
* Lua numbers, strings and tables can be passed for NSNumber, NSStrings, NSArray and NSDictionary args
* string names can be passed for class and selector args
* Lua functions can be passed for block and function-pointer args without specifying a type signature
* overriding methods does not require specifying the method type signature
* method signatures are inferred from existing supermethods and conforming protocols
* formal and informal protocols supported
* function-pointer args on overriden methods and blocks can be called without specifying a type signature
* GC Bridging
* attaching Lua variables to classes and objects
* Lua variables follow the lifetime of Obj-C objects
* Lua variables attached to classes are inherited
* automatic memory management of objects and blocks
* blocks are refcounted and freed when their last owner releases them
* Speed
* aggressive caching all-around
* no gc pressure in calling methods after the first invocation
* fast, small embedded XML parser
## Limitations
### FFI callback limitations
Blocks, function callbacks and overriden methods are based on ffi callbacks
which come with some limitations:
* can't access the vararg part of the function, for variadic functions/methods
* can't access the pass-by-value struct args or any arg after the first pass-by-value struct arg
* can't return structs by value
To counter this, you can use [cbframe] as a workaround. Enable it with
`objc.debug.cbframe = true` and now all problem methods and blocks
will receive a single arg: a pointer to a [D_CPUSTATE] struct that you have
to pick up args from and write the return value into. Note that self
isn't passed in this case, the cpu state is the only arg.
[D_CPUSTATE]: https://github.com/luapower/cbframe/blob/master/cbframe_x86_h.lua
### Broken bridgesupport files in OSX 10.13+
`*.bridgesupport` files are required to get extra type information not covered
by the objc RTTI API. In OSX 10.13 and above, these files are broken, so you
need to deploy your own and put them in the `bridgesupport` directory.
Parsable files from OSX 10.12.6 are available at https://github.com/luapower/bridgesupport
(just use `mgit clone bridgesupport` if you're using mgit).
## Quick Tutorial
### Loading frameworks
~~~{.lua}
--load a framework by name; `objc.searchpaths` says where the frameworks are. you can also use full paths.
--classes and protocols are loaded, but also C constants, enums, functions, structs and even macros.
objc.load'Foundation'
--you can also load sub-frameworks like this:
objc.load'Carbon.HIToolbox'
--which is the same as using relative paths:
objc.load'Carbon.framework/Versions/Current/Frameworks/HIToolbox'
~~~
### Creating and using objects
~~~{.lua}
--instantiate a class. the resulting object is retained and released on gc.
--you can call `release()` on it too, for a more speedy destruction.
local str = objc.NSString:alloc():initWithUTF8String'wazza'
--call methods with multiple arguments using underscores for ':'. last underscore is optional.
--C constants, enums and functions are in the objc namespace too.
local result = str:compare_options(otherStr, objc.NSLiteralSearch)
~~~
### Subclassing
~~~{.lua}
--create a derived class. when creating a class, say which protocols you wish it conforms to,
--so that you don't have to deal with type encodings when implementing its methods.
objc.class('NSMainWindow', 'NSWindw <NSWindowDelegate>')
--add methods to your class. the selector `windowWillClose` is from the `NSWindowDelegate` protocol
--so its type encoding is inferred from the protocol definition.
function objc.NSMainWindow:windowWillClose(notification)
...
end
--override existing methods. use `objc.callsuper` to call the supermethod.
function objc.NSMainWindow:update()
...
return objc.callsuper(self, 'update')
end
~~~
### Converting between Lua and Obj-C types
~~~{.lua}
local str = objc.toobj'hello' --create a NSString from a Lua string
local num = objc.toobj(3.14) --create a NSNumber from a Lua number
local dic = objc.toobj{a = 1, b = 'hi'} --create a NSDictionary from a Lua table
local arr = objc.toobj{1, 2, 3} --create a NSArray from a Lua table
local s = objc.tolua(str)
local n = objc.tolua(num)
local t1 = objc.tolua(dic)
local t2 = objc.tolua(arr)
~~~
### Adding Lua variables (luavars)
~~~{.lua}
--add Lua variables to your objects - their lifetime is tied to the lifetime of the object.
--you can also add class variables - they will be accessible through the objects too.
objc.NSObject.myClassVar = 'I can live forever'
local obj = objc.NSObject:new()
obj.myInstanceVar = 'I live while obj lives'
obj.myClassVar = 5 --change the class var (same value for all objects)
~~~
### Adding Lua methods
Lua methods are just Lua variables which happen to have a function-type value.
You can add them to a class or to an instance, but that doesn't make them
"class methods" or "instance methods" in OOP sense. Instead, this distinction
comes about when you call them:
~~~{.lua}
function objc.NSObject:myMethod() end
local str = objc.toobj'hello' --create a NSString instance, which is a NSObject
str:myMethod() --instance method (str passed as self)
objc.NSString:myMethod() --class method (NSString passed as self)
~~~
As you can see, luavars attached to a class are also inherited.
> If this looks like a lot of magic, it is. The indexing rules for class and instance
objects (i.e. getting and setting object and class fields) are pretty complex.
Have a look at the API sections "object fields" and "class fields" to learn more.
### Accessing properties & ivars
~~~{.lua}
--get and set class and instance properties using the dot notation.
local pr = objc.NSProgress:progressWithTotalUnitCount(123)
print(pr.totalUnitCount) --prints 123
pr.totalUnitCount = 321 --sets it
--get and set ivars using the dot notation.
local obj = objc.NSDocInfo:new()
obj.time = 123
print(obj.time) --prints 123
~~~
### Creating and using blocks
~~~{.lua}
--blocks are created automatically when passing a Lua function where a block is expected.
--their lifetime is auto-managed, for both synchronous and asynchronous methods.
local str = objc.NSString:alloc():initWithUTF8String'line1\nline2\nline3'
str:enumerateLinesUsingBlock(function(line, stop)
print(line:UTF8String()) --'char *' return values are also converted to Lua strings automatically
end)
--however, blocks are slow to create and use ffi callbacks which are very limited in number.
--create your blocks outside loops if possible, or call `collectgarbage()` every few hundred iterations.
--create a block with its type signature inferred from usage.
--in this case, its type is that of arg#1 to NSString's `enumerateLinesUsingBlock` method.
local block = objc.toarg(objc.NSString, 'enumerateLinesUsingBlock', 1, function(line, stop)
print(line:UTF8String())
end)
str:enumerateLinesUsingBlock(block)
--create a block with its method type encoding given manaully.
--for type encodings see:
-- https://code.google.com/p/jscocoa/wiki/MethodEncoding
local block = objc.block(function(line, stop)
print(line:UTF8String())
end, 'v@^B'}) --retval is 'v' (void), line is '@' (object), stop is '^B' (pointer to BOOL)
str:enumerateLinesUsingBlock(block)
~~~
### More goodies
Look up anything in Cocoa by a Lua pattern:
./luajit objc_test.lua inspect_find foo
Then inspect it:
./luajit objc_test.lua inspect_class PAFootprint
### Even more goodies
Check out the unit test script, it also contains a few demos, not just tests. \
Check out the undocumented `objc_inspect` module, it has a simple cmdline inspection API.
## Memory management
Memory management in objc is automatic. Cocoa's reference counting system is
tied to the Lua's garbage collector so that you don't have to worry about
retain/release. The integration is not air-tight though, so you need to know
how it's put together to avoid some tricky situations.
### Strong and weak references
Ref. counting systems are fragile: they require that retain() and release()
calls on an object be perfectly balanced. If they're not, you're toast.
Thinking of object relationships in in terms of weak and strong references
can help a lot with that.
A strong reference is a retained reference, guaranteed to be available until
released. A weak reference is not retained and its availability depends on
context.
A strong reference has a finalizer that calls release() when collected.
A weak reference doesn't have a finalizer.
Calling release() on a strong reference releases the reference, and removes
the finalizer, turning it into a weak reference. You should not call
release() on a weak reference.
### Return values are strong
Cocoa's rules say that if you alloc an object, you get a strong (retained)
reference on that object. Other method calls that return an object return
a weak (non-retained) reference to that object. Lua retains all object return
values so you always get a strong reference. This is required for the
alloc():init() sequence to work, and it's generally convenient.
### Callback arguments are weak
Object arguments passed to overriden methods (including the self argument),
blocks and function pointers, are weak references, not tied to Lua's garbage
collector. If you want to keep them around outside the scope of the callback,
you need to retain them:
~~~{.lua}
local strong_ref
function MySubClass:overridenMethod()
strong_ref = self:retain() --self is a weak ref. it needs to be retained.
end
~~~
### Luavars and object ownership
You should only use luavars on objects that you own. Luavars go away
when the last strong reference to an object goes away. Setting Lua vars
on an object with only weak references will leak those vars! Even worse,
those vars might show up as vars of other objects!
### Strong/weak ambiguities
If you create a `NSWindow`, you don't get an _unconditionally_ retained
reference to that window, contrary to Cocoa's rules, because if the user
closes the window, it is your reference that gets released. The binding
doesn't know about that and on gc it calls release again, giving you a crash
at an unpredictable time (`export NSZombieEnabled=YES` can help here).
To fix that you can either tell Cocoa that your ref is strong by calling
`win:setReleasedWhenClosed(false)`, or tell the gc that your ref is weak by
calling `ffi.gc(win, nil)`. If you chose the latter, remember that you can't
use luavars on that window!
## Main API
----------------------------------------------------------- --------------------------------------------------------------
__global objects__
`objc` namespace for loaded classes, C functions,
function aliases, enums, constants, and this API
__frameworks__
`objc.load(name|path[, option])` load a framework given its name or its full path \
option 'notypes': don't load bridgesupport file
`objc.searchpaths = {path1, ...}` search paths for frameworks
`objc.findframework(name|path) -> path, name` find a framework in searchpaths
__classes__
`objc.class'name' -> cls` class by name (`objc.class'Foo'` == `objc.Foo`)
`objc.class(obj) -> cls` class of instance
`objc.class('Foo', 'SuperFoo <Protocol1, ...>') -> cls` create a class which conforms to protocols
`objc.class('Foo', 'SuperFoo', 'Protocol1', ...) -> cls` create a class (alternative way)
`objc.classname(cls) -> s` class name
`objc.isclass(x) -> true|false` check for Class type
`objc.isobj(x) -> true|false` check for id type
`objc.ismetaclass(cls) -> true|false` check if the class is a metaclass
`objc.superclass(cls|obj) -> cls|nil` superclass
`objc.metaclass(cls|obj) -> cls` metaclass
`objc.isa(cls|obj, supercls) -> true|false` check the inheritance chain
`objc.conforms(cls|obj, protocol) -> true|false` check if a class conforms to a protocol
`objc.responds(cls, sel) -> true|false` check if instances of cls responds to a selector
`objc.conform(cls, protocol) -> true|false` declare that a class conforms to a protocol
__object fields__
`obj.field` \ access an instance field, i.e. try to get, in order: \
`obj:method(args...)` - an instance luavar \
- a readable instance property \
- an ivar \
- an instance method \
- a class field (see below)
`obj.field = val` \ set an instance field, i.e. try to set, in order: \
- an existing instance luavar \
- a writable instance property \
- an ivar \
- an existing class field (see below) \
- a new instance luavar
__class fields__
`cls.field` \ access a class field, i.e. try to get, in order: \
`cls:method(args...)` - a class luavar \
- a readable class property \
- a class method \
- a class luavar from a superclass
`cls.field = val` \ set a class field, i.e. try to set, in order: \
`function cls:method(args...) end` - an existing class luavar \
- a writable class property \
- an instance method \
- a conforming instance method \
- a class method \
- a conforming class method \
- an existing class luavar in a superclass \
- a new class luavar
__type conversions__
`objc.tolua(x) -> luatype` convert a NSNumber, NSString, NSDictionary, NSArray
to a Lua number, string, table respectively.
anything else passes through.
`objc.toobj(x) -> objtype` convert a Lua number, string, or table to a
NSNumber, NSString, NSDictionary, NSArray respectively.
anything else passes through.
`objc.ipairs(arr) -> next, arr, 0` ipairs for NSarray.
__overriding__
`objc.override(cls, sel, func[,mtype|ftype]) -> true|false` override an existing method, or add a method
which conforms to one of the conforming protocols.
returns true if the method was found and overriden.
`objc.callsuper(obj, sel, args...) -> retval` call the method implementation of the superclass
of an object.
`objc.swizle(cls, sel1, sel2[, func])` swap implementations between sel1 and sel2.
if sel2 is not an existing selector, func is required.
__selectors__
`objc.SEL(name|sel) -> sel` create/find a selector by name
`sel:name() -> s` selector name (same as tostring(sel))
__blocks and callbacks__
`objc.toarg(cls, sel, argindex, x) -> objtype` convert a Lua value to an objc value - used specifically
to create blocks and function callbacks with an appropriate
type signature for a specific method argument.
`objc.block(func, mtype|ftype) -> block` create a block with a specific type encoding.
----------------------------------------------------------- --------------------------------------------------------------
## Reflection API
----------------------------------------------------------- --------------------------------------------------------------
__protocols__
`objc.protocols() -> iter() -> proto` loaded protocols (formal or informal)
`objc.protocol(name|proto) -> proto` get a protocol by name (formal or informal)
`proto:name() -> s` protocol name (same as tostring(proto))
`proto:protocols() -> iter() -> proto` inherited protocols
`proto:properties() -> iter() -> prop` get properties (inherited ones not included)
`proto:property(proto, name, required, readonly) -> prop` find a property
`proto:methods(proto, inst, req) -> iter() -> sel, mtype` get method names and raw, non-annotated type encodings
`proto:mtype(proto, sel, inst, req) -> mtype` find a method and return its raw type encoding
`proto:ctype(proto, sel, inst, req[, for_cb]) -> ctype` find a method and return its C type encoding
__classes__
`objc.classes() -> iter() -> cls` loaded classes
`objc.protocols(cls) -> iter() -> proto` protocols which a class conforms to (formal or informal)
objc.properties(cls) -> iter() -> prop` instance properties \
use metaclass(cls) to get class properties
`objc.property(cls, name) -> prop` instance property by name (looks in superclasses too)
`objc.methods(cls) -> iter() -> meth` instance methods \
use metaclass(cls) to get class methods
`objc.method(cls, name) -> meth` instance method by name (looks in superclasses too)
`objc.ivars(cls) -> iter() -> ivar` ivars
`objc.ivar(cls) -> ivar` ivar by name (looks in superclasses too)
__properties__
`prop:name() -> s` property name (same as tostring(prop))
`prop:getter() -> s` getter name
`prop:setter() -> s` setter name (if not readonly)
`prop:stype() -> s` type encoding
`prop:ctype() -> s` C type encoding
`prop:readonly() -> true|false` readonly check
`prop:ivar() -> s` ivar name
__methods__
`meth:selector() -> sel` selector
`meth:name() -> s` selector name (same as tostring(meth))
`meth:mtype() -> s` type encoding
`meth:implementation() -> IMP` implementation (untyped)
__ivars__
`ivar:name() -> s` name (same as tostring(ivar))
`ivar:stype() -> s` type encoding
`ivar:ctype() -> s` C type encoding
`ivar:offset() -> n` offset
----------------------------------------------------------- --------------------------------------------------------------
## Debug API
----------------------------------------------------------- --------------------------------------------------------------
__logging__
`objc.debug.errors` (true) log errors to stderr
`objc.debug.printcdecl` (false) print C declarations on stdout
`objc.debug.logtopics= {topic = true}` (empty) enable logging on some topic (see source code)
`objc.debug.errcount = {topic = count}` error counts
__solving C name clashes__
`objc.debug.rename.string.foo = bar` load a string constant under a different name
`objc.debug.rename.enum.foo = bar` load an enum under a different name
`objc.debug.rename.typedef.foo = bar` load a type under a different name
`objc.debug.rename.const.foo = bar` load a const under a different name
`objc.debug.rename.function.foo = bar` load a global function under a different name
__loading frameworks__
`objc.debug.loadtypes` (true) load bridgesupport files
`objc.debug.loaddeps` (false) load dependencies per bridgesupport file (too many to be useful)
`objc.debug.lazyfuncs` (true) cdef functions on the first call instead of on load
`objc.debug.checkredef` (false) check incompatible redefinition attempts (makes parsing slower)
`objc.debug.usexpat` (false) use expat to parse bridgesupport files
__gc bridging__
`objc.debug.noretain.foo = true` declare that method `foo` already retains the object it returns
----------------------------------------------------------- --------------------------------------------------------------
## Future developments
> NOTE: I don't plan to work on these, except on requests with a use case. Patches/pull requests welcome.
### Bridging
* function-pointer args on function-pointer args (recorded but not used - need use cases)
* test for overriding a method that takes a function-pointer (not a block) arg and invoking that arg from the callback
* auto-coercion of types for functions/methods with format strings, eg. NSLog
* format string parser - apply to variadic functions and methods that have the `printf_format` attribute
* return pass-by-reference out parameters as multiple Lua return values
* record type modifiers O=out, N=inout
* auto-allocation of out arrays using array type annotations
* `c_array_length_in_result` - array length is the return value
* `c_array_length_in_arg` - array length is an arg
* `c_array_delimited_by_null` - vararg ends in null - doesn't luajit do that already?
* `c_array_of_variable_length` - ???
* `c_array_of_fixed_length` - specifies array size? doesn't seem so
* `sel_of_type`, `sel_of_type64` - use cases?
* core foundation stuff
* `cftypes` xml node - use cases?
* `already_retained` flag
* operator overloading (need good use cases)
### Inspection
* list all frameworks in searchpaths
* find framework in searchpaths
* report conforming methods, where they come from and mark the required ones, especially required but not implemented
* inspection of instances
* print class, superclasses and protocols in one line
* print values of luavars, ivars, properties
* listing sections: ivars, properties, methods, with origin class/protocol for each
### Type Cache
The idea is to cache bridgesupport data into Lua files for faster loading of frameworks.
* one Lua cache file for each framework to be loaded with standard 'require'
* dependencies also loaded using standard 'require'
* save dependency loading
* save cdecls - there's already a pretty printer and infrastructure for recording those
* save constants and enums
* save function wrappers
* save mtas (find a more compact format for annotations containing only {retval='B'} ?)
* save informal protocols

View File

@ -0,0 +1,260 @@
--Grand Central Dispatch binding by Fjölnir Ásgeirsson (c) 2012, MIT license.
--modified for luapower/objc by Cosmin Apreutesei, public domain.
--not used yet, thus not finished. the API will probably change in the future.
local ffi = require'ffi'
local objc = require'objc'
local C = ffi.C
local dispatch = {}
objc.dispatch = dispatch
ffi.cdef[[
// base.h
typedef void *dispatch_object_t;
typedef void (*dispatch_function_t)(void *);
// object.h
void dispatch_debug(dispatch_object_t object, const char *message, ...);
void dispatch_debugv(dispatch_object_t object, const char *message, va_list ap);
void dispatch_retain(dispatch_object_t object);
void dispatch_release(dispatch_object_t object);
void *dispatch_get_context(dispatch_object_t object);
void dispatch_set_context(dispatch_object_t object, void *context);
void dispatch_set_finalizer_f(dispatch_object_t object, dispatch_function_t finalizer);
void dispatch_suspend(dispatch_object_t object);
void dispatch_resume(dispatch_object_t object);
// time.h
typedef uint64_t dispatch_time_t;
dispatch_time_t dispatch_time(dispatch_time_t when, int64_t delta);
dispatch_time_t dispatch_walltime(const struct timespec *when, int64_t delta);
// queue.h
typedef struct dispatch_queue_s *dispatch_queue_t;
typedef struct dispatch_queue_attr_s *dispatch_queue_attr_t;
typedef long dispatch_queue_priority_t;
struct dispatch_queue_s _dispatch_main_q;
struct dispatch_queue_attr_s _dispatch_queue_attr_concurrent;
typedef id dispatch_block_t;
void dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
void dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
void dispatch_async_f(dispatch_queue_t queue, void *context, dispatch_function_t work);
void dispatch_sync_f(dispatch_queue_t queue, void *context, dispatch_function_t work);
void dispatch_apply_f(size_t iterations, dispatch_queue_t queue, void *context, void (*work)(void *, size_t));
dispatch_queue_t dispatch_get_current_queue(void);
dispatch_queue_t dispatch_get_global_queue(dispatch_queue_priority_t priority, unsigned long flags);
dispatch_queue_t dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);
const char *dispatch_queue_get_label(dispatch_queue_t queue);
void dispatch_set_target_queue(dispatch_object_t object, dispatch_queue_t queue);
void dispatch_main(void);
void dispatch_after_f(dispatch_time_t when, dispatch_queue_t queue, void *context, dispatch_function_t work);
void dispatch_barrier_async_f(dispatch_queue_t queue, void *context, dispatch_function_t work);
void dispatch_barrier_sync_f(dispatch_queue_t queue, void *context, dispatch_function_t work);
void dispatch_queue_set_specific(dispatch_queue_t queue, const void *key, void *context, dispatch_function_t destructor);
void *dispatch_queue_get_specific(dispatch_queue_t queue, const void *key);
void *dispatch_get_specific(const void *key);
// source.h
typedef struct dispatch_source_s *dispatch_source_t;
typedef const struct dispatch_source_type_s *dispatch_source_type_t;
typedef unsigned long dispatch_source_mach_send_flags_t;
typedef unsigned long dispatch_source_proc_flags_t;
typedef unsigned long dispatch_source_vnode_flags_t;
const struct dispatch_source_type_s _dispatch_source_type_data_add;
const struct dispatch_source_type_s _dispatch_source_type_data_or;
const struct dispatch_source_type_s _dispatch_source_type_mach_send;
const struct dispatch_source_type_s _dispatch_source_type_mach_recv;
const struct dispatch_source_type_s _dispatch_source_type_proc;
const struct dispatch_source_type_s _dispatch_source_type_read;
const struct dispatch_source_type_s _dispatch_source_type_signal;
const struct dispatch_source_type_s _dispatch_source_type_timer;
const struct dispatch_source_type_s _dispatch_source_type_vnode;
const struct dispatch_source_type_s _dispatch_source_type_write;
dispatch_source_t dispatch_source_create(dispatch_source_type_t type, uintptr_t handle,
unsigned long mask, dispatch_queue_t queue);
void dispatch_source_set_event_handler_f(dispatch_source_t source, dispatch_function_t handler);
void dispatch_source_set_cancel_handler_f(dispatch_source_t source, dispatch_function_t cancel_handler);
void dispatch_source_cancel(dispatch_source_t source);
long dispatch_source_testcancel(dispatch_source_t source);
uintptr_t dispatch_source_get_handle(dispatch_source_t source);
unsigned long dispatch_source_get_mask(dispatch_source_t source);
unsigned long dispatch_source_get_data(dispatch_source_t source);
void dispatch_source_merge_data(dispatch_source_t source, unsigned long value);
void
dispatch_source_set_timer(dispatch_source_t source, dispatch_time_t start, uint64_t interval, uint64_t leeway);
void dispatch_source_set_registration_handler_f(dispatch_source_t source, dispatch_function_t registration_handler);
// group.h
typedef struct dispatch_group_s *dispatch_group_t;
dispatch_group_t dispatch_group_create(void);
void dispatch_group_async_f(dispatch_group_t group, dispatch_queue_t queue, void *context, dispatch_function_t work);
long dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout);
void dispatch_group_notify_f(dispatch_group_t group, dispatch_queue_t queue, void *context, dispatch_function_t work);
void dispatch_group_enter(dispatch_group_t group);
void dispatch_group_leave(dispatch_group_t group);
// semaphore.h
typedef struct dispatch_semaphore_s *dispatch_semaphore_t;
dispatch_semaphore_t dispatch_semaphore_create(long value);
long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
long dispatch_semaphore_signal(dispatch_semaphore_t dsema);
// once.h
typedef long dispatch_once_t;
void dispatch_once_f(dispatch_once_t *predicate, void *context, dispatch_function_t function);
// data.h (Requires blocks for all of it's functionality, see http://github.com/aptiva/tlc if you need it)
typedef struct dispatch_data_s *dispatch_data_t;
struct dispatch_data_s _dispatch_data_empty;
// io.h (Requires blocks for all of it's functionality, see http://github.com/aptiva/tlc if you need it)
typedef int dispatch_fd_t;
]]
-- Types
dispatch.object_t = ffi.typeof'dispatch_object_t'
dispatch.function_t = ffi.typeof'dispatch_function_t'
dispatch.time_t = ffi.typeof'dispatch_time_t'
dispatch.queue_t = ffi.typeof'dispatch_queue_t'
dispatch.queue_attr_t = ffi.typeof'dispatch_queue_attr_t'
dispatch.queue_priority_t = ffi.typeof'dispatch_queue_priority_t'
dispatch.source_t = ffi.typeof'dispatch_source_t'
dispatch.source_type_t = ffi.typeof'dispatch_source_type_t'
dispatch.source_mach_send_flags_t = ffi.typeof'dispatch_source_mach_send_flags_t'
dispatch.source_proc_flags_t = ffi.typeof'dispatch_source_proc_flags_t'
dispatch.source_vnode_flags_t = ffi.typeof'dispatch_source_vnode_flags_t'
dispatch.group_t = ffi.typeof'dispatch_group_t'
dispatch.semaphore_t = ffi.typeof'dispatch_semaphore_t'
dispatch.once_ = ffi.typeof'dispatch_once_t'
dispatch.data_t = ffi.typeof'dispatch_data_t'
dispatch.fd_t = ffi.typeof'dispatch_fd_t'
if ffi.os ~= 'OSX' then
error('platform not OSX', 2)
end
-- Contants
dispatch.emptyData = ffi.cast(dispatch.data_t, C._dispatch_data_empty)
dispatch.defaultDataDestructor = nil
dispatch.nsecPerSec = 1000000000ULL
dispatch.nsecPerMsec = 1000000ULL
dispatch.usecPerSec = 1000000ULL
dispatch.nsecPerUsec = 1000ULL
dispatch.timeNow = 0
dispatch.timeForever = bit.bnot(0)
objc.DISPATCH_QUEUE_PRIORITY_HIGH = ffi.cast(dispatch.queue_priority_t, 2)
objc.DISPATCH_QUEUE_PRIORITY_DEFAULT = ffi.cast(dispatch.queue_priority_t, 0)
objc.DISPATCH_QUEUE_PRIORITY_LOW = ffi.cast(dispatch.queue_priority_t, -2)
objc.DISPATCH_QUEUE_PRIORITY_BACKGROUND = ffi.cast(dispatch.queue_priority_t, -32768) -- INT16_MIN
dispatch.main_queue = C._dispatch_main_q
dispatch.serialQueueAttr = nil
dispatch.queue_attr_concurrent = C._dispatch_queue_attr_concurrent
dispatch.defaultTargetQueue = nil
dispatch.machSendDead = ffi.cast(dispatch.source_proc_flags_t, 0x1)
dispatch.procExit = ffi.cast(dispatch.source_proc_flags_t, 0x80000000)
dispatch.procFork = ffi.cast(dispatch.source_proc_flags_t, 0x40000000)
dispatch.procExec = ffi.cast(dispatch.source_proc_flags_t, 0x20000000)
dispatch.procSignal = ffi.cast(dispatch.source_proc_flags_t, 0x08000000)
dispatch.sourceTypeDataAdd = ffi.cast(dispatch.source_type_t, C._dispatch_source_type_data_add)
dispatch.sourceTypeDataOr = ffi.cast(dispatch.source_type_t, C._dispatch_source_type_data_or)
dispatch.sourceTypeMachSend = ffi.cast(dispatch.source_type_t, C._dispatch_source_type_mach_send)
dispatch.sourceTypeMachRecv = ffi.cast(dispatch.source_type_t, C._dispatch_source_type_mach_recv)
dispatch.sourceTypeProc = ffi.cast(dispatch.source_type_t, C._dispatch_source_type_proc)
dispatch.sourceTypeRead = ffi.cast(dispatch.source_type_t, C._dispatch_source_type_read)
dispatch.sourceTypeSignal = ffi.cast(dispatch.source_type_t, C._dispatch_source_type_signal)
dispatch.sourceTypeTimer = ffi.cast(dispatch.source_type_t, C._dispatch_source_type_timer)
dispatch.sourceTypeVnode = ffi.cast(dispatch.source_type_t, C._dispatch_source_type_vnode)
dispatch.sourceTypeWrite = ffi.cast(dispatch.source_type_t, C._dispatch_source_type_write)
dispatch.vNodeDelete = ffi.cast(dispatch.source_vnode_flags_t, 0x1)
dispatch.vNodeWrite = ffi.cast(dispatch.source_vnode_flags_t, 0x2)
dispatch.vNodeExtend = ffi.cast(dispatch.source_vnode_flags_t, 0x4)
dispatch.vNodeAttrib = ffi.cast(dispatch.source_vnode_flags_t, 0x8)
dispatch.vNodeLink = ffi.cast(dispatch.source_vnode_flags_t, 0x10)
dispatch.vNodeRename = ffi.cast(dispatch.source_vnode_flags_t, 0x20)
dispatch.vNodeRevoke = ffi.cast(dispatch.source_vnode_flags_t, 0x40)
-- Functions
dispatch.debug = C.dispatch_debug
dispatch.debugv = C.dispatch_debugv
dispatch.retain = C.dispatch_retain
dispatch.release = C.dispatch_release
dispatch.dispatch_get_context = C.dispatch_get_context
dispatch.set_context = C.dispatch_set_context
dispatch.set_finalizer = C.dispatch_set_finalizer_f
dispatch.suspend = C.dispatch_suspend
dispatch.resume = C.dispatch_resume
dispatch.time = C.dispatch_time
dispatch.walltime = C.dispatch_walltime
dispatch.async_f = C.dispatch_async_f
dispatch.sync_f = C.dispatch_sync_f
dispatch.apply = C.dispatch_apply_f
dispatch.get_current_queue = C.dispatch_get_current_queue
dispatch.get_global_queue = C.dispatch_get_global_queue
dispatch.queue_create = C.dispatch_queue_create
dispatch.dispatch_queue_get_label = C.dispatch_queue_get_label
dispatch.set_target_queue = C.dispatch_set_target_queue
dispatch.main = C.dispatch_main
dispatch.after = C.dispatch_after_f
dispatch.barrier_async = C.dispatch_barrier_async_f
dispatch.barrier_sync = C.dispatch_barrier_sync_f
dispatch.queue_set_specific = C.dispatch_queue_set_specific
dispatch.dispatch_queue_get_specific = C.dispatch_queue_get_specific
dispatch.dispatch_get_specific = C.dispatch_get_specific
dispatch.source_create = C.dispatch_source_create
dispatch.source_set_event_handler = C.dispatch_source_set_event_handler_f
dispatch.source_set_cancel_handler = C.dispatch_source_set_cancel_handler_f
dispatch.source_cancel = C.dispatch_source_cancel
dispatch.source_testcancel = C.dispatch_source_testcancel
dispatch.source_get_handle = C.dispatch_source_get_handle
dispatch.source_get_mask = C.dispatch_source_get_mask
dispatch.source_get_data = C.dispatch_source_get_data
dispatch.source_merge_data = C.dispatch_source_merge_data
dispatch.source_set_timer = C.dispatch_source_set_timer
dispatch.source_set_registration_handler = C.dispatch_source_set_registration_handler_f
dispatch.group_create = C.dispatch_group_create
dispatch.group_async = C.dispatch_group_async_f
dispatch.group_wait = C.dispatch_group_wait
dispatch.group_notify = C.dispatch_group_notify_f
dispatch.group_enter = C.dispatch_group_enter
dispatch.group_leave = C.dispatch_group_leave
dispatch.semaphore_create = C.dispatch_semaphore_create
dispatch.semaphore_wait = C.dispatch_semaphore_wait
dispatch.semaphore_signal = C.dispatch_semaphore_signal
dispatch.once = C.dispatch_once_f
dispatch.DISPATCH_QUEUE_SERIAL = nil
--note: do not use with queues that call back from a different thread!
function dispatch.async(queue, block)
C.dispatch_async(queue, objc.block(block))
end
jit.off(dispatch.async)
function dispatch.sync(queue, block)
C.dispatch_sync(queue, objc.block(block))
end
return dispatch

View File

@ -0,0 +1,246 @@
local objc = require'objc'
local inspect = {}
objc.inspect = inspect
--pretty helpers
local _ = string.format
local function p(...) --formatted line
print(_(...))
end
local function isort(iter, method) --sort an iterator of object by a method
local t = {}
while true do
local v = iter()
if v == nil then break end
t[#t+1] = v
end
table.sort(t, function(a, b) return a[method](a) < b[method](b) end)
local i = 0
return function()
i = i + 1
if t[i] == nil then return end
return t[i]
end
end
local function icount(iter)
local n = 0
for _ in iter do
n = n + 1
end
return n
end
--class header
local function protocols_spec(protocols)
local t = {}
for proto in isort(protocols, 'name') do
t[#t+1] = proto:name() .. protocols_spec(proto:protocols())
end
return #t > 0 and _(' <%s>', table.concat(t, ', ')) or ''
end
local function class_spec(cls, indent)
indent = indent or 1
local super_spec = objc.superclass(cls) and
_('\n%s<- %s', (' '):rep(indent), class_spec(objc.superclass(cls), indent + 1)) or ''
return objc.classname(cls) .. protocols_spec(objc.protocols(cls)) .. super_spec
end
local function protocol_spec(proto)
return proto:name() .. protocols_spec(proto:protocols())
end
function inspect.class_header(cls)
p('Class %s', class_spec(objc.class(cls)))
end
--classes
function inspect.classes()
for cls in objc.classes() do
p('%-50s protocols: %-5s properties: %-5s ivars: %-5s methods (i): %-5s methods (c): %-5s',
objc.classname(cls),
icount(objc.protocols(cls)),
icount(objc.properties(cls)),
icount(objc.ivars(cls)),
icount(objc.methods(cls)),
icount(objc.methods(objc.metaclass(cls))))
end
end
--protocols
function inspect.protocols()
for proto in objc.protocols() do
p('%-50s %-10s protocols: %-5s properties: %-5s methods (i/o): %-5s methods (i/r): %-5s methods (c/o): %-5s methods (c/r): %-5s',
proto:name(), proto.formal and 'formal' or 'informal',
icount(proto:protocols()),
icount(proto:properties()),
icount(proto:methods(true, false)),
icount(proto:methods(true, true)),
icount(proto:methods(false, false)),
icount(proto:methods(false, true)))
end
end
--properties
local function inspect_properties(name, props)
for prop in isort(props, 'name') do
p('%-30s %-40s %-8s %-20s %-20s %-20s %-20s',
name, prop:name(), prop:readonly() and 'r/o' or 'r/w',
prop:ctype(), prop:ivar() or '', prop:getter(), prop:setter() or '')
end
end
local function not_(arg, list, process, ...)
if arg then return end
for arg in list() do
process(arg, ...)
end
return true
end
function inspect.protocol_properties(proto)
if not_(proto, objc.protocols, inspect.protocol_properties) then return end
proto = objc.protocol(proto)
inspect_properties(proto:name(), proto:properties())
end
function inspect.class_properties(cls)
if not_(cls, objc.classes, inspect.class_properties) then return end
cls = objc.class(cls)
inspect_properties(objc.classname(cls), objc.properties(cls))
end
--methods
function inspect.class_methods(cls, inst)
if not_(cls, objc.classes, inspect.class_methods, inst) then return end
cls = inst and objc.class(cls) or objc.metaclass(cls)
for meth in isort(objc.methods(cls), 'name') do
p('%-40s %-50s %-50s %s', objc.classname(cls), meth:name(),
objc.ftype_ctype(objc.method_ftype(cls, meth:selector(), meth)), meth:mtype())
end
end
function inspect.protocol_methods(proto, inst, required)
if not_(proto, objc.protocols, inspect.protocol_methods, inst, required) then return end
proto = objc.protocol(proto)
for selname, mtype in proto:methods(inst or false, required or false) do
p('%-40s %-50s %-50s %s', proto:name(), selname, objc.ftype_ctype(objc.mtype_ftype(mtype)), mtype)
end
end
--ivars
function inspect.class_ivars(cls)
if not_(cls, objc.classes, inspect.ivars) then return end
cls = objc.class(cls)
for ivar in isort(objc.ivars(cls), 'name') do
p('%-40s %-50s %-50s %-5s %s', objc.classname(cls), ivar:name(), ivar:ctype(),
tonumber(ivar:offset()), ivar:stype())
end
end
--full class inspection
function inspect.class(cls)
print''
inspect.class_header(cls)
print'\nProperties:\n'
inspect.class_properties(cls)
print'\nIvars:\n'
inspect.class_ivars(cls)
print'\nMethods:\n'
inspect.class_methods(cls)
end
function inspect.protocol(proto)
print''
print(objc.protocol(proto):name())
print'\nProperties:\n'
inspect.protocol_properties(proto)
print'\nMethods (i/o):\n'
inspect.protocol_methods(proto, true, false)
print'\nMethods (i/r):\n'
inspect.protocol_methods(proto, true, true)
print'\nMethods (c/o):\n'
inspect.protocol_methods(proto, false, false)
print'\nMethods (c/r):\n'
inspect.protocol_methods(proto, false, true)
end
function inspect.find(patt)
--TODO: find framework / load all frameworks
local function find_in_class(prefix, cls)
for meth in objc.methods(cls) do
if meth:name():find(patt) then
p('%-20s [%s %s]', prefix..' method', objc.classname(cls), meth:name())
end
end
for prop in objc.properties(cls) do
if prop:name():find(patt) then
p('%-20s %s.%s', prefix..' property', objc.classname(cls), prop:name())
end
end
for ivar in objc.ivars(cls) do
if ivar:name():find(patt) then
p('%-20s %s.%s', prefix..' ivar', objc.classname(cls), ivar:name())
end
end
end
for cls in objc.classes() do
if objc.classname(cls):find(patt) then
p('%-20s %s', 'class', objc.classname(cls))
end
find_in_class('instance', cls)
find_in_class('class', cls)
end
local function find_proto_method(proto, postfix, inst, required)
for selname in proto:methods(inst, required) do
if selname:find(patt) then
p('%-20s [%s %s]', 'protocol method ('..postfix..')', proto:name(), selname)
end
end
end
for proto in objc.protocols() do
if proto:name():find(patt) then
p('%-20s %s', 'protocol', proto:name())
end
find_proto_method(proto, 'i/o', true, false)
find_proto_method(proto, 'i/r', true, true)
find_proto_method(proto, 'c/o', false, false)
find_proto_method(proto, 'c/r', false, true)
for prop in proto:properties() do
if prop:name():find(patt) then
p('%-20s %s.%s', 'protocol property', proto:name(), prop:name())
end
end
end
local function find_global(title, prefix, namespace)
for k in pairs(namespace) do
if type(k) == 'string' and k:find(patt) then
p('%-20s %s%s', title, prefix, k)
end
end
end
find_global('global', 'objc.', objc)
find_global('C global', 'objc.', objc.debug.cnames.global)
find_global('C struct', '', objc.debug.cnames.struct)
end
if not ... then
for k,v in pairs(inspect) do
print(_('%-10s %s', type(v), 'objc.inspect.'..k))
end
end
return inspect

855
objc/objc_test.lua 100644
View File

@ -0,0 +1,855 @@
local glue = require'glue'
local objc = require'objc'
local ffi = require'ffi'
local pp = require'pp'
io.stdout:setvbuf'no'
io.stderr:setvbuf'no'
setmetatable(_G, {__index = objc})
--test options
local subprocess = false --run each bridgesupport test in a subprocess
objc.debug.lazyfuncs = true
objc.debug.checkredef = false
objc.debug.printcdecl = false
objc.debug.loaddeps = false
objc.debug.loadtypes = true
local bsdir = '_bridgesupport' --path where *.bridgesupport files are on Windows (tree or flat doesn't matter)
local luajit = ffi.os == 'Windows' and 'luajit' or './luajit' --luajit command for subprocess running
if ffi.os == 'OSX' then
objc.load'Foundation'
pool = NSAutoreleasePool:new()
end
--test helpers
local function printf(...)
print(string.format(...))
end
local function hr()
print(('-'):rep(80))
end
local n = 0
local function genname(prefix)
if not prefix then return genname'MyClass' end
n = n + 1
return prefix..n
end
local function errpcall(patt, ...) --pcall that should fail with a specific message
local ok, err = pcall(...)
assert(not ok)
assert(err:find(patt))
end
--test namespace
local test = {} --{name = test_func}
local eyetest = {} --{name = test_func}
local demo = {}
local tests = {tests = test, ['eye tests'] = eyetest, demos = demo}
function test.parsing()
assert(stype_ctype('[8^c]', 'arr') == 'char *arr[8]') --array of pointers
assert(stype_ctype('^[8c]', 'arr') == 'char (*arr)[8]') --pointer to array
assert(stype_ctype('[8[4c]]', 'arr') == 'char arr[8][4]') --multi-dim. array
assert(stype_ctype('[3^[8^c]]', 'arr') == 'char *(*arr[3])[8]')
assert(stype_ctype('{?="x"i"y"i""(?="ux"I"uy"I)}', nil, 'cdef') ==
'struct {\n\tint x;\n\tint y;\n\tunion {\n\t\tunsigned int ux;\n\t\tunsigned int uy;\n\t};\n}'
) --nested unnamed anonymous structs
local function mtype_ctype(mtype, ...)
return ftype_ctype(mtype_ftype(mtype), ...)
end
assert(mtype_ctype('@"Class"@:{_NSRect={_NSPoint=ff}{_NSSize=ff}}^{?}^?', 'globalFunction') ==
'id globalFunction (id, SEL, struct _NSRect, void *, void *)') --unseparated method args
assert(mtype_ctype('{_NSPoint=ff}iii', nil, true) ==
'void (*) (int, int, int)') --struct return value not supported
assert(mtype_ctype('iii{_NSPoint=ff}ii', nil, true) ==
'int (*) (int, int)') --pass-by-value struct not supported, stop at first encounter
assert(mtype_ctype('{_NSPoint=ff}ii{_NSPoint=ff}i', nil, true) ==
'void (*) (int, int)') --combined case
end
function eyetest.indent()
--_NXEvent (test indent for nested unnamed anonymous structs)
print(stype_ctype('{?="type"i"location"{?="x"i"y"i}"time"Q"flags"i"window"I"service_id"Q"ext_pid"i"data"(?="mouse"{?="subx"C"suby"C"eventNum"s"click"i"pressure"C"buttonNumber"C"subType"C"reserved2"C"reserved3"i"tablet"(?="point"{_NXTabletPointData="x"i"y"i"z"i"buttons"S"pressure"S"tilt"{?="x"s"y"s}"rotation"S"tangentialPressure"s"deviceID"S"vendor1"s"vendor2"s"vendor3"s}"proximity"{_NXTabletProximityData="vendorID"S"tabletID"S"pointerID"S"deviceID"S"systemTabletID"S"vendorPointerType"S"pointerSerialNumber"I"uniqueID"Q"capabilityMask"I"pointerType"C"enterProximity"C"reserved1"s})}"mouseMove"{?="dx"i"dy"i"subx"C"suby"C"subType"C"reserved1"C"reserved2"i"tablet"(?="point"{_NXTabletPointData="x"i"y"i"z"i"buttons"S"pressure"S"tilt"{?="x"s"y"s}"rotation"S"tangentialPressure"s"deviceID"S"vendor1"s"vendor2"s"vendor3"s}"proximity"{_NXTabletProximityData="vendorID"S"tabletID"S"pointerID"S"deviceID"S"systemTabletID"S"vendorPointerType"S"pointerSerialNumber"I"uniqueID"Q"capabilityMask"I"pointerType"C"enterProximity"C"reserved1"s})}"key"{?="origCharSet"S"repeat"s"charSet"S"charCode"S"keyCode"S"origCharCode"S"reserved1"i"keyboardType"I"reserved2"i"reserved3"i"reserved4"i"reserved5"[4i]}"tracking"{?="reserved"s"eventNum"s"trackingNum"i"userData"i"reserved1"i"reserved2"i"reserved3"i"reserved4"i"reserved5"i"reserved6"[4i]}"scrollWheel"{?="deltaAxis1"s"deltaAxis2"s"deltaAxis3"s"reserved1"s"fixedDeltaAxis1"i"fixedDeltaAxis2"i"fixedDeltaAxis3"i"pointDeltaAxis1"i"pointDeltaAxis2"i"pointDeltaAxis3"i"reserved8"[4i]}"zoom"{?="deltaAxis1"s"deltaAxis2"s"deltaAxis3"s"reserved1"s"fixedDeltaAxis1"i"fixedDeltaAxis2"i"fixedDeltaAxis3"i"pointDeltaAxis1"i"pointDeltaAxis2"i"pointDeltaAxis3"i"reserved8"[4i]}"compound"{?="reserved"s"subType"s"misc"(?="F"[11f]"L"[11i]"S"[22s]"C"[44c])}"tablet"{?="x"i"y"i"z"i"buttons"S"pressure"S"tilt"{?="x"s"y"s}"rotation"S"tangentialPressure"s"deviceID"S"vendor1"s"vendor2"s"vendor3"s"reserved"[4i]}"proximity"{?="vendorID"S"tabletID"S"pointerID"S"deviceID"S"systemTabletID"S"vendorPointerType"S"pointerSerialNumber"I"uniqueID"Q"capabilityMask"I"pointerType"C"enterProximity"C"reserved1"s"reserved2"[4i]})}', nil, 'cdef'))
end
function eyetest.tostring()
print(objc.NSNumber:numberWithDouble(0))
print(objc.NSString:alloc():initWithUTF8String'') --empty string = NSFConstantString with hi address
print(objc.NSString:alloc():initWithUTF8String'asdjfah') --real object
end
--test parsing of bridgesupport files.
--works on Windows too - just copy your bridgesupport files into whatever you set `bsdir` above.
function eyetest.bridgesupport(bsfile)
local function list_func(cmd)
return function()
return coroutine.wrap(function()
local f = io.popen(cmd)
for s in f:lines() do
coroutine.yield(s)
end
f:close()
end)
end
end
local bsfiles
if ffi.os == 'Windows' then
bsfiles = list_func('dir /B /S '..bsdir..'\\*.bridgesupport')
elseif ffi.os == 'OSX' then
bsfiles = list_func('find /System/Library/frameworks -name \'*.bridgesupport\'')
else
error'can\'t run on this OS'
end
local loaded = {}
local n = 0
local objc_load = objc.debug.load_framework --keep it, we'll patch it
function objc.debug.load_framework(path) --either `name.bridgesupport` or `name.framework` or `name.framework/name`
local name
if path:match'%.bridgesupport$' then
name = path:match'([^/\\]+)%.bridgesupport$'
else
name = path:match'/([^/]+)%.framework$' or path:match'([^/]+)$'
if ffi.os == 'Windows' then
path = bsdir..'\\'..name..'.bridgesupport'
else
path = path .. '/Resources/BridgeSupport/' .. name .. '.bridgesupport'
end
end
if loaded[name] then return end
loaded[name] = true
if glue.canopen(path) then
if ffi.os == 'OSX' then
--load the dylib first (needed for function aliases)
local dpath = path:gsub('Resources/BridgeSupport/.*$', name)
if glue.canopen(dpath) then
pcall(ffi.load, dpath, true)
end
--load the dylib with inlines first (needed for function aliases)
local dpath = path:gsub('bridgesupport$', 'dylib')
if glue.canopen(dpath) then
pcall(ffi.load, dpath, true)
end
end
objc.debug.load_bridgesupport(path)
n = n + 1
--print(n, '', name)
else
print('! not found', name, path)
end
end
local function status()
pp('errors', objc.debug.errcount)
print('globals: '..objc.debug.cnames.global[1])
print('structs: '..objc.debug.cnames.struct[1])
end
if bsfile then
objc.debug.load_framework(bsfile)
else
for bsfile in bsfiles() do
if bsfile:match'Python' then
print('skipping '..bsfile) --python bridgesupport files are non-standard and deprecated
else
--print(); print(bsfile); print(('='):rep(80))
if subprocess then
os.execute(luajit..' '..arg[0]..' bridgesupport '..bsfile)
else
objc.debug.load_framework(bsfile)
end
end
end
status()
end
objc.debug.load_framework = objc_load --put it back
end
function test.selectors()
assert(tostring(SEL'se_lec_tor') == 'se:lec:tor')
assert(tostring(SEL'se_lec_tor_') == 'se:lec:tor:')
assert(tostring(SEL'__se_lec_tor') == '__se:lec:tor')
assert(tostring(SEL'__se:lec:tor:') == '__se:lec:tor:')
end
--class, superclass, metaclass, class protocols
function test.class()
--arg. checking
errpcall('already', class, 'NSObject', 'NSString')
errpcall('superclass', class, genname(), 'MyUnknownClass')
errpcall('protocol', class, genname(), 'NSObject <MyUnknownProtocol>')
--class overloaded constructors
local cls = class('MyClassX', false) --root class
assert(classname(cls) == 'MyClassX')
assert(not superclass(cls))
--derived class
local cls = class(genname(), 'NSArray')
assert(isa(cls, 'NSArray'))
--derived + conforming
local cls = class(genname(), 'NSArray <NSStreamDelegate, NSLocking>')
assert(isa(cls, 'NSArray'))
assert(conforms(cls, 'NSStreamDelegate'))
assert(conforms(cls, 'NSLocking'))
local t = {0}
for proto in protocols(cls) do
t[proto:name()] = true
t[1] = t[1] + 1
end
assert(t[1] == 2)
assert(t.NSStreamDelegate)
assert(t.NSLocking)
--class hierarchy queries
assert(superclass(cls) == NSArray)
assert(metaclass(cls))
assert(superclass(metaclass(cls)) == metaclass'NSArray')
assert(metaclass(superclass(cls)) == metaclass'NSArray')
assert(metaclass(metaclass(cls)) == nil)
assert(isa(cls, 'NSObject'))
assert(ismetaclass(metaclass(cls)))
assert(isclass(cls))
assert(not ismetaclass(cls))
assert(not isobj(cls))
assert(isclass(metaclass(cls)))
local obj = cls:new()
assert(isobj(obj))
assert(not isclass(obj))
end
function test.refcount()
local cls = class(genname(), 'NSObject')
local inst, inst2, inst3
inst = cls:new()
assert(inst:retainCount() == 1)
inst2 = inst:retain() --same class, new cdata, new reference
assert(inst:retainCount() == 2)
inst3 = inst:retain()
assert(inst:retainCount() == 3)
inst3 = nil --release() on gc
collectgarbage()
assert(inst:retainCount() == 2)
inst3 = inst:retain()
assert(inst:retainCount() == 3)
inst:release() --manual release()
assert(inst:retainCount() == 2)
inst = nil --object already disowned by inst, refcount should not decrease
collectgarbage()
assert(inst2:retainCount() == 2)
inst, inst2, inst3 = nil
collectgarbage()
end
function test.luavars()
local cls = class(genname(), 'NSObject')
--class vars
cls.myclassvar = 'doh1'
assert(cls.myclassvar == 'doh1') --intialized
cls.myclassvar = 'doh'
assert(cls.myclassvar == 'doh') --updated
--inst vars
local inst = cls:new()
inst.myinstvar = 'DOH1'
assert(inst.myinstvar == 'DOH1') --initialized
inst.myinstvar = 'DOH'
assert(inst.myinstvar == 'DOH') --updated
--class vars from instances
assert(inst.myclassvar == 'doh') --class vars are readable from instances
inst.myclassvar = 'doh2'
assert(cls.myclassvar == 'doh2') --and they can be updated from instances
assert(inst.myclassvar == 'doh2')
--soft ref counting
local inst2 = inst:retain()
assert(inst.myinstvar == 'DOH') --2 refs
inst = nil
collectgarbage()
assert(inst2.myinstvar == 'DOH') --1 ref
inst2:release() --0 refs; instance gone, vars gone (no way to test, memory was freed)
assert(cls.myclassvar == 'doh2') --class vars still there
local i = 0
function NSObject:myMethod() i = i + 1 end
local str = toobj'hello' --create a NSString instance, which is a NSObject
str:myMethod() --instance method (str passed as self)
objc.NSString:myMethod() --class method (NSString passed as self)
assert(i == 2)
function NSObject:myMethod() i = i - 1 end --override
str:myMethod() --instance method (str passed as self)
objc.NSString:myMethod() --class method (NSString passed as self)
assert(i == 0)
end
function test.override()
objc.debug.logtopics.addmethod = true
local cls = class(genname(), 'NSObject')
local metacls = metaclass(cls)
local obj = cls:new()
local instdesc = 'hello-instance'
local classdesc = 'hello-class'
function metacls:description() --override the class method
return classdesc --note: we can return the string directly.
end
function cls:description() --override the instance method
return instdesc --note: we can return the string directly.
end
assert(objc.tolua(cls:description()) == classdesc) --class method was overriden
assert(objc.tolua(obj:description()) == instdesc) --instance method was overriden and it's different
--subclass and test again
local cls2 = class(genname(), cls)
local metacls2 = metaclass(cls2)
local obj2 = cls2:new()
function metacls2:description() --override the class method
return objc.callsuper(self, 'description'):UTF8String() .. '2'
end
function cls2:description(callsuper) --override the instance method
return objc.callsuper(self, 'description'):UTF8String() .. '2'
end
assert(objc.tolua(cls2:description()) == classdesc..'2') --class method was overriden
assert(objc.tolua(obj2:description()) == instdesc..'2') --instance method was overriden and it's different
end
function test.ivars()
local obj = NSDocInfo:new()
if ffi.abi'64bit' then
assert(ffi.typeof(obj.time) == ffi.typeof'long long')
else
assert(type(obj.time) == 'number')
end
assert(type(obj.mode) == 'number') --unsigned short
assert(ffi.typeof(obj.flags) == ffi.typeof(obj.flags)) --anonymous struct (assert that it was cached)
obj.time = 123
assert(obj.time == 123)
assert(obj.flags.isDir == 0)
obj.flags.isDir = 3 --1 bit
assert(obj.flags.isDir == 1) --1 bit was set (so this is not a luavar or anything)
end
test.properties = objc.with_properties(function()
--TODO: find another class with r/w properties. NSProgress is not public on 10.7.
local pr = NSProgress:progressWithTotalUnitCount(123)
assert(pr.totalUnitCount == 123) --as initialized
pr.totalUnitCount = 321 --read/write property
assert(pr.totalUnitCount == 321)
assert(not pcall(function() pr.indeterminate = true end)) --attempt to set read-only property
assert(pr.indeterminate == false)
end)
local timebase, last_time
function timediff()
objc.load'System'
local time
time = mach_absolute_time()
if not timebase then
timebase = ffi.new'mach_timebase_info_data_t'
mach_timebase_info(timebase)
end
local d = tonumber(time - (last_time or 0)) * timebase.numer / timebase.denom / 10^9
last_time = time
return d
end
function test.blocks()
--objc.debug.logtopics.block = true
local times = 20000
timediff()
--take 1: creating blocks in inner loops with automatic memory management of blocks.
local s = NSString:alloc():initWithUTF8String'line1\nline2\nline3'
for i=1,times do
local t = {}
--note: the signature of the block arg for enumerateLinesUsingBlock was taken from bridgesupport.
s:enumerateLinesUsingBlock(function(line, pstop)
t[#t+1] = line:UTF8String()
if #t == 2 then --stop at line 2
pstop[0] = 1
end
end)
assert(#t == 2)
assert(t[1] == 'line1')
assert(t[2] == 'line2')
--note: callbacks are slow, expensive to create, and limited in number. we have to release them often!
if i % 200 == 0 then
collectgarbage()
end
end
printf('take 1: block in loop (%d times): %4.2fs', times, timediff())
--take 2: creating a single block in the outer loop (we must give its type).
local t
local blk = toarg(NSString, 'enumerateLinesUsingBlock', 1, function(line, pstop)
t[#t+1] = line:UTF8String()
if #t == 2 then --stop at line 2
pstop[0] = 1
end
end)
local s = NSString:alloc():initWithUTF8String'line1\nline2\nline3'
for i=1,times do
t = {}
s:enumerateLinesUsingBlock(blk)
assert(#t == 2)
assert(t[1] == 'line1')
assert(t[2] == 'line2')
end
printf('take 2: single block (%d times): %4.2fs', times, timediff())
end
function test.tolua()
local n = toobj(123.5)
assert(isa(n, 'NSNumber'))
assert(tolua(n) == 123.5)
local s = toobj'hello'
assert(isa(s, 'NSString'))
assert(tolua(s) == 'hello')
local a = {1,2,6,7}
local t = toobj(a)
assert(t:count() == #a)
for i=1,#a do
assert(t:objectAtIndex(i-1):doubleValue() == a[i])
end
a = tolua(t)
assert(#a == 4)
assert(a[3] == 6)
local d = {a = 1, b = 'baz', d = {1,2,3}, [{x=1}] = {y=2}}
local t = toobj(d)
assert(t:count() == 4)
assert(tolua(t:valueForKey(toobj'a')) == d.a)
assert(tolua(t:valueForKey(toobj'b')) == d.b)
assert(tolua(t:valueForKey(toobj'd'))[2] == 2)
end
function test.args()
local s = NSString:alloc():initWithUTF8String'\xE2\x82\xAC' --euro symbol
--return string
assert(s:UTF8String() == '\xE2\x82\xAC')
--return boolean (doesn't work for methods)
assert(s:isAbsolutePath() == false)
--return null
assert(type(s:cStringUsingEncoding(NSASCIIStringEncoding)) == 'nil')
--selector arg
assert(s:respondsToSelector'methodForSelector:' == true)
--class arg
assert(NSArray:isSubclassOfClass'NSObject' == true)
assert(NSArray:isSubclassOfClass'XXX' == false)
--string arg
assert(NSString:alloc():initWithString('hey'):UTF8String() == 'hey')
--table arg for array
local a = NSArray:alloc():initWithArray{6,25,5}
assert(a:objectAtIndex(1):doubleValue() == 25)
--table arg for dictionary
local d = NSDictionary:alloc():initWithDictionary{a=5,b=7}
assert(d:valueForKey('b'):doubleValue() == 7)
end
function demo.window()
objc.load'AppKit'
local NSApp = class('NSApp', 'NSApplication <NSApplicationDelegate>')
--we need to add methods to the class before creating any objects!
--note: NSApplicationDelegate is an informal protocol brought from bridgesupport.
function NSApp:applicationShouldTerminateAfterLastWindowClosed()
print'last window closed...'
collectgarbage()
return true
end
function NSApp:applicationShouldTerminate()
print'terminating...'
return true
end
local app = NSApp:sharedApplication()
app:setDelegate(app)
app:setActivationPolicy(NSApplicationActivationPolicyRegular)
local NSWin = class('NSWin', 'NSWindow <NSWindowDelegate>')
--we need to add methods to the class before creating any objects!
--note: NSWindowDelegate is a formal protocol brought from the runtime.
function NSWin:windowWillClose()
print'window will close...'
end
local style = bit.bor(
NSTitledWindowMask,
NSClosableWindowMask,
NSMiniaturizableWindowMask,
NSResizableWindowMask)
local win = NSWin:alloc():initWithContentRect_styleMask_backing_defer(
NSMakeRect(300, 300, 500, 300), style, NSBackingStoreBuffered, false)
win:setDelegate(win)
win:setTitle"▀▄▀▄▀▄ [ Lua Rulez ] ▄▀▄▀▄▀"
app:activateIgnoringOtherApps(true)
win:makeKeyAndOrderFront(nil)
app:run()
end
function demo.speech()
objc.load'AppKit'
local speech = NSSpeechSynthesizer:new()
voiceid = NSSpeechSynthesizer:availableVoices():objectAtIndex(11)
speech:setVoice(voiceid)
speech:startSpeakingString'Calm, fitter, healthier, and more productive; A pig. In a cage. On antibiotics.'
while speech:isSpeaking() do
os.execute'sleep 1'
end
end
function demo.http() --what a dense word soup just to make a http request
objc.load'AppKit'
local app = NSApplication:sharedApplication()
local post = NSString:stringWithFormat('firstName=%@&lastName=%@&eMail=%@&message=%@',
toobj'Dude', toobj'Edud', toobj'x@y.com', toobj'message')
local postData = post:dataUsingEncoding(NSUTF8StringEncoding)
local postLength = NSString:stringWithFormat('%ld', postData:length())
NSLog('Post data: %@', post)
local request = NSMutableURLRequest:new()
request:setURL(NSURL:URLWithString'http://posttestserver.com/post.php')
request:setHTTPMethod'POST'
request:setValue_forHTTPHeaderField(postLength, 'Content-Length')
request:setValue_forHTTPHeaderField('application/x-www-form-urlencoded', 'Content-Type')
request:setHTTPBody(postData)
NSLog('%@', request)
local CD = class('ConnDelegate', 'NSObject <NSURLConnectionDelegate, NSURLConnectionDataDelegate>')
function CD:connection_didReceiveData(conn, data)
self.webData:appendData(data)
NSLog'Connection received data'
end
function CD:connection_didReceiveResponse(conn, response)
NSLog'Connection received response'
NSLog('%@', response:description())
end
function CD:connection_didFailWithError(conn, err)
NSLog('Connection error: %@', err:localizedDescription())
app:terminate(nil)
end
function CD:connectionDidFinishLoading(conn)
NSLog'Connection finished loading'
local html = NSString:alloc():initWithBytes_length_encoding(self.webData:mutableBytes(),
self.webData:length(), NSUTF8StringEncoding)
NSLog('OUTPUT:\n%@', html)
app:terminate(nil)
end
local cd = ConnDelegate:new()
cd.webData = NSMutableData:new()
local conn = NSURLConnection:alloc():initWithRequest_delegate_startImmediately(request, cd, false)
conn:start()
app:run()
end
function demo.http_gcd()
objc.load'AppKit'
local app = NSApplication:sharedApplication()
local url = NSURL:URLWithString'http://posttestserver.com/post.php'
local req = NSURLRequest:requestWithURL(url)
local queue = dispatch.main_queue --dispatch.get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
objc.debug.logtopics.block = true
local n = 0
local blk = block(function()
n = n + 1
print('called', n)
local response = ffi.new'id[1]'
local err = ffi.new'id[1]'
local data = NSURLConnection:sendSynchronousRequest_returningResponse_error(req, response, err)
print(tolua(NSString:alloc():initWithBytes_length_encoding(
data:mutableBytes(), data:length(), NSUTF8StringEncoding)))
if n == 2 then
print'---- Done. Hit Ctrl+C twice ----'
end
end)
dispatch.async(queue, blk) --increase refcount
dispatch.async(queue, blk) --increase refcount
print'queued'
blk = nil; collectgarbage() --decrease refcount (stil queued)
print'released'
app:run()
end
-- inspection ------------------------------------------------------------------------------------------------------------
local function load_many_frameworks()
objc.debug.loaddeps = true
for s in string.gmatch([[
AGL
AVFoundation
AVKit
Accelerate
Accounts
AddressBook
AppKit
AppKitScripting
AppleScriptKit
AppleScriptObjC
AppleShareClientCore
ApplicationServices
AudioToolbox
AudioUnit
AudioVideoBridging
Automator
CFNetwork
CalendarStore
Carbon
Cocoa
Collaboration
CoreAudio
CoreAudioKit
CoreData
CoreFoundation
CoreGraphics
CoreLocation
CoreMIDI
CoreMedia
CoreMediaIO
CoreServices
CoreText
CoreVideo
CoreWLAN
DVComponentGlue
DVDPlayback
DirectoryService
DiscRecording
DiscRecordingUI
DiskArbitration
DrawSprocket
EventKit
ExceptionHandling
FWAUserLib
ForceFeedback
Foundation
GLKit
GLUT
GSS
GameController
GameKit
ICADevices
IMServicePlugIn
IOBluetooth
IOBluetoothUI
IOKit
IOSurface
ImageCaptureCore
ImageIO
InputMethodKit
InstallerPlugins
InstantMessage
JavaFrameEmbedding
JavaScriptCore
Kerberos
LDAP
LatentSemanticMapping
MapKit
MediaAccessibility
MediaLibrary
MediaToolbox
NetFS
OSAKit
OpenAL
OpenCL
OpenDirectory
OpenGL
PCSC
PreferencePanes
PubSub
QTKit
Quartz
QuartzCore
QuickLook
SceneKit
ScreenSaver
Scripting
ScriptingBridge
Security
SecurityFoundation
SecurityInterface
ServiceManagement
Social
SpriteKit
StoreKit
SyncServices
System
SystemConfiguration
TWAIN
Tcl
Tk
VideoDecodeAcceleration
VideoToolbox
WebKit
]], '([^\n\r]+)') do
pcall(objc.load, s)
end
objc.debug.loaddeps = false
end
function eyetest.inspect_classes()
load_many_frameworks()
inspect.classes()
end
function eyetest.inspect_protocols()
load_many_frameworks()
inspect.protocols()
end
function eyetest.inspect_class_properties(cls)
load_many_frameworks()
inspect.class_properties(cls)
end
function eyetest.inspect_protocol_properties(proto)
load_many_frameworks()
inspect.protocol_properties(proto)
end
local function req(s)
return s and s ~= '' and s or nil
end
function eyetest.inspect_class_methods(cls, inst)
load_many_frameworks()
inspect.class_methods(req(cls), inst == 'inst')
end
function eyetest.inspect_protocol_methods(proto, inst, required)
load_many_frameworks()
inspect.protocol_methods(req(proto), inst == 'inst', required == 'required')
end
function eyetest.inspect_class_ivars(cls)
load_many_frameworks()
inspect.class_ivars(req(cls))
end
function eyetest.inspect_class(cls)
load_many_frameworks()
inspect.class(cls)
end
function eyetest.inspect_protocol(proto)
load_many_frameworks()
inspect.protocol(proto)
end
function eyetest.inspect_find(patt)
load_many_frameworks()
inspect.find(patt)
end
--------------
local function test_all(tests, ...)
for k,v in glue.sortedpairs(tests) do
if k ~= 'all' then
print(k)
hr()
tests[k](...)
end
end
end
function test.all(...)
test_all(test)
end
--cmdline interface
local function run(...)
local testname = ...
if not testname then
print('Usage: '..luajit..' '..arg[0]..' <test>')
for k,t in glue.sortedpairs(tests) do
printf('%s:', k)
for k in glue.sortedpairs(t) do
print('', k)
end
end
else
local test = test[testname] or eyetest[testname] or demo[testname]
if not test then
printf('Invalid test "%s"', tostring(testname))
os.exit(1)
end
test(select(2, ...))
print'ok'
end
end
run(...)

25
player.lua 100644
View File

@ -0,0 +1,25 @@
player={}
player.x=0
player.y=0
player.speed=0.2
player.direction=0
player.lastMoveTime=0
player.weapon={}
function player.update(dt)
local now=love.timer.getTime()
if (player.direction~=0) and (now-player.lastMoveTime>=player.speed) then
player.move(player.x+player.direction)
end -- check direction
end -- update
function player.move(dx)
player.lastMoveTime=love.timer.getTime()
if dx<0 or dx>game.field.width then
-- boundary shit goes here
else
player.x=dx
love.audio.setPosition(player.x, player.y, 0)
end -- if boundary
end

48
soundgroups.lua 100644
View File

@ -0,0 +1,48 @@
local fs=love.filesystem
group=object:extend()
function group:addDirectory(directory) files=fs.getDirectoryItems(directory)
for i,currentfile in ipairs(files) do
fullpath=directory .."/" ..currentfile
if not fs.isDirectory(fullpath) and not (currentfile:sub(1,1)==".") then
if fs.getInfo(fullpath, file) then
nameOnly=string.match(currentfile, "%P+") --%P matches all non punctuation characters
sound=love.audio.newSource(fullpath, "static")
if sound:typeOf("Source") then
--print("Loaded " .. nameOnly)
if sound:getChannelCount()==1 then sound:setRelative(true) end
self[nameOnly]=sound
self.sounds[nameOnly] =sound
end --if is a sound
end --if it's a file
end -- if it's not a dot
end --for
end --addDirectory
function group:new(directory)
self.sounds={}
self:addDirectory(directory)
end
function group:setEffect(effect)
--print("setting effects on group")
for i,currentsource in pairs(self) do
if currentsource.typeOf~=nil and currentsource:typeOf("Source") then
if currentsource:setEffect(effect) then
--print("effect set on " ..i)
else
print("failed to set effect on " ..i)
end
end --if it's a source
end --for loop
end --setEffect function
t={
a=2,
b=3
}

70
tick.lua 100644
View File

@ -0,0 +1,70 @@
-- tick
-- https://github.com/bjornbytes/tick
-- MIT License
local tick = {
framerate = nil,
rate = .03,
timescale = 1,
sleep = .001,
dt = 0,
accum = 0,
tick = 1,
frame = 1
}
local timer = love.timer
local graphics = love.graphics
love.run = function()
if not timer then
error('love.timer is required for tick')
end
if love.load then love.load(love.arg.parseGameArguments(arg), arg) end
timer.step()
local lastframe = 0
if love.update then love.update(0) end
return function()
tick.dt = timer.step() * tick.timescale
tick.accum = tick.accum + tick.dt
while tick.accum >= tick.rate do
tick.accum = tick.accum - tick.rate
if love.event then
love.event.pump()
for name, a, b, c, d, e, f in love.event.poll() do
if name == 'quit' then
if not love.quit or not love.quit() then
return a or 0
end
end
love.handlers[name](a, b, c, d, e, f)
end
end
tick.tick = tick.tick + 1
if love.update then love.update(tick.rate) end
end
while tick.framerate and timer.getTime() - lastframe < 1 / tick.framerate do
timer.sleep(.0005)
end
lastframe = timer.getTime()
if graphics and graphics.isActive() then
graphics.origin()
graphics.clear(graphics.getBackgroundColor())
tick.frame = tick.frame + 1
if love.draw then love.draw() end
graphics.present()
end
timer.sleep(tick.sleep)
end
end
return tick

31
ticker.lua 100644
View File

@ -0,0 +1,31 @@
ticker=object:extend()
function ticker:new(tickTime)
self.tickTime=tickTime
self.ticks=0
self.lastTickTime=-1
end
function ticker:update(dt)
if self.lastTickTime==-1 then
return false
end
local now=love.timer.getTime()
local diff=now-self.lastTickTime
if diff>=self.tickTime then
self.lastTickTime=now+(self.tickTime-diff)
self.ticks=self.ticks+1
self:tick()
return true
end
end
function ticker:calibrate(time)
self.lastTickTime=time
self.ticks=1 -- can't be bothered to be elegant tbh
self.tick()
end
function ticker:tick()
-- do stuff here
end

16
timeEvent.lua 100644
View File

@ -0,0 +1,16 @@
timeEvent=object:extend()
function timeEvent:new(i,func, recurring)
self.i=i -- either period or counter, depending on recurring
this.func=func
this.recurring=recurring or false
end
function timeEvent:update()
if this.recurring
else
end
end

71
tolk.lua 100644
View File

@ -0,0 +1,71 @@
local ffi = require "ffi"
local encoding = require "encoding"
ffi.cdef[[
void Tolk_Load();
void Tolk_Output(const char *s, bool interrupt);
void Tolk_Silence();
void Tolk_Speak(const char *s, bool interrupt);
void Tolk_Braille(const char *s, bool interrupt);
void Tolk_TrySAPI(bool try);
void Tolk_PreferSAPI(bool prefer);
const wchar_t * Tolk_DetectScreenReader();
bool Tolk_HasSpeech();
bool Tolk_HasBraille();
bool Tolk_IsSpeaking();]]
local tolk = ffi.load("Tolk")
tolk.Tolk_Load()
local function output(s, interrupt)
interrupt=interrupt or false
tolk.Tolk_Output(encoding.to_utf16(s), interrupt)
end
local function speak(s, interrupt)
interrupt=interrupt or false
tolk.Tolk_Speak(encoding.to_utf16(s), interrupt)
end
local function braille(s)
tolk.Tolk_Braille(encoding.to_utf16(s))
end
local function silence()
tolk.Tolk_Silence()
end
local function trySAPI(try)
tolk.Tolk_TrySAPI(try)
end
local function preferSAPI(prefer)
tolk.Tolk_PreferSAPI(prefer)
end
local function isSpeaking()
return tolk.Tolk_IsSpeaking()
end
local function detectScreenReader()
--todo, need to convert the returned value to something Lua likes
end
local function hasSpeech()
return tolk.Tolk_HasSpeech()
end
local function hasBraille()
return tolk.Tolk_HasBraille()
end
return {
output=output,
speak=speak,
braille=braille,
silence=silence,
isSpeaking=isSpeaking,
trySAPI=trySAPI,
preferSAPI=preferSAPI,
detectScreenReader=detectScreenReader,
hasSpeech=hasSpeech,
hasBraille=hasBraille}

7
track.lua 100644
View File

@ -0,0 +1,7 @@
track=object:extend()
function track:new(info)
self.info=info
self.music=love.audio.newSource(info.path,"static")
self.timeStep=60/info.bpm/self.info.beatDivisions
end

View File

@ -0,0 +1,9 @@
local t={
path="audio/tracks/clicky.flac",
bpm=60,
startTime=2,
beatDivisions=4,
volumeBase=1
}
return track(t)

View File

@ -0,0 +1,9 @@
local t={
path="audio/tracks/gourmet.mp3",
bpm=160,
startTime=0.05,
beatDivisions=4,
volumeBase=0
}
return track(t)

12
utils.lua 100644
View File

@ -0,0 +1,12 @@
utils={}
function utils.accuracy(beatDivisions, time)
local ticksToPrevDiv=(game.ticker.ticks-1)%beatDivisions
local ticksToNextDiv=beatDivisions-ticksToPrevDiv
local prevDivTime=game.ticker.lastTickTime-ticksToPrevDiv*game.ticker.tickTime
local nextDivTime=game.ticker.lastTickTime+ticksToNextDiv*game.ticker.tickTime
local closest=math.min(math.abs(time-prevDivTime), math.abs(time-nextDivTime))
local halfDivision=beatDivisions/2*game.ticker.tickTime
-- print(math.abs(time-nextDivTime),"to next beat, ",math.abs(time-prevDivTime)," to previous beat. Half division is ",halfDivision,". Closest is ",closest,". The current time is ",time,", and the next division is at ", nextDivTime,", last tick happened at ",game.ticker.lastTickTime," and is tick number ",game.ticker.ticks)
return (halfDivision-closest)/halfDivision -- Math is hard
end