Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion Loading/BlockType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -816,7 +816,11 @@ public static string[] GetCreativeTabs(AssetLocation code, Dictionary<string, st
{
for (int i = 0; i < val.Value.Length; i++)
{
string blockCode = RegistryObject.FillPlaceHolder(val.Value[i], searchReplace);
// string blockCode = RegistryObject.FillPlaceHolder(val.Value[i], searchReplace);

// Using optimized version of placeholder replacement method
// This is a very hot spot, as it is called during initialization of each block and item
string blockCode = RegistryObject.FillPlaceHolderOptimized(val.Value[i], searchReplace);

if (WildcardUtil.Match(blockCode, code.Path))
//if (WildCardMatch(blockCode, code.Path))
Expand Down
241 changes: 161 additions & 80 deletions Loading/RegistryObjectType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -272,124 +272,205 @@ private void loadInherits(ICoreAPI api, ref JObject entityTypeObject, string ent

}

internal virtual RegistryObjectType CreateAndPopulate(ICoreServerAPI api, AssetLocation fullcode, JObject jobject, JsonSerializer deserializer, API.Datastructures.OrderedDictionary<string, string> variant)
internal virtual RegistryObjectType CreateAndPopulate(ICoreServerAPI api, AssetLocation fullcode, JObject jobject, JsonSerializer deserializer, API.Datastructures.OrderedDictionary<string, string> variant)
{
return this;
}


/// <summary>
/// Create and populate the resolved type
/// </summary>
protected T CreateResolvedType<T>(ICoreServerAPI api, AssetLocation fullcode, JObject jobject, JsonSerializer deserializer, OrderedDictionary<string, string> variant) where T : RegistryObjectType, new()
{
T resolvedType = new T()
{
return this;
}
Code = Code,
VariantGroups = VariantGroups,
Enabled = Enabled,
jsonObject = jobject, // This is now already resolved JSON
Variant = variant
};

protected T CreateResolvedType<T>(ICoreServerAPI api, AssetLocation fullcode, JObject jobject, JsonSerializer deserializer, API.Datastructures.OrderedDictionary<string, string> variant) where T : RegistryObjectType, new()
// solvedbytype is no longer needed since we already resolved the JSON earlier

try
{
T resolvedType = new T()
{
Code = Code,
VariantGroups = VariantGroups,
Enabled = Enabled,
jsonObject = jobject,
Variant = variant
};
JsonUtil.PopulateObject(resolvedType, jobject, deserializer);
}
catch (Exception e)
{
api.Server.Logger.Error("Exception thrown while trying to parse json data of the type with code {0}, variant {1}. Will ignore most of the attributes. Exception:", this.Code, fullcode);
api.Server.Logger.Error(e);
}

try
resolvedType.Code = fullcode;
resolvedType.jsonObject = null;
return resolvedType;
}

/// <summary>
/// Checks if the token needs resolution (contains "byType" or placeholders "{...}").
/// This is a static check, independent of codePath and variant.
/// </summary>
private static bool NeedsResolve(JToken token)
{
if (token == null) return false;

// Check if the object has properties ending with "byType"
if (token is JObject obj)
{
foreach (var prop in obj.Properties())
{
solveByType(jobject, fullcode.Path, variant);
if (prop.Name.EndsWith("byType", StringComparison.OrdinalIgnoreCase)) return true;
if (NeedsResolve(prop.Value)) return true;
}
catch (Exception e)
return false;
}
else if (token is JArray arr) // Check array elements
{
// If at least one element needs resolution, return true
foreach (var item in arr)
{
api.Server.Logger.Error("Exception thrown while trying to resolve *byType properties of type {0}, variant {1}. Will ignore most of the attributes. Exception thrown:", this.Code, fullcode);
api.Server.Logger.Error(e);
if (NeedsResolve(item)) return true;
}
return false;
}
else if (token is JValue val && val.Type == JTokenType.String) // Check string values for placeholders
{
string str = (string)val.Value;
return str != null && str.Contains("{");
}

try
return false;
}

/// <summary>
/// Lazily resolves the JSON token for the given codePath and variant
/// Creates new containers (JObject/JArray) only for parts requiring changes
/// Unchanged subtrees are shared
/// </summary>
public static JToken Resolve(JToken token, string codePath, OrderedDictionary<string, string> searchReplace)
{
if (token == null)
return null;

// Check if the token needs resolution
if (token is JObject obj)
{
// Check if this object needs resolution at all
bool hasByType = false;
bool needsChildResolve = false;
foreach (var prop in obj.Properties())
{
JsonUtil.PopulateObject(resolvedType, jobject, deserializer);
if (prop.Name.EndsWith("byType", StringComparison.OrdinalIgnoreCase))
{
hasByType = true;
}
if (NeedsResolve(prop.Value))
{
needsChildResolve = true;
}
}
catch (Exception e)

if (!hasByType && !needsChildResolve)
{
api.Server.Logger.Error("Exception thrown while trying to parse json data of the type with code {0}, variant {1}. Will ignore most of the attributes. Exception:", this.Code, fullcode);
api.Server.Logger.Error(e);
return token; // Share the original object since nothing changes
}

resolvedType.Code = fullcode;
resolvedType.jsonObject = null;
return resolvedType;
}
// Create a new JObject only if needed
var newObj = new JObject();
Dictionary<string, JToken> propertiesToAdd = null;

protected static void solveByType(JToken json, string codePath, API.Datastructures.OrderedDictionary<string, string> searchReplace)
{
if (json is JObject jsonObj)
foreach (var prop in obj.Properties())
{
List<string> propertiesToRemove = null;
Dictionary<string, JToken> propertiesToAdd = null;

foreach (var entry in jsonObj)
var key = prop.Name;
if (key.EndsWith("byType", StringComparison.OrdinalIgnoreCase))
{
if (entry.Key.EndsWith("byType", StringComparison.OrdinalIgnoreCase))
string trueKey = key.Substring(0, key.Length - "byType".Length);
var byTypeObj = prop.Value as JObject;
if (byTypeObj == null) continue;

JToken selected = null;
foreach (var byTypeProp in byTypeObj.Properties())
{
string trueKey = entry.Key.Substring(0, entry.Key.Length - "byType".Length);
var jobj = entry.Value as JObject;
if (jobj == null)
{
throw new FormatException("Invalid value at key: " + entry.Key);
}
foreach (var byTypeProperty in jobj)
if (WildcardUtil.Match(byTypeProp.Name, codePath))
{
if (WildcardUtil.Match(byTypeProperty.Key, codePath))
{
JToken typedToken = byTypeProperty.Value; // Unnecessary to solveByType specifically on this new token's contents as we will be doing a solveByType on all the tokens in the jsonObj anyhow, after adding the propertiesToAdd
if (propertiesToAdd == null) propertiesToAdd = new Dictionary<string, JToken>();
propertiesToAdd.Add(trueKey, typedToken);
break; // Replaces for first matched key only
}
selected = byTypeProp.Value;
break; // First matched
}
if (propertiesToRemove == null) propertiesToRemove = new List<string>();
propertiesToRemove.Add(entry.Key);
}
}

if (selected != null)
{
if (propertiesToAdd == null) propertiesToAdd = new Dictionary<string, JToken>();
propertiesToAdd[trueKey] = selected; // Store JToken directly
}
}
else
{
newObj[key] = Resolve(prop.Value, codePath, searchReplace);
}
}

if (propertiesToRemove != null)
// Add the resolved "byType" properties
if (propertiesToAdd != null)
{
foreach (var add in propertiesToAdd)
{
foreach (var property in propertiesToRemove)
string trueKey = add.Key;
JToken selected = add.Value;
JToken resolvedSelected = Resolve(selected, codePath, searchReplace);

if (newObj[trueKey] is JObject existing)
{
jsonObj.Remove(property);
existing.Merge(resolvedSelected); // No mergeSettings, to match original (default Concat for arrays)
}

if (propertiesToAdd != null)
else
{
foreach (var property in propertiesToAdd)
{
if (jsonObj[property.Key] is JObject jobject)
{
jobject.Merge(property.Value);
}
else
{
jsonObj[property.Key] = property.Value;
}
}
newObj[trueKey] = resolvedSelected;
}
}
}

foreach (var entry in jsonObj)
return newObj;
}
else if (token is JArray arr)
{
// Check if the array needs resolution
bool needs = false;
foreach (var item in arr)
{
if (NeedsResolve(item))
{
solveByType(entry.Value, codePath, searchReplace);
needs = true;
break;
}
}
else if (json.Type == JTokenType.String)

if (!needs) return token; // Share original

var newArr = new JArray();
foreach (var item in arr)
{
string value = (string)(json as JValue).Value;
if (value.Contains("{"))
{
(json as JValue).Value = RegistryObject.FillPlaceHolder(value, searchReplace);
}
newArr.Add(Resolve(item, codePath, searchReplace));
}
else if (json is JArray jarray)
return newArr;
}
else if (token is JValue val && val.Type == JTokenType.String) // String value
{
string str = (string)val.Value;
if (str != null && str.Contains("{"))
{
foreach (var child in jarray)
{
solveByType(child, codePath, searchReplace);
}
return new JValue(RegistryObject.FillPlaceHolderOptimized(str, searchReplace)); // Using optimized version of placeholder replacement method
}
return token; // Share original
}
else
{
return token; // Primitives shared
}
}
#endregion

}
Expand Down
Loading