[Alephmodular-devel] CPreferences design
Status: Pre-Alpha
Brought to you by:
brefin
From: Br'fin <br...@ma...> - 2003-11-13 23:48:17
|
It looks like my attention has finally focused upon the way AM handles preferences. In addition to previous discussions with Woody and Mike with regards to preferences for AM and potentially A1, I've also been looking into how Java and MacOS X handle preferences internally. (I should also do some thinking on how I want to integrate other libraries into AM, most notably xmlpat, most likely for dealing with parsing the PList format, but that doesn't matter quite until halfway into dealing with the preferences anyhow) Revisions of the attached document can be found in the CVS repository at: alephModular/documentation/technical/CPreferences.txt -Jeremy Parsons Preferences Design GOAL We wish to rework the preferences mechanism of Aleph into a solid system that can be used now and into the foreseeable future. Rules: 1. Try to maintain read compatibility with older preference versions. A new version of Aleph should always be able to read a prior version of the preferences. It does not have to be able to write the format though. Corollary 1: At the very least do not ever complain that the prior version of preferences is unreadable, or worse. [ 770167 ] Old-style preferences cryptically choke A1 on startup [ https://sourceforge.net/tracker/index.php? func=detail&aid=770167&group_id=1997&atid=101997 ] HISTORY M2 Era Preferences M2 stored its preferences in a wad based binary format. Different areas of code (For instance player controls or graphic setup) had their own named section in the wad. That named structure was a direct serial dump of the preferences entity for that module. This makes the preferences both hard for a person to view/edit in the case of extra preferences with no ui yet. And makes it diffifult to arrange/manage the preferences over versions. A1 MML Preferences A1 shifted to an MML based preferences format (XML with a custom DTD). This allowed for human readable/editable preferences. But requires a certain amount of internal overhead for adding new keywords to the parser if one wishes to change/modify the preferences. It is also unclear if this is well suited for handling different preference versions over time. At the very least DESIGN What the preferences should be: * A centralized repository of application preferences Only the preferences system itself cares about the preferences file format. Other parts of the program query and ammend the preferences through APIs * Version aware The preference system should always be capable of reading prior versions of preferences. In case of reading a newer version of preferences, provide a warning. Allow user option to quit. Version in this case refers to general structure of the preferences file. For instance Version 1 would be binary. Version 2 would be MML (last version) Version 3 would be this format * Dictionary API Subsystems request and submit values based on keys (potentially nested) when speaking to the preferences. * Honors requests to flush When other aspects of the codes ask the the file to be written to disk, preferences does so. * Corruption resistant Attempt to do appropriate safe save techniques so that only when the preferences are fully written is the new data moved into the official preferences file location. ( write to temp file close temp file swap temp file and original file ) What the preferences proper should not have: * Preferences should not have application knowledge Preferences itself cannot initialize its values Application modules must do it for the values they know of Preferences itself cannot validate values Application modules must validate the values they know themselves. Potentially at the same time as initializing those values. Subsystems may wish to include their own version notes in the preferences. This way if they decide to use a new set of keys, then they can transition those keys themselves if an older version is found. Preferences can manage lists and dictionary of values, but is not responsible for their arrangement. width and height are just additional integer entries in a dictionary named screen. In addition screen means nothing at all to preferences, it is just an arbitrary name for a dictionary entry. Preference file requirements: * Human readable/editable Note: While not a requirement, the author of this document is partial to an XML plist based format. While the format is MacOS X native, we do not have to use OSX based apis to read/write it and it allows for both human editing in a text editor and allows people who choose to to edit them with tools that are plist aware. For instance the OSX tools: 'Property List Editor.app' and the command line command 'defaults' It was also designed for preferences by better people than us and is blissfully flexible without forcing the computerized readers of the preferences to have application knowledge. NOTES: Java and MacOS X both have mechanisms for distinguishing between system preferences and user preferences. For our needs, a simple user based mechanism will do. Java uses the concept of named preference nodes within either preference tree. Such that a class can get a section of preferences that is typically all of its own. A node is akin to a dictionary. MacOS X handles the app name for the file, then treats everything below as existing at one level. However it allows Dictionary as an explicit value type for deeper information. For our purposes, the Java outlook should be sufficient. The Java API uses the concept of value=get(key, defaultValue). This form of input is acceptable. Safe saving should be implicitly handled by AM's CFileDescs which already manage this. EXISTING PREFERENCES BREAKDOWN: struct graphics_preferences_data screen_mode_data size (index into array of screen sizes) (50%, 75%, 100%, full_screen) acceleration bool high_resolution bool texture_floor, texture_ceiling bool draw_every_other_line int bit_depth int gamma_level mac os device spec struct network_preferences_data { bool allow_microphone; bool game_is_untimed; int16 type; // look in network_dialogs.c for _ethernet, etc... int16 game_type; int16 difficulty_level; int16 game_options; // Penalize suicide, etc... see map.h for constants int32 time_limit; int16 kill_limit; int16 entry_point; }; struct player_preferences_data { char name[PREFERENCES_NAME_LENGTH+1]; int16 color; int16 team; uint32 last_time_ran; int16 difficulty_level; bool background_music_on; }; struct input_preferences_data { int16 input_device; int16 keycodes[NUMBER_OF_KEYS]; }; struct environment_preferences_data { FSSpec map_file; FSSpec physics_file; FSSpec shapes_file; FSSpec sounds_file; uint32 map_checksum; uint32 physics_checksum; uint32 shapes_mod_date; uint32 sounds_mod_date; uint32 patches[MAXIMUM_PATCHES_PER_ENVIRONMENT]; }; struct sound_manager_parameters { int16 channel_count; /* >=0 */ int16 volume; /* [0,NUMBER_OF_SOUND_VOLUME_LEVELS) */ uint16 flags; /* stereo, dynamic_tracking, etc. */ int32 unused_long; fixed pitch; int16 unused[9]; }; Scenarios: Load Preferences: This occurs when the application first initializes preferences once the preferences file is first set. Or whenever a request to reset preferences occurs. 1. application hands a preferences file (CFileDesc) to PreferencesFactory 2. PreferencesFactory wipes out preferences in memory (if any) 3. PreferencesFactory confirms file exists Otherwise, carry on with empty preferences. But not file anyways so we can write to it. 4. PreferencesFactory tests preferences file against a list of Preference Readers For each reader, ask reader 'Can you read this file?' If true, use that reader. If false, check next reader. a. The list of readers is generally configured at compile time. And for AM we see approximately 2 readers. WadPrefReader and PListPrefReader b. Each reader is asked to test the file. (For instance, a WadPrefReader would look to see if the file looked like a wad file and would return true if it was) c. If no readers are found, indicate an error somewhere In debug builds it is ok to assert here. In deployment builds, a diagnostic is recommended (throw an unknown pref file format error) however, this is not fatal, we just carry on with empty preferences 5. The found Preference Reader has full write access to the in memory preferences structure and reads the entire file into memory, converting chunks as needs be to fit the node-key-value structure. The reader only fills in the information it knows about Saving Preferences: This occurs whenever the application requests that the preferences be flushed. In fact, this is the only way to make sure the preferences are written out. The preferences will not auto-flush. It is recommended to request a flush whenever a group of preferences has been changed. But one could do things like change screen preferences and volume preferences mid-game, and denote that preferences should be flushed post-game. Calling for a preferences flush when no preferences setting functions has been called since preferences loading or last flush is a no-op. 1. We already know preferences file from the setup. 2. We already know the preferences writer from compilation. (The code base might have multiple writers building up over legacy time, however, only one will ever be active in a given build. Again, the first writer will be a WadPrefsWriter and the second will be a PListPrefWriter) 3. The preferences writer has full read access to the preferences structure. 4. The preferences writer walks the preferences structure and writes out all the information that it's format can encapsulate. 5. Any errors during preferences writing results in a thrown error (throw a preferences write error) and the original preferences file (if any) should remain untouched. |