diff --git a/src/main/java/li/cil/oc/api/machine/Callback.java b/src/main/java/li/cil/oc/api/machine/Callback.java index 81749183b..64bf4858b 100644 --- a/src/main/java/li/cil/oc/api/machine/Callback.java +++ b/src/main/java/li/cil/oc/api/machine/Callback.java @@ -95,9 +95,18 @@ public @interface Callback { * Whether this callback should work like a getter. *
* Callbacks that are getters do not appear as methods on a component's - * proxy. Instead they are accessed via the proxy's __index - * 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 __index metamethod, with it's only parameter being the + * accessed key. + * + * 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. + * + * Important: this only works in environments (for components), + * it does not work for userdata (Value objects). For + * userdata, use the apply method instead. */ boolean getter() default false; @@ -105,9 +114,18 @@ public @interface Callback { * Whether this callback should work like a setter. * * Callbacks that are setters do not appear as methods on a component's - * proxy. Instead they are accessed via the proxy's __newindex - * 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 __newindex metamethod, with it's only tow parameters + * being the accessed key and the new value. + * + * 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. + * + * Important: this only works in environments (for components), + * it does not work for userdata (Value objects). For + * userdata, use the unapply method instead. */ boolean setter() default false; } diff --git a/src/main/resources/assets/opencomputers/lua/kernel.lua b/src/main/resources/assets/opencomputers/lua/kernel.lua index 15ede5df7..a8269a9b0 100644 --- a/src/main/resources/assets/opencomputers/lua/kernel.lua +++ b/src/main/resources/assets/opencomputers/lua/kernel.lua @@ -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, diff --git a/src/main/scala/li/cil/oc/server/machine/luac/ComponentAPI.scala b/src/main/scala/li/cil/oc/server/machine/luac/ComponentAPI.scala index f58cb961d..5d020153b 100644 --- a/src/main/scala/li/cil/oc/server/machine/luac/ComponentAPI.scala +++ b/src/main/scala/li/cil/oc/server/machine/luac/ComponentAPI.scala @@ -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 diff --git a/src/main/scala/li/cil/oc/server/machine/luaj/ComponentAPI.scala b/src/main/scala/li/cil/oc/server/machine/luaj/ComponentAPI.scala index e52994442..34cfbff2c 100644 --- a/src/main/scala/li/cil/oc/server/machine/luaj/ComponentAPI.scala +++ b/src/main/scala/li/cil/oc/server/machine/luaj/ComponentAPI.scala @@ -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 })