Ymir/src/Ymir.Core/YmirWorldStateDocument.cs
2026-07-14 22:01:19 +01:00

273 lines
12 KiB
C#

using GameCult.Caching;
using GameCult.Caching.MessagePack;
using MessagePack;
namespace Ymir.Core;
[CultDocument("gamecult.ymir.world_state", "gamecult.ymir.world_state.v0")]
[MessagePackObject]
public sealed class YmirWorldStateDocumentV0
{
[Key(0)]
[CultName]
public string WorldId { get; set; } = "default";
[Key(1)]
public float Time { get; set; }
[Key(2)]
public string[] BodyIds { get; set; } = [];
[Key(3)]
public float[] PositionX { get; set; } = [];
[Key(4)]
public float[] PositionY { get; set; } = [];
[Key(5)]
public float[] VelocityX { get; set; } = [];
[Key(6)]
public float[] VelocityY { get; set; } = [];
[Key(7)]
public float[] Radius { get; set; } = [];
[Key(8)]
public float[] Mass { get; set; } = [];
[Key(9)]
public float[] DynamicMask { get; set; } = [];
[Key(10)]
public float[] Restitution { get; set; } = [];
[Key(11)]
public string[] FieldIds { get; set; } = [];
[Key(12)]
public float[] FieldPositionX { get; set; } = [];
[Key(13)]
public float[] FieldPositionY { get; set; } = [];
[Key(14)]
public float[] FieldStrength { get; set; } = [];
[Key(15)]
public float[] FieldRadius { get; set; } = [];
public static YmirWorldStateDocumentV0 FromSoA(string worldId, YmirSoAWorld world) => new()
{
WorldId = worldId,
Time = world.Time,
BodyIds = (string[])world.BodyIds.Clone(),
PositionX = (float[])world.PositionX.Clone(),
PositionY = (float[])world.PositionY.Clone(),
VelocityX = (float[])world.VelocityX.Clone(),
VelocityY = (float[])world.VelocityY.Clone(),
Radius = (float[])world.Radius.Clone(),
Mass = (float[])world.Mass.Clone(),
DynamicMask = (float[])world.DynamicMask.Clone(),
Restitution = (float[])world.Restitution.Clone(),
FieldIds = (string[])world.FieldIds.Clone(),
FieldPositionX = (float[])world.FieldPositionX.Clone(),
FieldPositionY = (float[])world.FieldPositionY.Clone(),
FieldStrength = (float[])world.FieldStrength.Clone(),
FieldRadius = (float[])world.FieldRadius.Clone()
};
public YmirSoAWorld ToSoA() => new()
{
Time = Time,
BodyIds = (string[])BodyIds.Clone(),
PositionX = (float[])PositionX.Clone(),
PositionY = (float[])PositionY.Clone(),
VelocityX = (float[])VelocityX.Clone(),
VelocityY = (float[])VelocityY.Clone(),
Radius = (float[])Radius.Clone(),
Mass = (float[])Mass.Clone(),
DynamicMask = (float[])DynamicMask.Clone(),
Restitution = (float[])Restitution.Clone(),
FieldIds = (string[])FieldIds.Clone(),
FieldPositionX = (float[])FieldPositionX.Clone(),
FieldPositionY = (float[])FieldPositionY.Clone(),
FieldStrength = (float[])FieldStrength.Clone(),
FieldRadius = (float[])FieldRadius.Clone()
};
}
[CultDocument("gamecult.ymir.world_state", "gamecult.ymir.world_state.v1")]
[MessagePackObject]
public sealed class YmirWorldStateDocumentV1
{
[Key(0), CultName] public string WorldId { get; set; } = "default";
[Key(1)] public float Time { get; set; }
[Key(2)] public string[] BodyIds { get; set; } = [];
[Key(3)] public float[] PositionX { get; set; } = [];
[Key(4)] public float[] PositionY { get; set; } = [];
[Key(5)] public float[] VelocityX { get; set; } = [];
[Key(6)] public float[] VelocityY { get; set; } = [];
[Key(7)] public float[] Radius { get; set; } = [];
[Key(8)] public float[] Mass { get; set; } = [];
[Key(9)] public float[] DynamicMask { get; set; } = [];
[Key(10)] public float[] Restitution { get; set; } = [];
[Key(11)] public string[] FieldIds { get; set; } = [];
[Key(12)] public float[] FieldPositionX { get; set; } = [];
[Key(13)] public float[] FieldPositionY { get; set; } = [];
[Key(14)] public float[] FieldStrength { get; set; } = [];
[Key(15)] public float[] FieldRadius { get; set; } = [];
[Key(16)] public float[] DirectionX { get; set; } = [];
[Key(17)] public float[] DirectionY { get; set; } = [];
[Key(18)] public float[] AngularVelocity { get; set; } = [];
public static YmirWorldStateDocumentV1 FromSoA(string worldId, YmirSoAWorld world) => new()
{
WorldId = worldId,
Time = world.Time,
BodyIds = (string[])world.BodyIds.Clone(),
PositionX = (float[])world.PositionX.Clone(),
PositionY = (float[])world.PositionY.Clone(),
VelocityX = (float[])world.VelocityX.Clone(),
VelocityY = (float[])world.VelocityY.Clone(),
Radius = (float[])world.Radius.Clone(),
Mass = (float[])world.Mass.Clone(),
DynamicMask = (float[])world.DynamicMask.Clone(),
Restitution = (float[])world.Restitution.Clone(),
FieldIds = (string[])world.FieldIds.Clone(),
FieldPositionX = (float[])world.FieldPositionX.Clone(),
FieldPositionY = (float[])world.FieldPositionY.Clone(),
FieldStrength = (float[])world.FieldStrength.Clone(),
FieldRadius = (float[])world.FieldRadius.Clone(),
DirectionX = (float[])world.DirectionX.Clone(),
DirectionY = (float[])world.DirectionY.Clone(),
AngularVelocity = (float[])world.AngularVelocity.Clone()
};
public YmirSoAWorld ToSoA() => new()
{
Time = Time,
BodyIds = (string[])BodyIds.Clone(),
PositionX = (float[])PositionX.Clone(),
PositionY = (float[])PositionY.Clone(),
VelocityX = (float[])VelocityX.Clone(),
VelocityY = (float[])VelocityY.Clone(),
Radius = (float[])Radius.Clone(),
Mass = (float[])Mass.Clone(),
DynamicMask = (float[])DynamicMask.Clone(),
Restitution = (float[])Restitution.Clone(),
FieldIds = (string[])FieldIds.Clone(),
FieldPositionX = (float[])FieldPositionX.Clone(),
FieldPositionY = (float[])FieldPositionY.Clone(),
FieldStrength = (float[])FieldStrength.Clone(),
FieldRadius = (float[])FieldRadius.Clone(),
DirectionX = (float[])DirectionX.Clone(),
DirectionY = (float[])DirectionY.Clone(),
AngularVelocity = (float[])AngularVelocity.Clone()
};
}
[CultDocument("gamecult.ymir.world_state", "gamecult.ymir.world_state.v2")]
[MessagePackObject]
public sealed class YmirWorldStateDocumentV2
{
[Key(0), CultName] public string WorldId { get; set; } = "default";
[Key(1)] public float Time { get; set; }
[Key(2)] public string[] BodyIds { get; set; } = [];
[Key(3)] public float[] PositionX { get; set; } = [];
[Key(4)] public float[] PositionY { get; set; } = [];
[Key(5)] public float[] VelocityX { get; set; } = [];
[Key(6)] public float[] VelocityY { get; set; } = [];
[Key(7)] public float[] Radius { get; set; } = [];
[Key(8)] public float[] Mass { get; set; } = [];
[Key(9)] public float[] DynamicMask { get; set; } = [];
[Key(10)] public float[] Restitution { get; set; } = [];
[Key(11)] public string[] FieldIds { get; set; } = [];
[Key(12)] public float[] FieldPositionX { get; set; } = [];
[Key(13)] public float[] FieldPositionY { get; set; } = [];
[Key(14)] public float[] FieldStrength { get; set; } = [];
[Key(15)] public float[] FieldRadius { get; set; } = [];
[Key(16)] public float[] DirectionX { get; set; } = [];
[Key(17)] public float[] DirectionY { get; set; } = [];
[Key(18)] public float[] AngularVelocity { get; set; } = [];
[Key(19)] public float[] KinematicMask { get; set; } = [];
[Key(20)] public float[] BulletMask { get; set; } = [];
[Key(21)] public float[] FieldParticipationMask { get; set; } = [];
[Key(22)] public ulong[] CollisionCategoryBits { get; set; } = [];
[Key(23)] public ulong[] CollisionMaskBits { get; set; } = [];
[Key(24)] public int[] CollisionGroupIndex { get; set; } = [];
public static YmirWorldStateDocumentV2 FromSoA(string worldId, YmirSoAWorld world) => new()
{
WorldId = worldId, Time = world.Time,
BodyIds = (string[])world.BodyIds.Clone(),
PositionX = (float[])world.PositionX.Clone(), PositionY = (float[])world.PositionY.Clone(),
VelocityX = (float[])world.VelocityX.Clone(), VelocityY = (float[])world.VelocityY.Clone(),
Radius = (float[])world.Radius.Clone(), Mass = (float[])world.Mass.Clone(),
DynamicMask = (float[])world.DynamicMask.Clone(), Restitution = (float[])world.Restitution.Clone(),
FieldIds = (string[])world.FieldIds.Clone(),
FieldPositionX = (float[])world.FieldPositionX.Clone(), FieldPositionY = (float[])world.FieldPositionY.Clone(),
FieldStrength = (float[])world.FieldStrength.Clone(), FieldRadius = (float[])world.FieldRadius.Clone(),
DirectionX = (float[])world.DirectionX.Clone(), DirectionY = (float[])world.DirectionY.Clone(),
AngularVelocity = (float[])world.AngularVelocity.Clone(),
KinematicMask = (float[])world.KinematicMask.Clone(), BulletMask = (float[])world.BulletMask.Clone(),
FieldParticipationMask = (float[])world.FieldParticipationMask.Clone(),
CollisionCategoryBits = (ulong[])world.CollisionCategoryBits.Clone(),
CollisionMaskBits = (ulong[])world.CollisionMaskBits.Clone(),
CollisionGroupIndex = (int[])world.CollisionGroupIndex.Clone()
};
public YmirSoAWorld ToSoA() => new()
{
Time = Time, BodyIds = (string[])BodyIds.Clone(),
PositionX = (float[])PositionX.Clone(), PositionY = (float[])PositionY.Clone(),
VelocityX = (float[])VelocityX.Clone(), VelocityY = (float[])VelocityY.Clone(),
Radius = (float[])Radius.Clone(), Mass = (float[])Mass.Clone(),
DynamicMask = (float[])DynamicMask.Clone(), Restitution = (float[])Restitution.Clone(),
FieldIds = (string[])FieldIds.Clone(),
FieldPositionX = (float[])FieldPositionX.Clone(), FieldPositionY = (float[])FieldPositionY.Clone(),
FieldStrength = (float[])FieldStrength.Clone(), FieldRadius = (float[])FieldRadius.Clone(),
DirectionX = (float[])DirectionX.Clone(), DirectionY = (float[])DirectionY.Clone(),
AngularVelocity = (float[])AngularVelocity.Clone(),
KinematicMask = (float[])KinematicMask.Clone(), BulletMask = (float[])BulletMask.Clone(),
FieldParticipationMask = (float[])FieldParticipationMask.Clone(),
CollisionCategoryBits = (ulong[])CollisionCategoryBits.Clone(),
CollisionMaskBits = (ulong[])CollisionMaskBits.Clone(),
CollisionGroupIndex = (int[])CollisionGroupIndex.Clone()
};
}
public static class YmirCultCacheStore
{
public static async Task SaveWorldAsync(string path, string worldId, YmirSoAWorld world)
{
var cache = await CultCacheMessagePack.OpenAsync(
path,
new CultCacheOpenOptions
{
UseDirectoryStore = true,
FlushOnDispose = true,
StoreFlushOnDispose = true
}).ConfigureAwait(false);
using (cache)
{
await cache.UpsertAsync(
YmirWorldStateDocumentV2.FromSoA(worldId, world),
new CultRecordHandle<YmirWorldStateDocumentV2>(new CultRecordKey($"ymir:world:{worldId}")))
.ConfigureAwait(false);
await cache.FlushAsync().ConfigureAwait(false);
}
}
public static async Task<YmirSoAWorld?> LoadWorldAsync(string path, string worldId)
{
using var cache = await CultCacheMessagePack.OpenAsync(
path,
new CultCacheOpenOptions { UseDirectoryStore = true }).ConfigureAwait(false);
var key = new CultRecordKey($"ymir:world:{worldId}");
return cache.Get<YmirWorldStateDocumentV2>(key)?.ToSoA()
?? cache.Get<YmirWorldStateDocumentV1>(key)?.ToSoA()
?? cache.Get<YmirWorldStateDocumentV0>(key)?.ToSoA();
}
}