/// <summary>Writes field into a config node.</summary> /// <param name="node">A node to write state to.</param> /// <param name="instance">An owner of the field. Can be <c>null</c> for static fields.</param> public void WriteToConfig(ConfigNode node, object instance) { var value = fieldInfo.GetValue(instance); if (value == null) { return; } if (collectionFieldHandler != null) { collectionFieldHandler.SerializeValues(node, value); } else { var cfgData = ordinaryFieldHandler.SerializeValue(value); if (cfgData != null) { if (ordinaryFieldHandler.IsCompound()) { ConfigAccessor.SetNodeByPath(node, cfgPath, (ConfigNode)cfgData); } else { ConfigAccessor.SetValueByPath(node, cfgPath, (string)cfgData); } } } }
/// <summary> /// Restores an ordinary value collection from the config node. The type can be compound. /// </summary> /// <remarks>This method never throws.</remarks> /// <param name="node">The node to read the state from.</param> /// <param name="instance"> /// The owner of the field. It can be <c>null</c> for the static fields. /// </param> void ReadOrdinaryFromConfig(ConfigNode node, object instance) { var value = fieldInfo.GetValue(instance); if (isCompound) { var cfgNode = ConfigAccessor.GetNodeByPath(node, cfgPath); if (cfgNode != null) { if (value == null) { // Try creating the instance using its default constructor. if (fieldInfo.IsInitOnly) { DebugEx.Warning( "Cannot assign to a NULL readonly compound field! Field is ignored: {0}.{1}", fieldInfo.DeclaringType.FullName, fieldInfo.Name); return; } try { value = Activator.CreateInstance(fieldInfo.FieldType); fieldInfo.SetValue(instance, value); } catch (Exception ex) { DebugEx.Error("Cannot restore field of type {0}: {1}", fieldInfo.FieldType, ex.Message); } } DeserializeCompoundFieldsFromNode(cfgNode, value); } } else { if (fieldInfo.IsInitOnly) { DebugEx.Warning("Cannot assign to a readonly field! Field is ignored: {0}.{1}", fieldInfo.DeclaringType.FullName, fieldInfo.Name); return; } var cfgValue = ConfigAccessor.GetValueByPath(node, cfgPath); if (cfgValue != null) { try { object fieldValue; if (isCustomSimpleType) { // Prefer the existing instance of the field value when available. fieldValue = value ?? Activator.CreateInstance(fieldInfo.FieldType); ((IPersistentField)fieldValue).ParseFromString(cfgValue); } else { fieldValue = simpleTypeProto.ParseFromString(cfgValue, fieldInfo.FieldType); } fieldInfo.SetValue(instance, fieldValue); } catch (Exception ex) { DebugEx.Error("Cannot parse value \"{0}\" as {1}: {2}", cfgValue, fieldInfo.FieldType.FullName, ex.Message); } } } }
/// <summary>Reads custom type fields from the part's config.</summary> /// <remarks> /// <para> /// The consumer code must call this method from the <c>OnLoad</c> method to capture the /// PartLoader initalization. This method automatically detects the game loading phase, so it's /// safe to call it in every <c>OnLoad</c> inovacation. /// </para> /// </remarks> /// <param name="module">The module to load the data for.</param> /// <param name="cfgNode">The config node, passed by the game to the module.</param> /// <seealso cref="PersistentFieldAttribute"/> /// <seealso cref="StdPersistentGroups.PartConfigLoadGroup"/> /// <seealso cref="CopyPartConfigFromPrefab"/> /// <example> /// <code source="Examples/ConfigUtils/ConfigAccessor-Examples.cs" region="ReadPartConfigExample"/> /// </example> public static void ReadPartConfig(PartModule module, ConfigNode cfgNode) { if (!PartLoader.Instance.IsReady()) { ConfigAccessor.ReadFieldsFromNode( cfgNode, module.GetType(), module, group: StdPersistentGroups.PartConfigLoadGroup); } }
/// <summary>Writes field into a config node.</summary> /// <remarks> /// This method is not expected to fail since converting any type into string is expected to /// succeeed on any value. /// </remarks> /// <param name="node">A node to write state to.</param> /// <param name="instance">An owner of the field. Can be <c>null</c> for static fields.</param> public void WriteToConfig(ConfigNode node, object instance) { if (isDisabled) { return; // Field is not supported. } var value = fieldInfo.GetValue(instance); if (value == null) { Debug.LogWarningFormat("Skip writing field {0}.{1} due to its value is NULL", fieldInfo.DeclaringType.FullName, fieldInfo.Name); return; } if (collectionProto != null) { // For collections iterative via proto class and serialize item values. foreach (var itemValue in collectionProto.GetEnumerator(value)) { if (itemValue != null) { if (isCompound) { ConfigAccessor.AddNodeByPath(node, cfgPath, SerializeCompoundFieldsToNode(itemValue)); } else if (isCustomSimpleType) { ConfigAccessor.AddValueByPath( node, cfgPath, ((IPersistentField)itemValue).SerializeToString()); } else { ConfigAccessor.AddValueByPath( node, cfgPath, simpleTypeProto.SerializeToString(itemValue)); } } } } else { // For ordinal values just serialize the value. if (isCompound) { ConfigAccessor.SetNodeByPath(node, cfgPath, SerializeCompoundFieldsToNode(value)); } else if (isCustomSimpleType) { ConfigAccessor.SetValueByPath(node, cfgPath, ((IPersistentField)value).SerializeToString()); } else { ConfigAccessor.SetValueByPath(node, cfgPath, simpleTypeProto.SerializeToString(value)); } } }
/// <summary>Restores a collection from the config node.</summary> /// <remarks>This method never throws.</remarks> /// <param name="node">The node to read the state from.</param> /// <param name="instance"> /// The owner of the field. It can be <c>null</c> for the static fields. /// </param> void ReadCollectionFromConfig(ConfigNode node, object instance) { var value = fieldInfo.GetValue(instance); if (value == null) { // Collections are the complex objects, they must exist in order to be restored. DebugEx.Warning("Skip reading collection field {0}.{1} due to it's not initalized", fieldInfo.DeclaringType.FullName, fieldInfo.Name); return; } collectionProto.ClearItems(value); if (isCompound) { // For compound items read nodes and have them parsed. var itemCfgs = ConfigAccessor.GetNodesByPath(node, cfgPath); if (itemCfgs != null) { foreach (var itemCfg in itemCfgs) { var itemValue = Activator.CreateInstance(collectionProto.GetItemType()); DeserializeCompoundFieldsFromNode(itemCfg, itemValue); collectionProto.AddItem(value, itemValue); } } } else { // For ordinary items read strings and have them parsed. var itemCfgs = ConfigAccessor.GetValuesByPath(node, cfgPath); if (itemCfgs != null) { foreach (var itemCfg in itemCfgs) { try { object itemValue; if (isCustomSimpleType) { itemValue = Activator.CreateInstance(collectionProto.GetItemType()); ((IPersistentField)itemValue).ParseFromString(itemCfg); } else { itemValue = simpleTypeProto.ParseFromString(itemCfg, collectionProto.GetItemType()); } collectionProto.AddItem(value, itemValue); } catch (Exception ex) { DebugEx.Error("Cannot parse value \"{0}\" as {1}: {2}", itemCfgs, collectionProto.GetItemType().FullName, ex.Message); } } } } }
/// <summary>Makes pacth from a config node.</summary> /// <remarks> /// The critical settings will be checked for the sane values. If a bad value found, then the /// menthod will throw. /// </remarks> /// <param name="node">The node to create from.</param> /// <param name="context"> /// The context of node loading. It can be any object, e.g. <c>UrlDir.UrlConfig</c> or /// <c>ConfigNode</c>. It's only used for logging errors to give a context for debugging. /// </param> /// <param name="url">The URL to the node in the game's DB.</param> /// <returns>The patch. It's never <c>null.</c></returns> static ConfigNodePatch MakeFromNodeInternal(ConfigNode node, object context, string url = null) { var patchNode = new ConfigNodePatch(); if (url != null) { patchNode.sourceConfigUrl = url; } ConfigAccessor.ReadFieldsFromNode(node, patchNode.GetType(), patchNode); // Sanity check of the test rules. Preconditions.ConfValueExists(patchNode.name, "node/name", context: context); Preconditions.ConfValueExists(patchNode.testSection, "node/TEST", context: context); Preconditions.ConfValueExists( patchNode.testSection.partTests, "node/TEST/PART", context: context); Preconditions.ConfValueExists( patchNode.testSection.partTests.GetValue("name"), "node/TEST/PART/name", context: context); foreach (var moduleNode in patchNode.testSection.moduleTests) { Preconditions.ConfValueExists( moduleNode.GetValue("name"), "node/TEST/MODULE/name", context: context); } // Sanity check of the upgrade rules Preconditions.ConfValueExists(patchNode.upgradeSection, "node/UPGRADE", context: context); Preconditions.ConfValueExists( patchNode.upgradeSection.partRules, "node/UPGRADE/PART", context: context); Preconditions.OneOf(patchNode.upgradeSection.partRules.action, new[] { PatchAction.Drop, PatchAction.Fix }, "node/UPGRADE/PART/action", context: context); foreach (var moduleRule in patchNode.upgradeSection.moduleRules) { Preconditions.ConfValueExists(moduleRule.name, "node/TEST/MODULE/name", context: context); } // Check the patch age. var patchAgeDays = (DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalDays - patchNode.patchCreationTimestamp / (24 * 60 * 60); if (patchAgeDays > 180) { DebugEx.Warning("Patch is too old: patch={0}, age={1} days", patchNode, patchAgeDays); } return(patchNode); }
/// <summary> /// Reads a value of an arbitrary type <typeparamref name="T"/> from the config node. /// </summary> /// <param name="node">The node to read data from.</param> /// <param name="pathKeys"> /// The array of values that makes the full path. The first node in the array is the top most /// component of the path. /// </param> /// <param name="typeProto"> /// A proto that can parse values of type <typeparamref name="T"/>. If not set, then /// <see cref="StandardOrdinaryTypesProto"/> is used. /// </param> /// <returns>The parsed value or <c>null</c> if not found.</returns> /// <typeparam name="T"> /// The value type to write. The <paramref name="typeProto"/> instance must be able to handle it. /// </typeparam> /// <exception cref="ArgumentException">If type cannot be handled by the proto.</exception> /// <seealso cref="SetValueByPath<T>(ConfigNode, string[], T, KSPDev.ConfigUtils.AbstractOrdinaryValueTypeProto)"/> public static T?GetValueByPath <T>( ConfigNode node, string[] pathKeys, AbstractOrdinaryValueTypeProto typeProto = null) where T : struct { if (typeProto == null) { typeProto = standardTypesProto; } if (!typeProto.CanHandle(typeof(T))) { throw new ArgumentException(string.Format( "Proto {0} cannot handle type {1}", typeProto.GetType(), typeof(T))); } var strValue = ConfigAccessor.GetValueByPath(node, pathKeys); return(strValue == null ? null : (T?)typeProto.ParseFromString(strValue, typeof(T))); }
/// <summary>Creates a collection from the config node.</summary> /// <param name="node">A node to read data from.</param> /// <returns>Сollection instance.</returns> internal object DeserializeValues(ConfigNode node) { object instance = null; var values = persistentField.ordinaryFieldHandler.IsCompound() ? ConfigAccessor.GetNodesByPath(node, persistentField.cfgPath) as object[] : ConfigAccessor.GetValuesByPath(node, persistentField.cfgPath) as object[]; if (values != null) { instance = Activator.CreateInstance(collectionType); foreach (var value in values) { var item = persistentField.ordinaryFieldHandler.DeserializeValue(value); if (item != null) { collectionProto.AddItem(instance, item); } } } return(instance); }
/// <summary>Reads field from a config node.</summary> /// <param name="node">A node to read state from.</param> /// <param name="instance">An owner of the field. Can be <c>null</c> for static fields.</param> public void ReadFromConfig(ConfigNode node, object instance) { object value = null; if (collectionFieldHandler != null) { value = collectionFieldHandler.DeserializeValues(node); } else { var cfgData = ordinaryFieldHandler.IsCompound() ? ConfigAccessor.GetNodeByPath(node, cfgPath) as object : ConfigAccessor.GetValueByPath(node, cfgPath) as object; if (cfgData != null) { value = ordinaryFieldHandler.DeserializeValue(cfgData); } } if (value != null) { fieldInfo.SetValue(instance, value); } }
/// <summary>Stores collection values into a config node.</summary> /// <param name="node">A node to add values into.</param> /// <param name="value">A collection instance to get values from.</param> internal void SerializeValues(ConfigNode node, object value) { var proto = collectionProto as GenericCollectionTypeProto; foreach (var itemValue in proto.GetEnumerator(value)) { if (itemValue == null) { continue; } var cfgData = persistentField.ordinaryFieldHandler.SerializeValue(itemValue); if (cfgData != null) { if (cfgData is ConfigNode) { ConfigAccessor.AddNodeByPath(node, persistentField.cfgPath, (ConfigNode)cfgData); } else { ConfigAccessor.AddValueByPath(node, persistentField.cfgPath, (string)cfgData); } } } }
/// <param name="cfgPath"> /// A path to the fields's value in the config. Components must be separated by symbol <c>/</c>. /// The path is relative, the absolute path is determined when doing actual (de)serialization. /// The path is case-insensitive. /// </param> protected BasePersistentFieldAttribute(string cfgPath) { this.path = ConfigAccessor.StrToPath(cfgPath); }
/// <summary> /// Reads a value of an arbitrary type <typeparamref name="T"/> from the config node. /// </summary> /// <param name="node">The node to read data from.</param> /// <param name="path"> /// The path to the node. The path components should be separated by '/' symbol. /// </param> /// <param name="typeProto"> /// A proto that can parse values of type <typeparamref name="T"/>. If not set, then /// <see cref="StandardOrdinaryTypesProto"/> is used. /// </param> /// <returns>The parsed value or <c>null</c> if not found.</returns> /// <typeparam name="T"> /// The value type to write. The <paramref name="typeProto"/> instance must be able to handle it. /// </typeparam> /// <exception cref="ArgumentException">If type cannot be handled by the proto.</exception> /// <seealso cref="SetValueByPath<T>(ConfigNode, string, T, KSPDev.ConfigUtils.AbstractOrdinaryValueTypeProto)"/> public static T?GetValueByPath <T>( ConfigNode node, string path, AbstractOrdinaryValueTypeProto typeProto = null) where T : struct { return(GetValueByPath <T>(node, ConfigAccessor.StrToPath(path), typeProto)); }
/// <summary>Reads field from a config node.</summary> /// <param name="node">A node to read state from.</param> /// <param name="instance">An owner of the field. Can be <c>null</c> for static fields.</param> public void ReadFromConfig(ConfigNode node, object instance) { var value = fieldInfo.GetValue(instance); if (collectionProto != null) { // For collection field use existing object and restore its items. if (value == null) { Debug.LogWarningFormat("Skip reading collection field {0}.{1} due to it's not initalized", fieldInfo.DeclaringType.FullName, fieldInfo.Name); return; } collectionProto.ClearItems(value); if (isCompound) { // For compound items read nodes and have them parsed. var itemCfgs = ConfigAccessor.GetNodesByPath(node, cfgPath); if (itemCfgs != null) { foreach (var itemCfg in itemCfgs) { var itemValue = Activator.CreateInstance(collectionProto.GetItemType()); DeserializeCompoundFieldsFromNode(itemCfg, itemValue); collectionProto.AddItem(value, itemValue); } } } else { // For ordinary items read strings and have them parsed. var itemCfgs = ConfigAccessor.GetValuesByPath(node, cfgPath); if (itemCfgs != null) { foreach (var itemCfg in itemCfgs) { try { object itemValue; if (isCustomSimpleType) { itemValue = Activator.CreateInstance(collectionProto.GetItemType()); ((IPersistentField)itemValue).ParseFromString(itemCfg); } else { itemValue = simpleTypeProto.ParseFromString(itemCfg, collectionProto.GetItemType()); } collectionProto.AddItem(value, itemValue); } catch (Exception ex) { Debug.LogErrorFormat("Cannot parse value \"{0}\" as {1}: {2}", itemCfgs, collectionProto.GetItemType().FullName, ex.Message); } } } } } else { // For ordinary field just restore value and assign it to the field. if (isCompound) { if (value != null) { DeserializeCompoundFieldsFromNode(ConfigAccessor.GetNodeByPath(node, cfgPath), value); } else { Debug.LogWarningFormat("Skip reading compound field {0}.{1} due to it's not initalized", fieldInfo.DeclaringType.FullName, fieldInfo.Name); } } else { var cfgValue = ConfigAccessor.GetValueByPath(node, cfgPath); if (cfgValue != null) { try { object fieldValue; if (isCustomSimpleType) { // Prefer the existing instance of the field value when available. fieldValue = value ?? Activator.CreateInstance(fieldInfo.FieldType); ((IPersistentField)fieldValue).ParseFromString(cfgValue); } else { fieldValue = simpleTypeProto.ParseFromString(cfgValue, fieldInfo.FieldType); } fieldInfo.SetValue(instance, fieldValue); } catch (Exception ex) { Debug.LogErrorFormat("Cannot parse value \"{0}\" as {1}: {2}", cfgValue, fieldInfo.FieldType.FullName, ex.Message); } } } } }