Bases of a Recipe System created, tags compponet, and Tempearue stuct with a temutrae delta.
This commit is contained in:
@@ -10,6 +10,7 @@
|
||||
<!-- Required for some nuget packages to work -->
|
||||
<!-- godotengine/godot/issues/42271#issuecomment-751423827 -->
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<!-- To show generated files -->
|
||||
<!-- <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles> -->
|
||||
<!--
|
||||
@@ -35,31 +36,34 @@
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Production dependencies go here! -->
|
||||
<PackageReference Include="Chickensoft.GameTools" Version="3.1.6" />
|
||||
<PackageReference Include="SjkScripts" Version="1.0.1" />
|
||||
<PackageReference Include="System.IO.Abstractions" Version="22.1.0" />
|
||||
<PackageReference Include="Arch" Version="2.1.0" />
|
||||
<PackageReference Include="Chickensoft.GameTools" Version="3.1.18" />
|
||||
<PackageReference Include="NCalc.LambdaCompilation" Version="5.12.0" />
|
||||
<PackageReference Include="NCalcSync" Version="5.12.0" />
|
||||
<PackageReference Include="SjkScripts" Version="1.0.17" />
|
||||
<PackageReference Include="System.IO.Abstractions" Version="22.1.1" />
|
||||
<PackageReference Include="EnvironmentAbstractions" Version="5.0.0" />
|
||||
<PackageReference Include="GodotSharp.SourceGenerators" Version="2.6.0" PrivateAssets="all" OutputItemType="analyzer" />
|
||||
<PackageReference Include="Chickensoft.SaveFileBuilder" Version="1.3.54" />
|
||||
<PackageReference Include="Chickensoft.AutoInject" Version="2.9.18" PrivateAssets="all" />
|
||||
<PackageReference Include="Chickensoft.SaveFileBuilder" Version="1.3.66" />
|
||||
<PackageReference Include="Chickensoft.AutoInject" Version="2.13.0" PrivateAssets="all" />
|
||||
<PackageReference Include="Chickensoft.Collections" Version="3.1.4" />
|
||||
<PackageReference Include="Chickensoft.GodotNodeInterfaces" Version="2.4.57" />
|
||||
<PackageReference Include="Chickensoft.GodotNodeInterfaces" Version="3.0.12" />
|
||||
<PackageReference Include="Chickensoft.Introspection" Version="3.0.2" />
|
||||
<PackageReference Include="Chickensoft.Introspection.Generator" Version="3.0.2" PrivateAssets="all" OutputItemType="analyzer" />
|
||||
<PackageReference Include="Chickensoft.Serialization" Version="3.1.0" />
|
||||
<PackageReference Include="Chickensoft.Serialization.Godot" Version="0.8.46" />
|
||||
<PackageReference Include="Chickensoft.Serialization.Godot" Version="0.8.64" />
|
||||
<PackageReference Include="Chickensoft.LogicBlocks" Version="5.20.0" />
|
||||
<PackageReference Include="Chickensoft.LogicBlocks.DiagramGenerator" Version="5.20.0" PrivateAssets="all" OutputItemType="analyzer" />
|
||||
<PackageReference Include="Chickensoft.UMLGenerator" Version="1.1.0" />
|
||||
<PackageReference Include="Chickensoft.Sync" Version="2.2.0" />
|
||||
<PackageReference Include="Chickensoft.UMLGenerator" Version="1.3.1" />
|
||||
<PackageReference Include="Chickensoft.Sync" Version="2.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(RunTests)' == 'true'">
|
||||
<!-- Test dependencies go here! -->
|
||||
<!-- Dependencies added here will not be included in release builds. -->
|
||||
<PackageReference Include="Chickensoft.GoDotTest" Version="2.0.27" />
|
||||
<PackageReference Include="Chickensoft.GoDotTest" Version="2.0.33" />
|
||||
<!-- Used to drive test scenes when testing visual code -->
|
||||
<PackageReference Include="Chickensoft.GodotTestDriver" Version="3.1.56" />
|
||||
<PackageReference Include="Chickensoft.GodotTestDriver" Version="3.1.68" />
|
||||
<!-- Bring your own assertion library for tests! -->
|
||||
<!-- We're using Shouldly for this example, but you can use anything. -->
|
||||
<PackageReference Include="Shouldly" Version="4.3.0" />
|
||||
|
||||
@@ -7,8 +7,8 @@ public partial class Game : Control
|
||||
public Button TestButton { get; private set; } = default!;
|
||||
public int ButtonPresses { get; private set; }
|
||||
|
||||
public override void _Ready()
|
||||
=> TestButton = GetNode<Button>("%TestButton");
|
||||
public override void _Ready()=> new Test().TestECS();
|
||||
// => TestButton = GetNode<Button>("%TestButton");
|
||||
|
||||
public void OnTestButtonPressed() => ButtonPresses++;
|
||||
|
||||
|
||||
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
|
||||
]
|
||||
},
|
||||
]
|
||||
},
|
||||
|
||||
]
|
||||
494
src/Math/Temperature.cs
Normal file
494
src/Math/Temperature.cs
Normal file
@@ -0,0 +1,494 @@
|
||||
//https://www.codeproject.com/Articles/311333/A-Csharp-Temperature-Struct
|
||||
namespace FoodFactory;
|
||||
|
||||
|
||||
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.Numerics;
|
||||
|
||||
/// <summary>
|
||||
/// Options for temperature measurement units.
|
||||
/// </summary>
|
||||
public enum TemperatureUnit
|
||||
{
|
||||
/// <summary>
|
||||
/// The SI base unit of thermodynamic temperature,
|
||||
/// equal in magnitude to the degree Celsius.
|
||||
/// </summary>
|
||||
Kelvin,
|
||||
|
||||
/// <summary>
|
||||
/// A scale of temperature on which water freezes
|
||||
/// at 0° and boils at 100° under standard conditions.
|
||||
/// </summary>
|
||||
Celsius,
|
||||
|
||||
/// <summary>
|
||||
/// A scale of temperature on which water freezes at 32°
|
||||
/// and boils at 212° under standard conditions.
|
||||
/// </summary>
|
||||
Fahrenheit
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A temperature value.
|
||||
/// </summary>
|
||||
|
||||
public struct Temperature : IFormattable, IComparable,
|
||||
IComparable<Temperature>, IEquatable<Temperature>,
|
||||
IAdditionOperators<Temperature, TemperatureDelta, Temperature>,
|
||||
ISubtractionOperators<Temperature, Temperature, TemperatureDelta>,
|
||||
ISubtractionOperators<Temperature, TemperatureDelta, TemperatureDelta>,
|
||||
IEqualityOperators<Temperature, Temperature, bool>,
|
||||
IComparisonOperators<Temperature, Temperature, bool>
|
||||
{
|
||||
private double _kelvin;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new temperature with the specified value in Kelvin.
|
||||
/// </summary>
|
||||
/// <param name="kelvin">The value of the temperature.</param>
|
||||
public Temperature(double kelvin) : this() { _kelvin = kelvin; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new temperature with the specified value in the
|
||||
/// specified unit of measurement.
|
||||
/// </summary>
|
||||
/// <param name="temperature">The value of the temperature.</param>
|
||||
/// <param name="unit">The unit of measurement that defines how
|
||||
/// the <paramref name="temperature"/> value is used.</param>
|
||||
public Temperature(double temperature, TemperatureUnit unit)
|
||||
: this()
|
||||
{
|
||||
switch (unit)
|
||||
{
|
||||
case TemperatureUnit.Kelvin:
|
||||
_kelvin = temperature;
|
||||
break;
|
||||
case TemperatureUnit.Celsius:
|
||||
Celsius = temperature;
|
||||
break;
|
||||
case TemperatureUnit.Fahrenheit:
|
||||
Fahrenheit = temperature;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException(
|
||||
"The temperature unit '" + unit.ToString() + "' is unknown.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the temperature value in Kelvin.
|
||||
/// </summary>
|
||||
public double Kelvin
|
||||
{
|
||||
readonly get => _kelvin;
|
||||
|
||||
set => _kelvin = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the temperature value in Celsius.
|
||||
/// </summary>
|
||||
public double Celsius
|
||||
{
|
||||
readonly get => KelvinToCelsius(_kelvin); set => _kelvin = CelsiusToKelvin(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the temperature value in Fahrenheit.
|
||||
/// </summary>
|
||||
public double Fahrenheit
|
||||
{
|
||||
readonly get => KelvinToFahrenheit(_kelvin); set => _kelvin = FahrenheitToKelvin(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the temperature value in the specified unit of measurement.
|
||||
/// </summary>
|
||||
/// <param name="unit">The unit of measurement
|
||||
/// in which the temperature should be retrieved.</param>
|
||||
/// <returns>The temperature value in the specified
|
||||
/// <paramref name="unit"/>.</returns>
|
||||
public readonly double ValueIn(TemperatureUnit unit) => unit switch
|
||||
{
|
||||
TemperatureUnit.Kelvin => _kelvin,
|
||||
TemperatureUnit.Celsius => Celsius,
|
||||
TemperatureUnit.Fahrenheit => Fahrenheit,
|
||||
_ => throw new ArgumentException(
|
||||
"Unknown temperature unit '" + unit.ToString() + "'."),
|
||||
};
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string representation of the temperature value.
|
||||
/// </summary>
|
||||
/// <param name="format">
|
||||
/// A single format specifier that indicates how to format the value of this
|
||||
/// temperature. The format parameter can be "G",
|
||||
/// "C", "F", or "K". If format
|
||||
/// is null or the empty string (""), "G" is used.
|
||||
/// </param>
|
||||
/// <param name="provider">
|
||||
/// An IFormatProvider reference that supplies culture-specific formatting
|
||||
/// services.
|
||||
/// </param>
|
||||
/// <returns>A string representation of the temperature.</returns>
|
||||
/// <exception cref="FormatException">
|
||||
/// The value of format is not null, the empty string (""), "G", "C", "F", or
|
||||
/// "K".
|
||||
/// </exception>
|
||||
public readonly string ToString(string? format, IFormatProvider? provider)
|
||||
{
|
||||
if (string.IsNullOrEmpty(format))
|
||||
{
|
||||
format = "G";
|
||||
}
|
||||
provider ??= CultureInfo.CurrentCulture;
|
||||
|
||||
return format switch
|
||||
{
|
||||
"G" or "C" or "g" or "c" => Celsius.ToString("F2", provider) + " °C",
|
||||
"F" or "f" => Fahrenheit.ToString("F2", provider) + " °F",
|
||||
"K" or "k" => _kelvin.ToString("F2", provider) + " K",
|
||||
_ => throw new FormatException(
|
||||
string.Format("The {0} format string is not supported.", format)),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string representation of the temperature value.
|
||||
/// </summary>
|
||||
/// <param name="format">
|
||||
/// A single format specifier that indicates how to format the value of this
|
||||
/// temperature. The format parameter can be "G",
|
||||
/// "C", "F", or "K". If format
|
||||
/// is null or the empty string (""), "G" is used.
|
||||
/// </param>
|
||||
/// <returns>A string representation of the temperature.</returns>
|
||||
/// <exception cref="FormatException">
|
||||
/// The value of format is not null,
|
||||
/// the empty string (""), "G", "C", "F", or
|
||||
/// "K".
|
||||
/// </exception>
|
||||
public readonly string ToString(string format) => ToString(format, null);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string representation of the temperature value.
|
||||
/// </summary>
|
||||
/// <returns>A string representation of the temperature.</returns>
|
||||
public override readonly string ToString() => ToString(null, null);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string representation of the temperature value.
|
||||
/// </summary>
|
||||
/// <param name="unit">
|
||||
/// The temperature unit as which the temperature value should be displayed.
|
||||
/// </param>
|
||||
/// <param name="provider">
|
||||
/// An IFormatProvider reference that supplies culture-specific formatting
|
||||
/// services.
|
||||
/// </param>
|
||||
/// <returns>A string representation of the temperature.</returns>
|
||||
public readonly string ToString(TemperatureUnit unit, IFormatProvider? provider)
|
||||
{
|
||||
return unit switch
|
||||
{
|
||||
TemperatureUnit.Celsius => ToString("C", provider),
|
||||
TemperatureUnit.Fahrenheit => ToString("F", provider),
|
||||
TemperatureUnit.Kelvin => ToString("K", provider),
|
||||
_ => throw new FormatException("The temperature unit '" +
|
||||
unit.ToString() + "' is unknown."),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a string representation of the temperature value.
|
||||
/// </summary>
|
||||
/// <param name="unit">
|
||||
/// The temperature unit as which the temperature value should be displayed.
|
||||
/// </param>
|
||||
/// <returns>A string representation of the temperature.</returns>
|
||||
public readonly string ToString(TemperatureUnit unit) => ToString(unit, null);
|
||||
|
||||
/// <summary>
|
||||
/// Compares this instance to a specified Temperature object and returns an indication
|
||||
/// of their relative values.
|
||||
/// </summary>
|
||||
/// <param name="value">A Temperature object
|
||||
/// to compare to this instance.</param>
|
||||
/// <returns>
|
||||
/// A signed number indicating the relative values of this instance and value.
|
||||
/// Value Description A negative integer This instance is less than value. Zero
|
||||
/// This instance is equal to value. A positive integer This instance is greater
|
||||
/// than value.
|
||||
/// </returns>
|
||||
public readonly int CompareTo(Temperature value) => _kelvin.CompareTo(value._kelvin);
|
||||
|
||||
/// <summary>
|
||||
/// Compares this instance to a specified object and returns an indication of
|
||||
/// their relative values.
|
||||
/// </summary>
|
||||
/// <param name="value">An object to compare, or null.</param>
|
||||
/// <returns>
|
||||
/// A signed number indicating the relative values of this instance and value.
|
||||
/// Value Description A negative integer This instance is less than value. Zero
|
||||
/// This instance is equal to value. A positive integer This instance is greater
|
||||
/// than value, or value is null.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentException">
|
||||
/// The value is not a Temperature.
|
||||
/// </exception>
|
||||
public readonly int CompareTo(object? value)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (value is not Temperature)
|
||||
{
|
||||
throw new ArgumentException($"Can not compare {value.GetType()} and {GetType()}");
|
||||
}
|
||||
|
||||
return CompareTo((Temperature)value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether or not the given temperature is considered equal to this instance.
|
||||
/// </summary>
|
||||
/// <param name="value">The temperature to compare to this instance.</param>
|
||||
/// <returns>True if the temperature is considered equal
|
||||
/// to this instance. Otherwise, false.</returns>
|
||||
public readonly bool Equals(Temperature value) => _kelvin == value._kelvin;
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether or not the given object is considered equal to the temperature.
|
||||
/// </summary>
|
||||
/// <param name="value">The object to compare to the temperature.</param>
|
||||
/// <returns>True if the object is considered equal
|
||||
/// to the temperature. Otherwise, false.</returns>
|
||||
public override readonly bool Equals([NotNullWhen(true)] object? value) => value is Temperature temp && Equals(temp);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns the hash code for this instance.
|
||||
/// </summary>
|
||||
/// <returns>A 32-bit signed integer hash code.</returns>
|
||||
public override readonly int GetHashCode() => _kelvin.GetHashCode();
|
||||
|
||||
/// <summary>
|
||||
/// Determines the equality of two temperatures.
|
||||
/// </summary>
|
||||
/// <param name="t1">The first temperature to be compared.</param>
|
||||
/// <param name="t2">The second temperature to be compared.</param>
|
||||
/// <returns>True if the temperatures are equal. Otherwise, false.</returns>
|
||||
public static bool operator ==(Temperature t1, Temperature t2) => t1.Equals(t2);
|
||||
|
||||
/// <summary>
|
||||
/// Determines the inequality of two temperatures.
|
||||
/// </summary>
|
||||
/// <param name="t1">The first temperature to be compared.</param>
|
||||
/// <param name="t2">The second temperature to be compared.</param>
|
||||
/// <returns>True if the temperatures are NOT equal. Otherwise, false.</returns>
|
||||
public static bool operator !=(Temperature t1, Temperature t2) => !t1.Equals(t2);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether one temperature is considered greater than another.
|
||||
/// </summary>
|
||||
/// <param name="t1">The first temperature to be compared.</param>
|
||||
/// <param name="t2">The second temperature to be compared.</param>
|
||||
/// <returns>True if the first temperature is greater than the second.
|
||||
/// Otherwise, false.</returns>
|
||||
public static bool operator >(Temperature t1, Temperature t2) => t1._kelvin > t2._kelvin;
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether one temperature is considered less than another.
|
||||
/// </summary>
|
||||
/// <param name="t1">The first temperature to be compared.</param>
|
||||
/// <param name="t2">The second temperature to be compared.</param>
|
||||
/// <returns>True if the first temperature is less than the second.
|
||||
/// Otherwise, false.</returns>
|
||||
public static bool operator <(Temperature t1, Temperature t2) => t1._kelvin < t2._kelvin;
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether one temperature is considered greater to or equal to another.
|
||||
/// </summary>
|
||||
/// <param name="t1">The first temperature to be compared.</param>
|
||||
/// <param name="t2">The second temperature to be compared.</param>
|
||||
/// <returns>
|
||||
/// True if the first temperature is greater to or equal to the second. Otherwise, false.
|
||||
/// </returns>
|
||||
public static bool operator >=(Temperature t1, Temperature t2) => t1._kelvin >= t2._kelvin;
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether one temperature is considered less than or equal to another.
|
||||
/// </summary>
|
||||
/// <param name="t1">The first temperature to be compared.</param>
|
||||
/// <param name="t2">The second temperature to be compared.</param>
|
||||
/// <returns>
|
||||
/// True if the first temperature is less than or equal to the second. Otherwise, false.
|
||||
/// </returns>
|
||||
public static bool operator <=(Temperature t1, Temperature t2) => t1._kelvin <= t2._kelvin;
|
||||
|
||||
/// <summary>
|
||||
/// Adds two instances of the temperature object.
|
||||
/// </summary>
|
||||
/// <param name="t1">The temperature on the left-hand side of the operator.
|
||||
/// </param>
|
||||
/// <param name="t2">The temperature on the right-hand side of the operator.
|
||||
/// </param>
|
||||
/// <returns>The sum of the two temperatures.</returns>
|
||||
public static Temperature operator +(Temperature t1, TemperatureDelta t2) => new(t1._kelvin + t2.KelvinDelta);
|
||||
|
||||
/// <summary>
|
||||
/// Subtracts one instance from another.
|
||||
/// </summary>
|
||||
/// <param name="t1">The temperature on the left-hand side of the operator.
|
||||
/// </param>
|
||||
/// <param name="t2">The temperature on the right-hand side of the operator.
|
||||
/// </param>
|
||||
/// <returns>The difference of the two temperatures.</returns>
|
||||
public static TemperatureDelta operator -(Temperature t1, Temperature t2) => new(t1._kelvin - t2._kelvin);
|
||||
public static TemperatureDelta operator -(Temperature left, TemperatureDelta right) => new(left._kelvin - right.KelvinDelta);
|
||||
|
||||
/// <summary>
|
||||
/// Converts a Kelvin temperature value to Celsius.
|
||||
/// </summary>
|
||||
/// <param name="kelvin">The Kelvin value to convert to Celsius.
|
||||
/// </param>
|
||||
/// <returns>The Kelvin value in Celsius.</returns>
|
||||
public static double KelvinToCelsius(double kelvin) => kelvin - 273.15;
|
||||
|
||||
/// <summary>
|
||||
/// Converts a Celsius value to Kelvin.
|
||||
/// </summary>
|
||||
/// <param name="celsius">The Celsius value to convert to Kelvin.
|
||||
/// </param>
|
||||
/// <returns>The Celsius value in Kelvin.</returns>
|
||||
public static double CelsiusToKelvin(double celsius) => celsius + 273.15;
|
||||
|
||||
/// <summary>
|
||||
/// Converts a Kelvin value to Fahrenheit.
|
||||
/// </summary>
|
||||
/// <param name="kelvin">The Kelvin value to convert to Fahrenheit.
|
||||
/// </param>
|
||||
/// <returns>The Kelvin value in Fahrenheit.</returns>
|
||||
public static double KelvinToFahrenheit(double kelvin) => (kelvin * 9 / 5) - 459.67;
|
||||
|
||||
/// <summary>
|
||||
/// Converts a Fahrenheit value to Kelvin.
|
||||
/// </summary>
|
||||
/// <param name="fahrenheit">The Fahrenheit value to convert to Kelvin.
|
||||
/// </param>
|
||||
/// <returns>The Fahrenheit value in Kelvin.</returns>
|
||||
public static double FahrenheitToKelvin(double fahrenheit) => (fahrenheit + 459.67) * 5 / 9;
|
||||
|
||||
/// <summary>
|
||||
/// Converts a Fahrenheit value to Celsius.
|
||||
/// </summary>
|
||||
/// <param name="fahrenheit">The Fahrenheit value to convert to Celsius.
|
||||
/// </param>
|
||||
/// <returns>The Fahrenheit value in Celsius.</returns>
|
||||
public static double FahrenheitToCelsius(double fahrenheit) => (fahrenheit - 32) * 5 / 9;
|
||||
|
||||
/// <summary>
|
||||
/// Converts a Celsius value to Fahrenheit.
|
||||
/// </summary>
|
||||
/// <param name="celsius">The Celsius value to convert to Fahrenheit.
|
||||
/// </param>
|
||||
/// <returns>The Celsius value in Fahrenheit.</returns>
|
||||
public static double CelsiusToFahrenheit(double celsius) => (celsius * 9 / 5) + 32;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public readonly struct TemperatureDelta : IFormattable, IComparable,
|
||||
IComparable<TemperatureDelta>, IEquatable<TemperatureDelta>,
|
||||
IComparisonOperators<TemperatureDelta, TemperatureDelta, bool>,
|
||||
IEqualityOperators<TemperatureDelta, TemperatureDelta, bool>,
|
||||
IAdditionOperators<TemperatureDelta, TemperatureDelta, TemperatureDelta>,
|
||||
ISubtractionOperators<TemperatureDelta, TemperatureDelta, TemperatureDelta>,
|
||||
IMultiplyOperators<TemperatureDelta, double, TemperatureDelta>,
|
||||
IDivisionOperators<TemperatureDelta, double, TemperatureDelta>
|
||||
{
|
||||
private readonly double _kelvinDelta;
|
||||
public TemperatureDelta(double kelvin)
|
||||
{
|
||||
_kelvinDelta = kelvin;
|
||||
}
|
||||
public readonly double KelvinDelta => _kelvinDelta;
|
||||
public readonly double FahrenheitDelta => CelsiusToFahrenheitDelta(_kelvinDelta);
|
||||
public readonly double CelsiusDelta => _kelvinDelta;
|
||||
public TemperatureDelta(double temperature, TemperatureUnit unit)
|
||||
{
|
||||
_kelvinDelta = unit switch
|
||||
{
|
||||
TemperatureUnit.Celsius => temperature,
|
||||
TemperatureUnit.Fahrenheit => FahrenheitToKelvinDelta(temperature),
|
||||
TemperatureUnit.Kelvin => temperature,
|
||||
_ => throw new ArgumentException($"Unit {unit} is not a valid {nameof(TemperatureUnit)}"),
|
||||
};
|
||||
}
|
||||
|
||||
public static double FahrenheitToKelvinDelta(double fahrenheit) => fahrenheit * 5 / 9;
|
||||
public static double KelvinToFahrenheitDelta(double kelvin) => kelvin * 9 / 5;
|
||||
public static double FahrenheitToCelsiusDelta(double fahrenheit) => FahrenheitToKelvinDelta(fahrenheit);
|
||||
public static double CelsiusToFahrenheitDelta(double celsius) => KelvinToFahrenheitDelta(celsius);
|
||||
public bool Equals(TemperatureDelta other) => _kelvinDelta == other._kelvinDelta;
|
||||
public override bool Equals([NotNullWhen(true)] object? obj) => obj is TemperatureDelta delta && Equals(delta);
|
||||
public override int GetHashCode() => _kelvinDelta.GetHashCode();
|
||||
|
||||
public readonly string ToString(string? format, IFormatProvider? provider)
|
||||
{
|
||||
if (string.IsNullOrEmpty(format))
|
||||
{
|
||||
format = "G";
|
||||
}
|
||||
provider ??= CultureInfo.CurrentCulture;
|
||||
|
||||
return format switch
|
||||
{
|
||||
"G" or "C" or "g" or "c" => CelsiusDelta.ToString("F2", provider) + " °C",
|
||||
"F" or "f" => FahrenheitDelta.ToString("F2", provider) + " °F",
|
||||
"K" or "k" => _kelvinDelta.ToString("F2", provider) + " K",
|
||||
_ => throw new FormatException(
|
||||
string.Format("The {0} format string is not supported.", format)),
|
||||
};
|
||||
}
|
||||
public readonly string ToString(string format) => ToString(format, null);
|
||||
public override string ToString() => ToString(null, null);
|
||||
public readonly int CompareTo(TemperatureDelta value) => _kelvinDelta.CompareTo(value._kelvinDelta);
|
||||
public readonly int CompareTo(object? value)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (value is not TemperatureDelta)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
$"Object must be of type {nameof(TemperatureDelta)}",
|
||||
nameof(value));
|
||||
}
|
||||
|
||||
return CompareTo((TemperatureDelta)value);
|
||||
}
|
||||
|
||||
public static TemperatureDelta operator +(TemperatureDelta left, TemperatureDelta right) => new(left._kelvinDelta + right._kelvinDelta);
|
||||
public static TemperatureDelta operator -(TemperatureDelta left, TemperatureDelta right) => new(left._kelvinDelta - right._kelvinDelta);
|
||||
public static TemperatureDelta operator *(TemperatureDelta left, double right) => new(left._kelvinDelta * right);
|
||||
public static TemperatureDelta operator /(TemperatureDelta left, double right) => new(left._kelvinDelta / right);
|
||||
public static bool operator >(TemperatureDelta left, TemperatureDelta right) => left._kelvinDelta > right._kelvinDelta;
|
||||
public static bool operator >=(TemperatureDelta left, TemperatureDelta right) => left._kelvinDelta >= right._kelvinDelta;
|
||||
public static bool operator <(TemperatureDelta left, TemperatureDelta right) => left._kelvinDelta < right._kelvinDelta;
|
||||
public static bool operator <=(TemperatureDelta left, TemperatureDelta right) => left._kelvinDelta <= right._kelvinDelta;
|
||||
public static bool operator ==(TemperatureDelta left, TemperatureDelta right) => left._kelvinDelta == right._kelvinDelta;
|
||||
public static bool operator !=(TemperatureDelta left, TemperatureDelta right) => left._kelvinDelta != right._kelvinDelta;
|
||||
}
|
||||
413
src/Recipes/Recipes.cs
Normal file
413
src/Recipes/Recipes.cs
Normal file
@@ -0,0 +1,413 @@
|
||||
namespace FoodFactory.Recipes;
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Arch.Core;
|
||||
using Arch.Core.Extensions;
|
||||
using FastExpressionCompiler.ImTools;
|
||||
using FoodFactory.Items;
|
||||
using Godot;
|
||||
using Parlot.Fluent;
|
||||
using SJK.Functional;
|
||||
|
||||
public class Recipes
|
||||
{
|
||||
private readonly Dictionary<RecipeAction, Dictionary<int, RecipeBucket>> _actions = [];
|
||||
private readonly Dictionary<RecipeName, Recipe> _recipes = [];
|
||||
public Option<Recipe> GetRecipeByName(RecipeName name) => _recipes.GetValue(name).ToStructOption();
|
||||
public Blueprint TESTBlueprint;
|
||||
public Recipes()
|
||||
{
|
||||
//TODO Utilize the singtaure ssytem to create the item, the have a post proccess step that would be for setting propertied
|
||||
var rawPotatoBlueprint = TESTBlueprint = new Blueprint("Potato", static ctx => ctx.World.Create(new Name("Potato_raw"), new Tags("potato", "raw", "vegetable"), new Temperature(71, TemperatureUnit.Fahrenheit), ctx.BluePrintId));
|
||||
var recipe = new PotatoCookRecipe();
|
||||
_recipes.Add(new RecipeName(recipe.Name), recipe);
|
||||
var compiledRecipe = new CompiledRecipe() { IsOrdered = true, RecipeRef = recipe, InputBlueprints = [new BlueprintId(rawPotatoBlueprint)], ItemEntries = [new CompiledRecipe.ItemEntry() { InputTags = recipe.Inputs[0], ExcludeTags = recipe.Exclude[0], RequireAll = recipe.RequireAll[0] }] };
|
||||
_actions.Add(new("cook"), new Dictionary<int, RecipeBucket>() { [1] = new RecipeBucket() {Single = new Dictionary<BlueprintId, CompiledRecipe[]>(){[new BlueprintId(rawPotatoBlueprint)] = [compiledRecipe] }, Unordered = [compiledRecipe] } });
|
||||
}
|
||||
public List<(Recipe Recipe, int[]? Mapping)> GetRecipes(RecipeAction action, int inputs, Tags[] itemTags, BlueprintId[] blueprintIds)
|
||||
{
|
||||
var list = new List<(Recipe, int[])>();
|
||||
Debug.Assert(inputs > 0);
|
||||
Debug.Assert(inputs == itemTags.Length);
|
||||
if (!_actions.TryGetValue(action, out var inputAmount))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (!inputAmount.TryGetValue(inputs, out var blueprints))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var recipes = blueprints.Resolve(blueprintIds);
|
||||
|
||||
|
||||
var mapping = new int[itemTags.Length];
|
||||
var used = new bool[itemTags.Length];
|
||||
for (int i = 0; i < recipes.Length; i++)
|
||||
{
|
||||
var item = recipes[i];
|
||||
if (inputs == 1 && IsValid(itemTags[0], item.ItemEntries[0]))
|
||||
{
|
||||
list.Add((item.RecipeRef, null));
|
||||
}
|
||||
else if (item.IsOrdered)
|
||||
{
|
||||
for (int ii = 0; ii < itemTags.Length; ii++)
|
||||
{
|
||||
if (!IsValid(itemTags[ii], item.ItemEntries[ii]))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
list.Add((item.RecipeRef, null));
|
||||
}
|
||||
else if (CanPossiblyMatch(itemTags, item))
|
||||
{
|
||||
if (Match(itemTags, item.ItemEntries, 0, used, mapping))
|
||||
{
|
||||
list.Add((item.RecipeRef, mapping));
|
||||
}
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
private static bool IsValid(Tags tags, CompiledRecipe.ItemEntry compiled)
|
||||
{
|
||||
if (!(compiled.RequireAll ? tags.ContainsAll(compiled.InputTags) : tags.ContainsAny(compiled.InputTags)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (tags.ContainsAny(compiled.ExcludeTags))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
private static bool CanPossiblyMatch(Tags[] items, CompiledRecipe recipe)
|
||||
{
|
||||
foreach (var entry in recipe.ItemEntries)
|
||||
{
|
||||
bool found = false;
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (IsValid(item, entry))
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
private static bool Match(
|
||||
Tags[] items,
|
||||
CompiledRecipe.ItemEntry[] entries,
|
||||
int depth,
|
||||
Span<bool> used,
|
||||
Span<int> mapping)
|
||||
{
|
||||
if (depth == entries.Length)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
for (int i = 0; i < items.Length; i++)
|
||||
{
|
||||
if (used[i])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!IsValid(items[i], entries[depth]))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
used[i] = true;
|
||||
mapping[depth] = i;
|
||||
|
||||
if (Match(items, entries, depth + 1, used, mapping))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
used[i] = false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool MatchArrays<T, T2>(T[] inputs, T2[] test, Func<T, T2, bool> isValid)
|
||||
{
|
||||
Debug.Assert(inputs.Length == test.Length);
|
||||
var used = new bool[inputs.Length];
|
||||
bool backTrace(int index)
|
||||
{
|
||||
if (index == inputs.Length)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
for (int i = 0; i < test.Length; i++)
|
||||
{
|
||||
if (used[i])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (!isValid(inputs[index], test[i]))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
used[i] = true;
|
||||
if (backTrace(index + 1))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
used[i] = false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return backTrace(0);
|
||||
}
|
||||
private int[]? MatchArraysAndMap<T, T2>(T[] inputs, T2[] test, Func<T, T2, bool> isValid)
|
||||
{
|
||||
Debug.Assert(inputs.Length == test.Length);
|
||||
var used = new bool[inputs.Length];
|
||||
var mapping = new int[inputs.Length];
|
||||
Array.Fill(mapping, -1);
|
||||
int[]? backTrace(int index)
|
||||
{
|
||||
if (index == inputs.Length)
|
||||
{
|
||||
return [.. mapping];
|
||||
}
|
||||
for (int i = 0; i < test.Length; i++)
|
||||
{
|
||||
if (used[i])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (!isValid(inputs[index], test[i]))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
used[i] = true;
|
||||
mapping[index] = i;
|
||||
var result = backTrace(index + 1);
|
||||
if (result is not null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
used[i] = false;
|
||||
mapping[index] = -1;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return backTrace(0);
|
||||
}
|
||||
}
|
||||
public abstract record Recipe(string Name, BlueprintId[]? BlueprintIds, bool[] RequireAll, Tags[] Inputs, Tags[] Exclude)
|
||||
{
|
||||
public abstract bool CanProcess(in RecipeContext context);
|
||||
public abstract RecipeResult Process(in RecipeContext context, ref RecipeResultBuilder builder);
|
||||
}
|
||||
public record PotatoCookRecipe() : Recipe(Name: "Potato_Cook", BlueprintIds: null, RequireAll: [false], Inputs: [new Tags("potato")], Exclude: [new Tags()])
|
||||
{
|
||||
public override bool CanProcess(in RecipeContext context)
|
||||
{
|
||||
if (!context.Entity[0].Has<Temperature>())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public override RecipeResult Process(in RecipeContext context, ref RecipeResultBuilder builder)
|
||||
{
|
||||
builder.AddRemove(true);
|
||||
builder.AddCreate(NewPotato);
|
||||
return builder.Build();
|
||||
}
|
||||
|
||||
private Entity NewPotato(in RecipeContext context)
|
||||
{
|
||||
Span<Entity> span = stackalloc Entity[1];
|
||||
var signature = context.World.GetSignature(context.Entity[0]);
|
||||
context.World.Create(span, signature, 1);
|
||||
|
||||
span[0].Set(new Name("Cooked_Potato"));
|
||||
span[0].Set(context.Entity[0].Get<Tags>().With("cooked").WithOut("raw"));
|
||||
span[0].Set(context.Entity[0].Get<Temperature>());
|
||||
return span[0];
|
||||
}
|
||||
}
|
||||
public ref struct RecipeResultBuilder
|
||||
{
|
||||
private Span<bool> _remove;
|
||||
private Span<ItemBuilder> _create;
|
||||
private int _removeCount;
|
||||
private int _createCount;
|
||||
|
||||
public RecipeResultBuilder(Span<bool> remove, Span<ItemBuilder> create)
|
||||
{
|
||||
_remove = remove;
|
||||
_create = create;
|
||||
_removeCount = 0;
|
||||
_createCount = 0;
|
||||
}
|
||||
|
||||
public void AddRemove(bool value) => _remove[_removeCount++] = value;
|
||||
public void AddCreate(ItemBuilder item) => _create[_createCount++] = item;
|
||||
|
||||
public RecipeResult Build()
|
||||
=> new RecipeResult(
|
||||
_remove.Slice(0, _removeCount),
|
||||
_create.Slice(0, _createCount));
|
||||
}
|
||||
public delegate Entity ItemBuilder(in RecipeContext ctx);
|
||||
public readonly ref struct RecipeContext
|
||||
{
|
||||
public readonly World World;
|
||||
|
||||
public RecipeContext(World world, Span<Entity> entity) : this()
|
||||
{
|
||||
World = world;
|
||||
Entity = entity;
|
||||
}
|
||||
|
||||
public readonly Span<Entity> Entity;
|
||||
// optional later:
|
||||
// public readonly Machine Machine;
|
||||
// public readonly float Delta;
|
||||
}
|
||||
public readonly ref struct RecipeResult
|
||||
{
|
||||
public readonly int RemoveLength => Remove.Length;
|
||||
public readonly int CreateLength => Create.Length;
|
||||
public readonly ReadOnlySpan<bool> Remove; // destroy original
|
||||
|
||||
public RecipeResult(ReadOnlySpan<bool> remove, ReadOnlySpan<ItemBuilder> create) : this()
|
||||
{
|
||||
Remove = remove;
|
||||
Create = create;
|
||||
Mutate = null;
|
||||
}
|
||||
|
||||
public readonly ReadOnlySpan<ItemBuilder> Create; // multi-output
|
||||
|
||||
public readonly Action<RecipeContext>? Mutate; // component changes
|
||||
|
||||
public RecipeResult(Action<RecipeContext> mutate) : this()
|
||||
{
|
||||
Mutate = mutate;
|
||||
}
|
||||
}
|
||||
public struct CompiledRecipe
|
||||
{
|
||||
|
||||
public Recipe RecipeRef;
|
||||
public BlueprintId[] InputBlueprints;
|
||||
public ItemEntry[] ItemEntries;
|
||||
public bool IsOrdered;
|
||||
public struct ItemEntry
|
||||
{
|
||||
public bool RequireAll;
|
||||
public Tags InputTags;
|
||||
public Tags ExcludeTags;
|
||||
}
|
||||
}
|
||||
// public record SingleItemRecipe(string Name, Tags Required, Tags Exclude) : Recipe(Name)
|
||||
// {
|
||||
|
||||
// }
|
||||
// public record MultipleItemRecipe(string Name, params ItemInput[] Inputs) : Recipe(Name)
|
||||
// {
|
||||
|
||||
// }
|
||||
public readonly record struct RecipeName
|
||||
{
|
||||
public readonly string Name;
|
||||
public RecipeName(string name)
|
||||
{
|
||||
Name = name.ToLowerInvariant();
|
||||
}
|
||||
public static implicit operator string(RecipeName name) => name.Name;
|
||||
}
|
||||
public readonly record struct RecipeAction
|
||||
{
|
||||
public readonly string Value;
|
||||
public RecipeAction(string value)
|
||||
{
|
||||
Value = value.ToLowerInvariant();
|
||||
}
|
||||
public static implicit operator string(RecipeAction name) => name.Value;
|
||||
}
|
||||
public readonly record struct BlueprintId
|
||||
{
|
||||
public readonly int Id;
|
||||
public Blueprint Blueprint => _registry.GetEntry(Id);
|
||||
private static Registry<Blueprint> _registry = new();
|
||||
public BlueprintId(Blueprint blueprint)
|
||||
{
|
||||
Id = _registry.GetOrCreate(blueprint);
|
||||
|
||||
}
|
||||
}
|
||||
public record Blueprint(string Name, Func<BlueprintContext, Entity> Factory)
|
||||
{
|
||||
|
||||
}
|
||||
public struct BlueprintContext
|
||||
{
|
||||
public World World;
|
||||
public BlueprintId BluePrintId;
|
||||
}
|
||||
public sealed class RecipeBucket
|
||||
{
|
||||
// 1 input → direct blueprint lookup
|
||||
public Dictionary<BlueprintId, CompiledRecipe[]> Single = [];
|
||||
|
||||
// ordered multi-input → first item is anchor
|
||||
public Dictionary<BlueprintId, CompiledRecipe[]> Ordered = [];
|
||||
|
||||
// fallback (unordered / no blueprint)
|
||||
public CompiledRecipe[] Unordered = [];
|
||||
/// <summary>
|
||||
/// Resloves Blueprints, if span is length 0, tehn Unordred is returned imdetly
|
||||
/// </summary>
|
||||
/// <param name="items"></param>
|
||||
/// <returns></returns>
|
||||
public ReadOnlySpan<CompiledRecipe> Resolve(Span<BlueprintId> items)
|
||||
{
|
||||
if (items.Length == 0)
|
||||
{
|
||||
return Unordered;
|
||||
}
|
||||
|
||||
if (items.Length == 1)
|
||||
{
|
||||
if (Single.TryGetValue(items[0], out var r))
|
||||
return r;
|
||||
|
||||
return Unordered;
|
||||
}
|
||||
|
||||
var main = items[0];
|
||||
|
||||
if (Ordered.TryGetValue(main, out var ordered))
|
||||
{
|
||||
return ordered;
|
||||
}
|
||||
|
||||
return Unordered;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user