Serialization misbehave when closing Unity - serialization

I'm using ISerializationCallbackReceiver to serialize my custom interrelated data following tutorial http://blogs.unity3d.com/2014/06/24/serialization-in-unity/ regarding serialization of graph nodes adjusting it to my needs.
My serialization is working fine when I'm hot swapping the code when I'm developing scripts. But once I save the scene, close Unity and reload the project again, my data is deserializing from some bogus.
[01] [17:23:43] Quest[The Sculpture and The Box|SLEEP]: Serializing QuestStepData[fqcn=experiment.questsjg.steps.GoToLocation|name=Sculpture|description=F|closedByIndex=-1|go1Present=True|go1=sculptures_01_02 (UnityEngine.GameObject)|go2Present=True|go2=[1] Go To Location Point (Sculpture) (UnityEngine.GameObject)|f1=8]
[02] [17:23:43] Quest[The Sculpture and The Box|SLEEP]: Serializing QuestStepData[fqcn=experiment.questsjg.steps.GoToLocation|name=box|description=S|closedByIndex=-1|go1Present=True|go1=box1 (UnityEngine.GameObject)|go2Present=True|go2=[2] Go To Location Point (box) (UnityEngine.GameObject)|f1=4]
[03] [17:24:09] Quest[The Sculpture and The Box|SLEEP]: Deserializing QuestStepData[fqcn=experiment.questsjg.steps.GoToLocation|name=Sculpture|description=|closedByIndex=-1|go1Present=True|go1= (UnityEngine.GameObject)|go2Present=True|go2= (UnityEngine.GameObject)|f1=8]
[04] [17:24:09] Quest[The Sculpture and The Box|SLEEP]: Deserializing QuestStepData[fqcn=experiment.questsjg.steps.GoToLocation|name=|description=|closedByIndex=-1|go1Present=False|go2Present=False|f1=10]
Here is a log of what I'm de/serializing. Unity got restarted between 17:23:45-17:24:00 and as you can see it did not deserialize (Rows 03 and 04) from the same data I serialized (Rows 01 and 02).
When I save the scene, I typically get Rows 01 and 02 logged.
But when these "rows" gets deserialized when I open Unity, I get Rows 03 and 04 and as you can see Row 03 "description" differs from Row 01 and Row 04 is missing everything except 'fqcn' field when compared with Row 02.
Logs are as precise as they can get (I'm writing them to the file), so these serialization lines 01 and 02 should be the last thing Unity is doing with my data when closing...
Now I'm buffled...
Serialization is working flawlessly when hot swapping the code
Not working when restarting Unity ... (the worst thing is that it "drops" only some infos)
Any hints what I might be doing wrong?
(The same happens for 3+ items... only 1st item is somehow correct)
P.S.: It does not matter whether you have Asset serialization set to "Mixed" or "Force Text".
If I have "MIXED" then data within the scene file looks like this:
The Sculpture and The Box ! NyŻ
stepsData.Array.data[0].fqcn&
experiment.questsjg.steps.GoToLocation ! NyŻ %
stepsData.Array.data[0].closedByIndex -1 ! NyŻ
stepsData.Array.data[0].f1 8 ! NyŻ
stepsData.Array.data[0].name Sculpture ! NyŻ
stepsData.Array.data[0].go1 ^ ! NyŻ "
stepsData.Array.data[0].go1Present 1 ! NyŻ
stepsData.Array.data[0].go2 *¨ ! NyŻ "
stepsData.Array.data[0].go2Present 1 ! NyŻ
stepsData.Array.data[1].fqcn&
experiment.questsjg.steps.GoToLocation ! NyŻ %
stepsData.Array.data[1].closedByIndex -1 ! NyŻ
stepsData.Array.data[1].f1 10 ! NyŻ
As you can see, serialized data are not there, just compare stepsData.Array.data[0] and stepsData.Array.data[1]
If I have "Force Text" then data within the scene file looks like this (it's actually even worse):
- target: {fileID: 11499854, guid: bd017908bdedeb24d9559a160e99a0c1, type: 2}
propertyPath: stepsData.Array.data[0].fqcn
value: quests.steps.GoToLocation
objectReference: {fileID: 0}
- target: {fileID: 11499854, guid: bd017908bdedeb24d9559a160e99a0c1, type: 2}
propertyPath: stepsData.Array.data[0].closedByIndex
value: -1
objectReference: {fileID: 0}
- target: {fileID: 11499854, guid: bd017908bdedeb24d9559a160e99a0c1, type: 2}
propertyPath: stepsData.Array.data[0].f1
value: 10
objectReference: {fileID: 0}
- target: {fileID: 11499854, guid: bd017908bdedeb24d9559a160e99a0c1, type: 2}
propertyPath: stepsData.Array.data[1].fqcn
value: quests.steps.GoToLocation
objectReference: {fileID: 0}
- target: {fileID: 11499854, guid: bd017908bdedeb24d9559a160e99a0c1, type: 2}
propertyPath: stepsData.Array.data[1].closedByIndex
value: -1
objectReference: {fileID: 0}
- target: {fileID: 11499854, guid: bd017908bdedeb24d9559a160e99a0c1, type: 2}
propertyPath: stepsData.Array.data[1].f1
value: 10
objectReference: {fileID: 0}
- target: {fileID: 107138, guid: bd017908bdedeb24d9559a160e99a0c1, type: 2}
propertyPath: m_Name
value: First Quest
objectReference: {fileID: 0}
Hmmmm, what's happening in there?

Aaaa!
The trick is to call EditorUtility.SetDirty(gameObject) EVERY TIME ANYTHING CHANGES as this page states: http://docs.unity3d.com/ScriptReference/EditorUtility.SetDirty.html
Unity internally uses the dirty flag to find out when assets have changed and need to be saved to disk.
E.g. if you modify a prefab's MonoBehaviour or ScriptableObject variables, you must tell Unity that the value has changed. Unity builtin components internally call SetDirty whenever a property changes. MonoBehaviour or ScriptableObject DON'T DO this AUTOMATICALLY so if you want your value to be saved you need to call SetDirty.
Therefore, whenever you're creating your CustomEditor following http://docs.unity3d.com/Manual/editor-CustomEditors.html make sure to use the trick:
GUI.changed = false;
// do you're edit code here
if (GUI.changed) SetDirty(yourGameObjectThatHasJustChanged);
Hope this saves someone's time :-)
Cheers!

Related

How to get Cargo Test to Pretty Print Failure Outputs

I have a Rust test that is comparing two structs of a type that implements PartialEq and Debug.
According to the help page for the cargo test output, it should be possible to "pretty print" the left and right arguments to each failed assertion:
$ cargo test_foo --lib -- --help
warning: `google2005` (lib test) generated 4 warnings
Finished test [unoptimized + debuginfo] target(s) in 0.07s
Running unittests (target/debug/deps/foo)
Usage: test_foo [OPTIONS] [FILTERS...]
Options:
--include-ignored
Run ignored and not ignored tests
--ignored Run only ignored tests
...
***** snipped *****
...
--format pretty|terse|json|junit
Configure formatting of output:
pretty = Print verbose output;
terse = Display one character per test;
json = Output a json document;
junit = Output a JUnit document
...
***** snipped *****
I would expect this would print my structs with proper indentation, like when you use the {:#?} pretty print marker for Debug output. But it doesn't seem to have any effect:
$ cargo test test_foo --lib -- --format pretty
Finished test [unoptimized + debuginfo] target(s) in 0.07s
Running unittests (target/debug/deps/foo)
running 1 test
test parser::test::test_walk_scrapes_description ... FAILED
failures:
---- parser::test::test_walk_scrapes_description stdout ----
thread 'parser::test::test_walk_scrapes_description' panicked at 'assertion failed: `(left == right)`
left: `Cat { name: "Fluffy", age: 8, breed: "Siamese", color: "Brown", weight: 12, chip_number: 10001001 }`,
right: `Cat { name: "Fluffy", age: 8, breed: "Siamese", color: "Brown", weight: 13, chip_number: 10010001 }`', src/parser.rs:170:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
failures:
parser::test::test_walk_scrapes_description
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 13 filtered out; finished in 0.00s
I thought it was possible to do achieve something like this:
left: `Cat {
name: "Fluffy",
age: 8,
breed: "Siamese",
color: "Brown",
weight: 12,
chip_number: 10001001
}`,
right: `Cat {
name: "Fluffy",
age: 8,
breed: "Siamese",
color: "Brown",
weight: 13,
chip_number: 10010001
}`',
I've tried both with rustc 1.59.0 (9d1b2106e 2022-02-23) and rustc 1.63.0-nightly (a6b8c6954 2022-06-03).
This is quite problematic since assert_eq!() itself prints the contents and cargo test gets already-formatted string.
It is also impossible to feature-gate the macro and under test pretty-print the variables, because std's code cannot be feature-gated (currently).
You can however write your macro to do that, or use a library like pretty-assertions.
Note, however, that there were RFCs to improve the display of the assert!() macros (it is not generally considered a breaking change), so it is possible that this will also be fixed in the future:
More readable assert_eq proposal
Tracking issue for RFC 2011: nicer assert messages

How can I compile a script that has some dynamic code?

I want to compile this script:
Red [File: %b.red]
context [
a: 123
hidden: 999
set 'global-exports context [
export-a: a
]
]
probe global-exports
But I get an error when trying to compile it with $ ./red-13oct19-a4ee537c -r b.red:
*** Red Compiler Internal Error: Script Error : Invalid path value: global-exports
*** Where: register-object
*** Near: [objects/context/global-exports: make object! [
a: none
hidden: none
]]
In general, you can wrap dynamic code with do [...] function, which treats a block of code like data and uses an interpreter to launch it at runtime:
Red [File: %b.red]
context [
a: 123
hidden: 999
do [ ;-- the code inside is run by interpreter at runtime
set 'global-exports context [
export-a: a
]
]
]
probe get 'global-exports ;-- dynamic retrieval
You also have to retrieve values dynamically, unless you initialize it statically (e.g. global-exports: none) somewhere earlier.
In this particular case also exchanging context with make object! will be sufficient:
Red [File: %b.red]
context [
a: 123
hidden: 999
set 'global-exports make object! [
export-a: a
]
]
probe global-exports
Compiling dynamic code will be possible with JIT compiler, which is planned in future, but not before Red version 1.0.
(thanks #9214 and #hiiamboris for the ideas on red/help gitter chat)

Smalltalk Input/Output

I am having trouble regarding Smalltalk. I am attempting to populate an array with the numbers that are read from the file, but it doesn't seem to work. I've tried numerous options and I was hoping someone would explain to me what I'm doing wrong.
Object subclass: #MyStack
instanceVariableNames:'anArray aStack'
classVariableNames:''
poolDictionaries:''
!
MyStack class comment: 'Creates a Stack Class.'
!
!
MyStack methodsFor: 'initialize Stack'
!
new "instance creation"
^ super new.
!
init "initialization"
anArray := Array new: 32.
aStack := 0.
! !
!MyStack methodsFor: 'methods for stacks' !
pop "Removes the top entry from the stack"
| item |
item := anArray at: aStack.
aStack := aStack - 1.
!
push: x "Pushes a new entry onto the stack"
aStack := aStack + 1.
anArray at:aStack put:x.
!
top "Returns the current top of the stack"
^anArray at: aStack.
!
empty "True if the stack is empty"
^aStack = 0.
!
full "True if the stack is full"
^aStack = 32.
!
printOn: aStream "Prints entire stack one entry per line, starting the top entry"
aStream show: 'Stack:'.
aStack to:1 by:-1 do:[:i |(anArray at:i) printOn:aStream. ].
aStream show: ''
! !
"----------------------------------------------------------------------------------"
Object subclass: #IOExample
instanceVariableNames: 'input output'
classVariableNames: ''
poolDictionaries: ''
!
IOExample class comment: '
basic I/O.
'
!
!
IOExample methodsFor: 'initialize'
!
new
^ super new.
!
init
[ input := FileSelectionBrowser open asFilename readStream. ]
on: Error
do: [ :exception |
Dialog warn: 'Unable to open file'.
exception retry.
].
[ output := FileSelectionBrowser open asFilename writeStream. ]
on: Error
do: [ :exception |
Dialog warn: 'Unable to open file'.
exception retry.
].
! !
!
IOExample methodsFor: 'copy input to output turning :: into :'
!
copy
| data lookAhead theStack myStack|
[ input atEnd ] whileFalse: [
data := input next.
(data isKindOf: Integer)
ifTrue: [
(input atEnd) ifFalse: [
"myStack push: data."
lookAhead = input peek.
(lookAhead asCharacter isDigit)
ifTrue: [
]
].
].
output show: myStack.
].
input close.
output close.
! !
Did you try to run this code? If you did, I'm surprised you didn't get a compilation warning due to #2 below.
There are a number of problems in #copy (besides the fact that I don't understand exactly what it's trying to do)...
First you seems to expect the data to be numbers: data isKindOf: Integer. But then later you treat it as a stream of Characters: lookAhead asCharacter isDigit. If the first condition is true to get you past that point, the second one never can be, as you would've matched [0-9], which aren't ASCII values for digits.
lookAhead = input peek. Here you're comparing uninitialized lookAhead (nil) with the peeked value, and then throwing away the result. I assume you meant lookAhead := input peek.
Then there is the empty inner condition ifTrue: [ ]. What are you trying to do there?
Then there's the odd protocol name, 'copy input to output turning :: into :'. What does that mean, and what does that have to do with copying numbers between streams?
Justin, let me try to help you with the class MyStack and defer to another answer any comments on your example.
I've divided your code into fragments and appended my comments.
Fragment A:
Object subclass: #MyStack
instanceVariableNames:'anArray aStack'
classVariableNames:''
poolDictionaries:''
Comments for A:
A Smalltalker would have used instance variable names without indeterminate articles a or an
Object subclass: #MyStack
instanceVariableNames:'array stack'
classVariableNames:''
poolDictionaries:''
Fragment B:
MyStack class comment: 'Creates a Stack Class.'
Comments for B:
This is weird. I would have expected this instead (with no class):
MyStack comment: 'Creates a Stack Class.'
Fragment C:
MyStack methodsFor: 'initialize Stack'
new "instance creation"
^ super new.
Comments for C:*
This code puts new on the instance side of the class, which makes no sense because you usually send new to the class rather than its instances. The correct form requires adding class:
MyStack class methodsFor: 'initialize Stack'
new
^super new.
You forgot to send the initialization method (however, see Fragment D below)
new
^super new init.
Fragment D:
init "initialization"
anArray := Array new: 32.
aStack := 0.
Comments for D:
In Smalltalk people use the selector initialize so it can send super first
initialize
super initialize.
array := Array new: 32.
stack := 0.
Note that this change would require also writing new as
new
^super new initialize.
However, if your dialect already sends the initialize method by default, you should remove the implementation of new from your class.
Fragment E:
pop "Removes the top entry from the stack"
| item |
item := anArray at: aStack.
aStack := aStack - 1.
Comments for E:
You forgot to answer the item just popped out
pop
| item |
item := array at: stack.
stack := stack - 1.
^item
Fragment F:
push: x "Pushes a new entry onto the stack"
aStack := aStack + 1.
anArray at:aStack put:x.
Comments for F:
This is ok. Note however that the stack will refuse to push any item beyond the limit of 32.
push: x
stack := stack + 1.
array at: stack put: x.
Fragment G:
top "Returns the current top of the stack"
^anArray at: aStack.
empty "True if the stack is empty"
^aStack = 0.
full "True if the stack is full"
^aStack = 32.
Comments for G:
These are ok too. However, a more appropraite name for empty would have been isEmpty because all collections understand this polymorphic message. Similarly, the recommended selector for full would be isFull:
top
^array at: aStack.
isEmpty
^stack = 0.
isFull
^stack = 32.
Note also that isFull repeats the magic constant 32, which you used in the initialization code. That's not a good idea because if you change your mind in the future and decide to change 32 with, say, 64 you will have to modify two methods an not just one. You can eliminate this duplication in this way
isFull
^stack = array size.
Fragment H:
printOn: aStream
"Prints entire stack one entry per line, starting the top entry"
aStream show: 'Stack:'.
aStack to:1 by:-1 do:[:i |(anArray at:i) printOn:aStream. ].
aStream show: ''
Comments for H:
The last line of this code is superfluous and I would get rid of it. However, you may want to separate every item from the next with a space
printOn: aStream
stream show: 'Stack:'.
stack to: 1 by: -1 do:[:i |
aStream space.
(array at: i) printOn: aStream].

Why the map values change even if they are not pointers - Golang

I'm trying to test a method. Its signature is func (gv *myType) Update(all map[string][]SomeType) error
Although the test fails (the expected value doesn't match the received value) I've found that also the map it receives(allSub) is "updated" after the method is evaluated. Here is the code:
t.Logf("allsub %v", allSub)
if err := gv.Update(allSub); err != nil {
t.Error(err)
return
}
if !reflect.DeepEqual(egv, gv) {
t.Errorf("allSub %v", allSub)
return
}
The output
mth_test.go:265: allsub map[b:[{1 3 3}] a:[{1 0 0} {1 2 0}]]
mth_test.go:271: allSub map[a:[{1 2 0}]]
As you can see on t.Logf("allsub %v", allSub) we have map[b:[{1 3 3}] a:[{1 0 0} {1 2 0}]] and then after the evaluation if !reflect.DeepEqual(egv, gv) allSub becomes allSub map[a:[{1 2 0}]]. This drives me crazy and I'm wondering how is this possible? allSub is not a pointer.
Map, slice, channels are reference types. The spec is in-process to become clearer about this but basically the implementation of these builtin types entails a reference to an underlying data structure.
So you can consider that an object of type map is actually a reference to an associative array.
Effective Go has a little paragraph on it :
https://golang.org/doc/effective_go.html#maps
I quote :
Like slices, maps hold references to an underlying data structure. If you pass a map to a function that changes the contents of the map, the changes will be visible in the caller.

is there an object constructor in rebol

I usually program by functions in an "instinctive" manner, but my current problem can be easily solved by objects, so I go ahead with this method.
Doing so, I am trying to find a way to give an object a constructor method, the equivalent of init() in python, for example.
I looked in the http://www.rebol.com/docs/core-fr/fr-index.html documentation, but I couldn't find anything relevant.
There is no special constructor function in Rebol, but there is a possibility to write ad hoc init code if you need it on object's creation in the spec block. For example:
a: context [x: 123]
b: make a [
y: x + 1
x: 0
]
So, if you define your own "constructor" function by convention in the base object, you can call it the spec block on creation. If you want to make it automatic, you can wrap that in a function, like this:
a: context [
x: 123
init: func [n [integer!]][x: n]
]
new-a: func [n [integer!]][make a [init n]]
b: new-a 456
A more robust (but bit longer) version of new-a that would avoid the possible collision of passed arguments to init with object's own words would be:
new-a: func [n [integer!] /local obj][
also
obj: make a []
obj/init n
]
You could also write a more generic new function that would take a base object as first argument and automatically invoke a constructor-by-convention function after cloning the object, but supporting optional constructor arguments in a generic way is then more tricky.
Remember that the object model of Rebol is prototype-based (vs class-based in Python and most other OOP languages), so the "constructor" function gets duplicated for each new object created. You might want to avoid such cost if you are creating a huge number of objects.
To my knowledge, there is no formal method/convention for using object constructors such as init(). There is of course the built-in method of constructing derivative objects:
make prototype [name: "Foo" description: "Bar"]
; where type? prototype = object!
My best suggestion would be to define a function that inspects an object for a constructor method, then applies that method, here's one such function that I've proposed previously:
new: func [prototype [object!] args [block! none!]][
prototype: make prototype [
if in self 'new [
case [
function? :new [apply :new args]
block? :new [apply func [args] :new [args]]
]
]
]
]
The usage is quite straightforward: if a prototype object has a new value, then it will be applied in the construction of the derivative object:
thing: context [
name: description: none
new: [name: args/1 description: args/2]
]
derivative: new thing ["Foo" "Bar"]
note that this approach works in both Rebol 2 and 3.
Actually, by reading again the Rebol Core documentation (I just followed the good old advice: "Read The French Manual"), there is another way to implement a constructor, quite simple:
http://www.rebol.com/docs/core-fr/fr-rebolcore-10.html#section-8
Of course it is also in The English Manual:
http://www.rebol.com/docs/core23/rebolcore-10.html#section-7
=>
Another example of using the self variable is a function that clones
itself:
person: make object! [
name: days-old: none
new: func [name' birthday] [
make self [
name: name'
days-old: now/date - birthday
]
]
]
lulu: person/new "Lulu Ulu" 17-May-1980
print lulu/days-old
7366
I find this quite convenient, and this way, the constructor lies within the object. This fact makes the object more self-sufficient.
I just implemented that successfully for some geological stuff, and it works well:
>> source orientation
orientation: make object! [
matrix: []
north_reference: "Nm"
plane_quadrant_dip: ""
new: func [{Constructor, builds an orientation object! based on a measurement, as given by GeolPDA device, a rotation matrix represented by a suite of 9 values} m][
make self [
foreach [a b c] m [append/only matrix to-block reduce [a b c]]
a: self/matrix/1/1
b: self/matrix/1/2
c: self/matrix/1/3
d: self/matrix/2/1
e: self/matrix/2/2
f: self/matrix/2/3
g: self/matrix/3/1
h: self/matrix/3/2
i: self/matrix/3/3
plane_normal_vector: reduce [matrix/1/3
matrix/2/3
matrix/3/3
]
axis_vector: reduce [self/matrix/1/2
self/matrix/2/2
self/matrix/3/2
]
plane_downdip_azimuth: azimuth_vector plane_normal_vector
plane_direction: plane_downdip_azimuth - 90
if (plane_direction < 0) [plane_direction: plane_direction - 180]
plane_dip: arccosine (plane_normal_vector/3)
case [
((plane_downdip_azimuth > 315) or (plane_downdip_azimuth <= 45)) [plane_quadrant_dip: "N"]
((plane_downdip_azimuth > 45) and (plane_downdip_azimuth <= 135)) [plane_quadrant_dip: "E"]
((plane_downdip_azimuth > 135) and (plane_downdip_azimuth <= 225)) [plane_quadrant_dip: "S"]
((plane_downdip_azimuth > 225) and (plane_downdip_azimuth <= 315)) [plane_quadrant_dip: "W"]
]
line_azimuth: azimuth_vector axis_vector
line_plunge: 90 - (arccosine (axis_vector/3))
]
]
repr: func [][
print rejoin ["Matrix: " tab self/matrix
newline
"Plane: " tab
north_reference to-string to-integer self/plane_direction "/" to-string to-integer self/plane_dip "/" self/plane_quadrant_dip
newline
"Line: " tab
rejoin [north_reference to-string to-integer self/line_azimuth "/" to-string to-integer self/line_plunge]
]
]
trace_te: func [diagram [object!]][
len_queue_t: 0.3
tmp: reduce [
plane_normal_vector/1 / (square-root (((plane_normal_vector/1 ** 2) + (plane_normal_vector/2 ** 2))))
plane_normal_vector/2 / (square-root (((plane_normal_vector/1 ** 2) + (plane_normal_vector/2 ** 2))))
]
O: [0 0]
A: reduce [- tmp/2
tmp/1
]
B: reduce [tmp/2 0 - tmp/1]
C: reduce [tmp/1 * len_queue_t
tmp/2 * len_queue_t
]
L: reduce [- axis_vector/1 0 - axis_vector/2]
append diagram/plot [pen black]
diagram/trace_line A B
diagram/trace_line O C
diagram/trace_line O L
]
]
>> o: orientation/new [0.375471 -0.866153 -0.32985 0.669867 0.499563 -0.549286 0.640547 -0.0147148 0.767778]
>> o/repr
Matrix: 0.375471 -0.866153 -0.32985 0.669867 0.499563 -0.549286 0.640547 -0.0147148 0.767778
Plane: Nm120/39/S
Line: Nm299/0
Another advantage of this way is that variables defined by the "new" method directly belongs to the object "instance" (I ran into some trouble, with the other methods, having to mention self/ sometimes, having to initialize variables or not).
I'm trying to find out how OO works in REBOL. Prototypical indeed. Yesterday I came across this page, which inspired me to the classical OO model below, without duplication of functions:
;---- Generic function for class or instance method invocation ----;
invoke: func [
obj [object!]
fun [word!]
args [block!]
][
fun: bind fun obj/.class
;---- Class method names start with a dot and instance method names don't:
unless "." = first to-string fun [args: join args obj]
apply get fun args
]
;---- A class definition ----;
THIS-CLASS: context [
.class: self ; the class refers to itself
;---- Class method: create new instance ----;
.new: func [x' [integer!] /local obj] [
obj: context [x: x' .class: none] ; this is the object definition
obj/.class: self/.class ; the object will refer to the class
; it belongs to
return obj
]
;---- An instance method (last argument must be the instance itself) ----;
add: func [y obj] [
return obj/x + y
]
]
Then you can do this:
;---- First instance, created from its class ----;
this-object: THIS-CLASS/.new 1
print invoke this-object 'add [2]
;---- Second instance, created from from a prototype ----;
that-object: this-object/.class/.new 2
print invoke that-object 'add [4]
;---- Third instance, created from from a prototype in another way ----;
yonder-object: invoke that-object '.new [3]
print invoke yonder-object 'add [6]
;---- Fourth instance, created from from a prototype in a silly way ----;
silly-object: yonder-object/.class/.class/.class/.class/.new 4
print silly-object/.class/add 8 silly-object
print this-object/.class/add 8 silly-object
print THIS-CLASS/add 8 silly-object
(It works in REBOL 2, and prints 3, 6, 9, 12, 12, 12 successively.) Hardly any overhead. Probably it won't be difficult to find a dozen of other solutions. Exactly that is the real problem: there are too many ways to do it. (Maybe we'd better use LoyalScript.)