-
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
Added FCManager and support for FC serialization #3118
Conversation
Hooray Jenkins reported success with all tests good! |
1 similar comment
Hooray Jenkins reported success with all tests good! |
|
||
Map<SimpleUri, String> getUnusedSettings(); | ||
|
||
Map<SimpleUri, Setting> getActiveSettings(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand the difference between an unused and an active setting from the api, maybe add some javadoc to explain the concept?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think some javadoc is warranted indeed, but I would also work on the terms used.
I think "settingsFromFile" and simply "settings" would go some way to mitigate this issue.
Other options might be "temporarilyParkedSettings" which is pretty accurate if I think about it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was planning on doing that after the code stabilizes a bit, but I'll add some comments explaining the basic logic for reviewers' benifit.
if (unusedSettings.containsKey(id)) { | ||
setting.setValueFromString(unusedSettings.remove(id)); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could be an unexpected sideeffect because a caller of fc.add(setting)
could expect that an add call keeps the value of the setting the same.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this will need to be clarified in the method's javadoc @oniatus, as it is the intended behavior.
The config file is loaded in memory, but its values aren't immediately imported into an existing FC - the config file contains too little information for that, i.e. it doesn't contain settings types. Instead, as systems add settings to the FCs (on startup or on game startup), IF a corresponding value is found in the file, that value overrides the setting's default.
Concrete example: display resolution. When the engine attempts to add, on startup, a display resolution setting to the appropriate FC instance, the corresponding setting instance is indeed added. But if the FC finds a value for it in the file, it will reset the setting to that value. Otherwise the setting maintains the default value.
I reiterate this happens on startup or on game startups, when modules are loaded. Nothing should add settings at any other time - another notion we might want to make clear in the javadocs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure how else we can handle the saving/loading part.
Can we make it clear in the javadocs that adding a settings will loads the non-default value that was saved earlier, and that if you want the default value, you must call .resetValue()
right after?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess you can, but I don't think we should encourage anybody to override a user's choice without his or her consent.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My previous comment wasn't quite concrete enough. I'd say: yes document the behaviour in the javadoc, no do not suggest to call resetValue() if necessary - it shouldn't be necessary unless the user wants it.
return jsonObject; | ||
} | ||
} | ||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dead lines, can be removed if not needed.
import org.terasology.engine.SimpleUri; | ||
|
||
public interface FlexibleConfigManager { | ||
void addFlexibleConfig(SimpleUri flexibleConfigUri, FlexibleConfig fc); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- use flexibleConfig for the second parameter, similar to the first one.
- the interface could define the behavior when two configs with the same uri are added (throw RTE)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I second what @oniatus is saying and further refine it with the following declaration:
void addFlexibleConfig(SimpleUri configId, FlexibleConfig config) throws RuntimeException;
Potentially we could also use UnsupportedOperationException
, to be a bit more specific. Or we could go with a custom exception such as DuplicateConfigException
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, I'd rename the method to simply addConfig.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When you say "the config can define the behaviour", you mean I should add a throws RuntimeException
here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, so based on a quick tour of the rest of the FC code, the throws
clause is added in the javadocs everywhere, and nowhere in the declaration.
Adding it in the code here makes things inconsistent - although we can can obviously go back and add it to the code everywhere.
Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for the delay about this.
I think in this case it is warranted for the interface to show the exception thrown as it is part of the intended behavior of this method AND it diverges from the behavior of a typical map that would just overwrite the previously existing value for a given key.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had not considered the second part - the divergence. Definitely makes way more sense to me now :D
gson = new GsonBuilder().setPrettyPrinting().create(); | ||
} | ||
|
||
return gson; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think lazy initialization provides a benefit here. Gson is not that expensive to build and also not that expensive to keep.
I would inline the initialization logic to the field and use direct field access instead.
value = (T)(Double)Double.parseDouble(valueString); | ||
} else if (value instanceof String) { | ||
value = (T)valueString; | ||
} else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't like this block for three reasons:
- It violates OCP because every new type needs a code change
- The double cast looks odd
- It supports only int, float, double and string but this is only visible in the code
I don't have a better alternative right now but I am sure we will find one :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I don't like this part of the code either - one of the first that I plan on reworking, as soon as i get a bit of free time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with @oniatus assessment.
I now have the even stronger feeling we might want to go back to a Setting class that is abstract, -not- generic-based, and is the base for classes such as FloatSetting, IntSetting etc.
I guess one alternative would be delegate the parsing to type-specific parsing objects which can be registered somewhere at runtime and retrieved/used in this method.
Any other possibility?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"delegate the parsing to type-specific parsing objects which can be registered somewhere at runtime"
Sounds a lot like gson adapters - can't say I'm the biggest fan of that.
I've implemented a hybrid generic-and-concerete solution which I feel is a decent compromise. Have a look at it.
FlexibleConfig renderingFlexibleConfig = new FlexibleConfigImpl(); | ||
flexibleConfigManager.addFlexibleConfig(new SimpleUri("engine:rendering"), renderingFlexibleConfig); | ||
|
||
flexibleConfigManager.load(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can't this be part of config.load()
? We already have an integration to config.save()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One thing we'll have to be careful about is that FCs get initialized -before- config gets loaded. Not sure if this is possible for every single config.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the aim is to eventually eliminate Config entirely and leave only the FCmanager to fill those shoes. So, I think it makes sense to keep them in parallel for the time being.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Quite a few issues I'm afraid, but various are deeper architectural issues that would have been hard to foresee earlier and is good to discuss in this format.
@@ -142,6 +144,8 @@ public void save() { | |||
} catch (IOException e) { | |||
logger.error("Failed to save config", e); | |||
} | |||
|
|||
CoreRegistry.get(FlexibleConfigManager.class).save(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An instance of this config class is created in ConfigurationSubsystem. Please, let's pass the Context to it, on construction, so that we don't have to use the CoreRegistry.
|
||
Map<SimpleUri, String> getUnusedSettings(); | ||
|
||
Map<SimpleUri, Setting> getActiveSettings(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think some javadoc is warranted indeed, but I would also work on the terms used.
I think "settingsFromFile" and simply "settings" would go some way to mitigate this issue.
Other options might be "temporarilyParkedSettings" which is pretty accurate if I think about it.
if (unusedSettings.containsKey(id)) { | ||
setting.setValueFromString(unusedSettings.remove(id)); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this will need to be clarified in the method's javadoc @oniatus, as it is the intended behavior.
The config file is loaded in memory, but its values aren't immediately imported into an existing FC - the config file contains too little information for that, i.e. it doesn't contain settings types. Instead, as systems add settings to the FCs (on startup or on game startup), IF a corresponding value is found in the file, that value overrides the setting's default.
Concrete example: display resolution. When the engine attempts to add, on startup, a display resolution setting to the appropriate FC instance, the corresponding setting instance is indeed added. But if the FC finds a value for it in the file, it will reset the setting to that value. Otherwise the setting maintains the default value.
I reiterate this happens on startup or on game startups, when modules are loaded. Nothing should add settings at any other time - another notion we might want to make clear in the javadocs.
@@ -91,4 +101,37 @@ public boolean remove(SimpleUri id) { | |||
public boolean contains(SimpleUri id) { | |||
return settingMap.containsKey(id); | |||
} | |||
|
|||
@Override | |||
public void setUnusedSettings(Map<SimpleUri, String> unusedSettings) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm thinking this method should be private. Only the FC should be responsible for reading its config file and setting this internal object. Nothing external should be able to do it.
import org.terasology.engine.SimpleUri; | ||
|
||
public interface FlexibleConfigManager { | ||
void addFlexibleConfig(SimpleUri flexibleConfigUri, FlexibleConfig fc); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I second what @oniatus is saying and further refine it with the following declaration:
void addFlexibleConfig(SimpleUri configId, FlexibleConfig config) throws RuntimeException;
Potentially we could also use UnsupportedOperationException
, to be a bit more specific. Or we could go with a custom exception such as DuplicateConfigException
.
} | ||
} | ||
|
||
// Add all the non-default settings that were not used in this session |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd change to:
// Add any temporarily parked setting that hasn't been used in this session of the application.
That been said, I wonder if that's a valid behavior. It could potentially lead to the accumulation of "zombie" settings that have been added to an FC once and are never used again. Is this a valid scenario? How should we react to it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is like old save files - we can't delete them based on suspicion of being a zombie, since they might very well be a vampire instead.
I think we should leave this untouched, since I'd rather live with 10 extra settings than lose all my settings because I did not load a module (and hence it's associated settings) for (say) 10 consecutive games.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmmm. I wonder if only the engine should add settings to an engine config and only modules should add settings to their very own module config. This should eliminate zombie settings as configs are atomically loaded and atomically discarded.
Programmatically this might not have consequences, but when it's time to group settings, potentially from different configs, into a single UI, that's where the uiPath idea would become a necessity.
What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should eliminate zombie settings as configs are atomically loaded and atomically discarded.
I'm sure I'm missing something, but I can't see how different config files can prevent zombie settings from piling up. (The only possible way I can see is deleting config associated with myModule when user deletes myModule, but that's not a normal scenario, since I don't expect user to be deleting modules all the time.)
} | ||
|
||
private Path getPathForFlexibleConfig(SimpleUri flexibleConfigUri) { | ||
Path filePath = PathManager.getInstance().getHomePath().resolve("configs").resolve(flexibleConfigUri.getModuleName().toString()).resolve(flexibleConfigUri.getObjectName().toString() + ".cfg"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wowowowow. This line is too short. Can you make it any longer please? 😄
At least let's split it over multiple lines, i.e.
...getHomePath.resolve(blah)
.resolve(blahblah)
.resolve(blahblahblah)
value = (T)(Double)Double.parseDouble(valueString); | ||
} else if (value instanceof String) { | ||
value = (T)valueString; | ||
} else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with @oniatus assessment.
I now have the even stronger feeling we might want to go back to a Setting class that is abstract, -not- generic-based, and is the base for classes such as FloatSetting, IntSetting etc.
I guess one alternative would be delegate the parsing to type-specific parsing objects which can be registered somewhere at runtime and retrieved/used in this method.
Any other possibility?
FlexibleConfig renderingFlexibleConfig = new FlexibleConfigImpl(); | ||
flexibleConfigManager.addFlexibleConfig(new SimpleUri("engine:rendering"), renderingFlexibleConfig); | ||
|
||
flexibleConfigManager.load(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the aim is to eventually eliminate Config entirely and leave only the FCmanager to fill those shoes. So, I think it makes sense to keep them in parallel for the time being.
FlexibleConfigManager flexibleConfigManager = new FlexibleConfigManagerImpl(); | ||
rootContext.put(FlexibleConfigManager.class, flexibleConfigManager); | ||
|
||
FlexibleConfig renderingFlexibleConfig = new FlexibleConfigImpl(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmm. I'm thinking that there might be reasons to split the existing rendering config in two separate configs, one for generic, always needed settings, i.e. resolution, and another for all those settings that kick in only in-game.
The only problem I see is that this would mean some settings become visible to the user only in-game, which is not quite the typical behavior of settings: you can normally tweak them before you enter the game proper.
On the other hand, given what @vampcat and I have been discussing about render nodes providing their own settings to the RenderingFC, at this stage we only instantiate those nodes when the renderer is alive, which leads anyway to this behavior.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm definitely up for the split.
And I do think the behaviour of not showing settings in the main menu is the desired one, at least in the long run.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see how else we can do it admittedly. But it will be a bit unusual I feel. Perhaps on loading/creating a game we'll need a brief alert, fading in/out near the bottom of the screen, informing the user that additional settings have become available, perhaps with a checkbox so that the user doesn't have to see the message every time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, the alert seems like a logical idea. That should be easily doable via NUI I think.
( To be fair, since I haven't dived into it too much, I might be totally and horribly wrong :D )
Uh oh, something went wrong with the build. Need to check on that |
@vampcat, please let me know when you think it's time to review the latest changes. |
Uh oh, something went wrong with the build. Need to check on that |
this.temporarilyParkedSettings = Maps.newHashMap(); | ||
} | ||
private Map<SimpleUri, Setting> settings = Maps.newHashMap(); | ||
private Map<SimpleUri, String> temporarilyParkedSettings = Maps.newHashMap(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Morale behind shifting this here: When something is initialized like this, it's clear at the first glance that it can never be null. However, if it's initialized in the constructor, that raises the question of whether this can ever be null, and whether checks for the same should be added.
(P.S. I could not find any real reason to prefer one style over the other.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
...hmmm... the code could still assign null (intentionally or by mistake) to such variable.
It's no big deal though, let's not worry about this anymore.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True. I was sorely tempted to make it final as well in the same line, but I refrained.
Not a big deal, but can't deny that I am tempted :D
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Admittedly, why not final?
I have no clue how I broke the build system 😓 |
@Cervator? Can you have a look at the outcome of the build? It doesn't look like something this PR should be capable of generating. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A number of left-over, small issues but we are getting there.
import org.terasology.config.flexible.validators.SettingValueValidator; | ||
import org.terasology.engine.SimpleUri; | ||
|
||
public class StringSetting extends SettingImpl<String> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see you made String, Float, Integer and Double settings. I would suggest to add BooleanSetting and perhaps LongSetting. Byte, Char and Short would complete the list but I feel they are probably a bit overkill for our needs. I wouldn't say no if you added them though... 😉
import org.terasology.config.flexible.validators.SettingValueValidator; | ||
import org.terasology.engine.SimpleUri; | ||
|
||
public class DoubleSetting extends SettingImpl<Double> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm guessing there is no way to eliminate the SettingImpl class without a lot of almost identical code (save for the type), right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As far as I know, that seems to be the case.
|
||
Map<SimpleUri, Setting> getActiveSettings(); | ||
void load(Reader reader); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are the reader/writer passed here because it is not possible to change their path once they have been instantiated?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The configManager is responsible for deciding the path of serialization, creating a reader/writer for it and passing that to the FC.
This way, the FC does not have to concern itself with where the settings are being stored, and can focus only on the how.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand that. But could the writer/reader be passed on Construction? If a path change requires re-instantiation, I guess the answer is no.
if (unusedSettings.containsKey(id)) { | ||
setting.setValueFromString(unusedSettings.remove(id)); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My previous comment wasn't quite concrete enough. I'd say: yes document the behaviour in the javadoc, no do not suggest to call resetValue() if necessary - it shouldn't be necessary unless the user wants it.
} catch (IOException e) { | ||
logger.error("Failed to load config file {}, falling back on default config"); | ||
// TODO: Handle exception |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes please. 🙂
|
||
jsonReader.endObject(); | ||
} catch (Exception e) { | ||
// TODO: Handle exception |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes please. 🙂
@@ -18,11 +18,11 @@ | |||
import org.terasology.engine.SimpleUri; | |||
|
|||
public interface FlexibleConfigManager { | |||
void addFlexibleConfig(SimpleUri flexibleConfigUri, FlexibleConfig flexibleConfig); | |||
void addConfig(SimpleUri configUri, FlexibleConfig config) throws RuntimeException; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry if I said otherwise, but this configURIs are really configIDs, so I'd rename all instances of configUri to configId, to stick to what it is rather than its type.
Uh oh, something went wrong with the build. Need to check on that |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good. Mostly just a few changes to the javadocs.
@@ -138,7 +136,7 @@ public void load(Reader reader) { | |||
|
|||
jsonReader.endObject(); | |||
} catch (Exception e) { | |||
// TODO: Handle exception | |||
throw new RuntimeException("Error parsing config file!"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmm... I imagine this prints out the whole stack trace normally. But will this include any hint of where the parsing went wrong? It would be important to have that information printed out.
And can the catch any more precise? I.e. see:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I restructured the code slightly to print the name of the config that failed to parse, and printed the stack trace of that exception as well - that should cover the "where" part.
* All the non-default values are loaded and "parked" till the actual setting is added to the config, | ||
* and which point they are "unparked" and parsed, replacing the default value of the setting. | ||
* | ||
* Note that this function should be called before adding any settings to the FlexibleConfig. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd expand on this a little and clarify what happens if this isn't the case, i.e. settings are overwritten.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reminder about this one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
More specifically we could write:
- Note that this function should be called before adding any settings to the FlexibleConfig, or any corresponding parked setting will never be unparked.
/** | ||
* Loads the values of the settings having non-default values, to enable persistence across sessions. | ||
* | ||
* All the non-default values are loaded and "parked" till the actual setting is added to the config, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
* All the non-default values are loaded and "parked", initially remaining inaccessible.
* Once a Setting object is added to the config, a corresponding non-default value is sought
* among the parked values. If one is found it is parsed and stored in the Setting object.
@@ -17,14 +17,46 @@ | |||
|
|||
import org.terasology.engine.SimpleUri; | |||
|
|||
/** | |||
* Stores multiple {@link FlexibleConfig} instances that can be retrieved using their id. | |||
* Also responsible for overseeing their serialization - to and from - disk. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd maybe write "coordinating" instead of "overseeing".
public interface FlexibleConfigManager { | ||
/** | ||
* Adds the given {@link FlexibleConfig} to this manager. | ||
* This method throws a RuntimeException if a FlexibleConfig with the given id already exists. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you can move the content of this line and put it besides the @throws line below, isn't it?
* Removes the config associated with the given id, and returns it. | ||
* | ||
* @param configId The id of the config to remove. | ||
* @return The config associated with the given id, and null if no config is associated with the given id. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The config associated with the given id, or null if no config is associated with the given id.
* Retrieves the config associated with the given id. | ||
* | ||
* @param configId The id of the config to retrieve. | ||
* @return The config associated with the given id. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should clarify what happens if there is no config associated with the given id.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reminder about this one.
Here's the failure from Jenkins:
So in other words it sounds like a test class is failing to compile due to the changes. Which is a common issue and easy to miss since the test classes may not auto-compile when tinkering with the regular Java source. Doesn't look like it would be a very difficult fix either. Try it out and force-compile the test source and/or try running all the |
@emanuele3d Passing the Context into Config to avoid the use of CoreRegistry (which is temporary, and will be removed once we're done finding a proper place to put |
Hooray Jenkins reported success with all tests good! |
@vampcat wrote:
Are you talking about changes that I haven't seen yet? Because so far the changes related to the context being passed in didn't seem so bad. And what would a proper place for FC.save() be? |
No, I just meant the changes I have pushed (not sure if you've viewed them all, when saying "the ones i've viewed"). However, that does mean that a lot of tests have to pass Although now that I think about it, it would have been better to simply add a new constructor that takes no arguments, that automatically sets the variable to null. Your thoughts? |
Tests are meant to also provide a hint of how things are used in practice. If in practice we should always pass a Context to the constructor then we should do it that way in the tests too. So, I'd avoid a new constructor in this case. Indeed passing null when we should pass an object is not ideal though. Ideally we'd pass a MockContext object. You didn't respond about where else you'd put FC.save() by the way. |
For the time being, nowhere. However, since FC will replace Config either way, we'll soon replace all |
I'm skeptical about that "soon". 😉 But ok for the rest. |
* | ||
* @param reader A reader that will serve as the source of the settings. | ||
*/ | ||
void load(Reader reader); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would separate loading and saving from managing settings. Changing and reading setting values and load/save are two different responsibilities for different clients and the strategy how to save a setting (io.Reader and io.Writer) are implementation details which should not be part of this interface.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, I had the loading/saving login in FCManager before, but @emanuele3d commented the following:
I am thinking that in terms of load/save functionality, it makes sense that the manager orchestrates it, i.e. iterating over the FCs as it happens here and providing the file or even the reader. But I'd want the FCs themselves to handle the actual reading/writing, to separate the concerns and mantain some of the functionality private to the FC class.
Which is why I put this here.
I'm fine with either, so can you two decide which feels better? :D
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd keep it as it is for the time being. Let's get this PR moving again and merged in. We can always refactor after we have gained some experience with FCs in general.
|
||
/** | ||
* Stores multiple {@link FlexibleConfig} instances that can be retrieved using their id. | ||
* Also responsible for overseeing their serialization - to and from - disk. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In addition to my previos comment about save/load, I'd expect a method like save(FlexibleConfig)
and include the reader/writer logic here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmmm... I'm not sure I agree with this and the related comment above.
Can you do a comparison between the two different ways of doing things and show why the changes you suggest would be better?
* Iterates over all the configs registered with this manager, and loads the | ||
* settings stored in them. | ||
*/ | ||
void load(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just by reading FlexibleConfigManager.load()
-> what will it do?
How about FlexibleConfigManager.loadAllRegisteredConfigs()
?
Also naming consistency -> javadoc calls it "registered", the method to register is called "add".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd recommend to rename load to loadAllConfigs().
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair point about consistency. I'd retain the add method and change the javadoc.
* Iterates over all the configs registered with this manager, and saves the | ||
* settings stored in them. | ||
*/ | ||
void save(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same as above
import java.util.Map.Entry; | ||
|
||
public class FlexibleConfigManagerImpl implements FlexibleConfigManager { | ||
private static final Logger logger = LoggerFactory.getLogger(FlexibleConfigManager.class); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be a logger for the -Impl class
try (Reader reader = Files.newBufferedReader(getPathForFlexibleConfig(flexibleConfigId), TerasologyConstants.CHARSET)) { | ||
flexibleConfig.load(reader); | ||
} catch (NoSuchFileException e) { | ||
// The config does not exist, so the default values will be used. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exceptions should not be part of the expected controlflow.
Use java.nio.files.Files.exists(Path)
to check if the file at the path exists and ignore this case explicitly
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmm... I'm not sure about the dogma implied in the first sentence. 😉
But the fix sounds reasonable enough. =)
Hooray Jenkins reported success with all tests good! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A couple of javadoc-related reminders/suggestions, but from my perspective we are pretty much good to go.
* All the non-default values are loaded and "parked" till the actual setting is added to the config, | ||
* and which point they are "unparked" and parsed, replacing the default value of the setting. | ||
* | ||
* Note that this function should be called before adding any settings to the FlexibleConfig. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
More specifically we could write:
- Note that this function should be called before adding any settings to the FlexibleConfig, or any corresponding parked setting will never be unparked.
* Retrieves the config associated with the given id. | ||
* | ||
* @param configId The id of the config to retrieve. | ||
* @return The config associated with the given id. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reminder about this one.
Hooray Jenkins reported success with all tests good! |
7ca067d
to
adbd098
Compare
Btw, how do you feel about creating a new MockContext object? I really dislike the |
Hooray Jenkins reported success with all tests good! |
Yes, to use a MockContext would be better, as I suggested in an earlier comment. I'd do it in this PR though: it is in this PR that a context is introduced as input to the constructor, therefore it is in this PR that ideally the tests should be modified accordingly. It's not a big deal though: if you feel strongly about doing it in a separate PR, ok. |
Hooray Jenkins reported success with all tests good! |
Approved, next change should use the new system for at least one of our config concepts to put some usage on the new Api 👍 |
@oniatus: indeed the idea is to start using FCs in the context of the Renderer, at least. As the two Rendering configs have a lot of parameters we are unlikely to migrate everything to FCs in one step, but that's the direction we are aiming for. |
@Cervator: this PR is good to go! |
Poking @Cervator for merge. Unless there is something in this PR that doesn't quite fully convince him? |
Cervator is currently flying to Europe or something, so I don't think he'll be available a whole lot for a few days. That being said, I'll try poking him on slack (since he tends to ignore all github pings). |
Went for a second review and gave it another playtest to make sure everything works. |
@emanuele3d Sorry for that, we should clarify the review/merge process then because I did not know this till now. |
No worries, I do realize this has never been thoroughly discussed and responsibilities within the merge-privileged have not been clearly defined. There is no doubt Cervator could use some help. But perhaps we can help with the least controversial, least consequential PRs first, leaving to him the more significant ones, especially in light of the fact that he tests them against his "mega-workspace", an IJ project he has that includes all existing modules, to spot conflicts that normally would go unnoticed. That been said, it is certainly good we have this conversation so that we can clarify things. Maybe he'd be happy to relax the workflow and let a number of other people deal with any PR. |
Belated response to this here. Interesting situation with the merging - I really would love more help merging, and honestly with this amount of PRs some bumps and bruises might be worth it. So I'm thrilled @oniatus stepped in :-) At the same time @emanuele3d couldn't have picked better timing with this PR as an example for the usefulness of a mega-workspace as it did in fact break something super subtle, a test class in PolyWorld that had I only found it from Alpha 9 prep after sorting out an API violation that slipped through and is documented at #3184. That was keeping my mega-workspace from doing full builds, so I might not have caught this anyway. To make things more perfect-storm'ish I had built every single module as part of release prep just 3 days before this got merged, making me puzzled as Jenkins was still happy ... :D Had me confused for a bit until I finally realized commit ee0d74a#diff-dbff4de92b8b73995ac93deb86683778 from here added a new constructor for Both API violations seemingly only caused one tiny module break each, fixing both right now. A fancier setup in Jenkins would have caught both and done away with the need for mega workspaces. In the meantime with the new Groovy utility making such a workspace is easier so maybe a few beyond me can keep such a workspace around for testing tricky PRs (but nothing else - keep a separate dir for it and don't do any work there, aiiee can they get tricky to keep clean) Thanks for everybody's effort and initiative here, please keep at it, even if the road may get bumpy from time to time. So long as we test before doing an actual release we can still fix issues like this and stay in compliance with SemVer, so even if we get a few PR merges with slight issues that still keeps us moving and OK. Onward and upwards! |
Closes issue #2893
This PR primarily deals with adding a new
FlexibleConfigManager
class, that is responsible for managing all the FCs, as well as saving/loading them.