ValveSoftware / portal2

Issues for the Linux port of Portal 2
145 stars 11 forks source link

[Portal 2 VScripts] VSquirrel Class Interactions Break Save/Load Functionality in Portal 2 #440

Open IaVashik opened 2 months ago

IaVashik commented 2 months ago

Description:

Unusual using classes in VSquirrel 2+ for Portal 2 modding can lead to critical save/load failures, effectively breaking the game. Several specific scenarios involving class interactions consistently trigger these issues.

Problem 1: Inheritance

Even simple inheritance can cause Portal 2 to crash on save/load.

Example:

class foo {
    function something() printl("test")
}

class bar extends foo {
    function doing() printl("bye bye my game :<")
}

While the inheritance mechanism itself works during runtime, attempting to load a saved game state containing these classes results in a crash.

Problem 2: Circular References Within Classes

Mutual references between class instances, even when using weakref, can still lead to crashes during save/load or cause the VSquirrel VM to break down. This is likely due to how references are handled during the serialization process, which fails to properly account for these circular dependencies.

Example:

class Node {
    nextNode = null
    prevNode = null
}

n1 <- Node()
n2 <- Node()

n1.nextNode = n2 // .weakref() does not help
n2.prevNode = n1 // .weakref() does not help

This results in errors like:

Failed to restore a Squirrel object of type <....>
Failed to restore a Squirrel object of type <....>

AN ERROR HAS OCCURED [null cannot be used as index]

CALLSTACK
*FUNCTION [VSquirrel_OnCreateScope()] unnamed line [166]

LOCALS
[result] NULL
[outer] NULL
[name] NULL
[this] NULL

image

Problem 3: Free Variables Within Class Methods

Using free variables within class methods in specific ways, particularly when they reference the class instance itself, can also lead to save/load errors.

Example:

class Foo {
    handler = null
    someInfo = null
    constructor(info) {
        this.someInfo = info
    }

    function RunHandler() return this.handler()
}

test <- Foo("secret information, lol")
test.handler = function() : (test) {
    // something logic
    printl("Aha! I know your info!!")
    printl(test.someInfo)
}

Final Notes and Workarounds:

I understand that classes in VScripts are not widely used in Portal 2 modding, and therefore, these issues might not be prioritized for fixing, which is completely reasonable. However, I'd like to offer some workarounds for those who do encounter these issues:

  1. Inheritance: Instead of using inheritance, consider using composition or replacing the class with a table and a clone method for creating copies.

  2. Mutual References: Use a table with delegation:

    ::qcam <- {}
    
    function qcam:new(owner) {
       local res = delegate qcam : {
           owner = owner,
           mode = null
       }
       res.mode = custommode(res)
       return res
    }
    
    class custommode {
       camRef = null
       constructor(cam) {
           this.camRef = cam
       }
    }
    
    z <- qcam.new("test")

    Or implement a global storage system, as in this example:

    cameras <- {}
    
    class qcam {
       mode = null
       owner = null
    
       constructor(owner) {
           this.owner = owner
           cameras[owner] <- this
           this.mode = custommode(this.owner) 
       }
    }
    
    class custommode {
       camOwner = null
       constructor(cam) {
           this.camOwner = cam 
       }
    }
    
    function GetCamera() {
       return cameras[this.camOwner]
    }
  3. Free Variables: Avoid using free variables in this particular way.

Radical Solution: Disable saving entirely with map_wants_save_disable 1, and you won’t have to deal with these issues! :D

IaVashik commented 2 months ago

I don't quite understand why this issue was moved to this repository, which is specifically related to the Linux native port, as I understand it. It seems that the Source-1-Games repository would be a more appropriate place for this problem, considering the nature of the issue. Could you please clarify the reasoning behind this decision?

kisak-valve commented 2 months ago

Hello @IaVashik, this issue tracker is more likely to be viewed by the Valve dev(s) that maintain Portal 2 than the Source-1-Games issue tracker.

IaVashik commented 2 months ago

Hello @IaVashik, this issue tracker is more likely to be viewed by the Valve dev(s) that maintain Portal 2 than the Source-1-Games issue tracker.

This is a little confusing to me, due to the repository description, but I get it. Thanks for the quick response!