Magi Log 'N Load! The Ultimate Save-Load System!

This bundle is marked as pending. It has not been reviewed by a staff member yet.

Introducing: Magi Log 'N Load!

A logging-based save-load system that allows YOUR MAP to save-and-load at unbelievable speeds!

Overview

Installation

Success Cases

Common Questions

Changelog

Credits


Feature Overview:

  • Save units, items, destructables, terrain tiles, variables, hashtables and more with a single command!
  • Save and load tricky stuff, like transports with units inside, unit groups, cosmetic changes and per-player table and hashtable entries!
  • The fastest syncing of all available save-load systems, powered by LibDeflate and COBS streams!
  • The fastest game state reconstruction of all available save-load systems, powered by Lua!
This resource was generously commissioned by @Adiniz / Imemi and the folks at Azeroth Roleplay!
If you want to check MagiLogNLoad's features and performance in the context of a large and complex map, please give Azeroth Roleplay a go!


Please, be gentle upon providing feedback and leave a like, if you found it helpful.
If you wish to use this system in your map, please remember to credit me and consider linking back to this page.

In case you want to chat about the system or need help in using it, message me (ModdieMads) on Hive's Discord server!


If all you want is to inspect the source code, consider checking it out here:

Installation Steps

Step 1:
Open the provided map (MagiLogNLoad1010.w3x) and copy-paste the Trigger Editor's MagiLogNLoad folder into your map.
It's important to keep the top-down order of the scripts the same as in the provided map.
You don't need to copy the GUI variables if you are not using GUI in your map, but make sure that you pass true
to the _skipInitGUI argument in MagiLogNLoad.Init(_debug, _skipInitGUI) when calling it.
pic1-png.516631


Step 2:
Edit the MLNL Config script to fit your needs.
Create the necessary Abilities in the Object Editor (MagiLogNLoad.FILEIO_ABIL, MagiLogNLoad.LOAD_GHOST_ID)
pic2-png.516630


Step 3:
Call MagiLogNLoad.Init() just after the map has been initialized.
I recommend using a trigger with a "Elapsed game time is 0.10 seconds" event.
Please note that ALL Hashtables must be created AFTER calling MagiLogNLoad.Init()!
pic3-png.516629


Step 4:
Use "-save FILENAME" and "-load FILENAME" to save/load files.
pic4-png.516627


Step 5:
Give credit and a shoutout if you can!
I promise to also shoutout your map when given the chance!

Step 6:
For more advanced uses, check the provided map and read the script files.
pic6-png.516628

Make sure to give the provided test map a go!

The incredible folks at Azeroth Roleplay have made some amazing builds that make full use of this system!

If you want to see what the system is capable of at its peak, check the save-files below and test them inside the Azeroth Roleplay map!

To test the save-files, follow these steps:
1. Download the Azeroth Roleplay map and put into your Maps folder.
2. Download the saves attached to this post (Alaria.pld and Aesir.pld) and put them into your CustomMapData/AzerothRoleplay folder.
3. Start the map.
4. After it has loaded, enter "-load Alaria" or "-load Aesir" to make the magic happen!


Build Name: Alaria ( Save-File )
Author: @Adiniz / Imemi


dasd-webp.516620

aaaada7a-webp.516621

------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------
Build Name: Aesir ( Save-File )
Author: Alucard

yuguyguy-webp.516619



"How does MagiLogNLoad work?"
The system uses a series of logs to register various game events that occured before a -save command is issued.
When a player issues the -save command, the logs of that specific player are serialized into instructions and written into the save-file.
When a player issues the -load command, the save-file is read and the instructions in it are carried out to re-construct the game state.
"Can MagiLogNLoad handle saving and loading in multiplayer maps? Is it desync proof?"
Yes! Extensive tests with the Azeroth Roleplay map did not result in any desyncs.
If you encounter one caused by this system, please let me know as soon as you can.
"Do the logs created by MagiLogNLoad create leaks or slow down my map?"
No, the system indexes all logs using crossed-hashmaps and direct references. No naive searches are performed.
Multiple log entries of the same object cannot happen.
Also, the system automatically cleans hashmaps in tandem with Lua's garbage collector.
"Can I use MagiLogNLoad in a GUI-only map?"
Yes! The system is GUI-friendly and even uses GUI variables to guide its operations in order to minimize Custom Script actions.
pic7-png.516626

Check the provided map for how it's done.
"Can MagiLogNLoad save pre-placed units, items and trees/destructables?"
Yes! Pre-placed widgets are auto-indexed by their properties.
When loading pre-placed widgets, the system edits them to be the same as when they were saved.
Use the mlnl_Modes GUI-variable or the MagiLogNLoad.DefineModes() function to configure the system to save pre-placed widgets.
"Can MagiLogNLoad save units/items/destructables created at runtime?"
Yes! Widgets created at runtime can be re-created when loading in accordance with the system's configurable modes.
Saving/loading summoned units is not supported due to the TimedLife API bugs.
"Can MagiLogNLoad save just one specific unit, like my RPG's hero, including runtime changes to its base stats?"
Yes! The system can save almost all properties of a unit, with the notable exception of skins and temporary buffs.
Make sure to not use the SAVE_ALL_UNITS_OF_PLAYER mode if the player owns other units.
If something about your hero cannot be saved/loaded properly, let me know.
"Can MagiLogNLoad save cosmetic changes to units? Like orientation, flying height, vertex coloring, etc?"
Yes!
"Can MagiLogNLoad save and load boats or zeppelins with units inside?"
Yes, and any other transports too!
Do note that units inside the transport must be saveable, otherwise the transport will be loaded empty.
On the upside, this means you can easily save only specific units inside transports.
"Can MagiLogNLoad save and load Unit Groups together with the units in it?"
Yes! Do note that units in a Unit Group must be saveable, otherwise the group will be loaded empty.
On the upside, this means you can easily save partial unit groups.
"Can MagiLogNLoad save global variables? Even if they contain widgets, integers, strings or reals?"
Yes! All you need is to use the WillSaveThis function/GUI-variable, as depicted below.
Any public/global variable can be save-loaded by name as long as they have the currently supported types.
"How much is MagiLogNLoad compatible with GUI Hashtables?"
The system is fully compatible, as long as all Hashtables are created AFTER calling MagiLogNLoad.Init().
Hashtables created before MagiLogNLoad.Init() will break!
This is due to MagiLogNLoad use of @Bribe and @Tasyen's Hashtable-to-Lua-table converter.
Despite the inconvenience, this makes GUI Hashtables much faster, more robust, and saveable.
"What data types are currently saveable and loadable by MagiLogNLoad?"
The system can save: integers, reals, strings, units (and their abilities), items, and destructables.
The system can also load values directly into variables/hashtables/tables.
This allows for triggers and timers to become operational automatically after loading if set up properly.
Consider checking the provided map for more examples.
If you wish something to be made saveable (Quests? Temporary Buffs? TextTags?), hit me up!
You can always find me on Hive's Discord server.
"How does MagiLogNLoad find out which player is creating a tree or setting a variable?"
The system needs the map to tell it which player is responsible for something when that cannot be inferred from the action alone.
This is done by using the SetLoggingPlayer GUI-variable and MagiLogNLoad.SetLoggingPlayer() function.
If your map is single-player, you just need to use the SetLoggingPlayer variable and MagiLogNLoad.SetLoggingPlayer(player) once at the beginning.
Additionally, you can reset the logging player to make something NOT be saved.
Examples:

pic9-png.516622
"Can multiple players save and load simultaneously?"
Saving is a local operation that can be done anytime by anyone.
Loading commands are managed by a jam-free, automatic concurrency queue.
All -load commands issued are queued and executed one after another to make it desync-proof.
"Does MagiLogNLoad log EVERYTHING that happens in a map?"
No, the system only logs the minimum amount necessary according to its current scope.
That scope was defined by the Azeroth Roleplay map, and should work out-of-the-box with most roleplay maps.
On that note, the system at this moment cannot save quests, temporary buffs, text-tags, sounds, among other things.
However, since the system can save integers/reals/strings, it can perform the same as other available save-load systems in this area.
"How compatible is MagiLogNLoad with other systems I might have in my map?"
This system makes heavy use of proxying tables and hooking API functions.
Since the main requisite for this system was speed, the hooking and proxying are done directly.
This makes it incompatible with other systems that hook the same API functions, or edit the same metatables.

Since MagiLogNLoad uses @Bribe and @Tasyen's Hashtable-to-Lua-table converter, GetHandleId/BJ and StringHash/BJ are proxied to return the argument passed instead of its handle.
Despite the inconvenience, this is a good practice in Lua maps that cannot trust handles and string hashes anyway.
"What is Constant Overhead Byte Stuffing (COBS)?"
COBS is an old-school technique that allows for minimal overhead when encoding byte streams in Base255.
Minimizing the time spent syncing is how I achieved the fast speeds of this system, since the Sync API is the strictest bottleneck.
"Why is the save-file written in ideograms?"
Doing it this way achieves maximum throughput in FileIO by leveraging Lua's utf8 capabilities.
"MagiLogNLoad is in Lua, but my map is in JASS. Can you port this system to JASS?"
Unfortunately no, this system depends too much on Lua's features.
It's impossible to achieve both the speed and flexibility of MagiLogNLoad with JASS.
On another note, it is my strong recommendation that all maps in development should be ported to Lua, just for DebugUtils alone if nothing else.
"Ok then. Can you help me port my map to Lua?"
Hit me up! You can always find me(ModdieMads) on Hive's Discord server.
"I would like to use MagiLogNLoad in my map, but I need it adapted to my needs. Can you do it?"
Hit me up! You can always find me(ModdieMads) on Hive's Discord server.


1.10
- [FEATURE] MagiLogNLoad can now unload units from transports seamlessly when loading a save-file.
Maps using this system require the custom ability 'AUNL' (customized 'Adri').
Please refer to the provided test map and the MLNL_Config file.

- [BREAKING] MagiLogNLoad.CreatePlayerSaveFile has replaced MagiLogNLoad.SaveLocalPlayer.
YOU MUST UPDATE ALL YOUR <MagiLogNLoad.SaveLocalPlayer> FUNCTION CALLS.
It's now sync while updating references, then it becomes async when saving the actual file.

- [BREAKING] Mode <savePreplacedUnits> has been renamed to <savePreplacedUnitsOfPlayer>
GUI config string has been changed to SAVE_PREPLACED_UNITS_OF_PLAYER. Please update your triggers.
Functionality unchanged.

- [BREAKING] Mode <saveAllUnitsOfPlayer> has been renamed to <saveNewUnitsOfPlayer>.
GUI config string has been changed to SAVE_NEW_UNITS_OF_PLAYER. Please update your triggers.
Enabling this will only saved units that have been created at runtime.

- [BREAKING] Mode <saveDestrsKilledByUnits> has been renamed to <saveDestroyedPreplacedDestrs>.
GUI config string has been changed to SAVE_DESTROYED_PREPLACED_DESTRS. Please update your triggers.
Functionality unchanged.

- [BREAKING] MagiLogNLoad.ALL_CARGO_ABILS has been replaced by MagiLogNLoad.ALL_TRANSP_ABILS
It now contains 2 arrays: CARGO, with all <Cargo Hold> abils in the map; and LOAD, with all <Load> abils in the map.
All units are assumed to have just one abil of each category.

- Added the mode <saveDestroyedPreplacedUnits>.
Save ALL destroyed pre-placed units as long as a logging player is defined when the destruction happens.
- Added a way to change the commands "-save" and "-load" to arbitrary strings in the config file.
- At the request of @Antares, added the following callback functions to ease integration and message editing:
MagiLogNLoad.onSaveStarted
MagiLogNLoad.onSaveFinished
MagiLogNLoad.onLoadStarted
MagiLogNLoad.onLoadFinished
MagiLogNLoad.onSyncProgressCheckpoint
MagiLogNLoad.onAlreadyLoadedWarning
MagiLogNLoad.onMaxLoadsError

1.09
- Fixed a bunch of linting bugs thanks to @Tomotz
- Fixed a bug preventing huge reals from being saved correctly. S2R and R2S are still unstable.

1.08
- Added parallel bursting sync streams. Load times can be up to 50x faster.
- Added filename validation to prevent users from shooting themselves in the foot.
- Added internal state monitoring capabilities to the system. Errors should be much easier to track.
- Added the option for the map-maker to set a time limit for loading and syncing. The loading will fail in case it's exceeded.
- Added progress prints for file loading. They should show up at 25, 50 and 75% loading progress.
- Changed the default behavior for MagiLogNLoad.WillSaveThis. Ommitting the second argument will default to <true>.
- Fixed a possible jamming desync when multiple players are syncing at the same time.
- Fixed a bug involving some weird Blizzard functions, like SetUnitColor.
- Fixed a bug involving unit groups that could cause saves to fail.
- Fixed a bug involving proxied tables that could cause saves to fail.
- Fixed a bug causing real variables to sometimes save as 10x their normal value.
- Fixed a bug causing research/upgrade entries to sometimes not load correctly.


Credits

Contents

Aesir Save-File @AzerothRoleplay (Binary)

Alaria Save-File @AzerothRoleplay (Binary)

MagiLogNLoad_ModdieMads (Map)

In your Config file you really don't need to do the MagiLogNLoad prefix nor the do...end block, you can inline your variable set lines all into the creation of the object itself.

I would highly advise annotating your main script with ---@param Emmy Notation, as your variables are otherwise guesswork for readers. The lack of descriptive parameter names it not always helpful in that case either.

Another thing to keep in mind is that it's not always a virtue to have all code in one file. You have over 4000 lines of code in one script, and generally even after several hundred lines you should start asking if something should be de-coupled from the same script, if not far fewer. Getting the utility functions out of there would be a start.

I don't see anywhere in your demo map that you are using the Hashtable API wrappers. But it also needs to be accompanied by Influa (Lua-Infused GUI) to prevent memory leaks with persistent keys when handles are removed from the game.
 
Level 9
Joined
Jun 28, 2023
Messages
51
Thanks for the feedback!
In your Config file you really don't need to do the MagiLogNLoad prefix nor the do...end block, you can inline your variable set lines all into the creation of the object itself.
It started like that, but once during development there was a need for a table of prefixes/codes to be created by a loop and everything had to be pushed out of the table creation block. That loop was later made unnecessary, but the out-lining remained. The mandatory do...end is a harmless good habit that I don't want to weaken.
It's a little verbose, granted, but also somewhat friendlier to JASS/vJASS/GUI users from what I could gather.

Another thing to keep in mind is that it's not always a virtue to have all code in one file. You have over 4000 lines of code in one script, and generally even after several hundred lines you should start asking if something should be de-coupled from the same script, if not far fewer. Getting the utility functions out of there would be a start.
I agree that monolithic code has fallen out of favor, but I really like the gains in performance and ease of installation that comes with it. Keeping the code all in one file saves me from caching the utility functions locally wherever they are used. I understand where you are coming from (and that many-files-few-lines is the current metagame for programming), but I would ask you to consider the advantages of having as little "bureaucratic" code as possible in a performance-first resource.
I know that the microseconds gains are laughed at these days, but I really feel like they are understimated when present in large numbers.

I don't see anywhere in your demo map that you are using the Hashtable API wrappers. But it also needs to be accompanied by Influa (Lua-Infused GUI) to prevent memory leaks with persistent keys when handles are removed from the game.
They are at the bottom of the InitGUI() function (loc: 3514+).
I noticed the persistent keys and implemented an auto-cleaner for the references (look for the code containing the tabKeyCleaner table) that works in the same 30s interval of Lua's GC.

I would highly advise annotating your main script with ---@param Emmy Notation, as your variables are otherwise guesswork for readers. The lack of descriptive parameter names it not always helpful in that case either.
I was hoping I had made the parameter names more descriptive this time.:wsad: Will take this into consideration for future updates and resources.
The --@param notation, unfortunately, is the single most dangerous thing one can do to their code in this age of AI gobblers. I fully understand how convenient they are, and I am more than willing to make up for it and clarify/explain anything when asked about it. DM me and it will be no problem.
 
Level 9
Joined
Jun 28, 2023
Messages
51
  • CHANGELOG:
  • v1.08
  • - Added parallel bursting sync streams. Load times can be up to 50x faster.
  • - Added filename validation to prevent users from shooting themselves in the foot.
  • - Added internal state monitoring capabilities to the system. Errors should be much easier to track.
  • - Added the option for the map-maker to set a time limit for loading and syncing. The loading will fail in case it's exceeded.
  • - Added progress prints for file loading. They should show up at 25, 50 and 75% loading progress.
  • - Changed the default behavior for MagiLogNLoad.WillSaveThis. Ommitting the second argument will default to <true>.
  • - Fixed a possible jamming desync when multiple players are syncing at the same time.
  • - Fixed a bug involving some weird Blizzard functions, like SetUnitColor.
  • - Fixed a bug involving unit groups that could cause saves to fail.
  • - Fixed a bug involving proxied tables that could cause saves to fail.
  • - Fixed a bug causing real variables to sometimes save as 10x their normal value.
  • - Fixed a bug causing research/upgrade entries to sometimes not load correctly.
 
Ctrl F ";" Ctrl H "" :pwink:

Lua:
--Documentation has been kept to a minimum following feedback from @Wrda and @Antares.
I am confused as to how that is what you took away from our conversations.

The algorithms used here are beyond my understanding and I'm not a madlad like @Wrda who likes to turn over every line twice, so I can't comment much about them.

What I noticed:
  • The deaths of preplaced units are not recorded, while those of units created later are. Is this intended, the result of some config option, or a bug?
  • The strings in the config are not explained. You should write that these are reroutes to the GUI variables and you can just replace them with a value.
  • A short explanation of the MAX_ config options would be nice.
  • The system is producing a lot of print messages and even prints those for the players who are not doing the loading. Ideally, I think, these messages should be part of some onLoad callback function that can be customized in the config.
  • Lua:
    transp = nil;
    u = nil;
    This is not JASS, my guy :pwink:.
 

Wrda

Spell Reviewer
Level 28
Joined
Nov 18, 2012
Messages
2,010
WHY TEST MAP NO WORK AND WHY IT ISN'T SET TO 2 PLAYERS AT LEAST????? IT'S A SAVE LOAD SYSTEM WITH PURPOSE OF MULTIPLAYER NERDING 🤓

Lua:
MagiLogNLoad = {configVersion = 1080};
What is the configVersion member for?
I find it a bit unintuitive that MagiLogNLoad.Init() is initializing with GUI support, but initializing without it requires to pass arguments.
That being said, I tested the system with Antares, so part of what he said is also what I think.
The system looks to be flawlessly with a single caveat, whether it is intended or not (Antares #1 point).
The algorithms used here are beyond my understanding and I'm not a madlad like @Wrda who likes to turn over every line twice, so I can't comment much about them.
Give me my 1 hour of life back!
 
Level 9
Joined
Jun 28, 2023
Messages
51
Thanks for the feedback!
Lua:
--Documentation has been kept to a minimum following feedback from @Wrda and @Antares.
I am confused as to how that is what you took away from our conversations.
I thought that there was a call for reducing the ramblings and keep it as focused as possible. I have to say, I kinda like the terseness of the docs more now.
-
  • The deaths of preplaced units are not recorded, while those of units created later are. Is this intended, the result of some config option, or a bug?
This was very much intended, however at some point I added the option for them to be saved, but that was not working very intuitively.
Issue fixed! There is now a way to instruct the system to save the death/removal of pre-placed units (please check the changelog)
-
  • The strings in the config are not explained. You should write that these are reroutes to the GUI variables and you can just replace them with a value,
This tidbit has been added to the documentation, and a few lines were added to clarify any possible confusion.
-
  • A short explanation of the MAX_ config options would be nice.
Short explanations added.
-
  • The system is producing a lot of print messages and even prints those for the players who are not doing the loading. Ideally, I think, these messages should be part of some onLoad callback function that can be customized in the config.
This was always in the plans, and now it's a reality. Please refer to the changelog for more details.
-
  • Lua:
    transp = nil;
    u = nil;
    This is not JASS, my guy :pwink:.
Indeed. These nil assignments were a "what-if"-decision that I could not find a verdict on. Those have been removed now.
-
What is the configVersion member for?
This is used for "customer support". It's not so rare for someone to update just the MagiLogNLoad script and forget about the MLNL_Config script. This allows me to quick diagnose the problem. In any case, I am now also using it to check on Init() if the MLNL_Config file is present.
-
I find it a bit unintuitive that MagiLogNLoad.Init() is initializing with GUI support, but initializing without it requires to pass arguments.
I was gnawing at that decision for a while during development, but I stand by it.
It's much easier for a Lua map-maker to provide additional arguments to functions than a GUI map-maker. It also helps that Lua devs have a much easier time understanding code and reading the docs than GUI, so yielding the defaults to GUI is probably the more productive path.
-
The system looks to be flawlessly with a single caveat, whether it is intended or not (Antares #1 point).

Give me my 1 hour of life back!
Thank you very much for the compliment!
Indeed, this caveat has been fixed, and there's now an option to save the death/removal of pre-placed units. Please refer to the changelog. Also, excellent time on evaluating this system. It takes me one hour just to bring myself back to speed on it, and I'm the freaking author!
 
Level 9
Joined
Jun 28, 2023
Messages
51
CHANGELOG
1.10
* [FEATURE] MagiLogNLoad can now unload units from transports seamlessly when loading a save-file.
Maps using this system require the custom ability 'AUNL' (customized 'Adri').
Please refer to the provided test map and the MLNL_Config file.

* [BREAKING] MagiLogNLoad.CreatePlayerSaveFile has replaced MagiLogNLoad.SaveLocalPlayer.
YOU MUST UPDATE ALL YOUR <MagiLogNLoad.SaveLocalPlayer FUNCTION CALLS.
It's now sync while updating references, then it becomes async when saving the actual file.

* [BREAKING] Mode <savePreplacedUnits> has been renamed to <savePreplacedUnitsOfPlayer>
GUI config string has been changed to SAVE_PREPLACED_UNITS_OF_PLAYER. Please update your triggers.
Functionality unchanged.

* [BREAKING] Mode <saveAllUnitsOfPlayer> has been renamed to <saveNewUnitsOfPlayer>.
GUI config string has been changed to SAVE_NEW_UNITS_OF_PLAYER. Please update your triggers.
Enabling this will only saved units that have been created at runtime.

* [BREAKING] Mode <saveDestrsKilledByUnits> has been renamed to <saveDestroyedPreplacedDestrs>.
GUI config string has been changed to SAVE_DESTROYED_PREPLACED_DESTRS. Please update your triggers.
Functionality unchanged.

* [BREAKING] MagiLogNLoad.ALL_CARGO_ABILS has been replaced by MagiLogNLoad.ALL_TRANSP_ABILS
It now contains 2 arrays: CARGO, with all <Cargo Hold abils in the map; and LOAD, with all <Load abils in the map.
All units are assumed to have just one abil of each category.

* Added the mode <saveDestroyedPreplacedUnits>.
Save ALL destroyed pre-placed units as long as a logging player is defined when the destruction happens.

* Added a way to change the commands "-save" and "-load" to arbitrary strings in the config file.
* At the request of @Antares, added the following callback functions to ease integration and message editing:

MagiLogNLoad.onSaveStarted
MagiLogNLoad.onSaveFinished
MagiLogNLoad.onLoadStarted
MagiLogNLoad.onLoadFinished
MagiLogNLoad.onSyncProgressCheckpoint
MagiLogNLoad.onAlreadyLoadedWarning
MagiLogNLoad.onMaxLoadsError
 
Last edited:
Top