diff --git a/rabi_splitter_WPF/RabiRibiDisplay.cs b/rabi_splitter_WPF/RabiRibiDisplay.cs index 2d32e9f..8f482dc 100644 --- a/rabi_splitter_WPF/RabiRibiDisplay.cs +++ b/rabi_splitter_WPF/RabiRibiDisplay.cs @@ -61,14 +61,18 @@ namespace rabi_splitter_WPF private void UpdateVariableExport() { - variableExportContext.CheckForUpdates(); long currentFrameMillisecond = (long)(DateTime.Now - UNIX_START).TotalMilliseconds; var diff = currentFrameMillisecond - lastUpdateMillisecond; if (diff >= 1000) { if (diff >= 2000) lastUpdateMillisecond = currentFrameMillisecond; else lastUpdateMillisecond += 1000; - variableExportContext.OutputUpdates(); + variableExportContext.UpdateVariables(true); + } + else + { + // Don't update files. + variableExportContext.UpdateVariables(false); } } diff --git a/rabi_splitter_WPF/StringInjectExtension.cs b/rabi_splitter_WPF/StringInjectExtension.cs new file mode 100644 index 0000000..dcbdae0 --- /dev/null +++ b/rabi_splitter_WPF/StringInjectExtension.cs @@ -0,0 +1,116 @@ +using System; +using System.Text.RegularExpressions; +using System.Collections; +using System.Globalization; +using System.ComponentModel; + +/// Source: http://mo.notono.us/2008/07/c-stringinject-format-strings-by-key.html + +[assembly: CLSCompliant(true)] +namespace StringInject +{ + public static class StringInjectExtension + { + /// + /// Extension method that replaces keys in a string with the values of matching object properties. + /// Uses internally; custom formats should match those used for that method. + /// + /// The format string, containing keys like {foo} and {foo:SomeFormat}. + /// The object whose properties should be injected in the string + /// A version of the formatString string with keys replaced by (formatted) key values. + public static string Inject(this string formatString, object injectionObject) + { + return formatString.Inject(GetPropertyHash(injectionObject)); + } + + /// + /// Extension method that replaces keys in a string with the values of matching dictionary entries. + /// Uses internally; custom formats should match those used for that method. + /// + /// The format string, containing keys like {foo} and {foo:SomeFormat}. + /// An with keys and values to inject into the string + /// A version of the formatString string with dictionary keys replaced by (formatted) key values. + public static string Inject(this string formatString, IDictionary dictionary) + { + return formatString.Inject(new Hashtable(dictionary)); + } + + /// + /// Extension method that replaces keys in a string with the values of matching hashtable entries. + /// Uses internally; custom formats should match those used for that method. + /// + /// The format string, containing keys like {foo} and {foo:SomeFormat}. + /// A with keys and values to inject into the string + /// A version of the formatString string with hastable keys replaced by (formatted) key values. + public static string Inject(this string formatString, Hashtable attributes) + { + string result = formatString; + if (attributes == null || formatString == null) + return result; + + foreach (string attributeKey in attributes.Keys) + { + result = result.InjectSingleValue(attributeKey, attributes[attributeKey]); + } + return result; + } + + /// + /// Replaces all instances of a 'key' (e.g. {foo} or {foo:SomeFormat}) in a string with an optionally formatted value, and returns the result. + /// + /// The string containing the key; unformatted ({foo}), or formatted ({foo:SomeFormat}) + /// The key name (foo) + /// The replacement value; if null is replaced with an empty string + /// The input string with any instances of the key replaced with the replacement value + public static string InjectSingleValue(this string formatString, string key, object replacementValue) + { + string result = formatString; + //regex replacement of key with value, where the generic key format is: + //Regex foo = new Regex("{(foo)(?:}|(?::(.[^}]*)}))"); + Regex attributeRegex = new Regex("{(" + key + ")(?:}|(?::(.[^}]*)}))"); //for key = foo, matches {foo} and {foo:SomeFormat} + + //loop through matches, since each key may be used more than once (and with a different format string) + foreach (Match m in attributeRegex.Matches(formatString)) + { + string replacement = m.ToString(); + if (m.Groups[2].Length > 0) //matched {foo:SomeFormat} + { + //do a double string.Format - first to build the proper format string, and then to format the replacement value + string attributeFormatString = string.Format(CultureInfo.InvariantCulture, "{{0:{0}}}", m.Groups[2]); + replacement = string.Format(CultureInfo.CurrentCulture, attributeFormatString, replacementValue); + } + else //matched {foo} + { + replacement = (replacementValue ?? string.Empty).ToString(); + } + //perform replacements, one match at a time + result = result.Replace(m.ToString(), replacement); //attributeRegex.Replace(result, replacement, 1); + } + return result; + + } + + + /// + /// Creates a HashTable based on current object state. + /// Copied from the MVCToolkit HtmlExtensionUtility class + /// + /// The object from which to get the properties + /// A containing the object instance's property names and their values + private static Hashtable GetPropertyHash(object properties) + { + Hashtable values = null; + if (properties != null) + { + values = new Hashtable(); + PropertyDescriptorCollection props = TypeDescriptor.GetProperties(properties); + foreach (PropertyDescriptor prop in props) + { + values.Add(prop.Name, prop.GetValue(properties)); + } + } + return values; + } + + } +} diff --git a/rabi_splitter_WPF/VariableExportConfig.cs b/rabi_splitter_WPF/VariableExportConfig.cs index 39e1d11..cfcf58f 100644 --- a/rabi_splitter_WPF/VariableExportConfig.cs +++ b/rabi_splitter_WPF/VariableExportConfig.cs @@ -9,95 +9,108 @@ namespace rabi_splitter_WPF { void ConfigureVariableExports() { - ExportableVariable.DefineVariableExports(new ExportableVariable[] { + variableExportContext.DefineVariableExports(new ExportableVariable[] { ExportVariable ( + handle: "playtime", displayName: "Playtime", tracker: () => snapshot.playtime ), ExportVariable ( + handle: "blackness", displayName: "Blackness", tracker: () => snapshot.blackness ), ExportVariable ( + handle: "mapid", displayName: "Map Id", tracker: () => snapshot.mapid ), ExportVariable ( + handle: "map", displayName: "Map", tracker: () => StaticData.GetMapName(snapshot.mapid) ), ExportVariable ( + handle: "musicid", displayName: "Music Id", tracker: () => snapshot.musicid ), ExportVariable ( + handle: "music", displayName: "Music", tracker: () => StaticData.GetMusicName(snapshot.musicid) ), ExportVariable ( + handle: "hp", displayName: "HP", tracker: () => snapshot.hp ), ExportVariable ( + handle: "amulet", displayName: "Amulet", tracker: () => snapshot.amulet ), ExportVariable ( + handle: "boost", displayName: "Boost", tracker: () => snapshot.boost ), ExportVariable ( + handle: "mana", displayName: "MP", tracker: () => snapshot.mana ), ExportVariable ( + handle: "stamina", displayName: "SP", tracker: () => snapshot.stamina ), ExportVariable ( + handle: "x", displayName: "x", tracker: () => snapshot.px ), ExportVariable ( + handle: "y", displayName: "y", tracker: () => snapshot.py ), ExportVariable ( + handle: "mapTile", displayName: "Map Tile", tracker: () => snapshot.mapTile ), ExportVariable ( + handle: "nDeaths", displayName: "Deaths", tracker: () => inGameState.nDeaths ), ExportVariable ( + handle: "nRestarts", displayName: "Restarts", tracker: () => inGameState.nRestarts ), }); - - - variableExportContext.NotifyExportableVariableUpdate(); } - private ExportableVariable ExportVariable(string displayName, Func tracker) + private ExportableVariable ExportVariable(string handle, string displayName, Func tracker) { - return new ExportableVariable(displayName, tracker); + return new ExportableVariable(handle, displayName, tracker); } } } diff --git a/rabi_splitter_WPF/VariableExportContext.cs b/rabi_splitter_WPF/VariableExportContext.cs index ea37216..24efff7 100644 --- a/rabi_splitter_WPF/VariableExportContext.cs +++ b/rabi_splitter_WPF/VariableExportContext.cs @@ -5,55 +5,65 @@ using System.ComponentModel; using System.Linq; using System.Text; using System.Windows; +using System.Windows.Controls; namespace rabi_splitter_WPF { public class VariableExportContext : INotifyPropertyChanged { private List _variableExportSettings; - private HashSet pendingUpdates; + private List _variables; + private Dictionary variableValues; + + private ItemCollection variableListBoxItems; + private ItemCollection variableExportListBoxItems; public VariableExportContext() { _variableExportSettings = new List(); - pendingUpdates = new HashSet(); + _variables = new List(); + variableValues = new Dictionary(); } #region Update Logic - public void OutputUpdates() + public void UpdateVariables(bool updateFile) { - foreach (var ves in pendingUpdates) + foreach (var variable in _variables) { + variable.UpdateValue(); + variableValues[variable.Handle] = variable.Value; + } + + foreach (var ves in _variableExportSettings) { - ves.OutputUpdate(); + ves.OutputUpdate(variableValues, updateFile); } - pendingUpdates.Clear(); } - private void RegisterUpdate(VariableExportSetting ves) + internal void SetItemControls(ItemCollection variableListBoxItems, ItemCollection variableExportListBoxItems) { - pendingUpdates.Add(ves); + this.variableListBoxItems = variableListBoxItems; + this.variableExportListBoxItems = variableExportListBoxItems; } - public void CheckForUpdates() + public void DefineVariableExports(ExportableVariable[] exports) { - foreach (var ves in _variableExportSettings) - { - bool hasUpdate = ves.CheckForUpdate(); - if (hasUpdate) RegisterUpdate(ves); - } + Variables = exports.ToList(); + variableValues.Clear(); } + #endregion - public void NotifyExportableVariableUpdate() + #region Properties + public List Variables { - foreach (var ves in _variableExportSettings) + get { return _variables; } + private set { - ves.NotifyExportableVariableUpdate(); + _variables = value; + variableListBoxItems.Refresh(); } } - #endregion - #region Variables public List VariableExportSettings { get { return _variableExportSettings; } @@ -62,12 +72,13 @@ namespace rabi_splitter_WPF internal void Add(VariableExportSetting ves) { _variableExportSettings.Add(ves); + variableExportListBoxItems.Refresh(); } internal void Delete(VariableExportSetting ves) { _variableExportSettings.Remove(ves); - pendingUpdates.Remove(ves); + variableExportListBoxItems.Refresh(); } #endregion diff --git a/rabi_splitter_WPF/VariableExportSetting.cs b/rabi_splitter_WPF/VariableExportSetting.cs index 2002dfa..d4d95c2 100644 --- a/rabi_splitter_WPF/VariableExportSetting.cs +++ b/rabi_splitter_WPF/VariableExportSetting.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Text; +using StringInject; namespace rabi_splitter_WPF { @@ -11,29 +12,28 @@ namespace rabi_splitter_WPF { private readonly Func tracker; - public ExportableVariable(string displayName, Func tracker) : base(displayName) + public ExportableVariable(string handle, string displayName, Func tracker) : base(handle, displayName) { this.tracker = tracker; } - internal override VariableTracker GetTracker() + public override void UpdateValue() { - return new VariableTracker(tracker); + Value = tracker(); } } - public abstract class ExportableVariable + public abstract class ExportableVariable : INotifyPropertyChanged { - private static int nextAvailableId = 0; - private static List _variableExports; - private static Dictionary _variableCaptions = new Dictionary(); - private readonly int _id; private readonly string _displayName; + private readonly string _handle; + + private object _value; - protected ExportableVariable(string displayName) + protected ExportableVariable(string handle, string displayName) { - _id = nextAvailableId++; + _handle = handle; _displayName = displayName; } @@ -47,81 +47,36 @@ namespace rabi_splitter_WPF get { return _displayName; } } - internal abstract VariableTracker GetTracker(); - - public static void DefineVariableExports(ExportableVariable[] exports) - { - _variableExports = exports.ToList(); - _variableCaptions = exports.ToDictionary(ev => ev, ev => ev.DisplayName); - } - - public static Dictionary VariableCaptions - { - get { return _variableCaptions; } - } - - #region Equals, GetHashCode - public override bool Equals(object obj) - { - var otherValue = obj as ExportableVariable; - if (otherValue == null) return false; - return _id.Equals(otherValue.Id); - } - - public override int GetHashCode() - { - return _id.GetHashCode(); - } - #endregion - } - - public class VariableTracker : VariableTracker - { - private readonly Func tracker; - private T currentValue; - - public VariableTracker(Func tracker) + public string Handle { - this.tracker = tracker; - forceUpdate = true; + get { return _handle; } } - public override bool CheckForUpdate() + public object Value { - T newValue = tracker(); - - if (forceUpdate || !newValue.Equals(currentValue)) + get { return _value; } + protected set { - currentValue = newValue; - forceUpdate = false; - return true; + if (value.Equals(_value)) return; + _value = value; + OnPropertyChanged(nameof(Value)); } - return false; } + + public abstract void UpdateValue(); - public override object GetValue() - { - return currentValue; - } - } - - public abstract class VariableTracker - { - protected bool forceUpdate; + public event PropertyChangedEventHandler PropertyChanged; - public void FormatChanged() + [NotifyPropertyChangedInvocator] + protected virtual void OnPropertyChanged(string propertyName) { - forceUpdate = true; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } - - public abstract bool CheckForUpdate(); - public abstract object GetValue(); } public class VariableExportSetting : INotifyPropertyChanged { - private ExportableVariable _selectedVariable; - private VariableTracker _variableTracker; + private string _outputFileName; private string _outputFormat; private string _formatPreview; @@ -131,7 +86,6 @@ namespace rabi_splitter_WPF public VariableExportSetting() { // Default values - _selectedVariable = null; _outputFileName = ""; _outputFormat = ""; _isExporting = false; @@ -139,11 +93,11 @@ namespace rabi_splitter_WPF } #region Logic - private string FormatOutput() + private string FormatOutput(Dictionary variableValues) { try { - return string.Format(_outputFormat, _variableTracker.GetValue()); + return _outputFormat.Inject(variableValues); } catch (FormatException e) { @@ -151,60 +105,21 @@ namespace rabi_splitter_WPF } } - internal void OutputUpdate() + internal void OutputUpdate(Dictionary variableValues, bool updateFile) { - if (_variableTracker == null) return; - var formattedOutput = FormatOutput(); + var formattedOutput = FormatOutput(variableValues); + if (formattedOutput == FormatPreview) return; FormatPreview = formattedOutput; - // TODO: Write to file - } - - internal bool CheckForUpdate() - { - if (_variableTracker == null) return false; - return _variableTracker.CheckForUpdate(); - } - - public void NotifyExportableVariableUpdate() - { - OnPropertyChanged(nameof(VariableCaptions)); - } - #endregion - - #region Dictionaries - public Dictionary VariableCaptions - { - get { return ExportableVariable.VariableCaptions; } - } - - internal void DefaultButton_Click() - { - if (_selectedVariable == null) - { - OutputFormat = "Variable not set."; - } - else + if (updateFile) { - OutputFormat = $"{_selectedVariable.DisplayName}: {{0}}"; + // TODO: Write to file + } } - #endregion #region Parameters - public ExportableVariable SelectedVariable - { - get { return _selectedVariable; } - set - { - if (value.Equals(_selectedVariable)) return; - _selectedVariable = value; - _variableTracker = _selectedVariable.GetTracker(); - OnPropertyChanged(nameof(SelectedVariable)); - } - } - public string OutputFileName { get { return _outputFileName; } @@ -223,7 +138,6 @@ namespace rabi_splitter_WPF { if (value.Equals(_outputFormat)) return; _outputFormat = value; - if (_variableTracker != null) _variableTracker.FormatChanged(); OnPropertyChanged(nameof(OutputFormat)); } } @@ -261,9 +175,7 @@ namespace rabi_splitter_WPF } } #endregion - - // Note: DO NOT OVERRIDE Equals and GetHashCode. We compare by reference equality. - + public event PropertyChangedEventHandler PropertyChanged; [NotifyPropertyChangedInvocator] diff --git a/rabi_splitter_WPF/VariableExportTab.xaml b/rabi_splitter_WPF/VariableExportTab.xaml index da3785a..d7fc2a4 100644 --- a/rabi_splitter_WPF/VariableExportTab.xaml +++ b/rabi_splitter_WPF/VariableExportTab.xaml @@ -5,48 +5,70 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:rabi_splitter_WPF" mc:Ignorable="d" - d:DesignHeight="500" d:DesignWidth="540"> + d:DesignHeight="500" d:DesignWidth="780"> - - -