Transport Fever 2

Transport Fever 2

Create your own game world!
Give your game a personal touch and change it to your liking. Create, share and install mods. Customize the game with new landscapes, vehicles, stations, assets and more.
okeating 22 29 Jun, 2024 @ 11:37am
Important note to mod developers: How to prevent random crashes
I think I have figured out the cause of some crash to desktop with "an error occurred":

It seems that when the api gives you a userdata object, the garbage collection link is only considered from the root object and not its children. That means if you reference a child object only, it can be garbage collected leaving you a "dangling pointer", at best giving a garbled result, and worst an unexplained crash to desktop with "an error occurred".

For example this is not safe:

local vehicleConfig = api.engine.getComponent(vehicle, api.type.ComponentType.TRANSPORT_VEHICLE).transportVehicleConfig

Do this instead:
local transportVehicle = api.engine.getComponent(vehicle, api.type.ComponentType.TRANSPORT_VEHICLE) -- this reference must stay in scope for as long as you need the child object local vehicleConfig = transportVehicle.transportVehicleConfig

Another example, unsafe:
local segments = api.engine.system.streetSystem.getNode2SegmentMap()[node]


Note also that this problem occurs if (less obviously) you iterate directly over the result, e.g.
for i, seg in pairs(api.engine.system.streetSystem.getNode2SegmentMap()[node]) do -- unsafe, "seg" may contain random data end



Do this instead:
local node2SegMap = api.engine.system.streetSystem.getNode2SegmentMap() -- refrence needed to safely access local segments = node2SegMap[node]


I am convinced this is required because I had chanced across a very reproducible scenario that was fixed by making a modification as described above.

I am not sure if this is a feature of lua or the way TP2 has designed its api, but either way these simple steps seem to eliminate random crashing.
Last edited by okeating; 29 Jun, 2024 @ 11:47am
< >
Showing 1-5 of 5 comments
RadiKyle 6 29 Jun, 2024 @ 12:04pm 
Hey thanks @okeating this is really useful info (tho I'm hoping I never need it haha).

It might still be over my knowledge level right now, but is this the same idea as trying to reference things in cases where the "parent" may not exist?

For example I'll try code that retrieves info for a nearby station module, then I'll try to read or write into its metadata or something, and get index error messages because I didn't account for the fact that there may not actually be a nearby station module, or it may not actually have the data that I'm trying to access. So I've learned that I often have to do stuff like this:

if module and if module.metadata and if module.metadata.platform and if....

instead of just:

if module.metadata.platform.justgimmewhatIwant

Is it the same idea here, that you're trying to access .transportVehicleConfig or a [node] index without knowing that the parent actually exists or that it actually contains that data?

Thanks!
okeating 22 29 Jun, 2024 @ 1:52pm 
Not quite - it is a bit more subtle, lets say that in your example "metadata" is a userdata object (not a lua table - this can depend on what point you are looking this up, test with:
type(metadata)=="userdata"

In the scenario I describe it would be something like this:
local platform -- to be assigned later if <some condition> then local module = getTheModuleFromApi() if module.metadata and module.metadata.platform then platform = module.metadata.platform -- at this point the "platform" reference is completely valid, and fine end -- at this point the root user data object has fall out of scope, it may have been gc'd and the memory at the location overwritten if platform.hasMyThing then ... -- access to the "platform" object - this will be ok probably 99% of the time, but may give garbage results or crash to desktop if was gc'd and memory was overwritten

Note this problem does not apply to pure "lua" objects. If the "module" you started with was a pure lua table, then no problem.

I speculate that there is something in the way the TPF code sets up these objects (in C) has not considered the possibility of a strongly referenced child when the parent has gone out of scope.
RadiKyle 6 29 Jun, 2024 @ 2:41pm 
Ah ok I think I get it now, thanks!

As a newbie to this and without much coding background, tbh I've been completely confused at times by how data is handled within the TpF 2 modding ecosys. There's been times I've had my mind blown when I discovered I could reference something that I didn't think was available to me in the current context, and then times I've gone mad when it wouldn't let me ref something that I swear I should be able to (with seemingly random reasons why at times).

The docs are adamant there's no "globals" and yet a lot of things sure seem to act like globals to me, despite everything being defined local. Tbh now I just mash buttons when coding, try stuff just to see if it works because I can't form a clear picture of the data scope. And that would probably walk me right into the type of issue you're describing too...
Last edited by RadiKyle; 29 Jun, 2024 @ 2:42pm
RadiKyle 6 10 Oct, 2024 @ 8:30pm 
...Stumbled over this topic again many months and much more coding later, and I think I maybe grasp it now.

I've just checked a few of my mods with api calls and it looks like I don't have any cases like this, but I see how it could happen very easily. For example:

if someStuff then local moreStuff = api.get.whatever() end if otherThings then local newThing = moreStuff[1] + 10 end

I think you're saying that last part might blow up because we've left the context of the first block where the api call was made, and the reference may have been flushed. And that even this is risky:

if someStuff then local moreStuff = api.get.whatever()[1] end if otherThings then local newThing = moreStuff + 10 end
Wyatt 24 Mar @ 6:01pm 
My error just freaking occured in my mac!
< >
Showing 1-5 of 5 comments
Per page: 1530 50