Bases of a Recipe System created, tags compponet, and Tempearue stuct with a temutrae delta.
This commit is contained in:
289
src/Items/Components/Tags.cs
Normal file
289
src/Items/Components/Tags.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
219
src/Items/ItemECSTest.cs
Normal file
219
src/Items/ItemECSTest.cs
Normal 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
49
src/Items/itemtest.jsonc
Normal 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
|
||||
]
|
||||
},
|
||||
]
|
||||
},
|
||||
|
||||
]
|
||||
Reference in New Issue
Block a user