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
if not info.getter and not info.setter then
proxy[method] = setmetatable({address=address,name=method}, componentCallback) proxy[method] = setmetatable({address=address,name=method}, componentCallback)
proxyDirectCache[proxy[method]] = direct 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
}) })