Replies: 2 comments
-
Another issue I found with this proposal is that, because you can't sense strings on units/buildings, reading a field on a value that may be an object but also may be a primitive is not possible with current solution. const points = {
a: Vars.this,
b: Vars.unit,
c: { x: 10, y: 10 },
};
const first = points.a;
const second = Math.rand(10) > 5 ? points.b : points.c;
const dotProduct = firts.x * second.x + first.y * second.y;
print(dotProduct); lowers to: // omitting setters because they are not used
let points_value = "[object Object]";
let points_a_value = Vars.this;
let points_a_get = key => points_a_value[key];
let points_b_value = Vars.unit;
let points_b_get = key => points_b_value[key];
let points_c_value = "[object Object]";
let points_c_x = 10;
let points_c_y = 10;
let points_c_get = key => {
switch (key) {
case "x":
return points_c_x;
case "y":
return points_c_y;
}
};
let first_value = points_a_value;
let first_get = points_a_get;
let second_value;
let second_get;
if (Math.rand(10) > 5) {
second_value = points_b_value;
second_get = points_b_get;
} else {
second_value = points_c_value;
second_get = points_c_get;
}
const dotProduct =
first_get("x") * second_get("x") + first_get("y") * second_get("y");
print(dotProduct); And while this example is simple enough that one could argue to create two branches to handle each scenario, it becomes non-trivial to do in more complex programs. So while this model is very useful to handle object references, it still requires a clear distinction between objects and stores. |
Beta Was this translation helpful? Give feedback.
-
To work around that problem, we can change the model to only define getters/setters for object values, while leaving primitive values with their getters/setters set to print(a.x) into this: let t0
if(a_get == undefined) {
t0 = sensor(LAccess.x, a)
} else {
t0 = a_get("x")
}
print(t0) Note that the compiler no longer needs to worry about knowing the actual type of And to handle the notorius overhead of cheking for the existence of a getter, the optimizer can use constant propagation to eliminate branches that cannot occur (which would be trivial in most cases). |
Beta Was this translation helpful? Give feedback.
-
This idea is being registered here with the intent of documentation, it may not be implemented.
This feature requires:
As of v0.5.2, objects have the following limitations that this idea seeks to solve:
readonly
But how do we address such problems? Let's begin with a simple base scenario:
Let's define a function that takes two values with x and y properties and returns the distance between the two:
Currently the compiler assumes that both
a
andb
are stores (dynamic register variables), and so, translates the.x
and.y
reads assensor
instructions. However, this also means that objects cannot be used here, since they cannot fit in a single variable.So, when mlogjs finds this code:
It throws an error:
Is there any way to fix this?
Well, forcing the compiler to inline the function call makes it reinterpret this:
as this:
Which compiles perfectly fine. However, inlining is not always possible/the best option:
In this scenario, we want to have a dynamic array that holds objects with three properties
x
,y
andweights
.However, the current compiler is unable to resolve this issue, as it expects values in the array to be stores (single register variables).
Inlining is also not viable due to the random index access.
But then how can the compiler transform this into mlog code?
Why not objects as copyable values
The most intuitive approach is to treat each object as a
C
struct, transforming assignments into value copies:output pseudo code:
This, however, falls apart because:
Making everything an object
This is heavily inspired by a workaround to read data from objects with a dynamic key:
switch
statements.Currently, if I want to index an object, it must be done manually:
Is not valid code, so it must be rewritten as:
In this case, it is straightforward for the compiler to generate the getter function automatically, so let's move on to another case, what if the object has nested objects?
Suddenly the code does not work, because we haven't defined how to generically represent an object in variables and function return values.
Simple value copies cannot be used due to the limitations mentioned above, so how can this be done?
But what if everything was an object with get/set functions? Everything becomes much simpler.
Instead of one, all variables now have three backing registers:
With this every variable can be assigned an object by reference, including function parameters and dynamic array items.
Let's have a look at one of the first examples:
if we lower the code we have the equivalent of:
As you can see there is a lot of indirection in the generated code, meaning that a good optimizer is essential to remove as much unused code as possible, be it unused get/set pointers or keys and/or fields that are never read/written to.
Final notes:
Copying objects is much more viable if they are not modified and have a small amount of fields.
Thanks to Anuken/Mindustry@cc4efdd, using
set @counter null
is a no op, which means that using null function pointers does not break the script's control flow. However it still might result in the user reading garbage values, it might be better to use a function that specifically returnnull
with itself as the getter and setter.Although not mentioned, this method also works with buildings/units:
The apparent limitation of not being able to sense string keys predates this discussion, so it will not be addressed here.
An optimizer could likely "specialize" functions based on the type of inputs they receive. It could also inline the function as long as the semantics were preserved
Beta Was this translation helpful? Give feedback.
All reactions