Editor/Layers/ArchLayerIds.cs
using System.Text.Json.Nodes;
namespace Sunless.Architecture;
// Every plan id in a saved subtree, moved as one block so the copy lands above whatever the plan already
// holds. Walked over the JSON and not the typed tree, because a unit carries payloads for kinds this build
// has no type for: an id left behind in those bytes is a stamped copy standing on the original's ids, which
// in the scene is one object where two were meant to be.
//
// Moving the whole range by one delta is what keeps the references honest. Every id inside the subtree is
// unique within it, so a constant shift preserves every pointer between them without this having to know
// which properties are pointers.
public static class ArchLayerIds {
// Named on something the copy does not bring with it - a shifted road id points at somebody else's kerb.
static readonly HashSet<string> Foreign = new() { "RoadId", "ApproachId" };
public static bool Range( string json, out int lowest, out int highest ) {
var low = int.MaxValue;
var high = 0;
lowest = 0;
highest = 0;
if ( JsonNode.Parse( json ) is not { } root ) {
return false;
}
Walk( root, ( property, value ) => {
if ( Foreign.Contains( property ) ) {
return value;
}
low = Math.Min( low, value );
high = Math.Max( high, value );
return value;
} );
lowest = low;
highest = high;
return high > 0;
}
public static HashSet<int> Owned( string json ) => new( Every( json ) );
// The same ids WITH their repeats. Owned answers whether an id is in the document, which is a question that
// cannot see two parts standing under one - and two parts under one id is the failure this file exists for.
public static List<int> Every( string json ) {
var every = new List<int>();
if ( JsonNode.Parse( json ) is not { } root ) {
return every;
}
Walk( root, ( property, value ) => {
if ( property == "Id" ) {
every.Add( value );
}
return value;
} );
return every;
}
public static string Shifted( string json, int delta ) {
if ( JsonNode.Parse( json ) is not { } root ) {
return json;
}
Walk( root, ( property, value ) => Foreign.Contains( property ) ? 0 : value + delta );
return root.ToJsonString();
}
static void Walk( JsonNode node, Func<string, int, int> visit ) {
switch ( node ) {
case JsonArray array:
foreach ( var entry in array ) {
if ( entry is not null ) {
Walk( entry, visit );
}
}
return;
case JsonObject: break;
default: return;
}
var holder = (JsonObject)node;
// Over a copy of the names: assigning back into the object while enumerating it throws.
foreach ( var property in holder.Select( pair => pair.Key ).ToList() ) {
var value = holder[property];
if ( value is JsonObject or JsonArray ) {
Walk( value, visit );
continue;
}
if ( !property.EndsWith( "Id", StringComparison.Ordinal ) || value is not JsonValue held || !held.TryGetValue<int>( out var id ) || id == 0 ) {
continue;
}
holder[property] = JsonValue.Create( visit( property, id ) );
}
}
}