Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Replay system #365

Closed
wants to merge 29 commits into from
Closed

Conversation

NQNStudios
Copy link
Collaborator

This is the big one.

@NQNStudios NQNStudios marked this pull request as draft June 13, 2024 18:30
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Preferences need to be recorded and retrieved by the replay system, or else the replays won't be deterministic in a lot of very unpredictable ways.

On Mac, a snazzy system library is used to manage preferences. But I think we should change things so that Mac handles preferences the same as Windows and Linux, because that way recording and replay can be deterministic on Mac too.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer not to change how the Mac stores preferences, and I don't understand how the Mac's preferences system gets in the way of what you want to do.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, looking through the code it seems like a Mac library handles all of the preference syncing behind the scenes, and I couldn't find an API function that would let me iterate through and serialize all of the preferences. It looked like the Mac API provides a dictionary object where you have to know a key to get it's value.

Which is really, really, weird. So maybe I just missed something.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So maybe I can use that function to write Mac-specific logic that serializes and deserializes the NSUserDefaults, but that seems weird to me, when we could run the same code on all 3 platforms instead.

Copy link
Collaborator Author

@NQNStudios NQNStudios Jun 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also don't know the programming language that prefs.mac.mm is written in, which makes things awkward. Is it Objective-C?

Copy link
Member

@CelticMinstrel CelticMinstrel Jun 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes and no. It's Objective-C++ – an unholy fusion of C++ and Objective-C.

You should probably be aware that things other than the core preferences are stored in the MacOS preferences plist. For example, it'll remember the last folder you opened a saved game from. Such things likely shouldn't be persisted in the replay, as they are platform-dependent.

Copy link
Member

@CelticMinstrel CelticMinstrel left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks decent enough as a starting point. The goal of recording the entire application (as opposed to just a game session) seems slightly absurd to me, but I guess we'll see.

src/tools/prefs.win.cpp Outdated Show resolved Hide resolved
proj/xc12/BoE.xcodeproj/project.pbxproj Show resolved Hide resolved
}

void record_action(std::string action_type, std::string inner_text) {
Element* root = log_document.FirstChildElement();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't really an issue, but I'm noticing you didn't use the XML printer class that the scenario editor uses to write scenario files. It might not actually be appropriate here, I'm not sure; but in case you simply didn't know about it, I thought I'd mention it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the situation with this? Is it appropriate to use my XML printer class here or not?

@@ -83,8 +83,7 @@ std::unique_ptr<Element> pop_next_action(std::string expected_action_type) {
Element* clone = next_action->Clone()->ToElement();
root->RemoveChild(next_action);

std::unique_ptr<Element> ptr { clone };
return ptr;
return *clone;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is what you're doing, do you really need to clone? Couldn't you just replace the clone line by Element clone = *next_action;?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calling RemoveChild() on next_action was freeing the data. Cloning is necessary to keep it around without leaving it in the document object

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you saying that Element clone = *next_action does not copy it?

But as it is now, isn't the clone still being leaked?

Copy link
Collaborator Author

@NQNStudios NQNStudios Jun 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But as it is now, isn't the clone still being leaked?

when a block does

{
   Element next = pop_next_action()
}

I thought the block is taking ownership of the data, then when the scope ends, it gets deleted.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you saying that Element clone = *next_action does not copy it?

Maybe it would.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For it to not crash, Clone() is required.

Element clone = *(next_action->Clone()->ToElement());

This works. Does that avoid a memory leak?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think calling Clone() in and of itself might be a memory leak, but I'd have to see the internals of Clone() be sure. The typical internals of a function called clone is essentially return new ClassName(*this), and if that's what it's doing, then this line is certainly a memory leak. But maybe Clone() is actually doing something else. I'm not sure.

Copy link
Collaborator Author

@NQNStudios NQNStudios Jun 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked into Clone() and it's definitely calling new. Which brings me full-circle to what I was trying to accomplish by returning a unique_ptr: I thought wrapping the pointer returned by Clone() in unique_ptr would make the unique_ptr manage that memory, so when the pointer went out of scope in the calling context of pop_next_action(), it would delete the clone.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the document object might still manage the memory of the pointer returned by Clone() though. Because I get a segfault when unique_ptr manages the pointer and frees it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked into Clone() and it's definitely calling new. Which brings me full-circle to what I was trying to accomplish by returning a unique_ptr: I thought wrapping the pointer returned by Clone() in unique_ptr would make the unique_ptr manage that memory, so when the pointer went out of scope in the calling context of pop_next_action(), it would delete the clone.

This is a more or less correct line of reasoning. The only problem is, it's not clear whether deleting the clone is the correct way to dispose of it.

* DRY, standardized window top offset
* handle_splash_events() handle multiple events per frame
* accurate windows menubar height for multiple rows
* Windows filter a resize event triggered by the menubar
* windows expand small window to fit menubar
* splash screens draw in view rect, not window rect
@NQNStudios
Copy link
Collaborator Author

NQNStudios commented Jul 3, 2024

That's annoying -- I wanted to rebase this onto master and it ended up adding commits from master into the PR

EDIT: trying again.

@NQNStudios NQNStudios force-pushed the replay-system branch 2 times, most recently from bf9900f to 43ef19c Compare July 3, 2024 19:48
@NQNStudios NQNStudios closed this Jul 3, 2024
@CelticMinstrel
Copy link
Member

Kinda looks like you rebased it backwards? If you're on the replay branch, you want to rebase it onto master with git rebase master. You definitely don't want to be on master and rebase onto this branch.

@NQNStudios NQNStudios deleted the replay-system branch September 8, 2024 20:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants