Difference between revisions of "Talk:State"
(8 intermediate revisions by 5 users not shown) | |||
Line 81: | Line 81: | ||
:::: If you remove the if(){} parts, then the compiler does still stop with ÉRROR : Global functions can't change state, so a case that doesn't throw this error is still demonstrably a bug, and not behavior to rely on. Treat it like invisiprims or PosJump. --[[User:ObviousAltIsObvious Resident|ObviousAltIsObvious Resident]] 20:05, 23 February 2013 (PST) | :::: If you remove the if(){} parts, then the compiler does still stop with ÉRROR : Global functions can't change state, so a case that doesn't throw this error is still demonstrably a bug, and not behavior to rely on. Treat it like invisiprims or PosJump. --[[User:ObviousAltIsObvious Resident|ObviousAltIsObvious Resident]] 20:05, 23 February 2013 (PST) | ||
::::: I agree it's not behaviour to rely on. I am simply suggesting that it would better to have a caveat that says, correctly, "this works, but don't rely on it," rather than one that incorrectly says, "this doesn't work at all" when clearly it does. This came up in a group yesterday, when someone asked about this. I said that I thought I'd seen in the wiki that this trick with "if" didn't work any more, and someone correctly said, "but it does still work, if you test it, so the wiki is wrong" [[User:Innula Zenovka|Innula Zenovka]] 06:49, 24 February 2013 (PST) | |||
The reason they wanted to kill this "feature" was because in LSO it has the potential to leak memory. The stack in LSO is untyped, so even though it might be able to pop the items off the stack safely it can't know if they are primitives or pointers into the heap. To make matters worse, globals live at the front of the stack and are allocated normally in the heap. I don't know how they solved the problem in LSO. Since the stack in Mono is typed, Mono does not have this problem. -- '''[[User:Strife_Onizuka|Strife]]''' <sup><small>([[User talk:Strife_Onizuka|talk]]|[[Special:Contributions/Strife_Onizuka|contribs]])</small></sup> 23:41, 23 February 2013 (PST) | |||
::Strife, if you're saying that Mono doesn't have a potential problem with this, can't the Wiki at least say it's fine to do this trick under Mono? (The 'trick' purely being a way of bypassing the compiler's attempt to protect LSO.) p.s. I tested a variant of Innula's code for 100,000 loops and saw no memory leak in either LSO or Mono. [[User:Omei Qunhua|Omei Qunhua]] 00:38, 25 February 2013 (PST) | |||
Mono handles it gracefully. After some experiments, the observed behavior is that of a return statement, but the state change isn't effective until the current event finalizes. The only problem under LSO is when the function should return a value and it doesn't, in that case, the script crashes with a Bounds Check Error. This script demonstrates that: | |||
<lsl> | |||
string f() { llOwnerSay("in f"); if (1) state another; return "something"; } | |||
default { state_entry() { | |||
f(); // prints "in f" both in Mono and in LSO | |||
llOwnerSay((string)llStringLength(f())); // prints "in f" then 0 in Mono, prints "in f" then crashes in LSO | |||
llOwnerSay("still alive"); // prints "still alive" in Mono | |||
} } | |||
state another { state_entry() { llOwnerSay("another"); } } // prints "another" | |||
</lsl> | |||
Under Mono, it returns the default value instead (the same assigned by default when declaring a variable of that type without initializing it, that is, 0 for integers, 0.0 for floats, "" for strings, etc.) --[[User:Sei Lisa|Sei Lisa]] 16:45, 5 August 2014 (PDT) | |||
:Is it possible it's waiting till the end of the time slice and not execution? Maybe put a one second sleep before the "still alive"? -- '''[[User:Strife_Onizuka|Strife]]''' <sup><small>([[User talk:Strife_Onizuka|talk]]|[[Special:Contributions/Strife_Onizuka|contribs]])</small></sup> 14:27, 6 August 2014 (PDT) | |||
:P.S. Either way the following might work to detect VM (or it might crash in Mono or not work at all): | |||
<lsl>test(){if(1) state LSO; } | |||
default { state_entry() { test(); state MONO; } } | |||
state LSO { state_entry(){ llOwnerSay("LSO"); } } | |||
state MONO { state_entry(){ llOwnerSay("MONO"); } } | |||
</lsl> | |||
:: Here's the CIL code resulting from compiling this function: | |||
<div><dl><dd><dl><dd> | |||
<lsl> | |||
switch_state() | |||
{ | |||
if (TRUE) | |||
state b; | |||
switch_state(); // just to add something | |||
} | |||
</lsl></dl></dl> | |||
<dl><dd><dl><dd> | |||
<pre> | |||
.method public hidebysig instance default void 'gswitch_state'() cil managed | |||
{ | |||
.maxstack 500 | |||
ldc.i4 1 | |||
brfalse LabelTempJump1 | |||
ldarg.0 | |||
ldstr "b" | |||
call instance void class [LslUserScript]LindenLab.SecondLife.LslUserScript::ChangeState(string) | |||
ret // <----- **** | |||
LabelTempJump1: | |||
ldarg.0 | |||
call instance void class LSL_Klass::'gswitch_state'() | |||
ret | |||
} | |||
</pre></dl></dl></div> | |||
:: If the function return type is not void, it inserts a return value: | |||
<div><dl><dd><dl><dd><lsl> | |||
rotation switch_state() | |||
{ | |||
if (TRUE) | |||
state b; | |||
return <1.0, 2.0, 3.0, 4.0>; | |||
} | |||
</lsl></dl></dl> | |||
<dl><dd><dl><dd><pre> | |||
.method public hidebysig instance default class [ScriptTypes]LindenLab.SecondLife.Quaternion 'gswitch_state'() cil managed | |||
{ | |||
.maxstack 500 | |||
ldc.i4 1 | |||
brfalse LabelTempJump1 | |||
ldarg.0 | |||
ldstr "b" | |||
call instance void class [LslUserScript]LindenLab.SecondLife.LslUserScript::ChangeState(string) | |||
ldc.r8 0 | |||
ldc.r8 0 | |||
ldc.r8 0 | |||
ldc.r8 1 | |||
call class [ScriptTypes]LindenLab.SecondLife.Quaternion class [LslUserScript]LindenLab.SecondLife.LslUserScript::'CreateQuaternion'(float32, float32, float32, float32) | |||
ret | |||
LabelTempJump1: | |||
ldc.r8 (00 00 00 00 00 00 f0 3f) | |||
ldc.r8 (00 00 00 00 00 00 00 40) | |||
ldc.r8 (00 00 00 00 00 00 08 40) | |||
ldc.r8 (00 00 00 00 00 00 10 40) | |||
call class [ScriptTypes]LindenLab.SecondLife.Quaternion class [LslUserScript]LindenLab.SecondLife.LslUserScript::'CreateQuaternion'(float32, float32, float32, float32) | |||
ret | |||
} | |||
</pre></dl></dl></div> | |||
:: Note how the code deliberately inserts ZERO_ROTATION as the return value in the state change branch. | |||
:: I think that program flow is not interrupted by the <code>state</code> statement, other than acting as a <code>return</code>. It's not anything like a ''longjmp'', despite being what anyone would expect. I also see no difference between LSO and Mono, except the crash that Sei reported, which happens only in LSO if the function is not void. It seems clear that the behaviour of a state change statement is to flag the state change, return immediately (it's not designed to happen in a function, after all), and when the event finishes, which should be immediately if it happened in an event as designed, perform the state change. --[[User:Pedro Oval|Pedro Oval]] 21:32, 6 August 2014 (PDT) | |||
:::Yes, apologies if I wasn't clear. It works in the same way in Mono and LSO, just LSO crashes if the function is supposed to return a value and that value is used by the caller. If the function does not return a value, or if the value returned is not used, LSO works fine. Strife, in the script you posted both Mono and LSO print "MONO". Apparently only the last state switch counts. Adding llSleep(1) doesn't change anything. --[[User:Sei Lisa|Sei Lisa]] 08:16, 7 August 2014 (PDT) | |||
::::That makes a lot of sense. -- '''[[User:Strife_Onizuka|Strife]]''' <sup><small>([[User talk:Strife_Onizuka|talk]]|[[Special:Contributions/Strife_Onizuka|contribs]])</small></sup> 10:42, 7 August 2014 (PDT) |
Latest revision as of 09:42, 7 August 2014
Constant vs Explicit values?
I've noticed an ongoing style difference in recent edits, namely in the preference between named constants versus explicit values... as can be seen from difference in edits for this entry (namely 0 or PUBLIC_CHANNEL). I have no preference either way, and can see merits for both (the named constant is more informative to new users [tells what it does], the explicit value more informative to experienced users [tells what it is])... AFAIK neither compiler actually cares one way or the other so there appears not to be any side effects. should we settle on a preference?
-- Void (talk|contribs) 08:35, 27 December 2012 (PST)
- My feeling was that new users are initially introduced to llSay(0, ... as that is what they are presented with from the very start with 2 occurrences in the default "new script". Using PUBLIC_CHANNEL in Wiki examples immediately adds a layer of confusion. Adding an explanatory comment each time seems like an admission that we've confused them, especially a comment that says "PUBLIC_CHANNEL has the integer value 0" ... as that is demonstrating the need to explain PUBLIC_CHANNEL rather than explaining '0' --- it's akin to a child being told "I think you'll find it easier to call me FEMALE PARENTAL HUMAN rather than Mum as that explains my role better" ... whereas Mum is what the child grew up with. SL scripters all 'grow up' with '0' as the open chat channel. I am strongly in favour of 99.9% of mnemonic constants, but 0 for channel 0 seems like a special case. For example CHANGED_OWNER is eminently more sensible than trying to remember the value 128. While we're at it, I do detest (float)FALSE and (float)TRUE ... if you really want to use TRUE and FALSE for the extremes of Alpha, then let the compiler auto-cast it for you e.g. llSetAlpha(FALSE, ALL_SIDES) but llSetAlpha(0.0, ALL_SIDES) says it much better for me and has the right flavour, as it's one value on a sliding scale. Omei Qunhua 14:31, 27 December 2012 (PST)
I'm not sure that adherence to the format of the default 'new script' does inexperienced users any favors... The default script simply a holdover from the early days of LSL, during which the only thing less friendly to inexperienced users than the language was the documentation for it =D Corey was, to put it mildly, a minimalist. There've actually been a few campaigns to get LL to replace the default (to no avail, may be buried in some fixed length record no one wants to dig up/mess with). There is no implicit understanding of what '0' means or does, or even why it's there, and the inexperienced simply repeat it cargo-cult style because it doesn't work if they omit it. it's more akin to a child calling every woman, or every parent "Mum" not knowing the relationship is specific. As for using TRUE|FALSE with things like alpha, I agree, it's generally horrible practice. As you pointed out, it's a percentage of opacity. I can't agree on relying on the compiler to autocast variables (actually it inserts a cast instruction, unless it's done in globals. even on key declarations) since there isn't a lot of consistency in what the compiler will and won't cast. it might be prettier to look at but that cast is still there whether it's documented or not, so if one must use that (admittedly horrible) structure, being explicit eliminates future subtle errors. Having that explicit cast there should serve as a warning sign
ETA: I don't usually write for inexperienced users, instead using comments for their benefit (I don't think I've ever used PUBLIC_CHANNEL myself). but that's just my natural mode, and I'm willing to work under consensus
-- Void (talk|contribs) 16:05, 27 January 2013 (PST)
- I do not like PUBLIC_CHANNEL for the reason that it creates this exact problem. We aren't sure if we should use it or not. We aren't sure which is better. It's existence instead of fixing a problem created one. This is like an argument over which is better: Celsius or Fahrenheit. I don't know if we should use it in the documentation. I don't really care either way. -- Strife (talk|contribs) 21:45, 27 January 2013 (PST)
One advantage of allowing the compiler to cast an integer to a float in things like llSetTimerEvent(0), llSetAlpha(1), is that it saves bytecode space, bizarrely. Omei Qunhua 03:39, 15 February 2013 (PST)
- Huh? unless something has changed drastically the compiler doesn't convert those numbers, it just inserts the cast instruction, just as if it'd been explicitly declared. It does something similar with negative numbers inserting a negation instruction instead of an actual negative number. Presumably this is for safety. Mind you that's LSO behavior, It's rather hard to pin down MONO behavior, since it's memory is assinged in blocks for functions, and can report different memory values for successive saves of event code (there seems to be some JIT insertions on run)
-- Void (talk|contribs) 08:12, 15 February 2013 (PST)
- Void, I think you're not considering the full scenario. Tests just done under Mono suggest that llSetTimerEvent(0) is 3 bytes shorter than llSetTimerEvent(0.0). I suspect the reason is the excess bytecode space involved in declaring a float value of 0.0 rather than an integer of 0, which presumably is more than the inserted cast instruction. And yes, I am allowing for the 512 byte blocks of Mono allocation, by simply comparing the code size of two scripts, one containing 1024 copies of llSetTimerEvent(0) and the other containing 1024 copies of llSetTimerEvent(0.0); Omei Qunhua 13:47, 15 February 2013 (PST)
- That doesn't seem to be it, Strife. llSetText(message, <123456, 123456, 123456>, 123456) comes out the same size as llSetText(message, <1, 1, 1>, 1) Omei Qunhua 04:39, 17 February 2013 (PST)
- Indeed. Integer constants take 6 bytes; float constants take 10 bytes; a cast from integer to float takes 1 byte. --Pedro Oval 20:38, 18 February 2013 (PST)
Change States with an if in a user function
Despite the comment in the main article that this trick no longer works, this script is working for me perfectly well, on Second Life Server 13.02.08.270166
<lsl>
ChangeStates() {
if(1==1) { state other; }
}
ChangeBack() {
if (1==1){ llOwnerSay("Changing back"); state default; }
} default {
state_entry() { llOwnerSay("state default"); }
touch_end(integer total_number) { ChangeStates(); }
}
state other{
state_entry() { llOwnerSay("now in state other"); ChangeBack(); }
} </lsl>
Innula Zenovka 09:25, 23 February 2013 (PST)
- Yes, working for me too on same server Omei Qunhua 14:51, 23 February 2013 (PST)
- Since a Linden made that edit, this would be a good question to bring to the server user group. If there's an error message in the compiler for this, then it's not really supposed to work. Docs should at least have a use at your own risk warning, unless a Linden can bless it as a supported construct. --ObviousAltIsObvious Resident 18:06, 23 February 2013 (PST)
- It compiles without error, at least for me, just as it always used to. The server user group isn't at a very good time for me in the UK, but if anyone else would like to raise the matter, I would be most grateful.
- I agree any reference to it should probably come with some sort of warning, but -- at least at present -- the caveat User-defined (global) functions cannot change a script's state. The compiler will throw the error 'ERROR: Global functions can't change state'. Note: Previously, global functions could change state in the body of a simple 'if' statement; this "workaround" no longer works. is demonstrably incorrect, in that the "workaround" does work and doesn't throw any sort of error message either when the script is compiled or when it runs. Innula Zenovka 19:48, 23 February 2013 (PST)
- If you remove the if(){} parts, then the compiler does still stop with ÉRROR : Global functions can't change state, so a case that doesn't throw this error is still demonstrably a bug, and not behavior to rely on. Treat it like invisiprims or PosJump. --ObviousAltIsObvious Resident 20:05, 23 February 2013 (PST)
- I agree it's not behaviour to rely on. I am simply suggesting that it would better to have a caveat that says, correctly, "this works, but don't rely on it," rather than one that incorrectly says, "this doesn't work at all" when clearly it does. This came up in a group yesterday, when someone asked about this. I said that I thought I'd seen in the wiki that this trick with "if" didn't work any more, and someone correctly said, "but it does still work, if you test it, so the wiki is wrong" Innula Zenovka 06:49, 24 February 2013 (PST)
The reason they wanted to kill this "feature" was because in LSO it has the potential to leak memory. The stack in LSO is untyped, so even though it might be able to pop the items off the stack safely it can't know if they are primitives or pointers into the heap. To make matters worse, globals live at the front of the stack and are allocated normally in the heap. I don't know how they solved the problem in LSO. Since the stack in Mono is typed, Mono does not have this problem. -- Strife (talk|contribs) 23:41, 23 February 2013 (PST)
- Strife, if you're saying that Mono doesn't have a potential problem with this, can't the Wiki at least say it's fine to do this trick under Mono? (The 'trick' purely being a way of bypassing the compiler's attempt to protect LSO.) p.s. I tested a variant of Innula's code for 100,000 loops and saw no memory leak in either LSO or Mono. Omei Qunhua 00:38, 25 February 2013 (PST)
Mono handles it gracefully. After some experiments, the observed behavior is that of a return statement, but the state change isn't effective until the current event finalizes. The only problem under LSO is when the function should return a value and it doesn't, in that case, the script crashes with a Bounds Check Error. This script demonstrates that:
<lsl> string f() { llOwnerSay("in f"); if (1) state another; return "something"; } default { state_entry() {
f(); // prints "in f" both in Mono and in LSO llOwnerSay((string)llStringLength(f())); // prints "in f" then 0 in Mono, prints "in f" then crashes in LSO llOwnerSay("still alive"); // prints "still alive" in Mono
} } state another { state_entry() { llOwnerSay("another"); } } // prints "another" </lsl>
Under Mono, it returns the default value instead (the same assigned by default when declaring a variable of that type without initializing it, that is, 0 for integers, 0.0 for floats, "" for strings, etc.) --Sei Lisa 16:45, 5 August 2014 (PDT)
- Is it possible it's waiting till the end of the time slice and not execution? Maybe put a one second sleep before the "still alive"? -- Strife (talk|contribs) 14:27, 6 August 2014 (PDT)
- P.S. Either way the following might work to detect VM (or it might crash in Mono or not work at all):
<lsl>test(){if(1) state LSO; } default { state_entry() { test(); state MONO; } } state LSO { state_entry(){ llOwnerSay("LSO"); } } state MONO { state_entry(){ llOwnerSay("MONO"); } } </lsl>
- Here's the CIL code resulting from compiling this function:
-
<lsl> switch_state() {
if (TRUE) state b; switch_state(); // just to add something
}
</lsl>
-
-
.method public hidebysig instance default void 'gswitch_state'() cil managed { .maxstack 500 ldc.i4 1 brfalse LabelTempJump1 ldarg.0 ldstr "b" call instance void class [LslUserScript]LindenLab.SecondLife.LslUserScript::ChangeState(string) ret // <----- **** LabelTempJump1: ldarg.0 call instance void class LSL_Klass::'gswitch_state'() ret }
-
- If the function return type is not void, it inserts a return value:
- <lsl>
rotation switch_state() {
if (TRUE) state b; return <1.0, 2.0, 3.0, 4.0>;
}
</lsl>
- <lsl>
.method public hidebysig instance default class [ScriptTypes]LindenLab.SecondLife.Quaternion 'gswitch_state'() cil managed { .maxstack 500 ldc.i4 1 brfalse LabelTempJump1 ldarg.0 ldstr "b" call instance void class [LslUserScript]LindenLab.SecondLife.LslUserScript::ChangeState(string) ldc.r8 0 ldc.r8 0 ldc.r8 0 ldc.r8 1 call class [ScriptTypes]LindenLab.SecondLife.Quaternion class [LslUserScript]LindenLab.SecondLife.LslUserScript::'CreateQuaternion'(float32, float32, float32, float32) ret LabelTempJump1: ldc.r8 (00 00 00 00 00 00 f0 3f) ldc.r8 (00 00 00 00 00 00 00 40) ldc.r8 (00 00 00 00 00 00 08 40) ldc.r8 (00 00 00 00 00 00 10 40) call class [ScriptTypes]LindenLab.SecondLife.Quaternion class [LslUserScript]LindenLab.SecondLife.LslUserScript::'CreateQuaternion'(float32, float32, float32, float32) ret }
- Note how the code deliberately inserts ZERO_ROTATION as the return value in the state change branch.
- I think that program flow is not interrupted by the
state
statement, other than acting as areturn
. It's not anything like a longjmp, despite being what anyone would expect. I also see no difference between LSO and Mono, except the crash that Sei reported, which happens only in LSO if the function is not void. It seems clear that the behaviour of a state change statement is to flag the state change, return immediately (it's not designed to happen in a function, after all), and when the event finishes, which should be immediately if it happened in an event as designed, perform the state change. --Pedro Oval 21:32, 6 August 2014 (PDT)- Yes, apologies if I wasn't clear. It works in the same way in Mono and LSO, just LSO crashes if the function is supposed to return a value and that value is used by the caller. If the function does not return a value, or if the value returned is not used, LSO works fine. Strife, in the script you posted both Mono and LSO print "MONO". Apparently only the last state switch counts. Adding llSleep(1) doesn't change anything. --Sei Lisa 08:16, 7 August 2014 (PDT)