private DataTreeObjectProperty[] MakeModelTransformPair(ConfigReference model, Transform3D transform, BooleanExpression expr = null) { string mdlName = model?.getName() ?? "null"; string trs = transform?.toString() ?? "null"; if (expr != null) { string state = "???"; try { state = expr.createEvaluator(new DummyScope()).evaluate().ToString(); } catch { } return(new DataTreeObjectProperty[] { new DataTreeObjectProperty("Condition: " + expr.toString() + " = " + state, SilkImage.Conditional), new DataTreeObjectProperty("Model: " + mdlName, SilkImage.Reference), new DataTreeObjectProperty("Transform: " + trs, SilkImage.Matrix) }); } else { return(new DataTreeObjectProperty[] { new DataTreeObjectProperty("Model: " + mdlName, SilkImage.Reference), new DataTreeObjectProperty("Transform: " + trs, SilkImage.Matrix) }); } }
/// <summary> /// Takes in a <see cref="ConfigReference"/> and loads its data. It then returns the loaded model and all of its descendants as a list of <see cref="Model3D"/> instances. /// </summary> /// <param name="sourceFile">The original base-level file that contains the reference.</param> /// <param name="reference">The reference itself.</param> /// <param name="modelCollection">A list of every model that has been loaded recursively.</param> /// <param name="dataTreeParent">For cases where the GUI is used, this is the data tree representation.</param> /// <param name="globalTransform">The transformation to apply to all loaded models.</param> /// <param name="appendModelsToModelCollection">If true, the loaded models will be appended to <paramref name="modelCollection"/>.</param> /// <param name="extraData">Any extra data that should be included. This is mainly used by references (e.g. a reference is a <see cref="StaticSetConfig"/>, the target model in the set may be included as extra data)</param> public static List <Model3D> HandleConfigReference(FileInfo sourceFile, ConfigReference reference, List <Model3D> modelCollection, DataTreeObject dataTreeParent, Transform3D globalTransform, bool appendModelsToModelCollection = true, Dictionary <string, dynamic> extraData = null) { if (reference == null) { return(null); } string filePathRelativeToRsrc = reference.getName(); if (extraData == null) { extraData = reference.ArgumentsToExtraData(); } else { extraData = extraData.MergeWith(reference.ArgumentsToExtraData()); } return(HandleConfigReferenceFromLiteralPath(sourceFile, filePathRelativeToRsrc, modelCollection, dataTreeParent, globalTransform, appendModelsToModelCollection, extraData)); }
/// <summary> /// Given a <see cref="ParameterizedConfig"/> and a path from a direct as well as a target value, this will modify the <see cref="ParameterizedConfig"/> so that its fields reflect the given value.<para/> /// If this data cannot be set due to it being on a direct chain (the end value is a <see cref="ConfigReference"/>), that <see cref="ConfigReference"/> will be returned /// </summary> /// <param name="config"></param> /// <param name="path"></param> /// <param name="argValue"></param> /// <param name="setToNull">If true, and if argValue is null, the property will actually be set to null (if this is false, it will skip applying it)</param> public static ConfigReference SetDataOn(ParameterizedConfig config, string path, object argValue, bool setToNull = false) { if (argValue == null && !setToNull) { return(null); } // implementation.material_mappings[0].material["Texture"]["File"] // A bit of a hack to make splitting this path easier: path = path.Replace("[", ".["); // The latest object stored when traversing this direct's path. object latestObject = config; string previousIndex = null; object previousObject = null; // Split it by the segments of this path, and get rid of the implementation word at the start if needed. string[] pathSegments = path.Split('.'); if (pathSegments[0] == "implementation") { latestObject = ReflectionHelper.Get(latestObject, "implementation"); pathSegments = pathSegments.Skip(1).ToArray(); } for (int idx = 0; idx < pathSegments.Length; idx++) { string currentIndex = pathSegments[idx].SnakeToCamel(); string betweenBrackets = currentIndex.BetweenBrackets(); if (betweenBrackets != null) { // This is either an array index, or a reference to a config. // The simple way to test this is that if it's a numeric index, it's an array index. if (int.TryParse(betweenBrackets, out int arrayIndex)) { // Access this array index. It is a number in brackets like [0] previousObject = latestObject; latestObject = ReflectionHelper.GetArray(latestObject, arrayIndex); } else { // Access the config reference. This is branching from a config reference and accesses a parameter ["Parameter Name"] ConfigReference latestAsCfg = (ConfigReference)latestObject; string parameterName = betweenBrackets.Substring(1, betweenBrackets.Length - 2); // First things first: Resolve the config reference. string configRefPath = latestAsCfg.getName(); // cloning this is super important as the tryget method will return a template object. // Do not edit the template! if (!latestAsCfg.IsRealReference()) { // Catch case: This isn't actually pointing to a *configuration*, rather a direct object reference. latestAsCfg.getArguments().put(parameterName, argValue); return(null); } ParameterizedConfig referencedConfig = ConfigReferenceBootstrapper.ConfigReferences.TryGetReferenceFromName(configRefPath)?.CloneAs <ParameterizedConfig>(); if (referencedConfig == null) { XanLogger.WriteLine("Something failed to reference a ConfigReference (It tried to search for \"" + configRefPath + "\", which doesn't exist). Some information on this model may not load properly!", XanLogger.DEBUG, System.Drawing.Color.DarkGoldenrod); return(null); } // So there's our reference. Now we need to get a parameter from it. Parameter referencedParam = referencedConfig.getParameter(parameterName); if (referencedParam is Parameter.Direct referencedDirect) { ConfigReference[] chainRefs = SetDataOn(referencedConfig, referencedDirect.paths, argValue); if (chainRefs != null) { // We're pointing to other ConfigReferences which means that this is a direct chain. Oh brother. foreach (ConfigReference reference in chainRefs) { if (reference != null) { if (File.Exists(ResourceDirectoryGrabber.ResourceDirectoryPath + configRefPath)) { // Catch case: This isn't actually pointing to a *configuration*, rather a direct object reference. latestAsCfg.getArguments().put(parameterName, argValue); } else { ParameterizedConfig forwardRefConfig = reference.Resolve <ParameterizedConfig>(); // Using as because it might be null. if (forwardRefConfig != null) { foreach (Parameter subRefParam in forwardRefConfig.parameters) { if (subRefParam is Parameter.Direct subRefDirect) { WrappedDirect wrappedSubRefDir = new WrappedDirect(forwardRefConfig, subRefDirect); wrappedSubRefDir.SetValue(argValue); } } latestAsCfg.getArguments().put(parameterName, ConfigReferenceConstructor.MakeConfigReferenceTo(forwardRefConfig)); } else { XanLogger.WriteLine("ALERT: Model attempted to set value of Direct [" + currentIndex + "] but it failed because the target object was not a ParameterizedConfig! Some information may be incorrect on this model.", XanLogger.DEBUG, System.Drawing.Color.DarkGoldenrod); return(null); } } } } } else { // This is by far one of the most hacky methods I've ever done in OOO stuff. // So basically, a model has a property for something like say, materials. // This property is a ConfigReference to a material object, and that ConfigReference has arguments in it // that tell the referenced material what it should be. // Rather than trying to traverse that ConfigReference and set the data on the remote object (PAINFUL), I // instead decided to write a system that can wrap any ParameterizedConfig into a ConfigReference and just // call it a day. ReflectionHelper.Set(previousObject, previousIndex, ConfigReferenceConstructor.MakeConfigReferenceTo(referencedConfig)); } } else { //throw new NotImplementedException("Cannot set data on referenced parameters that are not Directs (yet)."); XanLogger.WriteLine("Feature Not Implemented: Cannot set data on referenced parameters that aren't directs (e.g. parameters that are choices)", XanLogger.STANDARD, System.Drawing.Color.Orange); return(null); } return(null); } } else { // This is referencing a property. // But wait: If this is the second to last object, then we gotta modify it. if (idx == pathSegments.Length - 1) { // Second to last object. latestObject will contain the property that we want to set. // Let's manually find that field and set it if (currentIndex.BetweenBrackets() == null) { // We're good here. if (argValue is ConfigReference argValueCfg) { // There's some cases when a variant wants to set a config reference. // In these cases, we need to make sure the property is also a config reference so we know it's safe to set. // ... But before that, catch case: Not actually a config. if (argValueCfg.IsRealReference()) { object ptr = ReflectionHelper.Get(previousObject, previousIndex); if (ptr is ConfigReference) { ReflectionHelper.Set(previousObject, previousIndex, argValueCfg); } else { if (ReflectionHelper.GetTypeOfField(latestObject, currentIndex) == argValueCfg.GetType()) { ReflectionHelper.Set(latestObject, currentIndex, argValueCfg); return(null); } else { XanLogger.WriteLine("ALERT: Model attempted to set value of Direct [" + currentIndex + "] but it failed due to a type mismatch! Certain data on this model might be incorrect.", XanLogger.DEBUG, System.Drawing.Color.DarkGoldenrod); return(null); } } } else { if (ReflectionHelper.GetTypeOfField(latestObject, currentIndex) == argValueCfg.GetType()) { ReflectionHelper.Set(latestObject, currentIndex, argValueCfg); return(null); } else { XanLogger.WriteLine("ALERT: Model attempted to set value of Direct [" + currentIndex + "] but it failed due to a type mismatch! Certain data on this model might be incorrect.", XanLogger.DEBUG, System.Drawing.Color.DarkGoldenrod); return(null); } } } else { // Contrary to the oddball case above, if the result value at the end of this direct is a ConfigReference // then we need to return it so that whatever called this knows that it has more to traverse. // Ideally, this is only returned in the nested call above. string targetIndex = previousIndex; /*if (int.TryParse(previousIndex.BetweenBrackets(), out int _)) { * // The previous index was an array accessor. This means we want to actually reference the CURRENT index * // on the PREVIOUS object. A bit odd but it's intentional. * // This is done because the previous object will be the result of that indexer, which is identical * // to the current object. As such, we need to use the current index to reference it. * targetIndex = currentIndex; * }*/ if (previousObject == null || targetIndex == null) { return(null); } object ptr = ReflectionHelper.Get(previousObject, targetIndex); if (ptr is ConfigReference cfgRef) { // Special handling. argValue goes to a property on the config reference return(cfgRef); } if (ptr.GetType() == argValue.GetType()) { ReflectionHelper.Set(previousObject, targetIndex, argValue); } else { // In some cases, the object it's pointing to isn't the same time. // In cases where the previous index is used, this *might* mean we need to step forward like so: if (ReflectionHelper.GetTypeOfField(ptr, currentIndex) == argValue.GetType()) { ReflectionHelper.Set(ptr, currentIndex, argValue); } else { // But in other cases, it's not pointing to a prop up ahead. if (ReflectionHelper.Get(ptr, currentIndex) is ConfigReference cfgRefLow) { return(cfgRefLow); } else { XanLogger.WriteLine("ALERT: Model attempted to set value of Direct [" + currentIndex + "] but it failed due to a type mismatch! Certain data on this model might be incorrect.", XanLogger.DEBUG, System.Drawing.Color.DarkGoldenrod); return(null); } } } return(null); } } } if (previousIndex != null) { if (int.TryParse(previousIndex.BetweenBrackets(), out int _) && idx == pathSegments.Length - 1) { if (currentIndex.BetweenBrackets() == null) { // We're good here. ReflectionHelper.Set(previousObject, previousIndex, argValue); return(null); } } } previousObject = latestObject; latestObject = ReflectionHelper.Get(latestObject, currentIndex); if (previousObject == null || latestObject == null) { return(null); // Failed to traverse. } } previousIndex = currentIndex; } return(null); }
/// <summary> /// Given a full path from a <see cref="Parameter.Direct"/>, this will traverse it and acquire the data at the end.<para/> /// This will stop if it runs into another direct and instantiate a new <see cref="WrappedDirect"/>. This will occur if there is a reference chain, for instance, in many textures it references material["Texture"] (a direct) followed by a second direct ["File"]. Since each may have multiple paths, it's best to reference a new <see cref="WrappedDirect"/>. /// </summary> /// <param name="path"></param> private void TraverseDirectPath(string path) { // implementation.material_mappings[0].material["Texture"]["File"] // A bit of a hack to make splitting this path easier: path = path.Replace("[", ".["); // The latest object stored when traversing this direct's path. object latestObject = Config; // Split it by the segments of this path, and get rid of the implementation word at the start if needed. string[] pathSegments = path.Split('.'); if (pathSegments[0] == "implementation") { latestObject = ReflectionHelper.Get(latestObject, "implementation"); pathSegments = pathSegments.Skip(1).ToArray(); } for (int idx = 0; idx < pathSegments.Length; idx++) { string currentIndex = pathSegments[idx].SnakeToCamel(); string betweenBrackets = currentIndex.BetweenBrackets(); if (betweenBrackets != null) { // This is either an array index, or a reference to a config. // The simple way to test this is that if it's a numeric index, it's an array index. if (int.TryParse(betweenBrackets, out int arrayIndex)) { // Access this array index. It is a number in brackets like [0] latestObject = ReflectionHelper.GetArray(latestObject, arrayIndex); } else { // Access the config reference. This is branching from a config reference and accesses a parameter ["Parameter Name"] ConfigReference latestAsCfg = (ConfigReference)latestObject; string parameterName = betweenBrackets.Substring(1, betweenBrackets.Length - 2); // First things first: Resolve the config reference (AND CLONE IT. Don't edit the template object!) string configRefPath = latestAsCfg.getName(); if (!latestAsCfg.IsRealReference()) { // Catch case: This isn't actually pointing to a *configuration*, rather a direct object reference. _EndReferences.Add(new DirectEndReference(configRefPath)); return; } ParameterizedConfig referencedConfig = ConfigReferenceBootstrapper.ConfigReferences.TryGetReferenceFromName(configRefPath)?.CloneAs <ParameterizedConfig>(); if (referencedConfig == null) { XanLogger.WriteLine("Something failed to reference a ConfigReference (It tried to search for \"" + configRefPath + "\", which doesn't exist). Some information on this model may not load properly!", XanLogger.DEBUG, System.Drawing.Color.DarkGoldenrod); return; } ArgumentMap args = latestAsCfg.getArguments(); // So there's our reference. Now we need to get a parameter from it. ConfigReferenceContainerName = ConfigReferenceBootstrapper.ConfigReferences.GetCategoryFromEntryName(configRefPath); Parameter referencedParam = referencedConfig.getParameter(parameterName); if (referencedParam is Parameter.Direct referencedDirect) { _EndReferences.Add(new DirectEndReference(new WrappedDirect(referencedConfig, referencedDirect, null, args))); } else if (referencedParam is Parameter.Choice referencedChoice) { _EndReferences.Add(new DirectEndReference(new WrappedChoice(referencedConfig, referencedChoice, args))); } return; } } else { // This is referencing a property. latestObject = ReflectionHelper.Get(latestObject, currentIndex); } } // Now here's something important: Does an argument override this? if (Arguments != null && Arguments.containsKey(Name) && latestObject != null) { // This direct is included as an argument... // And if we're down here, then we're not referencing another direct, we're referencing a property. // But as a final sanity check: if (latestObject.GetType() == Arguments.get(Name)?.GetType()) { // Yep! Our argument is the same type as the latestObject. // Let's set latestObject to that argument. latestObject = Arguments.get(Name); } } _EndReferences.Add(new DirectEndReference(latestObject)); }
/// <summary> /// Attempts to resolve this <see cref="ConfigReference"/>, which is expected to point to a file, and returns the Clyde object that it points to.<para/> /// This is intended for use in cases where data must absolutely be loaded in-line. Most cases are better suited for <see cref="ConfigReferenceUtil"/> and its methods. /// </summary> /// <param name="cfgRef"></param> /// <returns></returns> public static object ResolveFile(this ConfigReference cfgRef) { if (!cfgRef.IsFileReference()) { throw new InvalidOperationException("Cannot resolve this ConfigReference as a file because the file it points to does not exist (or it references an actual config object)!"); } object clydeObject = ClydeFileHandler.GetRaw(new FileInfo(ResourceDirectoryGrabber.ResourceDirectoryPath + cfgRef.getName())); if (clydeObject == null) { throw new NullReferenceException("Failed to load Clyde file!"); } // Apply any arguments. if (clydeObject is ParameterizedConfig paramCfg) { paramCfg.ApplyArguments(cfgRef.getArguments()); return(paramCfg); } return(clydeObject); }
/// <summary> /// Returns <see langword="true"/> if this <see cref="ConfigReference"/> points to an actual config, and <see langword="false"/> if it does not (for instance, it points to a model file instead). /// </summary> /// <param name="cfgRef"></param> /// <returns>False if this ConfigReference points to a literal file (points to a .dat file in the rsrc directory), and true if it points to an actual configuration object (e.g. Texture/2D/Default for textures)</returns> public static bool IsRealReference(this ConfigReference cfgRef) { return(!File.Exists(ResourceDirectoryGrabber.ResourceDirectoryPath + cfgRef.getName())); }
/// <summary> /// Using the information in this <see cref="ConfigReference"/>, the object this reference points to will be resolved and returned. This <see cref="ConfigReference"/> must point to an actual configuration. If it points to a file, an <see cref="InvalidOperationException"/> will be thrown.<para/> /// This will automatically populate the arguments in the referenced config if applicable, making usage of the returned object relatively straightforward. /// </summary> /// <param name="cfgRef">The ConfigReference to resolve the reference to.</param> /// <typeparam name="T">The destination type for the new ManagedConfig</typeparam> /// <returns>The ManagedConfig the given ConfigReference is pointing to.</returns> /// <exception cref="InvalidOperationException">If IsFileReference() returns true on this ConfigReference.</exception> public static T Resolve <T>(this ConfigReference cfgRef) where T : ManagedConfig { if (cfgRef.IsFileReference()) { throw new InvalidOperationException("Cannot resolve the path to a non-config reference (this ConfigReference points to a file!)"); } T mgCfg = ConfigReferenceBootstrapper.ConfigReferences.TryGetReferenceFromName(cfgRef.getName())?.CloneAs <T>(); // Populate the values of the referenced object if possible. if (mgCfg is ParameterizedConfig paramCfg) { // This can store arguments. paramCfg.ApplyArguments(cfgRef.getArguments()); return(paramCfg as T); } // It's gotta stay as a ManagedConfig // Can't make use of this to apply arguments to. How do I handle this? // For now: Return the object without any changes. return(mgCfg); }