SaveGameSlot and refreshing save game list [Solved]

Started by WHAM, Mon 06/02/2012 22:38:55

Previous topic - Next topic

WHAM

It's me again, and with a new Save/Load GUI.
However, I have a small problem again.

Example scenario:

I have saved a game in slot 1 and it's description was written as "Testsave".
Later on I reopen the save/load GUI, which automatically calls the RefreshSavelist() given below and the name of the save shows up just fine and dandy. However, I then decide to delete the aforementioned save using the btnDeletesave_OnClick -function below, which just overwrites the save slot with a new name that identifies the slot as unloadable (I don't want null values and I want to keep the pre -created save files) and then refreshes the list again. But the refresh still gets the now deleted "Testsave" -name for slot 1! Only after the function has gone it's way completely and I reopen the GUI and the refresh function is run for a second time, do I get the correct value.

Wat do?

Code: ags

function RefreshSavelist() {
  LIST_Savegames.Clear(); // Clears the save game list 
  // Repopulate list manually with save slot descriptions
  LIST_Savegames.AddItem(Game.GetSaveSlotDescription(1)); 
  LIST_Savegames.AddItem(Game.GetSaveSlotDescription(2));
  LIST_Savegames.AddItem(Game.GetSaveSlotDescription(3));
  LIST_Savegames.AddItem(Game.GetSaveSlotDescription(4));
  LIST_Savegames.AddItem(Game.GetSaveSlotDescription(5));
}


function btnDeletesave_OnClick(GUIControl *control, MouseButton button)
{
  SaveGameSlot(ActiveSaveSlot, "Empty slot"); //"saves" game and gives the save the default "Empty slot" name, which identifies the slot as unloadable
  RefreshSavelist(); // Attempts to refrest save list - FAILS: deleted save game slot still receives it's pre-deletion name
}


Wrongthinker and anticitizen one. Utterly untrustworthy. Pending removal to memory hole.

monkey0506

#1
SaveGameSlot is a delayed function. This is by design. You can get around it...but...ugh.

Code: ags
// GlobalScript.asc

bool refreshSaveList = false; // added this

function RefreshSavelist() // this function is unchanged
{
  LIST_Savegames.Clear(); // Clears the save game list 
  // Repopulate list manually with save slot descriptions
  LIST_Savegames.AddItem(Game.GetSaveSlotDescription(1)); 
  LIST_Savegames.AddItem(Game.GetSaveSlotDescription(2));
  LIST_Savegames.AddItem(Game.GetSaveSlotDescription(3));
  LIST_Savegames.AddItem(Game.GetSaveSlotDescription(4));
  LIST_Savegames.AddItem(Game.GetSaveSlotDescription(5));
}

function btnDeletesave_OnClick(GUIControl *control, MouseButton button)
{
  SaveGameSlot(ActiveSaveSlot, "Empty slot"); //"saves" game and gives the save the default "Empty slot" name, which identifies the slot as unloadable
  // removed function call as it's not going to work if you put it here
  refreshSaveList = true; // update our variable
}

function repeatedly_execute_always()
{
  if (refreshSaveList)
  {
    RefreshSavelist();
    refreshSaveList = false;
  }
}


Technically the list is being updated in the next game loop after the call to SaveGameSlot. But, consider:

- User clicks mouse
- repeatedly_execute_always runs (refreshSaveList is false)
- btnDeleteSave_OnClick runs
- refreshSaveList is set to true
- SaveGameSlot is queued, to run at the end of the game loop
- ...
- SaveGameSlot is processed
- End of game loop
- Next game loop begins
- repeatedly_execute_always runs (refreshSaveList is true)
- RefreshSaveList is called and runs

There shouldn't be any difference between this and what you were trying to do.

Really we should do away with queueing (delayed) functions as they're confusing. We should assume that wherever we are in the script, that it's safe to execute the code being called without corrupting the game state. Functions like Character.ChangeRoom should ignore subsequent commands if they're placed inside of a room script as the current room is unloaded when that function is called...I get where CJ was going with queued/delayed functions...but it causes more problems than it fixes (IMO).

Also, since AGS updates the screen (presently) once per game loop, this technically means that the name would not change (visibly, in the list) until the second game loop after the one in which btnDeletesave_OnClick is called. Unless you're running at ridiculously low speeds though, this shouldn't even be noticeable. :P

WHAM

Thanks Monkey. I was HOPING to avoid having to resort to repeatedly execute in something that seems so very simple, but alas, it seems it is not meant to be.

If anyone else has ideas, feel free to contribute. If not, then I will go with Monkey's advice.

EDIT: Also marking this as solved
Wrongthinker and anticitizen one. Utterly untrustworthy. Pending removal to memory hole.

monkey0506

Calling code in repeatedly_execute(_always) isn't a bad thing, it's how computers work. The AGS engine itself is constantly polling for keypresses and mouse-clicks and the like. You can, to an extent, think of the engine as a giant repeatedly_execute function. It checks the current state of things, and calls the relevant code to update the screen, execute relevant functions, etc.

It can make the function seem cluttered if you have a lot of things of this nature that you have to update manually, but really it's not an inefficient solution. It's just an additional step you're taking to work around a (by-design) quirk in the AGS engine. It would be great if we could modularize something of this nature in a totally generic fashion, but except for using one of the many plugins that allow function pointers/delegates, it's not possible (and plugins are an ugly solution to your problems).

So, what would be best (IMO) is just to remove this "feature" from future versions of the engine. We could also include function pointers/delegates (based on the methods that Calin cleverly exposed in his plugin) as a new feature...anyway, that's a totally different topic.

If your GlobalScript is becoming too cluttered, you can always move your custom functions out to separate script files and then have the event handlers call them.

WHAM

I just tend to find Repeatedly Execute to be an easy solution to a lot of stuff and as a result my RepExecAlways gets reeeeally cluttered really fast, so I try to avoid using it too early on in a project so I can save it's use for the last minute crunch when I realize how much stuff still doesn't work two days before the deadline. :)

Also: now that you mention it, I should probably learn to create new script files t o organize the global script. I think I could go on the IRC to bother people with that one, though.
Wrongthinker and anticitizen one. Utterly untrustworthy. Pending removal to memory hole.

Khris

Just for reference:

This one goes without saying: only put stuff in repeatedly_execute that actually needs to be in there, not just because it's convenient.
If the game character is supposed to collect four items for an NPC, check if they have all four each time they collect one of the four/hand it over, not in rep_ex.

Use custom functions and organize your code. This is roughly how a jump'n'run's rep_ex could look like:
Code: ags
void repeatedly_execute() {

  HandleMovement();
  UpdateGameState();
  DrawScreen();
}

SMF spam blocked by CleanTalk