Skript

Skript

788k Downloads

Variable Reference Modifier {&var} for Functions

miberss opened this issue · 5 comments

commented

Suggestion

Add a new variable type that can be passed into a function by reference using the {&var} modifier.
It would only apply to variables, not to expressions or literals, which would not be allowed to be passed through reference due to not being able to handle this properly since it would be hard to do parse time typing, and would look messy.

# player's name = "miberss"
set {_value} to player's name
foo({_value})

# {_value} is now "hello", player's name is not.
broadcast {_value}

function foo(reference string: string):
  set {&string} to "hello"

I also believe that this modifier & should always be local to the function

The syntax for this would be (ref[erence]| &)<parameter_name>: <parameter_type>.

Why?

This would allow modifying the original variable directly inside a function, without returning and reassigning the value, which would be useful for do_thing functions.

This would also allow for better organization of data in Skript, especially with the new keyed expression (i'm not sure about this though since keyed is an expression, and this would be limited to variables, i'm not sure how that would work), but the idea would be something like this

function mk_enemy() returns objects:
  set {_enemy::damage} to 10
  set {_enemy::attack_speed} to 1.2
  return keyed {_enemy::*}

function double_damage_for(&enemy: objects, duration: timespan):
  set {_double} to {&enemy::damage}
  add {_double} to {&enemy::damage}
  wait {_duration}
  remove {_double} from {&enemy::damage}

on script load:
  set {_enemy::*} to mk_enemy()
  double_damage_for(keyed {_enemy::*}, 5 seconds)

Other

The reasoning for & as the symbol is

  1. Reference Operator in C++, Rust, Zig and more
  2. We do not have bitwise operations, so it wouldn't be confused with anything

Agreement

  • I have read the guidelines above and affirm I am following them with this suggestion.
commented

If this is implemented, I think the call-site also needs something indicating that this is a reference being passed in (this will make it more obvious when observing call-sites that a variable's value could directly change).
Example of this is C#'s implementation:

public void ChangeVariable(ref string theVariable) {
    theVariable = "i changed the variable";
}

public void UseCase() {
    string myString = "i am unchanged";
    ChangeVariable(ref myString);
    Console.WriteLine(myString); // i changed the variable
}
commented

A problem I see with this is delays, since an effect function call, has the function run at the same time with the rest of the code after calling the function.
Since doing something like this would not work

function test(&a: string):
	wait 2 seconds
	set {&a} to "b"

on load:
	set {_a} to "a"
	test({_a})
	broadcast {_a} # -> "a"

Then not to mention, it would need to be appropriately handled for not setting a variable for an event that has already passed and the local variables for that event have already been cleared.

It could mimic how expression function calls are treated, which would disallow any delays being used within the function.

commented

A problem I see with this is delays, since an effect function call, has the function run at the same time with the rest of the code after calling the function. Since doing something like this would not work

function test(&a: string):
wait 2 seconds
set {&a} to "b"

on load:
set {_a} to "a"
test({_a})
broadcast {_a} # -> "a"

I don't understand why that wouldn't work? is it just an issue with the way Skript works or what?

commented

A problem I see with this is delays, since an effect function call, has the function run at the same time with the rest of the code after calling the function. Since doing something like this would not work

function test(&a: string):
wait 2 seconds
set {&a} to "b"

on load:
set {_a} to "a"
test({_a})
broadcast {_a} # -> "a"

Then not to mention, it would need to be appropriately handled for not setting a variable for an event that has already passed and the local variables for that event have already been cleared.

It could mimic how expression function calls are treated, which would disallow any delays being used within the function.

I feel this should be something we should allow users to do. To avoid confusion among people who may not understand fully how using references like this works, I feel that adding a warning that gets printed when you use references and delays in a function would be sufficient. This warning can then be disabled using a config option.

commented

The delay example should not be an issue, generally. The only case that needs handling is when the event's local variables were cleared due to the trigger exiting, but in that case all that's needed is just a check to prevent the reference from being changed (because it's referencing nothing).