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.
* <p/>
* 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>
* metamethod, i.e. it is accessed as a field, with it's only parameter
* being the accessed key.
* proxy. Instead they are accessed as fields, for example in Lua via the
* proxy's <tt>__index</tt> metamethod, with it's only parameter being the
* 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;
@ -105,9 +114,18 @@ public @interface Callback {
* Whether this callback should work like a setter.
* <p/>
* 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>
* metamethod, i.e. it is accessed as a field, with it's only parameters
* being the accessed key and the assigned value.
* proxy. Instead they are accessed as fields, for example in Lua via the
* proxy's <tt>__newindex</tt> metamethod, with it's only tow parameters
* 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;
}

View File

@ -468,12 +468,65 @@ end
local libcomponent
-- Caching proxy objects for lower memory use.
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 = {
__call = function(self, ...)
return invoke(component, not not proxyDirectCache[self], self.address, self.name, ...)
return libcomponent.invoke(self.address, self.name, ...)
end,
__tostring = function(self)
return libcomponent.doc(self.address, self.name) or "function"
@ -493,16 +546,7 @@ libcomponent = {
invoke = function(address, method, ...)
checkArg(1, address, "string")
checkArg(2, method, "string")
local methods, reason = spcall(component.methods, address)
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)
return invoke(component, isDirect(address, method), address, method, ...)
end,
list = function(filter, exact)
checkArg(1, filter, "string", "nil")
@ -516,7 +560,31 @@ libcomponent = {
end})
end,
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,
proxy = function(address)
local type, reason = spcall(component.type, address)
@ -530,15 +598,19 @@ libcomponent = {
if proxyCache[address] then
return proxyCache[address]
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)
if not methods then
return nil, reason
end
for method, direct in pairs(methods) do
proxy[method] = setmetatable({address=address,name=method}, componentCallback)
proxyDirectCache[proxy[method]] = direct
for method, info in pairs(methods) do
if not info.getter and not info.setter then
proxy[method] = setmetatable({address=address,name=method}, componentCallback)
else
proxy.fields[method] = info
end
end
setmetatable(proxy, componentProxy)
proxyCache[address] = proxy
return proxy
end,

View File

@ -57,7 +57,13 @@ class ComponentAPI(owner: NativeLuaArchitecture) extends NativeLuaAPI(owner) {
lua.newTable()
for ((name, annotation) <- machine.methods(component.host)) {
lua.pushString(name)
lua.newTable()
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)
}
1

View File

@ -48,7 +48,13 @@ class ComponentAPI(owner: LuaJLuaArchitecture) extends LuaJAPI(owner) {
withComponent(args.checkjstring(1), component => {
val table = LuaValue.tableOf()
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
})