Download src/Repoverse.Core/Bridge/WorldSession.cs from Snapkitty/repoverse: direct link, hf CLI and curl.
- Browser
- Download file 8.7 kB
-
https://huggingface.co/Snapkitty/repoverse/resolve/main/src/Repoverse.Core/Bridge/WorldSession.cs
- Command line
-
hf download hf://Snapkitty/repoverse/src/Repoverse.Core/Bridge/WorldSession.cs
-
curl -L -o WorldSession.cs https://huggingface.co/Snapkitty/repoverse/resolve/main/src/Repoverse.Core/Bridge/WorldSession.cs
8.7 kB
| using Repoverse.Core.Generation; | |
| using Repoverse.Core.Model; | |
| using Repoverse.Core.Persistence; | |
| using Repoverse.Core.Voxels; | |
| namespace Repoverse.Core.Bridge; | |
| public sealed record MeshSection(ushort Key, bool Collision, float[][] Vertices, int[] Indices); | |
| public sealed record MeshPacket(string Id, IReadOnlyList<MeshSection> Sections); | |
| public sealed record StreamPacket(IReadOnlyList<string> Unloaded, IReadOnlyList<MeshPacket> Chunks, int Remaining, int Resident); | |
| public sealed record Destination(string Repo, string Description, double X, double Y, double Z, | |
| int Floors, int LadderX, int LadderZ, IReadOnlyList<RoomPlacement> Rooms); | |
| /// <summary>Single-client, serialized bridge session. Core Y-up units remain authoritative.</summary> | |
| public sealed class WorldSession | |
| { | |
| private readonly WorldGenerator _generator; | |
| private readonly Dictionary<ChunkCoord, Chunk> _chunks = new(); | |
| private readonly HashSet<ChunkCoord> _dirty = new(); | |
| private readonly int _radius; | |
| public WorldManifest Manifest { get; } | |
| public WorldEdits Edits { get; } = new(); | |
| public PlayerState Player { get; private set; } | |
| public long Tick { get; private set; } | |
| public IReadOnlyList<BuilderState> Builders => _builders.Values.ToArray(); | |
| private readonly Dictionary<string,BuilderState> _builders = new(StringComparer.Ordinal); | |
| public IReadOnlyList<Destination> Destinations { get; } | |
| public int Resident => _chunks.Count; | |
| public WorldSession(WorldManifest manifest, int radius = 1) | |
| { | |
| if (manifest.SchemaVersion != WorldManifest.CurrentSchema) throw new InvalidDataException("Unsupported manifest schema."); | |
| if (radius is < 0 or > 3) throw new ArgumentOutOfRangeException(nameof(radius)); | |
| Manifest = manifest; _radius = radius; _generator = new(manifest); | |
| Destinations = manifest.Buildings.Select(b => { | |
| var p = BuildingRasterizer.Plan(b); | |
| return new Destination(b.Repo.Key, b.Description, p.Entrance.X + 3.5, | |
| p.Entrance.Y + 1.5, p.Entrance.Z + .5, b.Floors, p.LadderX, p.CorridorZ + 1, p.Rooms); | |
| }).ToArray(); | |
| var start = Destinations.FirstOrDefault(); | |
| Player = new PlayerState { X = start?.X ?? 10, Y = start?.Y ?? 11, Z = start?.Z ?? 10, Yaw = 180 }; | |
| } | |
| public static void Validate(PlayerState p) | |
| { | |
| if (!double.IsFinite(p.X) || !double.IsFinite(p.Y) || !double.IsFinite(p.Z) || !double.IsFinite(p.Yaw) | |
| || Math.Abs(p.X) > 1_000_000 || Math.Abs(p.Z) > 1_000_000 || p.Y < -100 || p.Y > 10_000 | |
| || !Enum.IsDefined(p.Mode)) throw new ArgumentException("Invalid player state."); | |
| } | |
| public void SetPlayer(PlayerState player) { Validate(player); Player = player; } | |
| private static IEnumerable<ChunkCoord> Neighbours(ChunkCoord c) | |
| { | |
| yield return c; | |
| yield return c with { X = c.X - 1 }; yield return c with { X = c.X + 1 }; | |
| yield return c with { Y = c.Y - 1 }; yield return c with { Y = c.Y + 1 }; | |
| yield return c with { Z = c.Z - 1 }; yield return c with { Z = c.Z + 1 }; | |
| } | |
| private static int Distance(ChunkCoord a, ChunkCoord b) => Math.Max(Math.Abs(a.X-b.X), Math.Abs(a.Z-b.Z)); | |
| /// <summary>Bounded generation and upload, nearest first. Unloads are sent before replacement meshes.</summary> | |
| public StreamPacket Focus(double x, double z, int budget = 4) | |
| { | |
| Validate(Player with { X = x, Z = z }); | |
| if (budget is < 1 or > 16) throw new ArgumentOutOfRangeException(nameof(budget)); | |
| var center = ChunkCoord.FromVoxel((int)Math.Floor(x), WorldConstants.GroundLevel, (int)Math.Floor(z)); | |
| var unloaded = new List<string>(); | |
| foreach (var c in _chunks.Keys.Where(c => Distance(c, center) > _radius + 1).ToArray()) | |
| { | |
| _chunks.Remove(c); _dirty.Remove(c); unloaded.Add(c.ToString()); | |
| foreach (var n in Neighbours(c)) if (_chunks.ContainsKey(n)) _dirty.Add(n); | |
| } | |
| var wanted = new List<ChunkCoord>(); | |
| for (int dx = -_radius; dx <= _radius; dx++) | |
| for (int dz = -_radius; dz <= _radius; dz++) | |
| for (int y = 0; y <= WorldGenerator.MaxChunkY; y++) | |
| wanted.Add(new(center.X + dx, y, center.Z + dz)); | |
| wanted = wanted.OrderBy(c => Distance(c, center)).ThenBy(c => c.Y).ThenBy(c => c.X).ThenBy(c => c.Z).ToList(); | |
| foreach (var c in wanted.Where(c => !_chunks.ContainsKey(c)).Take(budget)) | |
| { | |
| _chunks[c] = _generator.Generate(c, Edits); | |
| foreach (var n in Neighbours(c)) if (_chunks.ContainsKey(n)) _dirty.Add(n); | |
| } | |
| var result = new List<MeshPacket>(); | |
| foreach (var c in _dirty.OrderBy(c => Distance(c, center)).ThenBy(c => c.Y).ThenBy(c => c.X).ThenBy(c => c.Z).Take(budget).ToArray()) | |
| { | |
| result.Add(Encode(GreedyMesher.Build(_chunks[c], Sample))); | |
| _dirty.Remove(c); | |
| } | |
| Tick++; | |
| return new(unloaded, result, wanted.Count(c => !_chunks.ContainsKey(c)) + _dirty.Count, _chunks.Count); | |
| } | |
| public Voxel? Sample(int x, int y, int z) => _chunks.TryGetValue(ChunkCoord.FromVoxel(x,y,z), out var c) | |
| ? c[x-c.Coord.OriginX, y-c.Coord.OriginY, z-c.Coord.OriginZ] : null; | |
| public static MeshPacket Encode(ChunkMesh mesh) | |
| { | |
| var sections = new List<MeshSection>(); | |
| foreach (var group in mesh.Quads.GroupBy(q => q.MeshKey).OrderBy(g => g.Key)) | |
| { | |
| var vertices = new List<float[]>(); var indices = new List<int>(); | |
| foreach (var q in group) | |
| { | |
| int i = vertices.Count; | |
| vertices.AddRange(GreedyMesher.Corners(mesh.Coord, q).Select(v => new[] {v.X,v.Y,v.Z})); | |
| indices.AddRange([i,i+1,i+2,i,i+2,i+3]); | |
| } | |
| sections.Add(new(group.Key, !new Voxel(group.Key).IsPassable, vertices.ToArray(), indices.ToArray())); | |
| } | |
| return new(mesh.Coord.ToString(), sections); | |
| } | |
| public IReadOnlyList<WorldObject> Objects() => Manifest.Buildings.SelectMany(BuildingRasterizer.Objects).ToArray(); | |
| public void Edit(int x, int y, int z, ushort packed) | |
| { | |
| if (y < 1 || y > WorldGenerator.MaxChunkY * WorldConstants.ChunkSize + 31 || Math.Abs((long)x)>1_000_000 || Math.Abs((long)z)>1_000_000 | |
| || !Enum.IsDefined(new Voxel(packed).Type)) throw new ArgumentException("Invalid voxel edit."); | |
| Edits.Set(x,y,z,new Voxel(packed)); | |
| var c = ChunkCoord.FromVoxel(x,y,z); | |
| if (_chunks.ContainsKey(c)) _chunks[c] = _generator.Generate(c,Edits); | |
| foreach (var n in Neighbours(c)) if (_chunks.ContainsKey(n)) _dirty.Add(n); | |
| } | |
| public BuilderState Build(BuilderPlan plan) | |
| { | |
| var voxels=BuilderRasterizer.Rasterize(plan); | |
| if(!_builders.ContainsKey(plan.Actor) && _builders.Count>=16)throw new ArgumentException("Builder actor limit reached."); | |
| var changed=new HashSet<ChunkCoord>(); | |
| foreach(var v in voxels){Edits.Set(v.X,v.Y,v.Z,new Voxel(v.Packed));changed.Add(ChunkCoord.FromVoxel(v.X,v.Y,v.Z));} | |
| foreach(var c in changed){if(_chunks.ContainsKey(c))_chunks[c]=_generator.Generate(c,Edits);foreach(var n in Neighbours(c))if(_chunks.ContainsKey(n))_dirty.Add(n);} | |
| var last=voxels[^1];var state=new BuilderState(plan.Actor,plan.Id,last.X+.5,last.Y+1,last.Z+.5,voxels.Count);_builders[plan.Actor]=state;return state; | |
| } | |
| public SaveGame Capture() => SaveGame.Capture(Manifest, Player, Edits, Tick) with {Builders=Builders}; | |
| public void Restore(SaveGame save) | |
| { | |
| if (save.WorldSeed != Manifest.WorldSeed || save.Generator != Manifest.Generator | |
| || save.Snapshots.Count != Manifest.Snapshots.Count || save.Snapshots.Any(s => !Manifest.Snapshots.TryGetValue(s.Key,out var v) || v != s.Value)) | |
| throw new InvalidDataException("Save belongs to a different world snapshot."); | |
| Validate(save.Player); | |
| // Validate in a temporary store before changing the live session. | |
| var restored = new WorldEdits(); save.RestoreEdits(restored); | |
| if(save.Builders.Count>16 || save.Builders.Any(b=>string.IsNullOrWhiteSpace(b.Actor)||b.Actor.Length>64||!double.IsFinite(b.X)||!double.IsFinite(b.Y)||!double.IsFinite(b.Z)) || save.Builders.Select(b=>b.Actor).Distinct().Count()!=save.Builders.Count)throw new InvalidDataException("Invalid saved builders."); | |
| Edits.Load(restored.Snapshot()); Player = save.Player; Tick = save.SimulationTick; | |
| _builders.Clear();foreach(var b in save.Builders)_builders.Add(b.Actor,b); | |
| foreach (var c in _chunks.Keys.ToArray()) { _chunks[c] = _generator.Generate(c,Edits); _dirty.Add(c); } | |
| } | |
| } | |