YoYoGames / GameMaker-Bugs

Public tracking for GameMaker bugs
13 stars 5 forks source link

Function references are not the same in HTML5 as other platforms. Causing crashes. #5837

Closed CarsonKompon closed 2 weeks ago

CarsonKompon commented 2 weeks ago

Description

My game is mostly comprised of a series of parent objects with functions defined in their create event like so:

objCharacter Create Event:

function Move(){
    // Movement code here...
}

function Jump(){
    // Jumping code here...
}

I then have child objects which override the functions while still maintaining their base usage:

objPlayer Create Event:

event_inherited();

// Set base_Jump to the old Jump function:
base_Jump = Jump;

// Replace the Jump function with a new function that references base_Jump
function Jump(){
    base_Jump();

    // Your added code here
    audio_play_sound(sndJump, 10, false);
}

This doesn't seem to work in HTML5 exports, yielding the following error: image

This seems to be because it is a reference to the same function in javascript. This means when the Jump function is overwritten, so is the base_Jump function. This leads to a recursive pattern which causes this crash.

Expected Change

I would like to see that HTML5 does not crash when other platforms do not. We have shipped "Turnip Boy Robs a Bank" on Xbox, Switch, and Windows. That game uses the same techniques and it was able to ship on each of those platforms without issue, leading me to believe HTML5 is the odd-one-out.

Steps To Reproduce

  1. Start GameMaker (I am using GameMaker-Beta 2024.400.0.556, likely happens in LTS as well)
  2. Create a new project or open an existing one
  3. Create an object called objTestParent
  4. Give objTestParent the following Create Event:
    function MyFunction(){
    show_debug_message("Test Message 1");
    }
  5. Create another object called objTestChild
  6. Set the parent of objTestChild to objTestParent
  7. Give objTestChild the following Create Event:
    
    event_inherited();

base_MyFunction = MyFunction; function MyFunction(){ base_MyFunction(); show_debug_message("Test Message 2"); }

MyFunction();


8. Create a new room and place objTestChild in the room
9. Set the room to the starting room in the Room Order
10. Set your platform to Windows and Run the game
11. See the following in your console:
![image](https://github.com/YoYoGames/GameMaker-Bugs/assets/5159369/95388dd5-7121-4555-b7b8-8e5b3a163b2e)
12. Set your platform to HTML5 and Run the game
13. See the following in the browser's console:
![image](https://github.com/YoYoGames/GameMaker-Bugs/assets/5159369/32978eb8-609f-46a4-8117-d3ab1b6402cf)

Here is my attached Contact Us Package (which includes a Sample Project):
[function_repro.zip](https://github.com/YoYoGames/GameMaker-Bugs/files/15288090/function_repro.zip)

### How reliably can you recreate this issue using your steps above?

Always

### Which version of GameMaker are you reporting this issue for?

2024.400 (Betas)

### Which platform(s) are you seeing the problem on?

HTML5

### Contact Us Package Attached?

- [X] I have attached my Contact Us Package

### Sample Project Added?

- [X] I have included a small sample project
Grisgram commented 2 weeks ago

I mean, it should work, no doubt, and this needs to be addressed, but have you tried, as a workaround, to rebind it as a method instead of just setting a variable?

What I mean: Instead of

basejump = jump;

Try

basejump = method(self, jump);

Maybe rebinding fixes your issue, at least until a fix for this is released.

I also wanted to say, that I do the same thing like 100 times in my ui library, but I didn't have those effects. Will try to reproduce in a blank project also... This alerts me a bit.

Grisgram commented 2 weeks ago

Wait... I just saw your code closely (sorry, writing from my mobile).

You do a

function XYZ()

in the create event of your object? A global function? This can't work afaik, you should use member declaration, like

jump = function () ...

And not

function jump()

This is likely the reason why it doesn't work in your end. I would've recognized this (and many many other devs too) if that wouldn't work in html.

Can you please verify, that this is the problem?

CarsonKompon commented 2 weeks ago

Wait... I just saw your code closely (sorry, writing from my mobile).

You do a

function XYZ()

in the create event of your object? A global function? This can't work afaik, you should use member declaration, like

jump = function () ...

And not

function jump()

This is likely the reason why it doesn't work in your end. I would've recognized this (and many many other devs too) if that wouldn't work in html.

Can you please verify, that this is the problem?

AFAIK, ever since GameMaker Studio 2.3, these two are both identical:

// This:
function myFunction(){
    show_debug_message("test!");
}

// Is the exact same as doing this:
myFunction = function(){
    show_debug_message("test!");
}

The difference is WHERE the code is placed. I know this to be true because in our recent release Turnip Boy Robs a Bank nearly everything single object has their own unique Update and Draw functions both defined in the create event of each individual as such:

function Update(_deltaTime){
    // Code here...
}

function Draw(_x, _y){
    // Code here...
}

And this WORKS across each separate enemy. HOWEVER, if I were to place the EXACT SAME CODE in a script instead of the create event of an object, THAT IS WHEN the functions would be public.

I am also on mobile so I won't be able to test the method binding until I get home, but I will get back to you whenever I get a chance...

Grisgram commented 2 weeks ago

It is as i expected, just tested with a blank project. Your description of the error is correct -- html goes into a recursion.

so you are obv right, html does something different, when using function syntax instead of member syntax.

and yes, you are right, they are not "global" functions, as I said in my first post -- what i meant, was, "the syntax of a global function" -- of course you can't call this from "anywhere" as it is declared in an object, not in a script. my apologies for not pointing that out clearly.

I only want to point out, that member syntax works flawless.


cpy = demo;

demo = function() {
    cpy();
    show_debug_message("demo-child");
}

demo();

works perfectly in html.

CarsonKompon commented 2 weeks ago

Hmm interesting..

Looking into the documentation it seems that you are correct in the assessment that function definitions are different from method definitions. So maybe it is intended that I should do func = function(){ } instead of function func(){ }.

However, I'd like to make the argument that there isn't a proper use case for defining a global function in an object. So in cases like these where functions are defined as function func(){ }, if it's within the scope of an object or struct, the compiler should understand that it should be a method variable rather than a function.

Huge shoutout to Michael (DragoniteSpam) for explaining this a bit better in the following video: https://www.youtube.com/watch?v=Da2tvRiKq8k

EDIT: So to clarify, what I'm saying is I really don't want to refactor all my gamemaker games that I want to export to html5

Grisgram commented 2 weeks ago

Hmm interesting..

Looking into the documentation it seems that you are correct in the assessment that function definitions are different from method definitions. So maybe it is intended that I should do func = function(){ } instead of function func(){ }.

I agree, you should use the member syntax all the way in objects. Still, I think, Russel should take a look at the html source code, maybe the recursion can be fixed, but I would expect, that this is internally treated very different and maybe no easy fix. maybe it's easier to forbid function syntax in objects in the future to avoid the confusion between both possible cases, as we have here. Maybe best with an error message, when you do it like the error you receive, when you try to create a static in an object.

rwkay commented 2 weeks ago

OK so taking a look at the project in the bug, it was not working on VM or YYC and I realised that the Child object is not actually parented to the objTestParent, once that was done it was working as expected. That took me far too long to work out (it is late though so I plead tiredness there).

Once I had done that I realised that the scope on HTML5 is being confused... if you were more explicit around what you wanted to do then it works. So if the code in the objTestChild Create Event is rewritten as

event_inherited();

base_MyFunction = self.MyFunction;
function MyFunction(){
    base_MyFunction();
    show_debug_message("Test Message 2");
}

self.MyFunction();

then it all works as the compiler is not getting confused between an instance variable and a function name.

I will take a look in the morning in fixing the HTML5 compiler to work the same way as the VM in this case though as it should be the same... in the meantime you can get the current compiler to comply in both VM, YYC and HTML5 by being more explicit about the scope of the thing that you are trying to read from.

CarsonKompon commented 2 weeks ago

Thanks Russell! Re-assuring to know that this should be working. Never hurts to be more explicit sometimes and I typically use self all the time in structs/constructors but not in objects, I will try to make that a habit :)

PS: Sorry for the mistake I made in the example project 🤦 I was in a bit of a rush.... 😅

rwkay commented 2 weeks ago

Fixed in 2024.6