User defined structs

To get something more custom or appropriate for printing out your SortData object, you can also make a String() conversion method that builds up your own string representation. By default, the String() conversion method inherited from Object just prints out the class name of the object.

You can also override as_code() which returns a string for use during debugging when building callstack, etc. If you don’t override as_code() it calls the String() conversion method by default.

So why would the assignment fail if I didn’t define the constructor or didn’t define it properly? Should I define things like !copy and assign as well?

1 Like

Currently, whenever you describe a class it will inherit all the data and routines from its superclass - including any constructors. If you want your constructors or other methods to do anything more than the superclass (including dealing with new data members) you must define them by hand. So you must make a constructor, optionally call the superclass constructor as needed, then do the initial binding of an object for each data member.

The idea was to give full power over how, when and if things are created - though this can be annoying for common code that isn’t just auto-generated. So yes, unless an inherited methods do exactly what you want you will need to create your own, copy constructor !copy() and assignment operator assign() (which is used as :=).

We’ve discussed this on the team and with some existing SkookumScript users and we intend to add more auto generation and warnings if all the required steps are not performed. So just like C++, we will have constructors auto call their superclass constructor and either create default objects for data members or give a warning if a data member isn’t initialized by the end of a constructor. We expect to have this in a near term update.

If you have additional thoughts on this, just let us know.

For example, in C++:
class MyClass {int a;};
MyClass myVar;
myVar.a = 5;
printf("%d", myVar.a); // prints ‘5’
works fine, but in Sk, if I don’t add a constructor, the equivalent code doesn’t work, it seems strange, that’s all. If initialization of data members is required is not a huge deal but it’s another gotcha that can cause difficult to find issues. Perhaps a warning or error if initialization is missing?

Look at this piece of code that contrively swaps the two values in the list:
!ml : {4, 0}
!v1 : ml.at(0)
!v2 : ml.at(1)
ml.at_set(0, v2)
ml.at_set(1, v1)
v1 := v2
println(ml) // prints {0, 0}
if I change the following lines it works correctly:
ml.at_set(0, v2!)
ml.at_set(1, v1!)
I surmised it was because parameters are passed by reference, so adding the ‘!’ sends copies instead.
In the OP example, something is not working right because
myList.append(var!) doesn’t work, but myList.append(var) does.
If I’m sending references it could lead to problems like in the example above.
Do I need to define !copy for this to work right?
I have it defined like this:
(SortData entity) SortData [@cost := entity.@cost @actor := entity.@actor this]

You can do this in SkookumScript like this (assuming SortData has an empty constructor here though):

!var: SortData!
var.@cost: 2.0
var.@actor: Actor!null

The important distinction here is between binding (via :) and assigning (via :=) to variables. When you create an empty object instance without a constructor, its member variables are not bound to an object yet. var.@cost: 2.0 for example binds the member variable @cost to an object with value 2.0. After this has happened and the object exists, you can now assign different values to it, e.g. var.@cost := 42.0. See also the following paragraph for clarification, which is part of this post.

SkookumScript variables

In SkookumScript, every variable is a reference to an object that gets stored and/or passed around. That means data in SkookumScript is not stored in a class instance or a local variable by value, but by reference to an object that contains its value. The following short example illustrates this:

!a : 42
!b : a
a := 7

after which the value of b is 7. This is because we are creating an object of class Integer with the value 42 and binding it to the local variable a, then binding that same object to the local variable b (i.e. now both a and b refer to the same Integer object). Thus changing its value via a also affects the value referenced by b.

These should both work, assuming your class has a copy constructor defined.

The copy constructor has to bind the member variables, since their objects do not yet exist, e.g. like this:

(SortData other) SortData [@cost: other.@cost @actor: other.@actor this]

Since you say “contrively” you probably just meant this as a demonstration, though I thought that I should also point out that there is a swap() method that swaps two items in a List object.

Yes, I did know about swap. Demonstrating how passing by reference can lead to unexpected behavior, and wondering why when I used the copy constructor, it didn’t seem work.
Passing by reference can lead to problems that are difficult to track down later down the line. I’d prefer it if the default was the other way around: passing by value and tag references like in other languages.

EDIT: Thanks!.I was assigning not binding in my copy.
What’s a proper ‘assign’ function? The problem is all the examples I see use Unreal data types which are treated differently (&raw).
(SortData other) SortData [@cost := other.@cost @actor := other.@actor this]

If I pass by reference, not using copy, I could get into trouble if I try to shuffle the list somehow, like in the example I listed, where (contrively for demonstration) I swapped 2 values and ended up corrupting the list.

Another thing you could do if it is easier or more correct based on the situation is that you can call a constructor inside another constructor so you can call the default constructor to set things up and then do the custom stuff after that.

(SortData other) SortData
  [
  // Do default construction and make data members
  SortData@!()

  @cost := other.@cost   // assign simple type
  @actor : other.@actor  // reference complex type

  this
  ]

The reason that SkookumScript defaults to pass by reference is that generally most of the objects that you deal with in a script are bigger heap objects: characters, world objects, etc.

We opted not to have two different kinds of types such as with C# for simplicity with both understanding and implementation and also efficiency.

You can sort of pass by value if you make copy of an argument as you are calling a method.

// pass by reference
method(obj1 obj2)

// pass by value
method(obj1! obj2!)

To make an operator method such as assignment := the method name is assign. If you look at a lot of the primitive classes such as Boolean, Integer, Real, String, etc. you can see all the operator normal names and their operator equivalents in square brackets such as assign [:=].

The problem is that the assign function in all of those are not implemented in Sk. They simply show:
(Boolean value) Boolean

For the example in this post for SortData, I have:
(SortData other) SortData [@cost := other.@cost @actor := other.@actor this]
Is that right?

I have this as my String function:
() String [@cost.String + " " + @actor.String]

If I do:
println(mySortData) // prints correctly

but if I do:
println(mySortData.String) // prints “null” (null)

I want that latter case to work for doing things like:
println("Adding: " idx.String + " " + mySortData.String)

Is there a way to make this work?

I was just pointing out those other classes for the names of operator methods. Yes there are few if any pure SkookumScript classes. Most classes are wrapped around existing C++ structures and some have a mix of script and C++.

As @GreatGuru was mentioning you need to create objects and bind the data members before you can assign something to them.

(SortData other) SortData
  [
  // bind to copy (could refer to same object instead)
  @cost : other.@cost!
  // bind to same actor (probably don't want to copy an actor)
  @actor : other.@actor

  this
  ]

EDIT: I meant to do a bind and not an assignment for the actor!

The print() and println() methods actually take any number of objects of any type and it internally calls their String conversion methods to concatenate up a string.

So instead of

println("Adding: " idx.String + " " + mySortData.String)

You can just use:

println("Adding: " idx " " mySortData)

Easy-peasy.

I’m not sure why - println(mySortData.String) // prints "null" (null)
I’ll see if I can get the same result and look into it…

I still can’t get this to work. I’m still getting null, null:

!myItem : SortData!
!itemList : SortData{}  
myltem.@cost := 5.0
myItem.@actor := @actor_list{0}
itemList.append(myItem!)
println(itemList)

my copy!

(SortData other) SortData [@cost : other.@cost! @actor := other.@actor this]

EDIT: also tried this copy:

(SortData other) SortData [ SortData@!() @cost := other.@cost @actor : other.@actor this]

my !()

() [@cost : 0.0 @actor : Actor!null]

my assign

(SortData other) SortData [@cost := other.@cost @actor := other.@actor this]

my @actor and @cost:

Real !@cost
Actor !@actor

SortData is parented to Object

Any ideas?

EDIT: I think my assignment operator is also broken:

!myItem : SortData!
myItem.@cost := 5.0
myItem.@actor := @actor_list{0}
!myVar : SortData!
myVar := myItem
println(myVar) // prints nil nil

Note, I understand this example is not the way I would do this sort of thing, it’s a use case for this issue.

We just tried to reproduce both of your examples but both work fine for us. We have to dig deeper what might be the reason and get back to you tomorrow. Thanks for hanging in there!

The only other factor is the function I’m running is a &blueprint function I’m triggering from a running game in Unreal. I have the function bound to a key.
When I put the same code in the Workbench, it works as expected.

Here’s the whole function (called testFunc and bound to the level blueprint of a vanilla third person project):

&blueprint
() 
  [
  !myItem : SortData!
  
  myItem.@cost := 5.0
  myItem.@actor := @actor_list{0}
  !myVar : SortData!
  myVar := myItem
  println(myVar)
  ]

note that replacing the assignment with this also makes it work as expected:

myVar.@cost := myItem.@cost
myVar.@actor : myItem.@actor

so for some reason := is not working correctly in this function, but works correctly in the Workbench

On the assign member function I added: println(“Here”)
Prints on workbench, doesn’t print when running from unreal

Odd. Again, we did the same thing over here (created the &blueprint function and bound to a key) but it works fine.

Assigning the actor by value is probably the issue here, because of how Entity (UObject) references are internally represented. Some actors are assigned a unique instance object which cannot be copied or assigned. Using a bind (:) is the proper solution. If you bind (:) the actor variable instead of assigning (:=) it whenever you transfer it, does the problem go away?

Actually, I just shut-off Unreal and Sk, re-launched them, then it worked as expected
¯\_(ツ)__/¯

1 Like