Given is this object:
foo = { }
function foo:new( p1, p2 )
local object = {}
setmetatable( object, self )
self.__index = self
object .p1 = p1
object .p2 = p2
return object
end
function foo:bar( boolbar )
self.p1 = not boolbar
print( self.p1 )
end
Now I got another object, which is calling foos function by using a reference to foo ( foo is not accessable directly ) and passing an argument to it. Somehow, self becomes the argument.
reference_to_foo = foo
function the_other_object:some_func()
-- does some stuff
reference_to_foo:bar( true )
end
Calling the foo:bar() works well, but per reference_to_foo the index self becomes the argument (boolean in this case).
In :bar(), I'll get this error:
attempt to index local 'self' (a boolean value)
Why is that and how can I solve this?
Related
I have multiple exported variables in my script and anytime a single one is changed I want to invoke a common getter and let the values be set automatically
tool
export(float) var sample1 setget ,smthn_changed;
export(float) var sample2 setget ,smthn_changed;
export(float) var sample3 setget ,smthn_changed;
func smthn_changed():
print("something changed!")
but this doesn't work and I have to create a setter for every single variable
is there any solution around this?
Please notice that you are defining smthn_changed as getter for those properties. And the getters are called when you try to read them, not when you try to assign them.
Alright, let us say you do want to know when the variables are being assigned. For that you would usually use setters, like this:
export var property:bool setget set_property
func set_property(new_value:bool) -> void:
if property == new_value:
return
property = new_value
print("value changed") # or emit a signal or whatever
The setter will be called at any time the variable is asignad externally (or internally with self.property = value, if you don't use self you can assign the variable directly without trigering the setter).
However, since you need to write the actual variable from the setter, this implies making a setter for each variable (if you used the same setter for multiple variable, you would not know which to set).
There is something else you can try: _set. The issue with _set is that will only be called for variables that are not declared in the script.
So, here is the plan:
We are going to declare backing variables with a different name, not export them.
We are going to use _set and _set to handle them.
And we are going to use _get_property_list to export them.
Let us see the case of just one variable:
tool
extends Spatial
var _x:String setget _no_set
func _set(property: String, value) -> bool:
if property == "x":
_x = value
smth_changed()
return true
return false
func _get(property: String):
if property == "x":
return _x
return null
func _get_property_list() -> Array:
if not Engine.editor_hint or not is_inside_tree():
return []
return [
{
name = "x",
type = TYPE_STRING,
usage = PROPERTY_USAGE_DEFAULT
}
]
func _no_set(_new_value) -> void:
pass
func smth_changed() -> void:
print("something changed!")
That is not worth the effort compared to a simple setter.
The setter _no_set is a setter that does nothing (not even set the variable). I have added it to prevent bypassing the mechanism externally by setting to the backing variable directly. You could add a warning there, as that is not something you code should be doing. On the flip the fact that your code should not be doing it could also be taken as an argument against having _no_set.
But let us see how it scales to multiple variables:
tool
extends Spatial
var _x:String setget _no_set
var _y:String setget _no_set
func _set(property: String, value) -> bool:
match property:
"x":
_x = value
"y":
_y = value
_:
return false
smth_changed()
return true
func _get(property: String):
match property:
"x":
return _x
"y":
return _y
return null
func _get_property_list() -> Array:
if not Engine.editor_hint or not is_inside_tree():
return []
return [
{
name = "x",
type = TYPE_STRING,
usage = PROPERTY_USAGE_DEFAULT
},
{
name = "y",
type = TYPE_STRING,
usage = PROPERTY_USAGE_DEFAULT
}
]
func _no_set(_new_value) -> void:
pass
func smth_changed() -> void:
print("something changed!")
Still not great, since we are having to repeat the variables multiple times. I would still prefer to have multiple setters, even if they all have the same code.
A generic case for an arbitrary set of properties is tricky, because calling get from _get, or set from _set, or get_property_list form _get_property_list in such way that it causes a stack overflow will crash Godot (and continue crashing it upon opening the project). So be careful when writing this code.
What I'm going to do to avoid calling get_property_list from _get_property_list is to put the properties we want in a dictionary:
tool
extends Spatial
var _properties := {
"x": "",
"y": ""
} setget _no_set, _no_get
func _set(property: String, value) -> bool:
if _properties.has(property):
_properties[property] = value
smth_changed()
return true
return false
func _get(property: String):
if _properties.has(property):
return _properties[property]
return null
func _get_property_list() -> Array:
if not Engine.editor_hint or not is_inside_tree():
return []
var result := []
for property_name in _properties.keys():
result.append(
{
name = property_name,
type = typeof(_properties[property_name]),
usage = PROPERTY_USAGE_DEFAULT
}
)
return result
func _no_set(_new_value) -> void:
pass
func _no_get():
return null
func smth_changed() -> void:
print("something changed!")
Notice also that I'm reporting the type based on the value with typeof.
I'll leave it to you to decide if this approach is worth the effort. It might be, if the set of variables can change, for example. And I remind you that you can call property_list_changed_notify so that Godot calls _get_property_list and updates the inspector panel with the new set of properties.
Despite the _no_set, the dictionary could still be read and manipulated externally. So I added a getter _no_get that returns null to prevent that. If you like a warning in your _no_set, you may want a warning in your _no_get too.
Addendum: Here is a variation that uses an array for the names of the properties you want to export. This way you can still have regular variables instead of dealing with a dictionary. It is up to you to keep the array up to date.
tool
extends Spatial
var _property_names := ["x", "y"] setget _no_set, _no_get
var _x:String
var _y:String
func _set(property: String, value) -> bool:
if _property_names.has(property):
set("_" + property, value)
smth_changed()
return true
return false
func _get(property: String):
if _property_names.has(property):
return get("_" + property)
return null
func _get_property_list() -> Array:
if not Engine.editor_hint or not is_inside_tree():
return []
var result := []
for property_name in _property_names:
if not "_" + property_name in self:
push_warning("Not existing variable: " + property_name)
continue
result.append(
{
name = property_name,
type = typeof(get("_" + property_name)),
usage = PROPERTY_USAGE_DEFAULT
}
)
return result
func _no_set(_new_value) -> void:
pass
func _no_get():
return null
func smth_changed() -> void:
print("something changed!")
Note that I have added a check to prevent exporting without a backing variable, which also pushes a warning. It is not catastrophic to expose them as they would just be handled as null.
Also note that I had to remove the _no_set from the variables in this version. The reason being that I set them with set, which results in calling the setter, and since the _no_set didn't set the variable the result was it wasn't saving the values.
Addendum on resetting the value
If you want to add that arrow to reset the value you need to implement a couple of (yikes) undocumented methods:
func property_can_revert(property:String) -> bool:
if property in self:
return true
return false
func property_get_revert(property:String):
match typeof(get(property)):
TYPE_NIL:
return null
TYPE_BOOL:
return false
TYPE_INT:
return 0
TYPE_REAL:
return 0.0
TYPE_STRING:
return ""
TYPE_VECTOR2:
return Vector2()
TYPE_RECT2:
return Rect2()
TYPE_VECTOR3:
return Vector3()
TYPE_TRANSFORM2D:
return Transform2D()
TYPE_PLANE:
return Plane()
TYPE_QUAT:
return Quat()
TYPE_AABB:
return AABB()
TYPE_BASIS:
return Basis()
TYPE_TRANSFORM:
return Transform()
TYPE_COLOR:
return Color()
TYPE_NODE_PATH:
return NodePath()
TYPE_RID:
return RID(Object())
TYPE_OBJECT:
return Object()
TYPE_DICTIONARY:
return {}
TYPE_ARRAY:
return []
TYPE_RAW_ARRAY:
return PoolByteArray()
TYPE_INT_ARRAY:
return PoolIntArray()
TYPE_REAL_ARRAY:
return PoolRealArray()
TYPE_STRING_ARRAY:
return PoolStringArray()
TYPE_VECTOR2_ARRAY:
return PoolVector2Array()
TYPE_VECTOR3_ARRAY:
return PoolVector3Array()
TYPE_COLOR_ARRAY:
return PoolColorArray()
return null
The idea is that property_can_revert will return true for any property that will have the reset arrow. And property_get_revert will give the value that will be set when you click said arrow. This had to be found in the source code since it is not documented.
How can I add a method to the string table and modify self inside it ?
Basically, I'm trying to mimic the behaviour of the io.StringIO.read method in python, which reads n char in the string and returns them, modifying the string by "consuming" it.
I tried this:
function string.read(str, n)
to_return = str:sub(1, n)
str = str:sub(n + 1)
return to_return
end
local foo = "heyfoobarhello"
print(string.read(foo, 3))
print(foo)
Output is:
hey
heyfoobarhello
I expected the second line to be only foobarhello.
How can I achieve this ?
To mimic Python's io.StringIO class, you must make an object that stores both the underlying string and the current position within that string. Reading from an IO stream normally does not modify the underlying data.
local StringIO_mt = {
read = function(self, n)
n = n or #self.buffer - self.position + 1
local result = self.buffer:sub(self.position, self.position + n - 1)
self.position = self.position + n
return result
end,
}
StringIO_mt.__index = StringIO_mt
local function StringIO(buffer)
local o = {buffer = buffer, position = 1}
setmetatable(o, StringIO_mt)
return o
end
local foo = StringIO"heyfoobarhello"
print(foo:read(3))
print(foo:read())
Output:
hey
foobarhello
I don't recommend adding this class or method to Lua's string library, because the object has to be more complex than just a string.
You can add methods to the datatype string independently from the string table.
Short example that shows that the string methods even work if string table gets deleted...
string=nil
return _VERSION:upper():sub(1,3)
-- Returning: LUA
So you can add a method...
-- read.lua
local read = function(self, n1, n2)
return self:sub(n1, n2)
end
getmetatable(_VERSION).__index.read=read
return read
...for all strings.
( Not only _VERSION )
And use it...
do require('read') print(_VERSION:read(1,3):upper()) end
-- Print out: LUA
Can anyone fix my buggy Lua object system?
Different instances have different numeric fields
But .. when I add tables to my initialization fields, then those tables are shared between different instances (see example, below).
What I think is that I need to do a deep copy on initial fields but I can't see where. My code is below. Any suggestions?
Object = {}
function Object:new (o)
-- o = deep_copy(o) or {} -- <== this didn't work
-- self = deep copy(self) -- <== this didn't work
o = o or {}
setmetatable(o, self)
self.__index = self
self.__tostring = show
return o
end
Account = Object:new{balance = 0,all={}}
function Account:push(v)
self.all[#self.all+1] = v
end
function Account:deposit(v)
self.balance = self.balance + v end
function Account:withdraw (v)
if v > self.balance then
error"insufficient funds" end
self.balance = self.balance - v
end
function show(i, str,sep)
str,sep = "{",""
for k,v in pairs(i) do
if type(v) ~= "function" then
str = str..sep..tostring(k)..":"..tostring(v)
sep = ", "
end
end
return str .. "}"
end
Just to illustrate the problem, below I have two instances a and b
When I update a,b's numeric fields, then different instances get different values.
But when I update the all table in one instance a, it changes it in the other instance b.
a=Account:new()
b=Account:new()
a:deposit(100)
b:deposit(200)
b:push(10)
b:push(20)
a:push(300)
print("a all", show(a), show(a.all))
print("b all", show(b), show(b.all))
The output should be:
a all {balance:100} {3:300}
b all {balance:200} {1:10, 2:20}
But what actually comes out is:
a all {balance:100} {1:10, 2:20, 3:300}
b all {balance:200} {1:10, 2:20, 3:300}
You do self.__index = self, and your prototype has an all field, but you never set one in the objects that you create. As a result, accesses to it (as push does) will always go through __index and end up hitting the prototype.
As Joseph said, you need to create distinct all for every object.
You could either write function Account:new() or write Object:new() function which accepts initializer (so that you would not need to implement distinct :new() for every class):
Object = {__init = function(o) end}
function Object:new(o, init)
-- when creating an instance:
-- o: the object itself
-- when creating a class:
-- o: table containing shared fields and methods
-- init: initializer for instances of this class
o = o or {}
self.__init(o)
setmetatable(o, self)
self.__index = self
if init then
function o.__init(o)
self.__init(o)
init(o)
end
end
return o
end
Account = Object:new({}, function(o) o.all={}; o.balance=0; end)
function Account:push(v)
self.all[#self.all+1] = v
end
function Account:deposit(v)
self.balance = self.balance + v
end
function Account:withdraw (v)
if v > self.balance then
error"insufficient funds" end
self.balance = self.balance - v
end
function show(i, str,sep)
str,sep = "{",""
for k,v in pairs(i) do
if type(v) ~= "function" then
str = str..sep..tostring(k)..":"..tostring(v)
sep = ", "
end
end
return str .. "}"
end
Object.__tostring = show
a=Account:new()
b=Account:new()
a:deposit(100)
b:deposit(200)
b:push(10)
b:push(20)
a:push(300)
print("a all", show(a), show(a.all))
print("b all", show(b), show(b.all))
If you'd like to inherit from Object, easiest is to create a new constructor for account as well. This should set a balance and and all for each object, not on the class itself.
Object = {}
Account = {}
function Object:new(o)
o = o or {}
setmetatable(o, self)
self.__index = self
self.__tostring = show
return o
end
function Account:new(o)
o = o or Object:new(o)
setmetatable(o, self)
self.__index = self
o.balance = 0
o.all = {}
return o
end
a all {all:table: 0x55ab93ec70d0, balance:100} {1:300}
b all {all:table: 0x55ab93ec6ed0, balance:200} {1:10, 2:20}
This is my simple groovy script;
def fourtify(String str) {
def clsr = {
str*4
}
return clsr
}
def c = fourtify("aa")
println("binding variables: ${c.getBinding().getVariables()}")
...
All I'm trying to do here is being able to access the free variable "str" using the closure instance to understand how closure works behind the scenes a bit more better. Like, perhaps, Python's locals() method.
Is there a way to do this?
The closure you have defined does not store anything in binding object - it simply returns String passed as str variable, repeated 4 times.
This binding object stores all variables that were defined without specifying their types or using def keyword. It is done via Groovy metaprogramming feature (getProperty and setProperty methods to be more specific). So when you define a variable s like:
def clsr = {
s = str*4
return s
}
then this closure will create a binding with key s and value evaluated from expression str * 4. This binding object is nothing else than a map that is accessed via getProperty and setProperty method. So when Groovy executes s = str * 4 it calls setProperty('s', str * 4) because variable/property s is not defined. If we make a slightly simple change like:
def clsr = {
def s = str*4 // or String s = str * 4
return s
}
then binding s won't be created, because setProperty method does not get executed.
Another comment to your example. If you want to see anything in binding object, you need to call returned closure. In example you have shown above the closure gets returned, but it never gets called. If you do:
def c = fourtify("aa")
c.call()
println("binding variables: ${c.getBinding().getVariables()}")
then your closure gets called and binding object will contain bindings (if any set). Now, if you modify your example to something like this:
def fourtify(String str) {
def clsr = {
def n = 4 // it does not get stored as binding
s = str * n
return s
}
return clsr
}
def c = fourtify("aa")
c.call()
println("binding variables: ${c.getBinding().getVariables()}")
you will see following output in return:
binding variables: [args:[], s:aaaaaaaa]
Hope it helps.
in your example str is a parameter of the method/function fortify
however maybe following example will give you better Closure understanding:
def c={ String s,int x-> return s*x }
println( c.getClass().getSuperclass() ) // groovy.lang.Closure
println( c.getMaximumNumberOfParameters() ) // 2
println( c.getParameterTypes() ) // [class java.lang.String, int]
the locals() Python's function better matches groovy.lang.Script.getBinding()
and here is a simple example with script:
Script scr = new GroovyShell().parse('''
println this.getBinding().getVariables() // print "s" and "x"
z = s*(x+1) // declare a new script-level var "z"
println this.getBinding().getVariables() // print "s", "x", and "z"
return s*x
''')
scr.setBinding( new Binding([
"s":"ab",
"x":4
]) )
println scr.run() // abababab
println scr.getBinding().getVariables() // print "s", "x", and "z"
I'd like to call a closure with a delegate parameter to override or shadow the calling context. But the following example prints prints "outside" where I expect "inside".
What am I doing wrong?
def f(String a){
def v = { return a }
v.delegate = [a:"inside"]
// Makes no difference:
// v.resolveStrategy = Closure.DELEGATE_FIRST
println(v.call())
}
f("outside")
I believe the issue is that when the closure is declared inside the function, it 'closes' round the known values in the method (a), so that value becomes effectively hard-coded into the closure (it never hits the delegate to find the unknown value as it is known to the Closure).
If you move the closure v definition outside of the function f, then it works:
v = { return a }
def f(String a){
v.delegate = [a:"inside"]
println(v.call())
}
f("outside")
Other option is to use getProperty('a') instead of directly using a as this forces the use of the delegate to retrieve the value of a.
Can also be done by referring the delegate in the closure. For v as a closure, a does not make any sense (equivalent to use of ExpandoMetaClass)
def f(String a){
def v = { delegate.a }
v.delegate = [a:"inside"]
println v()
}
f("outside")