from SimHubPluginSdk
- switch to SlipGrip branch for Custom ShakeIt effects implemented in C#

A common list of custom SimHub properties with some values potentially specific to each sim and car.
In this example, properties are managed for ShakeIt Wheel Slip haptics:

- a global
Propertylist- each
Propertyobject aNameandValue
- each
- a
Gamelist- each
Gameobject aname, game-specificdefaults Property List<>andCListofCarobjects- each
Carobject acarIDand itsList<>ofPropertyobjects- each
Propertyobject aNameandValue
- each
- each
- each
- Properties to be managed are configured in
JSONio.ini.
- instead of just copying that SimHubPluginSdk repository
- created a new Visual Studio JSONio WPF project, then quit
- deleted everything in that project except
JSONio.slnandJSONio.csproj - copied
Properties/and source files fromSimHubPluginSdk/ - performed GVIM split diff on
JSONio.slnandJSONio.csprojto preserve newProjectGuid, etc - forgot to update namespace from
User.PluginSdktoJSONioe.g. inProperties/...! - easily missed namespace in
.xamlcalledxmlns:local="clr-namespace:..."
this.AddAction("ChangeProperties",(a, b) =>saves changedCar propertieslist,
then loadspropertiesmatching changedCar.IDandGame gname.- updates
Valueslist fromdefaultsforGame gnameif noCar.IDmatch - could implement
this.AddEvent("CarChange");
inpublic void Init(PluginManager pluginManager),- then
this.TriggerEvent("CarChange");
inpublic void DataUpdate(PluginManager pluginManager, ref GameData data)
- then
- but instead, by GitHub NCalc 'CarChange' event in
JSONio.ini:[ExportEvent] name='CarChange' trigger=changed(20, [DataCorePlugin.GameData.CarId])
- updates

- my experience: SimHub ignored this Source when JSONio.ini was first loaded...
- in
JSONio.cs Init()- create and update global property settings
- create and maintain games object and JSON file
- populate from configured
.jsonfile, if existing
- populate from configured
- populate
simValuesobject from savedSettings,gamesandJASONio.ini - in
this.AddAction("ChangeProperties",(a, b)- create new
Gameobject ingameswhen none match currentGame name - set
gamefor matchinggames.data.name
- create new
- additional
this.AddAction()s for modifyingCaranddefaultsvalues
Jagged List<> supports
e.g. dynamically adding and deleting elements (based on JSONio.ini)
better than jagged array.
- done stop mouse click messing with selected property in UI
- done property indicating a new car
- done manage some SimHub properties not-per car, e.g. ShakeIt frequency limits
- some tricks:
- conversion to/from Car class has uses only first pCount
List<Values>,
which GameHandler class uses to save cars intoGames datafor.jsonfile. - JSONio knows about properties beyond pCount in
List<Values> simValues...- these are NOT saved in
.jsonfiles - these ARE available as SimHub properties e.g. for ShakeIt
- these ARE available for value changes in user interface
- these are NOT saved in
- conversion to/from Car class has uses only first pCount
- refactored to use only
List<Values> simValuesfor user interface;- using
JSONio.ini, convert from/toDataPluginSettings SettingsinInit()/End() - restore/save simValue.Current values from/to
GameHandler games.Carfrom/toJSONio.json
- using
- some tricks:
- done slim .json format storing only one instance of property names, instead of redundantly per-car
- done: generate release .zip file for Release 1.25 and newer builds
- done document error messages
- done
SetDefault(): {Gname} not in slim.data.gList- trying to change a default on first car
- try
CarChange()
- done disable sorting property columns in UI
- done UI feedback for property value change
- done buttons not enabled when bad JSON is loaded
- done handle too short
slim.data.gList[gndx].cList[0].Vlist.Count- KEY CONCEPT:
slim.datashould be ready for saving as JSON afterInit()- whether or not user changes any property values
- no practical use for
writeflag;
write new JSON only when users change certain values:- current per-car values
- default global or per-game values
- KEY CONCEPT:
- done add distinct
JSONio.iniconfiguration for per-game properties:- global saved to, restored from SimHub
DataPluginSettings - per game saved to, restored from JSON per DataCorePlugin.ExternalScript.JSONio.file
- per car same as per game; game properties are first "car" in game
cList
- global saved to, restored from SimHub
- done handle property moves (by .ini reconfiguration) among per-car, per-game and global.
- use ini default values for moves
- done
Init()bugs: substantial refactoring- done when
JSONio.fileproperty not 'PluginsData/JSONio.json',
initially generatedJSONio.fileis wrong... better later..??! - done save global properties and all property names to
Settings
- done when
- SimHub [WatchDog] Stacks dump for
OOps()popups- invoke
MessageBox.Show()byTriggerEvent().. but not in Init()... MessageBox.Show()inInit()does not display (UI thread not yet launched).
- invoke
- done Help link
- done Change() for slider
- done set
writetrue in SetDefault(); runDefaultCopy()inEnd()ifwrite
- done:
SaveCar() inChanged()only if badgndxorcndx` - done: skip
OOpa()inLoad() if emptyMsg` - done:
pListshould begCount, notsimValues.Count - done: convert slider TextBox to a button
- click reassigns slider to current Selection value
- done: bug fix: SaveCar() now explicitly saves per-car and per-game changes for existing cars
- done: bug fix: preserve saved settings only for first instance of previous game car
- OxyScope integration
- C#
List<>patterns, particularly with non-trivial objects.- Here are some snippits with
List<Student> - stackoverflow: list search
- M$ Learn: List.FindIndex Method:
index of item in a list:
int index = properties.FindIndex(a => a.Name == name); if (-1 == index) properties.Add ( new Property() { Name=name, Value=value }); else if (replace && properties[index].Value != value) properties[index].Value = value;
- Here are some snippits with
- C# JSON
- In Visual Studio, add
Newtonsoft.Json.NETpackage... - pretty-print JSON from C# AKA
serialize
using Newtonsoft.Json; if (games.Save_Car(current, gname) || changed) { string js = JsonConvert.SerializeObject(games.data, Formatting.Indented); - Eventually, Read and Parse a JSON File in C# AKA
deserialize
if (File.Exists(path)) { games = JsonSerializer.Deserialize<Games>(File.ReadAllText(path)); } else changed = true;
- In Visual Studio, add
- C# WPF DataGrid in XAML -
more references
- 4 column table:
- property name
- default value
- previous value
- current value
- XML header of row labels, as above
- programatically add a row for each property configured
- highlight current value of only selected property
- first steps in Visual Studio:
- click
Control.xaml - select View->Designer
- drag in
DataGridfromCommon WPF Controls- fiddle with margins for Grid and DataGrid to make space for Label
- add DataGrid column Headers
- drag in buttons for previous, next, +, -, etc
- click
- 4 column table:
- C# Dispatcher.Invoke()
- WPF DataGrid user interface updates want a method.
- Invoking method on WPF DataGrid resources is disallowed from other threads.
Dispatcher.Invoke()is less code than subscribing toPropertyChangedevents
- invoke
MessageBox.Show()- should be able to run on plugin's
View(UI) thread..? - but plugin UI thread starts (
GetWPFSettingsControl()) afterInit()- queue Msg until UI thread launch, then
View.Dispatcher.Invoke(() => View.OOpsMB()); - [WatchDog] Abnormal Inactivity detected gets provoked if message box displays > 5 seconds...
- queue Msg until UI thread launch, then
- should be able to run on plugin's
- 3 April 2024:
- bind Values class to DataGrid columns
<DataGrid.Columns>
<DataGridTextColumn Header="Property" Binding="{Binding Name}" />
<DataGridTextColumn Header="Default" Binding="{Binding Default}" />
<DataGridTextColumn Header="Current" Binding="{Binding Current}" />
<DataGridTextColumn Header="Previous" Binding="{Binding Previous}" />
</DataGrid.Columns>
...
public class Values
{
public string Name { get; set; }
public string Default { get; set; }
public string Current { get; set; }
public string Previous { get; set; }
}
...
public List<Values> simValues;
public SettingsControl()
{
InitializeComponent();
simValues = new List<Values>();
dg.ItemsSource = simValues;
}
- bind WPF button clicks directly to Plugin Action methods
-
4 Apr: - test buttons
- populateList<Values>from existingcurrent,previous, etc -
5 Apr: - fully functional by buttons
- Select hightlight forced for button changes, will not work for dashboard
-simValuesupdated from original Lists, pending refactor
- property updates by dashboard should work... -
7 Apr: - per-car fully functional by buttons and dashboard
- thanks to arguably sketchy code rearranging...
- still to do: fully integratesimValuesinJSONio.cs -
C# WPF XY plot: SimHub already uses OxyPlot
-
12 May 2024 - MessageBox
- SimHub Event triggers do not work from plugin
Init() MessageBox()duringInit()(e.g. forJASONio.iniconfiguration errors)
provokes extensive SimHub log error message.- for game runtime, added "JSONioOOps"
TriggerEventand
this.AddAction("OopsMessageBox", (a, b) => OOpsMB());
- SimHub Event triggers do not work from plugin
-
12 Oct - sync most code to SlipGrip branch
- instead of hard-coded to
Gscale - configured in
NCalcScripts/JSONio.ini,
wherevaluemay be any name inJSONio.properties, e.g.:
[ExportProperty]
name='JSONio.slider'
value='Gscale'
- removed obsolete
Documentation\release.sh - in
JSONio.csproj:
<PropertyGroup>
<PostBuildEvent>
cd "$(ProjectDir)"
if $(ConfigurationName) == Release (7z u R:\TEMP\JSONio.zip NCalcScripts\JSONio.ini)
cd "bin\Release"
if $(ConfigurationName) == Release (7z u R:\TEMP\JSONio.zip JSONio.dll)
</PostBuildEvent>
</PropertyGroup>
- restore
Settings- reconcile with JSONio.ini global properties
- load Slim .json
- reconcile JSONio.ini car- and game -specific
- bool
changedset true if any .json mismatches