-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
Serialization of FlexibleConfig #2893
Comments
I propose keeping unique top-level FCs, instead of storing them per save game. The idea is that the user (UI) should be able to define from which scope (of the three different Setting scopes, namely global engine, global module and per save game) a Setting should be loaded from and to which scope a modified Setting should be written (both preferably on a per-Setting basis), while keeping everything hidden from FC consuming engine code and third party modules, and preventing FC consumers to "hack" around Settings by using a Setting from a different Setting scope. This relies on the making a single FlexibleConfig manage all Settings of a specific type/usage rather than having multiple uncoordinated and unlinked FCs manage similar settings of different scopes but of the same type/usage. This may also require each FlexibleConfig to have its own unique ID, so that Settings can keep track of the FlexibleConfig they belong to. The way I'm envisioning this is that during load time, the FlexibleConfig/Setting deserializer will look for the FCs' settings in the three different setting scopes, namely global engine, global module and per save game (ordered with increasing priority). If Settings with the same ID are present in multiple scopes, only the Setting in the scope with higher priority will be loaded into the FC, unless the desired scope is explicitly stated by the user. At all places in the UI where a Setting can be modified, the options of
should be available. (These two options could potentially be merged.) To help illustrate this further, consider the following example. An engine FlexibleConfig defines a setting "SettingA". The user overrides "SettingA" in the save game scope, leaves its scope setting to the default (i.e. Settings are accessed from scopes according to scope priority) and saves the setting. As soon as the setting is saved (serialized) on button click, "SettingA" is retrieved from the desired scope (which is "save game" (highest priority) in this case) and stored into the FC, replacing the value before the modification and notifying SettingA's subscribers. From this point on, any calls to SettingA's Ping @emanuele3d @vampcat @Cervator |
I forsee a lot of confusion and mixing of terms for the 3 levels of the FCs, so for the purpose of this post, I'll be calling the 3 levels Global, Module and Save, although the final names will probably be slightly different. Unless I'm mistaken, having a single top level FC won't work, since we're hoping to provide 3 different levels for each setting. Unless we're willing to hack around the settingName to things like settingName_GLOBAL, settingName_MODULE and settingName_SAVE to save all 3 levels in one FC, 3 diffrent FCs are a better option. That being said, I don't recommend we use 3 raw FCs either. I was planning on throwing together a PublishableSettings class, that:
@eviltak This is in line with a lot of your points like "keeping everything hidden from FC consuming engine code and third party modules, and preventing FC consumers to "hack" around Settings by using a Setting from a different Setting scope", but takes a much.. simpler approach. Now, onto finer points of your post. "(These two options could potentially be merged.)" Regarding the saving : The serizalization of an FC will store all it's contents to the disk, as they are. If you feel I missed out something or I'm wrong on any count, please do let me know :) |
Posting just to comment that I read it all, and think I understand it ... but I don't have a hugely strong opinion on it so far with what's been posted. Sounds like the ideal target is being narrowed in on. Will keep an eye out for further details and maybe some initial code sample work :-) |
@vampcat I think you may not have fully understood my proposal (which I will elaborate further later in this post). I see two major problems with your proposal:
On the other hand, my proposal does not require
Both your proposed approaches (I agree, the first one is more of a hack) do not "keep everything hidden from FC consuming engine code and third party modules and prevent FC consumers to 'hack' around Settings by using a Setting from a different Setting scope", since the settings in various scopes are still accessible in some way or the other. My approach is much simpler than you think. It doesn't require any modification of the FlexibleConfig and Setting implementations as implemented in #2757, nor does it involve adding extra boilerplate to store and handle settings in the various scopes. Settings are stored in FCs normally, without any knowledge of which scope they belong to (be it through an ID suffix, a field, or any other mechanism) and without having multiple FCs containing similar settings but of different scopes. It is the (de)serializer that manages (manages is a loose term) the setting scopes. The workflow will be as follows:
In the 3rd and 4th steps we overwrite the values in the setting because there is only one of two possibilities:
Regarding the usage of unique IDs for FlexibleConfigs, it will be needed for storage and retrieval of various FlexibleConfigs created by the engine and third party modules, maybe through a class FlexibleConfigStore that uses a Map internally, but is out of scope for this issue. I hope that makes it more clear. |
That explanation helps clear things out a lot. First, to address your points: "How can we ensure that consumers use only the getSetting method, and not any of the other accessors?" "Which setting should a subscriber subscribe to, the Global FC, the Module FC or the Save FC?" "Both your proposed approaches (I agree, the first one is more of a hack) do not "keep everything hidden from FC consuming engine code and third party modules and prevent FC consumers to 'hack' around Settings by using a Setting from a different Setting scope", since the settings in various scopes are still accessible in some way or the other." That being said, I can certainly see the appeal of your approach, and would be more than happy to go with it. A few questions/comments though: "If the user simply changes a setting's desired scope without updating the value, the setting is simply loaded from the desired scope and the value of the setting is updated to the value of the setting in the new scope" "Regarding the usage of unique IDs for FlexibleConfigs, it will be needed for storage and retrieval of various FlexibleConfigs created by the engine and third party modules, maybe through a class FlexibleConfigStore that uses a Map internally, but is out of scope for this issue." |
That's the reason I mention a setting's "desired scope" everywhere. We will need to store a simple value (even an integer works) designating the setting's desired scope along with the setting (this would have been a requirement even when using another approach), which the deserializer takes into account if specified or if a value is available in the desired scope. If not, the deserializer falls back to the priority order. @vampcat I see what you're getting at regarding the usage of FlexibleConfig. I have one question though. How will setting ID collisions be dealt with if each module will be using a single FC to store its settings? I think a better approach would be to use unique FCs internally for each module, without exposing any details about the FC to the module. This will help in a number of ways:
Modules will/will not be given access to the engine FCs, as decided upon later. The engine will manage a FC for each loaded module that requests one through a class like |
"this would have been a requirement even when using another approach" Consider SnowWhite module again. In one saved game instance, we use Save scope value. In another, we use Module scope. In another, Global scope. That means, we're saving the scopeSelector variable on a per-game basis. Too many complications, imho. @emanuele3d : i would love to hear what you have to day about a single FC vs PublishableSettings, and deleting higher priority value vs using scopeSelector. "How will setting ID collisions be dealt with if each module will be using a single FC to store its settings?" Regarding sandboxing, if we need to, we can implement simple checks that compare the module name and the simpleUri of setting requested, if we really want to. "It will prevent setting ID collisions (unlikely but still possible)" "It will allow for easier segregation of settings for each module after serialization" With all that said, I can work with either global FCs or module-specific FCs, so unless I find a reason to swing in either direction I'll leave the choice upto manu. |
Just a few quick notes until I find the time to respond more in depth.
I'll get back to you with more in the next few days. |
Quick scope note (sort of re: @emanuele3d's 1st point): one of the GSOC items and/or part of what @Vizaxo might end up working on includes enhancing the world setup phases to include a preview/config stage before you fully create a world. Similar stuff could be done for saved worlds (that item didn't get a slot though - but maybe unofficial work?) Existing world preview/details has this to a point, where configurable settings in a chosen world generator from a module is exposed in the UI before you enter the world. But I think that's more stuck in the world generator system as a one-time thing to expose rather than any sort of persisted config. Just another thing to keep in mind, not otherwise trying to make a point :-) |
@Cervator: good point. In my 1st point I sort of included world gen in the concept of Module settings not existing until a game is loaded/created, but I can see it was too subtle a distinction. Indeed module settings (at this stage) begin to exist as soon as a game is being configured for the first time, which includes, for example, world generation. But there is a gap between the time when the game is being configured for the first time and the time the player is actually in-game. This gap does not exist when a game is loaded instead. I think the key will be to restrict the scope of the setting changes depending on the user context.
In this scenario the different scope UIs would simply have different pull-down menus for each setting, providing more or less options depending on the context. On that base, if we really wanted to, we could then have an additional button/pull-down menu, allowing the user to make a given custom value a global or module default. But I'd suggest working on this only when everything else is settled and works. Notice here I've been describing UIs fully aware this issue is about serialization rather than UI. I'm just using the UI-side of things to explore usage scenarios which in turn should help defining the underlying architecture. No time to contribute more to the discussion right now. I will write as soon as I can a follow-up post that goes deeper into the architectural/programmatic side of this. |
As we work out the bigger picture, and also noting that there is a fair amount of interest and a sense of urgency around FlexibleConfigs, I think we need make concrete progress. In this context I'd recommend a more pragmatic approach:
instead:
To clarify the difference between the second bullet point and point 2: theoretically MyModule could override a global setting so that any game taking advantage of MyModule uses the overriding value rather than the global one. I.e. the global setting might be: "engine:RoseColor" = "pink" while MyModule might override with "engine:RoseColor" = "blue". All games using MyModule will then have blue roses by default. In practice here I'm suggesting that to make some progress we will skip that aspect for the time being and MyModule can only publish settings in the form "MyModule:MySetting" - modules would still be able to access engine settings values via the API and modify them though, if necessary. @vampcat, @eviltak, @Cervator: how does this sound? Finally, a couple of issues we need to address sooner rather than later:
I look forward to your comments. |
How about private FCs? ("TCPWhitelistConfig"?) |
@soni: please elaborate. |
Not exposed to modules. |
In terms of functionality, I think engine-private settings could be useful. In terms of implementation, at this stage I'd implement settings that have to stay engine-private storing them in a separate FC object, one that doesn't get published in the Context/CoreRegistry or one that gets published in a sub-context that the modules do not obtain. And perhaps there are other ways, i.e. relying on sandboxing functionality. Initially though, I'd focus on providing public settings functionality as there are lots of details to iron out there. |
With PR #2757 almost ready to merge, it's time we start looking at the future of FlexibleConfig.
The currently open PR only deals with the actual implementation of FC, and not the storage/retrieval of data from the disk. This aspect is of utmost importance before this class can be used for practical purposes, since this is what makes settings persist across game sessions.
Here are the descriptions of a number of different, co-existing FCs:
Interestingly, the last description provides hints to previously unforeseen challenges. How do we connect one or more global FCs to one specific to the currently running game, so that the game-specific one simply defers to the global one for settings that the user has not yet customized? Or would this be the wrong approach? Should we instead make simple deep-copies of global FCs and use those within games, no dynamic linking involved? Or should the Setting class be expanded to always accommodate an hardcoded default value, a customized global value (if any) and a per-game value (if any)?
These are issues to be considered and also determine the path for the eventual development of a NUI frontend for FCs, as discussed in #2894.
Lastly, but compatibly with human resources, ideally this should be at least partially implemented before the start of GSoC, so that this class can be used during the work period. Specifically, it could be possible to at least implement global engine/module FCs for the benefit of GSOC projects but leave per-saved-game FCs at a lower priority.
Pinging @eviltak @emanuele3d @Cervator for feedback, and suggestions.
The text was updated successfully, but these errors were encountered: