riversong code showcase

This commit is contained in:
Daniele Marotta
2026-05-21 15:52:18 +02:00
commit 4c9eea1c02
462 changed files with 23406 additions and 0 deletions

View File

@@ -0,0 +1,15 @@
namespace DanieleMarotta.RiversongCodeShowcase
{
public struct BuildingProductionState
{
public ProducerState State;
public Recipe Recipe;
public int Progress;
public ProductionRequirements MetRequirements;
public bool AllRequirementsMet => MetRequirements == ProductionRequirements.All;
}
}

View File

@@ -0,0 +1,8 @@
namespace DanieleMarotta.RiversongCodeShowcase
{
[GameSystemGroup(typeof(DefaultGameSystemGroup))]
public class EconomySystemGroup : GameSystemGroup
{
}
}

View File

@@ -0,0 +1,13 @@
namespace DanieleMarotta.RiversongCodeShowcase
{
public interface IProductCatalog
{
public const int InvalidHandle = -1;
int ProductTypeCount { get; }
int GetHandle(ProductDefinition product);
ProductDefinition GetProduct(int productHandle);
}
}

View File

@@ -0,0 +1,15 @@
using Unity.Mathematics;
namespace DanieleMarotta.RiversongCodeShowcase
{
public interface IProductStackFactory
{
ProductStack Create(int2 tile, ProductDefinition product, int amount);
ProductStack CreateOrMerge(int2 tile, ProductDefinition product, int amount);
void CreateProductStacksFrom(ConstructionSite constructionSite);
void CreateProductStacksFrom(Building building);
}
}

View File

@@ -0,0 +1,9 @@
using UnityEngine;
namespace DanieleMarotta.RiversongCodeShowcase
{
public interface IProductStackVisualizationCollection
{
bool TryGetVisualization(int id, out GameObject visualization);
}
}

View File

@@ -0,0 +1,15 @@
namespace DanieleMarotta.RiversongCodeShowcase
{
public enum LaborTier
{
Minimum,
Low,
Medium,
High,
Count
}
}

View File

@@ -0,0 +1,51 @@
using UnityEngine;
namespace DanieleMarotta.RiversongCodeShowcase
{
[GameSystemGroup(typeof(EconomySystemGroup))]
[UpdateBefore(typeof(ProductionTickGameSystem))]
public class LaborUpdateSystem : GameSystem, IUpdatable
{
[InjectService]
private GameConfig _config;
[InjectService]
private World _world;
[InjectService]
private IEntityCache _entityCache;
public LaborUpdateSystem(IServiceLocator serviceLocator) : base(serviceLocator)
{
}
public void Update()
{
var state = _world.ProductionState;
state.LaborDemand = 0;
foreach (var building in _entityCache.GetBuildingsWithWorkers()) state.LaborDemand += building.Definition.WorkerCount;
var population = _world.PopulationState.Population;
state.LaborSupply = Mathf.FloorToInt(population * _config.Economy.PopulationToLaborFactor);
state.LaborFulfillmentRatio = Mathf.Clamp01(state.LaborDemand > 0 ? state.LaborSupply / (float)state.LaborDemand : 1);
var tiers = _config.Economy.LaborTiers;
state.LaborTier = LaborTier.Minimum;
for (var tier = Mathf.Min((int)LaborTier.Count, tiers.Count) - 1; tier >= 0; tier--)
{
if (state.LaborFulfillmentRatio < tiers[tier].Threshold) continue;
state.LaborTier = (LaborTier)tier;
break;
}
var minTier = Mathf.Max(0, (int)LaborTier.High - population / _config.Economy.PopulationPerMinLaborTierStep);
state.LaborTier = (LaborTier)Mathf.Max((int)state.LaborTier, minTier);
state.LaborEfficiencyModifier = (int)state.LaborTier < tiers.Count ? tiers[(int)state.LaborTier].EfficiencyModifier : 0;
}
}
}

View File

@@ -0,0 +1,9 @@
namespace DanieleMarotta.RiversongCodeShowcase
{
public enum ProducerState
{
NotWorking,
Working
}
}

View File

@@ -0,0 +1,36 @@
using System;
using Sirenix.OdinInspector;
using Unity.Properties;
using UnityEngine;
namespace DanieleMarotta.RiversongCodeShowcase
{
public interface IProductAmount
{
public ProductDefinition Product { get; set; }
public int Amount { get; set; }
}
[Serializable]
public class ProductAmountAuthoring : IProductAmount
{
[field: SerializeField]
[field: HorizontalGroup]
[field: HideLabel]
[CreateProperty]
public ProductDefinition Product { get; set; }
[field: SerializeField]
[field: HorizontalGroup(50)]
[field: HideLabel]
[CreateProperty]
public int Amount { get; set; }
public void Deconstruct(out ProductDefinition product, out int amount)
{
product = Product;
amount = Amount;
}
}
}

View File

@@ -0,0 +1,45 @@
using System.Collections.Generic;
using Cysharp.Threading.Tasks;
namespace DanieleMarotta.RiversongCodeShowcase
{
[Service(typeof(IProductCatalog))]
[GameSystemGroup(typeof(EarlyGameSystemGroup))]
[InitializeAfter(typeof(GameDatabaseSystem))]
public class ProductCatalogSystem : GameSystem, IInitializable, IProductCatalog
{
[InjectService]
private IGameDatabase _gameDatabase;
private Dictionary<int, int> _handles = new();
private List<ProductDefinition> _products = new();
public ProductCatalogSystem(IServiceLocator serviceLocator) : base(serviceLocator)
{
}
public int ProductTypeCount => _handles.Count;
public UniTask InitializeAsync()
{
foreach (var product in _gameDatabase.OfType<ProductDefinition>())
{
_handles[product.RuntimeId] = _products.Count;
_products.Add(product);
}
return UniTask.CompletedTask;
}
public int GetHandle(ProductDefinition product)
{
return _handles[product.RuntimeId];
}
public ProductDefinition GetProduct(int productHandle)
{
return productHandle != IProductCatalog.InvalidHandle ? _products[productHandle] : null;
}
}
}

View File

@@ -0,0 +1,19 @@
using Sirenix.OdinInspector;
using UnityEngine;
using UnityEngine.AddressableAssets;
namespace DanieleMarotta.RiversongCodeShowcase
{
[CreateAssetMenu(fileName = "ProductDefinition", menuName = "Riversong Code Showcase/Product Definition")]
public class ProductDefinition : GameDataAsset
{
public string ProductName;
[PreviewField]
public Sprite Icon;
public AssetReferenceGameObject ProductStackVisualization;
public AssetReferenceGameObject CarriedVisualization;
}
}

View File

@@ -0,0 +1,27 @@
using Unity.Mathematics;
namespace DanieleMarotta.RiversongCodeShowcase
{
public class ProductStack : Entity, IProductStorageEntity
{
private ProductStorage _storage;
private ProductStoragePolicyState _productStoragePolicy;
public int2 Tile { get; set; }
public ProductDefinition Product { get; set; }
public int ProductHandle { get; set; }
public ref ProductStorage GetStorageRW()
{
return ref _storage;
}
public ref ProductStoragePolicyState GetProductStoragePolicyRW()
{
return ref _productStoragePolicy;
}
}
}

View File

@@ -0,0 +1,216 @@
using System;
using System.Collections.Generic;
using Cysharp.Threading.Tasks;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Pool;
using Object = UnityEngine.Object;
using Random = UnityEngine.Random;
namespace DanieleMarotta.RiversongCodeShowcase
{
[Service(typeof(IProductStackFactory))]
[Service(typeof(IProductStackVisualizationCollection))]
public class ProductStackManager : GameSystem, IInitializable, IDisposable, IUpdatable, IProductStackFactory, IProductStackVisualizationCollection
{
private const int ProductStackCapacity = 10000;
[InjectService]
private IEntityCollection _entityCollection;
[InjectService]
private ITileSpace _tileSpace;
[InjectService]
private IScene _scene;
[InjectService]
private World _world;
[InjectService]
private IProductCatalog _productCatalog;
[InjectService]
private IProductStorageManager _productStorageManager;
[InjectService]
private ISignalBus _signalBus;
private Dictionary<int, GameObject> _visualizations = new();
public ProductStackManager(IServiceLocator serviceLocator) : base(serviceLocator)
{
}
public UniTask InitializeAsync()
{
_signalBus.Subscribe<CollectDeletedGameObjectsSignal>(OnCollectDeletedGameObjects);
_signalBus.Subscribe<DoDeleteToolSignal>(OnDoDeleteTool);
_signalBus.Subscribe<BuildingPlacementAnimationStartedSignal>(OnBuildingPlacementAnimationStarted);
return UniTask.CompletedTask;
}
public void Dispose()
{
_signalBus.Unsubscribe<CollectDeletedGameObjectsSignal>(OnCollectDeletedGameObjects);
_signalBus.Unsubscribe<DoDeleteToolSignal>(OnDoDeleteTool);
_signalBus.Unsubscribe<BuildingPlacementAnimationStartedSignal>(OnBuildingPlacementAnimationStarted);
}
public ProductStack Create(int2 tile, ProductDefinition product, int amount)
{
var productStack = _entityCollection.CreateAndAdd<ProductStack>();
productStack.Tile = tile;
productStack.Product = product;
productStack.ProductHandle = _productCatalog.GetHandle(product);
_world.ProductStacks.AddProductStack(productStack);
_world.BlockMap.AddReason(tile, BlockReason.ProductStack);
ref var storage = ref _productStorageManager.InitializeProductStorage(productStack, ProductStackCapacity);
storage.Put(productStack.ProductHandle, math.min(amount, ProductStackCapacity));
CreateVisualization(productStack);
return productStack;
}
public ProductStack CreateOrMerge(int2 tile, ProductDefinition product, int amount)
{
if (_world.ProductStacks.At(tile, out var productStack) && productStack.Product == product)
{
ref var storage = ref productStack.GetStorageRW();
storage.Put(productStack.ProductHandle, amount);
return productStack;
}
return Create(tile, product, amount);
}
public void CreateProductStacksFrom(ConstructionSite constructionSite)
{
ref var storage = ref constructionSite.GetStorageRW();
using var tiles = TileRange.From(constructionSite.Rect).GetEnumerator();
foreach (var (productHandle, amount) in storage)
{
if (!tiles.MoveNext())
{
Debug.LogError("Could not find tile for product stack");
break;
}
var tile = tiles.Current;
var product = _productCatalog.GetProduct(productHandle);
_ = CreateDelayedAsync(tile, product, amount);
}
}
public void CreateProductStacksFrom(Building building)
{
using var productsScope = DictionaryPool<int, int>.Get(out var products);
ref var storage = ref building.GetStorageRW();
using var tiles = TileRange.From(building.Rect).GetEnumerator();
foreach (var (product, amount) in building.Definition.BuildingMaterials)
{
var productHandle = _productCatalog.GetHandle(product);
products.TryGetValue(productHandle, out var current);
products[productHandle] = current + amount;
}
foreach (var (productHandle, amount) in storage)
{
products.TryGetValue(productHandle, out var current);
products[productHandle] = current + amount;
}
foreach (var (productHandle, amount) in products)
{
if (!tiles.MoveNext())
{
Debug.LogError("Could not find tile for product stack");
break;
}
var tile = tiles.Current;
_ = CreateDelayedAsync(tile, _productCatalog.GetProduct(productHandle), amount);
}
}
private async UniTask<ProductStack> CreateDelayedAsync(int2 tile, ProductDefinition product, int amount)
{
await UniTask.NextFrame();
return Create(tile, product, amount);
}
private void CreateVisualization(ProductStack productStack)
{
var prefab = (GameObject)productStack.Product.ProductStackVisualization.Asset;
var position = _tileSpace.TileToWorld(productStack.Tile);
var rotation = Quaternion.Euler(0, Random.Range(0, 8) * 45, 0);
var folder = _scene.SceneFolders.ProductStacks;
var visualization = Object.Instantiate(prefab, position, rotation, folder);
_visualizations.Add(productStack.Id, visualization);
}
public bool TryGetVisualization(int id, out GameObject visualization)
{
return _visualizations.TryGetValue(id, out visualization);
}
public void Update()
{
using var deSpawnScope = ListPool<ProductStack>.Get(out var deSpawn);
foreach (ProductStack productStack in _entityCollection.GetInternalEntityList(typeof(ProductStack)))
{
ref var storage = ref productStack.GetStorageRW();
if (storage.TotalCount <= 0) deSpawn.Add(productStack);
}
foreach (var productStack in deSpawn) DeSpawn(productStack);
}
private void OnCollectDeletedGameObjects(CollectDeletedGameObjectsSignal signal)
{
if ((signal.Filter & DeletedGameObjectsFilter.ProductStacks) == 0) return;
using var productStacksScope = ListPool<ProductStack>.Get(out var productStacks);
_world.ProductStacks.Find(signal.Rect, productStacks);
foreach (var productStack in productStacks) signal.GameObjects.Add(_visualizations[productStack.Id]);
}
private void OnDoDeleteTool(DoDeleteToolSignal signal)
{
DeSpawn(signal.Rect);
}
private void OnBuildingPlacementAnimationStarted(BuildingPlacementAnimationStartedSignal signal)
{
DeSpawn(signal.Rect);
}
private void DeSpawn(TileRect rect)
{
using var productStacksScope = ListPool<ProductStack>.Get(out var productStacks);
_world.ProductStacks.Find(rect, productStacks);
foreach (var productStack in productStacks) DeSpawn(productStack);
}
private void DeSpawn(ProductStack productStack)
{
_entityCollection.Remove(productStack.Id);
_world.ProductStacks.RemoveProductStack(productStack);
_world.BlockMap.RemoveReason(productStack.Tile, BlockReason.ProductStack);
_visualizations.Remove(productStack.Id, out var visualization);
Object.Destroy(visualization);
}
}
}

View File

@@ -0,0 +1,113 @@
using System;
using System.Collections.Generic;
using Unity.Mathematics;
using UnityEngine.Pool;
namespace DanieleMarotta.RiversongCodeShowcase
{
public class ProductStacksState
{
private const int SpatialLookupSize = 5;
private SpatialLookup<ProductStack> _spatialLookup = new(SpatialLookupSize);
private Func<ProductStack, int> _scoreExplicitProductFunc;
private Func<ProductStack, int> _scoreAllowedByPolicyFunc;
private ScoreFuncContext _scoreFuncContext;
public void AddProductStack(ProductStack productStack)
{
_spatialLookup.Add(productStack, TileRect.OneTile(productStack.Tile), productStack.Id);
}
public void RemoveProductStack(ProductStack productStack)
{
_spatialLookup.Remove(TileRect.OneTile(productStack.Tile), productStack.Id);
}
public bool At(int2 tile, out ProductStack productStack)
{
using var resultScope = ListPool<ProductStack>.Get(out var result);
_spatialLookup.Find(TileRect.OneTile(tile), result);
productStack = result.Count > 0 ? result[0] : null;
return productStack != null;
}
public void Find(TileRect rect, List<ProductStack> productStacks)
{
_spatialLookup.Find(rect, productStacks);
}
public bool FindClosest(TileRect sourceRect, int range, int productHandle, int amount, out ProductStack closest)
{
_scoreExplicitProductFunc ??= ScoreExplicitProduct;
_scoreFuncContext = new ScoreFuncContext(sourceRect, productHandle, amount);
return _spatialLookup.FindMax(sourceRect.Inflate(range), _scoreExplicitProductFunc, out closest);
}
public bool FindClosest(TileRect sourceRect, int range, int amount, in ProductStoragePolicyState storagePolicy, out ProductStack closest)
{
_scoreAllowedByPolicyFunc ??= ScoreAllowedByPolicy;
_scoreFuncContext = new ScoreFuncContext(sourceRect, amount, storagePolicy);
return _spatialLookup.FindMax(sourceRect.Inflate(range), _scoreAllowedByPolicyFunc, out closest);
}
public bool FindClosest(TileRect sourceTile, int range, out ProductStack closest)
{
return FindClosest(sourceTile, range, IProductCatalog.InvalidHandle, 1, out closest);
}
private int ScoreExplicitProduct(ProductStack productStack)
{
if (_scoreFuncContext.ProductHandle != IProductCatalog.InvalidHandle && _scoreFuncContext.ProductHandle != productStack.ProductHandle) return int.MinValue;
return Score(productStack);
}
private int ScoreAllowedByPolicy(ProductStack productStack)
{
if (!_scoreFuncContext.StoragePolicy.IsTakingAllowed(productStack.ProductHandle)) return int.MinValue;
return Score(productStack);
}
private int Score(ProductStack productStack)
{
ref var storage = ref productStack.GetStorageRW();
if (storage.AvailableNow(productStack.ProductHandle) < _scoreFuncContext.Amount) return int.MinValue;
return -TileMath.StepCount(_scoreFuncContext.Rect, productStack.Tile);
}
private struct ScoreFuncContext
{
public TileRect Rect;
public int ProductHandle;
public int Amount;
public ProductStoragePolicyState StoragePolicy;
public ScoreFuncContext(TileRect rect, int productHandle, int amount)
{
Rect = rect;
ProductHandle = productHandle;
Amount = amount;
StoragePolicy = default;
}
public ScoreFuncContext(TileRect rect, int amount, in ProductStoragePolicyState storagePolicy)
{
Rect = rect;
ProductHandle = IProductCatalog.InvalidHandle;
Amount = amount;
StoragePolicy = storagePolicy;
}
}
}
}

View File

@@ -0,0 +1,14 @@
using System;
namespace DanieleMarotta.RiversongCodeShowcase
{
[Flags]
public enum ProductionRequirements
{
None = 0,
InputsAvailable = 1,
All = InputsAvailable
}
}

View File

@@ -0,0 +1,33 @@
namespace DanieleMarotta.RiversongCodeShowcase
{
[GameSystemGroup(typeof(EconomySystemGroup))]
public class ProductionRequirementsGameSystem : GameSystem, IUpdatable
{
[InjectService]
private IEntityCache _entityCache;
public ProductionRequirementsGameSystem(IServiceLocator serviceLocator) : base(serviceLocator)
{
}
public void Update()
{
foreach (var producer in _entityCache.GetProducers())
{
ref var productionState = ref producer.GetProductionStateRW();
ref var recipe = ref productionState.Recipe;
ref var storage = ref producer.GetStorageRW();
productionState.MetRequirements = ProductionRequirements.All;
foreach (var (product, amount) in recipe.Inputs)
if (storage.AvailableNow(product) < amount)
{
productionState.MetRequirements &= ~ProductionRequirements.InputsAvailable;
break;
}
}
}
}
}

View File

@@ -0,0 +1,66 @@
using UnityEngine;
namespace DanieleMarotta.RiversongCodeShowcase
{
[GameSystemGroup(typeof(EconomySystemGroup))]
[UpdateAfter(typeof(ProductionRequirementsGameSystem))]
public class ProductionTickGameSystem : GameSystem, IUpdatable
{
[InjectService]
private GameConfig _config;
[InjectService]
private World _world;
[InjectService]
private IEntityCache _entityCache;
public ProductionTickGameSystem(IServiceLocator serviceLocator) : base(serviceLocator)
{
}
public void Update()
{
if (_world.TimeState.DayNightCycleStep != DayNightCycleStep.Day) return;
var state = _world.ProductionState;
state.ProductionTickTimer += Time.deltaTime;
if (state.ProductionTickTimer < _config.Economy.ProductionTickInterval) return;
state.ProductionTickTimer = 0;
ProductionTick();
}
private void ProductionTick()
{
var laborEfficiencyModifier = _world.ProductionState.LaborEfficiencyModifier;
foreach (var producer in _entityCache.GetProducers())
{
ref var storage = ref producer.GetStorageRW();
ref var productionState = ref producer.GetProductionStateRW();
ref var recipe = ref productionState.Recipe;
if (productionState.State == ProducerState.NotWorking)
{
if (!productionState.AllRequirementsMet || !storage.ReservePutting(recipe.OutputProductHandle, recipe.OutputAmount)) continue;
foreach (var (productHandle, amount) in recipe.Inputs) storage.Take(productHandle, amount);
productionState.State = ProducerState.Working;
}
ref var sleepState = ref producer.GetSleepStateRW();
var efficiencyMultiplier = Mathf.Max(0, 1 + sleepState.EfficiencyModifier + laborEfficiencyModifier);
productionState.Progress += Mathf.RoundToInt(1000 * efficiencyMultiplier);
if (productionState.Progress < 1000 * recipe.ProductionTime) continue;
productionState.Progress = 0;
if (!storage.FulfillPut(recipe.OutputProductHandle, recipe.OutputAmount)) Debug.LogError("Producer failed fulfilling put");
productionState.State = ProducerState.NotWorking;
}
}
}
}

View File

@@ -0,0 +1,15 @@
using Unity.Collections;
namespace DanieleMarotta.RiversongCodeShowcase
{
public struct Recipe
{
public FixedList32Bytes<(int, int)> Inputs;
public int OutputProductHandle;
public int OutputAmount;
public int ProductionTime;
}
}

View File

@@ -0,0 +1,15 @@
using System.Collections.Generic;
using UnityEngine;
namespace DanieleMarotta.RiversongCodeShowcase
{
[CreateAssetMenu(fileName = "RecipeDefinition", menuName = "Riversong Code Showcase/Recipe Definition")]
public class RecipeDefinition : GameDataAsset
{
public List<ProductAmountAuthoring> Inputs;
public ProductAmountAuthoring Output;
public int ProductionTime;
}
}

View File

@@ -0,0 +1,154 @@
using System;
using System.Collections.Generic;
using Cysharp.Threading.Tasks;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Pool;
using Random = UnityEngine.Random;
namespace DanieleMarotta.RiversongCodeShowcase
{
[GameSystemGroup(typeof(EconomySystemGroup))]
public class RestedWorkersSystem : GameSystem, IInitializable, IDisposable
{
[InjectService]
private ISignalBus _signalBus;
[InjectService]
private GameConfig _config;
[InjectService]
private IEntityCache _entityCache;
[InjectService]
private IBuildingFactory _buildingFactory;
[InjectService]
private IEditToolValidatorService _editToolValidatorService;
public RestedWorkersSystem(IServiceLocator serviceLocator) : base(serviceLocator)
{
}
public UniTask InitializeAsync()
{
_signalBus.Subscribe<NightStartedSignal>(OnNightStarted);
_signalBus.Subscribe<DayStartedSignal>(OnDayStarted);
return UniTask.CompletedTask;
}
public void Dispose()
{
_signalBus.Unsubscribe<NightStartedSignal>(OnNightStarted);
_signalBus.Unsubscribe<DayStartedSignal>(OnDayStarted);
}
private void OnNightStarted(NightStartedSignal signal)
{
foreach (var building in _entityCache.GetBuildingsWithWorkers())
{
ref var sleepState = ref building.GetSleepStateRW();
sleepState.RestedWorkerCount = 0;
sleepState.HasHomelessWorkers = false;
while (sleepState.RestedWorkerCount < building.Definition.WorkerCount)
{
if (!TryFindClosestFreeHouse(building, out var house)) break;
ref var houseSleepState = ref house.GetSleepStateRW();
var remainingToAllocate = building.Definition.WorkerCount - sleepState.RestedWorkerCount;
var remainingCapacity = house.PopulationCapacity - houseSleepState.AllocatedWorkerCount;
var allocatedWorkerCount = Mathf.Min(remainingToAllocate, remainingCapacity);
sleepState.RestedWorkerCount += allocatedWorkerCount;
houseSleepState.AllocatedWorkerCount += allocatedWorkerCount;
}
sleepState.HasHomelessWorkers = sleepState.RestedWorkerCount < building.Definition.WorkerCount;
if (sleepState.HasHomelessWorkers) TrySpawnTent(building);
}
}
private bool TryFindClosestFreeHouse(Building source, out Building house)
{
house = null;
var best = int.MaxValue;
foreach (var candidate in _entityCache.GetHouses())
{
ref var sleepState = ref candidate.GetSleepStateRW();
if (sleepState.AllocatedWorkerCount >= candidate.PopulationCapacity) continue;
var stepCount = TileMath.StepCount(source.Rect, candidate.Rect);
if (stepCount > _config.Economy.RestedWorkersHouseMaxStepCount || stepCount >= best) continue;
best = stepCount;
house = candidate;
}
return house != null;
}
private void TrySpawnTent(Building source)
{
if (!TryFindTentTile(source, out var tile)) return;
var definition = (BuildingDefinition)_config.Population.TentBuilding.Asset;
_buildingFactory.Create(definition, TileRect.OneTile(tile), (Directions)(2 * Random.Range(0, 4)));
}
private bool TryFindTentTile(Building source, out int2 tile)
{
using var candidatesScope = ListPool<int2>.Get(out var candidates);
var min = source.Rect.Min - 1;
var max = source.Rect.Max + 1;
for (var x = min.x; x <= max.x; x++)
{
Validate(new int2(x, min.y), candidates);
Validate(new int2(x, max.y), candidates);
}
for (var y = min.y + 1; y <= max.y - 1; y++)
{
Validate(new int2(min.x, y), candidates);
Validate(new int2(max.x, y), candidates);
}
tile = candidates.Count > 0 ? candidates[Random.Range(0, candidates.Count)] : int2.zero;
return candidates.Count > 0;
void Validate(int2 tile, List<int2> l)
{
var rect = TileRect.OneTile(tile);
var validationResult = _editToolValidatorService.DoCommonValidation(rect, BlockReason.CannotMakeCamp);
if (validationResult == EditToolValidationResult.Success) l.Add(tile);
}
}
private void OnDayStarted(DayStartedSignal signal)
{
var efficiencyModifier = _config.Economy.RestedWorkersEfficiencyModifier;
foreach (var building in _entityCache.GetBuildingsWithWorkers())
{
ref var sleepState = ref building.GetSleepStateRW();
sleepState.EfficiencyModifier = efficiencyModifier * ((float)sleepState.RestedWorkerCount / building.Definition.WorkerCount);
sleepState.RestedWorkerCount = 0;
}
foreach (var house in _entityCache.GetHouses())
{
ref var sleepState = ref house.GetSleepStateRW();
sleepState.AllocatedWorkerCount = 0;
}
}
}
}

View File

@@ -0,0 +1,11 @@
using System.Collections.Generic;
namespace DanieleMarotta.RiversongCodeShowcase
{
public interface IProductStorageCommonLogic
{
bool ContainsAll(in ProductStorage storage, List<ProductAmountAuthoring> products);
bool TakeAllOrNothing(ref ProductStorage storage, List<ProductAmountAuthoring> products);
}
}

View File

@@ -0,0 +1,9 @@
namespace DanieleMarotta.RiversongCodeShowcase
{
public interface IProductStorageEntity : IEntity
{
ref ProductStorage GetStorageRW();
ref ProductStoragePolicyState GetProductStoragePolicyRW();
}
}

View File

@@ -0,0 +1,9 @@
namespace DanieleMarotta.RiversongCodeShowcase
{
public interface IProductStorageManager
{
ref ProductStorage InitializeProductStorage(IProductStorageEntity entity, int capacity);
bool TryGetProductStorageEntity(int id, out IProductStorageEntity entity);
}
}

View File

@@ -0,0 +1,185 @@
using Unity.Collections;
using Unity.Mathematics;
namespace DanieleMarotta.RiversongCodeShowcase
{
public unsafe struct ProductStorage
{
private FixedList512Bytes<Slot> _slots;
public int TotalCount;
public int TotalPutReservations;
public int TotalTakeReservations;
public int Capacity;
public readonly void Get(int productHandle, out int count, out int putReservations, out int takeReservations)
{
var slot = _slots[productHandle];
count = slot.Count;
putReservations = slot.PutReservations;
takeReservations = slot.TakeReservations;
}
public readonly int AvailableNow(int productHandle)
{
var slot = _slots[productHandle];
return slot.Count - slot.TakeReservations;
}
public readonly int CountIncludingReservations(int productHandle)
{
var slot = _slots[productHandle];
return slot.Count + slot.PutReservations - slot.TakeReservations;
}
public readonly int FreeSpace()
{
return Capacity - TotalCount - TotalPutReservations;
}
public bool Put(int productHandle, int amount)
{
if (amount > Capacity - TotalCount) return false;
var slot = _slots[productHandle];
slot.Count += (ushort)amount;
_slots[productHandle] = slot;
TotalCount += amount;
return true;
}
public bool Take(int productHandle, int amount)
{
var slot = _slots[productHandle];
if (amount > slot.Count) return false;
slot.Count -= (ushort)amount;
_slots[productHandle] = slot;
TotalCount -= amount;
return true;
}
public bool ReservePutting(int productHandle, int amount)
{
if (amount > FreeSpace()) return false;
var slot = _slots[productHandle];
slot.PutReservations += (ushort)amount;
_slots[productHandle] = slot;
TotalPutReservations += amount;
return true;
}
public bool ReleasePutReservation(int productHandle, int amount)
{
var slot = _slots[productHandle];
if (amount > slot.PutReservations) return false;
slot.PutReservations -= (ushort)amount;
_slots[productHandle] = slot;
TotalPutReservations -= amount;
return true;
}
public bool ReserveTaking(int productHandle, int amount)
{
if (amount > AvailableNow(productHandle)) return false;
var slot = _slots[productHandle];
slot.TakeReservations += (ushort)amount;
_slots[productHandle] = slot;
TotalTakeReservations += amount;
return true;
}
public bool ReleaseTakeReservation(int productHandle, int amount)
{
var slot = _slots[productHandle];
if (amount > slot.TakeReservations) return false;
amount = math.min(amount, slot.TakeReservations);
slot.TakeReservations -= (ushort)amount;
_slots[productHandle] = slot;
TotalTakeReservations -= amount;
return true;
}
public bool FulfillPut(int productHandle, int amount)
{
return ReleasePutReservation(productHandle, amount) && Put(productHandle, amount);
}
public bool FulfillTake(int productHandle, int amount)
{
return ReleaseTakeReservation(productHandle, amount) && Take(productHandle, amount);
}
public static ProductStorage Create(int slotCount, int capacity)
{
var result = new ProductStorage { Capacity = capacity };
result._slots.Length = slotCount;
return result;
}
private struct Slot
{
public ushort Count;
public ushort PutReservations;
public ushort TakeReservations;
}
public struct Enumerator
{
private ProductStorage* _storage;
private int _productHandle;
internal Enumerator(ProductStorage* storage)
{
_storage = storage;
_productHandle = -1;
}
public bool MoveNext()
{
while (++_productHandle < _storage->_slots.Length)
{
var slot = _storage->_slots[_productHandle];
if (slot.Count > 0) return true;
}
return false;
}
public (int, int) Current
{
get
{
var slot = _storage->_slots[_productHandle];
return (_productHandle, slot.Count);
}
}
}
public Enumerator GetEnumerator()
{
fixed (ProductStorage* ptr = &this)
{
return new Enumerator(ptr);
}
}
}
}

View File

@@ -0,0 +1,80 @@
using System;
using System.Collections.Generic;
using Cysharp.Threading.Tasks;
namespace DanieleMarotta.RiversongCodeShowcase
{
[Service(typeof(IProductStorageManager))]
[Service(typeof(IProductStorageCommonLogic))]
public class ProductStorageManager : GameSystem, IInitializable, IDisposable, IProductStorageManager, IProductStorageCommonLogic
{
[InjectService]
private IProductCatalog _productCatalog;
[InjectService]
private IEntityCollection _entityCollection;
private Dictionary<int, IProductStorageEntity> _productStorageEntities = new();
public ProductStorageManager(IServiceLocator serviceLocator) : base(serviceLocator)
{
}
public UniTask InitializeAsync()
{
_entityCollection.On<Entity>().Removed += OnEntityRemoved;
return UniTask.CompletedTask;
}
public void Dispose()
{
_entityCollection.On<Entity>().Removed -= OnEntityRemoved;
}
public ref ProductStorage InitializeProductStorage(IProductStorageEntity entity, int capacity)
{
ref var storage = ref entity.GetStorageRW();
storage = ProductStorage.Create(_productCatalog.ProductTypeCount, capacity);
ref var productStoragePolicy = ref entity.GetProductStoragePolicyRW();
productStoragePolicy = ProductStoragePolicyState.Create(_productCatalog.ProductTypeCount);
_productStorageEntities.Add(entity.Id, entity);
return ref storage;
}
public bool TryGetProductStorageEntity(int id, out IProductStorageEntity entity)
{
return _productStorageEntities.TryGetValue(id, out entity);
}
private void OnEntityRemoved(Entity entity)
{
_productStorageEntities.Remove(entity.Id);
}
public bool ContainsAll(in ProductStorage storage, List<ProductAmountAuthoring> products)
{
foreach (var (product, amount) in products)
{
var productHandle = _productCatalog.GetHandle(product);
if (storage.AvailableNow(productHandle) < amount) return false;
}
return true;
}
public bool TakeAllOrNothing(ref ProductStorage storage, List<ProductAmountAuthoring> products)
{
if (!ContainsAll(storage, products)) return false;
foreach (var (product, amount) in products)
{
var productHandle = _productCatalog.GetHandle(product);
storage.Take(productHandle, amount);
}
return true;
}
}
}

View File

@@ -0,0 +1,82 @@
using Unity.Collections;
namespace DanieleMarotta.RiversongCodeShowcase
{
public struct ProductStoragePolicyState
{
private FixedList512Bytes<Slot> _slots;
public int NextProductToFetch;
public readonly int Length => _slots.Length;
public readonly void Get(int productHandle, out bool isTakingAllowed, out bool canFulfillRequests, out int requestedAmount)
{
var slot = _slots[productHandle];
isTakingAllowed = slot.IsTakingAllowed;
canFulfillRequests = slot.CanFulfillRequests;
requestedAmount = slot.RequestedAmount;
}
public readonly bool IsTakingAllowed(int productHandle)
{
return _slots[productHandle].IsTakingAllowed;
}
public void SetAllowTaking(int productHandle, bool value)
{
var slot = _slots[productHandle];
slot.IsTakingAllowed = value;
_slots[productHandle] = slot;
}
public readonly bool CanFulfillRequests(int productHandle)
{
return _slots[productHandle].CanFulfillRequests;
}
public void SetCanFulfillRequests(int productHandle, bool value)
{
var slot = _slots[productHandle];
slot.CanFulfillRequests = value;
_slots[productHandle] = slot;
}
public readonly int GetRequestedAmount(int productHandle)
{
return _slots[productHandle].RequestedAmount;
}
public void SetRequestedAmount(int productHandle, int value)
{
var slot = _slots[productHandle];
slot.RequestedAmount = value;
_slots[productHandle] = slot;
}
public static ProductStoragePolicyState Create(int slotCount)
{
var result = new ProductStoragePolicyState { NextProductToFetch = 0 };
result._slots.Length = slotCount;
for (var i = 0; i < slotCount; i++)
result._slots[i] = new Slot
{
IsTakingAllowed = true,
CanFulfillRequests = true
};
return result;
}
private struct Slot
{
public bool IsTakingAllowed;
public bool CanFulfillRequests;
public int RequestedAmount;
}
}
}

View File

@@ -0,0 +1,17 @@
namespace DanieleMarotta.RiversongCodeShowcase
{
public class WorldProductionState
{
public float ProductionTickTimer;
public int LaborSupply;
public int LaborDemand;
public float LaborFulfillmentRatio;
public LaborTier LaborTier;
public float LaborEfficiencyModifier;
}
}