question

Serge A avatar image
1 Like"
Serge A asked Joerg Vogel edited

The proper way to check Variant for null / detect nodes without data

According to FlexSim Module SDK headers, Variant is a nullable type:

It apparently can be null in Flexscript too, but there are some inconsistencies, and there is no well defined way to check for null (missing) values in the new dot-notation.

In particular:

  • .value of an non-object node without data is NULL
  • .value of an object node without data is not NULL, but the node itself!
  • NULL == x and x == NULL in Flexscript are not equivalent and may yield different results
  • comparing to nullvar works for .value of non-object nodes
  • comparing to nullvar doesn't work for .value of object nodes

I noticed these things when I saw that somenode.value == NULL doesn't work as expected.

I suppose, line 17 is a bug, can't imagine it being a desired feature (see also lines 13 and 28).

The commutative property of the == operator is violated (lines 5-8 & 20-23). It is probably another bug.

My questions are:

  • what is the recommended way [in dot notation] to check if a node has data?
  • given a Variant value in Flexscript, how it should be checked for null value?
FlexSim 17.1.2
flexscriptdot syntaxvariantnullnull variant
5 |100000

Up to 12 attachments (including images) can be used with a maximum of 23.8 MiB each and 47.7 MiB total.

1 Answer

·
Matthew Gillespie avatar image
2 Likes"
Matthew Gillespie answered Phil BoBo edited

Check if a node has data

The correct way to check if a node has data is to check its datatype:

node.dataType == NULL

Note that there are several different datatypes that a node can have:

// Constants group: DATATYPE
#define DATATYPE_NUMBER  1
#define DATATYPE_STRING  2
#define DATATYPE_COUPLING  3
#define DATATYPE_OBJECT  4
#define DATATYPE_PARTICLE  5
#define DATATYPE_BUNDLE  6
#define DATATYPE_SIMPLE  7

A node with Object data is just a node that can have attribute nodes. It doesn't really store a value so it defaults to returning itself when you ask it for its value. Similarly asking a node with bundle or simple (unless it is an array) data for its value isn't very useful either.

Check if a Variant is NULL

Once you've determined that a node has data and returns a meaningful value you can check it against nullvar or check if its type is a NULL type:

Variant val;
val == nullvar //true
val.type == VAR_TYPE_NULL // true
· 6
5 |100000

Up to 12 attachments (including images) can be used with a maximum of 23.8 MiB each and 47.7 MiB total.

Serge A avatar image Serge A commented ·

> A node with Object data is just a node that can have attribute nodes. It doesn't really store a value so it defaults to returning itself when you ask it for its value.

That's the point! If it doesn't really store a value, .value should be null.

Anyway, thank you for the answers, but I've got a couple of follow up question.

1. What is the difference in Flexscript between NULL, nullvar, and 0?

They're not the same:

[ nullvar == NULL // false
, NULL == nullvar // true
, NULL == 0 // true
, 0 == NULL // true
, nullvar == 0 // false
, 0 == nullvar // true
]

I assume NULL is a #define for 0 (literally the same thing), and nullvar is a Variant with type VAR_TYPE_NULL (0). Am I right? Could you please explain why (==) is not commutative for nullvar?

I also noticed that a string "NULL" is also used in some circumstances. What are the conditions that the "NULL" string is used instead of a NULL/nullvar value? "NULL" instead of an empty string ("")?

2. What's the difference between var and Variant?

I noticed that

Variant y;
return y == nullvar;

works, but

var y;
return y == nullvar;

doesn't. ("Variable y's type is inferred but has no assignment from which to infer its type. This is not allowed.")

What is exactly the semantics of var? What are the reasons to prefer var over Variant or vice versa?

3. How to compare other variable types to an empty value?

I tried to apply your NULL checking pattern (emptyvariable == nullvar) to other variable types, and saw that it doesn't always work. The proposed techinque works for non-initialized non-numeric types (string and Object), but doesn't work for numeric types (int and double). There is also a parsing error if an initialized Array is compared to nullvar.

string nullstr;
double nulldouble;
Object nullobj;
Array nullarr;
return 
   [ nullstr == nullvar // true
   , nulldouble == nullvar // false
   , nullobj == nullvar // true
   // , nullarr == nullvar // Unknown Flexscript Parse Error
   ];

Is there a single way to check for "missing" values that works across all Flexscript types?

0 Likes 0 ·
Phil BoBo avatar image Phil BoBo ♦♦ Serge A commented ·

> > A node with Object data is just a node that can have attribute nodes. It doesn't really store a value so it defaults to returning itself when you ask it for its value.

> That's the point! If it doesn't really store a value, .value should be null.

A FlexSim treenode has a datatype. Previously you set the datatype with nodeadddata() and get it with getdatatype(); now you set and get the datatype with treenode.dataType.

A node with Object data doesn't really store a value; it stores a whole bunch of values within its attribute tree. Its dataType is DATATYPE_OBJECT.

When you ask an Object for its data, it returns itself because its data is accessed on itself as an Object rather than as a treenode:

treenode processor = model().find("Processor1");
return processor.as(Object).attrs.shape.value;

It doesn't have no data; it has object data. Its value isn't null; it is itself, the Object containing its data.

> 1. What is the difference in Flexscript between NULL, nullvar, and 0?

NULL and 0 are synonymous. This is the same as Win32 C++. It is defined within Visual Studio's stdlib.h:

/* Define NULL pointer value */
#ifndef NULL
#ifdef __cplusplus
#define NULL    0
#else  /* __cplusplus */
#define NULL    ((void *)0)
#endif  /* __cplusplus */
#endif  /* NULL */

nullvar is a special keyword that creates a Null Variant: a Variant whose type is VAR_TYPE_NULL. This is the same as initializing an empty Variant in C++ using:

Variant myVariant = Variant();

> NULL == nullvar // true

This is a bug. This should equate to false. I'll add a note to the dev list to fix this.

"" == nullvar is also erroneously returning true. It should be returning false.

nullvar == "" is correctly returning false.

> I also noticed that a string "NULL" is also used in some circumstances. What are the conditions that the "NULL" string is used instead of a NULL/nullvar value? "NULL" instead of an empty string ("")?

The string "NULL" is a string with the characters 'N', 'U', 'L', and 'L' in it. It isn't something special. You did not describe exactly the circumstances where you saw a string "NULL", but I suspect that it was written in a text field or table display to describe that the value of something was null, such as in watch variables or an array label's value, or something like that.

> 2. What's the difference between var and Variant?

var is a short for "variable". It is a type-inferred variable based on the datatype of the variable being assigned to it. It is synonymous with the C++ keyword "auto". It is not short for "Variant".

Variant is a special datatype that can store a wide variety of data: either a number, a string, a treenode, an array of variants, or null.

Variant is used in many places where the value returned could be one of those types, such as in dynamic label access and the return value of nodefunction(), executefsnode(), and user commands.

var is just to make writing code easier and faster. It is resolved at compile time based on the datatype that is being assigned to it. For example:

var myVariable = Color.red;
Object processor = model().find("Processor1");
processor.color = myVariable;

myVariable is of type Color, not Variant. The following code is identical:

Color myVariable = Color.red;
Object processor = model().find("Processor1");
processor.color = myVariable;

> 3. How to compare other variable types to an empty value?

string nullstr;
double nulldouble;
Object nullobj;
Array nullarr;

These are not null. FlexScript initializes your variables for you if you do not initialize them yourself.

The above code is the same as this:

string nullstr = "";
double nulldouble = 0.0;
Object nullobj = nullvar;
Array nullarr = [];

You need to treat them as such. The string and double types are not nullable types. You cannot store a null pointer in a string or a double.

> Is there a single way to check for "missing" values that works across all Flexscript types?

No. You can't have "missing" values in all FlexScript types. A double is always a number. A string is always a string. An int is always a number. None of those types have "missing" values.

The comparison you should use depends on the type you are comparing with. FlexScript is strongly typed. When you are writing the code, you can know exactly what type you are working with, and compare it accordingly.

The place where this can become confusing is for the treenode datatype. You have multiple cases:

1. A treenode returned by an old global command, such as node(). The return value could be the SAFEREF node, which is a valid memory address, but it doesn't exist in the main or view trees. To check for whether the treenode is a valid reference, you need to use objectexists(). For example:

treenode fred = node("doesnotexist", model());
return fred == nullvar; // false
return fred == NULL; // false
return tonum(fred); // a non-zero number
if (fred) return 1; else return 0; // 1
if (objectexists(fred)) return 1; else return 0; // 0

This behavior is kept for backwards compatibility with legacy code written before the new Compiled FlexScript feature in FlexSim 2017.

2. A treenode returned by a new command, such as find(). The return value behaves as you would expect:

treenode fred = model().find("doesnotexist");
return fred == nullvar; // true (bug, should be false)
return fred == NULL; // true
return tonum(fred); // 0
if (fred) return 1; else return 0; // 0
if (objectexists(fred)) return 1; else return 0; // 0

3. A treenode stored in a Variant, where the treenode pointer is pointing at 0x0, returned by a command that returns a Variant or by dynamic label access:

Object processor = model().find("Processor1");
Variant fred = processor.myPointerLabel;
return fred == nullvar; // false
return fred == NULL; // true
return tonum(fred); // 0
if (fred) return 1; else return 0; // 0
if (objectexists(fred)) return 1; else return 0; // 0

4. A Variant that is nullvar, where the type of the Variant was unknown. For example, reading the .value of a node where you don't know whether the node has no data or treenode data:

treenode Tools = model().find("Tools");
Variant fred = Tools.value;
return fred == nullvar; // true
return fred == NULL; // false
return tonum(fred); // 0
if (fred) return 1; else return 0; // 0
if (objectexists(fred)) return 1; else return 0; // 0
2 Likes 2 ·
null-treenode.png (5.6 KiB)
anthony.johnson avatar image anthony.johnson ♦♦ Phil BoBo ♦♦ commented ·

Phil's example revealed a bug in the Variant implementation. Specifically, a null Variant (nullvar) should NOT be equal to a null treenode. When you are comparing something to nullvar, it should only be true if it is also a null variant. Technically, a Variant can be a null variant, or it can be a treenode variant that happens to point to null. These are two different things, and == and != should reflect that. We will make this change in our next release.

1 Like 1 ·
Show more comments

Write an Answer

Hint: Notify or tag a user in this post by typing @username.

Up to 12 attachments (including images) can be used with a maximum of 23.8 MiB each and 47.7 MiB total.