Frame Doctor (Automatic Save&Load Repair)

This bundle is marked as high quality. It exceeds standards and is highly desirable.
Overview

When you save and load a game, all custom frames are broken. They are no longer visible and calling any native on them crashes the game. The fix in the past has been to use a Frame Loader to reinitialize all of the frames upon loading a game. This approach works in a static context, where frames are created upon map initialization and then rarely modified. However, in a dynamic situation, where frames are created and modified constantly, this approach is entirely impractical, because you'd have to correctly update the state of the frames and re-reference everything.

This is where Frame Doctor comes in. Simply copy the Frame Doctor script plus its dependencies into your map and your frames will be automatically repaired and reloaded when a saved game is loaded. You no longer need Frame Loader functions. In fact, you don't need to do anything else.


Potential Side Effects

Ok, I would be remiss if I didn't tell you about the potential pitfalls of using Frame Doctor. These, however, should be rare and easy to fix if they occur.

Frame Doctor works by wrapping each frame native so that it accepts and returns tables instead of frames. The framehandle is stored in the table and retrieved before calling the native. This means that all your framehandle variables are actually secretly tables in your code. Generally, this doesn't change how you use them and you don't need to tell your IDE about this. However, there are some exceptions:
  • If you do type(myFrame) it returns "table" instead of "userdata". If you wanna check if something is a frame, you can use the IsFrame function. If you need to access the underlying framehandle, use GetFrame.
  • When you're debugging your code, the error messages you get when you pass nonsense into the frame natives might not be as useful. I therefore recommend that you disable Frame Doctor while working on your map and only enable it for the release version. If you need to create a condition to check if FrameDoctor is enabled, do if FrameDoctor then.
  • Frame Doctor works well with other hooks for most frame functions, but it breaks BlzFrameGetChild and BlzFrameGetParent hooks (extremely relevant, I know!).
Also note that Frame Doctor overwrites the BlzDestroyFrame function as this function can lead to desyncs. It is replaced with a BlzFrameSetVisible(myFrame, false) call, so you can use it safely. Using this function will prevent the frame from being recreated upon map load.

Installation

Copy the Frame Doctor script and its requirements into your map. These are:
Make sure TotalInitialization is above the other scripts in your map script.

Make sure that any Frame Loader functions on other UI resources are disabled as that would duplicate the frames.

Launch your map, then save and load the game to verify that everything is loaded correctly.


How it works

Whenever you create or reference a frame, it is registered and added to an ordered list. Take this code snippet for example:
Lua:
local myFrame = BlzCreateFrameByType("TEXT", "", BlzGetOriginFrame(ORIGIN_FRAME_WORLD_FRAME, 0), "", 0)
BlzFrameSetText(myFrame, "Consult your Frame Doctor!")
BlzFrameSetAbsPoint(myFrame, FRAMEPOINT_BOTTOMLEFT, 0.35, 0.25)
This will register two frames, the world frame and the myFrame. Because the BlzGetOriginFrame call is resolved first, the world frame is added to the list first. BlzGetOriginFrame returns a table and stores the actual framehandle along with the way to access it, which in this case is via the path "ORIGIN_FRAME_WORLD_FRAME-0". myFrame is registered second, storing the constructor function and all the arguments, including the data table of the parent frame. Then all natives that were used on it are stored in its data table.

When the game is loaded, Frame Doctor loops through all registered frames from beginning to end. First, the memory address of the new world frame is updated, using the path "ORIGIN_FRAME_WORLD_FRAME-0" to retrieve it. Then, the myFrame framehandle is created, using the stored constructor function and retrieving the new world frame from the data table stored as its parent. After all the frames are created, all modifying functions are applied on them, in this case BlzFrameSetText and BlzFrameSetAbsPoint.

Frame Doctor does not store the order in which modifying functions were applied to frames. This should not be a problem most of the time because these function calls are commutative. There might be some rare instances where you're using BlzFrameSetSize, BlzFrameSetPoint, BlzFrameSetAbsPoint, and BlzFrameClearAllPoints in a weird order, but just don't do that then 🫠.
Contents

Frame Doctor (Binary)

Reviews
Wrda
Doctor House is getting a promotion for being way too useful, a must have in singleplayer maps if you have custom frames.
I need to test it with UI v1.5
I just tested it and it doesn't work. chopinski is initializing his library at InitBlizzard, long before the game starts, which I think is something you shouldn't do with UI (@Tasyen ?); before my library initializes. @Bribe can I set an initialization point with TotalInit before InitBlizzard?
 
I just tested it and it doesn't work. chopinski is initializing his library at InitBlizzard, long before the game starts, which I think is something you shouldn't do with UI (@Tasyen ?);
I suggested this, when one wants to hide the bottom background textures that one moves bottom of ("ConsoleUI",0) out of screen. which needs to happen earlier, otherwise it crashed. Although i think the better way is to change the game interface fields to the blank blp . When I remember right, chopinski wanted no such so he went for the ConsoleUI move.
 
I just tested it and it doesn't work. chopinski is initializing his library at InitBlizzard, long before the game starts, which I think is something you shouldn't do with UI (@Tasyen ?); before my library initializes. @Bribe can I set an initialization point with TotalInit before InitBlizzard?
Yes, there are "root", "config" and "main" that you can use instead.
 

Wrda

Spell Reviewer
Level 28
Joined
Nov 18, 2012
Messages
2,010
I and Antares came to the conclusion that DISABLE_IN_MULTIPLAYER is not needed at all since Save Load works there, thus also removing the need to check for player slot state.
It's a niche but extremely useful resource that certainly makes Save/Load players happy, for RPGS or long maps or anything between.
Pay the doctor a visit and bring him chocolate for all the things he has done!

Approved
 
Top