Editor/Services/ArchOwned.cs
using System;
using System.Collections.Generic;
using System.Linq;
namespace Sunless.Architecture;
// Effects keyed by (host, ordinal) slot so a re-derive keeps existing ids.
public sealed record ArchOwnedHosts<THost, TEffect>(
Func<ArchPlan, IEnumerable<THost>> All,
Func<THost, int> IdOf,
Func<THost, List<TEffect>> Effects,
Func<TEffect, int> OwnerOf,
Action<TEffect, int, int> Stamp );
// One effect an owner wants. Dress writes everything but identity, so it may run over an effect that has
// been standing since the plan was authored.
public readonly record struct ArchWant<THost, TEffect>( THost Host, Action<TEffect> Dress );
public static class ArchOwned {
// Never a static field: a held delegate answers with the code the editor had before the last hotload.
public static ArchOwnedHosts<ArchWall, ArchOpening> Openings => new(
// AllWalls, not rooms: a wall also hosts on a roof parapet, and an effect left there could never be closed.
plan => plan.AllWalls(),
wall => wall.Id,
wall => wall.Openings,
opening => opening.OwnerId,
( opening, id, owner ) => {
opening.Id = id;
opening.OwnerId = owner;
} );
// Reconcile standing effects against wants: keep occupied slots, mint empty, remove unwanted.
public static bool Settle<THost, TEffect>( ArchPlan plan, ArchOwnedHosts<THost, TEffect> family, int ownerId,
IReadOnlyList<ArchWant<THost, TEffect>> wants, Func<TEffect> mint ) where TEffect : class {
if ( plan is null || family is null || wants is null ) {
return false;
}
var standing = Standing( plan, family, ownerId );
var taken = new HashSet<(int Host, int Ordinal)>();
var ordinals = new Dictionary<int, int>();
var moved = false;
foreach ( var want in wants ) {
var host = family.IdOf( want.Host );
ordinals.TryGetValue( host, out var ordinal );
ordinals[host] = ordinal + 1;
var slot = (host, ordinal);
if ( standing.TryGetValue( slot, out var effect ) ) {
taken.Add( slot );
} else {
effect = mint();
family.Stamp( effect, plan.AllocateId(), ownerId );
family.Effects( want.Host ).Add( effect );
moved = true;
}
want.Dress( effect );
}
var vanished = standing
.Where( entry => !taken.Contains( entry.Key ) )
.Select( entry => entry.Value )
.ToList();
if ( vanished.Count == 0 ) {
return moved;
}
foreach ( var host in family.All( plan ) ) {
family.Effects( host ).RemoveAll( vanished.Contains );
}
return true;
}
// Everything an owner has standing, wherever it stands - the other half of being able to re-derive.
public static int Close<THost, TEffect>( ArchPlan plan, ArchOwnedHosts<THost, TEffect> family, int ownerId ) {
if ( plan is null || family is null ) {
return 0;
}
return family.All( plan ).Sum( host => family.Effects( host ).RemoveAll( effect => family.OwnerOf( effect ) == ownerId ) );
}
static Dictionary<(int Host, int Ordinal), TEffect> Standing<THost, TEffect>( ArchPlan plan,
ArchOwnedHosts<THost, TEffect> family, int ownerId ) {
var standing = new Dictionary<(int, int), TEffect>();
foreach ( var host in family.All( plan ) ) {
var id = family.IdOf( host );
var ordinal = 0;
foreach ( var effect in family.Effects( host ).Where( effect => family.OwnerOf( effect ) == ownerId ) ) {
standing[(id, ordinal++)] = effect;
}
}
return standing;
}
}