Compare commits

..

13 Commits

Author SHA1 Message Date
170aebacdc ReWorked some code to fit more like GameDemo example. 2026-08-07 10:43:56 -04:00
b58acff195 Major Changes including Options menu, logic blocks, modding system, and more. 2026-08-06 01:28:52 -04:00
c9ac4641a0 Added a RTS camera, and some assorted changes. 2026-07-08 13:18:13 -04:00
0fa7ba766b Seprated many files, removed IDataCompiler with using YamlConverters. Refactored namespaces 2026-05-28 11:42:36 -04:00
a76a50f38e Added DataBased Compiled/Provider system 2026-05-27 18:32:08 -04:00
8a0347f1a1 Removed LaneSpan, may be add later as diffrent movemnet system. 2026-05-17 23:44:03 -04:00
75c93f0c43 Added Arch Persitance and Relationships, styackable items, hot fixed Tags.containsAll. AutoList in Ordered!DList 2026-05-16 12:44:51 -04:00
bf4d856608 Changed Namespaces, Move files 2026-05-12 09:53:30 -04:00
4db51be374 Spite up project, added BeltData, some asoited compoents, remvoed unused usings, and removed uneeded comment code. 2026-05-10 12:33:20 -04:00
d620189e4e Bases of a Recipe System created, tags compponet, and Tempearue stuct with a temutrae delta. 2026-05-04 00:02:08 -04:00
4a7494ff6b Redid inserrt logic and grid registry, added an test oven, and changed .vscode settings to try and get better debugging. 2026-04-26 23:51:03 -04:00
7d25f6d448 removed code that was commented out, and a bunch of obsolete classes. 2026-04-19 01:29:28 -04:00
e7ef3896b0 removed Structural instance and SJKScript was changed to be using an nuget package. 2026-04-19 00:46:18 -04:00
256 changed files with 62990 additions and 3659 deletions

2
.gitmodules vendored
View File

@@ -1,3 +1,3 @@
[submodule "assets"] [submodule "assets"]
path = assets path = assets
url = http://192.168.1.4:3000/Ronnie/FoodFactoryAssets url = https://gitea.superjrking.com/Ronnie/FoodFactoryAssets

View File

@@ -27,7 +27,7 @@
"dotnet.enableXamlTools": false, "dotnet.enableXamlTools": false,
"dotnet.formatting.organizeImportsOnFormat": true, "dotnet.formatting.organizeImportsOnFormat": true,
"dotnet.preferCSharpExtension": true, "dotnet.preferCSharpExtension": true,
// "dotnet.server.useOmnisharp": true, // You decide "dotnet.server.useOmnisharp": false, // You decide
"dotnetAcquisitionExtension.enableTelemetry": false, "dotnetAcquisitionExtension.enableTelemetry": false,
"editor.semanticHighlighting.enabled": true, "editor.semanticHighlighting.enabled": true,
// C# doc comment colorization gets lost with semantic highlighting, but we // C# doc comment colorization gets lost with semantic highlighting, but we
@@ -164,7 +164,7 @@
"omnisharp.enableEditorConfigSupport": true, "omnisharp.enableEditorConfigSupport": true,
"omnisharp.enableMsBuildLoadProjectsOnDemand": false, "omnisharp.enableMsBuildLoadProjectsOnDemand": false,
"omnisharp.maxFindSymbolsItems": 3000, "omnisharp.maxFindSymbolsItems": 3000,
"omnisharp.useModernNet": true, "omnisharp.useModernNet": false,
// Remove these if you're happy with your terminal profiles. // Remove these if you're happy with your terminal profiles.
"terminal.integrated.defaultProfile.windows": "Git Bash", "terminal.integrated.defaultProfile.windows": "Git Bash",
"terminal.integrated.profiles.windows": { "terminal.integrated.profiles.windows": {

View File

@@ -10,6 +10,7 @@
<!-- Required for some nuget packages to work --> <!-- Required for some nuget packages to work -->
<!-- godotengine/godot/issues/42271#issuecomment-751423827 --> <!-- godotengine/godot/issues/42271#issuecomment-751423827 -->
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<!-- To show generated files --> <!-- To show generated files -->
<!-- <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles> --> <!-- <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles> -->
<!-- <!--
@@ -35,31 +36,45 @@
<ItemGroup> <ItemGroup>
<!-- Production dependencies go here! --> <!-- Production dependencies go here! -->
<PackageReference Include="Chickensoft.GameTools" Version="3.1.6" /> <PackageReference Include="Arch" Version="2.1.0" />
<PackageReference Include="SjkScripts" Version="1.0.1" /> <PackageReference Include="Arch.LowLevel" Version="1.1.5" />
<PackageReference Include="System.IO.Abstractions" Version="22.1.0" /> <PackageReference Include="Arch.System" Version="1.1.0" />
<PackageReference Include="Chickensoft.GameTools" Version="3.1.27" />
<PackageReference Include="GodotHelper" Version="0.0.2" />
<!-- <PackageReference Include="LanguageExt.Core" Version="5.0.0-beta-77" /> -->
<PackageReference Include="MessagePack" Version="3.1.4" />
<PackageReference Include="NCalc.LambdaCompilation" Version="5.12.0" />
<PackageReference Include="NCalcSync" Version="5.12.0" />
<PackageReference Include="SharpYaml" Version="3.7.1" />
<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="EnvironmentAbstractions" Version="5.0.0" />
<PackageReference Include="GodotSharp.SourceGenerators" Version="2.6.0" PrivateAssets="all" OutputItemType="analyzer" /> <PackageReference Include="GodotSharp.SourceGenerators" Version="2.7.0" PrivateAssets="all" OutputItemType="analyzer" />
<PackageReference Include="Chickensoft.SaveFileBuilder" Version="1.3.54" /> <PackageReference Include="Chickensoft.SaveFileBuilder" Version="2.0.1" />
<PackageReference Include="Chickensoft.AutoInject" Version="2.9.18" PrivateAssets="all" /> <PackageReference Include="Chickensoft.AutoInject" Version="2.13.15" PrivateAssets="all" />
<PackageReference Include="Chickensoft.Collections" Version="3.1.4" /> <PackageReference Include="Chickensoft.Collections" Version="3.1.4" />
<PackageReference Include="Chickensoft.GodotNodeInterfaces" Version="2.4.57" /> <PackageReference Include="Chickensoft.GodotNodeInterfaces" Version="3.0.20" />
<PackageReference Include="Chickensoft.Introspection" Version="3.0.2" /> <PackageReference Include="Chickensoft.Introspection" Version="3.0.3" />
<PackageReference Include="Chickensoft.Introspection.Generator" Version="3.0.2" PrivateAssets="all" OutputItemType="analyzer" /> <PackageReference Include="Chickensoft.Introspection.Generator" Version="3.0.3" PrivateAssets="all" OutputItemType="analyzer" />
<PackageReference Include="Chickensoft.Serialization" Version="3.1.0" /> <PackageReference Include="Chickensoft.Serialization" Version="3.1.0" />
<PackageReference Include="Chickensoft.Serialization.Godot" Version="0.8.46" /> <PackageReference Include="Chickensoft.Serialization.Godot" Version="0.9.1" />
<PackageReference Include="Chickensoft.LogicBlocks" Version="5.20.0" /> <PackageReference Include="Chickensoft.LogicBlocks" Version="6.1.1" />
<PackageReference Include="Chickensoft.LogicBlocks.DiagramGenerator" Version="5.20.0" PrivateAssets="all" OutputItemType="analyzer" /> <PackageReference Include="Chickensoft.LogicBlocks.Auto" Version="6.1.1" />
<PackageReference Include="Chickensoft.UMLGenerator" Version="1.1.0" /> <PackageReference Include="Chickensoft.LogicBlocks.DiagramGenerator" Version="6.1.1" PrivateAssets="all" OutputItemType="analyzer" />
<PackageReference Include="Chickensoft.Sync" Version="2.2.0" /> <PackageReference Include="Chickensoft.UMLGenerator" Version="1.3.1" />
<PackageReference Include="Chickensoft.Sync" Version="2.4.1" />
<PackageReference Include="TrimKit.VirtualFileSystem" Version="1.8.1" />
<PackageReference Include="Utf8Json" Version="1.3.7" />
</ItemGroup>
<ItemGroup>
<Compile Remove="/mods" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(RunTests)' == 'true'"> <ItemGroup Condition="'$(RunTests)' == 'true'">
<!-- Test dependencies go here! --> <!-- Test dependencies go here! -->
<!-- Dependencies added here will not be included in release builds. --> <!-- 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.39" />
<!-- Used to drive test scenes when testing visual code --> <!-- Used to drive test scenes when testing visual code -->
<PackageReference Include="Chickensoft.GodotTestDriver" Version="3.1.56" /> <PackageReference Include="Chickensoft.GodotTestDriver" Version="3.1.72" />
<!-- Bring your own assertion library for tests! --> <!-- Bring your own assertion library for tests! -->
<!-- We're using Shouldly for this example, but you can use anything. --> <!-- We're using Shouldly for this example, but you can use anything. -->
<PackageReference Include="Shouldly" Version="4.3.0" /> <PackageReference Include="Shouldly" Version="4.3.0" />

8
GlobalSuppressions.cs Normal file
View File

@@ -0,0 +1,8 @@
// This file is used by Code Analysis to maintain SuppressMessage
// attributes that are applied to this project.
// Project-level suppressions either have no target or are given
// a specific target and scoped to a namespace, type, member, etc.
using System.Diagnostics.CodeAnalysis;
[assembly: SuppressMessage("Style", "IDE0065:Misplaced using directive", Justification = "<Pending>")]

View File

@@ -0,0 +1 @@
uid://oaa3naabb0bi

187
RTSCamera.cs Executable file
View File

@@ -0,0 +1,187 @@
using Godot;
using System;
using SJK.Math;
using Chickensoft.SaveFileBuilder;
using Chickensoft.Introspection;
using Chickensoft.Serialization;
[Meta, Id("save_data")]
public partial class SaveData
{
[Save("player_transform")]
public Transform3D Transform3D {get;set;}
[Save("player_camera")]
public Transform3D CameraTransform {get;set;}
}
public partial class RTSCamera : Node3D, ISaveable<SaveData>
{
[Export]
public Camera3D camera;
Vector3 positionStartPos;
Vector3 positionOffsetPos;
Vector3 lastTransalation;
Vector3 cameraTargetOffset;
Action Update_CurrentFunc;
//Generic bookkeeping variables;
Vector2 LastMousePostition;
//Camera Dragging Bookkeeping variables
Vector3 LastMouseGroundPlanePositon;
//Camera Rotating Bookkeeping variables
float CameraRotateSpeed = .5f;
bool isDraging;
public override void _Ready()
{
Update_CurrentFunc = Update_DetectModeStart;
}
void Update_DetectModeStart()
{
if (Input.IsActionJustPressed("move_mouse"))
{
//Mouse Left went Down
//Does nothing basicly
}
else if (Input.IsActionPressed("move_mouse") && GetViewport().GetMousePosition() != LastMousePostition)
{
LastMouseGroundPlanePositon = camera.MouseToGroundPlane() ?? Vector3.Zero;
Update_CurrentFunc = Update_CameraDrag;
Update_CurrentFunc();
// GD.Print("1");
}
else if (Input.IsActionPressed("camera_rotate") && GetViewport().GetMousePosition() != LastMousePostition)
{
LastMouseGroundPlanePositon = camera.MouseToGroundPlane() ?? Vector3.Zero;
Update_CurrentFunc = Update_RotateCamera;
Update_CurrentFunc();
// GD.Print("2");
}
}
public override void _Process(double delta)
{
if (Input.IsActionJustPressed("ui_cancel"))
{
CancelUpdateFunc();
}
// GD.PrintS(positionStartPos,positionOffsetPos,lastTransalation,LastMouseGroundPlanePositon,LastMousePostition,cameraTargetOffset,scrollAmount);
Update_CurrentFunc();
Update_CameraScroll((float)delta);
LastMousePostition = GetViewport().GetMousePosition();
camera.Fov = Mathf.Lerp(camera.Fov, Mathf.Lerp(90, 50, (camera.Position.Y - 5) / (80 - 5)), (float)delta * 10);
}
void CancelUpdateFunc()
{
Update_CurrentFunc = Update_DetectModeStart;
}
void Update_CameraDrag()
{
if (Input.IsActionJustReleased("move_mouse"))
{
CancelUpdateFunc();
}
var hitpos = camera.MouseToGroundPlane() ?? Vector3.Zero;
var diff = LastMouseGroundPlanePositon - hitpos;
GlobalTranslate(diff);
LastMouseGroundPlanePositon = hitpos = camera.MouseToGroundPlane() ?? Vector3.Zero;
}
float scrollAmount;
public override void _UnhandledInput(InputEvent @event)
{
scrollAmount += Input.IsActionJustReleased("mouse_scroll_up") ? 1 : 0;
scrollAmount += Input.IsActionJustReleased("mouse_scroll_down") ? -1 : 0;
if (@event is InputEventKey eventKey && eventKey.Keycode == Key.F)
{
GlobalPosition = Vector3.Zero;
camera.Position = Vector3.Zero;
CancelUpdateFunc();
}
}
void Update_CameraScroll(float delta)
{
// Zoom to scrollwheel
// float scrollAmount = Input.IsActionJustReleased("mouse_scroll_up")?1:0;
// scrollAmount += Input.IsActionJustReleased( "mouse_scroll_down")?-1:0;
// GD.Print(scrollAmount);
float minHeight = 2;
float maxHeight = 30;
// Move camera towards hitPos
// var hitPos = camera.MouseToGroundPlane();
// Vector3 dir = hitPos - camera.Translation;
var dir = -camera.GlobalTransform.Basis.Z * .5f;
var p = camera.Position;
// Stop zooming out at a certain distance.
// TODO: Maybe you should still slide around at 20 zoom?
if (scrollAmount > 0 || p.Y < (maxHeight - 0.1f))
{
cameraTargetOffset += dir * scrollAmount;
}
// DebugDraw3D.DrawArrow(Vector3.Zero,cameraTargetOffset);
Vector3 lastCameraPosition = camera.Position;
camera.Position = camera.Position.Lerp(camera.Position + cameraTargetOffset, delta);
cameraTargetOffset -= camera.Position - lastCameraPosition;
p = camera.Position;
if (p.Y < minHeight)
{
p.Y = minHeight;
}
if (p.Y > maxHeight)
{
p.Y = maxHeight;
}
camera.Position = p;
// Change camera angle
camera.RotationDegrees = new Vector3(
Mathf.Lerp(-50, -75, camera.Position.Y / maxHeight),
camera.RotationDegrees.Y,
camera.RotationDegrees.Z
);
scrollAmount = 0;
}
void Update_RotateCamera()
{
if (!Input.IsActionPressed("move_mouse"))
{
CancelUpdateFunc();
}
Vector2 hitpos = GetViewport().GetMousePosition();
Vector2 diff = LastMousePostition - hitpos;
camera.RotationDegrees = new Vector3(
camera.RotationDegrees.X,
camera.RotationDegrees.Y + (diff.X * CameraRotateSpeed),
camera.RotationDegrees.Z
);
LastMousePostition = hitpos = GetViewport().GetMousePosition();
}
public SaveData Save() => new(){Transform3D = Transform, CameraTransform = camera.Transform};
public void Load(in SaveData data)
{
Transform = data.Transform3D;
camera.Transform = data.CameraTransform;
}
}
public static partial class SJKMath
{
public static Vector3? MouseToPlane(this Plane plane, Camera3D viewCamera, Vector2? mousePos = null)
{
var from = viewCamera.ProjectRayOrigin( mousePos ?? viewCamera.GetViewport().GetMousePosition());
var normal = viewCamera.ProjectRayNormal(mousePos ?? viewCamera.GetViewport().GetMousePosition());
return plane.IntersectsRay(from, normal);
}
public static Vector3? MouseToGroundPlane(this Camera3D viewCamera, Vector2? mousePos = null, float offset = 0) =>
(Plane.PlaneXZ with { D = offset }).MouseToPlane(viewCamera, mousePos);
}

1
RTSCamera.cs.uid Normal file
View File

@@ -0,0 +1 @@
uid://bdvwvdqrbks7c

View File

@@ -20,7 +20,19 @@
"url": "https://github.com/DmitriySalnikov/godot_debug_draw_3d/releases/download/1.7.3/debug-draw-3d_1.7.3.zip/", "url": "https://github.com/DmitriySalnikov/godot_debug_draw_3d/releases/download/1.7.3/debug-draw-3d_1.7.3.zip/",
"source": "zip", // optional — this is the default "source": "zip", // optional — this is the default
// "checkout": "master", // optional — this is the default // "checkout": "master", // optional — this is the default
// "subfolder": "addons/debug_draw_3d" // optional — defaults to "/" // "subfolder": "" // optional — defaults to "/"
}, },
"controller_icons": {
"url": "https://github.com/jembawls/controller_icons_csharp/releases/download/v3.1.6/controller_icons_csharp-3.1.6.zip",
"source": "zip", // optional — this is the default
// "checkout": "master", // optional — this is the default
"subfolder": "controller_icons_csharp-3.1.6/addons/controller_icons/" // optional — defaults to "/"
},
// "editor_theme_explorer": {
// "url": "https://github.com/YuriSizov/godot-editor-theme-explorer/releases/godot-editor-theme-explorer-2.1.1.zip",
// "source": "zip", // optional — this is the default
// // "checkout": "master", // optional — this is the default
// // "subfolder": "controller_icons_csharp-3.1.6/addons/controller_icons/" // optional — defaults to "/"
// },
} }
} }

2
assets

Submodule assets updated: b5ad429c59...235643b054

49
export_presets.cfg Normal file
View File

@@ -0,0 +1,49 @@
[preset.0]
name="Linux"
platform="Linux"
runnable=true
dedicated_server=false
custom_features=""
export_filter="all_resources"
include_filter=""
exclude_filter="/mods"
export_path="../../Builds/ChickenGameTest.x86_64"
patches=PackedStringArray()
patch_delta_encoding=false
patch_delta_compression_level_zstd=19
patch_delta_min_reduction=0.1
patch_delta_include_filters="*"
patch_delta_exclude_filters=""
encryption_include_filters=""
encryption_exclude_filters=""
seed=0
encrypt_pck=false
encrypt_directory=false
script_export_mode=2
[preset.0.options]
custom_template/debug=""
custom_template/release=""
debug/export_console_wrapper=1
binary_format/embed_pck=false
texture_format/s3tc_bptc=true
texture_format/etc2_astc=false
shader_baker/enabled=false
binary_format/architecture="x86_64"
ssh_remote_deploy/enabled=false
ssh_remote_deploy/host="user@host_ip"
ssh_remote_deploy/port="22"
ssh_remote_deploy/extra_args_ssh=""
ssh_remote_deploy/extra_args_scp=""
ssh_remote_deploy/run_script="#!/usr/bin/env bash
export DISPLAY=:0
unzip -o -q \"{temp_dir}/{archive_name}\" -d \"{temp_dir}\"
\"{temp_dir}/{exe_name}\" {cmd_args}"
ssh_remote_deploy/cleanup_script="#!/usr/bin/env bash
pkill -x -f \"{temp_dir}/{exe_name} {cmd_args}\"
rm -rf \"{temp_dir}\""
dotnet/include_scripts_content=false
dotnet/include_debug_symbols=true
dotnet/embed_build_outputs=false

0
mods/.gdignore Normal file
View File

1
mods/modA/mod.json Normal file
View File

@@ -0,0 +1 @@
"Hello"

1
mods/modB/mod.json Normal file
View File

@@ -0,0 +1 @@
"World"

13
mods/raw_onion.yaml Normal file
View File

@@ -0,0 +1,13 @@
Id: raw_onion
# TypeMapping:
# Tags: FoodFactory.Items.Tags
Components:
Name: Raw Onion
Tags: ["onion", "raw", "vegetable", "burnable", "sliceable"]
Temperature:
Fahrenheit: 71.
# Bacteria:
# - salmonella: 400.
BurnableTemp:
Temperature: 477.594

13
mods/raw_potato.yaml Normal file
View File

@@ -0,0 +1,13 @@
Id: raw_potato
# TypeMapping:
# Tags: FoodFactory.Items.Tags
Components:
Name: Raw Potato
Tags: ["potato", "raw", "vegetable", "burnable"]
Temperature:
Fahrenheit: 71.
# Bacteria:
# - salmonella: 400.
BurnableTemp:
Temperature: 100.

15
mods/raw_red_onion.yaml Normal file
View File

@@ -0,0 +1,15 @@
Id: raw_red_onion
Extend:
- raw_onion
# TypeMapping:
# Tags: FoodFactory.Items.Tags
Components:
Name: Raw Red Onion
Color:
R: 1
G: 0
B: 0
Tags:
With: ["red"]
Except: ["sliceable"]

View File

@@ -19,6 +19,10 @@ run/main_scene="res://src/Main.tscn"
config/features=PackedStringArray("4.6", "C#", "Mobile") config/features=PackedStringArray("4.6", "C#", "Mobile")
config/icon="res://icon.png" config/icon="res://icon.png"
[autoload]
ControllerIcons="*uid://cvqaxgmq6by3f"
[debug_draw_3d] [debug_draw_3d]
settings/addon_root_folder="res://addons/debug_draw_3d" settings/addon_root_folder="res://addons/debug_draw_3d"
@@ -41,7 +45,7 @@ naming/scene_name_casing=1
[editor_plugins] [editor_plugins]
enabled=PackedStringArray("res://addons/imrp/plugin.cfg") enabled=PackedStringArray("res://addons/controller_icons/plugin.cfg", "res://addons/explore-editor-theme/plugin.cfg", "res://addons/imrp/plugin.cfg")
[gui] [gui]
@@ -49,6 +53,75 @@ theme/default_font_multichannel_signed_distance_field=true
theme/default_font_generate_mipmaps=true theme/default_font_generate_mipmaps=true
theme/default_theme_scale=2.0 theme/default_theme_scale=2.0
[input]
move_forward={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null)
, Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":1,"axis_value":-1.0,"script":null)
]
}
move_back={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null)
]
}
move_left={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":97,"location":0,"echo":false,"script":null)
]
}
move_right={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null)
]
}
rotate_left={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":81,"key_label":0,"unicode":113,"location":0,"echo":false,"script":null)
]
}
rotate_right={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":69,"key_label":0,"unicode":101,"location":0,"echo":false,"script":null)
]
}
camera_up={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":32,"key_label":0,"unicode":32,"location":0,"echo":false,"script":null)
]
}
camera_down={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194325,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}
place={
"deadzone": 0.2,
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":1,"position":Vector2(97, 9),"global_position":Vector2(106, 57),"factor":1.0,"button_index":1,"canceled":false,"pressed":true,"double_click":false,"script":null)
]
}
mouse_scroll_up={
"deadzone": 0.2,
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":4,"canceled":false,"pressed":false,"double_click":false,"script":null)
]
}
mouse_scroll_down={
"deadzone": 0.2,
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":0,"position":Vector2(0, 0),"global_position":Vector2(0, 0),"factor":1.0,"button_index":5,"canceled":false,"pressed":false,"double_click":false,"script":null)
]
}
move_mouse={
"deadzone": 0.2,
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":1,"position":Vector2(88, 14),"global_position":Vector2(97, 62),"factor":1.0,"button_index":1,"canceled":false,"pressed":true,"double_click":false,"script":null)
]
}
camera_rotate={
"deadzone": 0.2,
"events": [Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"button_mask":2,"position":Vector2(197, 20),"global_position":Vector2(206, 68),"factor":1.0,"button_index":2,"canceled":false,"pressed":true,"double_click":false,"script":null)
]
}
[rendering] [rendering]
renderer/rendering_method="mobile" renderer/rendering_method="mobile"

View File

@@ -0,0 +1,51 @@
namespace FoodFactory;
using System;
using System.Collections.Generic;
using FoodFactory.Blueprints;
using FoodFactory.Conveyors;
using FoodFactory.Recipes;
using FoodFactory.Voxel;
public interface IFoodFactoryApi
{
T? GetApi<T>() where T : class;
IRecipes Recipes { get; }
IBlueprintManger BlueprintManger { get; }
ITickManger TickManger { get; }
IItemRenderer ItemRenderer { get; }
IVoxelGridRegistry GridRegistry { get; }
}
public interface ITickManger
{
delegate void Tick(TickArgs args);
event Tick GameTick;
}
public record struct TickArgs(int Tick, double Delta);
public class TickManger : ITickManger
{
public event ITickManger.Tick? GameTick;
public void BroadCast(TickArgs args) => GameTick?.Invoke(args);
}
public class FoodFactoryApi : IFoodFactoryApi
{
public required IRecipes Recipes { get; set; }
public required IBlueprintManger BlueprintManger { get; set; }
public required ITickManger TickManger { get; set; }
public required IItemRenderer ItemRenderer { get; set; }
public required IVoxelGridRegistry GridRegistry { get; set; }
private readonly Dictionary<Type, object> _otherApi = [];
public T? GetApi<T>() where T : class
{
if (_otherApi.TryGetValue(typeof(T), out var api))
{
return api as T;
}
return null;
}
}

View File

@@ -0,0 +1 @@
uid://dbmq2c5nn8r1q

477
src/App/App.cs Normal file
View File

@@ -0,0 +1,477 @@
namespace FoodFactory;
using Godot;
using Chickensoft.AutoInject;
using Chickensoft.Introspection;
using Chickensoft.GodotNodeInterfaces;
using Chickensoft.LogicBlocks.Auto;
using Chickensoft.LogicBlocks;
using System;
using System.Threading.Tasks;
using Chickensoft.UMLGenerator;
using System.Collections.Generic;
using System.Linq;
using FoodFactory.Modding;
using System.IO.Abstractions;
using Chickensoft.Sync.Primitives;
using Chickensoft.SaveFileBuilder;
using System.IO;
using System.Text.Json;
using Chickensoft.Serialization;
using Chickensoft.Collections;
using Chickensoft.Serialization.Godot;
using SJK.Functional;
// using Shouldly;
public interface IApp : ICanvasLayer, IProvide<IAppRepo>, IProvide<IOptionConfig>, IProvide<ISaveService>;
[Meta(typeof(IAutoNode))]
[ClassDiagram(UseVSCodePaths = true)]
public partial class App : CanvasLayer, IApp
{
public const string GAME_PATH = "res://src/Game/Game.tscn";
public override void _Notification(int what) => this.Notify(what);
[Node] public ISubViewport GameViewPort { get; private set; } = default!;
[Node] public Menu.IMainMenu MainMenu {get;set;} = default!;
[Node] public ICreateGameMenu CreateGameMenu {get;set;} = default!;
[Node] public ILoadingGameMenu LoadingGameMenu {get;set;} = default!;
[Node] public IOptionsMenu OptionsMenu {get;set;} = default!;
[Node] public IModMenu ModsMenu {get;set;} = default!;
public IGame Game { get; set; } = default!;
public IAppRepo AppRepo { get; set; } = default!;
public IOptionConfig OptionConfig { get; set; } = default!;
public IAppLogic AppLogic { get; set; } = default!;
public LogicBlock.Binding AppBinding { get; set; } = default!;
IAppRepo IProvide<IAppRepo>.Value() => AppRepo;
IOptionConfig IProvide<IOptionConfig>.Value() => OptionConfig;
private AutoChannel.Binding _binding;
private static JsonSerializerOptions _serializerOptions = new JsonSerializerOptions()
{
Converters = {
new SerializableTypeConverter(new Blackboard())
},
TypeInfoResolver = new SerializableTypeResolver(),
WriteIndented = true
};
public ISaveService SaveService { get; set; } = new SaveService(){
FilePath =Path.Join(OS.GetUserDataDir(), "saves", "game.json.gz"),
CurrentSave =
Chickensoft.SaveFileBuilder.SaveFile.CreateGZipJsonFile(
Path.Join(OS.GetUserDataDir(), "saves", "game.json.gz"),
// Create a standard JsonSerializerOptions with our introspective type
// resolver and the logic blocks converter.
_serializerOptions
)
};
private Dictionary<IOptionKey,Action<Variant>> _settingsBind = [];
public void BindSetting<[MustBeVariant] T>(IOptionKey<T> option, Action<T> action) => _settingsBind.Add(option, variant => action(variant.As<T>()));
public void Initialize()
{
GodotSerialization.Setup();
var fileSystem =new FileSystem();
AppRepo = new AppRepo()
{
ModManger = new ModManger()
{
AllMods = new AllMods("/home/ronnie/Documents/Godot/Projects/ChickenGameTest/mods", fileSystem),
Profiles = new ModProfiles()
}
};
AppRepo.ModManger.Profiles.Add(("base", [.. AppRepo.ModManger.AllMods.GetMods()]));
AppRepo.ModManger.Profiles.Add(("test", []));
// GD.Print(string.Join(',', AppRepo.ModManger.AllMods.GetMods()));
AppLogic = new AppLogic();
AppLogic.Set(AppRepo);
AppLogic.Set(new AppLogic.Data());
OptionConfig = new TestConfig();
((TestConfig)OptionConfig).OnSettingChanged += AppRepo.OnSettingChanged;
BindSetting(OptionConfig.SettingKeys.Video.RenderQuality, value => GameViewPort.Scaling3DScale = value);
BindSetting(OptionConfig.SettingKeys.Video.FullScreen, value => DisplayServer.WindowSetMode(value ? DisplayServer.WindowMode.Fullscreen : DisplayServer.WindowMode.Windowed));
_binding = AppRepo.AutoChannel.Bind();
_binding.On<IAppRepo.GameSettingChanged>((in setting) =>
{
GD.Print(setting);
if (_settingsBind.GetValue(setting.Setting).HasValue(out var action)){
action(setting.Value);
}
return;
if (setting.Setting is OptionKey<bool> key && key.Key == "FullScreen")
{
DisplayServer.WindowSetMode(setting.Value.As<bool>() ? DisplayServer.WindowMode.Fullscreen : DisplayServer.WindowMode.Windowed );
}
if (setting.Setting is OptionKey<float> key2 && key2.Key == "RenderQuality")
{
GameViewPort.Scaling3DScale = setting.Value.As<float>() / 100f;
}
});
// _binding.On((in IAppRepo.GameExiting exiting) =>
// {
// AppLogic.Input(new AppLogicState.Input.EndGame(AppLogic.Get<AppLogic.Data>().ExitGameReason));
// });
MainMenu.NewGame += OnNewGame;
MainMenu.LastGame += () =>
{
var path = fileSystem.File.ReadAllText(Path.Combine(OS.GetUserDataDir(), "lastGame.txt"));
AppLogic.Input(new AppLogicState.Input.LoadGame());
OnLoadSaveGame(path);
};
MainMenu.LoadGame += OnLoadGame;
MainMenu.Options += OnOptions;
MainMenu.Exit += () => GetTree().Quit();
MainMenu.Mods += OnMods;
// =>
// {
// var vbox = new VBoxContainer();
// vbox.SetAnchorsPreset(Control.LayoutPreset.Center);
// foreach (var item in AppRepo.ModManger.AllMods.GetMods())
// {
// var b = new Button(){Text = item.ModName};
// b.Pressed += () =>
// {
// var p = AppRepo.ModManger.Profiles;
// if (p.Current.Contains(item))
// {
// p.Current.Remove(item);
// }
// else
// {
// p.Current.Add(item);
// }
// b.Text += "a";
// };
// vbox.AddChild(b);
// }
// MainMenu.AddChild(vbox);
// };
OptionsMenu.OnMenuExit += OnMenuExit;
CreateGameMenu.Exit += OnMenuExit;
CreateGameMenu.NewGame += OnNewGame;
CreateGameMenu.LevelSelected += (index) => GD.Print("selected ",index);
LoadingGameMenu.Exit += OnMenuExit;
LoadingGameMenu.LoadGame += index => OnLoadSaveGame(GetSaves()[index]);
MainMenu.SetLastGameVisible(fileSystem.File.Exists(Path.Combine(OS.GetUserDataDir(), "lastGame.txt")));
ModsMenu.ExitModMenu += OnMenuExit;
this.Provide();
}
private void OnLastGame()
{
AppLogic.Input(new AppLogicState.Input.LoadGame());
AppLogic.Input(new AppLogicState.Input.LoadSave());
}
private void OnMenuExit() => AppLogic.Input(new AppLogicState.Input.ExitMenu());
private void OnNewGame() => AppLogic.Input(new AppLogicState.Input.NewGame());
private void OnLoadGame() => AppLogic.Input(new AppLogicState.Input.LoadGame());
private void OnLoadSaveGame(string path)
{
SaveService.FilePath = path;
SaveService.CurrentSave = Chickensoft.SaveFileBuilder.SaveFile.CreateGZipJsonFile(
path,
_serializerOptions);
// Game.LoadExistingGame(GetSaves()[arg]);
GD.Print("save ", path, " loaded.");
AppLogic.Input(new AppLogicState.Input.LoadSave());
}
private void OnOptions() => AppLogic.Input(new AppLogicState.Input.Options());
private void OnMods() => AppLogic.Input(new AppLogicState.Input.Mods());
public void OnReady()
{
AppBinding = AppLogic.Bind();
AppBinding
.OnOutput((in AppLogicState.Output.ShowMainMenu _) =>
{
HideMenus();
MainMenu.Show();
//Fade In from black
})
.OnOutput((in AppLogicState.Output.StartLoadingSaveFile save) =>
{
Game.SaveFileLoaded += OnSaveFileLoaded;
Game.LoadGame();
})
.OnOutput((in AppLogicState.Output.RemoveExistingGame _) =>
{
GameViewPort.RemoveChildEx(Game);
Game.QueueFree();
Game = default!;
})
.OnOutput((in AppLogicState.Output.SetUpGameScene _) =>
{
Game = GD.Load<PackedScene>(GAME_PATH).Instantiate<Game>();
Game.Visible = false;
GameViewPort.AddChildEx(Game);
})
.OnOutput((in AppLogicState.Output.ShowGameCreationWindow _) =>
{
HideMenus();
CreateGameMenu.Initialize([.. GetLevels()]);
CreateGameMenu.Show();
})
.OnOutput((in AppLogicState.Output.ShowGameLoadingWindow _) =>
{
HideMenus();
LoadingGameMenu.Initialize(GetSaves());
LoadingGameMenu.Show();
})
.OnOutput((in AppLogicState.Output.ShowGame _) =>
{
HideMenus();
Game.Show();
})
.OnOutput((in AppLogicState.Output.QuitGame _) =>
{
GetTree().CallDeferred(SceneTree.MethodName.Quit);
})
.OnOutput((in AppLogicState.Output.ShowOptionMenu _) =>
{
HideMenus();
OptionsMenu.Show();
})
.OnOutput((in AppLogicState.Output.ShowModsMenu _) =>
{
HideMenus();
ModsMenu.Show();
})
;
AppLogic.Start<AppLogicState.MainMenu>();
}
private IReadOnlyList<string> GetSaves()
{
var fileSystem = new FileSystem();
var saves = fileSystem.Directory.EnumerateFiles(Path.Join(OS.GetUserDataDir(), "saves"));
return [.. saves];
}
private void OnSaveFileLoaded()
{
Game.SaveFileLoaded -= OnSaveFileLoaded;
AppLogic.Input(new AppLogicState.Input.SaveFileLoaded());
}
public void Setup()
{
}
public void OnExitTree()
{
AppLogic.Stop();
AppLogic.Dispose();
AppRepo.Dispose();
MainMenu.NewGame -= OnNewGame;
MainMenu.LoadGame -= OnLoadGame;
MainMenu.Options -= OnOptions;
MainMenu.Mods -= OnMods;
OptionsMenu.OnMenuExit -= OnMenuExit;
}
public void OnResolved()
{
}
public void HideMenus()
{
// Splash.Hide();
MainMenu.Hide();
CreateGameMenu.Hide();
LoadingGameMenu.Hide();
OptionsMenu.Hide();
ModsMenu.Hide();
}
public IEnumerable<LevelEntry> GetLevels()
{
yield return new LevelEntry("Level1", "res://assets/kenney_conveyor-kit/Sample.png");
yield return new LevelEntry("Level2", "res://assets/KayKit_Restaurant_Bits_1.0_FREE/sample.png");
yield return new LevelEntry("Level3", "res://assets/KayKit_Restaurant_Bits_1.0_FREE/contents.png");
}
public ISaveService Value() => SaveService;
// public override void _Ready()//=> new Test().TestECS();
// {
// // TestButton = GetNode<Button>("%TestButton");
// GD.Print(GameViewPort);
// }
// // public void OnTestButtonPressed() => ButtonPresses++;
// IGameLogic GameLogic = default!;
// LogicBlock.Binding GameLogicBinding = default!;
// Task task = default!;
// public void OnResolved()
// {
// // return;
// GD.Print("GLHF");
// GameLogic = new GameRootLogic();
// GameLogicBinding = GameLogic.Bind();
// GameLogicBinding
// .OnOutput((in GameRootLogicState.Output.QuitConfirmation _) => {
// var popup = new ConfirmationDialog();
// popup.Confirmed += () => GetTree().Quit();
// popup.Canceled += popup.QueueFree;
// popup.DialogText = "You sure you want to Quit";
// popup.OkButtonText = "Quit";
// popup.CancelButtonText = "Return to Menu";
// AddChild(popup);
// popup.PopupCentered();
// })
// .OnInput((in GameRootLogicState.Input.RequestQuit _) =>
// {
// }
// );
// // TestButton.ButtonUp += () => GameLogic.Input(new GameRootLogicState.Input.SaveRequested());
// GameLogic.Start<GameRootLogicState.MainMenu>();
// // GameLogic.Input(new GameLogicState.Input.Start());
// MainMenu.NewGame += () => GD.Print("New");
// MainMenu.LoadGame += () => GD.Print("Load");
// MainMenu.Options += () => GD.Print("Option");
// MainMenu.Mods += () => GD.Print("Mods");
// MainMenu.Exit += () => GameLogic.Input(new GameRootLogicState.Input.RequestQuit());
// }
}
// public interface IGameLogic : ILogicBlock
// {
// }
// [Meta]
// public partial class GameRootLogic : AutoBlock, IGameLogic
// {
// public GameRootLogic()
// {
// Preallocate<GameRootLogicState>();
// }
// }
// [Meta, StateDiagram]
// public abstract partial record GameRootLogicState : LogicBlockState
// {
// public static class Input
// {
// public readonly record struct Start;
// public readonly record struct Playing;
// public readonly record struct RequestQuit;
// // public readonly record struct SaveRequested;
// // public readonly record struct SaveCompleted;
// }
// public static class Output
// {
// // public readonly record struct StartSaving;
// public readonly record struct ShowMainMenu;
// public readonly record struct HideMainMenu;
// public readonly record struct QuitConfirmation;
// public readonly record struct Quit;
// }
// // [Meta]
// // public partial record Paused : GameRootLogicState, IGet<Input.SaveRequested>
// // {
// // public Paused()
// // {
// // this.OnEnter(() =>
// // {
// // GD.Print("enter paluse");
// // Output(new Output.ShowPauseMenu());
// // });
// // this.OnExit(()=>Output(new Output.HidePauseMenu()));
// // }
// // public Type On(in Input.SaveRequested input) => To<Saving>();
// // }
// // [Meta]
// // public partial record Saving : Paused, IGet<Input.SaveCompleted>
// // {
// // public Type On(in Input.SaveCompleted input) => To<MainMenu>();
// // public Saving()
// // {
// // this.OnEnter(
// // ()=>
// // {
// // GD.Print("enter save");
// // Output(new Output.StartSaving());
// // });
// // // this.OnExit(()=> Output(new Output.HidePauseMenu()));
// // }
// // }
// [Meta]
// public partial record MainMenu : GameRootLogicState, IGet<Input.RequestQuit>
// {
// public Type On(in Input.RequestQuit input) => To<ProcessQuit>();
// public MainMenu()
// {
// this.OnEnter(
// ()=>
// {
// GD.Print("enter menu");
// // Output(new Output.StartSaving());
// });
// // this.OnExit(()=> Output(new Output.HidePauseMenu()));
// }
// }
// [Meta]
// public partial record ProcessQuit : GameRootLogicState
// {
// public ProcessQuit()
// {
// this.OnEnter(
// ()=>
// {
// GD.Print("enter menu");
// Output(new Output.QuitConfirmation());
// });
// // this.OnExit(()=> Output(new Output.HidePauseMenu()));
// }
// }
// }

213
src/App/App.g.puml Normal file
View File

@@ -0,0 +1,213 @@
@startuml
package App-Type [[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/App/App.cs]] {
class App {
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/App/App.cs ScriptFile]]
[Properties]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/App/App.cs:47 AppBinding]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/App/App.cs:46 AppLogic]] - [[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/App/AppLogic.cs Script]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/App/App.cs:44 AppRepo]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/App/App.cs:37 CreateGameMenu]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/App/App.cs:42 Game]] - [[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/Game/Game.cs Script]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/App/App.cs:35 GameViewPort]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/App/App.cs:38 LoadingGameMenu]] - [[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/TitleMenu/LoadingGameMenu.cs Script]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/App/App.cs:36 MainMenu]] - [[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/TitleMenu/MainMenu.cs Script]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/App/App.cs:40 ModsMenu]] - [[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/ModMenu/ModMenu.cs Script]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/App/App.cs:45 OptionConfig]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/App/App.cs:39 OptionsMenu]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/App/App.cs:59 SaveService]]
--
[Methods]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/App/App.cs:33 _Notification()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/App/App.cs:70 BindSetting()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/App/App.cs:302 GetLevels()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/App/App.cs:255 GetSaves()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/App/App.cs:291 HideMenus()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/App/App.cs:72 Initialize()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/App/App.cs:273 OnExitTree()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/App/App.cs:168 OnLastGame()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/App/App.cs:177 OnLoadGame()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/App/App.cs:178 OnLoadSaveGame()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/App/App.cs:175 OnMenuExit()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/App/App.cs:190 OnMods()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/App/App.cs:176 OnNewGame()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/App/App.cs:189 OnOptions()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/App/App.cs:192 OnReady()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/App/App.cs:286 OnResolved()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/App/App.cs:263 OnSaveFileLoaded()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/App/App.cs:269 Setup()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/App/App.cs:309 Value()]]
}
class AppLogic {
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/App/AppLogic.cs ScriptFile]]
[Constructors]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/App/AppLogic.cs:14 AppLogic]]()
--
[Methods]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/App/AppLogic.cs:19 OnStartSubscriptions()]]
}
package Game-Type [[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/Game/Game.cs]] {
class Game {
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/Game/Game.cs ScriptFile]]
[Properties]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/Game/Game.cs:40 Environment]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/Game/Game.cs:43 EquipmentTable]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/Game/Game.cs:39 FileSystem]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/Game/Game.cs:54 GameBinding]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/Game/Game.cs:53 GameLogic]] - [[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/Game/GameLogicState.cs Script]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/Game/Game.cs:48 OptionsMenu]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/Game/Game.cs:47 PauseContainer]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/Game/Game.cs:46 PauseMenu]] - [[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/PauseMenu/PauseMenu.cs Script]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/Game/Game.cs:49 PlayerCamera]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/Game/Game.cs:38 SaveFile]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/Game/Game.cs:41 SaveFilePath]]
--
[Methods]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/Game/Game.cs:33 _Notification()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/Game/Game.cs:218 _UnhandledInput()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/Game/Game.cs:132 HideMenus()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/Game/Game.cs:71 Initialize()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/Game/Game.cs:232 Load()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/Game/Game.cs:233 LoadExistingGame()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/Game/Game.cs:125 LoadGame()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/Game/Game.cs:162 OnExitTree()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/Game/Game.cs:102 OnReady()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/Game/Game.cs:165 OnResolved()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/Game/Game.cs:81 OnSaveButton()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/Game/Game.cs:96 OpenOptions()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/Game/Game.cs:84 PauseButtonPressed()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/Game/Game.cs:83 QuitToDesktop()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/Game/Game.cs:82 QuitToMainMenu()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/Game/Game.cs:228 Save()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/Game/Game.cs:106 SaveGame()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/Game/Game.cs:118 SaveGame2()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/Game/Game.cs:138 Setup()]]
}
class GameLogicState {
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/Game/GameLogicState.cs ScriptFile]]
[Constructors]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/Game/GameLogicState.cs:15 GameLogic]]()
}
class PauseMenu {
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/PauseMenu/PauseMenu.cs ScriptFile]]
[Properties]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/PauseMenu/PauseMenu.cs:29 BackToGameButton]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/PauseMenu/PauseMenu.cs:30 OptionsButton]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/PauseMenu/PauseMenu.cs:33 QuitToDesktopButton]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/PauseMenu/PauseMenu.cs:32 QuitToMainMenuButton]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/PauseMenu/PauseMenu.cs:31 SaveButton]]
--
[Methods]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/PauseMenu/PauseMenu.cs:27 _Notification()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/PauseMenu/PauseMenu.cs:44 Initialize()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/PauseMenu/PauseMenu.cs:79 OnExitTree()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/PauseMenu/PauseMenu.cs:55 OnReady()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/PauseMenu/PauseMenu.cs:84 OnResolved()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/PauseMenu/PauseMenu.cs:74 Setup()]]
}
Game::GameLogicState ---> GameLogicState
Game::PauseMenu ---> PauseMenu
}
class LoadingGameMenu {
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/TitleMenu/LoadingGameMenu.cs ScriptFile]]
[Properties]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/TitleMenu/LoadingGameMenu.cs:26 ExitButton]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/TitleMenu/LoadingGameMenu.cs:25 ItemList]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/TitleMenu/LoadingGameMenu.cs:24 LoadGameButton]]
--
[Interface Methods]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/TitleMenu/LoadingGameMenu.cs:32 Initialize()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/TitleMenu/LoadingGameMenu.cs:36 Initialize()]]
[Methods]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/TitleMenu/LoadingGameMenu.cs:22 _Notification()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/TitleMenu/LoadingGameMenu.cs:65 OnExitTree()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/TitleMenu/LoadingGameMenu.cs:56 OnGameLoad()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/TitleMenu/LoadingGameMenu.cs:63 OnItemSelected()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/TitleMenu/LoadingGameMenu.cs:49 OnReady()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/TitleMenu/LoadingGameMenu.cs:70 OnResolved()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/TitleMenu/LoadingGameMenu.cs:45 Setup()]]
}
class MainMenu {
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/TitleMenu/MainMenu.cs ScriptFile]]
[Properties]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/TitleMenu/MainMenu.cs:29 ExitButton]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/TitleMenu/MainMenu.cs:24 LastGameButton]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/TitleMenu/MainMenu.cs:26 LoadGameButton]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/TitleMenu/MainMenu.cs:28 ModsButton]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/TitleMenu/MainMenu.cs:25 NewGameButton]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/TitleMenu/MainMenu.cs:27 OptionsButton]]
--
[Interface Methods]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/TitleMenu/MainMenu.cs:70 SetLastGameVisible()]]
[Methods]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/TitleMenu/MainMenu.cs:23 _Notification()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/TitleMenu/MainMenu.cs:39 Initialize()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/TitleMenu/MainMenu.cs:56 OnExitTree()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/TitleMenu/MainMenu.cs:47 OnReady()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/TitleMenu/MainMenu.cs:65 OnResolved()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/TitleMenu/MainMenu.cs:43 Setup()]]
}
package ModMenu-Type [[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/ModMenu/ModMenu.cs]] {
class ModMenu {
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/ModMenu/ModMenu.cs ScriptFile]]
[Properties]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/ModMenu/ModMenu.cs:27 BackButton]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/ModMenu/ModMenu.cs:26 DisableAllButton]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/ModMenu/ModMenu.cs:25 EnableAllButton]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/ModMenu/ModMenu.cs:20 ModEntryScene]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/ModMenu/ModMenu.cs:22 ModManger]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/ModMenu/ModMenu.cs:23 ModsContainer]] - [[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/ReorderableContainer/ReOrderableContainer.cs Script]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/ModMenu/ModMenu.cs:24 ProfilesButton]]
--
[Methods]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/ModMenu/ModMenu.cs:83 _ExitTree()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/ModMenu/ModMenu.cs:17 _Notification()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/ModMenu/ModMenu.cs:35 OnModProfileSelected()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/ModMenu/ModMenu.cs:28 OnResolved()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/ModMenu/ModMenu.cs:41 OnVisibilityChanged()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/ModMenu/ModMenu.cs:49 RefreshMods()]]
}
class ReOrderableContainer {
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/ReorderableContainer/ReOrderableContainer.cs ScriptFile]]
[Properties]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/ReorderableContainer/ReOrderableContainer.cs:62 AutoScrollRange]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/ReorderableContainer/ReOrderableContainer.cs:61 AutoScrollSpeed]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/ReorderableContainer/ReOrderableContainer.cs:23 HoldDuration]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/ReorderableContainer/ReOrderableContainer.cs:64 IsDebugging]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/ReorderableContainer/ReOrderableContainer.cs:38 IsVertical]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/ReorderableContainer/ReOrderableContainer.cs:60 ScrollContainer]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/ReorderableContainer/ReOrderableContainer.cs:63 ScrollThreshold]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/ReorderableContainer/ReOrderableContainer.cs:25 Separation]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/ReorderableContainer/ReOrderableContainer.cs:24 Speed]]
--
[Methods]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/ReorderableContainer/ReOrderableContainer.cs:148 _Draw()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/ReorderableContainer/ReOrderableContainer.cs:95 _GuiInput()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/ReorderableContainer/ReOrderableContainer.cs:20 _Notification()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/ReorderableContainer/ReOrderableContainer.cs:115 _Process()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/ReorderableContainer/ReOrderableContainer.cs:319 AdjustChildRect()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/ReorderableContainer/ReOrderableContainer.cs:364 AdjustDropZoneRect()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/ReorderableContainer/ReOrderableContainer.cs:294 AdjustExpectedChildRect()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/ReorderableContainer/ReOrderableContainer.cs:404 AsIReOrderableContainer()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/ReorderableContainer/ReOrderableContainer.cs:405 GetVisibleChildren()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/ReorderableContainer/ReOrderableContainer.cs:238 HandleAutoScroll()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/ReorderableContainer/ReOrderableContainer.cs:207 HandleDraggingChildPos()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/ReorderableContainer/ReOrderableContainer.cs:159 HandleInput()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/ReorderableContainer/ReOrderableContainer.cs:267 OnExitTree()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/ReorderableContainer/ReOrderableContainer.cs:276 OnNodeAdded()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/ReorderableContainer/ReOrderableContainer.cs:80 OnReady()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/ReorderableContainer/ReOrderableContainer.cs:284 OnSortChildren()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/ReorderableContainer/ReOrderableContainer.cs:283 OnSortChildrenWrapper()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/ReorderableContainer/ReOrderableContainer.cs:175 OnStartDragging()]]
[[vscode://file//home/ronnie/Documents/Godot/Projects/ChickenGameTest/src/ReorderableContainer/ReOrderableContainer.cs:186 OnStopDragging()]]
}
ModMenu::ReOrderableContainer ---> ReOrderableContainer
}
App::AppLogic ---> AppLogic
App::Game ---> Game
App::LoadingGameMenu ---> LoadingGameMenu
App::MainMenu ---> MainMenu
App::ModMenu ---> ModMenu
}
@enduml

84
src/App/App.tscn Normal file
View File

@@ -0,0 +1,84 @@
[gd_scene format=3 uid="uid://cywpu6lxdjhuu"]
[ext_resource type="Script" uid="uid://bcadf3uhcfy2" path="res://src/App/App.cs" id="1_tn3dc"]
[ext_resource type="PackedScene" uid="uid://nwt8o860iibl" path="res://src/TitleMenu/MainMenu.tscn" id="3_sb8ss"]
[ext_resource type="PackedScene" uid="uid://dkgrn8n1on4ec" path="res://src/TitleMenu/CreateGameMenu.tscn" id="4_qlumy"]
[ext_resource type="PackedScene" uid="uid://b7x2w61dwafxe" path="res://src/TitleMenu/LoadingGameMenu.tscn" id="5_sb8ss"]
[ext_resource type="PackedScene" uid="uid://bf3a5w3h1evaf" path="res://src/OptionsMenu/OptionMenu.tscn" id="6_6skpx"]
[ext_resource type="Script" uid="uid://c0qxjltjv53u7" path="res://addons/controller_icons/objects/ControllerIconTexture.cs" id="6_fj5p6"]
[ext_resource type="PackedScene" uid="uid://cgp6gntheq4g6" path="res://src/ModMenu/ModMenu.tscn" id="7_6skpx"]
[sub_resource type="Texture2D" id="Texture2D_6skpx"]
resource_local_to_scene = false
resource_name = ""
script = ExtResource("6_fj5p6")
path = "move_forward"
metadata/_custom_type_script = "uid://c0qxjltjv53u7"
[node name="App" type="CanvasLayer" unique_id=2068981625]
process_mode = 3
script = ExtResource("1_tn3dc")
[node name="GameSession" type="Control" parent="." unique_id=1185123368]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="SubViewportContainer" type="SubViewportContainer" parent="GameSession" unique_id=2046367171]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
stretch = true
[node name="GameViewPort" type="SubViewport" parent="GameSession/SubViewportContainer" unique_id=1388492347]
unique_name_in_owner = true
own_world_3d = true
handle_input_locally = false
size = Vector2i(720, 720)
render_target_update_mode = 4
[node name="CenterContainer" type="CenterContainer" parent="." unique_id=1905436581]
visible = false
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="VBoxContainer" type="VBoxContainer" parent="CenterContainer" unique_id=1941107825]
layout_mode = 2
[node name="TestButton" type="Button" parent="CenterContainer/VBoxContainer" unique_id=1060152075]
unique_name_in_owner = true
layout_mode = 2
text = "Test Button"
[node name="OptionsMenu" parent="." unique_id=2143215493 instance=ExtResource("6_6skpx")]
unique_name_in_owner = true
visible = false
[node name="MainMenu" parent="." unique_id=869490384 instance=ExtResource("3_sb8ss")]
unique_name_in_owner = true
[node name="CreateGameMenu" parent="." unique_id=1233498611 instance=ExtResource("4_qlumy")]
unique_name_in_owner = true
visible = false
[node name="LoadingGameMenu" parent="." unique_id=1791717647 instance=ExtResource("5_sb8ss")]
unique_name_in_owner = true
visible = false
[node name="Sprite2D" type="Sprite2D" parent="." unique_id=1847137886]
texture = SubResource("Texture2D_6skpx")
[node name="ModsMenu" parent="." unique_id=1651472975 instance=ExtResource("7_6skpx")]
unique_name_in_owner = true
visible = false
[connection signal="pressed" from="CenterContainer/VBoxContainer/TestButton" to="." method="OnTestButtonPressed"]

24
src/App/AppLogic.cs Normal file
View File

@@ -0,0 +1,24 @@
namespace FoodFactory;
using System;
using System.Collections.Generic;
using Chickensoft.Introspection;
using Chickensoft.LogicBlocks;
using Chickensoft.LogicBlocks.Auto;
public interface IAppLogic : ILogicBlock;
[Meta]
public partial class AppLogic : AutoBlock, IAppLogic
{
public AppLogic()
{
Preallocate<AppLogicState>();
}
public override IEnumerable<IDisposable> OnStartSubscriptions()
{
yield return Get<IAppRepo>().AutoChannel.Bind()
.On((in IAppRepo.GameExiting message) => (State as AppLogicState.InGame)?.OnGameExited(message.Reason));
}
}

1
src/App/AppLogic.cs.uid Normal file
View File

@@ -0,0 +1 @@
uid://dugahjx2qt4fq

View File

@@ -0,0 +1,22 @@
namespace FoodFactory;
public abstract partial record AppLogicState
{
public static class Input
{
public readonly record struct ExitMenu;
public readonly record struct NewGame;
public readonly record struct LoadGame;
public readonly record struct LoadSave;
public readonly record struct Options;
public readonly record struct Mods;
public readonly record struct EndGame(ExitGameReason Reason = ExitGameReason.QuitToMenu);
public readonly record struct SaveFileLoaded;
}
}
public enum ExitGameReason
{
QuitToDesktop,
QuitToMenu,
}

View File

@@ -0,0 +1 @@
uid://cvvofc4v4vmr5

View File

@@ -0,0 +1,25 @@
namespace FoodFactory;
public abstract partial record AppLogicState
{
public static class Output
{
public readonly record struct StartLoadingSaveFile();
// public readonly record struct StartLoadingSaveFile(LoadGameArgs Save);
public readonly record struct SetUpGameScene;
public readonly record struct ShowGameCreationWindow;
// public readonly record struct HideGameCreationWindow;
public readonly record struct ShowMainMenu;
public readonly record struct ShowOptionMenu;
public readonly record struct ShowModsMenu;
// public readonly record struct ShowHideMenu;
public readonly record struct PlayGame;
public readonly record struct ShowGame;
// public readonly record struct HideGame;
public readonly record struct RemoveExistingGame;
public readonly record struct ShowGameLoadingWindow;
public readonly record struct QuitGame;
// public readonly record struct HideGameLoadingWindow;
}
}

View File

@@ -0,0 +1 @@
uid://chh4b4blwffef

7
src/App/AppLogicState.cs Normal file
View File

@@ -0,0 +1,7 @@
namespace FoodFactory;
using Chickensoft.Introspection;
using Chickensoft.LogicBlocks;
[Meta, StateDiagram]
public abstract partial record AppLogicState : LogicBlockState;

View File

@@ -0,0 +1 @@
uid://c1315dvv52mf0

View File

@@ -0,0 +1,34 @@
@startuml AppLogicState
state "AppLogicState" as FoodFactory_AppLogicState {
state "CreatingGame" as FoodFactory_AppLogicState_CreatingGame
state "InGame" as FoodFactory_AppLogicState_InGame
state "LeavingGame" as FoodFactory_AppLogicState_LeavingGame
state "LoadingGame" as FoodFactory_AppLogicState_LoadingGame
state "MainMenu" as FoodFactory_AppLogicState_MainMenu
state "ModsMenu" as FoodFactory_AppLogicState_ModsMenu
state "OptionMenu" as FoodFactory_AppLogicState_OptionMenu
}
FoodFactory_AppLogicState_CreatingGame --> FoodFactory_AppLogicState_InGame : NewGame
FoodFactory_AppLogicState_CreatingGame --> FoodFactory_AppLogicState_MainMenu : ExitMenu
FoodFactory_AppLogicState_InGame --> FoodFactory_AppLogicState_LeavingGame : EndGame
FoodFactory_AppLogicState_LeavingGame --> FoodFactory_AppLogicState_MainMenu : EndGame
FoodFactory_AppLogicState_LoadingGame --> FoodFactory_AppLogicState_InGame : LoadSave
FoodFactory_AppLogicState_LoadingGame --> FoodFactory_AppLogicState_MainMenu : ExitMenu
FoodFactory_AppLogicState_MainMenu --> FoodFactory_AppLogicState_CreatingGame : NewGame
FoodFactory_AppLogicState_MainMenu --> FoodFactory_AppLogicState_LoadingGame : LoadGame
FoodFactory_AppLogicState_MainMenu --> FoodFactory_AppLogicState_ModsMenu : Mods
FoodFactory_AppLogicState_MainMenu --> FoodFactory_AppLogicState_OptionMenu : Options
FoodFactory_AppLogicState_ModsMenu --> FoodFactory_AppLogicState_MainMenu : ExitMenu
FoodFactory_AppLogicState_OptionMenu --> FoodFactory_AppLogicState_MainMenu : ExitMenu
FoodFactory_AppLogicState_CreatingGame : OnEnter → ShowGameCreationWindow
FoodFactory_AppLogicState_CreatingGame : OnNewGame → SetUpGameScene
FoodFactory_AppLogicState_InGame : OnEnter → ShowGame
FoodFactory_AppLogicState_LeavingGame : OnEndGame → QuitGame, RemoveExistingGame
FoodFactory_AppLogicState_LoadingGame : OnEnter → ShowGameLoadingWindow
FoodFactory_AppLogicState_LoadingGame : OnLoadSave → SetUpGameScene, StartLoadingSaveFile
FoodFactory_AppLogicState_MainMenu : OnEnter → ShowMainMenu
FoodFactory_AppLogicState_ModsMenu : OnEnter → ShowModsMenu
FoodFactory_AppLogicState_OptionMenu : OnEnter → ShowOptionMenu
@enduml

66
src/App/AppRepo.cs Normal file
View File

@@ -0,0 +1,66 @@
namespace FoodFactory;
using System;
using Chickensoft.Sync.Primitives;
using FoodFactory.Modding;
using Godot;
using TrimKit.VirtualFileSystem;
public interface IAppRepo : IDisposable
{
readonly record struct GameEntering;
readonly record struct GameExiting(ExitGameReason Reason);
readonly record struct GameSettingChanged(IOptionKey Setting, Variant Value);
void OnEnteringGame();
void OnExitGame(ExitGameReason reason = ExitGameReason.QuitToMenu);
void OnSettingChanged(IOptionKey setting, Variant value);
IAutoChannel AutoChannel { get; }
IModManger ModManger { get; }
}
public class AppRepo : IAppRepo
{
private readonly AutoChannel _autoChannel = new();
private bool _disposedValue;
public IAutoChannel AutoChannel => _autoChannel;
// private readonly IModManger _vFSManager;
public required IModManger ModManger {get;init;}
public void OnEnteringGame() => _autoChannel.Send(new IAppRepo.GameEntering());
public void OnExitGame(ExitGameReason reason = ExitGameReason.QuitToMenu) => _autoChannel.Send(new IAppRepo.GameExiting(reason));
public void OnSettingChanged(IOptionKey setting, Variant value) => _autoChannel.Send(new IAppRepo.GameSettingChanged(setting, value));
protected virtual void Dispose(bool disposing)
{
if (!_disposedValue)
{
if (disposing)
{
// TODO: dispose managed state (managed objects)
_autoChannel.Dispose();
ModManger.Dispose();
}
// TODO: free unmanaged resources (unmanaged objects) and override finalizer
// TODO: set large fields to null
_disposedValue = true;
}
}
void IDisposable.Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
public partial class AppLogic
{
public record Data
{
public ExitGameReason ExitGameReason { get; set; }
}
}

1
src/App/AppRepo.cs.uid Normal file
View File

@@ -0,0 +1 @@
uid://cgp65d616yhl0

View File

@@ -0,0 +1,53 @@
namespace FoodFactory;
using System;
using Chickensoft.Introspection;
using Chickensoft.LogicBlocks;
public partial record AppLogicState
{
[Meta]
public partial record CreatingGame : AppLogicState, IGet<Input.ExitMenu>, IGet<Input.NewGame>
{
public CreatingGame()
{
this.OnEnter(() =>
{
Output(new Output.ShowGameCreationWindow());
});
}
public Type On(in Input.ExitMenu input) => To<MainMenu>();
public Type On(in Input.NewGame input)
{
Output(new Output.SetUpGameScene());
return To<InGame>();
}
}
[Meta]
public partial record OptionMenu : AppLogicState, IGet<Input.ExitMenu>
{
public OptionMenu()
{
this.OnEnter(() =>
{
Output(new Output.ShowOptionMenu());
});
}
public Type On(in Input.ExitMenu input) => To<MainMenu>();
}
[Meta]
public partial record ModsMenu : AppLogicState, IGet<Input.ExitMenu>
{
public ModsMenu()
{
this.OnEnter(() =>
{
Output(new Output.ShowModsMenu());
});
}
public Type On(in Input.ExitMenu input) => To<MainMenu>();
}
}

View File

@@ -0,0 +1 @@
uid://coo66h6424rhu

30
src/App/States/InGame.cs Normal file
View File

@@ -0,0 +1,30 @@
namespace FoodFactory;
using System;
using Chickensoft.Introspection;
using Chickensoft.LogicBlocks;
public partial record AppLogicState
{
[Meta]
public partial record InGame : AppLogicState, IGet<Input.EndGame>
{
public InGame()
{
this.OnEnter(() =>
{
Get<IAppRepo>().OnEnteringGame();
Output(new Output.ShowGame());
});
}
public Type On(in Input.EndGame input)
{
// return To<LeavingGame>();
Get<AppLogic.Data>().ExitGameReason = input.Reason;
return To<LeavingGame>();
}
public void OnGameExited(ExitGameReason reason) => Input(new Input.EndGame(reason));
}
}

View File

@@ -0,0 +1 @@
uid://cqaqghb5xytb8

View File

@@ -0,0 +1,33 @@
namespace FoodFactory;
using System;
using Chickensoft.Introspection;
using Chickensoft.LogicBlocks;
public partial record AppLogicState
{
[Meta]
public partial record LeavingGame : AppLogicState, IGet<Input.EndGame>
{
public LeavingGame()
{
this.OnEnter(() =>
{
Input(new Input.EndGame());//PlaceHolder
// Output(new Output.ShowGame());
});
this.OnExit(()=> Get<AppLogic.Data>().ExitGameReason = ExitGameReason.QuitToMenu);
}
public Type On(in Input.EndGame input){
var reason = Get<AppLogic.Data>().ExitGameReason;
Output(new Output.RemoveExistingGame());
if (reason is ExitGameReason.QuitToDesktop)
{
Output(new Output.QuitGame());
}
return To<MainMenu>();
}
}
}

View File

@@ -0,0 +1 @@
uid://xumdrcvr86av

View File

@@ -0,0 +1,29 @@
namespace FoodFactory;
using System;
using Chickensoft.Introspection;
using Chickensoft.LogicBlocks;
public partial record AppLogicState
{
[Meta]
public partial record LoadingGame : AppLogicState, IGet<Input.ExitMenu>, IGet<Input.LoadSave>
{
public LoadingGame()
{
this.OnEnter(() =>
{
Output(new Output.ShowGameLoadingWindow());
});
}
public Type On(in Input.ExitMenu input) => To<MainMenu>();
public Type On(in Input.LoadSave input)
{
Output(new Output.SetUpGameScene());
Output(new Output.StartLoadingSaveFile());
return To<InGame>();
}
}
}

View File

@@ -0,0 +1 @@
uid://bovjntefvngmh

View File

@@ -0,0 +1,26 @@
namespace FoodFactory;
using System;
using Chickensoft.Introspection;
using Chickensoft.LogicBlocks;
public partial record AppLogicState
{
[Meta]
public partial record MainMenu : AppLogicState,
IGet<Input.NewGame>, IGet<Input.LoadGame>, IGet<Input.Options>, IGet<Input.Mods>
{
public MainMenu()
{
this.OnEnter(() =>
{
Output(new Output.ShowMainMenu());
});
}
public Type On(in Input.LoadGame input) => To<LoadingGame>();
public Type On(in Input.NewGame input) => To<CreatingGame>();
public Type On(in Input.Options input) => To<OptionMenu>();
public Type On(in Input.Mods input) => To<ModsMenu>();
}
}

View File

@@ -0,0 +1 @@
uid://c6qr7sej5f6vh

View File

@@ -0,0 +1,497 @@
using Arch.Core;
using Arch.Core.Extensions;
using Arch.Core.Extensions.Dangerous;
using Arch.Core.Utils;
using Arch.LowLevel.Jagged;
using MessagePack;
using MessagePack.Formatters;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Utf8Json;
namespace Arch.Persistence;
/// <summary>
/// The <see cref="SingleEntityFormatter"/> class
/// is a <see cref="IJsonFormatter"/> to (de)serialize a single <see cref="Entity"/>to or from json.
/// </summary>
public partial class SingleEntityFormatter : IMessagePackFormatter<Entity>
{
/// <inheritdoc cref="IMessagePackFormatter{T}.Serialize"/>
public void Serialize(ref MessagePackWriter writer, Entity value, MessagePackSerializerOptions options)
{
// Write id
writer.WriteInt32(value.Id);
#if !PURE_ECS
// Write world
writer.WriteInt32(value.WorldId);
#endif
// Write size
var componentTypes = value.GetComponentTypes();
writer.WriteInt32(componentTypes.Count);
// Write components
foreach (ref var type in componentTypes.Components)
{
// Write type
MessagePackSerializer.Serialize(ref writer, type, options);
// Write component
var cmp = value.Get(type);
MessagePackSerializer.Serialize(ref writer, cmp, options);
}
}
/// <inheritdoc cref="IMessagePackFormatter{T}.Deserialize"/>
public Entity Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options)
{
// Read id
var entityId = reader.ReadInt32();
#if !PURE_ECS
// Read world id
var worldId = reader.ReadInt32();
#endif
// Read size
var size = reader.ReadInt32();
var components = new object[size];
// Read components
for (var index = 0; index < size; index++)
{
// Read type
var type = MessagePackSerializer.Deserialize<ComponentType>(ref reader, options);
var cmp = MessagePackSerializer.Deserialize(type, ref reader, options);
components[index] = cmp!;
}
// Create the entity
var entity = EntityWorld.Create();
EntityWorld.AddRange(entity, components.AsSpan());
return entity;
}
}
/// <summary>
/// The <see cref="EntityFormatter"/> class
/// is a formatter that (de)serializes <see cref="Entity"/> structs.
/// </summary>
public partial class EntityFormatter : IMessagePackFormatter<Entity>
{
/// <inheritdoc cref="IMessagePackFormatter{T}.Serialize"/>
public void Serialize(ref MessagePackWriter writer, Entity value, MessagePackSerializerOptions options)
{
writer.WriteInt32(value.Id);
writer.WriteInt32(value.Version);
}
/// <inheritdoc cref="IMessagePackFormatter{T}.Deserialize"/>
public Entity Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options)
{
// Read id
var id = reader.ReadInt32();
var version = reader.ReadInt32();
return DangerousEntityExtensions.CreateEntityStruct(id, WorldId, version);
}
}
/// <summary>
/// The <see cref="ArrayFormatter"/> class
/// is a <see cref="IJsonFormatter{Array}"/> to (de)serialize <see cref="Array"/>s to or from json.
/// </summary>
public partial class ArrayFormatter : IMessagePackFormatter<Array>
{
/// <inheritdoc cref="IMessagePackFormatter{T}.Serialize"/>
public void Serialize(ref MessagePackWriter writer, Array value, MessagePackSerializerOptions options)
{
var type = value.GetType().GetElementType();
// Write type and size
MessagePackSerializer.Serialize(ref writer, type, options);
writer.WriteUInt32((uint)value.Length);
// Write array
for (var index = 0; index < value.Length; index++)
{
var obj = value.GetValue(index);
MessagePackSerializer.Serialize(ref writer, obj, options);
}
}
/// <inheritdoc cref="IMessagePackFormatter{T}.Deserialize"/>
public Array Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options)
{
// Write type and size
var type = MessagePackSerializer.Deserialize<Type>(ref reader, options);
var size = reader.ReadUInt32();
// Create array
var array = Array.CreateInstance(type, size);
// Read array
for (var index = 0; index < size; index++)
{
var obj = MessagePackSerializer.Deserialize(type, ref reader, options);
array.SetValue(obj, index);
}
return array;
}
}
/// <summary>
/// The <see cref="JaggedArrayFormatter{T}"/> class
/// (de)serializes a <see cref="JaggedArray{T}"/>.
/// </summary>
/// <typeparam name="T">The type stored in the <see cref="JaggedArray{T}"/>.</typeparam>
public partial class JaggedArrayFormatter<T> : IMessagePackFormatter<JaggedArray<T>>
{
private const int CpuL1CacheSize = 16_384;
private readonly T _filler;
/// <summary>
/// Constructor.
/// </summary>
/// <param name="filler">Filler.</param>
public JaggedArrayFormatter(T filler)
{
_filler = filler;
}
/// <inheritdoc cref="IMessagePackFormatter{T}.Serialize"/>
public void Serialize(ref MessagePackWriter writer, JaggedArray<T> value, MessagePackSerializerOptions options)
{
// Write length/capacity and items
writer.WriteInt32(value.Capacity);
for (var index = 0; index < value.Capacity; index++)
{
var item = value[index];
MessagePackSerializer.Serialize(ref writer, item, options);
}
}
/// <inheritdoc cref="IMessagePackFormatter{T}.Deserialize"/>
public JaggedArray<T> Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options)
{
var capacity = reader.ReadInt32();
var jaggedArray = new JaggedArray<T>(CpuL1CacheSize / Unsafe.SizeOf<T>(), _filler, capacity);
for (var index = 0; index < capacity; index++)
{
var item = MessagePackSerializer.Deserialize<T>(ref reader, options);
jaggedArray.Add(index, item);
}
return jaggedArray;
}
}
/// <summary>
/// The <see cref="ComponentTypeFormatter"/> class
/// is a <see cref="IJsonFormatter{ComponentType}"/> to (de)serialize <see cref="ComponentType"/>s to or from json.
/// </summary>
public partial class ComponentTypeFormatter : IMessagePackFormatter<ComponentType>
{
/// <inheritdoc cref="IMessagePackFormatter{T}.Serialize"/>
public void Serialize(ref MessagePackWriter writer, ComponentType value, MessagePackSerializerOptions options)
{
// Write id
writer.WriteUInt32((uint)value.Id);
// Write bytesize
writer.WriteUInt32((uint)value.ByteSize);
}
/// <inheritdoc cref="IMessagePackFormatter{T}.Deserialize"/>
public ComponentType Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options)
{
var id = reader.ReadUInt32();
var bytesize = reader.ReadUInt32();
return new ComponentType((int)id, (int)bytesize);
}
}
/// <summary>
/// The <see cref="ComponentTypeFormatter"/> class
/// is a <see cref="IJsonFormatter{ComponentType}"/> to (de)serialize <see cref="Signature"/>s to or from json.
/// </summary>
public partial class SignatureFormatter : IMessagePackFormatter<Signature>
{
/// <inheritdoc cref="IMessagePackFormatter{T}.Serialize"/>
public void Serialize(ref MessagePackWriter writer, Signature value, MessagePackSerializerOptions options)
{
var componentTypeFormatter = options.Resolver.GetFormatter<ComponentType>() as ComponentTypeFormatter;
// Write count and types
writer.WriteUInt32((uint)value.Count);
foreach (var type in value.Components)
{
componentTypeFormatter!.Serialize(ref writer, type, options);
}
}
/// <inheritdoc cref="IMessagePackFormatter{T}.Deserialize"/>
public Signature Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options)
{
var componentTypeFormatter = options.Resolver.GetFormatter<ComponentType>() as ComponentTypeFormatter;
// Read count
var count = reader.ReadUInt32();
// Read types
var componentTypes = new ComponentType[count];
for (var index = 0; index < count; index++)
{
var componentType = componentTypeFormatter!.Deserialize(ref reader, options);
componentTypes[index] = componentType;
}
return new Signature(componentTypes);
}
}
/// <summary>
/// The <see cref="ComponentTypeFormatter"/> class
/// is a <see cref="IJsonFormatter{ComponentType}"/> to (de)serialize <see cref="ComponentType"/>s to or from json.
/// </summary>
public partial class EntitySlotFormatter : IMessagePackFormatter<EntityData>
{
/// <inheritdoc cref="IMessagePackFormatter{T}.Serialize"/>
public void Serialize(ref MessagePackWriter writer, EntityData value, MessagePackSerializerOptions options)
{
// Write chunk index
writer.WriteUInt32((uint)value.Slot.ChunkIndex);
// Write entity index
writer.WriteUInt32((uint)value.Slot.Index);
}
/// <inheritdoc cref="IMessagePackFormatter{T}.Deserialize"/>
public EntityData Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options)
{
// Read chunk index and entity index
var chunkIndex = reader.ReadUInt32();
var entityIndex = reader.ReadUInt32();
return new EntityData(null!, new Slot((int)entityIndex, (int)chunkIndex), 0);
}
}
/// <summary>
/// The <see cref="WorldFormatter"/> class
/// is a <see cref="IJsonFormatter{World}"/> to (de)serialize <see cref="World"/>s to or from json.
/// </summary>
public partial class WorldFormatter : IMessagePackFormatter<World>
{
/// <inheritdoc cref="IMessagePackFormatter{T}.Serialize"/>
public void Serialize(ref MessagePackWriter writer, World value, MessagePackSerializerOptions options)
{
// Write important meta data
writer.WriteUInt32((uint)value.BaseChunkSize);
writer.WriteUInt32((uint)value.BaseChunkEntityCount);
// Write slots
MessagePackSerializer.Serialize(ref writer, value.GetEntityDataArray(), options);
//Write recycled entity ids
var recycledEntityIDs = value.GetRecycledEntityIds();
MessagePackSerializer.Serialize(ref writer, recycledEntityIDs, options);
// Write archetypes
writer.WriteUInt32((uint)value.Archetypes.Count);
foreach (var archetype in value)
{
MessagePackSerializer.Serialize(ref writer, archetype, options);
}
}
/// <inheritdoc cref="IMessagePackFormatter{T}.Deserialize"/>
public World Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options)
{
// Read important metadata
var baseChunkSize = reader.ReadUInt32();
var baseChunkEntityCount = reader.ReadUInt32();
// Create world and setup formatter
var world = World.Create(chunkSizeInBytes: (int)baseChunkSize, minimumAmountOfEntitiesPerChunk: (int)baseChunkEntityCount);
var archetypeFormatter = options.Resolver.GetFormatter<Archetype>() as ArchetypeFormatter;
var entityFormatter = options.Resolver.GetFormatter<Entity>() as EntityFormatter;
entityFormatter!.WorldId = world.Id;
archetypeFormatter!.World = world;
// Read slots
var slots = MessagePackSerializer.Deserialize<JaggedArray<EntityData>>(ref reader, options);
//Read recycled entity ids
var recycledEntityIDs = MessagePackSerializer.Deserialize<List<(int, int)>>(ref reader, options);
// Forward values to the world
world.SetRecycledEntityIds(recycledEntityIDs);
world.SetEntityDataArray(slots);
world.EnsureCapacity(slots.Capacity);
// Read archetypes
var size = reader.ReadInt32();
List<Archetype> archetypes = new();
for (var index = 0; index < size; index++)
{
var archetype = archetypeFormatter.Deserialize(ref reader, options);
archetypes.Add(archetype);
}
// Set archetypes
world.SetArchetypes(archetypes);
return world;
}
}
/// <summary>
/// The <see cref="ArchetypeFormatter"/> class
/// is a <see cref="IJsonFormatter{Archetype}"/> to (de)serialize <see cref="Archetype"/>s to or from json.
/// </summary>
public partial class ArchetypeFormatter : IMessagePackFormatter<Archetype>
{
/// <inheritdoc cref="IMessagePackFormatter{T}.Serialize"/>
public void Serialize(ref MessagePackWriter writer, Archetype value, MessagePackSerializerOptions options)
{
// Setup formatters
var types = value.Signature;
var chunks = value.Chunks;
var chunkFormatter = options.Resolver.GetFormatter<Chunk>() as ChunkFormatter;
chunkFormatter!.Signature = types;
// Write type array
MessagePackSerializer.Serialize(ref writer, types, options);
// Write lookup array
MessagePackSerializer.Serialize(ref writer, value.GetLookupArray(), options);
// Write chunk size
writer.WriteUInt32((uint)value.ChunkCount);
// Write chunks
for (var index = 0; index < value.ChunkCount; index++)
{
ref var chunk = ref chunks[index];
chunkFormatter.Serialize(ref writer, chunk, options);
}
}
/// <inheritdoc cref="IMessagePackFormatter{T}.Deserialize"/>
public Archetype Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options)
{
var chunkFormatter = options.Resolver.GetFormatter<Chunk>() as ChunkFormatter;
// Types
var types = MessagePackSerializer.Deserialize<Signature>(ref reader, options);
// Archetype lookup array
var lookupArray = MessagePackSerializer.Deserialize<int[]>(ref reader, options);
// Archetype chunk size and list
var chunkSize = reader.ReadUInt32();
// Create archetype
var chunks = new List<Chunk>((int)chunkSize);
var archetype = DangerousArchetypeExtensions.CreateArchetype(World.BaseChunkSize, World.BaseChunkEntityCount, types);
archetype.Chunks.Clear(true);
archetype.SetCount((int)chunkSize - 1);
// Pass types and lookup array to the chunk formatter for saving performance and memory
chunkFormatter!.World = World;
chunkFormatter.Archetype = archetype;
chunkFormatter.Signature = types;
chunkFormatter.LookupArray = lookupArray;
// Deserialise each chunk and put it into the archetype.
var entities = 0;
for (var index = 0; index < chunkSize; index++)
{
var chunk = chunkFormatter.Deserialize(ref reader, options);
chunks.Add(chunk);
entities += chunk.Count;
}
archetype.SetChunks(chunks);
archetype.SetEntities(entities);
return archetype;
}
}
/// <summary>
/// The <see cref="ChunkFormatter"/> class
/// is a <see cref="IJsonFormatter{Chunk}"/> to (de)serialize <see cref="Chunk"/>s to or from json.
/// </summary>
public partial class ChunkFormatter : IMessagePackFormatter<Chunk>
{
/// <inheritdoc cref="IMessagePackFormatter{T}.Serialize"/>
public void Serialize(ref MessagePackWriter writer, Chunk value, MessagePackSerializerOptions options)
{
// Write size
writer.WriteUInt32((uint)value.Count);
// Write capacity
writer.WriteUInt32((uint)value.Capacity);
// Write entitys
MessagePackSerializer.Serialize(ref writer, value.Entities, options);
// Persist arrays as an array...
foreach (var type in Signature.Components)
{
// Write array itself
var array = value.GetArray(type);
MessagePackSerializer.Serialize(ref writer, array, options);
}
}
/// <inheritdoc cref="IMessagePackFormatter{T}.Deserialize"/>
public Chunk Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options)
{
// Read chunk size
var size = reader.ReadUInt32();
// Read chunk size
var capacity = reader.ReadUInt32();
// Read entities
var entities = MessagePackSerializer.Deserialize<Entity[]>(ref reader, options);
// Create chunk
var chunk = DangerousChunkExtensions.CreateChunk((int)capacity, LookupArray, Signature);
entities.CopyTo(chunk.Entities, 0);
chunk.SetSize((int)size);
// Updating World.EntityInfoStorage to their new archetype
for (var index = 0; index < size; index++)
{
ref var entity = ref chunk.Entity(index);
entity = DangerousEntityExtensions.CreateEntityStruct(entity.Id, World.Id, entity.Version);
World.SetArchetype(entity, Archetype);
}
// Persist arrays as an array...
foreach (var type in Signature.Components)
{
// Read array of the type
var array = MessagePackSerializer.Deserialize<Array>(ref reader, options);
var chunkArray = chunk.GetArray(array.GetType().GetElementType()!);
Array.Copy(array, chunkArray, (int)size);
}
return chunk;
}
}

View File

@@ -0,0 +1 @@
uid://b22vip5klbxnq

View File

@@ -0,0 +1,822 @@
namespace Arch.Persistence;
using global::System;
using global::System.Collections.Generic;
using Arch.Core;
using Arch.Core.Extensions;
using Arch.Core.Extensions.Dangerous;
using Arch.LowLevel.Jagged;
using CommunityToolkit.HighPerformance;
using global::System.Runtime.CompilerServices;
using Utf8Json;
/// <summary>
/// The <see cref="SingleEntityFormatter"/> class
/// is a <see cref="IJsonFormatter{Entity}"/> to (de)serialize a single <see cref="Entity"/>to or from json.
/// </summary>
public partial class SingleEntityFormatter : IJsonFormatter<Entity>
{
/// <summary>
/// The <see cref="EntityWorld"/> the entity belongs to.
/// </summary>
internal World EntityWorld { get; set; } = null!;
/// <inheritdoc cref="IJsonFormatter{T}.Serialize"/>
public void Serialize(ref JsonWriter writer, Entity value, IJsonFormatterResolver formatterResolver)
{
writer.WriteBeginObject();
// Write id
writer.WritePropertyName("id");
writer.WriteInt32(value.Id);
writer.WriteValueSeparator();
#if !PURE_ECS
// Write world
writer.WritePropertyName("worldId");
writer.WriteInt32(value.WorldId);
writer.WriteValueSeparator();
#endif
// Write size
var componentTypes = value.GetComponentTypes();
writer.WritePropertyName("size");
writer.WriteInt32(componentTypes.Count);
writer.WriteValueSeparator();
// Write components
writer.WritePropertyName("components");
writer.WriteBeginArray();
foreach (ref var type in componentTypes.Components)
{
// Write type
writer.WriteBeginObject();
writer.WritePropertyName("type");
JsonSerializer.Serialize(ref writer, type);
writer.WriteValueSeparator();
// Write component
writer.WritePropertyName("component");
var cmp = value.Get(type);
JsonSerializer.NonGeneric.Serialize(ref writer, cmp, formatterResolver);
writer.WriteEndObject();
writer.WriteValueSeparator();
}
writer.AdvanceOffset(-1);
writer.WriteEndArray();
writer.WriteEndObject();
}
/// <inheritdoc cref="IJsonFormatter{T}.Deserialize"/>
public Entity Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver)
{
reader.ReadIsBeginObject();
// Read id
reader.ReadPropertyName();
var entityId = reader.ReadInt32();
reader.ReadIsValueSeparator();
#if !PURE_ECS
// Read world id
reader.ReadPropertyName();
var worldId = reader.ReadInt32();
reader.ReadIsValueSeparator();
#endif
// Read size
reader.ReadPropertyName();
var size = reader.ReadInt32();
reader.ReadIsValueSeparator();
var components = new object[size];
var count = 0;
// Read components
reader.ReadPropertyName();
reader.ReadIsBeginArray();
while (!reader.ReadIsEndArrayWithSkipValueSeparator(ref count))
{
reader.ReadIsBeginObject();
// Read type
reader.ReadPropertyName();
var type = JsonSerializer.Deserialize<ComponentType>(ref reader);
reader.ReadIsValueSeparator();
reader.ReadPropertyName();
var cmp = JsonSerializer.NonGeneric.Deserialize(type.Type, ref reader, formatterResolver);
components[count - 1] = cmp;
reader.ReadIsEndObject();
}
// Creat the entity
var entity = EntityWorld.Create();
EntityWorld.AddRange(entity, components.AsSpan());
reader.ReadIsEndObject();
return entity;
}
}
public partial class EntityFormatter : IJsonFormatter<Entity>
{
/// <summary>
/// The <see cref="World.Id"/> all deserialized <see cref="Entity"/>s will belong to.
/// <remarks>Due to the nature of deserialisation and changing world landscape we need to assign new WorldIds to the deserialized entities.</remarks>
/// </summary>
internal int WorldId { get; set; }
/// <inheritdoc cref="IJsonFormatter{T}.Serialize"/>
public void Serialize(ref JsonWriter writer, Entity value, IJsonFormatterResolver formatterResolver)
{
writer.WriteInt32(value.Id);
writer.WriteValueSeparator();
writer.WriteInt32(value.Version);
}
/// <inheritdoc cref="IJsonFormatter{T}.Deserialize"/>
public Entity Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver)
{
// Read id
var id = reader.ReadInt32();
reader.ReadIsValueSeparator();
var version = reader.ReadInt32();
return DangerousEntityExtensions.CreateEntityStruct(id, WorldId, version);
}
}
/// <summary>
/// The <see cref="ArrayFormatter"/> class
/// is a <see cref="IJsonFormatter{Array}"/> to (de)serialize <see cref="Array"/>s to or from json.
/// </summary>
public partial class ArrayFormatter : IJsonFormatter<Array>
{
/// <inheritdoc cref="IJsonFormatter{T}.Serialize"/>
public void Serialize(ref JsonWriter writer, Array value, IJsonFormatterResolver formatterResolver)
{
var type = value.GetType().GetElementType();
// Write type and size
writer.WriteBeginObject();
writer.WritePropertyName("type");
JsonSerializer.Serialize(ref writer, type, formatterResolver);
writer.WriteValueSeparator();
writer.WritePropertyName("length");
writer.WriteUInt32((uint)value.Length);
writer.WriteValueSeparator();
// Write array
writer.WritePropertyName("items");
writer.WriteBeginArray();
for (var index = 0; index < value.Length; index++)
{
var obj = value.GetValue(index);
JsonSerializer.NonGeneric.Serialize(ref writer, obj, formatterResolver);
writer.WriteValueSeparator();
}
writer.AdvanceOffset(-1);
writer.WriteEndArray();
writer.WriteEndObject();
}
/// <inheritdoc cref="IJsonFormatter{T}.Deserialize"/>
public Array Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver)
{
// Write type and size
reader.ReadIsBeginObject();
reader.ReadPropertyName();
var type = JsonSerializer.Deserialize<Type>(ref reader, formatterResolver);
reader.ReadIsValueSeparator();
reader.ReadPropertyName();
var size = reader.ReadUInt32();
reader.ReadIsValueSeparator();
// Create array
var array = Array.CreateInstance(type, size);
// Read array
reader.ReadPropertyName();
reader.ReadIsBeginArray();
for (var index = 0; index < size; index++)
{
var obj = JsonSerializer.NonGeneric.Deserialize(type, ref reader, formatterResolver);
array.SetValue(obj, index);
reader.ReadIsValueSeparator();
}
reader.ReadIsEndArray();
reader.ReadIsEndObject();
return array;
}
}
/// <summary>
/// The <see cref="JaggedArrayFormatter{T}"/> class
/// (de)serializes a <see cref="JaggedArray{T}"/>.
/// </summary>
public partial class JaggedArrayFormatter<T> : IJsonFormatter<JaggedArray<T>>
{
/// <inheritdoc cref="IJsonFormatter{T}.Serialize"/>
public void Serialize(ref JsonWriter writer, JaggedArray<T> value, IJsonFormatterResolver formatterResolver)
{
writer.WriteBeginObject();
// Write length/capacity and items
writer.WritePropertyName("capacity");
writer.WriteInt32(value.Capacity);
writer.WriteValueSeparator();
// Write items
writer.WritePropertyName("items");
writer.WriteBeginArray();
for (var index = 0; index < value.Capacity; index++)
{
var item = value[index];
JsonSerializer.Serialize(ref writer, item, formatterResolver);
writer.WriteValueSeparator();
}
// Cut last value seperator
if (value.Capacity > 0)
{
writer.AdvanceOffset(-1);
}
writer.WriteEndArray();
writer.WriteEndObject();
}
/// <inheritdoc cref="IJsonFormatter{T}.Deserialize"/>
public JaggedArray<T> Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver)
{
reader.ReadIsBeginObject();
// Read capacity;
reader.ReadPropertyName();
var capacity = reader.ReadInt32();
reader.ReadIsValueSeparator();
// Read items
var jaggedArray = new JaggedArray<T>(CpuL1CacheSize / Unsafe.SizeOf<T>(), _filler, capacity);
reader.ReadPropertyName();
reader.ReadIsBeginArray();
for (var index = 0; index < capacity; index++)
{
var item = JsonSerializer.Deserialize<T>(ref reader, formatterResolver);
jaggedArray.Add(index, item);
reader.ReadIsValueSeparator();
}
reader.ReadIsEndArray();
reader.ReadIsEndObject();
return jaggedArray;
}
}
/// <summary>
/// The <see cref="ComponentTypeFormatter"/> class
/// is a <see cref="IJsonFormatter{ComponentType}"/> to (de)serialize <see cref="ComponentType"/>s to or from json.
/// </summary>
public partial class ComponentTypeFormatter : IJsonFormatter<ComponentType>
{
/// <inheritdoc cref="IJsonFormatter{T}.Serialize"/>
public void Serialize(ref JsonWriter writer, ComponentType value, IJsonFormatterResolver formatterResolver)
{
writer.WriteBeginObject();
// Write id
writer.WritePropertyName("id");
writer.WriteUInt32((uint)value.Id);
writer.WriteValueSeparator();
// Write bytesize
writer.WritePropertyName("byteSize");
writer.WriteUInt32((uint)value.ByteSize);
writer.WriteEndObject();
}
/// <inheritdoc cref="IJsonFormatter{T}.Deserialize"/>
public ComponentType Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver)
{
reader.ReadIsBeginObject();
reader.ReadPropertyName();
var id = reader.ReadUInt32();
reader.ReadIsValueSeparator();
reader.ReadPropertyName();
var bytesize = reader.ReadUInt32();
reader.ReadIsValueSeparator();
reader.ReadIsEndObject();
return new ComponentType((int)id, (int)bytesize);
}
}
/// <summary>
/// The <see cref="ComponentTypeFormatter"/> class
/// is a <see cref="IJsonFormatter{ComponentType}"/> to (de)serialize <see cref="Signature"/>s to or from json.
/// </summary>
public partial class SignatureFormatter : IJsonFormatter<Signature>
{
/// <inheritdoc cref="IJsonFormatter{T}.Serialize"/>
public void Serialize(ref JsonWriter writer, Signature value, IJsonFormatterResolver formatterResolver)
{
var componentTypeFormatter = formatterResolver.GetFormatter<ComponentType>() as ComponentTypeFormatter;
writer.WriteBeginObject();
// write Count
writer.WritePropertyName("count");
writer.WriteUInt32((uint)value.Count);
writer.WriteValueSeparator();
// Write components
writer.WritePropertyName("components");
writer.WriteBeginArray();
foreach (var type in value.Components)
{
componentTypeFormatter!.Serialize(ref writer, type, formatterResolver);
writer.WriteValueSeparator();
}
// Cut last value seperator
if (value.Count > 0)
{
writer.AdvanceOffset(-1);
}
writer.WriteEndArray();
writer.WriteEndObject();
}
/// <inheritdoc cref="IJsonFormatter{T}.Deserialize"/>
public Signature Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver)
{
var componentTypeFormatter = formatterResolver.GetFormatter<ComponentType>() as ComponentTypeFormatter;
reader.ReadIsBeginObject();
reader.ReadPropertyName();
// Read count
var count = (int)reader.ReadUInt32();
reader.ReadIsValueSeparator();
// Read types
reader.ReadPropertyName();
reader.ReadIsBeginArray();
var componentTypes = new ComponentType[count];
count = 0;
while (!reader.ReadIsEndArrayWithSkipValueSeparator(ref count))
{
var archetype = componentTypeFormatter!.Deserialize(ref reader, formatterResolver);
componentTypes[count - 1] = (archetype);
}
// Set archetypes
reader.ReadIsEndObject();
return new Signature(componentTypes);
}
}
/// <summary>
/// The <see cref="ComponentTypeFormatter"/> class
/// is a <see cref="IJsonFormatter{ComponentType}"/> to (de)serialize <see cref="ComponentType"/>s to or from json.
/// </summary>
public partial class EntitySlotFormatter : IJsonFormatter<EntityData>
{
/// <inheritdoc cref="IJsonFormatter{T}.Serialize"/>
public void Serialize(ref JsonWriter writer, EntityData value, IJsonFormatterResolver options)
{
writer.WriteBeginObject();
// Write chunk index
writer.WritePropertyName("chunkIndex");
writer.WriteUInt32((uint)value.Slot.ChunkIndex);
writer.WriteValueSeparator();
// Write entity index
writer.WritePropertyName("index");
writer.WriteUInt32((uint)value.Slot.Index);
writer.WriteEndObject();
}
/// <inheritdoc cref="IJsonFormatter{T}.Deserialize"/>
public EntityData Deserialize(ref JsonReader reader, IJsonFormatterResolver options)
{
reader.ReadIsBeginObject();
// Read chunk index
reader.ReadPropertyName();
var chunkIndex = reader.ReadUInt32();
reader.ReadIsValueSeparator();
// Read entity index
reader.ReadPropertyName();
var entityIndex = reader.ReadUInt32();
reader.ReadIsEndObject();
return new EntityData(null!, new Slot((int)entityIndex, (int)chunkIndex), 0);
}
}
/// <summary>
/// The <see cref="WorldFormatter"/> class
/// is a <see cref="IJsonFormatter{World}"/> to (de)serialize <see cref="World"/>s to or from json.
/// </summary>
public partial class WorldFormatter : IJsonFormatter<World>
{
/// <inheritdoc cref="IJsonFormatter{T}.Serialize"/>
public void Serialize(ref JsonWriter writer, World value, IJsonFormatterResolver formatterResolver)
{
//var archetypeFormatter = formatterResolver.GetFormatter<Archetype>();
//var versionsFormatter = formatterResolver.GetFormatter<int[][]>();
//var slotFormatter = formatterResolver.GetFormatter<(int,int)[][]>();
writer.WriteBeginObject();
// Write meta data
writer.WritePropertyName("baseChunkSize");
writer.WriteUInt32((uint)value.BaseChunkSize);
writer.WriteValueSeparator();
writer.WritePropertyName("baseChunkEntityCount");
writer.WriteUInt32((uint)value.BaseChunkEntityCount);
writer.WriteValueSeparator();
// Write slots
writer.WritePropertyName("slots");
JsonSerializer.Serialize(ref writer, value.GetEntityDataArray(), formatterResolver);
writer.WriteValueSeparator();
//Write recycled entity ids
writer.WritePropertyName("recycledEntityIDs");
writer.WriteBeginArray();
var recycledEntityIDs = value.GetRecycledEntityIds();
foreach (var recycledId in recycledEntityIDs)
{
writer.WriteBeginObject();
writer.WritePropertyName("id");
writer.WriteInt32(recycledId.Item1);
writer.WriteValueSeparator();
writer.WritePropertyName("version");
writer.WriteInt32(recycledId.Item2);
writer.WriteEndObject();
writer.WriteValueSeparator();
}
// Cut last value seperator
if (recycledEntityIDs.Count > 0)
{
writer.AdvanceOffset(-1);
}
writer.WriteEndArray();
writer.WriteValueSeparator();
//Write archetypes
writer.WritePropertyName("archetypes");
writer.WriteBeginArray();
foreach (var archetype in value)
{
JsonSerializer.Serialize(ref writer, archetype, formatterResolver);
writer.WriteValueSeparator();
}
// Cut last value seperator
if (value.Archetypes.Count > 0)
{
writer.AdvanceOffset(-1);
}
writer.WriteEndArray();
writer.WriteEndObject();
}
/// <inheritdoc cref="IJsonFormatter{T}.Deserialize"/>
public World Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver)
{
// Create world and setup formatter
var archetypeFormatter = formatterResolver.GetFormatter<Archetype>() as ArchetypeFormatter;
var entityFormatter = formatterResolver.GetFormatter<Entity>() as EntityFormatter;
reader.ReadIsBeginObject();
// Read meta data
reader.ReadPropertyName();
var baseChunkSize = reader.ReadUInt32();
reader.ReadIsValueSeparator();
reader.ReadPropertyName();
var baseChunkEntityCount = reader.ReadUInt32();
reader.ReadIsValueSeparator();
// Construct world
var world = World.Create(chunkSizeInBytes: (int)baseChunkSize, minimumAmountOfEntitiesPerChunk: (int)baseChunkEntityCount);
entityFormatter!.WorldId = world.Id;
archetypeFormatter!.World = world;
// Read slots
reader.ReadPropertyName();
var slots = JsonSerializer.Deserialize<JaggedArray<EntityData>>(ref reader, formatterResolver);
reader.ReadIsValueSeparator();
// Read recycled ids
var count = 0;
List<(int, int)> recycledIds = new();
reader.ReadPropertyName();
reader.ReadIsBeginArray();
while (!reader.ReadIsEndArrayWithSkipValueSeparator(ref count))
{
reader.ReadIsBeginObject();
reader.ReadPropertyName();
var id = reader.ReadInt32();
reader.ReadIsValueSeparator();
reader.ReadPropertyName();
var value = reader.ReadInt32();
reader.ReadIsEndObject();
(int, int) recycledId = new(id, value);
recycledIds.Add(recycledId);
}
reader.ReadIsValueSeparator();
// Forward values to the world
world.SetRecycledEntityIds(recycledIds);
world.SetEntityDataArray(slots);
world.EnsureCapacity(slots.Capacity);
// Read archetypes
count = 0;
List<Archetype> archetypes = new();
reader.ReadPropertyName();
reader.ReadIsBeginArray();
while (!reader.ReadIsEndArrayWithSkipValueSeparator(ref count))
{
var archetype = archetypeFormatter.Deserialize(ref reader, formatterResolver);
archetypes.Add(archetype);
}
// Set archetypes
world.SetArchetypes(archetypes);
reader.ReadIsEndObject();
return world;
}
}
/// <summary>
/// The <see cref="ArchetypeFormatter"/> class
/// is a <see cref="IJsonFormatter{Archetype}"/> to (de)serialize <see cref="Archetype"/>s to or from json.
/// </summary>
public partial class ArchetypeFormatter : IJsonFormatter<Archetype>
{
/// <summary>
/// The <see cref="World"/> which is being used by this formatter during serialisation/deserialisation.
/// </summary>
internal World World { get; set; } = null!;
/// <inheritdoc cref="IJsonFormatter{T}.Serialize"/>
public void Serialize(ref JsonWriter writer, Archetype value, IJsonFormatterResolver formatterResolver)
{
// Setup formatters
var types = value.Signature;
var chunks = value.Chunks;
var chunkFormatter = formatterResolver.GetFormatter<Chunk>() as ChunkFormatter;
chunkFormatter!.Signature = types;
writer.WriteBeginObject();
// Write type array
writer.WritePropertyName("types");
JsonSerializer.Serialize(ref writer, types, formatterResolver);
writer.WriteValueSeparator();
// Write lookup array
writer.WritePropertyName("lookup");
JsonSerializer.Serialize(ref writer, value.GetLookupArray(), formatterResolver);
writer.WriteValueSeparator();
// Write chunk size
writer.WritePropertyName("chunkCount");
writer.WriteUInt32((uint)value.ChunkCount);
writer.WriteValueSeparator();
// Write chunks
writer.WritePropertyName("chunks");
writer.WriteBeginArray();
for (var index = 0; index < value.ChunkCount; index++)
{
ref var chunk = ref chunks[index];
chunkFormatter.Serialize(ref writer, chunk, formatterResolver);
writer.WriteValueSeparator();
}
// Trim last value separator
if (value.ChunkCount > 0)
{
writer.AdvanceOffset(-1);
}
writer.WriteEndArray();
writer.WriteEndObject();
}
/// <inheritdoc cref="IJsonFormatter{T}.Deserialize"/>
public Archetype Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver)
{
var chunkFormatter = formatterResolver.GetFormatter<Chunk>() as ChunkFormatter;
reader.ReadIsBeginObject();
// Types
reader.ReadPropertyName();
var types = JsonSerializer.Deserialize<Signature>(ref reader, formatterResolver);
reader.ReadIsValueSeparator();
// Archetype lookup array
reader.ReadPropertyName();
var lookupArray = JsonSerializer.Deserialize<int[]>(ref reader, formatterResolver);
reader.ReadIsValueSeparator();
// Archetype chunk size and list
reader.ReadPropertyName();
var chunkCount = reader.ReadUInt32();
reader.ReadIsValueSeparator();
// Create archetype
var chunks = new List<Chunk>((int)chunkCount);
var archetype = DangerousArchetypeExtensions.CreateArchetype(World.BaseChunkSize, World.BaseChunkEntityCount, types);
archetype.Chunks.Clear(true);
archetype.SetCount((int)chunkCount - 1);
// Pass types and lookup array to the chunk formatter for saving performance and memory
chunkFormatter!.World = World;
chunkFormatter.Archetype = archetype;
chunkFormatter.Signature = types;
chunkFormatter.LookupArray = lookupArray;
// Deserialise each chunk and put it into the archetype.
reader.ReadPropertyName();
reader.ReadIsBeginArray();
var entities = 0;
for (var index = 0; index < chunkCount; index++)
{
var chunk = chunkFormatter.Deserialize(ref reader, formatterResolver);
chunks.Add(chunk);
entities += chunk.Count;
reader.ReadIsValueSeparator();
}
archetype.SetChunks(chunks);
archetype.SetEntities(entities);
reader.ReadIsEndArray();
reader.ReadIsEndObject();
return archetype;
}
}
/// <summary>
/// The <see cref="ChunkFormatter"/> class
/// is a <see cref="IJsonFormatter{Chunk}"/> to (de)serialize <see cref="Chunk"/>s to or from json.
/// </summary>
public partial class ChunkFormatter : IJsonFormatter<Chunk>
{
/// <summary>
/// The <see cref="Archetype"/> the current (de)serialized <see cref="Chunk"/> belongs to.
/// Since chunks do not know this, we need to pass this information along it.
/// </summary>
internal World World { get; set; } = null!;
/// <summary>
/// The <see cref="Archetype"/> the current (de)serialized <see cref="Chunk"/> belongs to.
/// Since chunks do not know this, we need to pass this information along it.
/// </summary>
internal Archetype Archetype { get; set; } = null!;
/// <summary>
/// The types used in the <see cref="Chunk"/> in each <see cref="Chunk"/> (de)serialized by this formatter.
/// <remarks>Since <see cref="Chunk"/> does not have a reference to them and its controlled by its <see cref="Archetype"/>.</remarks>
/// </summary>
internal Signature Signature { get; set; } = Signature.Null;
/// <summary>
/// The lookup array used by each <see cref="Chunk"/> (de)serialized by this formatter.
/// <remarks>Since <see cref="Chunk"/> does not have a reference to them and its controlled by its <see cref="Archetype"/>.</remarks>
/// </summary>
internal int[] LookupArray { get; set; } = Array.Empty<int>();
/// <inheritdoc cref="IJsonFormatter{T}.Serialize"/>
public void Serialize(ref JsonWriter writer, Chunk value, IJsonFormatterResolver formatterResolver)
{
writer.WriteBeginObject();
// Write size
writer.WritePropertyName("count");
writer.WriteUInt32((uint)value.Count);
writer.WriteValueSeparator();
// Write capacity
writer.WritePropertyName("capacity");
writer.WriteUInt32((uint)value.Capacity);
writer.WriteValueSeparator();
// Write entitys
writer.WritePropertyName("entities");
JsonSerializer.NonGeneric.Serialize(ref writer, value.Entities, formatterResolver);
writer.WriteValueSeparator();
// Persist arrays as an array...
writer.WritePropertyName("arrays");
writer.WriteBeginArray();
foreach (var type in Signature.Components)
{
// Write array itself
var array = value.GetArray(type);
JsonSerializer.Serialize(ref writer, array, formatterResolver);
writer.WriteValueSeparator();
}
// Remove trailing
if (Signature.Count > 0)
{
writer.AdvanceOffset(-1);
}
writer.WriteEndArray();
writer.WriteEndObject();
}
/// <inheritdoc cref="IJsonFormatter{T}.Deserialize"/>
public Chunk Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver)
{
reader.ReadIsBeginObject();
// Read chunk size
reader.ReadPropertyName();
var size = reader.ReadUInt32();
reader.ReadIsValueSeparator();
// Read chunk size
reader.ReadPropertyName();
var capacity = reader.ReadUInt32();
reader.ReadIsValueSeparator();
// Read entities
reader.ReadPropertyName();
var entities = JsonSerializer.Deserialize<Entity[]>(ref reader, formatterResolver);
reader.ReadIsValueSeparator();
// Create chunk
var chunk = DangerousChunkExtensions.CreateChunk((int)capacity, LookupArray, Signature);
entities.CopyTo(chunk.Entities, 0);
chunk.SetSize((int)size);
// Updating World.EntityInfoStorage to their new archetype
for (var index = 0; index < size; index++)
{
ref var entity = ref chunk.Entity(index);
entity = DangerousEntityExtensions.CreateEntityStruct(entity.Id, World.Id, entity.Version);
World.SetArchetype(entity, Archetype);
}
// Persist arrays as an array...
reader.ReadPropertyName();
reader.ReadIsBeginArray();
foreach (var type in Signature)
{
// Read array of the type
var array = JsonSerializer.Deserialize<Array>(ref reader, formatterResolver);
var chunkArray = chunk.GetArray(array.GetType().GetElementType()!);
Array.Copy(array, chunkArray, (int)size);
reader.ReadIsValueSeparator();
}
reader.ReadIsEndArray();
reader.ReadIsEndObject();
return chunk;
}
}

View File

@@ -0,0 +1 @@
uid://ddnds4ritwn2f

View File

@@ -0,0 +1,410 @@
namespace Arch.Persistence;
using Arch.Core;
using MessagePack;
using MessagePack.Formatters;
using global::System;
using global::System.Buffers;
using global::System.IO;
using Utf8Json;
using Utf8Json.Resolvers;
using DateTimeFormatter = Utf8Json.Formatters.DateTimeFormatter;
using NullableDateTimeFormatter = Utf8Json.Formatters.NullableDateTimeFormatter;
/// <summary>
/// The <see cref="IArchSerializer"/> interface
/// represents an interface with shared methods to (de)serialize worlds and entities.
/// <remarks>It might happen that the serialized object is too large to fit into a regular c# byte-array. In this case use the <see cref="IBufferWriter{T}"/>-API.</remarks>
/// </summary>
public interface IArchSerializer
{
/// <summary>
/// Serializes an <see cref="Entity"/> to a <see cref="byte"/>-array.
/// </summary>
/// <param name="world">The <see cref="World"/>.</param>
/// <param name="entity">The <see cref="Entity"/>.</param>
byte[] Serialize(World world, Entity entity);
/// <summary>
/// Serializes an <see cref="Entity"/> to a <see cref="Stream"/> e.g. a File or existing array.
/// </summary>
/// <param name="stream">The <see cref="Stream"/>.</param>
/// <param name="world">The <see cref="World"/>.</param>
/// <param name="entity">The <see cref="Entity"/>.</param>
void Serialize(Stream stream, World world, Entity entity);
/// <summary>
/// Serializes an <see cref="Entity"/> to a <see cref="IBufferWriter{T}"/> e.g. a File or existing array.
/// </summary>
/// <param name="writer">The <see cref="IBufferWriter{T}"/>.</param>
/// <param name="world">The <see cref="World"/>.</param>
/// <param name="entity">The <see cref="Entity"/>.</param>
void Serialize(IBufferWriter<byte> writer, World world, Entity entity);
/// <summary>
/// Deserializes an <see cref="Entity"/> from its bytes to an real <see cref="Entity"/> in a <see cref="World"/>.
/// <remarks>The new <see cref="Entity.Id"/> and <see cref="Entity.WorldId"/> will differ.</remarks>
/// </summary>
/// <param name="world">The <see cref="World"/>.</param>
/// <param name="entity">The <see cref="Entity"/>.</param>
/// <returns></returns>
Entity Deserialize(World world, byte[] entity);
/// <summary>
/// Deserializes an <see cref="Entity"/> from its bytes to an real <see cref="Entity"/> in a <see cref="World"/>.
/// <remarks>The new <see cref="Entity.Id"/> and <see cref="Entity.WorldId"/> will differ.</remarks>
/// </summary>
/// <param name="stream">The <see cref="Stream"/>.</param>
/// <param name="world">The <see cref="World"/>.</param>
/// <returns></returns>
Entity Deserialize(Stream stream, World world);
/// <summary>
/// Serializes a <see cref="World"/> to a <see cref="byte"/>-array.
/// </summary>
/// <param name="world">The <see cref="World"/>.</param>
byte[] Serialize(World world);
/// <summary>
/// Serializes a <see cref="World"/> to a <see cref="Stream"/>.
/// </summary>
/// <param name="stream">The <see cref="Stream"/>.</param>
/// <param name="world">The <see cref="World"/>.</param>
void Serialize(Stream stream, World world);
/// <summary>
/// Serializes a <see cref="World"/> to a <see cref="IBufferWriter{T}"/>.
/// </summary>
/// <param name="writer">The <see cref="IBufferWriter{T}"/>.</param>
/// <param name="world">The <see cref="World"/>.</param>
void Serialize(IBufferWriter<byte> writer, World world);
/// <summary>
/// Deserializes a byte-array into a <see cref="World"/>.
/// </summary>
/// <param name="world">The <see cref="World"/> as an byte-array.</param>
/// <returns>The new <see cref="World"/>.</returns>
World Deserialize(byte[] world);
/// <summary>
/// Deserializes a byte-array into a <see cref="World"/>.
/// </summary>
/// <param name="stream">The <see cref="Stream"/>.</param>
/// <returns>The new <see cref="World"/>.</returns>
World Deserialize(Stream stream);
}
/// <summary>
/// The <see cref="ArchBinarySerializer"/> class
/// represents a binary serializer for arch to (de)serialize single entities and whole worlds by binary.
/// </summary>
public class ArchBinarySerializer : IArchSerializer
{
/// <summary>
/// The default formatters used to (de)serialize the <see cref="World"/>.
/// </summary>
private readonly IMessagePackFormatter[] _formatters =
[
new WorldFormatter(),
new ArchetypeFormatter(),
new ChunkFormatter(),
new ArrayFormatter(),
new ComponentTypeFormatter(),
new SignatureFormatter(),
new EntitySlotFormatter(),
new EntityFormatter(),
new JaggedArrayFormatter<int>(-1),
new JaggedArrayFormatter<(int,int)>((-1,-1)),
new JaggedArrayFormatter<EntityData>(new EntityData(null!, new Slot(-1,-1), -1))
];
/// <summary>
/// The default formatters used to (de)serialize a single <see cref="Entity"/>.
/// </summary>
private readonly IMessagePackFormatter[] _singleEntityFormatters =
[
new ComponentTypeFormatter(),
new SignatureFormatter(),
new SingleEntityFormatter()
];
/// <summary>
/// The standard <see cref="MessagePackSerializerOptions"/> for world (de)serialization.
/// </summary>
private readonly MessagePackSerializerOptions _options;
/// <summary>
/// The standard <see cref="MessagePackSerializerOptions"/> for single entity (de)serialization.
/// </summary>
private readonly MessagePackSerializerOptions _singleEntityOptions;
/// <summary>
/// The static constructor gets called during compile time to setup the serializer.
/// </summary>
public ArchBinarySerializer(params IMessagePackFormatter[] custFormatters)
{
// Register all important jsonformatters
_options = MessagePackSerializerOptions.Standard.WithResolver(
MessagePack.Resolvers.CompositeResolver.Create(
[.. _formatters, .. custFormatters],
[
MessagePack.Resolvers.BuiltinResolver.Instance,
MessagePack.Resolvers.ContractlessStandardResolverAllowPrivate.Instance
]
)
);
_singleEntityOptions = MessagePackSerializerOptions.Standard.WithResolver(
MessagePack.Resolvers.CompositeResolver.Create(
[.. _singleEntityFormatters, .. custFormatters],
[
MessagePack.Resolvers.BuiltinResolver.Instance,
MessagePack.Resolvers.ContractlessStandardResolverAllowPrivate.Instance
]
)
);
}
/// <inheritdoc/>
public byte[] Serialize(World world, Entity entity)
{
(_singleEntityFormatters[2] as SingleEntityFormatter)!.EntityWorld = world;
return MessagePackSerializer.Serialize(entity, _singleEntityOptions);
}
/// <inheritdoc/>
public void Serialize(Stream stream, World world, Entity entity)
{
(_singleEntityFormatters[2] as SingleEntityFormatter)!.EntityWorld = world;
MessagePackSerializer.Serialize(stream, entity, _singleEntityOptions);
}
/// <inheritdoc/>
public void Serialize(IBufferWriter<byte> writer, World world, Entity entity)
{
(_singleEntityFormatters[2] as SingleEntityFormatter)!.EntityWorld = world;
MessagePackSerializer.Serialize(writer, entity, _singleEntityOptions);
}
/// <inheritdoc/>
public Entity Deserialize(World world, byte[] entity)
{
(_singleEntityFormatters[2] as SingleEntityFormatter)!.EntityWorld = world;
return MessagePackSerializer.Deserialize<Entity>(entity, _singleEntityOptions);
}
/// <inheritdoc/>
public Entity Deserialize(Stream stream, World world)
{
(_singleEntityFormatters[2] as SingleEntityFormatter)!.EntityWorld = world;
return MessagePackSerializer.Deserialize<Entity>(stream, _singleEntityOptions);
}
/// <inheritdoc/>
public byte[] Serialize(World world)
{
return MessagePackSerializer.Serialize(world, _options);
;
}
/// <inheritdoc/>
public void Serialize(Stream stream, World world) => MessagePackSerializer.Serialize(stream, world, _options);
/// <inheritdoc/>
public void Serialize(IBufferWriter<byte> writer, World world) => MessagePackSerializer.Serialize(writer, world, _options);
/// <inheritdoc/>
public World Deserialize(byte[] world) => MessagePackSerializer.Deserialize<World>(world, _options);
/// <inheritdoc/>
public World Deserialize(Stream stream) => MessagePackSerializer.Deserialize<World>(stream, _options);
}
/// <summary>
/// The <see cref="ArchJsonSerializer"/> class
/// represents a json serializer for arch to (de)serialize single entities and whole worlds by binary.
/// </summary>
public class ArchJsonSerializer : IArchSerializer
{
/// <summary>
/// The default formatters used to (de)serialize the <see cref="World"/>.
/// </summary>
private readonly IJsonFormatter[] _formatters = [
new WorldFormatter(),
new ArchetypeFormatter(),
new ChunkFormatter(),
new ArrayFormatter(),
new ComponentTypeFormatter(),
new SignatureFormatter(),
new EntitySlotFormatter(),
new EntityFormatter(),
new JaggedArrayFormatter<int>(-1),
new JaggedArrayFormatter<(int,int)>((-1,-1)),
new JaggedArrayFormatter<EntityData>(new EntityData(null!, new Slot(-1, -1), -1)),
new DateTimeFormatter("yyyy-MM-dd HH:mm:ss"),
new NullableDateTimeFormatter("yyyy-MM-dd HH:mm:ss")
];
/// <summary>
/// The default formatters used to (de)serialize a single <see cref="Entity"/>.
/// </summary>
private readonly IJsonFormatter[] _singleEntityFormatters =
[
new ComponentTypeFormatter(),
new SignatureFormatter(),
new SingleEntityFormatter(),
new DateTimeFormatter("yyyy-MM-dd HH:mm:ss"),
new NullableDateTimeFormatter("yyyy-MM-dd HH:mm:ss")
];
// It can `not` garbage collect and create is slightly high cost.
// so you should store to static field.
private readonly IJsonFormatterResolver _formatterResolver;
// CompositeResolver.Create can create dynamic composite resolver.
// It can `not` garbage collect and create is slightly high cost.
// so you should store to static field.
private readonly IJsonFormatterResolver _singleEntityFormatterResolver;
/// <summary>
/// The static constructor gets called during compile time to setup the serializer.
/// </summary>
public ArchJsonSerializer(params IJsonFormatter[] custFormatters)
{
// Register all important jsonformatters
_formatterResolver = CompositeResolver.Create(
[.. _formatters, .. custFormatters],
[
EnumResolver.UnderlyingValue,
StandardResolver.AllowPrivateExcludeNullSnakeCase,
BuiltinResolver.Instance,
DynamicGenericResolver.Instance,
]
);
_singleEntityFormatterResolver = CompositeResolver.Create(
[.. _singleEntityFormatters, .. custFormatters],
[
EnumResolver.UnderlyingValue,
StandardResolver.AllowPrivateExcludeNullSnakeCase,
]
);
}
/// <summary>
/// The static constructor gets called during compile time to setup the serializer.
/// This variant allows custom resolvers to be passed in as well.
/// </summary>
public ArchJsonSerializer(IJsonFormatter[] custFormatters, IJsonFormatterResolver[] custResolvers)
{
// Register all important jsonformatters
_formatterResolver = CompositeResolver.Create(
[.. _formatters, .. custFormatters],
[
.. custResolvers,
.. new[] {
EnumResolver.UnderlyingValue,
StandardResolver.AllowPrivateExcludeNullSnakeCase,
BuiltinResolver.Instance,
DynamicGenericResolver.Instance,
},
]);
_singleEntityFormatterResolver = CompositeResolver.Create(
[.. _singleEntityFormatters, .. custFormatters],
[
.. custResolvers,
.. new[] {
EnumResolver.UnderlyingValue,
StandardResolver.AllowPrivateExcludeNullSnakeCase,
},
]);
}
/// <summary>
/// Serializes the given <see cref="World"/> to a json-string.
/// </summary>
/// <param name="world">The <see cref="World"/> to serialize.</param>
/// <returns>Its json-string.</returns>
public string ToJson(World world) => JsonSerializer.ToJsonString(world, _formatterResolver);
/// <summary>
/// Serializes the given <see cref="Entity"/> to a json-string.
/// </summary>
/// <param name="world">The <see cref="World"/> the entity belongs to..</param>
/// <param name="entity">The <see cref="Entity"/>.</param>
/// <returns>Its json-string.</returns>
/// <returns>A json-string of the entity with all its components.</returns>
public string ToJson(World world, Entity entity)
{
(_singleEntityFormatters[2] as SingleEntityFormatter)!.EntityWorld = world;
return JsonSerializer.ToJsonString(entity, _singleEntityFormatterResolver);
}
/// <summary>
/// Deserializes the given json <see cref="string"/> to a <see cref="World"/>.
/// </summary>
/// <param name="jsonWorld">The json <see cref="string"/> to deserialize.</param>
/// <returns>A new <see cref="World"/>.</returns>
public World FromJson(string jsonWorld) => JsonSerializer.Deserialize<World>(jsonWorld, _formatterResolver);
/// <summary>
/// Deserializes the given json <see cref="string"/> to a <see cref="World"/>.
/// <remarks>The deserialized <see cref="Entity"/> will receive a new id and a new worldId.</remarks>
/// </summary>
/// <param name="world">The <see cref="World"/> to deserialize the entity into.</param>
/// <param name="jsonEntity">The json <see cref="string"/> of the entity to deserialize.</param>
/// <returns>A new <see cref="Entity"/>.</returns>
public Entity FromJson(World world, string jsonEntity)
{
(_singleEntityFormatters[2] as SingleEntityFormatter)!.EntityWorld = world;
return JsonSerializer.Deserialize<Entity>(jsonEntity, _singleEntityFormatterResolver);
}
/// <inheritdoc/>
public byte[] Serialize(World world, Entity entity)
{
(_singleEntityFormatters[2] as SingleEntityFormatter)!.EntityWorld = world;
return JsonSerializer.Serialize(entity, _singleEntityFormatterResolver);
}
/// <inheritdoc/>
public void Serialize(Stream stream, World world, Entity entity)
{
(_singleEntityFormatters[1] as SingleEntityFormatter)!.EntityWorld = world;
JsonSerializer.Serialize(stream, entity, _singleEntityFormatterResolver);
}
/// <inheritdoc/>
public void Serialize(IBufferWriter<byte> writer, World world, Entity entity) => throw new NotImplementedException();
/// <inheritdoc/>
public Entity Deserialize(World world, byte[] entity)
{
(_singleEntityFormatters[2] as SingleEntityFormatter)!.EntityWorld = world;
return JsonSerializer.Deserialize<Entity>(entity, _singleEntityFormatterResolver);
}
/// <inheritdoc/>
public Entity Deserialize(Stream stream, World world)
{
(_singleEntityFormatters[2] as SingleEntityFormatter)!.EntityWorld = world;
return JsonSerializer.Deserialize<Entity>(stream, _singleEntityFormatterResolver);
}
/// <inheritdoc/>
public byte[] Serialize(World world) => JsonSerializer.Serialize(world, _formatterResolver);
/// <inheritdoc/>
public void Serialize(Stream stream, World world) => JsonSerializer.Serialize(stream, world, _formatterResolver);
/// <inheritdoc/>
public void Serialize(IBufferWriter<byte> writer, World world) => throw new NotImplementedException();
/// <inheritdoc/>
public World Deserialize(byte[] world) => JsonSerializer.Deserialize<World>(world, _formatterResolver);
/// <inheritdoc/>
public World Deserialize(Stream stream) => JsonSerializer.Deserialize<World>(stream, _formatterResolver);
}

View File

@@ -0,0 +1 @@
uid://8hihhfy6hol

View File

@@ -0,0 +1,139 @@
using System;
using System.Buffers;
using System.IO;
namespace Arch.Persistence;
/// <summary>
/// The <see cref="StreamBufferWriter"/> class
/// is a small wrapper around a <see cref="Stream"/> implementing a <see cref="IBufferWriter{T}"/>.
/// It buffers incoming bytes in an internally stored array and flushes it regulary into the <see cref="_destination"/>-<see cref="Stream"/>.
/// </summary>
public sealed class StreamBufferWriter : IBufferWriter<byte>, IDisposable
{
/// <summary>
/// The buffer.
/// </summary>
private byte[] _buffer;
/// <summary>
/// The <see cref="Stream"/>.
/// </summary>
private readonly Stream _destination;
/// <summary>
// / If this instance owns the <see cref="_destination"/> stream.
/// </summary>
private readonly bool _ownsStream;
/// <summary>
/// The current position and the amount of total leased bytes.
/// </summary>
private int _position, _leased;
/// <summary>
/// Creates a new <see cref="StreamBufferWriter"/> instance.
/// </summary>
/// <param name="destination">The <see cref="Stream"/>.</param>
/// <param name="bufferSize">The buffer-size of the <see cref="_buffer"/>.</param>
/// <param name="ownsStream">If it owns the stream.</param>
public StreamBufferWriter(Stream destination, int bufferSize = 1024, bool ownsStream = true)
{
const int minBufferSize = 128;
if (bufferSize < minBufferSize)
{
bufferSize = minBufferSize;
}
_buffer = ArrayPool<byte>.Shared.Rent(bufferSize);
_ownsStream = ownsStream;
_destination = destination;
}
/// <summary>
/// Leases an amount of bytes from the <see cref="_buffer"/>.
/// </summary>
/// <param name="sizeHint">The total amount.</param>
/// <returns>The leased amount.</returns>
private int Lease(int sizeHint)
{
var available = _buffer.Length - _position;
if (available < sizeHint && _position != 0)
{ // try to get more
Flush();
available = _buffer.Length - _position;
}
_leased = available;
return available;
}
/// <summary>
/// Flushes the buffered bytes to the <see cref="_destination"/>.
/// </summary>
/// <param name="flushUnderlyingStream">If it also should flush the <see cref="Stream"/>.</param>
public void Flush(bool flushUnderlyingStream = false)
{
if (_position != 0)
{
_destination.Write(_buffer, 0, _position);
_position = 0;
}
if (flushUnderlyingStream)
{
_destination.Flush();
}
}
/// <summary>
/// Advances the buffer, notifies this instance that there was something new written into the <see cref="_buffer"/> memory.
/// </summary>
/// <param name="count">The amount of bytes written.</param>
/// <exception cref="ArgumentOutOfRangeException">Throws if we are out of memory.</exception>
void IBufferWriter<byte>.Advance(int count)
{
if (count > _leased || count < 0)
throw new ArgumentOutOfRangeException(nameof(count));
_position += count;
_leased = 0;
}
/// <summary>
/// Returns a partion of the <see cref="_buffer"/> as a <see cref="Memory{T}"/>.
/// </summary>
/// <param name="sizeHint">The total amount.</param>
/// <returns>The new <see cref="Memory{T}"/> instance.</returns>
Memory<byte> IBufferWriter<byte>.GetMemory(int sizeHint)
{
var actual = Lease(sizeHint);
return new Memory<byte>(_buffer, _position, actual);
}
/// <summary>
/// Returns a partion of the <see cref="_buffer"/> as a <see cref="Span{T}"/>.
/// </summary>
/// <param name="sizeHint">The total amount.</param>
/// <returns>The new <see cref="Span{T}"/> instance.</returns>
Span<byte> IBufferWriter<byte>.GetSpan(int sizeHint)
{
var actual = Lease(sizeHint);
return new Span<byte>(_buffer, _position, actual);
}
/// <summary>
/// Disposes this instance, flushes and releases all memory.
/// </summary>
public void Dispose()
{
Flush(true);
var tmp = _buffer;
_buffer = null!;
ArrayPool<byte>.Shared.Return(tmp);
if (_ownsStream)
{
_destination.Dispose();
}
}
}

View File

@@ -0,0 +1 @@
uid://dbb3dlqyskwqs

View File

@@ -0,0 +1,125 @@
namespace Arch.Relationships;
using global::System.Diagnostics.Contracts;
using global::System.Runtime.CompilerServices;
using Arch.Core;
#if !PURE_ECS
/// <summary>
/// The <see cref="EntityRelationshipExtensions"/> class
/// stores several methods to forward relationship methods from the <see cref="World"/> to the <see cref="Entity"/>.
/// </summary>
public static class EntityRelationshipExtensions
{
/// <summary>
/// Adds a new relationship to the <see cref="Entity"/>.
/// </summary>
/// <param name="source">The source <see cref="Entity"/> of the relationship.</param>
/// <param name="target">The target <see cref="Entity"/> of the relationship.</param>
// / <typeparam name="T">The relationship type.</typeparam>
/// <param name="relationship">The relationship instance.</param>
public static void AddRelationship<T>(this in Entity source, Entity target, T relationship = default!)
{
var world = World.Worlds[source.WorldId];
world.AddRelationship(source, target, relationship);
}
/// <summary>
/// Sets a relationship to the <see cref="Entity"/> by updating its relationship data.
/// </summary>
/// <param name="source">The source <see cref="Entity"/> of the relationship.</param>
/// <param name="target">The target <see cref="Entity"/> of the relationship.</param>
/// <typeparam name="T">The relationship type.</typeparam>
/// <param name="relationship">The relationship instance.</param>
public static void SetRelationship<T>(this in Entity source, Entity target, T relationship = default!)
{
var world = World.Worlds[source.WorldId];
world.SetRelationship(source, target, relationship);
}
/// <summary>
/// Checks if an <see cref="Entity"/> has a certain relationship.
/// </summary>
/// <typeparam name="T">The relationship type.</typeparam>
/// <param name="source">The source <see cref="Entity"/> of the relationship.</param>
/// <param name="target">The target <see cref="Entity"/> of the relationship.</param>
/// <returns>True if it has the desired relationship, otherwise false.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining), Pure]
public static bool HasRelationship<T>(this in Entity source, Entity target)
{
var world = World.Worlds[source.WorldId];
return world.HasRelationship<T>(source, target);
}
/// <summary>
/// Checks if an <see cref="Entity"/> has a certain relationship.
/// </summary>
/// <typeparam name="T">The relationship type.</typeparam>
/// <param name="source">The source <see cref="Entity"/> of the relationship.</param>
/// <returns>True if it has the desired relationship, otherwise false.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining), Pure]
public static bool HasRelationship<T>(this in Entity source)
{
var world = World.Worlds[source.WorldId];
return world.HasRelationship<T>(source);
}
/// <summary>
/// Returns a relationship of an <see cref="Entity"/>.
/// </summary>
/// <typeparam name="T">The relationship type.</typeparam>
/// <param name="source">The source <see cref="Entity"/> of the relationship.</param>
/// <param name="target">The target <see cref="Entity"/> of the relationship.</param>
/// <returns>The relationship.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining), Pure]
public static T GetRelationship<T>(this in Entity source, Entity target)
{
var world = World.Worlds[source.WorldId];
return world.GetRelationship<T>(source, target);
}
/// <summary>
/// Returns a relationship of an <see cref="Entity"/>.
/// </summary>
/// <typeparam name="T">The relationship type.</typeparam>
/// <param name="source">The source <see cref="Entity"/> of the relationship.</param>
/// <returns>The <see cref="Relationship{T}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining), Pure]
public static ref Relationship<T> GetRelationships<T>(this in Entity source)
{
var world = World.Worlds[source.WorldId];
return ref world.GetRelationships<T>(source);
}
/// <summary>
/// Tries to return an <see cref="Entity"/>s relationship of the specified type.
/// Will copy the relationship if its a struct.
/// </summary>
/// <typeparam name="T">The relationship type.</typeparam>
/// <param name="source">The source <see cref="Entity"/> of the relationship.</param>
/// <param name="target">The target <see cref="Entity"/> of the relationship.</param>
/// <param name="relationship">The found relationship.</param>
/// <returns>True if it exists, otherwise false.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining), Pure]
public static bool TryGetRelationship<T>(this in Entity source, Entity target, out T relationship)
{
var world = World.Worlds[source.WorldId];
return world.TryGetRelationship(source, target, out relationship);
}
/// <summary>
/// Removes a relationship from an <see cref="Entity"/>.
/// </summary>
/// <typeparam name="T">The relationship type.</typeparam>
/// <param name="source">The <see cref="Entity"/> to remove the relationship from.</param>
/// <param name="target">The target <see cref="Entity"/> of the relationship.</param>
public static void RemoveRelationship<T>(this in Entity source, Entity target)
{
var world = World.Worlds[source.WorldId];
world.RemoveRelationship<T>(source, target);
}
}
#endif

View File

@@ -0,0 +1 @@
uid://574q5d6c3s6y

View File

@@ -0,0 +1,58 @@
namespace Arch.Relationships;
using global::System;
using global::System.Collections.Generic;
using Arch.Core;
/// <summary>
/// The <see cref="SortedListEnumerator{TValue}"/> struct
/// is a enumerator to enumerate a passed <see cref="SortedList{TKey,TValue}"/> in an efficient way.
/// </summary>
/// <typeparam name="TValue"></typeparam>
public struct SortedListEnumerator<TValue>
{
private readonly SortedList<Entity, TValue> _sortedList;
private int _currentIndex;
/// <summary>
/// Constructor.
/// </summary>
/// <param name="list">List.</param>
public SortedListEnumerator(SortedList<Entity, TValue> list)
{
_sortedList = list;
_currentIndex = -1;
}
/// <summary>
/// Current.
/// </summary>
public readonly KeyValuePair<Entity, TValue> Current
{
get
{
if (_currentIndex == -1 || _currentIndex >= _sortedList.Count)
{
throw new InvalidOperationException();
}
var key = _sortedList.Keys[_currentIndex];
var value = _sortedList.Values[_currentIndex];
return new KeyValuePair<Entity, TValue>(key, value);
}
}
/// <summary>
/// Moves to the next element in the enumerator.
/// </summary>
public bool MoveNext()
{
_currentIndex++;
return _currentIndex < _sortedList.Count;
}
/// <summary>
/// Resets the enumerator to its initial position.
/// </summary>
public void Reset() => _currentIndex = -1;
}

View File

@@ -0,0 +1 @@
uid://br3hmo40b0kmx

View File

@@ -0,0 +1,38 @@
namespace Arch.Relationships;
using Arch.Core;
/// <summary>
/// The struct <see cref="InRelationship"/>
/// represents a reference to a <see cref="Relationship{T}"/>.
/// It sits on an <see cref="Entity"/> to indicate in which other <see cref="Relationship{T}"/>s it is involved in.
/// </summary>
internal readonly struct InRelationship
{
/// <summary>
/// The id of the <see cref="Relationship{T}"/>-Component that this <see cref="InRelationship"/> points to.
/// Basically the <see cref="Relationship{T}"/> the <see cref="Entity"/> is in.
/// TODO: Uhmm... how the heck do we convert the Id back to the <see cref="ComponentType"/>?
/// </summary>
public readonly int ComponentTypeId;
/// <summary>
/// Creates a new <see cref="InRelationship"/> instance.
/// </summary>
/// <param name="targetRelation">The <see cref="ComponentType"/> that represents the relation.</param>
internal InRelationship(ComponentType targetRelation)
{
ComponentTypeId = targetRelation.Id;
}
/// <summary>
/// Creates a new <see cref="InRelationship"/> instance.
/// <remarks>Mostly for binary serialization.</remarks>
/// </summary>
/// <param name="componentTypeId">The <see cref="ComponentTypeId"/>.</param>
internal InRelationship(int componentTypeId)
{
ComponentTypeId = componentTypeId;
}
}

View File

@@ -0,0 +1 @@
uid://cu62xtv73hjq7

View File

@@ -0,0 +1,146 @@
namespace Arch.Relationships;
using global::System.Collections.Generic;
using global::System.Runtime.CompilerServices;
using Arch.Core;
/// <summary>
/// The <see cref="IRelationship"/> interface
/// is an interface that provides all methods required to act as a relationship.
/// </summary>
internal interface IRelationship
{
/// <summary>
/// The amount of relationships currently in the buffer.
/// </summary>
int Count
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get;
}
/// <summary>
/// Removes the buffer as a component from the given world and entity.
/// </summary>
/// <param name="world"></param>
/// <param name="source"></param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void Destroy(World world, Entity source);
/// <summary>
/// Removes the relationship targeting <paramref name="target"/> from this buffer.
/// </summary>
/// <param name="target">The <see cref="Entity"/> in the relationship to remove.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
void Remove(Entity target);
}
/// <summary>
/// A buffer storing relationships of <see cref="Entity"/> and <typeparamref name="T"/>.
/// </summary>
/// <typeparam name="T">The type of the second relationship element.</typeparam>
public class Relationship<T> : IRelationship
{
/// <summary>
/// Its relations.
/// </summary>
internal readonly SortedList<Entity, T> _elements;
/// <summary>
/// Initializes a new instance of an <see cref="Relationship{T}"/>.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal Relationship()
{
_elements = [];
}
/// <summary>
/// Initializes a new instance of an <see cref="Relationship{T}"/>.
/// <remarks>Mostly for binary serialization.</remarks>
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal Relationship(SortedList<Entity, T> elements)
{
_elements = elements;
}
/// <inheritdoc/>
int IRelationship.Count
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _elements.Count;
}
/// <inheritdoc cref="IRelationship.Count"/>
internal int Count
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => ((IRelationship)this).Count;
}
/// <summary>
/// Adds a relationship to this buffer.
/// </summary>
/// <param name="relationship">The instance of the relationship.</param>
/// <param name="target">The target of the relationship.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void Add(in T relationship, Entity target) => _elements.Add(target, relationship);
/// <summary>
/// Sets the stored <typeparamref name="T"/> for the given <see cref="Entity"/>.
/// </summary>
/// <param name="entity">The <see cref="Entity"/>.</param>
/// <param name="data">The data to store.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Set(Entity entity, T data = default!) => _elements[entity] = data;
/// <summary>
/// Determines whether the given <see cref="Relationship{T}"/> contains the passed <see cref="Entity"/> or not.
/// </summary>
/// <param name="entity">The <see cref="Entity"/>.</param>
/// <returns>True or false.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Contains(Entity entity) => _elements.ContainsKey(entity);
/// <summary>
/// Returns the stored <typeparamref name="T"/> for the given <see cref="Entity"/>.
/// </summary>
/// <param name="entity">The <see cref="Entity"/>.</param>
/// <returns>The stored <typeparamref name="T"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T Get(Entity entity) => _elements[entity];
/// <summary>
/// Returns the stored <typeparamref name="T"/> for the given <see cref="Entity"/>.
/// </summary>
/// <param name="entity">The <see cref="Entity"/>.</param>
/// <param name="value">The stored <typeparamref name="T"/>.</param>
/// <returns>The stored <typeparamref name="T"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryGetValue(Entity entity, out T value) => _elements.TryGetValue(entity, out value!);
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
void IRelationship.Remove(Entity target) => _elements.Remove(target);
/// <inheritdoc cref="IRelationship.Remove(Entity)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void Remove(Entity target) => ((IRelationship)this).Remove(target);
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
void IRelationship.Destroy(World world, Entity source) => world.Remove<Relationship<T>>(source);
/// <inheritdoc cref="IRelationship.Destroy(World, Entity)"/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void Destroy(World world, Entity source) => ((IRelationship)this).Destroy(world, source);
/// <summary>
/// Creates a new <see cref="SortedListEnumerator{TValue}"/>.
/// </summary>
/// <returns>The new <see cref="SortedListEnumerator{TValue}"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public SortedListEnumerator<T> GetEnumerator() => new(_elements);
};

View File

@@ -0,0 +1 @@
uid://d2oi4itpntwl3

View File

@@ -0,0 +1,272 @@
// [assembly:InternalsVisibleTo("Arch.Relationships.Tests")]
namespace Arch.Relationships;
using global::System.Diagnostics.Contracts;
using global::System.Runtime.CompilerServices;
using Arch.Core;
/// <summary>
/// The <see cref="WorldRelationshipExtensions"/> class
/// stores several extension methods for relationships handling.
/// </summary>
public static class WorldRelationshipExtensions
{
#if EVENTS
/// <summary>
/// Subscribes to entity destruction events to cleanup their relations.
/// </summary>
public static void HandleRelationshipCleanup(this World world)
{
world.SubscribeEntityDestroyed((in Entity entity) => CleanupRelationships(world, in entity));
}
// TODO: Probably someone will kill me for the dark magic that happens down below.
/// <summary>
/// Cleans up all relations of the passed <see cref="Entity"/>.
/// </summary>
/// <param name="world"></param>
/// <param name="entity"></param>
public static void CleanupRelationships(this World world, in Entity entity)
{
ref var relationships = ref world.TryGetRefRelationships<InRelationship>(entity, out var exists);
if (!exists)
{
return;
}
foreach (var (target, inRelationship) in relationships.Elements)
{
var id = inRelationship.ComponentTypeId;
var componentType = new ComponentType(id, 0);
// Get slots, chunk and array to prevent entity.Get(type) object allocation
ref readonly var chunk = ref world.GetChunk(target);
var array = chunk.GetArray(componentType);
var relationshipsArray = Unsafe.As<IRelationship[]>(array);
var slot = world.GetSlot(target);
var relationship = relationshipsArray[slot.Index];
relationship.Remove(entity);
if (relationship.Count == 0)
{
relationship.Destroy(world, target);
}
ref var targetRelationships = ref world.TryGetRefRelationships<InRelationship>(target, out exists);
if (!exists)
{
continue;
}
targetRelationships.Remove(entity);
}
}
#endif
/// <summary>
/// Adds a new relationship to the <see cref="Entity"/>.
/// </summary>
/// <param name="world">World.</param>
/// <param name="source">The source <see cref="Entity"/> of the relationship.</param>
/// <param name="target">The target <see cref="Entity"/> of the relationship.</param>
/// <typeparam name="T">The relationship type.</typeparam>
/// <param name="relationship">The relationship instance.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void AddRelationship<T>(this World world, Entity source, Entity target, in T relationship = default!)
{
ref var buffer = ref world.AddOrGetRelationships<T>(source);
buffer.Add(in relationship, target);
var targetComp = new InRelationship(Component<Relationship<T>>.ComponentType);
ref var targetBuffer = ref world.AddOrGetRelationships<InRelationship>(target);
targetBuffer.Add(in targetComp, source);
}
/// <summary>
/// Ensures the existence of a relationship on an <see cref="Entity"/>.
/// </summary>
/// <typeparam name="T">The relationship type.</typeparam>
/// <param name="world">World.</param>
/// <param name="source">The source <see cref="Entity"/> of the relationship.</param>
/// <param name="target">The target <see cref="Entity"/> of the relationship.</param>
/// <param name="relationship">The relationship value used if its being added.</param>
/// <returns>The relationship.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static T AddOrGetRelationship<T>(this World world, Entity source, Entity target, in T relationship = default!)
{
ref var relationships = ref world.TryGetRefRelationships<T>(source, out var exists);
if (exists)
{
return relationships.Get(target);
}
world.AddRelationship(source, target, in relationship);
return world.GetRelationship<T>(source, target);
}
/// <summary>
/// Ensures the existence of a buffer of relationships on an <see cref="Entity"/>.
/// </summary>
/// <param name="world">World.</param>
/// <param name="source">The source <see cref="Entity"/> of the relationships.</param>
/// <typeparam name="T">The relationship type.</typeparam>
/// <returns>The relationships.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static ref Relationship<T> AddOrGetRelationships<T>(this World world, Entity source)
{
ref var component = ref world.TryGetRef<Relationship<T>>(source, out var exists);
if (exists)
{
return ref component!;
}
world.Add(source, new Relationship<T>());
return ref world.Get<Relationship<T>>(source);
}
/// <summary>
/// Sets the existing relationship data.
/// </summary>
/// <typeparam name="T">The relationship type.</typeparam>
/// <param name="world">World.</param>
/// <param name="source">The source <see cref="Entity"/> of the relationship.</param>
/// <param name="target">The target <see cref="Entity"/> of the relationship.</param>
/// <param name="relationship">The new data.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SetRelationship<T>(this World world, Entity source, Entity target, in T relationship = default!)
{
ref var relationships = ref world.GetRelationships<T>(source);
relationships.Set(target, relationship);
}
/// <summary>
/// Checks if an <see cref="Entity"/> has a certain relationship.
/// </summary>
/// <typeparam name="T">The relationship type.</typeparam>
/// <param name="world">World.</param>
/// <param name="source">The source <see cref="Entity"/> of the relationship.</param>
/// <param name="target">The target <see cref="Entity"/> of the relationship.</param>
/// <returns>True if it has the desired relationship, otherwise false.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining), Pure]
public static bool HasRelationship<T>(this World world, Entity source, Entity target)
{
ref var relationships = ref world.TryGetRefRelationships<T>(source, out var exists);
if (!exists)
{
return false;
}
return relationships.Contains(target);
}
/// <summary>
/// Checks if an <see cref="Entity"/> has a certain relationship.
/// </summary>
/// <typeparam name="T">The relationship type.</typeparam>
/// <param name="world">World.</param>
/// <param name="source">The source <see cref="Entity"/> of the relationship.</param>
/// <returns>True if it has the desired relationship, otherwise false.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining), Pure]
public static bool HasRelationship<T>(this World world, Entity source) => world.Has<Relationship<T>>(source);
/// <summary>
/// Returns a relationship of an <see cref="Entity"/>.
/// </summary>
/// <typeparam name="T">The relationship type.</typeparam>
/// <param name="world">World.</param>
/// <param name="source">The source <see cref="Entity"/> of the relationship.</param>
/// <param name="target">The target <see cref="Entity"/> of the relationship.</param>
/// <returns>The relationship.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining), Pure]
public static T GetRelationship<T>(this World world, Entity source, Entity target)
{
ref var relationships = ref world.GetRelationships<T>(source);
return relationships.Get(target);
}
/// <summary>
/// Tries to return an <see cref="Entity"/>s relationship of the specified type.
/// Will copy the relationship if its a struct.
/// </summary>
/// <typeparam name="T">The relationship type.</typeparam>
/// <param name="world">World.</param>
/// <param name="source">The source <see cref="Entity"/> of the relationship.</param>
/// <param name="target">The target <see cref="Entity"/> of the relationship.</param>
/// <param name="relationship">The found relationship.</param>
/// <returns>True if it exists, otherwise false.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining), Pure]
public static bool TryGetRelationship<T>(this World world, Entity source, Entity target, out T relationship)
{
ref var relationships = ref world.TryGetRefRelationships<T>(source, out var exists);
if (!exists)
{
relationship = default!;
return false;
}
return relationships.TryGetValue(target, out relationship);
}
/// <summary>
/// Returns all relationships of the given type of an <see cref="Entity"/>.
/// </summary>
/// <typeparam name="T">The relationship type.</typeparam>
/// <param name="world">World.</param>
/// <param name="source">The source <see cref="Entity"/> of the relationship.</param>
/// <returns>A reference to the relationships.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining), Pure]
public static ref Relationship<T> GetRelationships<T>(this World world, Entity source) => ref world.Get<Relationship<T>>(source);
/// <summary>
/// Tries to return an <see cref="Entity"/>s relationships of the specified type.
/// </summary>
/// <typeparam name="T">The relationship type.</typeparam>
/// <param name="world">World.</param>
/// <param name="source">The <see cref="Entity"/>.</param>
/// <param name="relationships">The found relationships.</param>
/// <returns>True if it exists, otherwise false.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining), Pure]
internal static bool TryGetRelationships<T>(this World world, Entity source, out Relationship<T> relationships) => world.TryGet(source, out relationships!);
/// <summary>
/// Tries to return a reference to an <see cref="Entity"/>s relationships of the
/// specified type.
/// </summary>
/// <typeparam name="T">The relationship type.</typeparam>
/// <param name="world">World.</param>
/// <param name="source">The <see cref="Entity"/>.</param>
/// <param name="exists">True if it exists, otherwise false.</param>
/// <returns>A reference to the relationships.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining), Pure]
internal static ref Relationship<T> TryGetRefRelationships<T>(this World world, Entity source, out bool exists) => ref world.TryGetRef<Relationship<T>>(source, out exists);
/// <summary>
/// Removes a relationship from an <see cref="Entity"/>.
/// </summary>
/// <typeparam name="T">The relationship type.</typeparam>
/// <param name="world">World.</param>
/// <param name="source">The <see cref="Entity"/> to remove the relationship from.</param>
/// <param name="target">The target <see cref="Entity"/> of the relationship.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void RemoveRelationship<T>(this World world, Entity source, Entity target)
{
ref var buffer = ref world.GetRelationships<T>(source);
buffer.Remove(target);
if (buffer.Count == 0)
{
world.Remove<Relationship<T>>(source);
}
ref var targetBuffer = ref world.GetRelationships<InRelationship>(target);
targetBuffer.Remove(source);
if (targetBuffer.Count == 0)
{
world.Remove<Relationship<InRelationship>>(target);
}
}
}

View File

@@ -0,0 +1 @@
uid://dfnift6mppxam

129
src/Blueprints/Blueprint.cs Normal file
View File

@@ -0,0 +1,129 @@
// namespace FoodFactory.Blueprints;
// using System;
// using System.Diagnostics;
// using System.Diagnostics.CodeAnalysis;
// using Arch.Core;
// using SharpYaml.Model;
// using SJK.Functional;
// using Utf8Json;
// public record Blueprint(string Name, Func<BlueprintContext, Entity> Factory)
// {
// }
// public record Blueprint(string Name, Func<BlueprintContext, Entity> Factory)
// {
// }
// public class ComponentBinderRegistry
// {
// private readonly Dictionary<ComponentType, IComponentBinder> _binder = [];
// public void Add<T>(ComponentBinder<T> componentBinder) where T : struct
// {
// _binder.Add(typeof(T), componentBinder);
// }
// public void Apply(Entity entity, World world, CompiledBlueprint itemData)
// {
// var s = world.GetSignature(entity);
// for (int i = 0; i < s.Count; i++)
// {
// if (!_binder.ContainsKey(s.Components[i]))
// {
// continue;
// }
// var binder = _binder[s.Components[i]];
// var type = s.Components[i].Type;
// var context = new BlueprintContext(){World = world};
// binder.ApplyUntyped(entity, itemData.Providers[type], context);
// }
// }
// public void Apply(Span<Entity> entities, World world, CompiledBlueprint itemData)
// {
// var signature = world.GetSignature(entities[0]);
// for (int i = 1; i < entities.Length; i++)
// {
// Debug.Assert(world.GetSignature(entities[i]) == signature);
// }
// for (int i = 0; i < signature.Count; i++)
// {
// var binder = _binder[signature.Components[i]];
// var type = signature.Components[i].Type;
// var context = new BlueprintContext(){World = world};
// binder.ApplyUntyped(entities, itemData.Providers[type], context);
// }
// }
// }
// public abstract class DataNode//(Dictionary<string, object> Data)
// {
// public abstract DataNodeType NodeType { get; }
// // public T Get<T>(string name) => (T)Data[name];
// }
// public enum DataNodeType
// {hthous stomatitis, pharyngitis, adenitis) is a
// Value,
// Sequence,
// Mapping,
// Component
// }
// public sealed class ValueNode(object value) : DataNode
// {
// public override DataNodeType NodeType => DataNodeType.Value;
// public object Value { get; set; } = value;
// public Option<T> As<T>() => Value is T o?Option<T>.Some(o):Option<T>.None;
// }
// public sealed class SequenceNode(IReadOnlyList<DataNode> values) : DataNode
// {
// public override DataNodeType NodeType => DataNodeType.Sequence;
// public IReadOnlyList<DataNode> Values { get; set; } = values;
// }
// public sealed class MappingNode(IReadOnlyDictionary<string, DataNode> values) : DataNode
// {
// public override DataNodeType NodeType => DataNodeType.Mapping;
// public IReadOnlyDictionary<string, DataNode> Values { get; set; } = values;
// public bool TryGet<T>(string key, [NotNullWhen(true)] out T value) where T : DataNode
// {
// var result = Values.TryGetValue(key, out var node);
// value = (T)node!;
// return result;
// }
// public bool TryGet(string key, [NotNullWhen(true)] out DataNode node) => Values.TryGetValue(key, out node!);
// }
// public class DataNodeSerlicer : YamlConverter<DataNode>, IJsonFormatter<DataNode>
// {
// private Dictionary<string,string> _data = new();
// public DataNode Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver)
// {
// var i = 0;
// while (reader.ReadIsInObject(ref i))
// {
// var name = reader.ReadPropertyName();
// var value = reader.ReadString();
// _data.Add(name, value);
// }
// throw new NotImplementedException();
// }
// public void Serialize(ref JsonWriter writer, DataNode value, IJsonFormatterResolver formatterResolver) => throw new NotImplementedException();
// public override DataNode? Read(YamlReader reader)
// {
// // reader.pa
// throw new NotImplementedException();
// }
// public override void Write(YamlWriter writer, DataNode value)
// {
// throw new NotImplementedException();
// }
// }

View File

@@ -0,0 +1 @@
uid://cj088ogl8uqmt

View File

@@ -0,0 +1,121 @@
namespace FoodFactory.Blueprints;
using Arch.Core;
public struct BlueprintContext
{
public World World;
public BlueprintId BluePrintId;
public object PlaceHolderApi;
}
// public class ComponentBinderRegistry
// {
// private readonly Dictionary<ComponentType, IComponentBinder> _binder = [];
// public void Add<T>(ComponentBinder<T> componentBinder) where T : struct
// {
// _binder.Add(typeof(T), componentBinder);
// }
// public void Apply(Entity entity, World world, CompiledBlueprint itemData)
// {
// var s = world.GetSignature(entity);
// for (int i = 0; i < s.Count; i++)
// {
// if (!_binder.ContainsKey(s.Components[i]))
// {
// continue;
// }
// var binder = _binder[s.Components[i]];
// var type = s.Components[i].Type;
// var context = new BlueprintContext(){World = world};
// binder.ApplyUntyped(entity, itemData.Providers[type], context);
// }
// }
// public void Apply(Span<Entity> entities, World world, CompiledBlueprint itemData)
// {
// var signature = world.GetSignature(entities[0]);
// for (int i = 1; i < entities.Length; i++)
// {
// Debug.Assert(world.GetSignature(entities[i]) == signature);
// }
// for (int i = 0; i < signature.Count; i++)
// {
// var binder = _binder[signature.Components[i]];
// var type = signature.Components[i].Type;
// var context = new BlueprintContext(){World = world};
// binder.ApplyUntyped(entities, itemData.Providers[type], context);
// }
// }
// }
// public abstract class DataNode//(Dictionary<string, object> Data)
// {
// public abstract DataNodeType NodeType { get; }
// // public T Get<T>(string name) => (T)Data[name];
// }
// public enum DataNodeType
// {hthous stomatitis, pharyngitis, adenitis) is a
// Value,
// Sequence,
// Mapping,
// Component
// }
// public sealed class ValueNode(object value) : DataNode
// {
// public override DataNodeType NodeType => DataNodeType.Value;
// public object Value { get; set; } = value;
// public Option<T> As<T>() => Value is T o?Option<T>.Some(o):Option<T>.None;
// }
// public sealed class SequenceNode(IReadOnlyList<DataNode> values) : DataNode
// {
// public override DataNodeType NodeType => DataNodeType.Sequence;
// public IReadOnlyList<DataNode> Values { get; set; } = values;
// }
// public sealed class MappingNode(IReadOnlyDictionary<string, DataNode> values) : DataNode
// {
// public override DataNodeType NodeType => DataNodeType.Mapping;
// public IReadOnlyDictionary<string, DataNode> Values { get; set; } = values;
// public bool TryGet<T>(string key, [NotNullWhen(true)] out T value) where T : DataNode
// {
// var result = Values.TryGetValue(key, out var node);
// value = (T)node!;
// return result;
// }
// public bool TryGet(string key, [NotNullWhen(true)] out DataNode node) => Values.TryGetValue(key, out node!);
// }
// public class DataNodeSerlicer : YamlConverter<DataNode>, IJsonFormatter<DataNode>
// {
// private Dictionary<string,string> _data = new();
// public DataNode Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver)
// {
// var i = 0;
// while (reader.ReadIsInObject(ref i))
// {
// var name = reader.ReadPropertyName();
// var value = reader.ReadString();
// _data.Add(name, value);
// }
// throw new NotImplementedException();
// }
// public void Serialize(ref JsonWriter writer, DataNode value, IJsonFormatterResolver formatterResolver) => throw new NotImplementedException();
// public override DataNode? Read(YamlReader reader)
// {
// // reader.pa
// throw new NotImplementedException();
// }
// public override void Write(YamlWriter writer, DataNode value)
// {
// throw new NotImplementedException();
// }
// }

View File

@@ -0,0 +1 @@
uid://cy4shmpf6ncqn

View File

@@ -0,0 +1,14 @@
namespace FoodFactory.Blueprints;
using FoodFactory.Core;
public readonly record struct BlueprintId
{
public readonly int Id;
public CompiledBlueprint Blueprint() => _registry.GetEntry(Id);
private static readonly Registry<CompiledBlueprint> _registry = new();
public BlueprintId(CompiledBlueprint blueprint)
{
Id = _registry.GetOrCreate(blueprint);
}
}

View File

@@ -0,0 +1 @@
uid://b7kmhd6uamhg

View File

@@ -0,0 +1,304 @@
namespace FoodFactory.Blueprints;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using FoodFactory.Blueprints.Providers;
using FoodFactory.Blueprints.Providers.Converters;
using FoodFactory.Items;
using Godot;
using SharpYaml;
public interface IBlueprintManger
{
CompiledBlueprint GetCompiledBlueprint(string name);
// ComponentBinderRegistry BinderRegistry { get; }
}
public class BlueprintManger : IBlueprintManger
{
public readonly Dictionary<string, CompiledBlueprint> CompiledBlueprints = [];
// public ComponentBinderRegistry BinderRegistry { get; }
public BlueprintManger()
{
var root = "/home/ronnie/Documents/Godot/Projects/ChickenGameTest/mods";
var dir = DirAccess.Open(root);
var itemDatas = new List<ItemData>();
var options = new YamlSerializerOptions()
{
Converters = [
// new ComponentsPatcherConverter(
// new(){
// [nameof(Temperature)] = typeof(IValueProvider<Temperature>),
// [nameof(Tags)] = typeof(IValueProvider<Tags>),
// [nameof(Name)] = typeof(IValueProvider<Name>),
// [nameof(Mass)] = typeof(IValueProvider<Mass>),
// [nameof(Color)] = typeof(IValueProvider<Color>),
// [nameof(BurnableTemp)] = typeof(IValueProvider<BurnableTemp>),
// }
// ),
new ComponentTemperatureConverter(),
new ComponentTagsConverter(),
new ComponentNameConverter(),
]
};
foreach (var item in dir.GetFiles())
{
if (item.EndsWith(".yaml"))
{
var file = FileAccess.Open(dir.GetCurrentDir() + "/" + item, FileAccess.ModeFlags.Read);
var data = YamlSerializer.Deserialize<ItemData>(file.GetAsText(),options);
itemDatas.Add(data);
// var compiledBlueprint = new CompiledBlueprint
// {
// // Id = data.Id,
// // Signature = new Signature([typeof(BlueprintId), .. types.Where(f => data.Components.ContainsKey(f.Name)).Select(Component.GetComponentType)]),
// // Providers = new IValueProvider[data.Components.Count]
// // };
// // var i = 0;
// // foreach (var item2 in data.Components)
// // {
// // var compiler = Providers[item2.Key];
// // var compiled = compiler.CompileUnTyped(item2.Value);
// // compiledBlueprint.Providers[i] = compiled;
// // i++;
// // }
// CompiledBlueprints.Add(compiledBlueprint.Id, compiledBlueprint);
}
}
//TODO Call a Patch method thingy to adjust exsting data before loading
CompiledBlueprints = LoadBluprints([.. itemDatas]);
}
private Dictionary<string, CompiledBlueprint> LoadBluprints(ItemData[] all, Dictionary<string, CompiledBlueprint>? existing = null)
{
var completed = existing is not null ? new(existing) : new Dictionary<string,CompiledBlueprint>();
var queue = new Queue<ItemData>(all);
while (queue.Any())
{
reset:
var item = queue.Dequeue();
var sources = new List<CompiledBlueprint>();
foreach (var c in item.Extend)
{
if (!completed.ContainsKey(c))
{
queue.Enqueue(item);
goto reset;
}
sources.Add(completed[c]);
}
var providers = new Dictionary<Type, List<IValueProvider>>();//TODO Make Blupritns able to hold muiple providers in a array for cases taht build off previouse providers
foreach (var blueprint in sources)
{
foreach (var provider in blueprint.Providers)
{
if (item.Remove.Contains(provider[0].GetProviderType().Name))
{
continue;
}
if (!providers.TryGetValue(provider[0].GetProviderType(), out var providersList))
{
providers[provider[0].GetProviderType()] = providersList = new();
}
for (int i = 0; i < provider.Length; i++)
{
if (provider[i].Mode == ProviderMode.Static)
{
providersList.Clear();
providersList.Add(provider[i]);
}
}
}
}
foreach (var component in item.Components)
{
// var compiler = Providers[component.Key];
var compiledProvider = component.Value;//compiler.CompileUnTyped(component.Value);
if (!providers.TryGetValue(compiledProvider.GetProviderType(), out var providersList))
{
providers[compiledProvider.GetProviderType()] = providersList = new();
}
providersList.Add(compiledProvider);
}
var compiled = new CompiledBlueprint()
{
Id = item.Id,
// Traits = new Tags([.. item.Components.Keys]),//TODO need a way for recipies to look in each 'parent/extend' for getting recipes, either iterating each parent pool(recipe behing only in max shared, or each buckert container a copy for each parent)
Signature = [.. providers.Keys],
Providers = [.. providers.Values.Select(list => list.ToArray())],
};
/*
ex Food-> onion -> red onion
if onion id, then onion and red onion bucket have a recipe
of for each trait, iterate each bucket, starting from spefic, to top.
*/
completed.Add(item.Id, compiled);
}
return completed;
}
public CompiledBlueprint? GetCompiledBlueprint(string id)
{
if (CompiledBlueprints.TryGetValue(id, out var compiledBlueprint))
{
return compiledBlueprint;
}
return null;
}
}
// public Blueprint GetBlueprint(string name)
// {
// // throw new NotImplementedException();
// if (!Blueprints.TryGetValue(name, out var blueprint))
// {
// //TODO this is where this should look through mods/base game
// if (name == "Potato")
// {
// blueprint = new Blueprint("Potato", static ctx => ctx.World.Create(new Name("Potato_raw"),
// new Tags("potato", "raw", "vegetable", "burnable"),
// new Temperature(40, TemperatureUnit.Fahrenheit),
// ctx.BluePrintId,
// new Bacteria(BacterialSystem.Resources.Add([new BacteriaData("slamnala", 400)])),
// new BurnableTemp(new Temperature(400, TemperatureUnit.Fahrenheit))));//TODO blueprintID should not be passed, but know when blueprint is created, meaning a rework of Blueprint id register
// }
// else if (name == "Onion")
// {
// blueprint = new Blueprint("Onion", static ctx => ctx.World.Create(new Name("Onion_raw"),
// new Tags("onion", "raw", "vegetable", "burnable", "sliceable"),
// new Temperature(71, TemperatureUnit.Fahrenheit),
// ctx.BluePrintId,
// new BurnableTemp(new Temperature(400, TemperatureUnit.Fahrenheit)),
// new Mass(Weight.FromGrams(110)),
// new Carbohydrates(Fiber: 2, Starch: 3, Sugars: new(Glucose: 0, Fructose: 1.2f, Sucrose: 1, Lactose: 0))
// ));//TODO blueprintID should not be passed, but know when blueprint is created, meaning a rework of Blueprint id register
// }
// else
// {
// throw new Exception();
// }
// }
// return blueprint;
// }
// }
// public class ComponentBinderRegistry
// {
// private readonly Dictionary<ComponentType, IComponentBinder> _binder = [];
// public void Add<T>(ComponentBinder<T> componentBinder) where T : struct
// {
// _binder.Add(typeof(T), componentBinder);
// }
// public void Apply(Entity entity, World world, CompiledBlueprint itemData)
// {
// var s = world.GetSignature(entity);
// for (int i = 0; i < s.Count; i++)
// {
// if (!_binder.ContainsKey(s.Components[i]))
// {
// continue;
// }
// var binder = _binder[s.Components[i]];
// var type = s.Components[i].Type;
// var context = new BlueprintContext(){World = world};
// binder.ApplyUntyped(entity, itemData.Providers[type], context);
// }
// }
// public void Apply(Span<Entity> entities, World world, CompiledBlueprint itemData)
// {
// var signature = world.GetSignature(entities[0]);
// for (int i = 1; i < entities.Length; i++)
// {
// Debug.Assert(world.GetSignature(entities[i]) == signature);
// }
// for (int i = 0; i < signature.Count; i++)
// {
// var binder = _binder[signature.Components[i]];
// var type = signature.Components[i].Type;
// var context = new BlueprintContext(){World = world};
// binder.ApplyUntyped(entities, itemData.Providers[type], context);
// }
// }
// }
// public abstract class DataNode//(Dictionary<string, object> Data)
// {
// public abstract DataNodeType NodeType { get; }
// // public T Get<T>(string name) => (T)Data[name];
// }
// public enum DataNodeType
// {hthous stomatitis, pharyngitis, adenitis) is a
// Value,
// Sequence,
// Mapping,
// Component
// }
// public sealed class ValueNode(object value) : DataNode
// {
// public override DataNodeType NodeType => DataNodeType.Value;
// public object Value { get; set; } = value;
// public Option<T> As<T>() => Value is T o?Option<T>.Some(o):Option<T>.None;
// }
// public sealed class SequenceNode(IReadOnlyList<DataNode> values) : DataNode
// {
// public override DataNodeType NodeType => DataNodeType.Sequence;
// public IReadOnlyList<DataNode> Values { get; set; } = values;
// }
// public sealed class MappingNode(IReadOnlyDictionary<string, DataNode> values) : DataNode
// {
// public override DataNodeType NodeType => DataNodeType.Mapping;
// public IReadOnlyDictionary<string, DataNode> Values { get; set; } = values;
// public bool TryGet<T>(string key, [NotNullWhen(true)] out T value) where T : DataNode
// {
// var result = Values.TryGetValue(key, out var node);
// value = (T)node!;
// return result;
// }
// public bool TryGet(string key, [NotNullWhen(true)] out DataNode node) => Values.TryGetValue(key, out node!);
// }
// public class DataNodeSerlicer : YamlConverter<DataNode>, IJsonFormatter<DataNode>
// {
// private Dictionary<string,string> _data = new();
// public DataNode Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver)
// {
// var i = 0;
// while (reader.ReadIsInObject(ref i))
// {
// var name = reader.ReadPropertyName();
// var value = reader.ReadString();
// _data.Add(name, value);
// }
// throw new NotImplementedException();
// }
// public void Serialize(ref JsonWriter writer, DataNode value, IJsonFormatterResolver formatterResolver) => throw new NotImplementedException();
// public override DataNode? Read(YamlReader reader)
// {
// // reader.pa
// throw new NotImplementedException();
// }
// public override void Write(YamlWriter writer, DataNode value)
// {
// throw new NotImplementedException();
// }
// }

View File

@@ -0,0 +1 @@
uid://bvf8g3wbikopb

View File

@@ -0,0 +1,48 @@
namespace FoodFactory.Blueprints;
using Arch.Core;
using FoodFactory.Blueprints.Providers;
using FoodFactory.Core.Components;
// public sealed class StaticReflectionCompiledNode<T> : IValueProvider<T> where T : struct
// {
// //TODO Make more performance by only having properties of each method, and share use a list using tuples instead.
// // private readonly Dictionary<string, object> _data;
// private readonly T _template;
// public StaticReflectionCompiledNode(Dictionary<string, object> itemData)
// {
// StructReflection.ApplyValues(ref _template, itemData);
// }
// public void Resolve(BlueprintContext context, ref T value) => value = _template;
// public ProviderMode Mode => ProviderMode.Static;
// }
// public sealed class RuntimeReflectionCompiledNode<T> : IValueProvider<T> where T : struct
// {
// //TODO Make more performance by only having properties of each method, and share use a list using tuples instead.
// private readonly Dictionary<string, object> _data;
// // private readonly T _template;
// public RuntimeReflectionCompiledNode(Dictionary<string, object> itemData)
// {
// _data = itemData;
// }
// public void Resolve(BlueprintContext context, ref T value) => StructReflection.ApplyValues(ref value, _data);
// public ProviderMode Mode => ProviderMode.Modify;
// }
public class CompiledBlueprint
{
public required string Id { get; init; }
public Tags Traits { get; set; }
public IValueProvider[][] Providers = [];
public Signature Signature;
public Entity CreateEmptyItem(BlueprintContext blueprintContext) => blueprintContext.World.Create(Signature);
}

View File

@@ -0,0 +1 @@
uid://7ncdfun3kqil

View File

@@ -0,0 +1,162 @@
namespace FoodFactory.Blueprints.Providers.Converters;
using System;
using FoodFactory.Core.Components;
using SharpYaml.Serialization;
public sealed class ComponentTagsConverter : ItemComponentsConverter<Tags>
{
public override IValueProvider<Tags> Read(YamlReader reader)
{
if (reader.TokenType == YamlTokenType.StartSequence)
{
if (reader.GetConverter(typeof(string[])) is not YamlConverter<string[]> converter)
{
throw new Exception();
}
return new StaticCompiledNode<Tags>(new Tags(converter.Read(reader)));
}
if (reader.TokenType == YamlTokenType.StartMapping)
{
string[] with = [];
string[] exclude = [];
reader.Read();
while (reader.TokenType != YamlTokenType.EndMapping)
{
var key = reader.ScalarValue;
reader.Read();
if (key == "With")
{
if (reader.GetConverter(typeof(string[])) is not YamlConverter<string[]> converter)
{
throw new Exception();
}
with = converter.Read(reader);
}
if (key == "Except")
{
if (reader.GetConverter(typeof(string[])) is not YamlConverter<string[]> converter)
{
throw new Exception();
}
exclude = converter.Read(reader);
}
reader.Read();
}
return new FactoryCompiledNode<Tags>((in factory, ref tags) => tags.With(with).WithOut(exclude));
}
throw new NotSupportedException();
}
public override void Write(YamlWriter writer, IValueProvider<Tags> value) => throw new NotImplementedException();
}
// public class ComponentBinderRegistry
// {
// private readonly Dictionary<ComponentType, IComponentBinder> _binder = [];
// public void Add<T>(ComponentBinder<T> componentBinder) where T : struct
// {
// _binder.Add(typeof(T), componentBinder);
// }
// public void Apply(Entity entity, World world, CompiledBlueprint itemData)
// {
// var s = world.GetSignature(entity);
// for (int i = 0; i < s.Count; i++)
// {
// if (!_binder.ContainsKey(s.Components[i]))
// {
// continue;
// }
// var binder = _binder[s.Components[i]];
// var type = s.Components[i].Type;
// var context = new BlueprintContext(){World = world};
// binder.ApplyUntyped(entity, itemData.Providers[type], context);
// }
// }
// public void Apply(Span<Entity> entities, World world, CompiledBlueprint itemData)
// {
// var signature = world.GetSignature(entities[0]);
// for (int i = 1; i < entities.Length; i++)
// {
// Debug.Assert(world.GetSignature(entities[i]) == signature);
// }
// for (int i = 0; i < signature.Count; i++)
// {
// var binder = _binder[signature.Components[i]];
// var type = signature.Components[i].Type;
// var context = new BlueprintContext(){World = world};
// binder.ApplyUntyped(entities, itemData.Providers[type], context);
// }
// }
// }
// public abstract class DataNode//(Dictionary<string, object> Data)
// {
// public abstract DataNodeType NodeType { get; }
// // public T Get<T>(string name) => (T)Data[name];
// }
// public enum DataNodeType
// {hthous stomatitis, pharyngitis, adenitis) is a
// Value,
// Sequence,
// Mapping,
// Component
// }
// public sealed class ValueNode(object value) : DataNode
// {
// public override DataNodeType NodeType => DataNodeType.Value;
// public object Value { get; set; } = value;
// public Option<T> As<T>() => Value is T o?Option<T>.Some(o):Option<T>.None;
// }
// public sealed class SequenceNode(IReadOnlyList<DataNode> values) : DataNode
// {
// public override DataNodeType NodeType => DataNodeType.Sequence;
// public IReadOnlyList<DataNode> Values { get; set; } = values;
// }
// public sealed class MappingNode(IReadOnlyDictionary<string, DataNode> values) : DataNode
// {
// public override DataNodeType NodeType => DataNodeType.Mapping;
// public IReadOnlyDictionary<string, DataNode> Values { get; set; } = values;
// public bool TryGet<T>(string key, [NotNullWhen(true)] out T value) where T : DataNode
// {
// var result = Values.TryGetValue(key, out var node);
// value = (T)node!;
// return result;
// }
// public bool TryGet(string key, [NotNullWhen(true)] out DataNode node) => Values.TryGetValue(key, out node!);
// }
// public class DataNodeSerlicer : YamlConverter<DataNode>, IJsonFormatter<DataNode>
// {
// private Dictionary<string,string> _data = new();
// public DataNode Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver)
// {
// var i = 0;
// while (reader.ReadIsInObject(ref i))
// {
// var name = reader.ReadPropertyName();
// var value = reader.ReadString();
// _data.Add(name, value);
// }
// throw new NotImplementedException();
// }
// public void Serialize(ref JsonWriter writer, DataNode value, IJsonFormatterResolver formatterResolver) => throw new NotImplementedException();
// public override DataNode? Read(YamlReader reader)
// {
// // reader.pa
// throw new NotImplementedException();
// }
// public override void Write(YamlWriter writer, DataNode value)
// {
// throw new NotImplementedException();
// }
// }

View File

@@ -0,0 +1 @@
uid://cxtk4cn40072m

View File

@@ -0,0 +1,263 @@
namespace FoodFactory.Blueprints.Providers.Converters;
using System;
using FoodFactory.Math;
using SharpYaml.Serialization;
// public interface IComponentBinder
// {
// ComponentType ComponentType { get; }
// void ApplyUntyped(
// Entity existing,
// IValueProvider data,
// BlueprintContext context);
// void ApplyUntyped(
// Span<Entity> existing,
// IValueProvider data,
// BlueprintContext context);
// }
// public class ComponentBinder<T> : IComponentBinder where T : struct
// {
// public ComponentType ComponentType => Component<T>.ComponentType;
// public void ApplyUntyped(
// Entity existing,
// IValueProvider data,
// BlueprintContext context)
// {
// if (data is IValueProvider<T> dataT)
// {
// ref var component = ref context.World.Get<T>(existing);
// Apply(ref component, dataT, context);
// return;
// }
// throw new System.Exception();
// }
// public void ApplyUntyped(Span<Entity> existing, IValueProvider data, BlueprintContext context)
// {
// if (data is not IValueProvider<T> dataT)
// {
// throw new System.Exception();
// }
// for (int i = 0; i < existing.Length; i++)
// {
// Apply(ref existing[i].Get<T>(), dataT, context);
// }
// }
// public void Apply(
// ref T existing,
// IValueProvider<T> data,
// BlueprintContext context) => data.Resolve(context, ref existing);
// }
// public class DefaultBinder<T> : ComponentBinder<T> where T : struct
// {
// public override void Apply(ref T existing, IValueProvider<T> data, BlueprintContext context);
// }
// public class TemperatureBinder : ComponentBinder<Temperature>
// {
// public override void Apply(ref Temperature existing, IValueProvider<Temperature> data, BlueprintContext context) => data.Resolve(context, ref existing);
// }
public sealed class ComponentTemperatureConverter : ItemComponentsConverter<Temperature>//, IDataCompiler<Temperature>
{
// public IValueProvider<Temperature> Compile(object data)
// {
// if (data is IValueProvider<Temperature> t)
// {
// return t;
// }
// if (data is string says)
// {
// if (says == "ambient")
// {
// return new AmbientTemperatureProvider();
// }
// }
// if (data is float f)
// {
// return new StaticCompiledNode<Temperature>(new Temperature(f));
// }
// if (data is double d)
// {
// return new StaticCompiledNode<Temperature>(new Temperature(d));
// }
// if (data is Dictionary<string, object> dict)
// {
// return new StaticReflectionCompiledNode<Temperature>(dict);
// }
// throw new NotSupportedException();
// }
public override IValueProvider<Temperature>? Read(YamlReader reader)
{
if (reader.TokenType == YamlTokenType.Scalar)
{
var value = reader.ScalarValue;
reader.Read();
if (value == "ambient")
{
return new AmbientTemperatureProvider();
}
if (float.TryParse(value, out var f))
{
return new StaticCompiledNode<Temperature>(new Temperature(f));
}
if (double.TryParse(value, out var d))
{
return new StaticCompiledNode<Temperature>(new Temperature(f));
}
throw new NotImplementedException();
}
if (reader.TokenType == YamlTokenType.StartMapping)
{
reader.Read();
IValueProvider<Temperature>? provider = null;
while (reader.TokenType != YamlTokenType.EndMapping)
{
var key = reader.ScalarValue;
reader.Read();
if (key == nameof(Fahrenheit))
{
provider = new StaticCompiledNode<Temperature>(new Fahrenheit(float.Parse(reader.ScalarValue)));
}
if (key == nameof(Celsius))
{
provider = new StaticCompiledNode<Temperature>(new Celsius(float.Parse(reader.ScalarValue)));
}
if (key == nameof(Kelvin))
{
provider = new StaticCompiledNode<Temperature>(new Kelvin(float.Parse(reader.ScalarValue)));
}
reader.Read();
}
return provider??throw new NotSupportedException();
}
throw new NotImplementedException();
}
public override void Write(YamlWriter writer, IValueProvider<Temperature> value) => throw new NotImplementedException();
public sealed class AmbientTemperatureProvider : IValueProvider<Temperature>
{
public ProviderMode Mode => ProviderMode.Static;
public void Resolve(BlueprintContext context, ref Temperature value)
{
value.Kelvin = (float)context.PlaceHolderApi;
}
}
}
// public class ComponentBinderRegistry
// {
// private readonly Dictionary<ComponentType, IComponentBinder> _binder = [];
// public void Add<T>(ComponentBinder<T> componentBinder) where T : struct
// {
// _binder.Add(typeof(T), componentBinder);
// }
// public void Apply(Entity entity, World world, CompiledBlueprint itemData)
// {
// var s = world.GetSignature(entity);
// for (int i = 0; i < s.Count; i++)
// {
// if (!_binder.ContainsKey(s.Components[i]))
// {
// continue;
// }
// var binder = _binder[s.Components[i]];
// var type = s.Components[i].Type;
// var context = new BlueprintContext(){World = world};
// binder.ApplyUntyped(entity, itemData.Providers[type], context);
// }
// }
// public void Apply(Span<Entity> entities, World world, CompiledBlueprint itemData)
// {
// var signature = world.GetSignature(entities[0]);
// for (int i = 1; i < entities.Length; i++)
// {
// Debug.Assert(world.GetSignature(entities[i]) == signature);
// }
// for (int i = 0; i < signature.Count; i++)
// {
// var binder = _binder[signature.Components[i]];
// var type = signature.Components[i].Type;
// var context = new BlueprintContext(){World = world};
// binder.ApplyUntyped(entities, itemData.Providers[type], context);
// }
// }
// }
// public abstract class DataNode//(Dictionary<string, object> Data)
// {
// public abstract DataNodeType NodeType { get; }
// // public T Get<T>(string name) => (T)Data[name];
// }
// public enum DataNodeType
// {hthous stomatitis, pharyngitis, adenitis) is a
// Value,
// Sequence,
// Mapping,
// Component
// }
// public sealed class ValueNode(object value) : DataNode
// {
// public override DataNodeType NodeType => DataNodeType.Value;
// public object Value { get; set; } = value;
// public Option<T> As<T>() => Value is T o?Option<T>.Some(o):Option<T>.None;
// }
// public sealed class SequenceNode(IReadOnlyList<DataNode> values) : DataNode
// {
// public override DataNodeType NodeType => DataNodeType.Sequence;
// public IReadOnlyList<DataNode> Values { get; set; } = values;
// }
// public sealed class MappingNode(IReadOnlyDictionary<string, DataNode> values) : DataNode
// {
// public override DataNodeType NodeType => DataNodeType.Mapping;
// public IReadOnlyDictionary<string, DataNode> Values { get; set; } = values;
// public bool TryGet<T>(string key, [NotNullWhen(true)] out T value) where T : DataNode
// {
// var result = Values.TryGetValue(key, out var node);
// value = (T)node!;
// return result;
// }
// public bool TryGet(string key, [NotNullWhen(true)] out DataNode node) => Values.TryGetValue(key, out node!);
// }
// public class DataNodeSerlicer : YamlConverter<DataNode>, IJsonFormatter<DataNode>
// {
// private Dictionary<string,string> _data = new();
// public DataNode Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver)
// {
// var i = 0;
// while (reader.ReadIsInObject(ref i))
// {
// var name = reader.ReadPropertyName();
// var value = reader.ReadString();
// _data.Add(name, value);
// }
// throw new NotImplementedException();
// }
// public void Serialize(ref JsonWriter writer, DataNode value, IJsonFormatterResolver formatterResolver) => throw new NotImplementedException();
// public override DataNode? Read(YamlReader reader)
// {
// // reader.pa
// throw new NotImplementedException();
// }
// public override void Write(YamlWriter writer, DataNode value)
// {
// throw new NotImplementedException();
// }
// }

View File

@@ -0,0 +1 @@
uid://dpgrony8su3im

View File

@@ -0,0 +1,131 @@
namespace FoodFactory.Blueprints.Providers.Converters;
using System;
using SharpYaml.Serialization;
public sealed class ComponentNameConverter : ItemComponentsConverter<Name>
{
public override IValueProvider<Name> Read(YamlReader reader)
{
if (reader.TokenType == YamlTokenType.Scalar)
{
var key = reader.GetScalarValue();
reader.Read();
return new StaticCompiledNode<Name>(new(key));
}
return base.Read(reader);
}
public override void Write(YamlWriter writer, IValueProvider<Name> value) => throw new NotImplementedException();
}
// public class ComponentBinderRegistry
// {
// private readonly Dictionary<ComponentType, IComponentBinder> _binder = [];
// public void Add<T>(ComponentBinder<T> componentBinder) where T : struct
// {
// _binder.Add(typeof(T), componentBinder);
// }
// public void Apply(Entity entity, World world, CompiledBlueprint itemData)
// {
// var s = world.GetSignature(entity);
// for (int i = 0; i < s.Count; i++)
// {
// if (!_binder.ContainsKey(s.Components[i]))
// {
// continue;
// }
// var binder = _binder[s.Components[i]];
// var type = s.Components[i].Type;
// var context = new BlueprintContext(){World = world};
// binder.ApplyUntyped(entity, itemData.Providers[type], context);
// }
// }
// public void Apply(Span<Entity> entities, World world, CompiledBlueprint itemData)
// {
// var signature = world.GetSignature(entities[0]);
// for (int i = 1; i < entities.Length; i++)
// {
// Debug.Assert(world.GetSignature(entities[i]) == signature);
// }
// for (int i = 0; i < signature.Count; i++)
// {
// var binder = _binder[signature.Components[i]];
// var type = signature.Components[i].Type;
// var context = new BlueprintContext(){World = world};
// binder.ApplyUntyped(entities, itemData.Providers[type], context);
// }
// }
// }
// public abstract class DataNode//(Dictionary<string, object> Data)
// {
// public abstract DataNodeType NodeType { get; }
// // public T Get<T>(string name) => (T)Data[name];
// }
// public enum DataNodeType
// {hthous stomatitis, pharyngitis, adenitis) is a
// Value,
// Sequence,
// Mapping,
// Component
// }
// public sealed class ValueNode(object value) : DataNode
// {
// public override DataNodeType NodeType => DataNodeType.Value;
// public object Value { get; set; } = value;
// public Option<T> As<T>() => Value is T o?Option<T>.Some(o):Option<T>.None;
// }
// public sealed class SequenceNode(IReadOnlyList<DataNode> values) : DataNode
// {
// public override DataNodeType NodeType => DataNodeType.Sequence;
// public IReadOnlyList<DataNode> Values { get; set; } = values;
// }
// public sealed class MappingNode(IReadOnlyDictionary<string, DataNode> values) : DataNode
// {
// public override DataNodeType NodeType => DataNodeType.Mapping;
// public IReadOnlyDictionary<string, DataNode> Values { get; set; } = values;
// public bool TryGet<T>(string key, [NotNullWhen(true)] out T value) where T : DataNode
// {
// var result = Values.TryGetValue(key, out var node);
// value = (T)node!;
// return result;
// }
// public bool TryGet(string key, [NotNullWhen(true)] out DataNode node) => Values.TryGetValue(key, out node!);
// }
// public class DataNodeSerlicer : YamlConverter<DataNode>, IJsonFormatter<DataNode>
// {
// private Dictionary<string,string> _data = new();
// public DataNode Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver)
// {
// var i = 0;
// while (reader.ReadIsInObject(ref i))
// {
// var name = reader.ReadPropertyName();
// var value = reader.ReadString();
// _data.Add(name, value);
// }
// throw new NotImplementedException();
// }
// public void Serialize(ref JsonWriter writer, DataNode value, IJsonFormatterResolver formatterResolver) => throw new NotImplementedException();
// public override DataNode? Read(YamlReader reader)
// {
// // reader.pa
// throw new NotImplementedException();
// }
// public override void Write(YamlWriter writer, DataNode value)
// {
// throw new NotImplementedException();
// }
// }

View File

@@ -0,0 +1 @@
uid://boar6ktwp2m1e

View File

@@ -0,0 +1,133 @@
namespace FoodFactory.Blueprints.Providers.Converters;
using System;
using SharpYaml.Serialization;
public abstract class ItemComponentsConverter<T> : YamlConverter<IValueProvider<T>> where T : struct
{
public override IValueProvider<T> Read(YamlReader reader)
{
var converter = reader.GetConverter(typeof(T));
var value = converter.Read(reader, typeof(T));
if (value is not T t)
{
throw new Exception();
}
return new StaticCompiledNode<T>(t);
}
public override void Write(YamlWriter writer, IValueProvider<T> value) => throw new NotSupportedException();
}
// public class ComponentBinderRegistry
// {
// private readonly Dictionary<ComponentType, IComponentBinder> _binder = [];
// public void Add<T>(ComponentBinder<T> componentBinder) where T : struct
// {
// _binder.Add(typeof(T), componentBinder);
// }
// public void Apply(Entity entity, World world, CompiledBlueprint itemData)
// {
// var s = world.GetSignature(entity);
// for (int i = 0; i < s.Count; i++)
// {
// if (!_binder.ContainsKey(s.Components[i]))
// {
// continue;
// }
// var binder = _binder[s.Components[i]];
// var type = s.Components[i].Type;
// var context = new BlueprintContext(){World = world};
// binder.ApplyUntyped(entity, itemData.Providers[type], context);
// }
// }
// public void Apply(Span<Entity> entities, World world, CompiledBlueprint itemData)
// {
// var signature = world.GetSignature(entities[0]);
// for (int i = 1; i < entities.Length; i++)
// {
// Debug.Assert(world.GetSignature(entities[i]) == signature);
// }
// for (int i = 0; i < signature.Count; i++)
// {
// var binder = _binder[signature.Components[i]];
// var type = signature.Components[i].Type;
// var context = new BlueprintContext(){World = world};
// binder.ApplyUntyped(entities, itemData.Providers[type], context);
// }
// }
// }
// public abstract class DataNode//(Dictionary<string, object> Data)
// {
// public abstract DataNodeType NodeType { get; }
// // public T Get<T>(string name) => (T)Data[name];
// }
// public enum DataNodeType
// {hthous stomatitis, pharyngitis, adenitis) is a
// Value,
// Sequence,
// Mapping,
// Component
// }
// public sealed class ValueNode(object value) : DataNode
// {
// public override DataNodeType NodeType => DataNodeType.Value;
// public object Value { get; set; } = value;
// public Option<T> As<T>() => Value is T o?Option<T>.Some(o):Option<T>.None;
// }
// public sealed class SequenceNode(IReadOnlyList<DataNode> values) : DataNode
// {
// public override DataNodeType NodeType => DataNodeType.Sequence;
// public IReadOnlyList<DataNode> Values { get; set; } = values;
// }
// public sealed class MappingNode(IReadOnlyDictionary<string, DataNode> values) : DataNode
// {
// public override DataNodeType NodeType => DataNodeType.Mapping;
// public IReadOnlyDictionary<string, DataNode> Values { get; set; } = values;
// public bool TryGet<T>(string key, [NotNullWhen(true)] out T value) where T : DataNode
// {
// var result = Values.TryGetValue(key, out var node);
// value = (T)node!;
// return result;
// }
// public bool TryGet(string key, [NotNullWhen(true)] out DataNode node) => Values.TryGetValue(key, out node!);
// }
// public class DataNodeSerlicer : YamlConverter<DataNode>, IJsonFormatter<DataNode>
// {
// private Dictionary<string,string> _data = new();
// public DataNode Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver)
// {
// var i = 0;
// while (reader.ReadIsInObject(ref i))
// {
// var name = reader.ReadPropertyName();
// var value = reader.ReadString();
// _data.Add(name, value);
// }
// throw new NotImplementedException();
// }
// public void Serialize(ref JsonWriter writer, DataNode value, IJsonFormatterResolver formatterResolver) => throw new NotImplementedException();
// public override DataNode? Read(YamlReader reader)
// {
// // reader.pa
// throw new NotImplementedException();
// }
// public override void Write(YamlWriter writer, DataNode value)
// {
// throw new NotImplementedException();
// }
// }

View File

@@ -0,0 +1 @@
uid://cv0hsod03jmng

View File

@@ -0,0 +1,21 @@
namespace FoodFactory.Blueprints.Providers;
using System;
using Arch.Core;
using Arch.Core.Extensions;
public sealed class DefaultValueProvider : IValueProvider
{
private readonly object _object;
public DefaultValueProvider(Type type, object @object)
{
Type = type;
_object = @object;
}
public ProviderMode Mode => ProviderMode.Static;
public Type Type { get; }
public Type GetProviderType() => Type;
public void Resolve(BlueprintContext context, Entity entity) => entity.Set(_object);
}

View File

@@ -0,0 +1 @@
uid://croijjg1q3ui3

View File

@@ -0,0 +1,16 @@
namespace FoodFactory.Blueprints.Providers;
public sealed class FactoryCompiledNode<T> : IValueProvider<T>
{
public delegate void ResolveFunc(in BlueprintContext context, ref T value);
private readonly ResolveFunc _factory;
public ProviderMode Mode => ProviderMode.Modify;
public FactoryCompiledNode(ResolveFunc factory)
{
_factory = factory;
}
public void Resolve(BlueprintContext context, ref T value) => _factory(context, ref value);
}

View File

@@ -0,0 +1 @@
uid://ds3aflbt1maj5

View File

@@ -0,0 +1,22 @@
namespace FoodFactory.Blueprints.Providers;
using System;
using Arch.Core;
using Arch.Core.Extensions;
public interface IValueProvider
{
Type GetProviderType();
void Resolve(BlueprintContext context, Entity entity);
ProviderMode Mode { get; }
}
public interface IValueProvider<T> : IValueProvider
{
Type IValueProvider.GetProviderType() => typeof(T);
void Resolve(BlueprintContext context, ref T value);
void IValueProvider.Resolve(BlueprintContext context, Entity entity) => Resolve(context, ref entity.Get<T>());
}
public enum ProviderMode
{
Static,
Modify
}

View File

@@ -0,0 +1 @@
uid://bp0bhlpk25uh8

View File

@@ -0,0 +1,14 @@
namespace FoodFactory.Blueprints.Providers;
public sealed class StaticCompiledNode<T> : IValueProvider<T>
{
public StaticCompiledNode(T value)
{
Value = value;
}
public T Value { get; }
public void Resolve(BlueprintContext context, ref T value) => value = Value;
public ProviderMode Mode => ProviderMode.Static;
}

View File

@@ -0,0 +1 @@
uid://2srbqlc7hyo8

View File

@@ -1,71 +1,70 @@
using ChickenGameTest; namespace FoodFactory.Conveyors;
using Godot;
using System;
public partial class ConveyorBeltStraight : Node3D using System.Diagnostics;
using Chickensoft.AutoInject;
using Chickensoft.Introspection;
using FoodFactory.Items;
using FoodFactory.Math;
using FoodFactory.Voxel;
using Godot;
[Meta(typeof(IAutoNode))]
public partial class ConveyorBeltStraight : Node3D, IProvide<IVoxelGridRegistry>
{ {
[Export] public Vector3I VoxelPos { get; set; } = default; public override void _Notification(int what) => this.Notify(what);
[Export] public int Width { get; set; } = default; [Export] public int Width { get; set; } = default;
public IVoxelGridRegistry Value() => FoodFactoryApi.GridRegistry;
[Dependency] protected IFoodFactoryApi FoodFactoryApi => this.DependOn<IFoodFactoryApi>();
private VoxelGuid _guid = default;
public GridTransform3D VoxelTransform public GridTransform3D VoxelTransform
{ {
get => GridTransform3D.FromGodot(Transform); get => GridTransform3D.FromGodot(GlobalTransform);
set => Transform = value.ToGodot(); set => GlobalTransform = value.ToGodot();
} }
[Node("ConveyorLogic")]
public TestItemConveyor ConveyorLogic { get; set; } = default!;
public override void _Ready() public override void _Ready()
{ {
var node = GetNode("Node") as TestItemConveyor;
base._Ready(); base._Ready();
Debug.Assert(ConveyorLogic is not null, $"{nameof(ConveyorLogic)} is not set on {nameof(ConveyorBeltStraight)}, Path {SceneFilePath}");
// GD.PrintS(VoxelTransform.Origin,VoxelTransform.Forward); // GD.PrintS(VoxelTransform.Origin,VoxelTransform.Forward);
node.StartPort = node.CreatePort( ConveyorLogic.StartPort = ConveyorLogic.CreatePort(
new(VoxelTransform.TranslatedLocal(new(1-Width,0,0)).RotateY(2), new(VoxelTransform.TranslatedLocal(new(1 - Width, 0, 0)).RotateY(2),
// Direction.Back, // Direction.Back,
Width, Width,
PortAccess.BiDirectional), PortAccess.BiDirectional),
new ItemConveyor.BeltTEnd(ItemConveyor.ConveyorEnd.Start), new BeltTEnd(ConveyorEnd.Start));
new LaneSpan(0, 1)); ConveyorLogic.EndPort = ConveyorLogic.CreatePort(
// GD.Print(VoxelTransform.Origin,GridTransform3D.FromGodot(Transform.TranslatedLocal(Vector3.Left)).Origin);
node.EndPort = node.CreatePort(
new(VoxelTransform, //new(GridTransform3D.FromGodot(Transform.TranslatedLocal(Vector3.Left)), new(VoxelTransform, //new(GridTransform3D.FromGodot(Transform.TranslatedLocal(Vector3.Left)),
// Direction.Front, // Direction.Front,
Width, Width,
PortAccess.BiDirectional), PortAccess.BiDirectional),
new ItemConveyor.BeltTEnd(ItemConveyor.ConveyorEnd.End), new BeltTEnd(ConveyorEnd.End));
new LaneSpan(0, 1));
// node.OtherPorts = [ ConveyorLogic.OtherPorts = [
// node.CreatePort( ConveyorLogic.CreatePort(
// new(VoxelTransform.RotateY90CW(), new(VoxelTransform.RotateY90CW(),
// // Direction.Right, // Direction.Right,
// 1, 1,
// PortAccess.In), PortAccess.In),
// new ItemConveyor.BeltTOffset(node.Length/2), new BeltTOffset(ConveyorLogic.Length/2)),
// LaneSpan.One), ConveyorLogic.CreatePort(
// node.CreatePort( new(VoxelTransform.RotateY90CCW(),
// new(VoxelTransform.RotateY90CCW(), // Direction.Left,
// // Direction.Left, 1,
// 1, PortAccess.In),
// PortAccess.In), new BeltTOffset(ConveyorLogic.Length/2))
// new ItemConveyor.BeltTOffset(node.Length/2), ];
// LaneSpan.One)
// ]; }
// new ConveyorPort(this, public void OnResolved()
// new BeltPortProfile(VoxelPos, Direction.Back, 1, PortAccess.BiDirectional), {
// new ItemConveyor.BeltTEnd(ItemConveyor.ConveyorEnd.Start), _guid = FoodFactoryApi.GridRegistry.Register(this, VoxelTransform.Origin);
// (item, offset) => this.Provide();
// {
// var obstacle = node.GetDistanceToNextItem(ItemConveyor.BeltDirection.TowardEnd, 0, offset, LaneSpan.One); }
// return ItemConveyor.ITEMSIZE < obstacle.DistanceToCenter - (obstacle.IsItem ? ItemConveyor.ITEMSIZE : 0); public override void _ExitTree()
// }, {
// (item, offset) => { _items.Insert(0, new(item, offset)); return true;}); FoodFactoryApi.GridRegistry.UnRegister(_guid);
// node.EndPort = new ConveyorPort(this, base._ExitTree();
// new BeltPortProfile(VoxelPos, Direction.Front, 1, PortAccess.BiDirectional),
// new ItemConveyor.BeltTEnd(ItemConveyor.ConveyorEnd.End),
// (item, offset) =>
// {
// var obstacle = node.GetDistanceToNextItem(ItemConveyor.BeltDirection.TowardEnd, node.Length, offset, LaneSpan.One);
// return ItemConveyor.ITEMSIZE < obstacle.DistanceToCenter - (obstacle.IsItem ? ItemConveyor.ITEMSIZE : 0);
// // return ItemConveyor.ITEMSIZE < GetDistanceToNextItem(ItemConveyor.BeltDirection.TowardStart, Length, SpeedMagnitude, LaneSpan.One);
// },
// (item, offset) => { _items.Add(new(item, offset)); return true;});
} }
} }

View File

@@ -1,10 +1,10 @@
[gd_scene load_steps=7 format=3 uid="uid://c4h7mwnfrdesg"] [gd_scene format=3 uid="uid://c4h7mwnfrdesg"]
[ext_resource type="Script" uid="uid://dgyuh6un1qoj4" path="res://src/Conveyors/ConveyorBeltStraight/ConveyorBeltStraight.cs" id="1_mn8ro"] [ext_resource type="Script" uid="uid://dgyuh6un1qoj4" path="res://src/Conveyors/ConveyorBeltStraight/ConveyorBeltStraight.cs" id="1_mn8ro"]
[ext_resource type="Script" uid="uid://j24fuotdwwx4" path="res://src/VoxelGrid/TestItemConveyor.cs" id="2_1vr1d"] [ext_resource type="Script" uid="uid://j24fuotdwwx4" path="res://src/Conveyors/TestItemConveyor.cs" id="2_1vr1d"]
[ext_resource type="PackedScene" uid="uid://bapdy1c6rb4qr" path="res://assets/kenney_conveyor-kit/Models/GLB format/conveyor-sides.glb" id="2_mn8ro"] [ext_resource type="PackedScene" uid="uid://bapdy1c6rb4qr" path="res://assets/kenney_conveyor-kit/Models/GLB format/conveyor-sides.glb" id="2_mn8ro"]
[ext_resource type="PackedScene" uid="uid://ccuhhy2oa8xvm" path="res://assets/kenney_conveyor-kit/Models/GLB format/arrow-basic.glb" id="3_1vr1d"] [ext_resource type="PackedScene" uid="uid://ccuhhy2oa8xvm" path="res://assets/kenney_conveyor-kit/Models/GLB format/arrow-basic.glb" id="3_1vr1d"]
[ext_resource type="Script" uid="uid://bjuntmf2sjynp" path="res://src/VoxelGrid/ConveyorItemRender.cs" id="4_g7n1f"] [ext_resource type="Script" uid="uid://bjuntmf2sjynp" path="res://src/Conveyors/ConveyorItemRender.cs" id="4_g7n1f"]
[sub_resource type="Curve3D" id="Curve3D_x5qdr"] [sub_resource type="Curve3D" id="Curve3D_x5qdr"]
_data = { _data = {
@@ -13,22 +13,23 @@ _data = {
} }
point_count = 2 point_count = 2
[node name="ConveyorBeltStraight" type="Node3D"] [node name="ConveyorBeltStraight" type="Node3D" unique_id=1032197109]
script = ExtResource("1_mn8ro") script = ExtResource("1_mn8ro")
Width = 1
[node name="Node" type="Node" parent="."] [node name="ConveyorLogic" type="Node" parent="." unique_id=1693852977]
script = ExtResource("2_1vr1d") script = ExtResource("2_1vr1d")
[node name="conveyor-sides" parent="." instance=ExtResource("2_mn8ro")] [node name="conveyor-sides" parent="." unique_id=516239399 instance=ExtResource("2_mn8ro")]
transform = Transform3D(-4.371139e-08, 0, -1, 0, 1, 0, 1, 0, -4.371139e-08, 0, 0, 0) transform = Transform3D(-4.371139e-08, 0, -1, 0, 1, 0, 1, 0, -4.371139e-08, 0, 0, 0)
[node name="arrow-basic2" parent="." instance=ExtResource("3_1vr1d")] [node name="arrow-basic2" parent="." unique_id=1668302786 instance=ExtResource("3_1vr1d")]
transform = Transform3D(-4.371139e-08, 0, -1, 0, 1, 0, 1, 0, -4.371139e-08, 0, 0.21879572, 0) transform = Transform3D(-4.371139e-08, 0, -1, 0, 1, 0, 1, 0, -4.371139e-08, 0, 0.21879572, 0)
[node name="Path3D" type="Path3D" parent="."] [node name="Path3D" type="Path3D" parent="." unique_id=878303794]
curve = SubResource("Curve3D_x5qdr") curve = SubResource("Curve3D_x5qdr")
[node name="Node2" type="Node" parent="." node_paths=PackedStringArray("Path3D", "ItemConveyor")] [node name="Node2" type="Node" parent="." unique_id=1875202216 node_paths=PackedStringArray("Path3D", "ItemConveyor")]
script = ExtResource("4_g7n1f") script = ExtResource("4_g7n1f")
Path3D = NodePath("../Path3D") Path3D = NodePath("../Path3D")
ItemConveyor = NodePath("../Node") ItemConveyor = NodePath("../ConveyorLogic")

View File

@@ -0,0 +1,209 @@
namespace FoodFactory.Conveyors;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using Arch.Core;
using Chickensoft.AutoInject;
using Chickensoft.Introspection;
using Chickensoft.Sync.Primitives;
using CommunityToolkit.HighPerformance.Helpers;
using FoodFactory.Items;
using Godot;
using SJK.Math;
[Meta(typeof(IAutoNode))]
public partial class ConveyorItemRender : Node
{
public override void _Notification(int what) => this.Notify(what);
[Export] protected Path3D Path3D { get; set; } = default!;
[Export] protected TestItemConveyor ItemConveyor { get; set; } = default!;
[Chickensoft.AutoInject.Dependency] protected IItemRenderer Items => this.DependOn<IItemRenderer>();
[Chickensoft.AutoInject.Dependency] protected IFoodFactoryApi FoodFactoryApi => this.DependOn<IFoodFactoryApi>();
// private Chickensoft.Sync.Primitives.AutoList<ConveyorSlice>.Binding _binding = default!;
private AutoList<Ordered1DList<IBeltItem>.Entry>.Binding _binding = default!;
public void OnResolved()
{
_binding = ItemConveyor.Items.Values.Bind().OnUpdate((old, updated) =>
{
if (old.Position == updated.Position)
{
return;
}
Items.UpdateTransform(updated.Value, Path3D.GlobalTransform * Path3D.Curve.SampleBakedWithRotation(updated.Position), (float)delta);
});
// FoodFactoryApi.TickManger.GameTick += _ =>Process(_.Delta);// offset = 0;
FoodFactoryApi.TickManger.GameTick += _ => delta = _.Delta;// offset = 0;
}
// public override async void _Ready()
// {
// base._Ready();
// if (!ItemConveyor.IsNodeReady())
// {
// await ToSignal(ItemConveyor, Node.SignalName.Ready);
// }
// await ToSignal(GetTree(), SceneTree.SignalName.ProcessFrame);
// // ItemConveyor.GetChildren().OfType<Timer>().First().Timeout += () => offset = 0;
// return;
// // _binding = ItemConveyor.Items.Items.Bind();
// // binding.OnRemove(callback =>
// // {
// // Items.Remove(callback.Item);
// // // GD.PrintS(callback.Item, callback.BeltT);
// // // if (itemsRenders.TryGetValue(callback.Item, out var node))
// // // {
// // // GD.PrintS(callback.Item, callback.BeltT,node);
// // // itemsRenders.Remove(callback.Item);
// // // node.QueueFree();
// // // }
// // });
// // _binding.OnAdd((i, v) => Items.UpdateTransform(i.Item, Path3D.GlobalTransform * Path3D.Curve.SampleBakedWithRotation(i.BeltT)));
// // _binding.OnUpdate((a, b) => Items.UpdateTransform(a.Item, Path3D.GlobalTransform * Path3D.Curve.SampleBakedWithRotation(b.BeltT).Translated(-Path3D.Curve.SampleBakedWithRotation(b.BeltT).Basis.X * b.LaneSpan.Start)));
// }
float offset = 0;
double delta = 0;
// public void Process(double delta)
// {
// offset += (float)delta;
// base._Process(delta);
// var e = ItemConveyor.EnumerateTowardStart();
// while (e.MoveNext())
// {
// // Items.UpdateTransform(e.Current.Value, Path3D.GlobalTransform * Path3D.Curve.SampleBakedWithRotation(e.Current.Position + (ItemConveyor.SignedSpeed * offset)));
// Items.UpdateTransform(e.Current.Value, Path3D.GlobalTransform * Path3D.Curve.SampleBakedWithRotation(e.Current.Position), (float)delta);
// }
// }
protected override void Dispose(bool disposing)
{
_binding.Dispose();
base.Dispose(disposing);
}
}
public interface IItemRenderer
{
// Node3D GetVisualNode(IBeltItem beltItem);
void UpdateTransform(IBeltItem beltItem, Transform3D newTransform);
void UpdateTransform(IBeltItem beltItem, Transform3D newTransform, float time);
void Remove(IBeltItem beltItem);
}
public partial class ItemRenderBuffered : Node3D, IItemRenderer
{
private record struct ItemEntry(IBeltItem Item, Transform3D PreviousTransform, Transform3D NewTransform, float Elapsed, float Duration);
private List<ItemEntry> _current = [];
private List<ItemEntry> _next = [];
private Dictionary<IBeltItem, Node3D> _visuals = [];
public void Remove(IBeltItem beltItem) => throw new System.NotImplementedException();
public void UpdateTransform(IBeltItem beltItem, Transform3D newTransform) => throw new System.NotImplementedException();
public void UpdateTransform(IBeltItem beltItem, Transform3D newTransform, float time)
{
var index = _current.FindIndex(f => f.Item == beltItem);
if (index >= 0)
{
_current[index] = _current[index] with { PreviousTransform = _current[index].NewTransform, NewTransform = newTransform, Elapsed = 0, Duration = time };
}
else
{
if (!_visuals.TryGetValue(beltItem, out var node3D))
{
_visuals[beltItem] = node3D = beltItem.CreateItemVisual();
node3D.Transform = newTransform;
AddChild(node3D);
beltItem.Disposed += _ => node3D.QueueFree();
}
// if (node3D.Transform.Origin.DistanceTo(newTransform.Origin) < .00001f)
// {
// return;
// }
_current.Add(new(beltItem, node3D.Transform, newTransform, 0, time));
}
}
public override void _Process(double delta)
{
_next.Clear();
GD.Print(_current.Count);
for (int i = 0; i < _current.Count; i++)
{
var entry = _current[i];
if (!IsInstanceValid(_visuals[entry.Item])){
_visuals.Remove(entry.Item);
continue;
}
entry.Elapsed = (float)(entry.Elapsed + delta);
_visuals[entry.Item].Transform = entry.PreviousTransform.InterpolateWith(entry.NewTransform, entry.Elapsed / entry.Duration);
if ((entry.Elapsed / entry.Duration) < 1f)
{
_next.Add(entry);
}
}
(_current, _next) = (_next, _current);
}
}
public partial class ItemRenderSimple : Node3D, IItemRenderer
{
private Dictionary<IBeltItem, Node3D> _items = [];
public void Remove(IBeltItem beltItem)
{
if (_items.TryGetValue(beltItem, out var node))
{
node.QueueFree();
}
_items.Remove(beltItem);
}
public void UpdateTransform(IBeltItem beltItem, Transform3D newTransform, float time)
{
if (!_items.TryGetValue(beltItem, out var node))
{
_items.Add(beltItem, node = beltItem.CreateItemVisual());
AddChild(node);
beltItem.Disposed += _ => { node.QueueFree(); _items.Remove(beltItem); };
node.Transform = newTransform;
return;
}
node.Transform = newTransform;
}
public void UpdateTransform(IBeltItem beltItem, Transform3D newTransform) => UpdateTransform(beltItem, newTransform, .25f);
}
public partial class TestItemRendered : Node3D, IItemRenderer
{
public Dictionary<IBeltItem, Node3D> _items = [];
private ConditionalWeakTable<IBeltItem, Tween> _tweens = [];
public void Remove(IBeltItem beltItem)
{
if (_items.TryGetValue(beltItem, out var node))
{
node.QueueFree();
}
_items.Remove(beltItem);
}
public void UpdateTransform(IBeltItem beltItem, Transform3D newTransform, float time)
{
if (!_items.TryGetValue(beltItem, out var node))
{
_items.Add(beltItem, node = beltItem.CreateItemVisual());
AddChild(node);
beltItem.Disposed += _ => { node.QueueFree(); _items.Remove(beltItem); };
node.Transform = newTransform;
return;
}
if (_tweens.TryGetValue(beltItem, out var tween))
{
tween.Kill();
_tweens.Remove(beltItem);
}
// GD.Print(newTransform);
tween = GetTree().CreateTween().BindNode(node);
tween.TweenProperty(node, "transform", newTransform, time);
_tweens.Add(beltItem, tween);
}
public void UpdateTransform(IBeltItem beltItem, Transform3D newTransform) => UpdateTransform(beltItem, newTransform, .25f);
}

View File

@@ -0,0 +1,312 @@
namespace FoodFactory.Conveyors;
using Godot;
using System;
using System.Collections.Generic;
using System.Linq;
using Chickensoft.Sync.Primitives;
using SJK.Functional;
using SJK.Math;
using FoodFactory.Items;
using FoodFactory.Voxel;
public interface IMovementConveyor
{
IBeltPort StartPort { get; }
// IBeltSlotProfile StartPort { get; }
IBeltPort EndPort { get; }
// IBeltSlotProfile EndPort { get; }
// IOption<IBeltSlotProfile> InputPort { get; }
// IOption<IBeltSlotProfile> OutputPort { get; }
// IEnumerable<IBeltSlotProfile> GetPorts();
IEnumerable<IBeltPort> GetPorts();
IAutoValue<float> SpeedValue { get; }
float SpeedMagnitude { get; set; }
float SignedSpeed { get; set; }
bool IsReversed { get; set; }
float Length { get; set; }
bool HasItems();
// float GetAvailableTravel(ItemConveyor.BeltDirection beltDirection, LaneSpanT itemSpan, float maxDistance);
// IBeltSlotProfile GetPortFacingStart();
// IBeltSlotProfile GetPortFacingEnd();
BeltDirection GetBeltDirection();
// Option<ConveyorSlice> GetItemTowardStart();
// Option<ConveyorSlice> GetItemTowardEnd();
// Option<ConveyorSlice> GetItemTowardInput();
// Option<ConveyorSlice> GetItemTowardOutput();
Ordered1DList<IBeltItem>.Enumerator EnumerateTowardEnd();
Ordered1DList<IBeltItem>.Enumerator EnumerateTowardStart();
IBeltMovement GetMovementPolicy();
// IList<ConveyorSlice> Items { get; }
IOption<IBeltPort> GetPortFacing(IBeltPort slot);
BeltObstacle GetDistanceToNextItem(BeltDirection beltDirection, float itemBeltT, float maxDistToCheck, HashSet<IMovementConveyor>? visted = null);
ConveyorPort CreatePort(BeltPortProfile profile, BeltT beltT);
}
public interface IBeltMovement
{
void AdvanceBelt(IMovementConveyor conveyor, float delta);
}
public sealed class IndividualMovement : IBeltMovement
{
public void AdvanceBelt(IMovementConveyor conveyor, float delta)
{
// GD.Print(conveyor.Items);
if (conveyor.GetBeltDirection() == BeltDirection.NotMoving)
{
return;
}
// GD.Print("hello");
var towardStart = conveyor.GetBeltDirection() == BeltDirection.TowardStart;
var enumerator = towardStart ? conveyor.EnumerateTowardStart() : conveyor.EnumerateTowardEnd();
while (enumerator.MoveNext())
{
var itemRef = enumerator.Current;
float maxMove = conveyor.SpeedMagnitude * delta;
var space = conveyor.GetDistanceToNextItem(conveyor.GetBeltDirection(), itemRef.Position, maxMove);
// space -= ITEMSIZE;//AcountForSPacing
// if ()
// {
// continue;
// }
if (space is ItemBeltObstacle itemOb && itemOb.Distance <= 0)
{
continue;
}
float amountToMove = Mathf.Min(space.Distance, maxMove);
enumerator.Set(itemRef.Position + amountToMove * Mathf.Sign(conveyor.SignedSpeed));
// itemRef.Replace(itemRef.Value with { BeltT = itemRef.Value.BeltT + amountToMove * Mathf.Sign(conveyor.SignedSpeed) });
// GD.PrintS(conveyor.Items.Count,space,amountToMove," "+ itemRef.BeltT,itemRef.Item,conveyor.GetBeltDirection(),towardStart);
if (towardStart ? itemRef.Position <= 0 : itemRef.Position >= conveyor.Length)
{
var facing = conveyor.GetPortFacing(towardStart ? conveyor.StartPort : conveyor.EndPort);
// GD.Print(facing.HasValue(out var slot2),slot2);// , slot2.CanAccept(new TestItem(),LaneSpan.One,0));
if (facing.HasValue(out var slot) && slot.TryInsert(itemRef.Value, 0))
{
// GD.Print(space is PortBeltObstacle port?port.LaneSpan:itemRef.Value.LaneSpan);
// GD.PrintS(itemRef.Value.LaneSpan,itemRef.Value.LaneSpan);
// GD.Print(itemRef.Item);
enumerator.Remove();
}
}
}
// enumerator.SortAll();
}
}
public sealed class StrictMovement : IBeltMovement
{
// // public event IBeltMovement.ItemMoved? OnItemMoved;
// public void AdvanceBelt(ItemConveyor conveyor, float delta)
// {
// if (!conveyor.AnyItems())
// {
// return;
// }
// var beltDirection = conveyor.GetBeltDirection();
// if (beltDirection == BeltDirection.NotMoving)
// {
// return;
// }
// var towardStart = beltDirection == BeltDirection.TowardStart;
// // GD.Print("Thing: " + Speed + " "+ conveyor.GetConveyorPortFacing(conveyor.GetOutputPort()));
// // var moveItemsIntoNextBelt = false;
// var next = conveyor.GetConveyorPortFacing(conveyor.GetOutputPort());
// var possibleItemTravel = conveyor.GetAvailableTravelForFrontItem(next.HasValue);
// // return;
// if (!possibleItemTravel.HasValue)
// {
// //There is no items, this should not be called
// throw new NotSupportedException("This should not e possible if there is no items or the belt is not moving, it should have returend before");
// }
// // if (next.HasValue && next.Value.Port.BeltT is BeltTEnd beltTEnd && (next.Value.Port.Direction == PortAccess.In || next.Value.Port.Direction == PortAccess.BiDirectional))
// // {
// // possibleItemTravel += next.Value.Conveyor.DistanceFromEnd(beltTEnd.End);
// // moveItemsIntoNextBelt = true;
// // }
// // var itemPair = conveyor.GetFirstItem();
// // var distToEnd = conveyor.GetAvailableTravel(conveyor.ItemsCount - 1, beltDirection);//(Speed < 0 ? ConveyorEnd.Start : ConveyorEnd.End);
// var maxSpeed = Mathf.Min(conveyor.SpeedMagnitude * delta, possibleItemTravel.Value) * Mathf.Sign(conveyor.SignedSpeed);//Or possibly (int)BeltDirection, givn that they stgore the sign as part of the enum
// if (maxSpeed == 0)
// {
// // var fI = conveyor.GetAvailableTravelForFrontItem();
// // if (fI.HasValue && fI.Value <= 0 && next.Value.Port.TryInsertItem(conveyor.GetFirstItem().Value.Item, LaneSpan.One))
// // {
// // GD.Print("Item Moved");
// // conveyor._items.Remove(conveyor.GetFirstItem().Value);
// // }
// return;
// }
// List<ConveyorSlice> toMove = [];
// // Dictionary<IBeltItem, List<ItemMovementSegment>> moves = [];
// for (int i = 0; i < conveyor.ItemsCount; i++)
// {
// // List<ItemMovementSegment> segments = [];
// // var startT = conveyor._items[i].BeltT;
// var endT = Mathf.Clamp(conveyor._items[i].BeltT + maxSpeed, 0, conveyor.Length);
// conveyor._items[i] = conveyor._items[i] with { BeltT = endT };
// // segments.Add(new(conveyor, startT, towardStart? Mathf.Max(endT, 0) : Mathf.Min(endT, conveyor.Length),Mathf.Abs(maxSpeed)));
// if (towardStart ? (endT <= 0) : endT >= conveyor.Length)
// {
// toMove.Add(conveyor._items[i]);
// // segments.Add(new(next.Value.Conveyor, ));
// }
// // moves.Add(conveyor._items[i].Item, segments);
// GD.Print($"Item: {conveyor._items[i].Item}, Position:{endT}");
// }
// // conveyor._items.ForEach((item) => item = item = Mathf.Clamp(item.Position + maxSpeed, 0, Length));
// // var list = _items.Where(item=> Speed>=0?item.BeltT >= Length:item.BeltT <= 0).ToList();
// // GD.Print(list.Count);
// if (next.HasValue)
// {
// foreach (var item in toMove)
// {
// GD.Print($"Item: {item.Item}, Position:{item.BeltT} -----------------");
// //TEST
// // var v = GetConveyorPortFacing(GetOutputPort());
// // if (v.HasValue)//Should be true if items are > Length
// // {
// // GD.PrintS(v.Value.Port.LocalOffset,v.Value.Port.BeltT,v.Value.Port.Direction,v.Value.Port.PullPush,v.Value.Port.Face);
// // var end = (next.Value.Port.BeltT as BeltTEnd).End;
// // next.Value.Conveyor.InsertAtEndWithoutCheck(end, item.Item);
// if (next.Value.Port.TryInsertItem(item.Item, item.LaneSpan))
// {
// GD.Print("Item Moved");
// conveyor._items.Remove(item);
// }
// }
// }
// }
public void AdvanceBelt(IMovementConveyor conveyor, float delta)
{
// GD.Print(conveyor.Items);
if (conveyor.GetBeltDirection() == BeltDirection.NotMoving)
{
return;
}
if (!conveyor.HasItems())
{
return;
}
// GD.Print("hello");
var towardStart = conveyor.GetBeltDirection() == BeltDirection.TowardStart;
float maxMove = conveyor.SpeedMagnitude * delta;
var enumerator = !towardStart ? conveyor.EnumerateTowardStart() : conveyor.EnumerateTowardEnd();
BeltObstacle freeSpace = default!;
float lastPos = 0;
if (enumerator.MoveNext())
{
lastPos = enumerator.Current.Position;
freeSpace = conveyor.GetDistanceToNextItem(conveyor.GetBeltDirection(),lastPos,maxMove,[]);
}
if (lastPos > conveyor.Length || lastPos < 0)
{
enumerator = !towardStart ? conveyor.EnumerateTowardStart() : conveyor.EnumerateTowardEnd();
while (enumerator.MoveNext() && (enumerator.Current.Position >= conveyor.Length || enumerator.Current.Position <= 0))
{
if (towardStart ? enumerator.Current.Position <= 0 : enumerator.Current.Position >= conveyor.Length)
{
var facing = conveyor.GetPortFacing(towardStart ? conveyor.StartPort : conveyor.EndPort);
// GD.Print(facing.HasValue(out var slot2),slot2);// , slot2.CanAccept(new TestItem(),LaneSpan.One,0));
if (facing.HasValue(out var slot) && slot.TryInsert(enumerator.Current.Value, 0))
{
// GD.Print(space is PortBeltObstacle port?port.LaneSpan:itemRef.Value.LaneSpan);
// GD.PrintS(itemRef.Value.LaneSpan,itemRef.Value.LaneSpan);
// GD.Print(itemRef.Item);
enumerator.Remove();
}
}
}
return;
}
// var freeSpace = conveyor.GetDistanceToNextItem(conveyor.GetBeltDirection(),conveyor.GetLastItemPosition(),maxMove,LaneSpan.One,[]);
if (freeSpace.Distance <= 0)
{
return;
}
enumerator = !towardStart ? conveyor.EnumerateTowardStart() : conveyor.EnumerateTowardEnd();
if (enumerator.MoveNext())
{
enumerator.MoveAllBy(MathF.Min(maxMove, freeSpace.Distance));
}
enumerator = !towardStart ? conveyor.EnumerateTowardStart() : conveyor.EnumerateTowardEnd();
while (enumerator.MoveNext() && (enumerator.Current.Position >= conveyor.Length || enumerator.Current.Position <= 0))
{
if (towardStart ? enumerator.Current.Position <= 0 : enumerator.Current.Position >= conveyor.Length)
{
var facing = conveyor.GetPortFacing(towardStart ? conveyor.StartPort : conveyor.EndPort);
// GD.Print(facing.HasValue(out var slot2),slot2);// , slot2.CanAccept(new TestItem(),LaneSpan.One,0));
if (facing.HasValue(out var slot) && slot.TryInsert(enumerator.Current.Value, 0))
{
// GD.Print(space is PortBeltObstacle port?port.LaneSpan:itemRef.Value.LaneSpan);
// GD.PrintS(itemRef.Value.LaneSpan,itemRef.Value.LaneSpan);
// GD.Print(itemRef.Item);
enumerator.Remove();
}
}
}
}
}
public struct ConveyorSlice(IBeltItem item, float beltT = 0)
{
public IBeltItem Item = item;
public float BeltT = beltT;
public override readonly string ToString() => $"{Item}, {BeltT}";
}
public enum BeltDirection : sbyte
{
TowardStart = -1,
NotMoving = 0,
TowardEnd = 1
}
public enum ConveyorEnd { Start, End }
public abstract record BeltT();
public record BeltTEnd(ConveyorEnd End) : BeltT();
public record BeltTOffset(float T) : BeltT();
public static class ConveyorExtensions
{
public static BeltDirection Reverse(this BeltDirection beltDirection) => beltDirection switch
{
BeltDirection.TowardStart => BeltDirection.TowardEnd,
BeltDirection.TowardEnd => BeltDirection.TowardStart,
_ => throw new NotSupportedException(),
};
public static IOption<IBeltPort> GetPortFacing(this IBeltPort slot, IVoxelGridRegistry gridRegistry)
{
var toCheck = new List<Vector3I>();
for (int i = 0; i < slot.Profile.Width; i++)
{
toCheck.Add(slot.Profile.LocalOffset.LocalToWorld(Vector3I.Forward));
}
return gridRegistry
.Get<IBeltPort>([.. toCheck])
.Where(port => port.Profile.LocalOffset.TransformDirection(Vector3I.Forward) == slot.Profile.LocalOffset.TransformDirection(Vector3I.Back))
.FirstOrNone();
}
public static BeltDirection DirectionTo(this ConveyorEnd end) => end switch
{
ConveyorEnd.Start => BeltDirection.TowardStart,
ConveyorEnd.End => BeltDirection.TowardEnd,
_ => throw new NotImplementedException(),
};
public static ConveyorEnd Swap(this ConveyorEnd end) => end switch { ConveyorEnd.Start => ConveyorEnd.End, ConveyorEnd.End => ConveyorEnd.Start, _ => throw new NotSupportedException() };
}

View File

@@ -0,0 +1,360 @@
namespace FoodFactory.Conveyors;
using Chickensoft.Introspection;
using Chickensoft.AutoInject;
using Godot;
using System;
using System.Collections.Generic;
using System.Linq;
using Chickensoft.Sync.Primitives;
using SJK.Functional;
using SJK.Math;
using FoodFactory.Items;
using FoodFactory.Voxel;
[Meta(typeof(IAutoNode))]
[Tool]
public partial class TestItemConveyor : Node, IMovementConveyor
{
public override void _Notification(int what) => this.Notify(what);
[Dependency] public IBeltMovement MovementSystem => this.DependOn<IBeltMovement>(() => new IndividualMovement());
[Dependency] public IVoxelGridRegistry GridRegistry => this.DependOn<IVoxelGridRegistry>();
[Dependency] public IFoodFactoryApi FoodFactoryApi => this.DependOn<IFoodFactoryApi>();
// private readonly AutoList<ConveyorSlice> _items = [];
// public IAutoList<ConveyorSlice> Items => _items;
// public readonly Sorted1DList<ConveyorSlice> Items = new(pos => pos.BeltT);
private readonly Ordered1DList<IBeltItem> _items = new();
public Ordered1DList<IBeltItem> Items => _items;
// private AutoList<ConveyorSlice>.Binding _itemsBinding;
public IBeltPort StartPort { get; set; }
// public IBeltSlotProfile StartPort => new ItemConveyor.ConveyorPort() {
// Face = Direction.Back,
// MovementConveyor = this,
// LocalOffset = Position,
// BeltT = new ItemConveyor.BeltTEnd(ItemConveyor.ConveyorEnd.Start),
// PullPush = TransferMode.PushPull ,
// Direction = PortAccess.BiDirectional,
// AcceptItemFunc = (item,offset)=>{ _items.Insert(0,new ConveyorSlice(item,offset)); return true;},
// CanAcceptItemFunc = (item, offset) =>
// {
// return .25f <= GetDistanceToNextItem(ItemConveyor.BeltDirection.TowardEnd, 0, 5f, LaneSpan.One);
// }
// };
public IBeltPort EndPort { get; set; }
// public IBeltSlotProfile EndPort => new ItemConveyor.ConveyorPort() {
// Face = Direction.Front,
// MovementConveyor = this,
// Direction = PortAccess.BiDirectional,
// LocalOffset = Position,
// BeltT = new ItemConveyor.BeltTEnd(ItemConveyor.ConveyorEnd.End),
// PullPush = TransferMode.PushPull,
// AcceptItemFunc = (item,offset)=>{ _items.Add(new ConveyorSlice(item,Length-offset)); return true;},
// CanAcceptItemFunc = (item,offset)=> .25f >= GetDistanceToNextItem(ItemConveyor.BeltDirection.TowardStart,Length,.5f,LaneSpan.One)
// };
public IList<IBeltPort> OtherPorts = [];
// [Export] public Vector3I Position { get; set; } = default!;
//Speed per unit time
private readonly AutoValue<float> _speed = new(.5f);
public IAutoValue<float> SpeedValue => _speed;
public float SignedSpeed
{
get => _speed.Value;
set => _speed.Value = value;
}
public float SpeedMagnitude
{
get => Mathf.Abs(_speed.Value);
set => _speed.Value = Mathf.Abs(value) * Mathf.Sign(_speed.Value);
}
public bool IsReversed { get => _speed.Value < 0; set => _speed.Value = SpeedMagnitude * (value ? -1 : 1); }
// public IList<ConveyorSlice> Items => _items;
public float Length { get; set; } = 1;
// public IEnumerable<ConveyorSlice> EnumerateTowardEnd() => _items;
// public IEnumerable<ConveyorSlice> EnumerateTowardStart() => _items.Reverse();
public BeltDirection GetBeltDirection() => IsReversed ? BeltDirection.TowardStart : BeltDirection.TowardEnd;
public IBeltMovement GetMovementPolicy() => MovementSystem;
public IEnumerable<IBeltPort> GetPorts() => [StartPort, EndPort, .. OtherPorts];
public void OnResolved()
{
if (Engine.IsEditorHint())
{
return;
}
// var timer = new Timer() { WaitTime = .05f, Autostart = true };
// AddChild(timer);
FoodFactoryApi.TickManger.GameTick += args => MovementSystem.AdvanceBelt(this, (float)args.Delta);
if (StartPort is null || EndPort is null)
{
throw new Exception();
}
foreach (var item in GetPorts())
{
var points = item.Points();
var guid = GridRegistry.Register(item, [.. points]);
TreeExiting += () => GridRegistry.UnRegister(guid);
}
}
//ChatGPT Assisted
public BeltObstacle GetDistanceToNextItem(
BeltDirection beltDirection,
float itemBeltT,
float maxDistToCheck,
HashSet<IMovementConveyor>? visited = null)
{
if (beltDirection == BeltDirection.NotMoving)
{
throw new NotSupportedException();
}
visited ??= [];
if (!visited.Add(this))
{
return new BeltObstacle(0);
}
bool towardStart = beltDirection == BeltDirection.TowardStart;
// 1⃣ Try to find next item on this conveyor
var nextItem = FindNextLocalItem(towardStart, itemBeltT);
// GD.Print(nextItem);
if (nextItem.HasValue(out var item))
{
return new ItemBeltObstacle(MathF.Abs(itemBeltT - item.BeltT) - ITEMSIZE, item);
// return MathF.Abs(itemBeltT - item.BeltT);//TODO UseLaneSpan inadditon
}
// 2⃣ No local item → try crossing into adjacent conveyor
var recursiveDistance = GetDistanceAcrossPort(
towardStart,
itemBeltT,
maxDistToCheck,
visited
);
return recursiveDistance;// with { Distance = recursiveDistance.Distance + Length };
}
//ChatGPT Assisted
private IOption<ConveyorSlice> FindNextLocalItem(
bool towardStart,
float itemBeltT)
{
var sequence = towardStart
? _items.EnumerateTowardStart(itemBeltT)
: _items.EnumerateTowardEnd(itemBeltT);
while (sequence.MoveNext())
{
var item = sequence.Current;
if (towardStart ? item.Position < itemBeltT : item.Position > itemBeltT)
{
return Some<ConveyorSlice>.Of(new ConveyorSlice(item.Value, item.Position));
}
}
return None<ConveyorSlice>.Of();
// return sequence
// .Where(i => towardStart ? i.Value.BeltT < itemBeltT : i.Value.BeltT > itemBeltT)
// .Where(i => LaneSpan.OverLaps(i.Value.LaneSpan, laneSpan))
// .Select(item => item.Value)
// .FirstOrNone();
}
//ChatGPT Assisted
private BeltObstacle GetDistanceAcrossPort(
bool towardStart,
float itemBeltT,
float maxDistToCheck,
HashSet<IMovementConveyor> visited)
{
var port = towardStart ? StartPort : EndPort;
var boundaryT = towardStart ? 0f : Length;
var distanceToBoundary = MathF.Abs(itemBeltT - boundaryT);
// 🚫 Boundary already exceeds budget
// if (distanceToBoundary >= maxDistToCheck)
// {
// return maxDistToCheck;
// }
var neighborPort = FindFacingConveyorPort(port);
if (!neighborPort.HasValue(out var conveyorPort))
{
var o = GetPortFacing(port);
if (o.HasValue(out var otherport))
{
return new BoundaryBeltObstacle(distanceToBoundary);
// return new BeltObstacle(ObstacleKind.Boundary,distanceToBoundary,laneSpan,this,null, null);//TODO THIS SHOULD GET SPACE IN PORT SO ITEMS KNOW IF THEY CAN FIT
}
return new BoundaryBeltObstacle(distanceToBoundary);
}
if (conveyorPort.BeltT is not BeltTEnd end)
{
return new BoundaryBeltObstacle(distanceToBoundary);
}
var nextDirection = DirectionAwayFromEnd(end.End);
var startT = end.End == ConveyorEnd.Start
? 0f
: conveyorPort.Conveyor.Length;
// 🔻 Remaining budget after reaching boundary
// var remainingDist = maxDistToCheck - distanceToBoundary;
// if (remainingDist <= 0f)
// {
// return maxDistToCheck;
// }
var recursiveDistance =
conveyorPort.Conveyor.GetDistanceToNextItem(
nextDirection,
startT,
maxDistToCheck,
visited
);
return recursiveDistance with { Distance = recursiveDistance.Distance + distanceToBoundary };
}
private IOption<ConveyorPort> FindFacingConveyorPort(
IBeltPort slot)
{
var targetpos = slot.Profile.LocalOffset.TransformDirection(Vector3I.Forward);
// var targetPos = port.Profile.Position + port.Profile.Face.ToVector();
return GridRegistry
.Get<IBeltPort>(slot.Profile.LocalOffset.LocalToWorld(Vector3I.Forward))
.Where(port => port is ConveyorPort)
.Where(port => port.Profile.LocalOffset.TransformDirection(Vector3I.Forward) == slot.Profile.LocalOffset.TransformDirection(Vector3I.Back))
.FirstOrNone()
.Bind(p => p is ConveyorPort cp
? cp.ToOption()
: None<ConveyorPort>.Of());
}
public IOption<IBeltPort> GetPortFacing(IBeltPort slot) => slot.GetPortFacing(GridRegistry);
private BeltDirection DirectionAwayFromEnd(ConveyorEnd conveyorEnd) => conveyorEnd switch { ConveyorEnd.Start => BeltDirection.TowardEnd, ConveyorEnd.End => BeltDirection.TowardStart, _ => throw new NotSupportedException() };
// public IEnumerable<Sorted1DList<ConveyorSlice>.ItemHandle> EnumerateTowardEnd() => Items.EnumerateTowardEnd();
// public IEnumerable<Sorted1DList<ConveyorSlice>.ItemHandle> EnumerateTowardStart() => Items.EnumerateTowardStart();
private static readonly Vector3[] _square = [new(-.5f, .5f, -.5f), new(.5f, .5f, -.5f), new(.5f, -.5f, -.5f), new(-.5f, -.5f, -.5f), new(-.5f, .5f, -.5f)];
public const float ITEMSIZE = .2f;
private const float ITEMHALFSIZE = ITEMSIZE / 2f;
// public override void _Process(double delta)
// {
// if (StartPort is null || EndPort is null)
// {
// return;
// }
// foreach (var item in GetPorts())
// {
// for (int ii = 0; ii < item.Profile.Width; ii++)
// {
// DebugDraw3D.DrawLinePath(_square.Select(i => item.Profile.LocalOffset.ToGodot().TranslatedLocal(i).TranslatedLocal(new Vector3(ii, 0, 0)).Origin).ToArray(), (!Engine.IsEditorHint()) && GetPortFacing(item).HasValue() ? Colors.Green : Colors.Red);
// }
// // DebugDraw3D.DrawArrow(item.Profile.LocalOffset.Origin, item.Profile.LocalOffset.LocalToWorld(item.Profile.Face.ToVector()),(!Engine.IsEditorHint())&&GetPortFacing(item).HasValue()?Colors.Green:Colors.Red);
// }
// }
public ConveyorPort CreatePort(BeltPortProfile profile, BeltT beltT)
{
// var towardStartEnd = beltT is ItemConveyor.BeltTEnd end && end.End == ItemConveyor.ConveyorEnd.End;
bool accept(IBeltItem item, float offset)
{
if (beltT is BeltTEnd end)
{
var startBeltT = end.End == ConveyorEnd.Start ? 0 : Length;
var obstacle = GetDistanceToNextItem(end.End.Swap().DirectionTo(), startBeltT, offset);
var distanceAllowed = obstacle.Distance;
return ITEMSIZE < distanceAllowed;
}
else if (beltT is BeltTOffset endOffset)
{
return HasClearance(endOffset.T);
}
throw new NotImplementedException();
}
bool tryInsert(IBeltItem item, float offset)
{
if (beltT is BeltTEnd end)
{
if (end.End == ConveyorEnd.End)
{
_items.Insert(item, Length - offset);
// Items.Insert(new(item, Length - offset) { LaneSpan = laneSpan });
// _items.Add(new(item, Length-offset));
}
else
{
_items.Insert(item, offset);
// Items.Insert(new(item, offset) { LaneSpan = laneSpan });
// _items.Insert(0, new(item, offset));
}
return true;
}
else if (beltT is BeltTOffset endOffset)
{
if (!HasClearance(endOffset.T))
{
return false;
}
_items.Insert(item, endOffset.T);
// Items.Insert(new(item, endOffset.T) { LaneSpan = laneSpan });
// int insertIndex = FindInsertIndex(endOffset.T);
// _items.Insert(insertIndex, new(item, endOffset.T));
return true;
}
// _items.Insert(0,new ConveyorSlice(item,0));
// return true;
throw new NotImplementedException();
}
var port = new ConveyorPort(this,
profile,
beltT,
accept,
tryInsert);
return port;
}
private bool HasClearance(float centerT)
{
var lower = GetDistanceToNextItem(
BeltDirection.TowardStart,
centerT,
ITEMSIZE
);
var upper = GetDistanceToNextItem(
BeltDirection.TowardEnd,
centerT,
ITEMSIZE
);
float lowerAllowed =
lower.Distance;// -
// (lower.IsItem ? ItemConveyor.ITEMSIZE : 0);
float upperAllowed =
upper.Distance;// -
// (upper.IsItem ? ItemConveyor.ITEMSIZE : 0);
return ITEMSIZE < lowerAllowed &&
ITEMSIZE < upperAllowed;
}
public Ordered1DList<IBeltItem>.Enumerator EnumerateTowardEnd() => _items.EnumerateTowardEnd(-1);
public Ordered1DList<IBeltItem>.Enumerator EnumerateTowardStart() => _items.EnumerateTowardStart(_items.Count);
public bool HasItems() => _items.Count > 0;
}
//TODO Should liklely account for max search distance where the conveyorm may be needed to know
public record class BeltObstacle(float Distance);
public record ItemBeltObstacle(float Distance, ConveyorSlice Item) : BeltObstacle(Distance);
public record BoundaryBeltObstacle(float DistanceToBoundary) : BeltObstacle(DistanceToBoundary);
public record PortBeltObstacle(float DistanceToBoundary, IBeltPort BeltPort) : BoundaryBeltObstacle(DistanceToBoundary);

View File

@@ -0,0 +1,15 @@
namespace FoodFactory.Core.Components.Converters;
using SharpYaml.Serialization;
public sealed class TagConverter : YamlConverter<Tag>
{
public override Tag Read(YamlReader reader)
{
var tag = reader.GetScalarValue();
reader.Read();
return Tag.Create(tag);
}
public override void Write(YamlWriter writer, Tag value) => writer.WriteScalar(value.Name);
}

View File

@@ -0,0 +1 @@
uid://cgs4d1j3i5ua6

View File

@@ -0,0 +1,34 @@
namespace FoodFactory.Core.Components.Converters;
using System.Collections.Generic;
using SharpYaml.Serialization;
public sealed class TagsConverter : YamlConverter<Tags>
{
public override Tags Read(YamlReader reader)
{
if (reader.TokenType != YamlTokenType.StartSequence)
{
throw new System.Exception();
}
List<string> tags = [];
reader.Read();
while (reader.TokenType != YamlTokenType.EndSequence)
{
tags.Add(reader.GetScalarValue());
reader.Read();
}
reader.Read();
return new Tags(tags);
}
public override void Write(YamlWriter writer, Tags value)
{
writer.WriteStartSequence();
foreach (var item in value)
{
writer.WriteScalar(item.Name);
}
writer.WriteEndSequence();
}
}

View File

@@ -0,0 +1 @@
uid://dqn40oryt7yyt

Some files were not shown because too many files have changed in this diff Show More