-
Notifications
You must be signed in to change notification settings - Fork 27
add JSON based custom data storage #43
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
Conversation
…in BaseSystemData
…ion for basic experimentation with the custom data extensions system
|
The following code was used to test the feature: Data Classes: class ExampleData {
public class NestedExampleData {
public int nestedTest;
}
public string test;
public NestedExampleData nesting = new NestedExampleData();
}Runtime Code: LogInfo("Testing custom data thing");
ActorData testingData = new ActorData();
BasicCustomData<ExampleData> testingCustomData = new BasicCustomData<ExampleData> {
Data = {
test = "test",
nesting = {
nestedTest = 6
}
}
};
testingData.Set("custom_data_test", testingCustomData);
if (!testingData.TryGet("custom_data_test", out BasicCustomData<ExampleData> deserializedCustomData)) {
LogError("Failed to deserialize custom data for unclear reason!");
return;
}
LogInfo($"Deserialized custom data: {deserializedCustomData.Data.test}, {deserializedCustomData.Data.nesting.nestedTest}");When running the code, both the |
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.
It would be better to cache the data object with ConditionalWeakTable to support read/write without (de)serialize. In addition, it also supports change value without "Set" manually.
It can be implemented by serializing only in BaseSystemData.save and check cached deserialized value first in TryGet
Example code:
public abstract class BasicCustomDataBase
{
public abstract Type Type {get;}
public abstarct string Key {get;}
protected object wrapped_data;
}
public class BasicCustomData<T> : BasicCustomDataBase
{
public override Type Type => typeof(T);
public override string Key => typeof(T).FullName;
public readonly T Data;
public BasicCustomData(T data)
{
wrapped_data = data;
Data = data;
}
}
private static ConditionalWeakTable<BaseSystemData, Dictionary<Type, BasicCustomDataBase>> _cache_table = new();
private static void BaseSystemData_save_prefix(BaseSystemData __instance)
{
if (!_cache_table.TryGetValue(__instance, out var data_dict) return;
foreach (var item in data_dict)
{
__instance.set(item.Value.Key, Serialize(item.Value.wrapped_data, item.Key));
}
}
public static TData Get<TData>(this BaseSystemData data)
{
if (_cache_table.TryGetValue(data, out var data_dict)
{
if (data_dict.TryGetValue(typeof(TData), out var t_data)
{
return t_data.wrapped_data as TData;
}
goto DESERIALIZE_DATA;
}
else
{
data_dict = new();
_cache_table.Add(data, data_dict);
}
DESERIALIZE_DATA:
data.get(typeof(TData).FullName, out string data_str);
if (string.IsNullOrEmpty(data_str)) return null;
var deserialized_data = Deserialize<TData>(data_str);
data_dict[typeof(TData)] = new BasicCustomData(deserialized_data);
return deserialized_data;
}|
@inmny Honestly, I am not sure if getting rid of the manual set is a good idea. Personally, I'd argue that there can be a lot of benefits in stability to the more "transactional" behaviour of the base game system, as it prevents issues like ending up with half changed data if an exception occurs halfway through a set of changes to a data entry. |
This PR adds a new
DataExtensionfile atNeoModLoader.General.Game.extensionswith the classesDataExtension,SerializedCustomData,ICustomData, andBasicCustomData<TDataClass>, including documentation for all public members.DataExtensioncontains two extension methods applied toBaseSystemDatawhich can be used to serialize/deserialize any object supporting ourICustomDatainterface to and from thecustom_data_stringDictionary offered byBaseSystemDatavia theSerializedCustomDatawrapping class by NML, storing mod ID and data version alongside the custom data to make support for changes in data format easy to detect. The extension methods can accomplish this by usingICustomData.Serialize()andICustomData.Deserialize(), which are expected to be able to both export and import the stored custom data to/from aJObjectstored within theSerializedCustomData.Additionally, there's a
BasicCustomData<TDataClass>for enabling modders to get a quick basic grasp on how to interface with the system. However, actually using this example implementation in distributed mods is discouraged, as the default system for serialization/deserialization completely lacks support for versioning, making it incredibly difficult to adapt to changes in data storage with it (much more so than with a well-written customICustomDataimplementation).All a mod has to do to leverage the feature is to create its own
ICustomDataimplementation and to use the relevant extension methods fromDataExtension.