Lua-side implementation of getter/setter callbacks.

This commit is contained in:
Florian Nücke 2014-10-06 14:16:15 +02:00
parent 818caae9d0
commit b3862a99d1
4 changed files with 126 additions and 24 deletions

View File

@ -95,9 +95,18 @@ public @interface Callback {
* Whether this callback should work like a getter. * Whether this callback should work like a getter.
* <p/> * <p/>
* Callbacks that are getters do not appear as methods on a component's * Callbacks that are getters do not appear as methods on a component's
* proxy. Instead they are accessed via the proxy's <tt>__index</tt> * proxy. Instead they are accessed as fields, for example in Lua via the
* metamethod, i.e. it is accessed as a field, with it's only parameter * proxy's <tt>__index</tt> metamethod, with it's only parameter being the
* being the accessed key. * accessed key.
* <p/>
* Note: if you wish to have a field that is read/write, that is you need
* both a getter and a setter, you have to implement them in the same
* method. This a limitation due to callback names being unique. You can
* differentiate between contexts by checking the number of arguments.
* <p/>
* <em>Important</em>: this only works in environments (for components),
* it does <em>not</em> work for userdata (<tt>Value</tt> objects). For
* userdata, use the <tt>apply</tt> method instead.
*/ */
boolean getter() default false; boolean getter() default false;
@ -105,9 +114,18 @@ public @interface Callback {
* Whether this callback should work like a setter. * Whether this callback should work like a setter.
* <p/> * <p/>
* Callbacks that are setters do not appear as methods on a component's * Callbacks that are setters do not appear as methods on a component's
* proxy. Instead they are accessed via the proxy's <tt>__newindex</tt> * proxy. Instead they are accessed as fields, for example in Lua via the
* metamethod, i.e. it is accessed as a field, with it's only parameters * proxy's <tt>__newindex</tt> metamethod, with it's only tow parameters
* being the accessed key and the assigned value. * being the accessed key and the new value.
* <p/>
* Note: if you wish to have a field that is read/write, that is you need
* both a getter and a setter, you have to implement them in the same
* method. This a limitation due to callback names being unique. You can
* differentiate between contexts by checking the number of arguments.
* <p/>
* <em>Important</em>: this only works in environments (for components),
* it does <em>not</em> work for userdata (<tt>Value</tt> objects). For
* userdata, use the <tt>unapply</tt> method instead.
*/ */
boolean setter() default false; boolean setter() default false;
} }

View File

@ -468,12 +468,65 @@ end
local libcomponent local libcomponent
-- Caching proxy objects for lower memory use.
local proxyCache = setmetatable({}, {__mode="v"}) local proxyCache = setmetatable({}, {__mode="v"})
local proxyDirectCache = setmetatable({}, {__mode="k"})
-- Short-term caching of callback directness for improved performance.
local directCache = setmetatable({}, {__mode="k"})
local function isDirect(address, method)
local cacheKey = address..":"..method
local cachedValue = directCache[cacheKey]
if cachedValue ~= nil then
return cachedValue
end
local methods, reason = spcall(component.methods, address)
if methods then
for name, info in pairs(methods) do
if name == method then
directCache[cacheKey] = info.direct
return info.direct
end
end
end
error("no such method", 1)
end
local componentProxy = {
__index = function(self, key)
if self.fields[key] and self.fields[key].getter then
return libcomponent.invoke(self.address, key)
else
rawget(self, key)
end
end,
__newindex = function(self, key, value)
if self.fields[key] and self.fields[key].setter then
return libcomponent.invoke(self.address, key, value)
elseif self.fields[key] and self.fields[key].getter then
error("field is read-only")
else
rawset(self, key, value)
end
end,
__pairs = function(self)
local keyProxy, keyField, value
return function()
if not keyField then
repeat
keyProxy, value = next(self, keyProxy)
until not keyProxy or keyProxy ~= "fields"
end
if not keyProxy then
keyField, value = next(self.fields, keyField)
end
return keyProxy or keyField, value
end
end
}
local componentCallback = { local componentCallback = {
__call = function(self, ...) __call = function(self, ...)
return invoke(component, not not proxyDirectCache[self], self.address, self.name, ...) return libcomponent.invoke(self.address, self.name, ...)
end, end,
__tostring = function(self) __tostring = function(self)
return libcomponent.doc(self.address, self.name) or "function" return libcomponent.doc(self.address, self.name) or "function"
@ -493,16 +546,7 @@ libcomponent = {
invoke = function(address, method, ...) invoke = function(address, method, ...)
checkArg(1, address, "string") checkArg(1, address, "string")
checkArg(2, method, "string") checkArg(2, method, "string")
local methods, reason = spcall(component.methods, address) return invoke(component, isDirect(address, method), address, method, ...)
if not methods then
return nil, reason
end
for name, direct in pairs(methods) do
if name == method then
return invoke(component, direct, address, method, ...)
end
end
error("no such method", 1)
end, end,
list = function(filter, exact) list = function(filter, exact)
checkArg(1, filter, "string", "nil") checkArg(1, filter, "string", "nil")
@ -516,7 +560,31 @@ libcomponent = {
end}) end})
end, end,
methods = function(address) methods = function(address)
return spcall(component.methods, address) local result, reason = spcall(component.methods, address)
-- Transform to pre 1.4 format to avoid breaking scripts.
if type(result) == "table" then
for k, v in pairs(result) do
if not v.getter and not v.setter then
result[k] = v.direct
else
result[k] = nil
end
end
return result
end
return result, reason
end,
fields = function(address)
local result, reason = spcall(component.methods, address)
if type(result) == "table" then
for k, v in pairs(result) do
if not v.getter and not v.setter then
result[k] = nil
end
end
return result
end
return result, reason
end, end,
proxy = function(address) proxy = function(address)
local type, reason = spcall(component.type, address) local type, reason = spcall(component.type, address)
@ -530,15 +598,19 @@ libcomponent = {
if proxyCache[address] then if proxyCache[address] then
return proxyCache[address] return proxyCache[address]
end end
local proxy = {address = address, type = type, slot = slot} local proxy = {address = address, type = type, slot = slot, fields = {}}
local methods, reason = spcall(component.methods, address) local methods, reason = spcall(component.methods, address)
if not methods then if not methods then
return nil, reason return nil, reason
end end
for method, direct in pairs(methods) do for method, info in pairs(methods) do
proxy[method] = setmetatable({address=address,name=method}, componentCallback) if not info.getter and not info.setter then
proxyDirectCache[proxy[method]] = direct proxy[method] = setmetatable({address=address,name=method}, componentCallback)
else
proxy.fields[method] = info
end
end end
setmetatable(proxy, componentProxy)
proxyCache[address] = proxy proxyCache[address] = proxy
return proxy return proxy
end, end,

View File

@ -57,7 +57,13 @@ class ComponentAPI(owner: NativeLuaArchitecture) extends NativeLuaAPI(owner) {
lua.newTable() lua.newTable()
for ((name, annotation) <- machine.methods(component.host)) { for ((name, annotation) <- machine.methods(component.host)) {
lua.pushString(name) lua.pushString(name)
lua.newTable()
lua.pushBoolean(annotation.direct) lua.pushBoolean(annotation.direct)
lua.setField(-2, "direct")
lua.pushBoolean(annotation.getter)
lua.setField(-2, "getter")
lua.pushBoolean(annotation.setter)
lua.setField(-2, "setter")
lua.rawSet(-3) lua.rawSet(-3)
} }
1 1

View File

@ -48,7 +48,13 @@ class ComponentAPI(owner: LuaJLuaArchitecture) extends LuaJAPI(owner) {
withComponent(args.checkjstring(1), component => { withComponent(args.checkjstring(1), component => {
val table = LuaValue.tableOf() val table = LuaValue.tableOf()
for ((name, annotation) <- machine.methods(component.host)) { for ((name, annotation) <- machine.methods(component.host)) {
table.set(name, LuaValue.valueOf(annotation.direct)) table.set(name, LuaValue.tableOf(Array(
LuaValue.valueOf("direct"),
LuaValue.valueOf(annotation.direct),
LuaValue.valueOf("getter"),
LuaValue.valueOf(annotation.getter),
LuaValue.valueOf("setter"),
LuaValue.valueOf(annotation.setter))))
} }
table table
}) })