Raw data members - how do they work?

SkookumScript has a feature called raw data members that allows script code to elegantly access and modify arbitrary data structures stored in C++ engine/application objects as if they were SkookumScript native data members. For the most part you won’t know the difference because both raw and native data members behave exactly the same in 99.9% of all use cases. But there are some subtle differences that I’d like to point out. Let me first explain how SkookumScript variables and native :sk: data members work.

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.

Native data members

Native data members work exactly the same way. They store a reference to a SkookumScript object that can potentially be also referenced by other variables. A native data member can also be bound and re-bound to new objects at any time.

Raw data members

Raw data members work differently. They represent memory in which variables are stored by value. We use them to make UE4 properties accessible via SkookumScript. They differ from native data members in the following ways:

  • Declaration: A raw data member is declared by prefixing it with the annotation &raw in the script code. At runtime, the engine implementation then provides a callback to SkookumScript which will resolve the data member into two access callbacks and a size and offset in the containing object, allowing it to read and write its value. I am not getting into more detail on how this exactly works at this point - if you are curious check out SkUEClassBindingHelper::resolve_raw_data() in our plugin source code and/or ask me!
  • Binding: A raw data member cannot be bound (using the : operator) to SkookumScript objects like native data members, since it is stored by value. It can only be assigned a value (using the := operator).
  • Modification: You can modify a raw data member’s value in two ways:
    1. Using the assignment operator :=
    2. Invoking a modifying method call on it, for example the += operator or play_from_start
  • Reading: When you read the value of a raw data member, SkookumScript will under the hood create a new object, assign the raw data member’s value to that object, and return that object - essentially making a copy of the raw data member. Modifying that object after that will not affect the original raw data member! You can imagine reading the value of a raw data member !a : @my_raw_data_member as equivalent to copying the value of a native data member !b : @my_native_data_member! (note the exclamation mark after the data member which invokes its copy constructor to make a copy of the object).

Hope this clarifies things a bit. Please ask if something is not clear to you!

5 Likes

OK thanks for that write up. I always wondered what &raw was doing.

I think I see why I have not run into issues. I always update the variables directly and never assign to a new variable unless I want that to new variable to be considered a new object.

Interesting to find out that skookum variables work the opposite though.

1 Like

Yes in 99.9% of all use cases you will never notice the difference between raw and native data members since the common things you do with them work just the same.