• Hello [name]! Thanks for joining the GMC. Before making any posts in the Tech Support forum, can we suggest you read the forum rules? These are simple guidelines that we ask you to follow so that you can get the best help possible for your issue.

GMS 2.3+ Be aware switch...case... might not work properly with yyc! (sample project provided)

Tornado

Member
On some places switch...case... statements DON'T WORK when compiled with YYC.
At least on Android YYC and Windows YYC. We didn't try other platforms.
So far it happend in HTTP Async Event and in Animation End Event. Who knows where else!

The comparison in some "case"-clauses simply doesn't work. For example both variables in "switch" and in "case" have value 2, but for some reason 2 doesn't equal 2!
It happens only for YYC, for VM is ok.

Here is the sample project if you want to try:
https://www.dropbox.com/s/hflft5o2d2sosd7/WrongCase.zip?dl=0

When you build with VM then you get this:
VM_ok.png
So everything works as expected.

When you build with YYC you get this:
YYC_not_ok.png

So for YYC, in the switch-case 2 doesn't equal 2, so we land in the "default" clause!

The bug has been reported to Yoyo minutes ago.
We use latest GMS, IDE v2.3.1.542, runtime v2.3.1.409.
 

Kezarus

Member
That's not good... well, if you be so kind, could you provide the code here for others to see what is happening and not have to download and open a project, please?

Thanks a ton, mate! =]
 

Yal

🐧 *penguin noises*
GMC Elder
Isn't there a function to set the math epsilon? (called something like math_set_epsilon). It basically changes the breakoff point where two floats are considered equal / nonequal, and it's intended to prevent exactly this kind of issues. The default is a really small number (so you don't get false positives) but for a lot of cases a precision in the range of 1/10,000 should work just fine... and you can always revert it back to more decimals if you need to do some heavy maths where the rounding errors would compound.

Powers of two in particular are exactly representable in all IEEE floating-point formats (remember, they're stored as approximate mantissa x 2^exponent, and for a power of 2 the mantissa will be exactly 1 - in theory at least) so if your 2 is a literal (and not the result of a maths operation with a bunch of cancellation) it should be exactly 2 with no decimals.
 
I'm going to go out on a limb here and suggest that it's because "2" isn't actually "2" but something more like "2.0000000000000001". YYC is much more sensitive to that kind of thing... quick solution would be to round all values entering the switch...
I tried to display both of the compared values using string_format() with a hundred decimals, all turned out as zeroes, and I get the same switch case result even if I round both of them.

Modifying the code further, I think this illustrates the absurdity of the situation:
GML:
if (status == 0) {
    switch (received_id) {
        case firstRequestID:
               alarm[0] = 30;
        break;

        case secondRequestID:
            show_message("caught properly"); // This pops up with VM
        break;

        default:
            switch (received_id) {
                case secondRequestID:
                    show_message("something is wrong"); // This pops up with YYC
            }
    }
}
 

Tornado

Member
That's not good... well, if you be so kind, could you provide the code here for others to see what is happening and not have to download and open a project, please?

Thanks a ton, mate! =]
Async HTTP Event:
Code:
var status = ds_map_find_value(async_load, "status");
var received_id = ds_map_find_value(async_load, "id")

switch (received_id) {
    case firstRequestID: // Ako se vratio check interneta
        if (status == 0) {
            scr_append_message("RECEIVED firstRequest! id=" + string(received_id));
            alarm[0] = 30; // send second http request
        }
    break;

    case secondRequestID:
        if (status == 0) {
            scr_append_message("RECEIVED secondRequest! id=" + string(received_id));
        }
    break;

    default:
        if (status == 0) {
            scr_append_message("RECEIVED in 'case-default'! id=" + string(received_id));
        }
    break;
}
Create Event:
Code:
#macro THE_URL "https://www.google.com"

msg = "";

firstRequestID = noone;
secondRequestID = noone;

firstRequestID = http_get(THE_URL);
scr_append_message("SENT firstRequest! id=" + string(firstRequestID));
Alarm[0]:
Code:
secondRequestID = http_get(THE_URL);
scr_append_message("SENT secondRequest! id=" + string(secondRequestID));
Draw Event:
Code:
draw_set_color(c_yellow);
draw_set_valign(fa_top);
draw_set_halign(fa_left);

draw_text_ext_transformed(50, 200, msg, 30, 1000, 2, 2, 0);
scr_append_message:
Code:
function scr_append_message(newMsg)
{
    msg = msg + newMsg + "\n";
}
Yes, I have already tried using floor() when sending and when reciving, but it doesn't help.
The same problem we have with at least one Spine animation, where in "Animation End Event" switch...case is used.
As quick "fix", we converted switch...case... to if...else.. in some Spine animations and some HTTP Asynh events.

We have around 200 "switch" statements in the project, we are thinking of converting them all to if...else. The drawback is that such kinkd of work is error prone.
But if we want to release a new version of the game soon, we will have to do it that way, because it is uncertain in which "switch" the problem will occur.
 
Last edited:
I personally don't use switch statements with non constant cases (literals, macros, enums) since that always looks fishy to me. Have you tried replacing the variables by their values for testing the switch case with constant members?
 
Last edited:

Tornado

Member
Just to clarify, the code in our real project where this happens is not new, it worked without problems for a long time.

@Yal seting epsilon didn't help.

I personally don't use switch statements with non constant cases (literals, macros, enums) since that always looks fishy to me. Have you tried replacing the variables by their values for testing the switch case with constant members?
Yes, if I use a constant value in the "case" it works:
case 2:

but if I use variable
case secondRequestID:
it doesn't work.

secondRequestID gets the value from the http-request so it must be a variable.
 
Yes, if I use a constant value in the "case" it works:
case 2:

but if I use variable
case secondRequestID:
it doesn't work.

secondRequestID gets the value from the http-request so it must be a variable.
I always apply the rule of thumb that if the cases are constant then a switch can be used, otherwise always use an if. I'm not sure if GML is meant to explicitly support these types of dynamic switch branches but given the plethora of different platforms it supports that is bound to have differing behaviour on some (as you've just encountered). My advise going forward is that it probably is better to abide by a similar rule of thumb, however I'm not sure if there is an easy way to fix the problem when you already have several switch statements relying on it.
 
GMS2 Manual on the switch statement:
Next it is compared with the results of the different constants after each of the case statements. When we say "constant" what we mean is that the value in the case cannot be a variable expression and must be a fixed value of any data type, like "fight" or 3 or the keyword noone.
Same with C++. There too, switch statement cases must be constants. Guess which language YYC translates to?

I honestly have zero idea why the compiler didn't throw an error in either YYC or VM. That's the real bug.
 

kburkhart84

Firehammer Games
GMS2 Manual on the switch statement:


Same with C++. There too, switch statement cases must be constants. Guess which language YYC translates to?

I honestly have zero idea why the compiler didn't throw an error in either YYC or VM. That's the real bug.
I'm guessing that this is the root cause of the issue. The fact that it was by chance working is really strange, but maybe the translation to C++ is more direct(in 2.3 compared to previous versions) and not filtering out the bad code now, I don't know.
 
Interestingly, it was only specified in the "new" one (https://manual.yoyogames.com), the pre-2.3 (https://docs2.yoyogames.com) doesn't have it.
Yes it does, just not in the GML switch statement section. In the DnD case section, the pre-2.3 manual says this:
Note that a Case value must be a constant, which can be any data type (real, string, etc...), as long as it evaluates to a single value on compile - so you can't use expressions that depend on variables. Also note that Case cannot be used outside of a Switch and trying to do so will give you an error.
 

Tornado

Member
DND is different, I'm not using DND. I posted above GMS2.2 "Language Features" screenshot, where it say "expression".

Now we are totaly unsecure, when we now discover a bug it could be a part of code with switch...case which worked fine for years, but now not anymore.
I guess we will have to get rid of all non-constant "case"-clauses.

Not nice!
 
DND is different, I'm not using DND. I posted above GMS2.2 "Language Features" screenshot, where it say "expression".

Now we are totaly unsecure, when we now discover a bug it could be a part of code with switch...case which worked fine for years, but now not anymore.
I guess we will have to get rid of all non-constant "case"-clauses.

Not nice!
AFAIK, this has been a thing since GMS1. https://forum.yoyogames.com/index.php?threads/how-not-to-use-switch.8745/
Use switch only when you are matching an expression against a discrete set of possible values using == only (must be constant in GMS 1.x and above)
The documentation wasn't the greatest at staying up-to-date, but it finally got a thorough run-through with GMS2.3. You should still file a bug report, since the fact that the engine isn't exploding and giving you an error in either VM or YYC seems like a huge oversight.
 

Tornado

Member
I filed a bug report yesterday.

Im not using HTTP events or anything but I am testing a normal switch case
I can confirm that it works normally in VM and YYC
So far we discovered it in HTTP Async event and Animation End Event.

Im not using HTTP events or anything but I am testing a normal switch case
I can confirm that it works normally in VM and YYC
In your example you are using constants in "case"-clauses:
1611359083631.png
That works.
The problem occurs (or might occur) with yyc when you use variables. (see my sample code in my second reply)
 

Tornado

Member
@EvanSki
"switch can only use constants" is not true, second part about "cases" is obviously correct. I guess you typed too fast.

Let's not lead this thread ad absurdum. My point is that something has changed in GMS which (for yyc) broke the code which was perfectly running before for long time.
Objects started to behave differently in the game and it took us quite an effort to discover where the problem is.

I filed the report and let you know to be aware of that.

@EvanSki
"the cases can be variables but if the variables can change they will not work for a switch case"
That is very true.
I tried it in my sample. If I define a variable with a value and never change the value of it, it works in the "case", but as soon as the value of the variable changes, it doesn't work anymore in the "case"!
 

EvanSki

Raccoon Jam Host
@EvanSki
"the cases can be variables but if the variables can change they will not work for a switch case"
That is very true.
I tried it in my sample. If I define a variable with a value and never change the value of it, it works in the "case", but as soon as the value of the variable changes, it doesn't work anymore in the "case"!
Yes so they have to be CONSTANT values
a switch can only use constants
 
@Tornado Throwing this out. You could try to cast to int64() before the case and see if that works.
Even if it does fix the error, going with that as a solution would be a terrible idea. It's a band-aid at best. Cases in a switch statement being expressions instead of constants is recommended against by the manual (really, it reads more like an enforcement than a suggestion). Meaning, the effects of using non-constant values as the case in a switch is subject to change at a moment's notice. When this bug gets fixed, GM will crash instead of "working," requiring you to go and manually fix all the cases to make them constants anyway. Best to deal with it now rather than later.
 

Tornado

Member
First thing I tried was floor() and int64(). None of it solves the problem.
We are going to go over 300 places and replace switch...case by if...else wherever the constants are not used in "cases". :-(

By the way, if someone would ask why first case
case firstRequestID:
works, it is only because the value is 0, because first http-request always gets the value 0. If I do some other http-request right at the beginning, then firstRequestID won't be 0, and in that case also
case firstRequestID:
won't work.
 
Last edited:

HalRiyami

Member
Even if it does fix the error, going with that as a solution would be a terrible idea. It's a band-aid at best. Cases in a switch statement being expressions instead of constants is recommended against by the manual (really, it reads more like an enforcement than a suggestion). Meaning, the effects of using non-constant values as the case in a switch is subject to change at a moment's notice. When this bug gets fixed, GM will crash instead of "working," requiring you to go and manually fix all the cases to make them constants anyway. Best to deal with it now rather than later.
It would be more of a workaround and I personally don't recommend not using constants.
My understanding is that the switches use a hashmap which is why they tend to be fast but that also means they require constants since they are generated either at game start or during compile. Someone correct me here.
I'm surprised it even works in VM.
 

Tornado

Member
Well it worked always before, but apparently not amymore, as we painfully discovered. (The code with switch...case... in Animation End Event of several objects worked for several years, but not anymore, as it seems)
It is not first time that GMS 2.3 breaks the working code. We have filed some bug reports before.
Now the problem is located, so we must change the code accordingly.
Even if Yoyo is going to do something about it, we can't afford to wait for that.

Thank you all for participation and help!
 
Top