Bases of a Recipe System created, tags compponet, and Tempearue stuct with a temutrae delta.

This commit is contained in:
2026-05-04 00:02:08 -04:00
parent 4a7494ff6b
commit d620189e4e
8 changed files with 1481 additions and 13 deletions

View File

@@ -0,0 +1,289 @@
namespace FoodFactory.Items;
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Numerics;
using System.Threading;
public readonly struct Tags : IEquatable<Tags>, IReadOnlyCollection<Tag>
{
private readonly Tag[] _values;
public Tags()
{
_values = [];
}
/// <summary>
/// Will error if there is not tags in contained.
/// </summary>
public readonly Tag First => _values[0];
public Tags(params string[] tags)
{
var arr = new Tag[tags.Length];
for (var i = 0; i < tags.Length; i++)
{
arr[i] = TagRegistry.GetTag(tags[i]);
}
_values = SortAndDeduplicate(arr);
}
public override string ToString() => $"Tags: [{string.Join(", ", _values.Select(t => t.Name))}]";
private static readonly Dictionary<TagArrayKey, Tag[]> _internedTags = [];
public readonly int Count => _values.Length;
private static Tag[] SortAndDeduplicate(Tag[] tags)
{
Array.Sort(tags);
var key = new TagArrayKey(tags);
if (!_internedTags.TryGetValue(key, out var interned))
{
_internedTags[key] = interned = tags;
}
return interned;
}
public Tags(params Tag[] tags)
{
_values = SortAndDeduplicate(tags);
}
public Tags(ISet<Tag> tags) : this(tags.ToArray())
{
}
public Tags With(params Tag[] tags)
{
HashSet<Tag> set = [.. _values, .. tags];
return new Tags(set);
}
public Tags With(params string[] tags)
{
HashSet<Tag> set = [.. _values, .. TagRegistry.GetTags(tags)];
return new Tags(set);
}
public Tags WithOut(params string[] tags)
{
HashSet<Tag> set = [.. _values];
for (int i = 0; i < tags.Length; i++)
{
set.Remove(TagRegistry.GetTag(tags[i]));
}
return new Tags(set);
}
public readonly bool Contains(Tag tag) => Array.BinarySearch(_values, tag) >= 0;
public readonly bool Equals(Tags other) => _values == other._values;
public override readonly bool Equals([NotNullWhen(true)] object? obj) => obj is Tags tags && Equals(tags);
public override readonly int GetHashCode() => _values.GetHashCode();
public readonly IEnumerator<Tag> GetEnumerator()
{
foreach (var item in _values)
{
yield return item;
}
}
readonly IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public readonly struct TagArrayKey : IEquatable<TagArrayKey>
{
private readonly Tag[] _tags;
public TagArrayKey(Tag[] tags)
{
_tags = tags;
}
public bool Equals(TagArrayKey other)
{
var a = _tags;
var b = other._tags;
if (a == b)
{
return true;
}
if (a == null || b == null)
{
return false;
}
if (a.Length != b.Length)
{
return false;
}
for (var i = 0; i < a.Length; i++)
{
if (a[i] != b[i])
{
return false;
}
}
return true;
}
public override bool Equals(object? obj)
=> obj is TagArrayKey other && Equals(other);
public override int GetHashCode()
{
var hash = new HashCode();
foreach (var tag in _tags)
{
hash.Add(tag._id);
}
return hash.ToHashCode();
}
}
public readonly bool ContainsAll(Tags other)
{
var i = 0;
var j = 0;
while (i < _values.Length && j < other._values.Length)
{
if (_values[i] == other._values[j])
{
i++;
j++;
}
else if (_values[i] < other._values[j])
{
return false;
}
else
{
j++;
}
}
return j == other._values.Length;
}
public readonly bool ContainsAny(Tags other)
{
for (var i = 0; i < _values.Length; i++)
{
if (other.Contains(_values[i]))
{
return true;
}
}
return false;
}
}
public readonly struct Tag : IEquatable<Tag>, IEqualityOperators<Tag, Tag, bool>, IComparisonOperators<Tag, Tag, bool>,
IComparable<Tag>
{
public readonly string Name => TagRegistry.GetName(this);
internal readonly int _id;
internal Tag(int id)
{
Debug.Assert(id >= 0);
_id = id;
}
public override bool Equals([NotNullWhen(true)] object? obj) => obj is Tag tag && Equals(tag);
public bool Equals(Tag other) => _id == other._id;
public override int GetHashCode() => _id;
public override string ToString() => Name;
public int CompareTo(Tag other) => _id.CompareTo(other._id);
public static bool operator >(Tag left, Tag right) => left._id > right._id;
public static bool operator >=(Tag left, Tag right) => left._id >= right._id;
public static bool operator <(Tag left, Tag right) => left._id < right._id;
public static bool operator <=(Tag left, Tag right) => left._id <= right._id;
public static bool operator ==(Tag left, Tag right) => left._id == right._id;
public static bool operator !=(Tag left, Tag right) => left._id != right._id;
}
public static class TagRegistry
{
private static Registry<string> _registry = new();
// private static readonly ConcurrentDictionary<string, Tag> _stringTags = [];
// private static readonly List<string> _tagsStrings = [];
public static Tag GetTag(string name) => new(_registry.GetOrCreate(name));
// {
// name = name.ToLowerInvariant();
// if (_stringTags.TryGetValue(name, out var tag))
// {
// return tag;
// }
// return CreateNewTag(name);
// }
public static string GetName(Tag tag) => _registry.GetEntry(tag._id);//=> tag._id < _tagsStrings.Count ? _tagsStrings[tag._id] : throw new IndexOutOfRangeException($"{nameof(tag)} was somehow created with an value greater then the count of {nameof(_tagsStrings)}. {nameof(tag)}:{tag._id} {nameof(_tagsStrings.Count)}:{_tagsStrings.Count}");
public static Tag[] GetTags(string[] names)
{
var array = new Tag[names.Length];
for (int i = 0; i < names.Length; i++)
{
array[i] = GetTag(names[i]);
}
return array;
}
// private static Tag CreateNewTag(string name)
// {
// Tag tag;
// lock (_tagsStrings)
// {
// tag = new Tag(_tagsStrings.Count);
// _tagsStrings.Add(name);
// }
// _stringTags[name] = tag;
// return tag;
// }
}
public class Registry<TEntry> where TEntry : notnull
{
private readonly Dictionary<TEntry, int> _data = [];
private readonly List<TEntry> _list = [];
private readonly object _lock = new();
public IReadOnlyList<TEntry> Entries => _list;
public void Clear()
{
lock (_lock)
{
_data.Clear();
_list.Clear();
}
}
public int GetOrCreate(TEntry name)
{
lock (_lock)
{
if (_data.TryGetValue(name, out var tag))
{
return tag;
}
return CreateNewKey(name);
}
}
public TEntry GetEntry(int index)
{
lock (_lock)
{
if ((uint)index >= (uint)_list.Count)
{
throw new KeyNotFoundException($"The index {index} you are trying to access does not exist in the registry. The registry only has {_list.Count} of type {typeof(TEntry).Name}s");
}
return _list[index];
}
}
private int CreateNewKey(TEntry name)
{
int index = _list.Count;
_list.Add(name);
_data[name] = index;
return index;
}
}

View File

219
src/Items/ItemECSTest.cs Normal file
View File

@@ -0,0 +1,219 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Arch.Core;
using Arch.Core.Extensions;
using Arch.LowLevel.Jagged;
using FoodFactory;
using FoodFactory.Items;
using FoodFactory.Recipes;
using Godot;
public class Test
{
public void TestECS()
{
using var world = World.Create();
var bucket = new BucketStorage<List<Entity>>();
var pizza = world.Create(new Name("Pizza"), new Temperature(5f), new MarcoNutrients(5, 5, 10), new MicroNutrients([new Vitamin("D", 5)]));
var turkey = world.Create(new Name("Turkey"), new Temperature(5f), new MarcoNutrients(5, 5, 10));
// var componentType = new ComponentType(ComponentRegistry.Size-1, 8);
// ComponentRegistry.Add(typeof(LayeredStack2), componentType);
var query = new QueryDescription().WithAll<Name, Temperature, MarcoNutrients>();
world.Query(in query, (Entity entity, ref Name name, ref Temperature temperature) =>
{
temperature.Kelvin += .1f;
GD.Print(temperature);
});
if (pizza.Has<Temperature>())
{
pizza.Get<Temperature>().Kelvin += .1f;
}
pizza.Add(new IngredientOf(turkey));
var a = new List<Entity>(){pizza};
turkey.Add(new LayeredStack(bucket.Add(a)));
turkey.Add(new LayeredStack2(a));
var list = bucket.GetRef(turkey.Get<LayeredStack>().Handle);
GD.Print(string.Join(',', list));
var list2 = turkey.Get<LayeredStack2>().Children;
turkey.Get<LayeredStack2>().Children.Add(turkey);
GD.Print(string.Join(',', list2));
var r = pizza.Get(typeof(Name));
ComponentRegistry.TryGet<LayeredStack2>(out var type);
GD.Print(type);
var flour = world.Create(new Name("flour"), new Temperature(71, TemperatureUnit.Fahrenheit), new Tags("flour", "wheat"));
var tag2 = new Tags();
world.SubscribeEntityDestroyed((in entity) =>
{
foreach (var item in entity.GetAllComponents())
{
if (item is IEntityContainer container)
{
foreach (var item2 in container.GetEntities())
{
world.Destroy(item2);
}
}
}
}
);
var recipes = new Recipes();
var item = recipes.TESTBlueprint.Factory(new BlueprintContext(){World = world, BluePrintId = new BlueprintId(recipes.TESTBlueprint)});//world.Create(new Name("Potato_Raw"), new Temperature(71f), new Tags("raw", "potato", "vegetable"));
Span<Entity> items = [item];
var context = new RecipeContext(world, items);
var builder = new RecipeResultBuilder(stackalloc bool[10], new ItemBuilder[10]);
// var recipe = new PotatoCookRecipe();
var recipe = recipes.GetRecipes(new RecipeAction("cook"), 1, [item.Get<Tags>()], [])[0].Recipe;
if (recipe.CanProcess(context))
{
var result = recipe.Process(context, ref builder);
var list3 = new List<Entity>();
for (int i = 0; i < result.CreateLength; i++)
{
list3.Add(result.Create[i](in context));
}
if (result.RemoveLength > 0)
{
for (int i = 0; i < result.RemoveLength; i++)
{
GD.Print(result.Remove[i]);
if (result.Remove[i])
{
world.Destroy(items[i]);
}
}
}
GD.Print(string.Join(',',list3[0].GetAllComponents()));
GD.Print(items[0].IsAlive());
}
}
}
internal interface IEntityContainer
{
IEnumerable<Entity> GetEntities();
}
public record struct Name(string Value);
public record struct MarcoNutrients(float Fat, float Protein, float Carbohydrate);
public record struct MicroNutrients(List<Vitamin> Vitamins);
public record struct Vitamin(string Id, float Amount);
public record struct IngredientOf(Entity Source);
public record struct LayeredStack(Handle Handle);
public record struct LayeredStack2(List<Entity> Children);
public record struct Handle(int Index, int Version);
public class BucketStorage<T>
{
private struct Slot
{
public T Value;
public int Version;
public bool Occupied;
}
private Slot[] _slots = new Slot[1];
private Stack<int> _freeIndices = new();
// Allocate a new slot
public Handle Add(T value)
{
if (_freeIndices.Count > 0)
{
int index = _freeIndices.Pop();
var slot = _slots[index];
slot.Value = value;
slot.Occupied = true;
slot.Version++; // bump version on reuse
_slots[index] = slot;
return new Handle { Index = index, Version = slot.Version };
}
else
{
var slot = new Slot
{
Value = value,
Version = 1,
Occupied = true
};
Array.Resize(ref _slots, _slots.Length + 1);
_slots[^1] = slot;
return new Handle
{
Index = _slots.Length - 1,
Version = slot.Version
};
}
}
// Safe access
public bool TryGet(Handle handle, out T value)
{
if (handle.Index < 0 || handle.Index >= _slots.Length)
{
value = default;
return false;
}
var slot = _slots[handle.Index];
if (!slot.Occupied || slot.Version != handle.Version)
{
value = default;
return false;
}
value = slot.Value;
return true;
}
// Direct (unsafe-ish) access if you trust the handle
public ref T GetRef(Handle handle)
{
return ref _slots[handle.Index].Value;
}
// Remove and recycle index
public bool Remove(Handle handle)
{
if (handle.Index < 0 || handle.Index >= _slots.Length)
return false;
var slot = _slots[handle.Index];
if (!slot.Occupied || slot.Version != handle.Version)
return false;
slot.Occupied = false;
slot.Version++; // invalidate old handles
_slots[handle.Index] = slot;
_freeIndices.Push(handle.Index);
return true;
}
}

49
src/Items/itemtest.jsonc Normal file
View File

@@ -0,0 +1,49 @@
[
{
"name" : "wheat_to_flour",
"description" : "Wheat to Flour",
"inputs" : [{"tag" : "wheat"}],
"actions" : ["mill"],//should be a list of actions, bacily addioanl tags, used for reduceing howm may recipes to look for
"effects" : [
{
"effect_type" : " replace_tag",
"target" : "wheat",
"result" : "flour"
}
]
},
{
"name" : "carmlize",
"description" : "Camalize sugars",
"inputs" : [{"tag" : "meat"},{"tag" : "sugar"}, {"tag": "bread"}],
"actions" : ["heat"],// maybe incude addioanl aip contox for like heat applied, all need to be preset to be valid recipe
"expr" : [
{
"target": "Nutration.Flavors.Camalize",
"condition" : "Temperature.F > 350",// i want to be able to have nested expressions that would branch
"op" :"add",
"expr" : [
"MarcoNutrients.Sugar * delta * someMathForRateOfCamrlization",//exprestion could branch and allow for more complex recipes, or be step and maybe variables
]
},
]
},
{
"name" : "stack",
"description" : "Burned the meat",
"inputs" : [{"tag" : "stackable"}],//realticly could be any item, but allow cetin thigns to stack makes senes
//Also need an way to allow mutple item inputs, like stacking a meat slce on slice of bread
"actions" : ["stack"],// maybe incude addioanl aip contox for like heat applied, all need to be preset to be valid recipe
"expr" : [
{
"target": "Nutration.Flavors.Camalize",
"condition" : "Temperature.F > 350",// i want to be able to have nested expressions that would branch
"op" :"add",
"expr" : [
"MarcoNutrients.Sugar * delta * someMathForRateOfCamrlization",//exprestion could branch and allow for more complex recipes, or be step and maybe variables
]
},
]
},
]