From fae12b7963fe6e5347f0fc531e1497d3d05c4c6d Mon Sep 17 00:00:00 2001 From: copygirl Date: Fri, 16 Sep 2022 22:06:44 +0200 Subject: [PATCH] Initial commit --- .editorconfig | 20 ++ .gitignore | 4 + .gitmodules | 6 + .vscode/launch.json | 16 + .vscode/settings.json | 8 + .vscode/tasks.json | 40 +++ gaemstone.sln | 48 +++ src/FastNoiseLite | 1 + src/Immersion/Immersion.csproj | 23 ++ src/Immersion/Program.cs | 91 +++++ src/Immersion/Resources/LICENSE_NOTICES.md | 11 + src/Immersion/Resources/default.fs.glsl | 12 + src/Immersion/Resources/default.vs.glsl | 20 ++ src/Immersion/Resources/heart.blend | Bin 0 -> 99631 bytes src/Immersion/Resources/heart.glb | Bin 0 -> 8408 bytes src/Immersion/Resources/sword.blend | Bin 0 -> 106853 bytes src/Immersion/Resources/sword.glb | Bin 0 -> 22568 bytes src/Immersion/Resources/terrain.png | Bin 0 -> 5361 bytes src/flecs-cs | 1 + src/gaemstone.Bloxel/BlockFacing.cs | 61 ++++ src/gaemstone.Bloxel/BlockPos.cs | 75 ++++ src/gaemstone.Bloxel/Chunk.cs | 17 + src/gaemstone.Bloxel/ChunkPaletteStorage.cs | 188 ++++++++++ src/gaemstone.Bloxel/ChunkPos.cs | 81 +++++ .../Client/ChunkMeshGenerator.cs | 125 +++++++ src/gaemstone.Bloxel/Neighbor.cs | 205 +++++++++++ src/gaemstone.Bloxel/Utility/ChunkedOctree.cs | 135 ++++++++ src/gaemstone.Bloxel/Utility/ZOrder.cs | 154 +++++++++ .../WorldGen/BasicWorldGenerator.cs | 42 +++ .../SurfaceGrassGenerator.cs.disabled | 57 +++ src/gaemstone.Bloxel/gaemstone.Bloxel.csproj | 18 + src/gaemstone.Client/Color.cs | 56 +++ src/gaemstone.Client/GLExtensions.cs | 62 ++++ src/gaemstone.Client/Mesh.cs | 14 + src/gaemstone.Client/MeshManager.cs | 104 ++++++ src/gaemstone.Client/Modules/CameraModule.cs | 81 +++++ src/gaemstone.Client/Modules/Input.cs | 81 +++++ src/gaemstone.Client/Modules/Renderer.cs | 109 ++++++ src/gaemstone.Client/Modules/Windowing.cs | 31 ++ src/gaemstone.Client/Resources.cs | 30 ++ src/gaemstone.Client/Texture.cs | 14 + src/gaemstone.Client/TextureCoords4.cs | 36 ++ src/gaemstone.Client/TextureManager.cs | 76 ++++ src/gaemstone.Client/gaemstone.Client.csproj | 21 ++ src/gaemstone/ECS/Attributes.cs | 12 + src/gaemstone/ECS/Entity.cs | 193 +++++++++++ src/gaemstone/ECS/EntityDesc.cs.disabled | 20 ++ src/gaemstone/ECS/Filter.cs | 39 +++ src/gaemstone/ECS/FlecsException.cs | 17 + src/gaemstone/ECS/Identifier.cs | 53 +++ src/gaemstone/ECS/Iterator.cs | 93 +++++ src/gaemstone/ECS/Module.cs | 13 + src/gaemstone/ECS/Observer.cs | 18 + src/gaemstone/ECS/Query.cs | 33 ++ src/gaemstone/ECS/System.cs | 93 +++++ src/gaemstone/ECS/Universe+Modules.cs | 140 ++++++++ src/gaemstone/ECS/Universe+Systems.cs | 167 +++++++++ src/gaemstone/ECS/Universe.cs | 184 ++++++++++ src/gaemstone/GlobalTransform.cs | 13 + src/gaemstone/Utility/CStringExtensions.cs | 20 ++ .../Utility/IL/ILGeneratorWrapper.cs | 291 ++++++++++++++++ .../Utility/IL/QueryActionGenerator.cs | 327 ++++++++++++++++++ src/gaemstone/Utility/RandomExtensions.cs | 38 ++ src/gaemstone/Utility/ReflectionExtensions.cs | 73 ++++ src/gaemstone/Utility/TypeWrapper.cs | 229 ++++++++++++ src/gaemstone/gaemstone.csproj | 18 + 66 files changed, 4258 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 .vscode/tasks.json create mode 100644 gaemstone.sln create mode 160000 src/FastNoiseLite create mode 100644 src/Immersion/Immersion.csproj create mode 100644 src/Immersion/Program.cs create mode 100644 src/Immersion/Resources/LICENSE_NOTICES.md create mode 100644 src/Immersion/Resources/default.fs.glsl create mode 100644 src/Immersion/Resources/default.vs.glsl create mode 100644 src/Immersion/Resources/heart.blend create mode 100644 src/Immersion/Resources/heart.glb create mode 100644 src/Immersion/Resources/sword.blend create mode 100644 src/Immersion/Resources/sword.glb create mode 100644 src/Immersion/Resources/terrain.png create mode 160000 src/flecs-cs create mode 100644 src/gaemstone.Bloxel/BlockFacing.cs create mode 100644 src/gaemstone.Bloxel/BlockPos.cs create mode 100644 src/gaemstone.Bloxel/Chunk.cs create mode 100644 src/gaemstone.Bloxel/ChunkPaletteStorage.cs create mode 100644 src/gaemstone.Bloxel/ChunkPos.cs create mode 100644 src/gaemstone.Bloxel/Client/ChunkMeshGenerator.cs create mode 100644 src/gaemstone.Bloxel/Neighbor.cs create mode 100644 src/gaemstone.Bloxel/Utility/ChunkedOctree.cs create mode 100644 src/gaemstone.Bloxel/Utility/ZOrder.cs create mode 100644 src/gaemstone.Bloxel/WorldGen/BasicWorldGenerator.cs create mode 100644 src/gaemstone.Bloxel/WorldGen/SurfaceGrassGenerator.cs.disabled create mode 100644 src/gaemstone.Bloxel/gaemstone.Bloxel.csproj create mode 100644 src/gaemstone.Client/Color.cs create mode 100644 src/gaemstone.Client/GLExtensions.cs create mode 100644 src/gaemstone.Client/Mesh.cs create mode 100644 src/gaemstone.Client/MeshManager.cs create mode 100644 src/gaemstone.Client/Modules/CameraModule.cs create mode 100644 src/gaemstone.Client/Modules/Input.cs create mode 100644 src/gaemstone.Client/Modules/Renderer.cs create mode 100644 src/gaemstone.Client/Modules/Windowing.cs create mode 100644 src/gaemstone.Client/Resources.cs create mode 100644 src/gaemstone.Client/Texture.cs create mode 100644 src/gaemstone.Client/TextureCoords4.cs create mode 100644 src/gaemstone.Client/TextureManager.cs create mode 100644 src/gaemstone.Client/gaemstone.Client.csproj create mode 100644 src/gaemstone/ECS/Attributes.cs create mode 100644 src/gaemstone/ECS/Entity.cs create mode 100644 src/gaemstone/ECS/EntityDesc.cs.disabled create mode 100644 src/gaemstone/ECS/Filter.cs create mode 100644 src/gaemstone/ECS/FlecsException.cs create mode 100644 src/gaemstone/ECS/Identifier.cs create mode 100644 src/gaemstone/ECS/Iterator.cs create mode 100644 src/gaemstone/ECS/Module.cs create mode 100644 src/gaemstone/ECS/Observer.cs create mode 100644 src/gaemstone/ECS/Query.cs create mode 100644 src/gaemstone/ECS/System.cs create mode 100644 src/gaemstone/ECS/Universe+Modules.cs create mode 100644 src/gaemstone/ECS/Universe+Systems.cs create mode 100644 src/gaemstone/ECS/Universe.cs create mode 100644 src/gaemstone/GlobalTransform.cs create mode 100644 src/gaemstone/Utility/CStringExtensions.cs create mode 100644 src/gaemstone/Utility/IL/ILGeneratorWrapper.cs create mode 100644 src/gaemstone/Utility/IL/QueryActionGenerator.cs create mode 100644 src/gaemstone/Utility/RandomExtensions.cs create mode 100644 src/gaemstone/Utility/ReflectionExtensions.cs create mode 100644 src/gaemstone/Utility/TypeWrapper.cs create mode 100644 src/gaemstone/gaemstone.csproj diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..dc74b1f --- /dev/null +++ b/.editorconfig @@ -0,0 +1,20 @@ +root = true + +[*] +end_of_line = lf +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.cs] +indent_style = tab +indent_size = 4 +# IDE0005: Using directive is unnecessary +dotnet_diagnostic.IDE0005.severity = suggestion +# IDE0047: Parentheses can be removed +dotnet_diagnostic.IDE0047.severity = none + +[*.md] +# Allows placing double-space at end of lines. +trim_trailing_whitespace = false diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2f26c96 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +**/obj/ +**/bin/ +/artifacts/ +/packages/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..8a9c26f --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "src/flecs-cs"] + path = src/flecs-cs + url = https://github.com/flecs-hub/flecs-cs +[submodule "src/FastNoiseLite"] + path = src/FastNoiseLite + url = https://github.com/Auburn/FastNoiseLite.git diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..4327687 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Launch Immersion", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/src/Immersion/bin/Debug/net6.0/Immersion.dll", + "args": [], + "cwd": "${workspaceFolder}/src/Immersion", + "console": "internalConsole", + "stopAtEntry": false + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..bfa239e --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "files.exclude": { + "**/.git": true, + "**/.DS_Store": true, + "**/bin": true, + "**/obj": true, + }, +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..5adaf07 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,40 @@ +{ + "version": "2.0.0", + "tasks": [{ + "label": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/src/Immersion/Immersion.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "publish", + "command": "dotnet", + "type": "process", + "args": [ + "publish", + "${workspaceFolder}/src/Immersion/Immersion.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "watch", + "command": "dotnet", + "type": "process", + "args": [ + "watch", + "run", + "--project", + "${workspaceFolder}/src/Immersion/Immersion.csproj" + ], + "problemMatcher": "$msCompile" + } + ] +} diff --git a/gaemstone.sln b/gaemstone.sln new file mode 100644 index 0000000..ff837b3 --- /dev/null +++ b/gaemstone.sln @@ -0,0 +1,48 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30114.105 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{599B7E67-7F73-4301-A9C6-E8DF286A2625}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "gaemstone.Bloxel", "src\gaemstone.Bloxel\gaemstone.Bloxel.csproj", "{7A80D49C-6768-4803-9866-691C7AD80817}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "gaemstone.Client", "src\gaemstone.Client\gaemstone.Client.csproj", "{67B9B2D4-FCB7-4642-B584-A0186CAB2969}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "gaemstone", "src\gaemstone\gaemstone.csproj", "{7744A8A5-7D9A-474C-BC24-1CF0A8CB7EC0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Immersion", "src\Immersion\Immersion.csproj", "{4B9C20F6-0793-4E85-863A-2E14230A028F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7A80D49C-6768-4803-9866-691C7AD80817}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7A80D49C-6768-4803-9866-691C7AD80817}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7A80D49C-6768-4803-9866-691C7AD80817}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7A80D49C-6768-4803-9866-691C7AD80817}.Release|Any CPU.Build.0 = Release|Any CPU + {67B9B2D4-FCB7-4642-B584-A0186CAB2969}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {67B9B2D4-FCB7-4642-B584-A0186CAB2969}.Debug|Any CPU.Build.0 = Debug|Any CPU + {67B9B2D4-FCB7-4642-B584-A0186CAB2969}.Release|Any CPU.ActiveCfg = Release|Any CPU + {67B9B2D4-FCB7-4642-B584-A0186CAB2969}.Release|Any CPU.Build.0 = Release|Any CPU + {7744A8A5-7D9A-474C-BC24-1CF0A8CB7EC0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7744A8A5-7D9A-474C-BC24-1CF0A8CB7EC0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7744A8A5-7D9A-474C-BC24-1CF0A8CB7EC0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7744A8A5-7D9A-474C-BC24-1CF0A8CB7EC0}.Release|Any CPU.Build.0 = Release|Any CPU + {4B9C20F6-0793-4E85-863A-2E14230A028F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4B9C20F6-0793-4E85-863A-2E14230A028F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4B9C20F6-0793-4E85-863A-2E14230A028F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4B9C20F6-0793-4E85-863A-2E14230A028F}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {7A80D49C-6768-4803-9866-691C7AD80817} = {599B7E67-7F73-4301-A9C6-E8DF286A2625} + {67B9B2D4-FCB7-4642-B584-A0186CAB2969} = {599B7E67-7F73-4301-A9C6-E8DF286A2625} + {7744A8A5-7D9A-474C-BC24-1CF0A8CB7EC0} = {599B7E67-7F73-4301-A9C6-E8DF286A2625} + {4B9C20F6-0793-4E85-863A-2E14230A028F} = {599B7E67-7F73-4301-A9C6-E8DF286A2625} + EndGlobalSection +EndGlobal diff --git a/src/FastNoiseLite b/src/FastNoiseLite new file mode 160000 index 0000000..5923df5 --- /dev/null +++ b/src/FastNoiseLite @@ -0,0 +1 @@ +Subproject commit 5923df5d822f7610100d0e77f629c607ed64934a diff --git a/src/Immersion/Immersion.csproj b/src/Immersion/Immersion.csproj new file mode 100644 index 0000000..95950e3 --- /dev/null +++ b/src/Immersion/Immersion.csproj @@ -0,0 +1,23 @@ + + + + Exe + net6.0 + disable + enable + + + + + + + + + + + + + + + + diff --git a/src/Immersion/Program.cs b/src/Immersion/Program.cs new file mode 100644 index 0000000..e6615e2 --- /dev/null +++ b/src/Immersion/Program.cs @@ -0,0 +1,91 @@ +using System; +using gaemstone; +using gaemstone.Bloxel; +using gaemstone.Client; +using gaemstone.ECS; +using gaemstone.Utility; +using Silk.NET.Maths; +using Silk.NET.OpenGL; +using Silk.NET.Windowing; +using static flecs_hub.flecs; +using static gaemstone.Client.CameraModule; +using static gaemstone.Client.Input; +using static gaemstone.Client.Windowing; + +var universe = new Universe(); +var game = universe.Lookup(); +Resources.ResourceAssembly = typeof(Program).Assembly; + +var window = Window.Create(WindowOptions.Default with { + Title = "gæmstone", + Size = new(1280, 720), + FramesPerSecond = 60.0, + PreferredDepthBufferBits = 24, +}); +window.Initialize(); +window.Center(); + +universe.RegisterModule(); + +game.Set(new Canvas(window.CreateOpenGL())); +game.Set(new GameWindow(window)); + +TextureManager.Initialize(universe); + +universe.RegisterComponent(); +universe.RegisterComponent(); +universe.RegisterComponent(); + +universe.RegisterModule(); +universe.RegisterModule(); +universe.RegisterModule(); + +game.Set(new RawInput()); + +// TODO: Find a way to automatically register this chunk storage. +universe.RegisterComponent>(); +universe.RegisterAll(typeof(Chunk).Assembly); + +universe.Create("MainCamera") + .Set(Camera.Default3D) + .Set((GlobalTransform)Matrix4X4.CreateTranslation(0.0F, 2.0F, 0.0F)) + .Set(new CameraController { MouseSensitivity = 12.0F }); + +var heartMesh = MeshManager.Load(universe, "heart.glb"); +var swordMesh = MeshManager.Load(universe, "sword.glb"); + +var rnd = new Random(); +for (var x = -12; x <= 12; x++) +for (var z = -12; z <= 12; z++) { + var position = Matrix4X4.CreateTranslation(x * 2, 0.0F, z * 2); + var rotation = Matrix4X4.CreateRotationY(rnd.NextFloat(MathF.PI * 2)); + universe.Create() + .Set((GlobalTransform)(rotation * position)) + .Set(rnd.Pick(heartMesh, swordMesh)); +} + +var texture = TextureManager.Load(universe, "terrain.png"); + +var stone = universe.Create("Stone").Set(TextureCoords4.FromGrid(4, 4, 1, 0)); +var dirt = universe.Create("Dirt" ).Set(TextureCoords4.FromGrid(4, 4, 2, 0)); +var grass = universe.Create("Grass").Set(TextureCoords4.FromGrid(4, 4, 3, 0)); + +var sizeH = 4; +var sizeY = 2; +for (var cx = -sizeH; cx < sizeH; cx++) +for (var cy = -sizeY; cy < sizeY; cy++) +for (var cz = -sizeH; cz < sizeH; cz++) { + var pos = new ChunkPos(cx, cy - 2, cz); + var storage = new ChunkPaletteStorage(default); + universe.Create() + .Set((GlobalTransform)Matrix4X4.CreateTranslation(pos.GetOrigin())) + .Set(new Chunk(pos)) + .Set(storage) + .Set(texture); +} + +window.Render += (delta) => { + if (!universe.Progress(TimeSpan.FromSeconds(delta))) + window.Close(); +}; +window.Run(); diff --git a/src/Immersion/Resources/LICENSE_NOTICES.md b/src/Immersion/Resources/LICENSE_NOTICES.md new file mode 100644 index 0000000..d202a84 --- /dev/null +++ b/src/Immersion/Resources/LICENSE_NOTICES.md @@ -0,0 +1,11 @@ +# Resources License Notices + +## Voxelgarden Textures + +- terrain.png + +**License:** [CC-BY-SA] +**Source:** [github.com/CasimirKaPazi/Voxelgarden](https://github.com/CasimirKaPazi/Voxelgarden) + + +[CC-BY-SA]: https://creativecommons.org/licenses/by-sa/2.0/ diff --git a/src/Immersion/Resources/default.fs.glsl b/src/Immersion/Resources/default.fs.glsl new file mode 100644 index 0000000..0f5aa8e --- /dev/null +++ b/src/Immersion/Resources/default.fs.glsl @@ -0,0 +1,12 @@ +#version 330 core +in vec4 fragColor; +in vec2 fragUV; + +uniform sampler2D textureSampler; + +out vec4 color; + +void main() +{ + color = fragColor * texture(textureSampler, fragUV); +} diff --git a/src/Immersion/Resources/default.vs.glsl b/src/Immersion/Resources/default.vs.glsl new file mode 100644 index 0000000..595f341 --- /dev/null +++ b/src/Immersion/Resources/default.vs.glsl @@ -0,0 +1,20 @@ +#version 330 core +layout(location = 0) in vec3 vertPosition; +layout(location = 1) in vec3 vertNormal; +layout(location = 2) in vec2 vertUV; + +uniform mat4 cameraMatrix; +uniform mat4 modelMatrix; + +out vec4 fragColor; +out vec2 fragUV; + +void main() +{ + gl_Position = cameraMatrix * modelMatrix * vec4(vertPosition, 1.0); + // Apply a pseudo-lighting effect based on the object's normals and rotation. + vec3 normal = mat3(modelMatrix) * vertNormal; + float l = 0.5 + (normal.y + 1) / 4.0 - (normal.z + 1) / 8.0; + fragColor = vec4(l, l, l, 1.0); + fragUV = vertUV; +} diff --git a/src/Immersion/Resources/heart.blend b/src/Immersion/Resources/heart.blend new file mode 100644 index 0000000000000000000000000000000000000000..a95b4409fe21d0c5a8fa181bb2a334dd32ac8ab8 GIT binary patch literal 99631 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 Tx-55h%lO$r)o#iU=h6QG07;nZ literal 0 HcmV?d00001 diff --git a/src/Immersion/Resources/heart.glb b/src/Immersion/Resources/heart.glb new file mode 100644 index 0000000000000000000000000000000000000000..70e6912ae5abec0b2d2633c87b2c974015221d88 GIT binary patch literal 8408 zcmeHJZE#fO6@Em}3Vs3Awn!n~P>34C?q>5TxmSo_67m5_O3{F_Np8YwvWvSL1Y;ml z3Ti7NTA@K(YthySRck-gviBmbMQPd@XRPf^J42__j(=#U(;rOjIAhOwci+tkNw~K^ z`Y)T=^FDj-^Pcyd=RNm*)7{g$Y8rq)MaKwV2yW4{qeChe3_*>7~)F*^}S#S=!Q zv3heN9*ZaQx@k(@iXJ=GX(#e(mp0{X4HgB8N{m8dtDQ(j<1ylfiUP(!p^@yM`9@_R zi;_lVTfY&D^pekLQO^pESiIBb2Z4-%NQF%apg29SVpdwUJ=*JV}w6Y86P*in7ua)x^g)`9uB^9NCaA~+?KvhJw zDiLz(RQI*poOdGH8%;&GW*X=0O8)qHr~3T;tKUCD-D*E2g9Kti8g^NP?{+|R9qeo zmY0-Ouxe52m$sWH;7{V4C`d}BXRKRUT}*99+bYT+K|IU2(EQN(~Wtw3&!+LZR~DZAc|b*Jd{2!+ciKlU_rb*XnYFj~J4HqXDn zgkWj7d~En|ML2wc3FG*sB^4p=@Jv0~T0{Y5p)faMUd?=9MQtNy{XW0y?AkZXXR2pa ztvME}s{8O|bN|V{s+pT=%=SCd3XhD`n}N5o7)Jj zmGKkb(R_;I=s3i8G+)-W_nMc?&i9Y1IJ}m~KUXiu)PCYSnlE*b^>U3jY@r1T=k8+x`4kBl{r@6hL9JPj=zuuQL*PY!*&R=QH`Li$EZ|R|bC3SzMwT7C* zx-izYFebk4PmZJeR$7)B|OdRIu_;N4g9*wOr(?3#w+Q%y9=-wlo>qFK<_nXE%mpNKDy%zb^`J;QD za})KdIl7m0Oy;oeoP);M+|;^^DL&_?dXG6erodI2!#Pl%xxL`J^jwW4CyS|nGG{My z`OAKdbng=ghG= zuEg2=bDD6DydL|tZW2$RNlwG_>dMv2I_PzI)L7KTn$WtV?rdXV^UnuyZsg8Yp$VUjxij#*b5iG~&KZ5SNIvhOzAxncD6xzw&rI%BF*moCJTv8) zFV8f2M{pc{X0mSjJ(78He`!DGtoc$andiT6DsXCc6<8n4U1s^jrr>*G%>I+}$$w_J z^V^nLe(+O&oNdjqPo&pgwAS*8UBUOnfBz)U+=E5df$m!^pV$$6Pn^H+dUwa!VRP~F zV#^OEO-!MWCqD3SfmL=o-<(yy%=LpQ2TSDRi5FI#GYj52=?vadXZggA;Co`q*%kSF z;?RAIoO8*=*1$Z|^@&Zv_r$+-&p3a6?lO1xP`T>|pE{Lq9@$%9p<$Wp2RFaE#JcK{ zGwFlfmg^I{g71kb2S?=Ni5t%>aO-P_&6@X0TtApJF@-*!m||KYz9+u;qubr-XBWEf z5C83TpV$(7PrUTwPOIqSznXLp4En*-&MvfOP)^MEgDIvf;(Ovi+cu}VZJWjMePUDa zJ@J~|54wfx3mlH`6I+7siAfVz=;Mh?+qUI$_K9=)5Z@D%HZJL?_SYx21m6>rj@i#3 z@7ZK{kRH%iaTwFb6Aym+=}y%LOr8Y$i8C2zQ05@wLtw=@GCarqXvr1k+}|}?KCvbE zo|qG)FMS5pdo>BpxHB9@&hp1$%AeyX|0I~6skJh!QOr6|I9Kooec*BN_g^=|edV1G zoO733u1{WZTh!PT1%o+l6jyMilpuy$AQA2fc`q zjUkRLxDN@k2_%t1AGVU+iu|w@pXIyk6|C#eRv%E@dOT#J%A_i6uyb4$v%yPID~Iuh`xt#7|+mnnEnpo zS$vy*9maR?9KK8Avv?lgBi|9cK)+t3zvuCN`~WWz;|O_%@iLC$7=B3OF}#9T@fz9J z@FTpAALA$V_ftYYBg^Ow!f)a&{G9M{qzO52aRP7SBz{32_WqLmZxcF6_8l63g;RJJ R@6q@!PUF`YAv=QK;J=;@uR8z$ literal 0 HcmV?d00001 diff --git a/src/Immersion/Resources/sword.blend b/src/Immersion/Resources/sword.blend new file mode 100644 index 0000000000000000000000000000000000000000..ccf803b552af047bd21bbf92e7edfc5b8ef8096f GIT binary patch literal 106853 zcmXuJcRZWz`~QEp+Pd#*X{)U*U5X<1CaPL$rf6%%4z)_6u|svCMyQ}>sxfPCF>3{} zYQ`$9*b%Wuf_(G-Jbu6Yc|9K2b)Gq{<9Hpf>pY%-sDJQ#4GJKY1N)=T@dXS9uy3{Qt#|Yukrnt^~j>Zqkk&f z-y?)NIDY!@h z*J(s4uf_4~$Us4GR+&ou;_k*2Wln8|A?)mfQ)9z8t^k*CHQTdSEv@-(A(nndVE|=B6+Qt!c z7wgmeS<67t;3eAmPvPT7gxx&yT_dtc)OVZ%QmK3Q>I>F|DVt~xg`VL=`ma9lMUejj6T8rj(RSPU8+b{#H=eqtOGwasc3gO%i>Zu2gW zJn0GE_K?2R@d<{z4zku*C>T+591h9%L&WKqObacmF0Ld=zcq!hanSrI)=hsDZZWu7 zEp=3BXL4q9I)34EU^Tq@EgkgZYv41ODse*BO8g zP&;Pws2+RtwGW1xnl`EfEO1a=dw7DEfFi~LI?VQUeb&dF} z^|fk&@{V|-g1D)Up2mVM%%}?|;I2Nfhj#zrX>or{DJiSn_<4HhVxP|gju)}wAsh(c zSja1`@UU2gZ1hkje7mH@qp^l)PQ%WQg%PyMXlniLO z5EQibl(csC{zVX-D;fAg*@d-&#%}gC%I}7 z>gC6v^G}%(b82Sco)3nuSzEAPs$Xf@WRw@`F~g#GsQcJ)zSOF^Y~6& zqdPXh0dnyY6y}eKmGLa?ovIbfE7r$)qZ5W}7f$s0{E7~~%LcH1Vm)zJopRGl7sF=g zL)7+Mw~rey=MDVuhp(fD=>@{f6e#$=KN^Mo*`NU8K|Wr!26ViyS4kGeC%#|)yOXz7 zUsNt$7_^)5?)OiX0>1*QQnD=>l0oh*j`BB&kre4`aG<@*!thCh0t%DJ6@#&ehg>}2Zs=v0@YRZf+ z-qpfb_w+@bqSPDztC{brSF}jCguy?`#T!)mu|)w>aVA{hJFCGmv~y!25M_j=;EUL{ zd62-y*pv(WZzT^!n4rLicVXpO%-(FTC?qzPpcb7hT2;LvO4*IyraQl|LIvs#0B9U{ zv7hwl(5GPj*soYa3*mx&FpnsrP7)PV4R=yYJA>~WiB-X5%9t?shU7N8Fq?=A`I&6S z1qqtMy^y>qv{UQU?vAXfDD(@k74L||Gufg7CJI>vXSE_g{!m@Pnb&6U+P1H7@ zP&fxAT*=U<^&e{cP;G9b4oVr2=m%*5j&MLKbSd#)|k>9yvr4YGpp@iQs178E=hozjFU~)AZzp7 z*r>Z;HGkx@4HM!(p74dLZz7>1mvD+w9v(fcaxWd3sS(|4D)JAb+li zX&=6Mu<;6)I6a>n&Mn~wEs0eV>8%=i zPKNLYaA_$YuT+rDcEp8y*?hYZf7yD5-}(Krq31>W_g4ZBn?@wjtOwON)@fdUPxx- zOM8m|Xn3-lK?(Hm8^+o*V#K%plKRQZOJeoxgV9j+x-Y(#;1qW-j-wDUYKjkp*z_VS zNL7+}7R$I~;-^NVg8b&7(};4+<7Oc$>JDS^N$2$Wk&FE7G_4I77>s|UxIr0Kl+H%G z>j5ltPu#7z_;T(V(T8MgCxx-0CU$R96taKyfc`iKpf)T0o&U*Xl?DNJb@jwOMg0o! zM}*gqD&oYKcWs6EOQJ2SRclv+xMKMaRx7n*gWLiaH|b@~TwK?)o9GW!lVyz3+5825 zBF~g}96Ef9Mk}N`R~}3bUkItngcF`xV0v8=Ou;I-&fV(Upe^B@ZP`pukEG>vK)fSI zd1^gOv&%f+2Ra4%Q55XKZTWl{sTZ(F@%e-bm|r(0VNzs+cRA&6yr?z1DO?KdPN3>P z%_sptF*iB9Re+tfdZ)E^>c0lJs{!L#(0T)PWPKz4d!H657&P~ zk&AHfoIL$+ME}4o?LSW8ko{vep#Ne_6jzJPo0VTpRjk_oYtaxObn3?9vKMcPc$4PR zP7|AUEdBb@f$P}agII~;I@hQ6S7*zgk`9z9e4;N#C^;KDaRD?PWKby^v88ONmMr4_KUkQD|dP4qp@- z$u$gmvN>H0j8+~f-7kfqlW*Qc#7Y(({xd+7snvMDbAZlj-w@2?hkItZgf8t|719z= z`z}?I?*B}U-VVCTy76Cw`Q}g8fD))@UN3ISou5)^JaGj}5e+w*6`1)zz5YDrL9^~* zzt8djo%V#QG!PoFb5wc>06MlqHw|KvT7eppUXBP|_(8#qF+bf*jaKAT$97h2H>g-& ziiZkbW%fI|^Kohgzl+wTe2^WM;&k~{yQSU1XJv|Hl?KEYY1&2mzRR1;_Q~H6>04w* z`n;%YPnk@#?NIG(|8#k}vJrQbD1<*5#Qo^K@`2oye2>Av54@pSVwh? zvHbC=u@+%gf?gFJw{)>!D60mAWd1+?l(4^d5e{8G~!i_Sdx*{ha+{)Y0~Z?67C zOfScE>eMo!QgGOs648+DGtBj?nPb*>8l#!>J3+I+Z}qSuOZJpd-ovAmG?#%;@7`55!KU5Ok)S0Me*?TDu43zaMan-48j>qIrZq@CKx+M-dj_yU zDVcR#`29?*`jVhrpQ`86>R9O7_5Oq=&BUU3StqaH&+B~p*5dy2OwSheeM+Y!#F~#d zP|W7<`F>keR;;AOckk;dg)5qhu~3H-7fp>?T*=IZ-AglqymmSYta8mR+sb9<*I7@y zwoJKP$A=TmA>-yr7Ob%BU#2?ZOCa{Yn*GPj7V*U`H129?!8!+_@W+qbKXK$P8H~iq zE7kd@k&!=i$gT-uIT8gmBQiKM8*w7Bk`HhA+Dgi8CW=A-6@6+;O$kFBpO!;!j9lD# zzh^Z4%2vc(fUuw|57N_C7U3Y)lEW2vb$GU8W_G^KEWfW;(i0$`m2`htQBkpd-GVc> zH>CWt%D5)qul$OrG0-b5o&eh2oXlo9p-(k8o^-Yk@GI22j)}_p`t_-~OOEeE8Ksn) zmh|v;p}2ADo?Lwt+uz>WjK*AJ#bH!YkUV_0@OG)_Nxcj~w}AD3wgB{F3#9I}OKSae zlnk@mmjTZ1>Drc9Pvf9GtREK_7ad`*g<@i`P<1ub_dcbSt~@_z6*e6tHz*@|lJH2-OuJe7 z;C$EJ=(Ii8APC1|c0CYdFuBxg1AB}>?7f*3izNmu-dHYf zRwjeL!e7v%aX~811EKzug%o11`p%6CaM~@poX6;+P?M4`S-m)jJYuSfX64m#GDsy; zD7Lt@V@Zz&=XNS}vNDg6)f7ah)-T^ZxqLaMG=i;ip!~Js{r(1SrfHTE>>mMsrj;?% zgD`spPR}(4B}!r#&$MlCVuXV}=<%?c!b0Le><6EJ4hfS-8}}0oLE_3zMQY^6E6PcT zC@BGbYgAI={!sA%rIkx1fa|{ zc-b=YFh_-_gUHjJlLL*zXN-2#I=SWGl$GW(m`N9%lJ&(Gx~30wx(Y%)3wwb;zS89u z261e_UtwhRiz1H03xAOS$sK?Oh>9Z~%QeccA{p z5&rZzeTWk%V5Ts-J`%kD+F)1WfTl`qBm-q@HE11OG>p1`8KWRi<2R$DOJj%;DU4ZX zZ2msjEwyZ-nMxEFm##POLr#)ye2)s-!%?rYno5B+Vcdp1tgie?zP$Q86zhy7W4!%N z>{qL7{!9kMcEPnvMFcgE$l%q&j`1I5*Qm9qOXZ?aZdp zh>+7^dJgGeN<%#HIsMj!ptF;AWARG=9(=9P%SBKT>|WMmD%n~Ai{y^5?f9?>Zni`_wmg?`QWyEH z4W+pOy@b9q=O25wOW|*Ak9j_R1}kZZu;^-|vEJFS7mL!@o~H$NX1hY*CkWw^PF_6t zbwU&Ek;QVjUi6I$jjz$ZPp7}s+fYz2?H+5@?W(&u)}{s13xFK}UA>6Wt>O8Qjg9T% zV6*oE!lkjCx5f6=w!sfrTo_R%sQ@bwuvRPp7Z=rkX(( zg~WPvvpOI|I53@+))|ss<@4o`|Go4%t)maH??nLu2}zX(>H*Ji_y=(ZT)7gw@*$L* z1aQL{;&k%ps7-_S=-~^vhNoMWn%Q@Oe4u{Xr(2-a1Kuhy{mEpPskTPP6JtnTP2%al z+g?kI=F@2*#0g`4(W}61h-dvMsH*wBf(Rv%^aq zl940`g|A3f&OWX-?zN~PBMUo5?kwKKpKm%8kKc3rFzPaXxk#w-WuE$0wq;4Lzk*CP zeBu!x_o2wZ#|y+Y?g0Of!_vq6WRDFHQSrhvVcfJnXQG*|ebe z;(~E(5mFB`y4$#{4nO4>XH08cU$CA2MU2~STf^5zH~IA~44+54{s>-VieDGlD26$-v@?_R?JJYczAt{iKG)}4s99;WN? z*S6#Trv9!=c$bpY+8~4Ra**phoJi+OIImhAaMq_ak}7;YEMRx#=OK?MTy-FYaadzda^u2OYv5D848Z>umK<rW$;KQh3*l=Y&NPEGEely(+Ehq7A_e0G- zQf^X{{npe_Ey3$5es-GomH0ZV$y9sE%NK&1g~ zXo*61x4Dn>rAnP>3s8MkaE01|!E|1$(ag=B!_v0;Pb}nf<7~8hPsF3UBsf|0qOHcc zYYq4c6kUB}jq#5~7mfYbK00`*pTnWkWAWBC6dwBi9+~3odC^DDOrVx2^&&v)SQRY z@?KoFH8-}FvEFP2Nk}HDj+3!J9gE`!pDt*wbvB=I*P2o`n*RL2H&d%rdMXK8tYeYu zaCPZ^37?Z^+lg}3T>y#2U1nY@RytgBNJHjzp|tb41mBZ4tI%Zi=ws^d+PQT`R}7y< z${yah0O+-({K8Aa2ex;=W1;<#V2;lgZ(c8uy>VserK=H)2QR&StLte`K+D4NgCk(8 z=ZIK1#X{llXur?mDWR_7^XdlZGGgWZ-a}{d)2IydwTG|}^r_$j>1a*-b zPGhnXevJg(;OckfZuTUkXBEDgj)+OXsca87pY5?Xfm_{1T1j~FhRs|Q5PE}th7Na3 zcSMPvfOr)gyp}5BlAJXECe61!3Z5$l)V%9dzC^$Nv+ekM-#!is09zSOzC6?xh%j`J z@iCgZ(x7m7{P4cxzd`Qn4WF|5;da3>+LHq$nKzu%ET7dc4!nO=x*{{~)KNgz;*mMp z2=5DB2(@J(In*XJ-1pa;Mz4ack{_XhSl9*tTSn{uS*x9xt42dszkk2o%{TSQ`i@PS z>94DCT9OpNyO1l>-ku);f}0W(e-%n|J-ZdhkwevQEyYW8I&%tqK8%J*f1qDV^ewrl zw!-$u*p~xi-SxL!--<0e_Tqv;tn{no;=x0m*n9gX(T39@f8t2-?}~3)&5+yVWkC1~ zXy|xB0D7n;pQHH=vg7^mg*UE_SPKSVvc-0r@sRQEJlla=v{f#E_K!w(k@v);n9hDQ zce2{$oofN*o(>^H)UTB4dlRUH`H zHvKd)$MRKLYO?y3Et6sar2Am zfQ@uT-uHT^v-swTdCmbL+PyIqG-=_Nt2*I}m=EOp$|~ zI*O2^{6s~YV)_Qp?x^EYmYV`92T7q|8nTTX4rG$7*zVnZXJ^k}A&B7m*92WxEj{so zNRFWEYKpB0kB=)`nl@#ETncgu8||mm_!HFr+`*ssCo+QUWtSffalFzxkB_UmYmNGg zL70a|t_=x|+>}F2KxDG*M4;bzmn(gk6{$kea`Inx%kKDqe3a4=zAXCURbEG(MVbH0 z@-rmO=@C}ZHi^EQ<@&ROi>-nqh56aTFe>l}CUc_rdL`;X6+gW14tzi7d)^dGQVX=NH)>E6$P3 zk4f``eW9W?k(cHuvi{F@-$y5##5)2>jt&mik49()9=}|2ZL%iZ=Yu`g+N`f+F5~Ps zQRW@tii~`%v4nWj;shCd`g5Lj)E~aRJ2rfkxc#fqJO>H`-f9<7GE-Lebe-nUQ2e!n zu7Eqq_Ra$VV=*x?C_4sVsioXik~};bDP2spPCve2krCXoUc9|tcFr@NMi^qeVsp^y z+qVn()v_27g^Uh~L)96@2>dnn$~=@JZq(a+bdQJqx}%n#$ylmCD{Xh|{W~$_CvUX+ z?LkI&Inq;R#b;5~L(#$h;`p3m_t^Of2++GFnix}KyU$g|C&@s*`dqN_zYue-7H z1kp&c+i1|UO@VRPs;$^|8KhVV)9P0ncLdp%T8bF(Q?Iz=1|>2Z{p}s)HhC^scPsyw zm-oZ3n;C&B=i5wOI3SVx--0@XLGusB^2DRm*7Y$4Cm%|&XZRMl(8w}m6?xBUh zV%{y{&MdR;^a&u{9c7e16<6tYv|2_g=$=XmmN$P0^6n6N#wjf7ICfn{xi8S!v9nIm zJ^8RB;JS3m-eqKX;4@)%QAe+6c?r*f3Q*Qxb-`%aiEk1n8q2=g8g~??WQ{h*&Eks= zP%Fon+TbHnL@)j^?*O;}Y@-Ym|8nZg;9cHKp!D1yyRmKs-C5E2%$F>?rwU&SuBx))FINSt69*kqz`qZM0p*C%Y{E{*CKB zD$nE{?H8>U4qV)#^!t>5E*J-gA3YB7j<~dSOGhg%R36^}xx%`1Uh1{MAK0xqc-5v| zW0}swvhdKuI2B_6hiU1PgVn@z8@hfRe?;ttn09 zMLd~`@yXe`O_2!_%W@4spD<2uF;FQ7r^y-2vClirlNH7EC@1c(^5+h;%Qp0HtS<8j zu&&igr&jYzC!5_wCC;UrD`tQH_aJY`IYD&`6R%asdI9EeDW&?K5If=KEzN&j9eH54 z=k9pjIJuC;*P(qN8~ojH>0wHEA}spDvb+^5EsgwES@I0Zdj%EBO_{5xfgbV$c{2N{@!0MV2aAmyuOhY1V1763o-+F;CQ zcn4Fp@b6O?nh9)iUVZALo*P~fwi6ZVuK`~aah1rkfREd%XwKZek~;Gew(xIR`O|{V z@hsZxMTlHb){?a-qLqs#!Jm+nH}fn$Md9;cjGB~S2YU^#qj!Gqxag)`)|1~iQMiZz zmjV6x=>;o)S;_N`Q?BHW`ge8xDxT$|Sew!C0C#+%oncT|xKXe@7vZ^1=MF(VK|=!>ggr$a^;el>r&wUsv-DTV`J}u}rHyhkeJk^~gN|BN%LzV({YWR7OQRJW$wl32ql=-+nZty^CGdk3PSG79Wj_E;qOQif%D`Q-6H(_gNnK zGW*bgg5fm(?vc`Y!;E2tBG>LF9+v%MuX(Pb4L(lPHfIfo677jqfVstlf*29`LE)RN zG{7$WEABLH>oxKNqBXpe*R6Nd(fX@-5qLnhh{yTV-e@Awu3b9ruPKoY^V8BoH}F)$ zZK-7sy8Qqu8xkRWLwW5#$7+T8Qp5F|;ipohC)+kM@U}SF@ZQQh=v9tpvvkmveQQnOn!=_0@HWqER4hEBSEoN+8dN)0^a-=7KutxQ)XZvC4xvzwE_!jFyk zj{=?h8ghlt2k@chbDQGv(1nxNOC^aZ3bM=J*~rEq1GQ)_-|?H~(Kljq%51L!f`J94 za|dz32c)Yw2IDY{4RI2~4tAn{+LUA;8TRl|o+t%+vY(cG8nOvK*5oTB>1 zJW`ECHsIf#+kp%JF01>5RH!*E#if6d3C-39?%A~N0Z5wu1AMVJMidpa_f3`Zp4F$a;xS_-r^EuUDM=`{wQ5{VxTO7;O0X6l%BC67D)H$yn6rj zoEJX1TFb!svhwnb&*CxTDdV(DQ1-~Sx$#JPUtVPT;@T?TzWR=i%|XT_N)PEgb5R~? zVr^o6pZrtYn)BiP(@8V?<2x<`6R$TW`&0_?4XzkQCxtzW&{ot>B^KZm|M3aMjDB;p}gRF6_f_&ya-=a~AJXYr6`Bz$cVs;Z# ztR5MLM)H$nu^xNtR!hl2XDV`HAT(ImnpqX!H;ouswv!m1kSxcO*OS}#@$*jh0b||sqB4QrG1(_l2mJc? z0rMk8rNfUmtx1;_kGVAXVCK=~y*c6m-8^ie*YvruXdczB#1C<&SEH}Sy|bUpsk*ML zVFFz{zFl5VR_?mF&%-aATl#^cOO~$br0s|2ad|^hJR`;6=swmsxa>9IGMzNURhHHC z-JbnQLU(`gnHS;ZJG-SFFK*fXD<;zAr=QK5;y#k>00_x`Eo<}P6TsM^kxvj3skMYG ze71nu!T6j zp$Zz(XCA+nu-$y{riXz!=&hdP1TgA)T4L0(;5#%)7U8{*GPUB3k!*-E@s;^6PWDeK zik{8R{fifxx)E7! z!>3x9t5p0&Tr`WR;+>=)eviFMo;i?*6&yn*Aw z*kb&e%-%P&j&TeVu&FknkHA+qd5?+}6BrE}xIEitu#{pN5)Jyn4gpBN-#eqqJOkg1 zX)J7SWRS*71Bf={XSp_}=zu!^?T&;4;~4vt>;ASWGA2YXbh;9zxu&LOSqpIL%}ATu z9yf!^yx=5brv#z>EYt+(j+QEy&RjMqeOz}|c9!9X{6h3CxuhaE3dMp#r?kmT1VRgk z|88KD94R!WGG8$AoP-B|Eraa8PpGy@t_DDSrAOl*q2QyMK{3oRyV1_BK3|sk0q%lR z27@pV#QrvO4~KBgV4> z9{8j0XmD>X<~AW#3BuKD+2#960LmoJ2$o!C1Lyg|BZoBw^P-|6RXo9=mfHOJ;Ee>F z0`Nj3IG3Wfmx7aoKa*vwb}jcN;+j`AnDmC$5&`RPRzl9hC`IEN7DXCFaPDBxMKJSl z*HcT~NxT8=Z?e`y7m)&`_xshIwvTtZ+9V^?LCQ{Q5qn#fdA-J<$lC}Q4DnN&ge_yv z^?*cBY@UQ@`GT$}tGZ>^!{A=-pN-2`2SV}ZZKrHYr0W%SNd)ZiRX|SO%<^F3P;wHT zV0nQ7VzMdtELSu;T#$FL{`;Gb`oPVqG3Mg)-JD_pgZ_+U^W^%P)WPaY7?wSKHOGOM zTwB>yEL1|3WO46(RNF96kDAaRlk7)2SheutAM>s6!%5bdv%qH=?f~(Gdg-ptoN``P z+bK-pT`<4sV(KtQ!FZMdZeKGcsvkcRE5|oGHWE8J_C9P)f#{Fo?~ z3^6?FHkhD<^yMY3$zv|Fo;;KSD#Q|1AhDmpj?eZ5rd}pjODq7vMPgmYTmnm=*s7z_ z@_GYX+_QV#B3Px#{MT>@aQ zi1Vl>KxadmU1kYaGZcuKDJCxZln;K&%G_%}2b!$4k}JUA$%h5|Q5C)PWjd0`X{oh=oBwFPea}O~vn57P%36tmwYY%HIynB2Cs0Gr55XfP` zqOlSUv1Mrlz?swnlLtFFNRgE}vF7+9S0Dn~UYThf+-YCAdXH*O9a(CrVe0s6>(K@{ zfJ2<4_@F1>?}P(A*oh)1C;(&HkKTY%$efM%RhjZQ92^tvPcfZdm67^Ne`NJ=QEE99 zV@r(Pdli7r!2ber>4jDa1E7VlpLJ?bSja2VxMl!aVvnWzE{HIjhl;YlrIJRKVyJw| zmmx)dhxy`G=B0{8I_uP6sb@*^a;4?3mCg9qgUty~SLYV7>yg5HlS4@=&mg(Q`jp15 znXaoF3|I1ek2?|w#Rx!gCJ-?CvXojx&sePgGYki%OTvA24}0Ru-#eMV?^i|0A6eoH zPz>Mi|H|Dk{-h@&OoD<)p;?PikP{*p5TDsiXjEXZIi#g9Zy=GZVx3VcD6#X`0)@H; zUULu;hC==ni}$rezxO^JK`{r<%vtWvAxEib7=pu7$I2{samuC_sbCyP)Im!u{?s|T zgV%9g9&ruj?V9+$yZeN1*u%d#k7L0qcJmmFBPU3^Dwk(>y!Gqth;vilN_FG!9o6al zL+D8uwxK(waGMZF`5bI1pjTj?faLn-?u2^(&B-^GfES+t3$_BTL`G5aJ`=53TdUZr zVgh%%^7ur}WkPqt@N5nf4BAbJiUvW%7k6_4Y*Y5LJL=S!NNS*j&g7y!IAuZI^HhtS zM=I51!-Q*jyTc`cIrR6VRBlbKSFTnb<(}NpQn}cy{CHZ*YVem0(|>_Kx^SEE@i-+y7O7bxO$7)h-v( z4rL10NJh~-n$0z~7FNO2WVk|8lu+g=c8!T6Dj6bDLT-=1`x~0OM_%rTyW7H)*31Ev zm2fo8daEj^IG+zyqEj2DWR|=2I>p?im?#6euig75B{12!{b8vz6A8q@`?2fWObPg3 z1E?)R)~jqz?y{5*HFNV745E^T#>4Ys{rfqo1nZ)4Q&v6PzE%p8FX|XdI<;0lQUa6| zN5$;?b&$tI2}pIs4~O@fmK5N>6$QjFOKJ$}Ai_M}-|lezfe?2mRJfB8II)P=7CRm9 zX$$E1YfJoHzLdSicYe~q(vu2 z0jROuKvFYP=aI%6CiCAHewi*Ok^DW|RpiD3A>$xXR;UF%qV1X%U$UF3gZ$oD9&$y7 zB67EsiMce(l%eVCB0kcoMCd#zKZ=#fqJbiAf1LdvI6E7j7ah_w61#03Fh8G99_iFx zMZyUW1L{tHa&qNF4ZAC!B-A%sd(;HFQfWI!L*}$Gr8TTbdA6~VnUPw-xaox-y}a*b z5SAMRYa2%hV6v=4A^`LIArvUs7}PPQBalzlC!5E%KkK5oa_YEh^*s!j!pQ+I)Up$A zHb%h$rY=1fxIuOvrm>Pm@ogw=;a7X`psB34sw?V>&$2scsr2OlEcsBDif0 z-G#{&l)fgmaA>9IL#92{)>q$~_csI9^}he{g2O!H@JZ#=KJR`@g(JKBxQkg4CRLi?=4rM%}Fl6~Gr-QPRf zn!Lv3Ou-pRw;gD7AtUJ5C-^vp zsdiA$QqQ+AL`ep|0qP0XNWDg#7tt^wn6KK>vC(Li4xIcP>UNWclygkP(O5@O=cFldZUN~3-?%G#T$(j}5iiJbZbohS>I%a7Ge{ySZ) zzXKfSio%;RWw0DLcKSw{eR*UkX|(fICbvm()B|^MLTY$SuR9ZNP>mTuxbqQ2?}FC| z2z7DyOTF2<*a9$@@OvXNQthNciAlEy(neI~A$pfYnL_M$a^#QkVshEhAy4I*Q4~I} zP-2jgE8?EHXPk?46NkZ=EQe9=_v5SBy^`q0eR6^^;E4sRr*4>S^Zfj$7at=#vZw;} z6qMq^+mFNINkIR9f_Iu+tX;?dqRyd%se(Eo z_`>_m&f()p#Zs_xLVJu0ul2}qwZ56Ii;!mn<9d9amiw&ddQkJuw{k2X6VPotJ``fu z|9Sj31%kd5y^jE`QHUNk{>^&#UW0ehGd2;&%R(3X&RE5U&$^6|^5$lbZPNmE>9aR? z2mdxHrurVJpnbEGTPV}BbqNh;gLZvl-cB|jbiaLQ(8C7ek8Vg7lz#r$6)++6F68^EEIovYjE>Fca9=ve z7zXR(*m=@oe6&XI9_r^J4J1k8Z!GmL3HK2{`p#p%cZzrnG zgcL#=C=tkj>`Zf?4CSp$;#=#?IY}>I61L60CnaJk4K*cRb7Q}&cdB^~qoc^Fd|V_g zYaO2x6%V4Z$#{Ep0?Zh|W%dA7dP2Lg0bUQeH-jlEqjNTR*#|u(7F$eCjyIoA>(LCo zP@!Qj)Gv8~t~=asc8<R~kh zjsB-$_WyTdKT2>hc=E7vF$(tasN(3qoxQb@R-|;APVMh6rTxX8?_0RehQ3d+WnHg- zSl?BXJQ^b#yIg%$>i_bxkh+CUlf-98*hc?<`vyz|PpZ{ZY-*j0*LnSXtBFN!lnI48 z`s?>S#HBo{;z6O7+fMbQiJ}p}7W(m{UQh6l*YoC7j8grP!_4G%=l{h|%GN_~cqv|O zwO&5i=4EA(MBQ(th@hBF*{{KSCx|%7;U@~<>}bhYxNWzK!cQ67UB1N9W)$H64}d!_ zXQWwIc4k8;#K9$KeyTAS){o!!q(p9v8&dvDv4>5FWJuap(>2^S$aM3*D?nA{1KWK#P zIpMWt_x+w2ZS4~9MWg+nan4;r z*~Et7WYWkZN`B+`cD;a(`1@`dc8nL4^kU!88W|y{ZCeiIq%;lh{?{G2R^oq58m?7G z)PXL8`pz7^a@#H)Wm!ygb&@`6)*qf_9XPlDl{|k|^%DI7qjLD5c}Jh2{`dUc1gFL2 zV1jDsr|%vhnE_ATk|owt>19TYyvw*;)yAt**Y%n4-(&TT|I{X+#*xWOJ*`0$kJk8E zKV_33ye5H4j=N9Yo=a9eC~PPD|C!}phnCi!bL2W`+F936J72*ILplfuuQG#}vH#Ft z{R8>?S5TQsrsDT_zAt0jp68Fc*UayJ@G;)-9cN3cqs|5`p6;66bToSO)=%_SLcjx^ zvs>a>be_y7)x{eN_QuHB>bX zc%0{FyE*d_eNnkIm4oy>6LwYSVz3o`On?uy;(ERbnrClaqw%|LVlJW(N&d87v-i8z z=dWf{3T$vAy;z?qm zm3+&uBNgMhLk1yB{Cs_mSN%b)V%NM}_4prnZRg8Awv`U@aKh%jrmqwr*hd&ZStAVJ?7uk@b(_9SXh|R)2d?b0 zk{6t>dPCIiK_`S-_V@p&|EgeF!qra}MxpgqEc``Qb zy$OCoH-yaDN=LR-?Ao3sY<2By-EO@z#~+GxALmi^JKl@Y1B4=xTjakQghSH$-@~(u zhu9XP+p&;)i3}y~0XU?O)+vp3btZZrmowl3q5O)t=oXuTB>GQDy~*en@cCuZY*&lx zxJKo)>EFFrT|nrGr>8nMw(RmO*2s&f=yYs9yW73+izFGJZ<`9YvXz|UHVbwOu4pE@ z1q+t#h~MZX42vzBWKR8-Thb zG@9hJ8dzB}Ho!iv=$G+YFc}0k2Y4Vc)NZvf$CBQv&|*B2+SR;)oEzerj+7j6ge1#a z4ZsGO`s z-8(5AzyV$MFK$=9>{!=W0GM(Ca7IaFAWrXi11&@~xBl#2&@sS4wb&lfXHTst`%-$# zY#a1iU@=OcIChmo{lr^*(=w>c@%FUruZ4e_@4i*Ym;hviLIynKw`E^29~pfC{|PJ zel+YX=+7!6e(V1Fzi$QMpg&F=>FZ$$>fRNsOc`fh3=8_^>{=XH_K}>aK5W?>dLk-H zqO~JAg7oP(e(Ma2lFj%d5rm{ICL5=JZhB~}+;u1ZTw{p5x**S!M*of81Ghbd22~(# zjePuVd=&XMI3aPxoc;R3{)}Q8W`U$}(9A&@Pwxj)x>)ChY0ZmE;H4#Dbr7<+w0Bj+ zyK%#Xu?YW-qMuvOv))=qS#Npx0sUtZ<_~z^4)TK|tAb4UU0wy5Yz8OOEG@XC4t@{D zpYB%&*lVTlhe#b>79L)Rc(Ign@u>@gMa-`W#~TvXf^yiwo%9_9EWsYV79mA^kB&NOxOE30rczT~ z>IyhsLzU-F&)ip|9+4-$X#9s4oBzRezKf!Icb;%ua(0&R<#-xq^kD9b41kuSM)0DV zZus3g+Y*-i|JeHOs3w-MZxw~BC|u<#3PMywtVBg=fkZ_`rHQDBl&FaGUP1^-L=;4X zC@4q`ihxK{=`9hFCMEP1Naz6q1PJNvTkd_o_k4eRhm&)5v-8Z(&TO7vnc1b6I1HXK zoU}RMGy{Fnaq<+r_7(wfQ`O-XL(kxfs$=r9^#|cGe*&NzuHy~M-&{@VzkKXRa~I>@ zO`S5P{wrh)&+N{w#J6@915ko2y1~zbSbl>!VwBx4G zZ|sS*5LPbU$AKI4dAF4EV11&*Q6zZZlyr{weN4su*6Wn^Zs}F)%df-F7uc4ATEOq5 zX+7!sDD!tp?g^jhnhDz%cdCv-f!)LEF}jU!IXo%4BQH~+)U^h767 zod&7qZQ{|Nn&-g>;dR>GhYor{TH=+)c1qMp+BiDmlAdu;X-UU2+B*1~%5&zGTd(!N zV7vz4DGamUWh&0d;Se+qo7)?;&_0vLBj;OHyqNE2riTeRmP+EIYxYo*Q zNwZ#n0i`;Jd%%MBH28Ymr8-+!Jz#pwdNH()G#e;3l9rQ?4<2UBe`k?(U7B=9ggY$+ zQvKkh_vN~{r6X~fA^#d6!}?(U9bg5hWM7Mt!TA{^Xm8C$`drFpTAd^C=_aw57wnNnsWFSS}oknJ&kQsWHx1=DT-iJ7N6;27Uc@V%6Zk-q}TKz zUo?h&;g68e7fEis3Sq5AGyG69wWOw=r@Pne)zxY5xcj6~9usB4Go;*T2JKNOY?4?j zJ*d_zJ63`B3q@~x+_8l^+vUsgZZ}!z+hlu{IA+koJVc6bH>*R&(JazI$aLH=!rvaG z<|vT$8{>29I%Gv=cQ+i#|@Hn zZ9~T)e?I4;T#P3TD&hQ{KtGxHG7mEwgx^Qo^2SjPZ=gl!w^f%n2~vL~{*;);!def~ z9Vag8Esql-u%u{Vd)P2fr z?mkQ_W%BW#eJk^e(+l;W)mJbvT3lH-V+~>QWp#d0ckqe(ssyLzz!dtqfyJp8bye-G z)PeUkgy<^DGl|t>$>}S{>-EE_eYq_}i^Hp%zrc!5wRjH+!V=hNs`&$GW~|3(HmE~D z(UPHowI&9xPS8_E+>eu7y{48J5>On?9vE}?bkC+2Dxf33(zCM<+)78p%YmqJudPDc zSgx&Uk516+Z!uV|W@6oa?R6?+KNBR4)AFp#X$UFrxq+O@eE)pTz05Wc&^hWfX`Z7) zctvCjt6c!UQMys+y>#_b*yJ z7oLH)fl#lI>e&3wFT#V+84_Z2*TSiQ6S0d6>aC#HXpM$8*gooSv{7=zp7-yG-yO<# zO`{kpDF>^)T}e#8;ybZpJ*nlZdhZIUTGd`{I1p+nyU4VuW`52aTE6ryK_=lO1o5eX z-G&OaM2&`Hx#PQLv08czS%!e$(IDHD&U*GO@LOceJK9Bgc>Hfw?Oz&W<<~1MFmyWX&26;dZJY5i<+tzEq2zF%wMnCjGOC>;iO;qAGsG0WmxQ zs>5=Vgze@%Ad=WVaC9i8%z(=Jtxk%@(M}v;UXQaBOI9qH8Ns$kAxGC{t z;?fmBjT)(`t1G8W8k)L0$LXJaCW|eSG5ziBcOq6Y7o{DRa#8HOR{9NwaMa)005#`N zrt+d_0dhB1RSm5LHLK1R;D*3x@dI=SJ+7F(N5sk_i53`)kixMX7_uX5ukH-v!0nvfE^}Pfwm^B#A8hjOo3Ql{@$#Deuz?{&&-DC*TF%h_ET+VM`QzBGJHa^cc?P#*urTt?B%^3UZY%ti+Y`aPCHyXOv#)9@g9iV6EL=iWmHXlFlna@pmp`zkh|QF z^B+;Ak_e_i;9YT$tGu~=hvJ2!3ZIX^1sB~ub^Mpd3i{LPfW}yb@Cs_J&u^ji4RMd2 z#o$@M>ko(S@;)NEXnyd<{4eK&-0J%DEW5{2Z#QLV>V*EB2XNGuNYol z%BjFkpdyA(G@hd1lhuvC;kE_I5x!F(zX%!^`O?+6ubc%`fcj3SLaEw*`GK&R234#f zO<$1hhyXQ++g#XDRk$C#N8-4^AdnMNY0YRDdgJn-Jn_BY>+8a^0{gdqpevY%(z`ZM zp>B2)3!7uxaNZv7|EwyGWOm-l_q{j$I47aUEzt{g(l7OYdaoFvsw%Z3>JNxxxx#~N ziacmw2yZ|NvKT3Rb}hCS^WJT#*Za|Zu-?T zHgkPI4b1r#bnjdB_ku4azU5za{Xh+91&(EYn&1Qm^qg^GrPhj)cnI@jyK%lDzc$Ny z+cM?WZ_$@zL7RWg=d9Wt>`)}ZnOcA2(a`~4{w-Q}Y`4{9Q7ueU zij(fB^z7J$#SL#eC#fCaTrWVC!9{!BBGlTd-Lt;^TkH~I`!$C_iDI(1`h zYO?uf2;;rf3*mThZEgAQ&@)&+)xY{(A@bp>25hSB1XLG_ofC{HJQS~^i?IRohWOsc zp?-@cjgjEW@@p+P09&y8B)0lbMDLipd_%ome^H^H>7H4`N!NtA-@iF9k5ccaiYd2- zbgVxhp2V&hrZJ}s@lTNDw4PNc?siY{1q!dyh{Du6z+gX>xFaRbUu&5DSC`W*EJpd+ zSxf|a^(IG$V2?xX3MSD=<@shPlJhtbt-ff8Z-?_;?R!yB$$62QiRNA_TaE=Ib@_!m zhQ-d2pk`NT+7d3W0nVM^w4<)z$pmJD6g3z*hfqBpveG9(b2|qVgU;WUERn%&63j)T zz;Oo!BOmX}pWc(+vYT2s3C}QXBI?+XhramQL62ur7RUQNnNu*puGfr75 z8lS<+@BSk!9nhlg@zFeSYujWa)`L)f+e!c6Os#ro-?t2xwF?y6SM-5_@TDn~i3Zg+ z4uSiHNWA4pu~*m?1m1uYqD+?0eB&gT+AovppVv{{CmF@No1eWnpT{GNAu{o0U}xid*SF$ocR*Tk%Grq6{U{D+_+_U3YTOtna29-r z!D%0rZ@j*LF&XPYFJ^TY{wL!4#7EbKS0!=a>i)$&qSN0piSJbAO+updISpOhgXKz9m2%U7*EXQAXL#;=`IH0>%Mzex|+AcSmR663B8j z5a^QVgDHy99ab;ELrshzwDsnw&}7&3npXQij|IS<@0u~xa(ndwELWt}VI;psR*NJSvVf|ZNuz%w17E;xJ?Z7XKH_gG zg@!91MYBKCF|MFH*n);0@$~gDL(y4Ghj%$pj>J3$)DA4m%P|xI+K)3di3X*Y^HRT- zp9%I(#(aPw_E$5*h*Dv+JpacH@)(%Mrn2)CR9y=y94+U{d{HOrBt&oe!}rhHOX7-7 z^T*LuQ#bHL%NStVUq|$^zG|0o1-Oc+fl82$zLGFbQWs^(W3P34+s8 zH5^w^eq*SJ9Y~PQy5?!IWDBk&unV_!IJf7aX{8IfW5_s+u0^dlHj^rkYGLP)r8MS| zaR8bOdd>7LQ>gNJn5|V|Ld>8_K>F2il+zL;5`FEe7FKe(9_^?cVhD10x4#NR)KLr+ zE`I>-x6>VTY+UDPY4d=R#Q^s~6JY@?6g;%csr1rV{8zDL)j+3Mdx}s2k#e(w?U{?5 zeJ9NdOX=*C|IsG|6GO2%27fVU@xxJOO)JoEwe)AgGesA_X%I<1riab8)U9)OcySu3 zE$^(}kyn;9dqUH02{e?CvTM2=F1>Fvvk8a_XsWr2#g8w`{+wrMEkidEiDMXiSjeE0 z09t?fsR90raHkR;mNDhjsxTHVEN#A;JCJUX@;SAYFgL}yBx<46zAJ&g7C^&CbzTUL z3Q`jVCn(M{Xg>#;YEIhD9sTJxx6Tw|rz3->!j`R`yx}*7_=Ow7;t1+vDYgazRjgId zXFHj=b-X5&hX`3GJ+{3@;<%3kGi8Zo4yQ|VEgKCRp@i^uq1{mfsL(;1cL6lw zedDm$M>Q7j;OFBzhlm`Q^xoFGw+?pp%H4lP({NHAd<}q>X&lF5yyplw4LK%5ZeN~3 zk+^y@zD(w&i|I1P?fL57f2xUjVZOJj+eHOU=~W5SxO1kGhFrD*X!LJF;J{FzrL(PI zxRA|uS?KCKU=a)@apo!*pS&qe6X+95#0u&J`sBn#-}EH4we8n~mr?t%XWghH*J4Xa zDkm4oE~FxrbadRxE{XgAnm=CEMP6D#>=zXd`OKLB1S_fcYYIa;s@#<*C_@*jhM9x1P-QrAky`9Y z&>Ry4Q#~z&oRi=32B^g@>5`-phSS2RD9svNc~a}2*F8!*wd4pmXd@vp#dW7JdH1@_ zI9+DAW-L(t&vR^Lqtw?u$4}&uZ4c3a*@5=eh3oIX3Btx}od9M0H-AL*QZ}Cz}g^LIcCX9`K@`9Ds;MD6(WN zrG%v=zmL7g0#!HwM8`@Eqz4aM!QDmO>5CbvlT9^E-Xz%?i%!7>J}->*Ly{z~gJ({E z_BFb`T%DHnKQaR)K=+LvS}sXzf_ROa|T|^Z+;Q186!cjc5<6Tus4N zQP%`v0`GI;;-SS5PP^Xt?0-5bR%Yh>&sLPs5KO#mTWMW$8*_(eatuoNO`}UrqUpTe zsFRb1Vnz#fqa9j$kwI0O!mUNtG2S0O#rmys zLO&G#ro5dqlR%ugIK{XV^>k%?6a_Ksp12W2@lQ%8=G}$YxDCyh0xOD&F@3`<^8KQv z!QSD~u1Zf)M1QK-t@+BwD`rMsdtAr&&P>2KoJqr1IqDC(H=I{fSh%L^rDv|^wRJLB zj%&Ee*&s2AAsfaZgGSQT0(A!E1RqtvOB}@~w&;2 zen38S6nRJKZHcen$PsL+Sa6|5y*w+19IdQ*ufJlCV~``QY>90@N{8s!4G~tr{3Z-x zfJ%iK$DO$|p|N9ir(IpkobGD4ui&f8P{+4Xi5JsJkXa7>Iy!1!Kn`lQaPo-Bug|#$ zIhq$>fmLJfUTpYVTEw}jQliWW=3N)3c;d1iR-YmTDOe$cv(H{&AYGlGX|Vo5jOe;F|y?UD)p>R;`CWA zQifgGAhK<>j{Je~6ESz%zlPaly-0Tn%WA-L59<2YFDt^)s%TY)%X;ffv^n=T&FB z-kizaA^W2wg>b20>>=4xpqAgZ=a!(fz?R&PKm>UWeaAS```Lwe73XNy zg&tpB&vBDWPqQ;8viizKlLVzdyLG{49MZSyg@=CNF&Y~cw^ zu;6Gx*D~H<|LbVYZ;NH3ol1k32AA@N5v>VMqF8oV7UPoR-Qp)tpSV7>8*|m!;89M~ zc5nfek;}9^5Fb6u7jd0G_xuVfU-F>O29yr*vck1Y{0+b+)kETQYOT)~%NDnjJsFCJ zfyR_QtghDA4lF64b`F)HMCa4&ZHMh1Y$hr_Iltr4-D0l|Ph2UDPOjxO+KRlbz-`hJ z)N{a95N_Z)xKx)$P%xeprs1+bARGtY6+@!GyBmP83qJGc1ZPqD0lr!$sxI1MAz z=-FbT>`TlUtZ_ROJI?&@8SOx#aYE(lY1czFn`mbjJfX0oiC3H>O)m%?e*dXypP>b` zGe3YJB*(@fhKg-+{?>E>$IKinwvZ%}X{RH>IIOA>tOh*KqKO@Tzcs*JPaQpqfF%B5 zZZP4AIbBFq<`aNi`88I_*?=5J>Rj6yq+%X`!N+?$KRRpf;cmsk=CdW?t*~t5s|9n$Z*~=Tr{9FmEt^7bU~`|HLY~| zBqNy=U(WJCJb(=8SiDhrH= zV4E{vXncT_C8q9N=Y1260x{`xhZ>OQivPh=s)(g0NorH1Ymqh`60sx+xUrH{Lw`?p z{T;>g@omo9R;Ro#m)I0lx&dFWjopY43Zq$@1sm2aq+(_g(y;&jKj$T)d;TT4eJJ@w^<8_$@cibRd>@kEDeFE-{-Im^E-MvB^0`anFybc=sCM0gFg+B9=H zDmMBM=VH;FZ=n)cfxNY`#L#>xr2_ZJZUpWt`OPsx+INWlS;^R2ZoRdErKYJpvs?b{ zt#9(C;$K6?wbO-TAzj^+o@dM~0TjBhl3RNw80OdJR0o|O3*#h@6Svp^E@hK(=d)LtiNpj*xbW?hsUqP^b{*?Q)N@A2NV$CDy(L2z zCWjLQ@|YXbQoN^HhNPqP%Y`CwcHQ*4%HcmDBh$3yb#y)h7dlQyV79*PNN}|L+i&6$ zq!RSQV4%$Zq@-Yz?KO?SU-QJKV;rjvv(V}d#`>K;XJ`+5!&&*}ezTadQx=6j<8kRb zy{N_7Sok+4u4E8O`_{`!jA4iZ-jqw>TTWpksXEKT+T#}wdxkEFLK+7I)ABjB8o@)n z*8X6}T|;vjAmFVG@OWCsV0?T0<~P#w`?gJkS8M8g|M-$2I(y6a zlfp_X8$+Pe#~K)l<63v?e9K6y`^%3(yjxzAQ>LL096lXBW2&0B+azk>kAp1kZ`y9d2^iPL<181ba!Cm|P?=%)?XPf)Y>%a@idnR7oelaG$ zPpo!m#C>>ATxT3Bqe;%@qss<5; zi(IKWKMo!aNn05a+~o7l@>0goCnmltUJxwT@xA(0J_6HpQ!Z~A3>yEjB^JiX)hh^8 zcB8H}^#3kuqo#j{37TtFSPd|Wz*^6N}h3a9ky^HL47(e2P)~& z5!tO723oul$u}H%d$0OJuc2~-;sWqupr7S0;oKi=Lr?ojdVeTwnnolp`gL)7(FLrq zDsK!c9gN8pT9dACC#At2J{g4|-ksvrmG=)s*^jo3f*gECn_zbk%wZ7PGBor%a#hyk zYlkaJ3>S-S19utOkH52`YuoXA<0=`scAdd>A9AS7nXPaG`VwRhma|rDiM8n3vLmt( zZPC(=wd0G8B$~iwYSib5VQj*dpI^VuOWL;oVr_-)`aSS>oD1~Ag{#st9osKkGq+~n zPJVs*uV)w3Hb0p=RqU0_<^JhxQ=Bt)#qh4R`0TACG-B^QNm~1Mw4k|Dc)Ulo5nE6@ zz(_^2M`_TRjzI%<1O&B+?!S*>l(HSu0@*`3D<@=xS(6FIg)aM-xR@nFMp}wP0RL;# z9s{AZI?if9QZQbaSZ+oR8I{ScJtr;kY5bgx#her^*o0L&~g@pmyqHCFpjmR|R7Idvj2z-i#)sdhpVn+iOcPg(>%@BQrt zXD9Sd_@`hfu}p`Bw>qf)V*wao-4`|Dd*uhFqxiga$9`Vng+A%W!NpR%bdM|rL&=X#=_+ITZk{cf?orHQmWj8qXy538@^0`>bU+E6YhVgUu=W* z{)gKsGxapIU#3S2wC3+umFArRyMgi)Jq5~(wp2-;f`>in$382Dc6u9Lp%R~hXFbtj zs@x3F3ggsQNykotOZh3|P9U~&G7u&We}}&;AKFP8%AJ-ce9>8RZv}BPsA5Gkc|wLs z9Bla_QM~^xzE_GR4XO?u3s+^zb3aKA&&hK$WU$LGgc&k+&b&PI_p_vNJ-Zn^HKrYw zQA~c=Dn*T%5Gf1MP-xEQB*Jo&`%^l_Ffh+S@mFx1fp>1tnRy$xwna<9vFGwarL?AM z?xLYR^S#KlPUUWuPp;Xc*LfmSUHszu8smrng*q7)IIE^1R2Y*FW_rc;7EQb)v?Q{-xyA)sACB=CpSnh%v=( z5J86zmPwoGmk@Nep)X?JfY-kE=yV1vm+cz}a=_HDr9mui@=r|H#MVC+?(T+#O`ZOjOS$%uV_ZcrFH}GdpJEiz53g+zeH`t~y8$8UR0;MFW_38grd;US8UFsn?lYD+aE)LcH(J zXnM3=f$z)47ySZe$e3tn9Q9_O4^Y>pzi6IGIcK~l^=JGA;^T9es5$Oye*$qFY0Hzy z6j>06JTk_Oc;m)IhieF-qdnh)A`N;iu&Z(x9*9hjjc-J|PS2wwvc}n7C-bY<2gkLW zAMSAHgnnVlN84~g&o+X8gy_Y~G)~TZ-eV`Ccm3o+*U>;UE&m;|u-t7YKb(+3>-IJw6vUV_b*}P*VW2fM9l?pjhIvX3z6FfDm2i6Sho>sXXQj-U9ysR z>&KnDm9TblB_PRpYtUUjjSl`)2bg`(a;7{4m^B}r&v(0-4 zVFzCu#70JEIGol}(>2Q|B_S^NMQ@a>hF5Q{9r(aeR_w=9yk!bS7qLJOM3!AFy2eS+ zy^ghm^{BSxfrlbiYA6M@y~-Sdm&9`E$!5u_HB9C-Aznt0l zD|2Dt@I;_WSCNL2IqXKLyqbftE8l}%*kA^OOw7f4YDk-zN|xhMIXK(k@$9mq)bCOA z-&{$Si0nv_XkZ6CdCjcQsQx^}YebhlJl|N5q)5({`)Y0{{%zx){6)?zx1QMfM){1k zA*(xFdx_hvxP+bk5w&P6%`vOS%*KBua7-md?XY2D6>>OrxEj(`s}nj$8k5v^`6St#^dG5)C1d}-C|ZqZXe0>7^Y}7qPH0ok!#}0A2&U#Vx05a z)@~tvzl34BWQ@;>&T6~1Gj{YYkU+US^R0Zp05LNB zMs*b^*%t}#{^sC-WSxi2r0>9FkjRK5!5Ow-*JUaDzYU5Oo)D;cS*BxQUW>9-2Jd96 zW)Ax51A86O$#TLAO@a>H41=*SO^tvt#jb`{Db?8w__FNWGygBxtAf6#GC)yKciq@a z^F>HJCL9fO#1@fdLVE`iz~%9CVe=P^B~bhAS-+S9kP6dcYW=rj40@%43MgC zW5>dw#rFJ<%NHfrjsuw*4qh9J=95KAi!#vJ@@?p=k7mJ9K(dD)odktiVwb}lETKRS zzdKu}xOi3aWBmj=&s9Ua#D_zGWG<{zWCe9G30iIb!J0kCm=4Xxje5gxdqK;>H=?7& zj3grlgl-zD8DDK*B>Pfp-wKgv+;o)K`+@b5lA{1!cK>Ydw?f8N&Bzilj8R0{i^nai zB}PPay5{=tE(?}{bzZOH7oa&b9hJ5a%a?}NH+tAt|fva$O=UAw3kAK#4G=_tn^M?BLTt89DN zABa9L%v*CdNc_l|ZMr>bT~GX5V{_b^^Af~=j?{kYF`2d@9X9l!DRczONptPHrnrrD z+k?8V|4M%RzDn><{#L;+2k-Bg$SpfJU|BMM%`lII*D>xU+M~||exV>DbP=!eKM*Xg#W&Wt6doi);`*SD!-dZ%hd=`wgac%2~@joNHX< za6x)a2EdnGtrBsD_2@rPZ$HlpVj^Wz<{EakrX}sgi`F2=c&RJyn)=?|Vcj`DGGMc^ zeEPeU?X~Q(tChfOkTZEghz)jyMy)OIobHubPmQqUrw*CSBUWd{K9tX@*rJ|vm7x7A z5tW(GKRC5q^#0WIWBOd7mL0dI<-s?4zIC=qH@@nKV9t(jGSmDH4gMyc>7Kx`0;KEK zh%U=Cv#KkbQw)yO_r#M@o{w&ZY_od^ufsf}Zo$Zqv2_6k4jow^7&w_?g&?21Hrzr& z`Zn_(n%Rw1h*f{9yzYYP6NkeOR5yUyq>_rO8jG&^js^{>r*tQAi0F*<%%7|#y2Ou+ zGi&;?R2&W=Sk%nc-26T2A)~>I=F_1!*RM7d-w#^*Dq!nP!S_DoM(T>7@5MV03NJ}t z$V)an{O~di96HL@>HHy%esFPzWc!g#M`DQeE}HeBGpjl-ULM9*4r$ zS}zIr(R%cQY>rG_Z6kVnmNNJ@UvU0l&EZosXv@Oo$X^30eAF@SjUAHJO7%JU4>hWl zYdH+ZV37Cc(!VwZQlmbu4}pYO?n=&P zhBPaDYS4qA-&=Kd6hX&-=ookRM|l9tYE;i4Is4?zy^;p)5JMkfNe1jehAClDwtS#= zGF***|A<$*f&Uaq1J?8H;o0%NGs5ub!n?Y!1$9Wg^_;PsV0_9N7wooLY-FoJoDA1F z@xgUpqGMxPdFziB6u~p)27o^b466#WJeX?ZcCB_}3(=pcMA}HUPZGRm{;NKraeC!^ zHg>I4r;i0bx&JPrXDiMO+&RA(Q~LgYWH}z`Kd6!& z1rqm}-+W~d8X;OR(-^>^Io0z`aD7a3)WQ^$ig<9ualP6A8PMMFScHYAZ%H~)W z219n(UZ)rq%HVvdIFs7a%UGFs$KMHZEtGI_vInRJ$L*KRMBTOvff`_jgJWG5E{9Pc%ZJ?mrvWIr><>(~zcP{uZq}OG(*1Wan;7GgV59 ze9nA(7M{RZZQ<}dhKGuuklbxcLbu>?=O~1GSMR%7pKTaST+`E3bLjHDI>GXu5123Z zgLcdJHxR6x6n{=XB7H#@+)U==^zYg)EY#qJb%-@V%NNRT&i({{LZw&T59rtl?A~49g&e*r zW6&A6O@$j)h-^Oir&LY^V6pdKz`o|*P`6SW3jk~vTJLL)$pFxsb379I2LRcb_2Y$Q z!}H*0GF7Rc>%gC)>GHuDaF^0Hsh>?1-vla&t?S_XYoJ86PEk~2&fmg)2B0g}2*A^TAlPQZ z<%=-3cU35W((Vd>00=g&DV(cQoIcV8=&QUJs-XvUO6tAVRn#3IVvSvKK>#3duX2?b zKy8inLb5=it|gsaQ!=5Q#Df5si{sbzm^Zh2)`FWs`CshMmvM9w4y~@Eyx1sFp6~{{ z3phERpOY;E&zEP1ZWL_D;*&~u3OLNGS`-}BM_Jqc+iz$q$~cg+F~ zSp>=))mi5Ypo45#?j;HN-;2;%{~Go$jW`9gD8vy{CIBs8lo!Z`Hr=R=2O>k3W#GN! zD@7fV3J`gQJK}~?Xs2Y+_oeR+17>K2m5X{Wtv>C7J3-*_tXH3>nfp7*pM^?G^RH%O z&f_!!p{dJt0=HbIosi$b^HI2-aMA*@MrMv*hIfMaQvAq-@X64M*n<{>XK&m9wv6`s zsVlJvU(nEzk8AebMk?Iv4CQ26y_rv^tCwgwRCgb-6BO{ba%3S;1$Kt#(`nJo^QnkE zd-yKDm=p4_kIt|voc6Tu1RLfozy=U5Y2R=}ZE(MQ@HRlp@b+CH>tIfvrt?Sy` zEiZstCRTC?CdVg-u!$vS8~VCk-AX*vF^T^$&&@2FCzYSUlR1$U7QfJmHjpw9cYl54 ztj_Ze`N9H%p)aN5B*Y@k_=Rri1=2v8;%#m3aQVjC6u4(`p-u^cn^xT z;p(jJOBbGjT2wvlOX!iyX6*;6`pBp5SsLCFt?C_#K{Y-`{y52VmO-a)lT1UJ37~IF zmw*AeFJ1dJpy}_f0j(Yu>)mJf9C?kTzE>D8sqee%I|AVYOzq+?heVd9{K@n-qVT3E zcCWm4R!T}tAEG;-=zH7#HLYNCVz-uS^3AZusBLY=L-A+jA3FT8S7#rd(O41G722I} zCCk5JG0)was(WnWhFX>RdNOh8mUkjELcQbEE|j0I6LS|pX`NGrT)~_Dr@^BSvsitf ziCpulpO<{i^eFO_2oC?*Ihu26Z);r}LtY@b@^apAy=auIbo2G7`5v>G2 z&8F@MO~=0* zpT=e0S>h(1l`A#0`NR7_{JQUY_ujb&w74Ca7t?LOoE+eJCEH@ZADM=H@3s60o&}fN z(d9z|UoOLv-+v}{A1|%ek1RYlLWy}H*a#4r*5=>{2bArJxV5H99M%9|Dbx*_ViEl( zfKWgdJKVXSw#D@3WR_NtEiYvbuEK17O_nF-xVAFx{xR5g9Pyf`Ky5wqJqYEs)_Z{a za>9t?9~!kp!`$%)7`v(I4!}$4S4$L+jI8oF@As=>-E)bL4AEi7&3jzCHrV#Rc&Rm& zBu{0$|3zwOqV2HTnws7jz}?~Z$orSLtyye0n5DO%SVy>f-4XPvC0%scWVVUD3(Ke~ z;~RZ#t(tk+3*$iO1BpGs7i%spyf#r`f3mr@=`{B>GE!vZnmv zmO$sqxgsNYI`xb=1L#iwsQ=jL$jpHX*XRfyIIEcRWliZu9jPW}s%iYL$_pwp<>XxP z$R?AX>8p-J=~|1MimCPuu!HA5J(|SPv=T+1&JRdBoFf4f<6T=*XWpLC-*a^%w)mI7 zb?@gCyO-r>nsO)8vjU&ir|EBFMV>2cb-H{5Tm821Yct|kQPcJ#v9MelV_3#z-#Bsb z!b8Iw{czxgKwXBPo`vW=Us;zEU$oa=1$alruQjp4#dc!F!9A9ckaG4dNfq3X7&EUN z`A+-=E5&fFYCEMr3*j(>U%tRCIrQvp&G>yjyf~$qW(HFU!p=+uMLZGCMea~ok~jz= zY)Lmm|6h|Ky9z@~VvvJ5U;5YH_BeRle5XyTzCB;=<_;{nyd-)r))N(5g9k1x$#JmKu|N#A4G zzc+18>MN1FoOPW#rGL(@c(O4D0^{5Yai3`j`f4PQRZGT6CRvFArXNbPqvksUmd4>O z6&}9_*T8pGOI9L)r4S=mu+1gjwTu;4yg*!Z8?6yKH{Z^2Si9dNpP=ncSs{`2uimxq zOBD$5AlM2$rd?)xRqb1P2KH;j@}1Y7e`caRJp3_O`^8qfUZYQpCWyp*Nb|$mzq&g z^HULSj_s4IPL&JMgE4o!yl(4c=WmnVlG_{j>UwrkAhR|nJy{z5wyrum`$^(>{U*MC zo)U_Gqcr$_u33*4VeV;!lG^}fvgOsMz>ti{Ev>`%!4AHr^!c)|V^O|L+nE>$cI0zH zf0P0ZmjXu!R}^MqQi6Zg@R+t1dl6z^=6#xlm;Gti?ya;Kn0I%DZ{8fD?L{ki*80b% z?*V??==%ve=}xg@p{iI7i+T%OsV>Gb#4e-B!b{5v zA7ht6Ekjtp?}%>kX6Fw9lmcqsLXAtE*h>1t0ncwU}&N%JURHj(}^{ z{V%;FI?UGTyzeAg0^SrRuQMDxC9gNUZbw<2l{{r!I`%J-1SSivh%DvpX#C;=d>m4) z3^S9<4VBTijqQ}>K4fSy#)t4%6EKm)j)-yr;tR4)#Q_<3Rg8auD2d$a1^~ zT2)@A2iH_j+#ir^3k!{e19tF5G2py2A;Or*cOHEz-R50rk1DUtmXce&gE~C!l1R3srM;wGio*!BnV|{I^;s?1!E+DT6_Zh+w;%`5Y z5CE!|AVu>^uwr||t<{gMLXaXKQrLEiO-x<&fD4`p=5XDP6bX?mLk^UfoyC8nArw-LPn9 zn!+u9^$mgIW6}v+Jony4r~jbW510?2N1`0A00g+-YW7ZRfmg`i9>M$1ZhvYgDM~HErSIpZ*9~jGnuKmH0fG9ga<{JT7WF0nII>p7k1-bU;0%ZZ%mF zp=B8Ek3zB!axY#YiPMA6820!i^9VO;f?W6u&IEsl$*(!846d*r*2VJRLyAVI|5x`%aF+)=YYF8SjbZTxxRyFnlV%|R!AgguGHm0hs*KtU4`pv259Qmw z0hg3^l}be5=W1kSR?}lt;nXzPFMz+CVn88?P z#(NLn=lMOq=l$dNzMp&e4DK0oUDtV@$9W#ddENKN$!?SZ`0r;BV)UV#m0dNxa}|Rf z=zKeVqJ+J6+1H&1KIe9f*K7Ny5IS))Klj9rEG?a_WoLE11?>3~6<@pjbC_p`ZP6g< zd4#R!xC_$Td)0m2LB6{HQt<@#){Dn`98|Er5Dm|$x5JHSzI)-tQ%=qNp(IeW|D47s zYT@T3Loe<};T-O=62Fp6*-j=uj;9yVylSvGg07=fc8H=MUmVF7>XvNVJV!E!-tL0L zae~CoT;;CHt#QJUc|=ANQoW-6qY5Fgfeo7y2uzLmE;>r{`Pt7;cWM+nsbVO+^GYg= z@QMAo__OU&wy7jbrT(n#D;C%y!iPI1Rhyad-LwXTWhlR=lJ?g*Mux(=m<9ggdfY3# z!HlRUa{qeTU|Pg_qPSfg&J=q|#^D{jAjI_+I9lx~u-*E-CF)b7Th;H{i;DW5inZ(w zGuY|pg{0$abqKWI6UnNCD*>c7fJNzJ5+P$_z$GpBI1!1%N&0=awvB8=I6RWQRQ>}M z22AePLKSl15C9>#Qx1S5Igf|N094BKhm*9cl`9470FA;0JipVgD4-4e-kwu_`M~AH zTA>KOt!B>UJ2LmSc$u8b@{&zI%j!PGPD|#=?q<)bG5Y{9#Ed9it)SQ`ghi=}e z(${4l0Dpk10sPCFPAc$`^p7cR zq>io2-e;cg3CJqHl$cZp{yLND*nLY8gRpu|atqBo%oGqwh;mHDo5|IT1B^;pa1zxC zYKW6_iJx3Q&|21Vx>>Nbf#R{Q8JZTQL7G$)cH)KD)RY#TA|M2CMM*tU8_}0skdL?Z z0Hymw9K?hsk&w4JA!k4uZTVf*Y>)(<&EJ+~mUDvi{dASK`hqafu$7`rQN}qP3tmW+ zO5TUx)T^*<*&N%GC$jch6++#-!;s63fAv_m z0GJeF(JAyQ(B^sIas*|NMs*rXo1gru=}ZcDj~84TxsFRj&KUN0 zex15N6xc9Jm;uO~PGzk4Rh_D7x?AE0K&~j}VnUBHwx(>k#ChFmo}&1e#eA`+i0brG zXInO(E$DRB)@{?ChX-a6vaOSjgHxt(S>0d>0i@nH1{pw7-Rg?>T} z<|Tb#%F~wX7jKHC#vZ(+wZ4XUP`F1FV(lQokXx)7H`U=@-Te z_gI`U+TW^U4FmUKD!`i;CV%A5vae082^%2k%L@hu;1uw${hqyZ-`iujaWJvjjkBAq z$~`={W>BERo*?DN*RH=D6+NLo2{hQzDhRkG;hOFtcU~nN$|dy5&bt9s3}0VHfXtl1 z+LcrkZ%)#0d(&FG4}h|8*X!7ymv=HPWVIcWy*f4P{?Ehgj=E1x@R+G=^3x?QqTu-d zaXkLa+DZ``f?WHJXn|i!bQ`Bf(-7b>_xf$gxRZ3ov9aKKF9{FQK?3V|4e|zo>g?no zNYc-E-}#P{ZB8jWPbn|!G9aO>I8H11$G9G^&1eR8w4YHP*cskbV8bj#Ui>OKhAJ0K zl6W$E?ES5Go=Edy!_*9_&?yBxaB+^#hhNm>NIG|t)JZy44l7ilpr=*(rXP2U_cD{M zz}aUq6FJ3>+c`$5haRP`+N#L|souu8%1LB6U#i|;Q#!)q1PRP@l^_g0Tm7_&DHZ~` zkK|W^un-9oh5?_xx%htHZmMwcBCa#XVd$QGOLcQ6XGW1xi0iBU30Gpk)17Q4g?JL0 z(w@nR%9}7%#tYC_HYV{tUdVgQ4AH#P_SB!-UGZ~0@5irp_kZfa)y&Nb)z-F@fOu-@ zWN}_udg&Y(U9qCNwW#r-lYK@LJLywgn}_E(`3~7Wg%1VY)*4={fOEO)#Y)rkooKGT z!S7pp0j8yY!^K60z?Y^qXTZ13)o+41cO!kh1m`Ev~$+f@O2GTtf}cTp)+-@p)bzHl=aQd&}aNzC)` zYb9}kv2QriVd&hEwGls|D@WhIKYz(($3{NGQq^`iHChm8`@|S455s>@%%~Yt#t#SH z%l6yky47A-xKs2Rp)+@SYXx?$s&g}*?Rkw#;tJubksjKI z?7PDFdzdIyhz%3E@;b-KXH>u5t7w>Uzj|OPV!)c}Mz0x6r(8ECb%aiH+d;Y| zLkGwgdvlGnEyf3gQlp>9=U}M6D5E*fe>HlyuZrD4UiUT`KJf?1Uq$L2*XyMn@E3-z zEza7U;?}UJcYCS&Y}xW&Fvi`wdrkVnZjVW12xRtIiKw?uBCadb4E^j4nKua{8={Yd zEnu%G2fTW~>guTj5Trl> zchS@D@wb0YZ?!vYzO}XQX)5W^G>rLD|4hN9>x)B4X{ZUXccIUMAZ~>CeK2_Fm;Bc< zySaC&oZMWq6QL(Uv$)*tuE02XiB>*1@|oo`7k-_2(O0@LF)dCplvJMaPLe)UDC|94 z!`d3N@8Z7rfurO5=G9o9+|AAos@?cCGjL<`%gMZ=n&c#vz@7b!!^Zw77KDFDLe)o? zTPuo3H#{6zkgVsl7{6E;0Xq?-IgOD`{R$Ed-GKDDceL>bx}I&_b;;3WQa|~dL(N(1 z@1l0wcsV^vW%veahXlS@B^lX{iY3W2`FikyxLR0tZl(REIhQ?0mI4eC6 zn`Wel)=cFl*Kh203xSyey9p;FY3b2CBdX^aO=&b)Z4{6A*ezp~K7Qjg&@u#En1F#5 zXQQifoZv#1fJN4o`YO%BY#0|`0E#kL(SKsIeNktkwTZ4y-|gAy7okY4W;dzf;DoX4 z)r_7rH~FeJZSt)i4MI>MF>Gla8oX2M?iombC#C=<}%&qOq2RngUk5O ztza*Q!NXp#+)|`t z6Jq)JDsbOQBuwrhczHx=F^7ToI*USw%F58AsE|vwPL#IA_Q~dK^@!%$a@iUpFGD}E zzLbV^v;sR|6XS=B@{tMLP1c#nFIJ2ikWyf0k*Uy2Vj$yf>Z>hw4m?|czD5s(-Gf5Q zq&(SKI=qx?ZN63FrSnjS(S??wueXo}@4F4Gb`u;maB_t3RY?*ej@Y^ELClDNVmve^ zr!X`}h29kUXB4~)j=eY-nuBz;=jctD3npR)-;(~Q^pFP1iRlzPbZn;@*`>p~J)OZD zzur-abAcE2GZwyfJWxVBq_{iE+EJhk9-=zRerzY|{q3?Pxk_01q}TWV)%x^p3ujy+ z3|~|SajEgHJrV?JO>ca2)q|4Eu!Qy0FKlI>8@@rJMPQ1wsj<1=oMMB2)}uhTPlrA7 z3atdecN|TvHoDUtZ-#xeg^rXLP?EmxwlW3!PyAl>A1g`^^a$5F^!zOxQu0S}2^^&^ z8b3ynbesDcR^5k~a_BE6k>_~JOt&UVQck`kmOE+C`QLM8@~kwht0HUNIAWTp;%^>) zMQL$=9AwO0xKA~922;eQ4Zho*i3)wYP`ZvK70knrMIpo7PUTK}g`wfDuOt3?ea~$% zE~l}eBe&c$SCsnp4+13I|EB4{S1>Ia_@~pPoNUz6VwcM^V8fgDxBPQkJhmTaQkQ5v zqP3VHQUP=<4a4z~RD8+p>G*;Hq%`kkL1Avm`f9YQW)&lM+6qyu`4-LD>OQ8nkb;Sc z`Rvi~l1#t5;y!(Qj^5`WAulr*GJNk=^~pa>377OdFUoVerx}xf%rK#xVx-2uY_SzN#53E8sta?HTlf~rRA_r%oCc@6KYc*v){L7)Z@e70 znJcUF=V{TPf5tgm0$oaon%cjIWV8=dCenO|B)~AIe=1vZoioZ zw5TFd{#`)qRv5d2<(4*(=!ckw=ouk_te>fkhJ&Z9m>skhQuq{}n@MFttGvK#LxI<} z?v@EIz&Tdb!#SoZz$=tyY?oE5c9qXxY~H$#oJwG}x#Fbv=|hvm14Af|%@p2b(-cK- znZU9zGIeg1dOL>$Qx>#-l~aN6*f!b0#Y)yn$tt?2iIf)=dQy+}-F~ww^0# zZ0=fNX#1rV*9bA}me)1?Gvb?FMQ0S~v$S4G`gFc5dJ3)@PKb9!)d9U#NP6f601;Eb zQ}_QjvGq6RQ(pNMZ$}DM!03UQ5s2g!Kb*sUvJ@b*mLd$0|3PNa#xg@pXW7YqfCL10 zqy#-gUe8@Ci#YYXcrFO$PY`FgY(mE0R7Z0COgblLj6+| zRYzdydmqul;j!gz(ICIMmO`>;wzno0VDciE*)mim|N0_rY-Y)5!%z-CTz$0NqF}vg z0C@J*lhP}tnD}V!0O%s8eQwk^Wuh8%J_)zwWGnW)~@ z+kMl|^XR#DjNfX@v_NiE`)BcoJ=mMCib87lHDLAu>P^MyE&~8i^ayIrkLt)1S&UABZREy$5Y=HiSYx39BQ8{rdr-?v~6Ln$wSQ zgPAU~=e~dGCa<6rL>f5Diu=+|%JlHyUq05PU}g9Ah2v2jNEE#+95{6#>BoWL^U#;3 z+7wz=ZJEe=r{g!NjMbKO$mjO~2!z&^Yce}E9kEv4KmX54qfRq9nRTEF=xjNEpUPRo zTK#_>hss$sC_;IoS_EIWSjfA%#Lz;#6G{zz7KRbT;KQgU%Zy{;D=&|axzu|}dLW&? zyS@C`RxETLFEdHc_I^1~EA1JUZ5w7gFXN1rnn>|Pc@viNYVzOFsUm;h(=mw+mCfwV zvvUK0hS;`%Bw_9_HAG$*kjQ7hj!<&_82!0=r(23YN#bf9{57H2bLSv|iy!N?wevT9P#a=1fA8SF#7>dTFJ)G!v;# zAVEoRNl3>qwisss$A6{NB>TrXC!U9ui^u=?`{d~E%h&0ZE4i$csw1ZzZYlf6$DRJZ zN5`>>_@(C5pl%*WYH{TE1)t*hiDGu7#}(S6$o0n_fhI(u$z?2OPH zJCT=&TVtHDYxs7##>gv6>y^Tjxv(wK<+s{vC)yB=lYHlmA^N5uSxt(_j_UwDE~5A1 zZxf}MO$9Jq<`cor^I9S?A*CpRH<=P2=mI^HxZ-^|kyxFCb)H#Ga~-3+a&4yQZW$96 zWNH;F=PIQ8-4wl74}8+v>8wuc86>P9#kuiprG>7#^U$iYoEu}N zA973g^4BErOyS31><|b(wRlei@Mcbty_cps*znZHJ^ehi z>B((6u{0Ew=xt6>!j0@~@U_sYtt-K3L-&t6jm?-jV4W%#jP0ifDMLWnMIYbbJV^#6q^}sOrqqmcmz5l&Eo1&_0L>7( z{Yest1A)Xhnw48wAS7Ha@dbI_{n^4qJQ8&(v!Cktv=Gv>Ql zqKlYEa@)g8v^#bvy%WHOmGhM}XOxgAC|d}Si(Ue9(RImPF7O<{1)7*8GSA7l4+dJ7 zm2^zR<N(3yE|7L zrvcABw?7(vd5gFi&y3X!A4babxGY+Jc;kb3TKX+)R-W^(D64_Kb_H~0<;-~UkS}Vg zWpc&GCEJ7$Vhi*$CD`otGwlOtJ^7fe7^Ao1JxHPbZU)axGQ6~Hgsn8KewUavc|lQnTiq;=81KR=WR_x;M!|zf8z7Q{?2{KaipAcQM9{uc?f87 zYMxiH=0SREOE(vR(Umtx&nciU0o_gZ8<-oYNk(U_P6Ir`po;Ggc$4vwV6Tjpf7TP{ zjdV#&&=Yr>Sp?WTZry*_JRof2?DTbhs!!Gy-`vJ4D=|BXx%ZllH7D=cn!YTGk`Pl` zLUaF7u35;suSs~y{~UL|1XKO>d)QsqoLa86ssF<1n%~|#VP-#n5k;M>WCDTcJo1+r zogLUwdi357-C^Oxn$pL@Rxh=Jo&io zE7jm-iQpFnlwQq?Ih-PT%l0G;bYk7$nz8$AzC?K#Hmep_FZpkDE<<<#rikj~ZSy^_ znyoiDr%MZbbcuDLFJc{B&`v{X}OSB`tgURO_x z8>l!0z@lQ0%7A@4&GGL=A`#ka#9pAGhr%TaefSR)e>K|i+F=6Q%}FLNAP)94O>IyN z&p^1MOq6G|<(@lTgdyE)-0UgQJri}&Tb zH9Lh|xx7FIQG;21yd3e>ZMCgM`x0_St~ZZrx<%a91Qwhv@D=yfQKJa~`YT4`b}Gauo*p zdhI*Iuq6nMvVXnh|5A<*ie15D=yV_}kTDtht4P@B^onMp;24IedVqKXxc-o=aD6p zcds{|mD_hFj}ole!4%N~$T+zf<%3D4JA|1fFyX}#Bq`&bM%-tnR7PhcOztCatMDF> zUMFyuwR5>M%GJelIfSshGMJ@}NuhQwnUxPP3ZX3N#u9CErUZ)`sWj_O=fH9y$I_i9 zpZAbrkHzS_dOGA2t!vg3fXl@mdnf-j{U&vea)Bh`^ipBFxCpp$uL$n_bhpMWaAjUJ zs{b}=)p|pfsCkRp*Zg8^>>IZEF|6d#>Ds__+t{Nv?9-G8Jp~82WQ)4*Vl_ID)kE`0 zANLnwS4I{D`*PqWrX+X$k0r%Ozqq9Iyqdeo<*8ONY00%gQpYrO&hV`ucHxQOmsZW1 z)amHTB`ys_T*E5tPe5GWSvkQKWDdmuH~a$Eo!68KDP~_&x4eXLD(x~Y9Yx@dK@u!m zH;bc0qw|e*m#m&_lzcsJ8O5O^WR281P4g0-eOXSdyM@SdZ>?~7Fn2-LB+M~^hm|(` z9+B8__eL}V*n%-eC>&E&<_sEhiB5RJQF{{+nnE2E9X?;&PW<`p{OM;J?CSljpof%a zcL}KloMaE?bsPq(n{q{0f3dynw>b0A*DRFEVg-x2;{NNRGuzeG&l)Y$h`vHWQOWtr zf%h77x|pxYE8y$yf6_)|69})znibiUV|Shi8Bj)Q*A@FmZaS_Uv~PPZA>L*+d+D6b z1N;-gEaW{w_ew&A#p@)6=Pg+)E3y+$uQx*PgTE5)D_?Mw`^=I4*)}AsY_2U@%(kIP zqA{vF?U&6($1J~fMSX)K$M6L&7BD|4-1E8)?USD{(r2U67OxHza~CCE!uqGO&$3Nt zbml{v*Ff=hZ5f>3UmUJ>p46C2Bd>bLDzx?C+6CJTpWnB#yhkJlY-PFD=0x(WnJc^^bk6U@Ci-|rautmb0}ex*RsWnYR@x8s zEJwT=$POBsalPXb}SMvbzEk+zP#Sb;{H-<0l zN-K|Fuso~h2?^(UgxoMv-E?`KTsyVNQ(0?P&qBt7GZvOMsyoEKG|Ze}PrvNlTdDrE zU9T_MnsRsJ|JBHp$xG9}sXdSF_zPf`Kv2qr?fEmaUZDy@Osv=rq_ zCu^JwR?*F?4!GPjME(B$x#6onKuvka7uy$yupj7IbE%lu5JY)N?)1B(#*k4P$jmis z1(z2CDruEawnpeR>W;?^0QwiSie66FzeG&`)(jJjxHhKZ-MR}YR}5I}Aa9zm?K?{s zdR90mMGHW251u`~EHk+3eth%H!lRFrpTe#6?40!vw;vYH(U-Qh-)~09brpi{UJiQ& zF{eWZqN~2p>G~{YQN4Z1V^BdhQ4!u*nvjYB)!-m zNjt4jkw#h^SxiFd)lb%+i8&ssZ~08G(L(R;Q{W($wPC+^S@xJF#mh~#G%d7i(JKy8 zcWi2bg=oXAwK;+pw0`BAE9fn>c4BEU199faA!#!O7fYi%xDKJA7>!4w9H4oXj4#uF zvub1H>%lT%TPNFoaR=WHxwV|COhCmyDC|_~&>DVQOS|=HXHUd?9K<@7O_=fi`B*HV zqfa3z-hw%Qy16jMbU5!YG?PA3?eZ5khW+!CV{_2<;_FLSMfO3jO~t&1MAJr$nV*DP zjKmf|zbKiAD$_CTc2*~?K%m+yz=*M~Ybo92YH-mlIl;Ce9ueimUCRy^@Qnq7EV^&?emh#+Bc0$*T1n)=Q`idMT>iZF@e6B3ZAHlQ$g4b$E~|i{;)VgYzFTl#8kF9 zU+sIBcw|#>FisFG!&!B+7=&TmwcJ5BGVWIdlq4Ex=1+}* z=}b=r5C%U$|K>3?9M@#|^aa+0*qFpLH?u1mbw5DtE+ibcmbv;6Dhs8@8O;Tj!3*&U z)|l1rG%sk`ia#f2Z;sH`pP{oA$K41nF5mGI3VOrgBm0u#N1b#PikgPw458sD+r;1- z`~0Ap|6U{SrwdvIU|k9GQc#SzeQsk)R;B2c*anpEIb5=*lY;A@kfa}(tYuPtUIfj= z7QagO+&2L=Cu5Su8V$W{(EOd^b?CW{{g>3ALS01g3lpP0KacB>!5C<9=jZm(!vjr_&X zvh293(qdPXW$NhR5V|&O30XSR7f*O|a6nC6|h1o0g ztV7;3dno9&og^3a-~4s!r}^Q3>~#TmmT53$L5U*kkZM;1yOo+z+jRuK+C8BsX}FBA z*zO%a@F%Jaw5V*JS(9k3(a`Xcg?Whi&cZ2Xk30RSJ|pYCBEw0_=ut(5h*uRdC~YTS zR36{3l()7*pJPnoMJ3V$BZ|c^_BC=w${F#7D-jhJuYg0LE<%s?I7t0H#mD-uWnpL) zh^i=1WXvH!^o!@kd`dyKbEi6B!Hc$nnN_{0I9NQ=L1Hp~X|ugnk7ofDA0w3SZ4fiC zt@L$o_*<)wZo~Q2%}>Ek3FBi@@G`ub{Z(5^SI!r{&)+z7pfMzxQ%(0IqWs%=2aTuB z+Fna2_oUx=f^T9VbJ5h?2u%N4C1R7rnZuL743p)UMx$1kmFjt|n$c*v{O zkSA@@qMR-zUgARZ_q(|zPshpM{Oc|E1EKc#_EZZM92;}hZaHA);%pUqHIA|s(unfl zHBUZ#7=90tG0!nsie9aNAFr)SG$r`PykB|UviZeFnGa*WbsWPfgz)cVFHoVKnn3-~ zJOgeF)U2BK3%BMs3|=^+p)EQr;1W(%nZk1n(=$AVw)ukEzWqqhTq@()D^`4>>+C$Q z|9*94ldo@Qn5^<^%YHb*+wt_^VMQ62Utz(Ue9md~+Uw#9zi=g3)eAlYNl)>D=QrKf zV4Rd!VAj=>VAdLio1YNr`m<*R9~6vtgg!snI6MrIebnZnv#l1z!hZJ4`FU3H? zzl>L*^ZJ*`2o4V}fy(KxQLO@!p?}*ofZ{Xw=`&2xt;=I*M8D{n$>PSQ_P$8rl|E z9SW%JDjC=osJvG8aDDm&OBgdS-$)ir_CAA-IDF`fj`_BBn-Cwd%w7i?uQPEN_#bKp z-IsvaYwq=AQ{X-#YD*y5y+`O(ULu&+GnK4UP40U1*WhB4_ZjbxZ&Pl3ggT} zkeZ0_@ZS(G|wjlq37Bt%Mvn4V@YQ$noafn!S6&^$jnggw>2e4mV zRbRNKd}QU>eYdXg4FPR*#M`P%mHk3eW7`5jcuAp-W4g6e_4|dhUlpt5xfK(E&``H8 z(ub_heeVSCLofcD>tlxvF!o-BRo-VRbjO5Ff67glmiW1k6~#KGpQXd%DLE5yXOo+A zm@DPDdPLp3M1q>uDOBVA6?JaZc*H5iD%0j*GZ+w8gGb98s8qS#FahtmbNUpn8*oXg zT)E@oSvWOPxy2KHsnvPzj78%e{!?2YXd1l`?PhzOInX398~?p%^hS0*#*ws}M?+|* zDCflsB%`CBxA`yP#6I2nn9=8>l}J@_U91JT^J?C!5@BS^OT>6(W_C3$`1m19E4$Oa zSH}gw_a$C%f-z6}9EXz@B*6C}!KIL+BIe`9nue2P!F^!n_;^`7%jl-D@m5$2cFhtI(5^tIJZHLpuMGe3JLQgkz|s&tAP#rA z>&zAPpxh6i(_QEmY!2{My8<{{R5mUlWH_mt;F+(ZqB~9GAd6<=I@$S{G0G`mgRc?% z>ZzLVgnw+!j|}??cy41cS>>#eipF>b{ggGy{~-@moh}5%Z}X8YpClP;&KB@A?Sehf zX%$m%=Xr~M`PKma@(G4^o!2aZ5vmsMv7;vNYC!VCJ)gN{y|_I0r3YQ-Xm_%J06$Bz zVzsV8A@U~9)vuQg-MJj*gbe?@He4!HAr^@yyviiV$@6e3UJdOw-S-$ygQA@kBuVGXe^F)DS->%4GW)akJ zKB_|+xPeNqg6j_{IW?bAe^QAP{S#^bfh6tCX7)cT7ogYrNIXR#9a(A(4KB<3f<@id zxYqTag5H#VL81uM(h9;O1f=FD=!sugJKGANy13obeH&lx%1q@D%8Z0Ud!5L#>wvD)GSv-1&2<*5$8nKkPre z>rFyTXL70O7kYZXAWMbg_G8u$dzgrF55)5UCVdY4VMA!)%RO-8T-$kZlUJtVBDo={ zv6fHI81Q^4Oq17HMFdPC%-7?7{-COyjn;9xNLbTB?RizW!2ZgPOCE@T#ZumTOow{C zeWj9pnwcONx3hQ2HE;73FI)ll%kLq;V$PY(XY>9(V_vQ7r%93v{@?5`rCqWpjaf3q z^CJTE8rhu;_b~9t=Bz9Rd=(SRYo0r=0^|MR zYd#0K(KD3D#WJM~+#)04qKZN0$wvcs3jRSi|5F&1S^roOApYwN2>Kxc2HVcNcAQ8l zvwv{=qshJB){FkKKr9c!^T)$mPI!So7)fNbl};gur}vb^L+QL%-2+PY#ufs|L3-e)s=R zDJAT?f6o5}GJM)UoayXUZR}mJbUgd%wYt49|F1Ae(gw3)c*E-Gf@IaR1MDlxD=OZ1 zXr~DOz?ZO2i9hRcWISWl~}T{DMFBH7&f~b^-mq=zxWGQ_I`vgdS2Q8IK`t- zSjN%gN(Tc`1G&-&T%Ty4lSe(Pnl1}?%A%u|(S5n6d}(IzR5R@LL7e|S$q`=HR%WJW ze7@g=tymf?QOcX@@)hiV{E1fS5x}Rv^RB)8_$xmL5zvX3T<$=z6i-}ipJFsj78av`vOhwvP_gauAs#%lirlB^)Bf#y>49SdaTe-#mPps@ zX0D6fwa-yK&-JRI_Wr`_8$r%12Ur%g65NN+DY(3Hqro`)NJ&66<=lXmb(+fAax!cC z9OyO5UzcCW?uw*Tm#E&Fpgg^&DjYX4q+c7-&Zlc&0lp!M{GxAbcH-qslUzFbogg`( zcm20{_cHJ13+-XSN~BhCI2x&Z=4iw;Vax9@U)`22Av};4wAqt_i`-4Q+@0eqdd-P$|6kXeU#6CvL8&1R$?w2Y)Q)ofx>7 zo7-j-a|qapS#~-IUDeO-_}YyS&Dt$lDGjzwT93^vNAiz0=1ICcUFo&ifx;+%2R)|3(Mn?m}}_Vz&ia(x~#C za(z*%okbA<5$#tUA@eJQd2j{+rcmT+P%&*Eb{4h1ix3aL)EQYozT#NZuhq0-8@EO+ zpqV?NJf_>y7V5;lS4f}zvd@cHXw5+Iv$dhWk&iCo{Ux99U8B81&PZ(bS9V~hwz#&} z_s2}*fd8xqeUsOR;y>l)yXV0Q9ORsVJ+pK?4NyNik-_D#zCcaR@CQXvJc&8w-Vw~v zVFiKh{iEkK<{^yg>{iQw(-Rhw`+>vP{EUY!Ev`=?)i}$c5cGca!x4^ve&Z&|cZ-B6 zsA1bdTt@+d8kra;k0;^7Jk7hec=s5Ve~s5Yc+VF`6p2&Onuf!zWfJvwLi3+AzlrQW z7qIOfZZyBx-fA!42zf5ikmveJXOfv>-w<5x8;E;P@f*%r(E5&7vV=fO0S;!KWqKIk z+nWC2*xZg4U{XES-h-*JR5N zed11u-#YpZ#SSk#-GA%0DdAKLsAntDfvcsFbo4kPAY%U2%l_%rbQ;;szBE_nH~U&^ zOjwpe?Ij%*>{igfD6fYs@82nHI0G40%H+dVghL(ybx&}X>O&@s-RaA`Vlq3pEo}aY z*SCP`^LEMP7TedgAM_Zg;p9)$UhQlwg_&tt_qyYy*e!TLnRIVD58O?)B~ z=L*MKr!t0OY$%L&8p48_Hernc7nO$`ZL4S>BX%(n?9dO^^^{U7ZF^?>iAW#Mqn9!( zdgL-Y-IBbY%~KclI_k87IPQHW!$FwT+7_N&u_5aO>KDsljbGBoj7c@q48SU8M8Zc;H=6k5s z@tKD5L)G6f0AV9=Y&5H*_ynO%r)LARN1$XptlI{;ydK}>WySO&SerWfzGF8 zaPn@`v}1-|9bR|ho-rz5ui}NX9GfU~7fQKeYmA6mK-ga95Tx!iDz3CM;!UO5{BV3s z{{fC@R{{y+`M{DbVRZuxEpN$CqUl_P+Ae`vTo$@ZiY?-bQSp%TTlD{-2$`Sa%R>7X zRD3Wq=f?bxTh9eYV~so(0D}-SDIWilHxS>7%3}Cf_nY@ppTu}56nV!BWAb4NAk%Bp zze{Wr2lQY-(tZ5?k!oq^o7XvhH=p0WmhqG7!-!y}(*V4@F!YpEia%v{IPa*U8 z;U0>s`CB2kOU!MWclFWAV-w>vyqIwas4jri8XyM$Rb3$H5e)+joxnYl_^OnjYY8J3 zpvy|dYRg5#I^H{!fIu-Yg&upVz?z7AEfDRUMNCRB@G0*hi14-j4Om6H@E+7}{51mU ziNuQ^J}9T2IH`5+XXVFBL4!gzehx(d18{8}a%tH$8v@XVCzxEfS1Wcw@h67u>>Pdx z{P4jAA0|I|ZaB_d|08rJ?H?O0O001HO+E)2GJMXL-~Iy7H)=HE{+zBPLo(liPg+(D z46DzAOoD4@0Hb5I6egePzX!fYH34#aE*aWiMASgV7bVV(8;*LfVlNe*$8k?CYWJEz5h-jWEvA)W%`ZRZ0OgiHhfX2h?3arTXeWW`(*}qykJfXG!U5Dk;9- zN}&e@!omaIUmU*CT?gp-uuav%$L8%`YR?ubGs(z*99YxrLfTo{k^q>JvsxiH9R9hc zS@lq%_I*-(4sd~#U2O&{J&z@6&%9=eAeB~(HkW*b(#7^_m!Ys##_psTx9kHJiCLF2 zB@l1zDkiU8@$5f{exrgPnhGFkj^4%GTK4tn#%z)$bcLZc5ERuoh60NL8@=uc{d;M zu1plN-nWtf6fUKUTHCaCtmh6-c=Y8df+7B&5>-HrVH+GVBoFvSQ ziztP~rU|GYuE=lMc}{Ob`w0XUl->Q`TRi92LMG!D!)s>mznO65Y%dXMY;#Mbcr%Om zHoFmE`t@IDhn{^i@PFHe~(ffM8_8b0E_RODY3V?9FHWEgT`| zyJM-4^It+SGke+8DS(C`_s3qDl|W_+;vJ}vuiLkNsU306YHxZ>0^S7Pui6#b4fi3F+_{zmstP zM^1j4aU&^78b0_%FqiUN%&_#oW8VGc?=wY?v*W&VqJg@b;T*6aC-f~nt*Z^FFt>TkSB5k>`oNt8^gXl z^&-hHyB<)4-yuhI7Q(Bj`PuYq?9Ea=HAiMDo#HnMFXA_xF-I%diH!BxO& z!V%6y4O^Xp%CkhO6&uy7E~9w?#UjyvU=qSD^nDp0iG6D+@Ez^Jclw4BIaI^m&WhIC z`Ekesh4Sp)%(_Lvyeg}={&H6Lc^7PgcH*-8Yy$nUEWTXI^L9*s%-M6Vuw^47BTlAz zYZF8r7enA0IF-N1^K(A1?Zu~^2&ES<<{dppr=UxBB}G$4-dvAx11}}mqRAxd?!PVg zofT@4@~7&S@713!k;qlY6|wx)HyrslOc46N*kNG?ups=jdy~~U@YHG2Covw0wCin; z&oxvpxG37A4K1GWEN99s^BhR(d}N9E(-vsxj?l=dd@OG}efp(Xm~|G$c4*kK()v<~ z`(&wG{u7?Eyz9x;E!?mWnP-y)PgN8lz>W@LAg|`LzaoaBI#DT<7lwReY6@Y)?Q2uz z*K^t|W8cX?uq90ECi$VQ#Hy|TAdC)$4U#RCLlv8YyP(H0UY9yVxys-)$)3CobbNRMSwM*X(o`#W=Guso9 zE`8r>)vDuo371zFC6T{=>3OihjV6!e{`7JRYL3{-Qe0L46KAnH+T7xhd=s14x)MlT zK&%E*J7rA`Rovv zxD9?IlB*ygm3C8w@5bGkzxXw+TrtmlrVER3`GU4A(QVU+xeH&ES_QA_{SIE!n^)lD z*~*$~+7cm`=cl0FJtxdD{BO##)6cqf!Crx%O&bC?xm`eHlfNUftvV1I{m*#Xj?yFZ z%7*Dr$&s{99lz&u{j=z7;~B_m&yVA)dfk#yr%C8^wx7XNy>Tbx9q_@VB`)BNil2jf zVQ;|j4f81U_Q%y8(-Y6!{wl8OiSYO7WJnYsjw+hO{6INhgzlc!>lR4WGl_WtR;@5T z6vobTtOQ&O8=Ub_-x9mqB=~^*B_x#oPb`wfx0TW&43Sg>DK&X^r8bT1g@LDt^8;60uC+tZSyI?x>^z$uyQA71#8gY zq18g0HQYXS6XpDsB1bZd(-GA9ds&PkGP#fB_g1-g`lk4nNNX=_=)9HPfVO2P_wA{1 zXgfOc0S%j2N$Yh4pU0x#wE(krPBHV-jGhE_BnwSilm4=QMlX@NI?w-2!c$p^?kClA zA&mHLGm`uiKVAI;HGlK=eC03iY+@?;21+AQX1;P(Iepk`NzH^)33XMcUloUb!YQcN z4t$T|#G;?%MMp`bt^CD=kIK2u%gW>CKBwisl>Y1>7~V3uNKPZAlQ=#CcQ7AoP4GoG z|D1k+ydmWA+QJ4Qq|8CqgSC?;aUW>1Ix7k*ap9=e9*?i4P>jVx_MzqvO6ss%?w>of z+z^RkE&2NT2kR!QO^RM6{j@;*P{?YxKq2N|OADo%swZV18+Ch)N?pGiX5ApCYgG|) zWrI2$!f8wf>v0hnrHu7yLMB)N;WpgB<_E)f!zyeA9Ql`@dxIM%6@dmu;2xF3H9OQ`{zS*|O<>9@Ey=+5Q_k^5gEa`X65GXXH=`$}(K6J0-RO4+_nz>CdIc z7EQTb7jhtfvZ}9av`kN`uQxhgU(tgZg?4#-LAMS#he)VcJ$rfTIQFJ^l&HMCTurF! z?`g79x40|C-|JeQObM1v()Xq4{-vU0SF>wpvD)I>XU!zv`Qo;_ zR=3mAK9OQWPD0V%kxjI6ZR7K7Gfg)rZ#+N7@%G$k@83^x>%0kGEbDcf*^*K&iy}7O z=vPJRsP?mMgd9uw%SzG#H{)X%OZ>~j!paZLn!A)qUG-zEIf<|jIL6gx3&R?dDmgzrcl{%zLPOW3?(=eH|6K+VBZc*rk-qPcY9;J6l08Ro@TV%m~`H; zh~0b(HrxBwW?pl%U6JdeK0Iaa~t=Abk1xT2gM&`Txkkv@s4e=X3;@!D6xQ+=2&p)8?Sxwsw)>+ zT;kA&+oyemC}xT?IOaU1o+O+;K2*V)_TQ_b+#mS&{8sRKk`6FU!T(It3M{q+!AVQ0%dJ~C>N&f^oFvjP$5>z9Uvc*3)T_Qf-X@YH`8L{|X}T+X zzD6RofK}Ubc&DO%$)tg<{l|3u{xAY2wC`c^s6#m2bT+%#bUz zkDJ~_c8%$r36amg@}eIWX~Ti8*a3-~&9KL|?QeHuV?HcdUaJ4d+Yx@|t_?y)?>7r| z8&%7y8$9X_$4@y?)SL@f_@-M1ns=itni@zon`G=9LMB+tbTZ|nM2)bMh| z!uGx4sr!5LZky%*L)KeBwefsm!=p@HK4e=1PYWE3GNOp?(RW~6sI^v3beQu zC>q?gcyV_KK|+wom-hF6&->kcXDUu-upcFPG&c|6Ut($KrceNHZ$`4Kn?}MT@RS8B#@+d?U&a5A_M_Uw`8?>WL@fj$1#v3r4 z+Iu1)W`;O=m3IR&G2`0vdzhf(iKV*R<+Hq&0h~8a>6WLb#;uPH`{g382CAZ($E=EG z+$3pa&6Ri5dxKfYIW|FTO%BR=X^G(ez4rB?iP9O>!}g?2ZoJWX(Xs1*oP(!U49XKc zTYQo|jgzM9%fOnniT!n=&=7Z)E(4vyFwY0}nXyFe$~-=+@-=Db{dK3%2804`BUx;V zE4|3rjvGCedkz*qpwo!EbD8q*zJlBd`{G0P5+<~Eh8@i^M)rih898D1{4MV>68^hm z#O&y>_q;;8l6~(*9(d5dWi=YJPGQqb7gU@)5R27l8jZQR2!u~RFbCjYaXv0=lcZ*P z-UhGGG)QAHVrQbgn|e!j9(Dggal8M*)M6&)r3DF6)c&@*?n60<28?dvi_72UV8s9L<|a$R z@Fr3N`X5SgfZg|eaiq@eS-IJ7V%?iVy&-g2DEN5Jv`2F+xyYeVtgW_`@8AK4dbmOf z-B%aOLH-MSa5UTWlQZD zb2wVBZkS{ItdE(n52!P*1EED&!fvAt13^l_ zPYr7>`>IZQ9EV;0#{vDk8^KeE*kD%y4up35oa|h%y|%tpt3XhpI`03$WUr@abn7E; zfbe0_{%vR;pmc!f>wu8LppRFoafLPyI@He^kLVh5T*V)wWb_z2f@F3R@kZF# ze{R}8^5s+R#Ei;c#U3*9TV*#F5QsC{_S^m|gH8}-&+iZJwFmq0jRJ_Q+Q%nBv!2*2 zBqGELe4P_*<2-v4ska$5p9gMA_!Q-y8y07DKRLQOUDiWCK zsIPBoW)wDhM=Tf`m@AzMOgrQhCNs{DsBWLb)0?NP9lkzK3^;hpW)XmLUDtyf=JcC- zu5=DXBV2PVlXW&&)`RDI)oX^7&BpWCce2t|0wQS1U8F}u5Ovslf@0_TI3+ASh%K|- zBRZt6v9QbikPDYr*oe`i$L=Vj(geJ(KG_Lk{G*QR|0L#nYcH_F(#}Lm{ya~%skni$ zmw%uzRT!06q+Msa%&j|jht!9ksfe)Oj81=|V90m-FHWueh+^%#LqbkNpEBP3 zFHWsi=@O~B-y*TG$7x`^=DQ;ymgG(;etaPETufk=n_qRcJ%taTKv%1h(4U267b_e2 zmBGQ!qMlt1_FU7Z20b9#P8v^^`$97H{FQ)O(`@wx6r| z%B{X;V$fu_(TJ3E)V4xaFh9elEkewZ zGcajA$2Oa5CvzjmmT2aD2s$n>KR5Nk$2zO9LE!0XW>Lw;s~+*`8Q0XM_OEU3i1ABY zyB#+AVq!a_kA#%F6!7SlZp|AekYv5BDS{Srq%)nHGzhHX-1xXC?wYj|1aM(LrTiEm zSXExDS-r%EJl+NSwX*s6j#9#;6bI*8&vXY;uOx{jq5>y87zZD|nrz?m-T*xD1}rx+ zd8;Cv2PCN*3EMwLmS*yA@hWMo+s+l~aq|v@s9d^QX#O7)LCYx1|ic zbWP|91ckz?uJTiN703`h*Nb^5U6BnrTkW|*SK@)3czrg{KoV3|AMKm4&H;8DcagwHW(Y;|}w0OH2UKnxbr~o@xdg8a0Qo zYFEYbtFo0=SJhrY7e6?jNNO!^BXMK2I5}J*`>?Fu?&A6KmaAm;sxKGNQ^R0mU$lwe z#dul2X>UtwH(_h0$lm47HD{Z>;<|YKpNWpy|9W}h^|xz%&i~G+*0th?HJerbJKNis z0XFD5Hd7Y6arc$R#<;SX!qlVyA)0Nu>^X-#oQh^^|o4|CC+&u@) zQ>6^5s^)!1&5IR&CZU>QlDcBEhA<7}lj=Q17P{j;;}G(bN4fag&Da0)a>w#`1`Z&A5Rt7~_q&4I(RQ}!kd;g-F*!;Wyswn4oYnLS#5qSKaJ zo_pBA4D_wqO4-8!dV-0(i6C~_i$y4%uec%`9`TwhOClQ(9$=twPigg>%Wz|urdT)y zps$oJwHPi}T#zgU^vwC^6W;>&SxpQMRRg5~v$BB|t_8QHxn-x+-uHxi@e)*f=sPi1 zDu`?tUTb%}2Hvom?o|Jr$qNmiz!sTRz!;}Bzar8g;{)dDvP)lNSw=mBEMqM2*^s}X z>%<8E@GFrj3xk&WJ<q?p?{D067VC)4yES3U{l%~2DV)41oypX^4^0M?IC=Ot zFr0ggX@HPo zRiEz!AU92>eGRUsy`PC@)(+vS>s5V7E}+MyO!ofHybIxSvCBkX;N#)|z^A`7K#Pl{ zVjeJCjdU$YPT0zgymS2PBQ{l!D!($RvU%r?s=iW&NEaHGvNdtnvQrjU&SmHIPXoID zbP;y#ZiSWy-8@}x5ZNh5fXIqbS|Gf<0hh9b!ymU`5|%asbobH3)(k^~(#cXk-s#gd ze0G$k$W9?54=-fm!2|Us9pM?~PY`}2- zGGf1ZiWLm4Y;*21PPhwP_H3G17ji)h13j++uCmLF>vJw>yka5bOH6_IvKWM*i3~G~2Di`-|relE6#Z>t( zX$_anm-oI@*KDG7mmk1q|E2^q;e6LhEbT^nHU|BR>WwGdrWQj8P=HQA+?^QE;~Pq+1I-U9c7ZeJ?C*DM%5kH)AxusFr05q2P&exu zX650W?&uDd3GMjdX@7L^Qa4g~ImQ*f-u!=gq2OA4X0%hz0QfvDqK?#3oo!sd8G)k2 zfj$Ufk#LIYJ%Vf~7oyA*^`IC7v^rv~rxY-2+P$ai!i{lr+1j2feKwT44fPOsqTV{w zKC$@;9_t)V!QIyOz!Z~JU@`wPb9eE-kGviX8WZZ-xAHqB5~=>=Kp19Bs&?MP^f+tM zVCeCpEeM-M0*?{nZ$<8a4hA^(C(}r@@pW}~2Qg}5;|M<@ZvaG%vWGH8q3f>UJs>7j z*ck{5?SEz(2Ld51ui<^@r-;bS4oY;=wR7kEEI#)D8i>$c2bum2KlnO3<4$n^pGQ!&Ij?bnsm%7bs@#Z#3#qLptG3q@FWPoegBOId&|b6;MV(V zKPvO8r89rb_iG931QXqj7@ib}#}Q19C2W-E&6plG)@iiuE5g)#*>sVW9*Wfg^6-KY zP#m`yDYyhP1o=!>9+uv=5tv>UVZQW>foKzBerL#6pZ4ndf7dm{9*2Jk$`U~pyIw;Hy5tnokS9Pr!hB2y# zdC#va%&$+|Llb7!B?YfzX##WzgWL$E)WRmJyQpT@mF%E9g4e?|0iJ|GPK520*SR9L zI7@;}x?z2H?pZQ~bIVCveS7>8)m?^ydhRyBivk+N7aGJ*8jjWM=@>444wG#2$&V1t zv#G}&r8qIT+t=Q6>+l(~Nk!mAJ`JLl2Jx8&F+_tXr9ouUAZ};^+zC+@geXNqlpP^z zga)B2cx^9u?Q9R7w}-CHt~=keHhM2L34UqE4;#+%+QlI{5x7HHQ6^a$>k@OT|JE#F6!_2xXVeagDB@LpRCg6$4*5MDqYm8H@?V#nD%^qrR52dw(ezk)d z+C!J@p{;gMe>>=%Jrvgt8fgz52<9ILsD}ac!vNZ00L?IfX_#|WmuW?pX=#^fWfw?4 z3|)o;s>DG%5~BPFQLQwHI2r_8@H)>93Ya(hA_$h+Z1RHcfyRp;RM|sahEK{c)uKd&2fM98>+U>Z-1Z z%C7mjbs%B;Wp=llPP?}su>^q!Xrx1FqPbr52zaZaVp9zDHXPQt!mSFNo$@BQTupC^ zeL&MY^&pn(R>IUYi66DeBTw(I zMa)N!R7o_=9ec2}a?^UY)V*)?58dpLPZ`?zm9i7yXL{S^*Xj*ERGMd_YfxjVk|A2l|YDY8tkR~wj#E}v4)!IaR;DUd*?kvE|UqKt#A#O6!w_=5su)BLxj5>u+a3jb3 z8H2pMTF0aA_PukS1_9mAQMCEk&3BtlQt7k2kSc`c0w~;H0$77;S1<1|{Cp?ODwUhN zuZs+-yp`P8YneG6VCaY~w$w!rYjiMXtNLY20o2nPSs_|$6XDC`W4E@5)@D#kM|ojV zkjsqJa!%ji#DP;Qw^6*k&-CjiSjVkT2TW@KFyu4gGww5SnR#b9UzdIsG;70S3h=F` zD!vVJ8j_5`_7+)%pMu66`~9m1PGJ12{JH$A?o!QwcuD)Gr+e_?EvyF}tMub;i%VO1 zhj+~t)6o;ur=}erSiq)z0~K!#g4V`6gmW7~=$pMjBsO64%-wp-p!*K4QCF7jtTQ!P zmx{9ARI&{kyHIyB$}RRXuCc3f+SsURyE{oU3_3?X_Go@i&-UK^S!ewWVUm0>*&72_Gw^aQ5-%ZyD>=KMhzj8 z?)Ls97l7P4=vytCK}6$*)0(7DfiNUKJ8me5VR->SMf7&;sqo>l33JO`Hu8?qb9J8E zzp*3P%Nnk#4NTF(Z*?-81vCTP(8qp?o5HqqCw{w?un7ozRt_q_;Q6tTj5a!Z`K~!} zIvN}PMZ$Yv%=s|yF2{S)lDOFO+_t$yd}hAsH$o>>v-ne?!5X09Hhz9=FKz2JObJqK zsj9N|I-%Hl*$$BNM79(*{T`f`FVwuGw#AYl#&TwANbnu((U)1*TS!djc3U^cJLEc(uLVd0g5K+Pj*doiO%a&Y9SI#iF&t0aKmgpWDr!VQdC@NxqKDlZf{_ zHJxMsj#>!(i2hD#&I(fp0k(fVovg(Mc+JL)s$4=QsjLzOP-&;m*^V9@#7&;WetjJ?Z| zb$8K|6$hsYx=%q$C3654SdexnJs=39=LNw3!gvOtExOhQ`s<7ZSdBo?xd;she4?wt zNmrw^xfQN`3@rGqTLTk@tBC%ubC3d5>9GD` z-b~vfBKh_ue4_SL^{>%qm6ybI14HlJKBBX?@4TJR0lP&pRlXm7f|@Y5uji7}FrKp)udn$ldubiE|ie}lpgRZK1&{$^uU0XEf6^kLwPJR=A>k}VEnNkkGi>~ zFBw2hF|FM+{|~OMVmm`e#COynvhE1?AI?EXNOwMtvQ+@fMbK=7cp)fg-CcN1{x;|T zdeC2s22i@j7VyBMi#3*h2h;f8?ef{}(cq2V0-*W3_#D;R6hf1G?>e(e2s0b|i%^C@ z(3HrceycmqR>SW2;1a3SEWEq`vVTS{3PNJw@e@7F`%^sPm7$H^#wg={T>IA<^|hmG zT(l~4s(m^zOv**N4=m*$OYJG8XP-4Uz*lbW2o{UWhw)HpOWHRI&PG!`KZ1sk_3R6d z+o+|6hGo^%>;N}vz%1Nd9FiT>a_ughvbFC6Ngk1+0Gt-<>@5EZ8b*G(W1Y{Z(_I=t zdVF+m^83PUJbsJm3)bp>dvKH2y3h67+JCK;WA7JC^OyJHg6dPoR+E}#RhEyRQu%xS zwypD1*mxPu;QbBEDx|4ks#BNTa?wAnFYsciyh?NQv*PfT*FrlNT=SO#SmDj5{ynVq zfYtSo&S7U^AgC2t-Zb6Bzdvd*|5XZeRsFf7)VOAQ%lGnJlpIU~(gzA5g?FHla0zG# zu>|yYq|~K>Xg4mzJ@4+gc^(X5B832N{C|fr)bG7l^_wcsyIbB{J#X8SbasWVclGh- z-Hi72QJJef$liqHg^5NV9aHhvBM5axV{rnoRFN z?8NA{ZBoRfgO5MPq_=G|#ZZCw5GS6v(zMbbbxyYLT13uKQHlP#t8uj!&i!p8cv$|>$ zJFq3+v1VFtP}!F+8)+078{7C#5+ZhD5pRZbJY*4kTTZlXL84#yD4cHT>at=H) zq;d|r_)6s*utiISnMxPu>jn43$x`xqD)ICP}VbnT%Odl1v_9HZXha`3qMMt;Bf=5-~*K+m01~q8%+RA^P{Pa1D#aBpGzoW z4*+uAN!8_&BW5!caJ@Y!?r4d}_L;}FIpxelT^3Vy+_iSJUgxac(9g>@iKx@dk#~n3 zLKo-T=Pomjpvi!nyufea;bS z+Rd_8G|;*MTv-CH;*F%5?)ItLq2b}nr)bn^D?@jWwbaE^c%L(B*BN7XcSwJP%eS-A zpx+s4fJeAxVLm%brnedw@8aH{*_{oFIcwaEQdER(CcvE$e}kZp5w*d{g<2;@b;3xv zv?6nS)aXB9$0<#2F0G-wE*@g4J&cQt#Xc+AaB13y`4LP8?^Y;rHTPbF&Xe;HJ4RC1 zh;v5t86&qAuIQ|ckD6%-QNhp}>SWDi=0JUm4<6>z&5hdR&H|ti6X*a@1_6fZ4wT=8 z_1UYqF!I!iKW;b8MBQPxZv>uaNsvmGG4c@!P|D$UhYVAhP6fcasoH;glt|eKeck;` z6DfYC%D8&X0y<}jsSem-kKqR0jKpvUoy902pNV1g4qLm9TYGK$eCt`l!wqSVZ#Ob+ zq6yfr5X{P*1*62eIy#CB_rPsis_08qDz~zp^!7`p>FzP%H3x%=E$R9U-|sHZ+EaYL zxFp`l6k)}M`Q3DP^lw-)Tx1kBqcdM}qcKEox&BM!aH!fq$i6i48C?`n&{1Phd2fhM zVSnhp^u>?e;bmOkpyfY*c({j8CU!vc-hsNJRD9f0_p(}dCeVn9IP6r@fi9rflLSgr zGF3H38xE_WXF;#%Z0Kx)bZ+cAZUGnOlR-IXJmSlFajkZjZ&HKxCAHtK_G(|Ra?s7( zNsE*u$e+EO489KDl~3hn_;%Kq4BwWL)@ja0waX*bPQxT9dx>XoH%G(|)C9hXid}>n zwfoc#&j);OhDNhjCDOSlak&>0uS#XP{u4eD84P?zqsCYgFezOmGsNQiSdsa+M2YY9 zB!<+JIXk;hc;FiR1ZTeSm<~6SJBFy2SU!@?G%(S&CLT0oyNQ)aY|lW3zB|LvfVLx* zqf9LL$nad`31IYOUSxFZS)(aJ1P>)jCX$| zf5T1Z2c&aw-0uWgcCPkN6YfddSVM9JlKpq-4OV68XO-)BtMGQ>Z-k5##EYU-HjlSO zc_$(_L$U}2QN3Z|{tZai1pnp99Lp2KRAUe-rC4Ie&4+D;0cnyR_ljk=S#2iT{)Te#?qmO%dgX#wJ4hj%iGBEF z73@#!K8W>U2s^>2_vO`~jOh<$Z^Mz+YYj+7OXE0p`-tDC0qCaVB!N-D0mVu_D*;+b6TsEhd8Xa z;gZE^kACiZD}M_uwzuWF)8pm;^#tkfT%m6I{}iIa&7^N}W|-yY00DGORaCz(%0G|X zrYRKf2|IKI4oRJ?n+Ys|(<6(o(?>>b_RhWNKcBlv8m+c#a>UNN`--BCRt?y==X-We zI<|)Y3OE6GcKn*!_#sW_Zm8zA=`}LHIwRtA__cUdTQ-R6G@|jXZ!-heDW10xruNUa z?^L5O7hmhukewe0OnZA6Sr+UmO@#mA&G8n`Gv{_RW=?yVgC=T{l5*y}E6xFpyJSF= zL>5{VQs9oPN0>MM!-_oC+|L=^5*+Ya`C*fFYv;R>sqw|uBc;oC>mP{k7Uu!ir63p; z=xk;leAupoFwB5IVSnykQR{ zE!h4(>F~7mN)_4uHmMl^1AijlIQWBYDPfJ4vjrCIvY`b(q+K9m4B0^^N4qS|u@YjZ z2i%f-e?aep7+Spp^H!hgsR81KuJsNh0r`0|xVp$`1?4)0b{AwZu(#T&k}F*R6bFwF z+JsGX0E65b(^{ed=%4OaK@NTfXXDOBw~QX#V3b@8uCL32Xu1jXAqTNe#7d=pEnlvO zaC9mWqDb!m#-fQ?XeOxfx@j%R!X6CRE`I;+_pfh9dt(dv{JFZL9*>MV>sRPJuoRnW z7f!0HST9K7>7FcohaUtOsc7jIdQRA_mbRRDa~hA~IAgOGe~eBU{6f#|oLnSp%lX&w zvz{ieNO#&S=kyB7#JSehDacnQ%7W&Mot0a_kniFS-n7F-(TuSrdcE{{^$C)_X>1!D zFKl}&l_ON}Gb^>jfibB+6D>x;8Y@E;YdMrPY|H73Mn8_tz3bMn!jU?5^U3gmfE2$)<3%&-}(U)A`xENlQ%fWq27 zYx)w-ajq*b>+W}F)mtAr`_hhmUVHYg(n=mA!8RB;MMw2%|0its(uJ4)k!Hcj1DWiT z8@Ug^rZO*mr@N|xVh$E^rrOgQ{$YH1#(|KhX=BAT>Ve2ijGra%4{qxAnO6u`>+DfcE?o3Mi$9p(iLBI1H zsTiiVG1o}6t9}bz7qP~6|19y_Zt)9RUZWn9Mg}`+9^wc*bl9e&632E#!mPRk+b*{i z{eLq~D|Qu{V4h1^$%H*oVVuv6mkvoPxr#TKm-#BMDN9l2AXhhHwlK%`_V|Zlp22FS zgV18(P-6c5{HUSHp6Kke2D-lQX2s%Ge|`k>0u`8ZOSWb6^s4&Cenq>wi+yYYnz@QO zRpCr2T-DC8?PuHaW{u9hjkB?`o?2F3DMQ(HwlBNJIhM4py;SgD>Jy|A5-ZGY2?^mj z42cOnD{XpH^-xwn(op5%4vgZ6IL_oTyY}?fQr?wKo*b@U3Fq@C7ddhvlWK6oeWF=_ z+{`TiuP{6*?0H;`gs2cF}6!eT|h zD;5-e-y&;0;(nV%PS+VsgJR>bG%i6e@tA03YZ9#iM-kary8Ael zw<}lG&55Dodcoq06N@d@#^mbmVyjaVUK=H>A3bSF_A3BJIXJFD$|$Rtgh<(78f~-1 zci5{~V=uaqC}4a<;V*@neqPzGjvh!^rtAPLv^A?=_nq#j@UUnK-i}yF%$g&bVRFQh zO|zc);=mwGZH6V37st3u-Pz9uAaL(AapDZSaB!JZ_6}T-h3VV97v|pOsK7Xg#4WB z(iwYR^mx#zxV?^af* z(x_geZxXCi!Ku$xzA2iq(fpkRae++Ti=trzCPz*S3$EE53e>*ps1>r7da^V3&W)Rc z@O^ikF0pUS#tKwPl!qQ>)ei9>Yb;Q}`MeQt;PUQcMt6!`v$H{L@zU8ebl>TL%4;x= zKfYb)sgq#m+4nUIw_lyR`og^mBAjbuiY|{N3Q;MIf?b4!{TYv&0LO&8SrJopI_`@T z{F2xbWKUzZxHgqt=6>chG_AiN`@LA0_G#-A^?PbocioVMAA*cL^&IDXJ2KKdtG3Cw z&ugm#q^WcS!$;p!rtAcdJlN6Jx4pWa$V;q zLpjF>X55b-_!yJ$l6BBwOQ*jTkFipnnwtAA4tcTYz`|%t#3x^N{2~)5p-uR8wAqT+ zpL3PioEze=CvZ``O(Hkco4&1G$NEKI&SA(M&2gNtX z4pp~}JEm`KyOxFZSA^ga&l zO0kps=ovg164evXRDR<)%ojOKWXWouy3uH2Sqpepsmm$-VQFsH2;=AkxS`xa~t~?4*SJ&zGS-Q`TdO zlEP7rpKQbG)zv z@_k;$qF{EWRWO=lI!o>))y|tF+>NJaIU}jFygnN9$q`IvO#_PceQf0V@r-F6hFdSc zMVvPn$l7gdbHB5y?XLT-QQ;x}nxy`gT_^j^PT>aim<_o3+bnIY+b3|y_v(drBR6AG zW7F?c+^RZl-1be|@QrN+t)o<*(>Qzd-Sv)S4L+3DsI55RoV%g`gXI&}kQ_7A0melV zc>Wx>1R0NFel^dT)YGQG7*1s>15#ds-?q|3U2SbRh`~>BQnSv(8=yjZVb2^UdX}JK z9uU*Zw~-5R>y=R9=%<>rU!-1Jcq_Uhz?3(p;?(zMzfpz_dW65=E$hR$`;!gE{e6FW11g z3+aocJ92jAGEt5q+}8~d8Me$-v-+Vwztbn`V_{KKWlAr}lw%;0;VSb@@PP*j<`a1G+Wm|Mx$sI-rQ~0dJK-JzU&=bAY(nIZF{_utnLyQZxU~jR*L-wjq}Sfq zGTO-vxlf$N@Hk%i#;K$o*ai3g5GssNU*0!hDO`)V^F88+Xz7_4GVU#HP<_FR zD@FEn3u;dO`X^D{@~zlt1Xdx0+vLLqvrkTi0TGJkw3+4tMH{gbb8i`x*rx^6 zTztsGHMByR@CZD%yCxHLZ=-ag+>!YL?~e|j6b+_&S~vcL@O6LXe?T{9W~z@HV`gdm zrsa1knUlWHvu%_n#uDmKlKWbS#5~EA)MVmy3w=5M63N076@yJL%q7q= z9kH(kCr=g_N=x!Vv**-`v=1M9PR+k83_s5d@R#q9PP*`++|ab`NFjWw`-GiFJWxL5 zfH)7;(}!P?qHU0GS9dN)Ujo;ESf3=TPiEuD{0?ALx%{64Hk&-cP76yJ+a$ zi@n&em^yJUnQGZI@skn8g-z)iyK$XZ1|D&e^i35bTSMD3e8p#apLI;Qkv3${ z-sndr7al~vn&Z`stBY`GHxE>9I36bD-&QplTz-bLlbUr`IF~MGxknq<8&ntS@}o5< zw?UMBDDv!6K@!T|iT6oiOlL=ZLcV#8*_Z%pj%%T)jJ^T>Jsz%%m($PO7>;@GO%O~u zRDSyYif2f_ucHrTeG{cKBPY+Z zE~@O-fFpmO)!x28^V>{V7|JiyEl)AJ#Iv^K-MPW2)jsz=7?r4vE5MS$#K6-+_du&Z zL@hU3=>`95-8&+92_iD{VF;MHfWR~*n&wCb0(H@*1K8Adk^UVZ5c#6wd<-nnn-OkK#=DsDs&|3Z(As{&Cj2}u6&K<30!wXh zyhi)kdk@+Pj_xFj%7r{17kp@o%pDb51?A)=+pp;0?`fL#>eq%oMl#A_7d#YqdZ+jZ zGlI80)eo#WMf?0|NOdF1aFtD=gBHfiN&&4ZxyIFL4)I6Ll7d%1D&xzwxDre1`rZoEMLC)6Dvj+(RIX=u`Af~tG5ig1e(9&-R z;>lDR4Zhu{r7gF;Z&QzJ@tD_{*(vLO7f{oz8x=+gumRVtsbyP#%P4BuvV&vIK2q6O zQR%4Vv-AE6)%UNJv!wp%;MUk}U>#V5MZU_M^XX5t1jqZK@+Vhnv|nv*Y|?ca?k8nT zzmW{3=(*D5H+o`K=*k?vTGBlJ{XxD`r=1|Ny`|1wp|nnwEm@T#E;|_S$1;Z4Qu95AbH+KAN$!slX!+8CqoZQXxCfb=Xx^e*RB?{LQPD@B_C_3i}W1u zTe8O4RT96D({NzkedU8X0dm+*euS3vr+`n=wB?h%DuSlNh$IGx?;$$*(}psLQ(2z$ zi2PAGd~lkj+HnZs_-wKM zh=|1I{C?MxZ|(Ggnf(K>rj>>*>&x+EC<|+so_zL9qARS0N#ON!$2KN>^2EzJN@JMZ zFj#=?ZW`;WujZ##r`A(d;S5@yI{$EX4*M&^_$L;hpFE9pNykj-1z>OD2HwRTB~|Ab z3v?|Grl#xAzEeo`jOM}ihe-F~T*T7)@5n%RzB45A;gDQ-s*{~|iWa{%8kl10TQp*; zP^s{$82TA`&i10=)d~x%q6T`oqoh1oxROC8R<1bQ&Ax7bG-mv57k={n-+0lf=5%DV z+x|qgvRn-d`Z~SOgBG@>M+ja9%ou1k@)3RGIn`>V?X!F#BIm%VpKw^O;E9i=MJO~G z@hBg+)bjh@+vn_O-2wx^JW9oQwao|2Z*<7U%o#+-SVu`K8^D8>gV74__YG? zu8S_zM%E=trH-G2cqek0=7@W7y;hpm@$HM|bo1dwi^Wb6+@gdZZz^PDf2t9Hm%G=> zJEf4v^9eIV)Z_1n6ev@Nr|3vLzff-oy;Ll+WHpmbqIjE~#_NC=%J{hQ@ss*uO#$I& zv7V&*`(JpJf79_Lp>EnTUKBkP7bFXirdl#w2~FD3zIehfXiH0GP-Y`kBXCi7gij9_ z;2vI%0h8bfXxz4p%_tthfs2Lb5y7`LnpN`R?~LL@9{Q`1rajnhqTx{U;{C=eTd2d7 z(;`1>7am9au_%b_HK7$gU!pClv!Sk~PwDw@w&sZ%Ql}`#5A0>QwUaDU%Oh%Lz9SIf zSV!!L$-@y8%iBgC-LOz12l&S3Yav+nvuQvdjXCGc3-gZ7!jKyha^?7*B7CcoD!aLy_@ZvCF#>7_LUurQ^P!sS^9bPseAqNdIh_spfN5A+rg@6l_fkJk-kM6 zj|}6ZS6(ba;QNleiwm`eSge_^_4{nLtdVBFV8I^;ArnN`)1*Cpd3XHnZ^hD~2B&)jX^&EPJsS@uq zUS*AzB8>^9vj3dF-U)aPhvDnRcif9}2={b$@P}G`x z`QYtgh%rHJSSIh~Pb*Ou&xLMKH1?uJ57^L z29167s^HD><=2ptp^e0le&q|KP-yb8eDth3IV}?v3(uFSO#GP4;r0Pmel8DsmpUeFH%SP2T9|3!)Hckv&bPn)aX-|7^?8{F?8C9oZzVz* zs!r*8w*+dLXjlJSQXT1Xn(%HUg|ProFNF9`*Av%pDBZlOo9`mSXKVjlan7kByHfGt zCsKl`OFwgtsY?F#$J%dbX$LH@vn#y08Lq5#6<4a8q0dHs*wEvBh+YGenX44BiU#=R z@)}D`5cz4&5vXOhlWaJG?VP|qe>Am+o7148zdoG^vQP~lj;V6)PI0lSesEu7P_35Z zd}^bJWsVQc9D4KA)tC}Hx0z`7poGC!KBHEKS!alY#VA^ZRer9#P<=Dtu!>z83;P8} zf!?hO2_19!Z7pe66fjucVxq?`JHN-O`pHUI(n+TeaV0zVbm7&skw_o(hs}$GpB0Yr zU(VDA*&SLBAKDOOhn$7;u|$*;_J5>jL~5cIWh z2TPjG)MuW}E=YL$&*hk`=FkZ^ud5ftjK%zj-m@>FJz!#{a2#!@Wa1_8uCW;xN0N^C zGng+f9>mv^kYs*m8F{Xg++Zzc5DdW?A+{#N+-m5Pmfgnc5;oYWPt= zaoDR-YdbsM%CI-9SJ$eQqdJ!d|vDc_#0Vh{q(k=+Xnrd8PTHdshCV#jG`pMgwF=EobfTxSy50aaX(# zmaAG2^ z^R_UYpfu){hRBaM#gBbIEGlUt?bQC(MmFyB_+UuPj?^ z%6E2%9PCSI_{Fm{1b^l|!vfh4Z#Pvko=l$`R2a@SFL z{&@JhbqS5J0H;i5r7s(a-Jk%O+GXU1@)Xe^M0JbVP+4q;a~L35ViK6zITL zs3wv5r9fk+-!3@`y?^=Cv?~!_W;!_`lf;P$H?W4Zw=$9Wm~ER4|7F6w!6UqMK_}gt7)Wq{QLZ-jOiCfiC2C~}L~idI-v0Qw z{+fX#OQ`IvI&YL&j{$+rfa6rE5m9G+)U%_@6_$wa9y9t6Te`7X>O=dpDc=A~8eVza z>zR~N$>n4+PO$yxpk4R#BJD}*MHLQnP6=A#hH3dX^;7+DE@Dr;ZxSkAb1DK|Vpa=n zQK->VYl3?^1xXUAI9#E=zh6`6Mm(nu?;A+0$Rv2E5;?U0*nYwzgP+prZ8M|4=gV4S z=c9FF#q?nUt&<+c zllWN|Ut25h&C3DNT0R|l3&B2h6@4o9ZuWJ&9?cKxIA10sdTieH^6J%e%_mmW2P&+I z{#WBbY#b(bz`Tk2;Q{ma^v~a?){>qSX{?qFxWAeqb5o@6sqTu`Z1zgVIt9r z#N2o8Y37|ve?kyTa=mFM8Np=>^k?nh{fdv9l6<(4`-~#rc?9u8ahblC0@J2nF%<83 z?wCIixz?`^W_` zZDsbpMW}$@fm@0k#7NG<$n^yetW+)bK`V_x4c~W-vfy~R4~1U3-zW|I(wbA>ibbNc ze|-w;vw#$iHn?3y5HGQGfpvtE0c-F4y!AyDj23?;kdpL2U5#)a&v(t~&8l=Av>3F7@=g2HceySvAv@(SZ9?s4<64dr?m=lFk$NvuE zOHD!i)Fq?0=Kz-4qNl9wC#w{fwEJ2*dX1*lNM8l&a=xo>c~x9CRa|L3pmWpA46L6C zD%9ppnw_fq#!e~V`N>zN{z;hheXev(n8bQmzN{&Af_+ld!@x(dT8ExTqAkoev~T&| z3vrRD=#fly?rWWN4CE^pcqx2$ETry+^Zl9+s+JNA}i@1mM4;wdBqzXt#8 zxC`=*xuGNX10^r5#d;imIvl* z1%;R`3)`FI%EpgN$b32ZUau55sr+TyYI7lY!w`$Re zQgVK)vpSJDl;j)3Y_IyFf)@N&r=7985ym4}34>frfpll-LZlhNosZc-;!UTN+9WM{ z9Zn)m%El&V>llinDT&2X>cpkESWsok&&(&rfrv3l<0&GYxpVEgAwr`m_BU!UvlN>t z+CG=C!Ag3Fu2CtgmaxT!Ko!z#vBJ3&*@lWSdY#W9qbWmI^10ShXap3(?t%Sy)@mio z^x2Fv(4h3NRMSRzsOuPu!I!H!!m^sKF3{0A57;=_rIor&CRmNftpY33Q0bUb1NjLZ zW%YD2Z&h*F2E+MNm@SPS2Yl3O?XM;i5FWCbAbx$ON)an!vSgxV?5m|E#5o)>y>bI5 zPY7<^Ss+0ozmyEn4MrO~!kXRKWY3hSHI9_s z9x-leS&dpL>-2gV*4-4Gyi!tuUIVaU71pwf;pp6o76yY2=qB;3P*=E@W^44@R5sGt zUJP9rhSjzD&scoO{tMAo!?6s;?q?N*9mzggX=M7V#uyXXsDhSp&P=~pr>7&5gpM%S zWksWwv4iYiy{pBd-}kDKOgdPRayEo$PjZ`L%@%mPqlURGFXgfZk&okaTS8h97)pj6 zd8sC*Ovex555i&@-PLt(YD^g;EG$RZ7^Hk|VXe||l@KSd*m8(9w}nXa0S0;du(s5) z4lLP*Gz=VL%La@3& zxz@oacoK5%iJl}W)A@*hHF9R+jQcr+pRvZYk6ubOOe>Mn3^%Wh#yW>tzMo??;*N{7 z#>tG!c3QkA&V|SjcVbPts}Q~C8qW-gHZh(sI$BUB6cbZ)z#4K+RRW0)6q&bB5Ys~7 z%L~_~hp`s3DYqn421a5^Nd=DTG2)F0&!1*r&b2Yu{ZA2w??fUUAfQ>Q02 zF%B&r-8P?182U^oJGZO-BkQB|z@WFJqiN}3Y*K4)Puqv_g5zw>Q;+Zfsz<=%q+eB$ zW!V}uGHb)`DD=9Cs4`h-DvAqdw7f4vDJ!*<2|uR^JrLWyh`(tFmCr&}OCz$3t-le2 zRWgcZGu19X)tne~KwJ0nIx?4meWI^NI&}3s6i5kW@N+7WH($j zb*30jdlrcsY8~j*&N@98Of)B55J*J0T7SWR()L(dnP?eFA+m^Ig(Cq!o9m|pjB+>6 zRkCcgy2j-YLXmDJ;qJ3oh)X+=Bu8z^ClEt{Jls62wZW^xhFn;Kb$a1WOQaCxc>NRY z;FKLDi1zkL@s0Wq<6icf=m!gF`?bFc(fActGUw!p(VR{@JF#MYB6AoRU}Nct9Brd@ zEMhgU!3`KMJHs(J6AolZ6^T_YO&6;Dj z!f0@?Mh$Cahg`-lm)uALrGj-1R%x_+w3?^r%o?1kw(TA!3JTEju8fD$W>%@o@&p&_ z08OLppKyd&5}lbITdi|ynh)K#@tP@>u9$6cA&M!+QY!!f6T@9(H23RJ@+>IzMQ{w)F068;> zS`6_VYcJ*}Jq|m;j!Y*-nxSAP>;8Ba#vQ&%wCNT_kjU%-1J|X7 z^e|?av>sExK2h*zcYvEI3LIhS!g30m;%EU@4y$uW)w`$~m+(Rcp&+R=#6I?l@1zm# zN(AhIkKJ4Qur6>&esFsllXY*zi`vPxX0pwib2$QzHltQaub!nV9dpr0uHnBm=xO(R z-p~8s=x-3vIX{SyJoR-NQg8J+X@c2ZOUFJ#Cbt(mvRD#Wp%7DWz?P($n6SGsqeMu+ z&VJBrafXVa!g;?s90_AqMy;<@@N|X@Gk2tpV9;%ll8zlnbsiz^p-6zK((o2{7>Pt3 zkcccJAn)@SjI(-aveTN$=b}qM^P2+{Bwsur`}vuvCNRH|Y$9=&J7NEF6$Jr-EQl4OrQFiV+8mS5R?|0t_q3{K~41Bshr# zrVRM%)m%Wc74c%e0gdO_l?vfbuXtCm7Od5Q`k~8@XS%q=@5Fcxm?6U%4a`#fgu?@v zhLlYh_u17b3l0${h_lgMZ*irOFytD<5U`4~9*!wOJPDJRI^7|$CDGP*_|~p=+wx+a ze77@jBm*J5uk}@Gz}jGDqqvGF_l~P5UtAT*rHhG6gT1gQL-cD}k(5!fhS(9b5kncr zlX&LxPKK1`)p$*3vxt z7Bm!q;~~)tlD63~p&J$G6{3Jxy$PzYXYQ^j19dd#{X;#}s;s+JL1H(lz3G^wpoVq? zJ`}3FXQ0*;j&-mu(G%~>G1DwZpjoKL=>&u=L8HZz)Aeo@?Iz}ESn0gQ>B&{|;7|%G zxrCm_3(!?ujk!eGGPIFH5EJJea>yy^vK^@vQ@&xbk_gKx$}qJ27$^_KH@D#Uw}N-Z;QGuO)D+^i;HL=;tq5OeKv z1a?9hJ9Lo*$EB{yaUxJ|RZ=cFss*_n)+||%jGeuD%sS$NZp5p{Wn!q2P|h_Kbb=>k zc_(<5HWWci!c)#Jm%jW?OcSX_Y0jwL6=9trT{bf)#)(*>`6gf`&oQ>qTaI8qtCqk< zJoSjQq^u>W3QWV)T8A`4pcU4oZ=;r$o=AFZ;F^EHFGA@!#I9c|Yg8K&rDNUaUJfw6 zap2XUr{surUj^tZq5|}0N`UUB06j}+IRrhie_J7;ic3oj8db%m-TkFFO|Ym2g^wzs zCzc(B#6oR39g8k)K`ckcdS7w$aNju_@RpBaQMTx*7x)57X~gFGiix8-g1iani%k?_ zf75K#QbEb`Dw4I$0vyUm$$Xl*nz7PQtfr);h#C>@h}TZ9s;(RC^5*t__l3BiRNAqZ z;QBd6keFLMS4cde3b6xM3u)a~3uf(g=KNaMGD~8SV@x{O?16RV>rmPJ9jVIrGI>no zj3JK9^ERx%PTpm3$Fp+aCdE# z&YY9i%JWtpSA8u59j{PT0s(GULW`w<_L|)I7~)vn$iUj#7(FgyD*-riO(w5OBYB?p zsubu#kmfZISe-f-PI6);6Y_%=g0+d-!J*{;QWAAkMy5~qC=*u24Q`}sl9y%bGVDXt zWeS~euLWdJkah(9f}}CWh`@8YJdBh=aQ&IL({sYKx1QF;?2xBnl2?+o2w#z+_oP4z z6Yb^Dasuwp7^a5#g%n;+q$g!2PNsoh#@GUVIaIzMyIf+y=mqtW2&Jn>#wb^;Fd#j8 z%V^G_ToSWPh+s1|3T6oWsP(WBEN0THAX8z>iv<|A1G^mTvj`v%WM6S z2eZ#FSkn>OZ}N8ir6<#R79$0&;gTGfW4Okmt{xkW(<^-JqaL4Pz48mbVJ)M~2}l+d zFzmYnV=La$-yR+Qm3Gb;mg#Of+q&5t96k=r+dp2kq>I_E;|`8Sf^{LTO|c}yYSTUT zAY_a!#daVlTQ;PfXIX9~jUO4YZR5!qt6A1HWe|;Ux~M>%654uwG05fE;+UmA3J(lJ z_b2_QOg6mmbxzs}h|?pWybQ$Fpn~z;oU~PFZEKlGJ;d&KdVjpl1v(sEPeG5oGSuID zR`tm9rk8M7>P7y+L6~@%7LJRtc{C;~GKfXJ#*#Z-F0DrDU=o~K*A~N(eU_-`w7A0q z?H_j2LigrcmrKs10}o9!dGy1w!stblXj5K$`K-y=#?UR+*yh z+tTR{O|hQ)F%00gpn!dJ&avsNmf<8Jo1o*A50tISC8bg7qr3l z74}?k1&*><=F#k{Q(&s`zS0I3T@~s`)IX)&)86IaKDSKdB>M1J@DNqqq+FG6%mOg# zCA<6Hj~rlZan??~qn*Z12Yz5%RIUC!*3x^bgA}vd@q$$Q8pZ5v`*w9w>4O>^)j}~X z>kP1y*XU7y`wB4HSu@n^l*p!#>xmjGQdU!k4ghA;bZT94dB>=^4?Gt zEk#Q| z%Ll&M!4VucRtN5khEzI7Wa8mO9`U19UA~NhHCs^zWAZjCv#739UmbXhAZ9o^HW{CC zFG8vE($1Yn-V?RUVHDUfN$QxgY7LesY@X4^TTdZD$Ttt@YYq8!xFsuTDaYZF+6*(% zrh9qlz;v^r9zgBd{Hd%R;rEQ#osog?9kE*Q)Gw&?7K>%{c|Y;xdK5Wx$#C#%ZA$A~ zCBXrM&@fgnHI_+TER{6pO3auwcofbDWb&|4oF;@&Db{1Hn>H8GO(#jaosS=t;gW9j zxw>K4d4RL1pILt1URvsX#DA%|lX+pZAkBMAaT<87*gjlNY{{UajAR!f$;^9L2n|xw zIjqHl?SHhmFd(tW024jiTO2rI|1!3~Jx^=@jBQp9A?bm9_yRMGteY?z0LE8;01q5A!5~^ntL?mUt4Q!T^@eW9J;I+hEG&`crPH zUg4VR6{FUQ-cnLiUCRKQky|n)WcLk5%T64>9PQKkF(OKkJIb}AM%w(@t(bsS4K|Yt z8(CnROMkK7lPhk_w$UVa=r+CxSQc!Bb&~dCaUZ?w_#_kH?l^jL(mLcja+z{8AYm-&59)z#!*j1os|uYI=J+ErN?fO zl=!9{Be)(Yjc#J^z|W)il1M2zk$b>=0Z!1PkrX^$M@KAwiZ5a-!6CNpswQEZ`=bET zF$LKT=>vr7Nq>k?M*(1CQH)EpLXx3LqM**KwzmN`XYKrL1hoWQb50boB6>P@1vK|* zpA$>&C_jtC7L3@3m+YS~c3fH-=c!xQU14P_yShZOsxT6-z81qR!`>J~^2mvd23Uh^ zDa0}bhWMx*+!|PWpaYVr1p(?ia6UEZ&b0LEksfv{fptDEH-{p{f0&ld_ZZ2#R>sk? zBs#R0`O(DWwTbSpOTwVRw1hz5z!1COA>R?@iwRSGGQoE{hXkWr20SY;?B$oT_l#X| zm!&~wnw+MHS?spwU{pIBDI(5cy&_WpbQTaH`>O%?s|j%f{|W*%crOjz#VB+-o@vad zRaSIky7N#~$wA&am$1q-#PQ;+dSIVzM1332T5(m@_rZkp&sQ7iDUKi8bU(hJhc+l* z0Hp~!4L6dBJy|CQN!bK2)N#ns;udj%MZg&SUk~@<7yiZXeiSIzn+=Cqe$L0M8D<>h zHBqbTGHTKJk+F%%OPI2)4y6V&Trf4ZoDg6(HncRDs_m|(@c2^K3QBENgN7iq6797i zGaZ}Y5;IiSc&_C>rOJeb!Hmpty;XM>WkdpLHKj)~^{b*-0&|&Q6_#)z6A};FhIU;7 zvZxe^Gif+!G8=_Q@S#KYnK@L#l|M4HNrG+TIyZ@1aT?6A_nI{-&Q4FkAYslbD^GkTGE#!HCp1^LWdr&L)89OfNx*0T7)w# zHbjxUA8K>czL|+tcM{X$bBO+`Mkw8b0D^3+NJje6k$f{!+i!_|g+ZS>Gm%4BcIgJO z`tXX7g_toP=?G;JoKTuxeP~dpL3RI<*a1>{5jT-k8`%C>MVuew{I0mYFjqTB&gbX2 z@Y5!WjevFWvhf;0DAboW*P=p4pOV|&h%Iu?j4LZ`H*58v6(rj#VgVZbI_R%cq~2YW z_Iu0qnLjL^h*VIY1myB%?wnVfRFwGy#HYEM8^wsy6X7HldOa&NEfpw2b1)vA zrWpf_DW&Scl@wU%jtlO6LgPgrkR(`z01bkD`jn#tx7aa8_%TpQ0adWE;RSA7GzP~~ z?XkS0)z~fZgEELawPO;tAcbIqlApL6C~h>c zZdJorSctfELJ9;kMe}xTvNBrC?rfr`SyJ!m76bX-!mXs1{*d*r)>hu7Fh4`Xhl(rS z5}e-@9HBVa~1}xI@qsNWv7yibIjzRY@yMdv2gaix__qmT_o=`TP}=pk)v2 z`wZK1R8UM3f@}=T=`hTl&4Lvdkm7(X80b!JEa>xLCS5qH17*o-&GSYqoi!j56byup zuzsjIrI_Aopn0HP{vwuAWH{w%+hArZwqHEx44K9ai420bdxM4pWmRa}i}z6TI&Di| z=p-NLm@%fa)g5V|bbu!zvBYV@6Dt$#ABo-)650D-ibxsN)0CO=Y4F3YO;guL$CQuk z&&NKcsf#@hgA(!#xDZ1tfq#=W*~o4ZULJnBz(PVBa~b4{`E5WZl!Bv2qI>D+YUC@k zg<4Re%Fdwg{iq1uZ{%^|Kpq%Y&cG5>Ou%uP3(>0All7e*^bbqck*v`_%;c7CDCl$5 z;xNJfLR(nC#@Zr2cQaPaUHu?W1%o_S+@|;}RZQj~u1Z=$fg0A+ZacF%YMCm6F2D{l zmI^2b98wK^!yzW7hb{f!Te~L9Qp5_0BI<{vV9&Q}V#GO=gz6}nu1rgylx9q1( zd)y%@%O>$X?w<2kO#todlLKmtRt%~0&SOQ`R=}3{%hiodOBRFL&9YTE<DgDxgKqS`$rQ>{Yo{?wI@CuhFGBL!%4wcbCBRp@jKBir!c+C-I z01kmVjqK#JgoziUH;A!?Pdbj8D2hosr-A}0EO8NV9oJTD9XTjwda2Z1+ZD){Ua@lb zukYYU5WN*p+ERfZbm<FUnY^Znr-#&yO`O0&ZF37=R?$4QhuwH#F7k4Ktl}+JMj!D zK=LA<7*t9s(a&-b6wLVcSx*{>{hX$m^D~}o=qI#d@e$V?;$C4i0Von;p`5xz0*}SsXx*FE=-0wmOki#{3f~ApK2LWjDVcOCLXKLmNhRSO zN^sk?wo)?pmN$#?HdRs<=U{tnPByB%L@hX7S}~XBi$umcHIp7|g|;F@88@fvVoRHL z>ni8d^~JXt+s4Q{5#|^HQ>@J7A{_lvCg5Y2TqQi7=sw{NKXZP{wO|17xBx;$;2up2 zS<3)n{lub?Va2-3H>7Ct4#N}?PY1k$uF?QS1CnK2`GBEX!tiWnZXYrb2GRA&s$Sor z^he{TaRG{N;|n@BST3$$I@a5WhX>&4p6k%#b8E1q08l9Fl8Z3TqU(|-Ew*+DCT&_W zeT(B)^bM;59lY|%q-lSZYn3c~BRL9=Nn9U_mt2Krz5HZ{gkgl>)RVzcko40&QNE>9 zF`}6qw(NCCq5|Jt<*Tc+{d_?bkpbeMhU5>*xFlc?JKtT z7TbK_w%KkFwihAd72;Xzut}6fe|rSQHjZco4jy$EN70H+5}1;XJFR$lsPh_UJYK95 zvO=Kp46BKYESdE!HHZroRHo4S9TL3`jVIi8z*dVU7tiAYf{~yJ^oKwC>#99U8L*5l z5nk!Zh1IaaS{lpeJ}vuvr&afzB}Iliq6|$B$2V-)1mN4MbTk8 zW6P<<#nwq_5`Cx|Cp||6>Pkaq74dj>>`;NE%Co<6eKBP`^O=zvj4qD#W0)HB_=LL- zCcBa%?De@m!J=I>O&|VFRRvKb(xp=Srb9ACqniWB8kV`TPE#5Yf9EU?8FC|k2%YQ` zSNB?a!lm47&7~QU8aAFX3eH`_|x8aLi{ z`Zg|M+KLN`gb-bbdR#>QDxt4}fw?nP!&_M#p>C11OwY^TgJaH@5(z;-HvlBjG2P3EF)|uXQItMqf)b6%1c4Z4 z;#qaAL*#%35iiR}bG&o+_jPrmFrqh^) z(@96e>HM$Zl%B86?(=tS(@|G%S=ElcYUxN-n}|SGTuC(qFDwJW?((B;xL5foF>921 zTqlq9cqA=@=`pnySqWj!KsJyOAch0%)=@QQnscgp+B$B4pw1#BO zTQ;)MD-xE_FDgct9X(jH%qXLBZ=@utKg0o0dLsD8#kIO4h)I-- zL<}KW>JT7;m_Oa%!*Km|Yt3_Yw1Weih>HT^^qS^?nhX(VX0pYHE=tY3+M`I^J(k2O z*8Q#-Csi=KhCb-RhZb1~qoEcx>J8*E_qwpA`&ES?EU}g&QxDp9DO;O(Ngd2Lrosxj znnpFx<>uwQ-MwX`yWLUwQ!Yr2%!fG?6rPInDNI>B`<_W}f?LndlG~VzQ=0x%d@aHf zT`e4&CpyrXtvmdljGa=0Q%f;aWfx^tAAzed9p|eslj7FajQ!Lvfwdf&rzF0L2`Omq zPvp$?{@ef-^p#k)%2u^<53&WS^%?55r2GtmUye%`hd2vey(4+VD#X+ixzyaz!4uv3 z*mO43)-sFWBkiaQ5Op-7Rg zPJWUSF2yaSEViFnxEv;hErg&9= zJGmqXpvgl#lWcGXwIwshN;(dJ7_Z#Wqd`!t%F^l|{SbNR+c@w)|MIaxU_L&`uP-+P zO+uJdHQM?}f<#E9Of~vail1x!mQ_|+(sMI}KWz~qczD=L@fVvnc*=9r>M4Idp1rX6 z5=w^&Hb_Og8Coo|9iaQm(Z7$7U*L@1Sd<*o2{S}iJZq>hd}+OJI!{Q{O%6QMJu8jF z`TI3U)iexkE!SNa+xip6;(m)Wy*gdT#tBz>u(ZU)Rj^bn@*R=%?+Rml;pdtu%RuzHC`E73kOZPFk1aK# z1zXqb;iH>TkzDj81@<;|EQ5syM8nJ!%3)0?n(x`0#yAYZk(ssU=Yrb>Q-R#C8__KL zV-p(=lfJ{OjDupea#2!qZ$QsWjDM1=j?$D@CfWwx)ygB5|qRep=GUWUWmAR)7%Pl2k3?h6Z9*?W< zo+&DS>3b4@E?w{7k zv`-(%#FWHWNU|Q$v0uiYvEbPc0|M1QU`M0-;NJOYPm5esjf8|1iBX64a@N8-*RdHv zcA~=AYVzj=Q5Un1D6KKy9o(l)bP0%7zKYqQjkSCc{XN^15s5_7v&1R2yX@T`0Ww{b zAL@q{Cr!213uj7NqrLu(k5sGx&(^%%!?(js5)mk)5%&@U@dF3J2VdGU2WE0Y53VnJ znQySFOTid-ZWIKCSRm`!*j<&)C;}3*e4X)8*WC@>CIjd4H?q`B&=J-B+iL5>(g?oU3>}NqcVdU zlxdXYR2w6GwxrE5mKa5va`bho3r!x#iZ-tR6VXt$GxkzL50b9BH@XsRhMM4ny&PJj zKhuDMNTex{rcX);Sg>^wxq6yh`N)$A1x6&qXBC=yr4GyfnwPC!OYz15vrzdUBAicv zGAG0w7o4$SK`m%>pYMmr0)YO?=q!PZEj`Xj^~rX0U*3JqIw_h&WaQ#o3xc23z}zG< z(#-Eu?~y*h74E;&rV5)tTLCbYi_2BwuVJP ziv=F=@zpZ)^O7RGl=q<-IH8u)^)Ky4g}Jgz5F5Fk#2MiP8^oBvcOV{ zHLv)}05*)7iu7^du#e03kW#t0xux2l5Bwop`4Y>oec&kR}W}%gY6abMu5kBxq(T&NN5hme* zNsZzpIfze+|5;M3tj5##)1^~nPJx*-RoZJX-3P!&m2_#$MBkdW5<`^QW{X{mRl!&V zM2*!O+K{S3`mVi^h0SM9MCHSZC?zADPJ?N$R(a$h$PGpLJ2qP5WmwhGU6E;^hJ4Eq zWWdBa%*~AvM7NuRLh^Bgq3mc`;4uxz*iuK`gb_$rQx5BsP!kh?G_)8~G}qO6=%R{3 z+~+fx=#bDnU{axzusT5pp7{Vwz6+CYRULQbB`Q>}MW%espvz`flszpl)RkeqhNN`{ z)+;ijLszW%IO7MEq|IxE8D=`{7a&7~uNo2QCXmny7wVOotLoUs4Mn0;(jo7VL%La?3jJV+L%OH z5a%cWSXLpoq*E$~*ZDw0hplMTXM@;u79^g4L4k5m-y8*;&ypD|G@?i)AYi z13wcZ#{(vqHIBxZ$hJoaX8pEC74Pw_i7*DBSUwdVc0*cN#wk7)(lps4F)yD3aY;qO zRy1KmD}(_rAJ7lz9*SUUDcZ*Dl;sC0pe?1~iu&}s7?@_I;C!~YtG2E@>d(_wC!6EK zd9_SHX3qzdX?%}XBD9Ikuopz@)4$@1K8Y^2?h`CJ42h_V5pRfg4Z!8!NYWJ zgm7FTq0K?@6-)|2FH1dvQ48NH$Y9C^k&IhGVVNi;gcR+|$L6jps8Fqa3Q{}<^7^vM z1Zy-{scHv`5M?t4F#(Xp45W{M$oI`NZFLm$3IVq%ImmnpMAIqwy85P%c3nnNj>i{`2=Mns}$ zaBLJ7#u5%`o#X=#IfNP|((we2ZI~%y8Xs_?2W02N)b5)M;#G&e;z~y+c&wnZDxWDY z2Tqik^xs@`Zh_ow5~tQ$*=t9JvUXTfz_cYc0Gmnhri`nw4#<8?#1RMS>Fk6a8)O)@ z5GfR{APlzjwGTy>D>({W3S)?~6h1-~J>eJQJ?i!GUj23=P_D`p7w1Y$5KgG!m`KIk zy3tcK_Hal!Fz*Pu<;YCP9TGiqkT#|mTb;0a(z|%-*b)pA!SoOr5mHr+7;cztb(jF? zAKwlT0TQO@+9rS$vHCa*a-Y`FN+VgkRL5I?Bju{U79rc22n0>2MpRW8aleFVtXWuq zNgjix=|JvAM_UO5;-Li@$bt6T0xyX%VdEZW7bM_~<3sjdqNH23L>68gQd?vw<_Q_D zg-w-v*>jDz0W5M;LDfXn%^Pc2pZOXWDYMy~68(s=@+HxLt9&C2D+m!bg0aY@DYZ9g zo0c6EhM_Pjoph31_6&UVr4|zb+c{XpoT)Lr$Jl9j*`LB8-O&f^K(Xd3*Ki^=2-_ z@nt~A(pb>K)L_3lVXlo^v~dUxwFqBV8xbkTNWz=e;mGuO$8pLOPNyk#g+;1kaBekF z6zs`MTw3QP+v3;sjIX(okhjH+^j_4bR;%Njjz+Mx6rd(^#$`5Qix49>W{Er@&#FnO*j9GOV&a#NTLgCJZdaEny-j z1e3a$`}DYP4T7BML1>$Cn<$B*xSsdl@dTS1Gbv&U2U{PSgcMH zS==QMVdh9y#-Ig}4M;PmSgyWn#dcGVAtX~gwy)jLgm=Ty9ise7(psPET4cCM?4D|> zbwBHt0)t`~B@Csqt`ABI)t;LM4S_9@X6R><+8lj+OgJXm#K`}D%FYDN#_E6k=Xvg( zxifQTE+HgajTr0L$r5H4!!To)F~eXm_FamSUA7kOd`p%Rg%T~=wQCcSB@sm_Dq8e^ zf6jTHJNMdNzkhnqInP->XL-)PJXbH%%c!3IXS^I~(HnQUVKl`1+-(cf>VV zSQ&{0^{7yk@*~Vr)udzRdF?RcKviCp*v=|_L4h^ z5e?TdM={(EG2uw?MQ$c@`y<@VluO)933DSc4-dMb8(&6%j)Uvk*?5z$CK#!%LC~XX z5DeEf2n#jT@Di^kM1}`+tMb;IZZue#jg7dvyp8yg+F!_rp!g9_8I~bQ+=rUha(@W3 zxE?rL1=3xNF64h1nNi`;$o8{7EH6b&*77*+z%7puBe|kQv6s&iGXImlK-w2Jt@Q^m z>D<4GS$HP;s;2k2hSFa2q|;t8hMg*@TU1TW^Uj;nk8Y>hc(?)EIsiIkB8 z#?dp$mZABY1s8M}w8)iJ_xKxUGF&YuM>ygm(UcR-P6jwn=E{z1=L&Id_EYj?PoH}{ z`eUiN;~kZifBASS=D{S$FwEJ9awpL;{pTzjKesFgIcWPN4bo@^kLXEuISG<Xk6F)lHWzgMOVm30(FoDA4;4&WC?Or0Sa=#lg5k zPq~{!xs(B_vqt2`qZXaK?6T0UO0OrPbrmMAvKz)Cvy&NGg0s{dZYFnA+_fmju{mCO zar9)3Kz?3~Yjm=@Y#zuaGZ!ST&K8V}x%us3_cic}D2$BcgD-BP zOpkA5%95shI5xOrrgtG7QzuwsB$HR?cg2Ha9R^p@49IX>Mr1Nc5tU(&9OyJ>pS6a* zd!RlEZJrndx(5cGAvc}G#U^rLOU@bwhvobzs+_-!qX)i&$<2})f zcZm|iuz|AG;?{7Pt%s(D;b7=<@sg_>Sg}cQWJHJ=y;p+7re7XOWmY{%^55j38J=p#`U@HS3b*XVYBA!|(VBfb?M!QPdS#`a zwUxA>-00JP`BfZxO72F8LYqbvr)3?l{|*{HQkHy2jwf+q*R*jiPF>9eIkntT4kF2) z6geBvkvE+r=SW%QY{;4~e+S9mMhyC?!}4d4O5wvKUF5qYDQv8YH}MsGQ0-H)OvRq8 zMHW9|)0uI!Y*%&UORM}Uk?XKC?SPrfUeU>=o-rZ5T8>I%W~xKWqT?$VD32=3tmL)V zzY&<8)h)+ab!|UtWQyLvQl0huSYePXAGT0QSu*ZdNd;4Km=7WX8`Ys;a+0jiiAJ?! zDM^PT!pOA(B^rhFl+J0{?(cdh>xG1DW)%e^FEUI@H}MJi+fBZyn<+nokjy%9u}+&C zQI`tO?MjCzGY8{=iZ(^W6EW)Ek!Wwc%Sga!NCCK__I zQ6)?FmYFw#m5PB{ueYv)$uX&GG%(Fq9cBKBWtPm9bZj&8$W;z#q-W_X5!rg!=NhW+X{`2|zd9C~ z?7F1Nm!Ohl(38Zkdx=RBjh^bnO;(jmHEJ&(W+#ksVr|MnDm#bTUNl-{W=D?TQYdn> zlAI9VaS*iD@>cRhS27X0h>Z z0j@ z`yTq*55lFfaBHN>Y(FC8AzPN72L~-XGx5%r%}X7V;FDw$QW=3G+zCa;yI5~h;5>&t zm?Sth9rekb%&rA8a#!gz8M+8$$~wJHayIi&mFA8bwBO*95$lZ0Op;BBlUJP?YR>zS zC6zF$_j0mX$l_|qPq@vN=*2v zj4*e}yreLL9!y8V4B9!rE*)l=!DuYZAbaE>KD0A1JkyeCq%I0hOq4dyE-VTrOdEtH zRX07!rZp2k8SKjI@?iYx&nr0%pZL?eAYl>GMU85&lDcQ;hANxVa&g`{c4>^Xbql~} z7G)JYTo6^0(&wCmkcyYg2C{BGOr;HTo#iakWpl@-py(=A%5~XZUmLpUrKV0AHJm0V z%)eVL!wl1g%lYxb@Q_@?VEz`jTw$Jh1$32{Nkz6?N2JpXcP&4a!5(!!j&)DhZC+>{b1gCpCtcP9Mc*uNhS{db$A=3Cn06;rT&P8I z-iQf#V}n@~YLz7SRf9HN8NQAUFUHb@g@s5#W3sP*Ng~X=W1du4>@HXJoR3OQC@_{O z{IqJGQ{*qQ3X|Eb`z84&g;)Cwa~KT#GHd2 z*FCrBRlbpw&Poa25SHGA!HMcPa-ivz%&0Ng>lgQ|xsnp;2F99kgpRdJH=mGCl6^{J zlAoTHVm4T^^>y5A=gfY7J2OYm&*E!%3ReSVNS-g_x$cEif<-AmlO3#)BeLWam(+h^ ztA`HKdM43&%E--~W~VS%;{2*aOm*c0MT~(4PVSsvtWRc5pmV;o#Hwqjbd}1nZaM2< zvTLl$%F$6yl}lIdLnh-X*PP*n%1)BEz|hZ)KrS_(kE=828aUY*QKQS&%a*v#9>7?i zJzL$t7_Z%(`yQ&t$T6H8YB!-UnTBhCZmZMMGDj5*E`Dw&KO3QK^qAKSUpvYIMDcil zRLLSjh=bx$uoW`os z|IYj=Rj-t3nKU}GerMWaCz^WJFtJEV#uz7HnF_NenyhglRCC%Z<+N8<^eKv(SGz?NV`dwRxuc;5yfOTKZPZ^Y`rh6 z%{uW@Ra!#>tw5jS9Fr|y3K=W2-n5=d8a(4)CQ^EYE^W4%&rnqJ_+=?=uUm5Yqn8UN z7MV#ud9Tg%3Y~Fi=EhDS8LTbvk*>TVVU2AhU2*5(YU|VKa0misK9Q9MLFdMmoOGReR{) zHP`h+zOru~vnh)OL9;AbPIjJhQr z@+GpFEG!o5+B1L*f6DDlr{+2oYr3tAedNkakM^9gnyyZ&2fxVGDbM{u7A4;4c7o-{ zeby1ynd+M8q98NHP*au98;uN=nH|s40!S~c^UD0q zk(JW%g*5%F?HZ$GvQ>Uw0WmDtU?&SxMsZzZ!YGX@;e2%_q-nsVvt}eQkPw3j%$N9e zl~Q0VOlirk?Xr_NM z=~kA6d+-_g?tETV5wVF{6AVp1>GGjNG>>vznYzRMui?g9-gqt}=8Z_=0+I1f<<%PP ziQ%~M=QJx}gR;xtHC$UIDPaqMxczzme$u60+_mEGzI~f=z zE$AhO72l$~@To`pB3`32cv5<4fPoZ6a%_ErU#0E$?o^ZQn(WD%jGZ}ovOU?@*|u%l zwq4WYcIIRor+MG+59f2v-|(!pF0AWW_XMYEV))ct9xS8*iers2i zRA!7y#V#Jtg|!4`8p6*n^IAh+=eWhaJ(6+$5Vg_X6;0`+X)Zlv^^h5@Yozv*16iyO}{i0?+9XVSnELzTB z=v*>()=?02@nm72>#icg=9x4~OXi@SOC4>KM+YY?GQ>4c={q{02r|QniA9$w*TuIx zYWA3T%KO#^@uq34&hx{brMN~skY{1A@;XOem7C3eopz2%`p!)lF~m)rYai8I>`ceN z#Qu^rIAWh+howd23(arxm$(<(g_TApZPCcq^prbwOP}=~+#y1)&Bk?m179onF(F|cQLm|Pk z;&5G(V?h>uV&aF3-}W7;!zmyXbBx3!fJ#=)aZy<8S8?5E4s$vsDlFihdOHhWuCo*e z8b&5*rql(sq&G?It-dM~i{zqpd~i)wa&aRUy7=7*+rs=2 zh+S^>3QN9!S($jH&W*{(A~4U?wnu_Zl>ZY=^q+p?6iV)5>|ak&mQfO-GsxW2AVMuI zbBI@S_t-YfnDz1Wc3fvhxRN&=r{UDI$2XERWOP*LC)!OqGrNft%>mx8Fx zU`Fw%m`Ihy5TM`NcKS0&l7XoOc};xV%_#Rd<&Q`5Z_lK&sd>|V#C_TZw5adPa6=6{ zbnxA(eF@D=-=&Od+=5cjk85Pevj&w+uO-cXu?!S@#4RJx_y_jkKoL2O3r6FZGZ~!N zt~}>RV(n0}7aY><5=1etQVd?QGO~U%us<9grH!XE1&038u5GqnGj33Q!Kb7D?inVt z$5&{A=YL5S_kc^OpEBzRD1 zu}uf|r)r1|-p%F7YmwKOH?v(m2#)(#UtK-R&6t8j-1i4Th)|J>-o#?n&*WiK_PQf@ zXECIR_6&ff6K)1VsM;SKJqTvve>(Nm+wsa9dt>ElV=I;m<*O!R>4CQV@=O9)X$_Tz zIkyp^KN0%(ev(<0tHSit1s^N1pIPUJEK+W}5z_|dTJH_`v|ktI}4}V0Cm(Acq(Vv&DoPuogLjw)__#}~0_6y42 zF#PL3%?W!u^x`vbG67?OV!~|3dX%&+Yn%l?gS64ZS%#5h^E8bmr?hRplt)({4<3h# zmOfd4O;mG9P!gSIsLk=Se!w%91YX6WkmI!qlh}|}6%a1iwK3D8=YI0fxbSu^wojO9 z#my4^WF{A$} zr_uU_^HWHyMPByTJd=F3!x@AVp0u@0gXX8G?@e9cr(cRj19YDAzt0dAIh`!+pYb!Wr=)O-Qt!hs40X^_|ig*ZR_~utV?;O!J*3ngIANZd_WPs z5AlY&AO{sQ`|7+wZ^eHJIz2om)Vfl82jpyT5dCE&MnD$6Rxi5P*}mxENY?S5uGzEd zjEHvJL48vs3E%L8K9n5R)Of1PQ zV+{DrI+PdoQo`AaU11_glEZc$mwC;=&=I;jD0uOos)#~x2lJp>ao4@Cl+4A6&%_Ok zb+RKb?_<^uaN>3vqG_k)oN{(EGUXB2a^O{Ee$Fv?3-DOKu%3-= zR?s#wYI8#`*Ub||EMqSC%cm-)iSxP5Env9c=LD@Auy9(=#5)Uug3uP3(6aQ%tIRJaBpwrG6~_ zA*oE*acHO@UFMtfLzzu`UKRF5v#M#kqQE@-k3l1^K|)y(%N$apGPMQFU9hdD4x$1r zDcW4v;n%FYlF!fbk5?_zS4sDGBFYa@idL1FDnQIV_56Lc_KLx>E>rI5apsLfWeJ?T zG7UaERmJ%Ot&Z8twEQvgFrBw}95@~2Ao-OmRf7oCM(NYKyqVnQvTZTNxuovem)ksS zRUI~1m+^Vow@I*Ha#VNY%6_J_S7jQo3kh(giZA?j7#7Bwi{dS1X;g==(FmAgz{9NR+cd5?@LY|TxiximH< zT%}3_^_68`el;0sZj&$*q0OTMXfNtxIVFEy5u*Qa&|D81pcG7uKnYcz$CvYtuA|&y zhoBZVpm2_#`x1?gW1?Ul%pq9Wo5QbftWL?Gu<&)12cGpM?yN-ea6oxFEqt6jLEZX1 zLV(c5n3*4KjvQ27mpYVfE^X|tUpD>IIVy&irELLV%nYoMs(0*y4 zxoZkA8l4%C8)7I&ju5Lj=Dl~TOC7DC2woW{Q6jA7z&l>)u>RQ!ulzS{X;HyShpzo$ zhEr5S^!6J4g5n5rpGElY)l0fzW`9G$9ba(w?@`R^0!Y}`%SA1FxD8E78Q^w&;k)Ri zs=ZcL!8(FqpTW9?oU+0@S>w|hohn5>%Y`nHI-Gj&gXT*%^>4+g{0_xFQAOOhBvtBx z98!wxHnfkM8YF&>Qei)owUqa=19XGBJ2<8Zqp!{`POdVpPj)vLUR{dCXPbYoVgYU? zff&Z*@1^kRUqLmvZu zArMohA_pEQJ9(alTBE&_>yk(3-E6Q7)O8>IDYY40UG;YyD^pI?vXNbvC{3v2C+LP- z{VE4ug<+FR&%$aO@1qU%?r2pjhKg0lrijY8LUtT80Lwb&&uyhlK_1Ik_!Et?5C82; zXUZ3c#YkSsRHl6~#_1xAFrcLNCOKfmZ*G{*Nu#e`CC0CehNsx#3SScy8&9P1N5+*8 zxb!14vJVj}V6XABo2IA$`JaYFbr@9RT+2cXz3O+p>wmlMhFsojSreo5y*ww$p`07E#;pU^TVMa zXS0>(;u6aOB#le_(#vf9owdQ@;WYLm1F)mdVLW~ad&loob5FD7d}dbF%as$*kjXMP zN= z2kZioil-q~Xy)2k7CRsBoJhP4D|uTc!kNiKjTt1%o>W6ZVwH}_&nvszU#J|SY66& zvLoThKfR0C=AQjfJWKu)&_Zf&y9%%yn@`d3qCrE13sHj?vI;&YvOY}QM-5(MpK^G!~D;Qp&qr}sQzi!l%8vmm?j|0 zd0>#7jBhD-m6H>0S7uF7FM%!9_h8(XR`|31NolTZ*n5<2b*9x&Qx+2kJqGGfNXb}H z*i>=YR5@660a*2r*UlOkku!tSMHT6WrmyZ$MR!NHsQ zt^sk*+DvjXUVLS*4|9e~03^8{I5pUJf2u8NHAuOB^UU+hh61N~jqSQ9UiTmjUif3l zYlnnG#De_K6mDd8ZiG5MGBvmynwz4ouuW)p<0XoOO});pkYkGIyu_U;qLHqNUq~8- zeCJ%KsU}NEbmaB^xRIGuGFb$w(++D}+KcvAl)plb-5`x&k3=WA5ET(mZioHhNJX2&4L#AHbsg6SC1S$X-5EG4lkz zXK!J1B6j!LEM@xEU+}zudHc;BJJ=Gw`E_p*Jzz8Rc<0b%*Y!XlyOMyD_ChP5ATnvxg|s|pU2ho#XRom;(dRqgHIcm(-lMd2`CHT|M> zf(YCa3kf!VXdjg-Ok}fCb$Xfe`3z#PyDFx9(!e^cr+EC1H|d~V68tVQUVXIF1@>*A z0`*%E{6PlB zAZtoAH~0=Z%@#v#dJ)^v))c%-s`}HJX%_OuIZCCfnls%U|KTf$CiHt2dO+ox9S%rV zO0{~NlRpXWwY}mFB&15~7Uji0h~GuiLenH*u=tHQmT(!O#*L{w3pEB(6AwzT#WVncZ;*1S2w| z4uyonujn;>M-uMZx|IzB8Fx7Ma(zkxB6X^si zCuwFC;6KhGO|1KHY|*>ot@lQEM7zXZ5K@OykBien^F2fJkkUWU#~Mh|~M?=Esn zAl)gXIZ~nxN0q=TQg3+HPFZa?7N5o9rYy|deq`-?#P9N9+@f7V#H@M#;uJlf$Q-X( z4Pnl>b?$FN2OLf{2VBPSJ|)>A^$2E__maFwt;IO29}Vk2AVr~cpPT7MY0TsHKg}^u zc=_za_;kyTaQi;}t@JI77liK3{ckUD-IpFvQyLs3f$RjVWWz&5r+{fv##jXnb$Mb# z9W$c0nL9=6jw8ACXRgyJl>_E?__rYMP2CosjCXUEd&Z z+-mo#4tdTgY17Jk$=?G~roP*%ld+l?R-7k^G5 z2U)l>KthMKH{caAGeRsH>=)(IJ=wR=BQG-NRD4<#7Uh&~8``Qy^P&;PEP>1`|1wJv8%~s~@JC+MM;} zqib>@r|GSz;X7e+o6U{yRVo`jh8~=W_BlM2@lIA(noFHmv;m0$X%Xodmo}p1KX^0> z1-XruI)AwHsIHmcs}j_l!=%a+aItJKc`E(XambmRQq4RCqZubGX!}7o%6u?1d;m*Dppnr zyTD7L^}4iozyb!NPaX%VKUbn#^$GkXVi$-J(Ij5RygLjf_Mi~aI&8p!)Vfm}&%yx) z$dBbgbwax|4alQ31V9$`ou)`XW)!U<7p~c!CbmJ;^VBkqAL=e}6q7x-uthK*d}V{^ zb`exJ!Bi|dA6olPHNi8A%&4y_c!o=Tf5F4oJ<lV6FHJa}8U_V#8ahRE= zm^Pl`l?ztL*~1)1TL!M2(u@Id%|j+KhnDjjxi<*?kCfq0YFiY{NxaV*hqHM(w=%dr4+;8KyF4^(2qs>MpJZJ~cjgKEEPLyEn zXuK%Pp-&`_1l_|lqB1+Yf%m`%8S-hu?M}#td#1fZl8Jx%+7&;`2eSrkjD;+oWj@;D znP^&WrI}23mLt-Igp5zpf20$F@0D;X!MG?8wQTOsACf2BggD%ksTmd{1jbhojZyfj zQ4FyE_Z>oG+Ec?&SGe6iLgRw0;_=VuEWyM25nbfzWtVuJ+WL7YR~+^}93gkYTSx+` z?v;Ry$-D1Ya-bpBq68)n^YEtb15yXg&JduJi65oblj8cc%C4mD8_Sk;y(y3Lr8$qa z19LJ9gVO<~J5sB8A26{-$A3{Dn&o3YG}2|e!E@R4^AJ0%xG;N9XN7-`&&uTH&34mG zf1ZOLd6>x(_X_Wn^DKCsEY4<`Cc1tJjlYvqaOpL@_zQ4-sx|2kS)>Wov9-{5R~^$j zn+2x1PeWa*r_I=$DbO2IQkP05^RJM%6n&6{h4f(!Sy z7*V~*Dd{d>jZI2-vf5-`WcBXJUIN^pm%wcBW8Y2p z=k%Jl-U`Sav(VvgjeY-K782uga1+G^V-HONj&~Aqph!`Kd*|5IjKlTGr39C@EMKf6 z-$07QTyWyzbx7C+Zy70H1(Yjzg%N*Mo*fZ{*UM1e5HL#~Y#MI=e!Eyc@dBglKj=GD zuc#gV`~kp#y)Ev(iciPt$}Q_;<#2;A z8ppmeM;^esa=}eqGvvO4^0nh@8?6kf08uKD5^jQ|d z1R=v+40HY>CCG@pUh^~cDxwz4$3MunS8&ocyQshU@5^$IRs^&-eG5z(eNh}so@?#z z2v3h)1UKROy}qh!AC1rQf`LIt9Zko zEk^_aX?2b9Ol`T0<8-qLxiS;*6ue5+?6F;EE$3i-c2tHJI*0DUL|4*R1&mu8`^)$h zzh9ZA1K(`@I>!1>CFhzY;yZ+4uPscf6CF7j%RHu~%|BT~3xQcf`_5(emI&vd1obiD zAc+#6PQiep$z}7&>6xI0`~sL0TqDVJcI@gA)s;f-KcAqfm$K}=+Ixz2q)BwiUP%zJ z{@L)`7>)X_gsp2*2%03)lp#S-&rS);UJYIR)&g-hFzwivwI!+OLUL_=!^+$aO;anh zKbA6hZ3&w8Z9P2b^#~yYm5+X{X@WLBA!EcM_9oc^xzE?A`ww{4M%6c5R&$>2mYatj zur*@f))l3XjHQ`T6;`txCyj^vMv3W2v{(9h4%1Y7&$WM_Pg%}^??8=LVMux{sCEX7 z=OBI@>PY3}jaUWG9JXG4!O~|*9y-lm0*K(t@$Bs}gZQhAy9rL9vwGITXxrjl?vphB z0IAd#--6$%1XpH@M~amXv&FO{X?W6_`|9u7BbF!I6CI^=Dh53Wc**W+2vsh+>J_r% zO?Rz`3Gudou3+r?>k;ZTqr!#k=!^8WX${>HcsFQf=#TU2k>8xIXWN>T+hf}(&UZYE zk)u@^$xQJqgH<<`T_c%vg9$F6&fpJ;)(2kbQ-YVjWKOdLJrJi5J^$2Xh3SuwR738~ zt`Myvv9bxrKl(|bB0kCUEYa!z+I~S;VOr=&b@ZGi_w%6R2%@hPysqh*#0%gT&6mxB z^eE!#_gLH_V;*_1!V~H5hiW@?JSj*Qb%bOF;N|@rdvaK zIOT7--^K2rJH#lfeHR{)U`;7Xa=g z?j?HY-LAL`06U?S_^cdZs&dheV$L9 znfLj8!#<{clJXG8p2UEruD`YTb4ssu zE3IZ0OpTt6NM4CQQCFu`jUQ02Br2t3&+fCr95cX1+v_APoLPe=$g9sU`G=m44O`s5 zx5syxVV#-u+#dgY~gY#GGL)0tH&mA9ds_y`wiB7ZkASnukg{>e^p--SG#xj z7VN4x7@e9lJOb;By7G|IX1yZ3c`HkD_f|sQl3S1^6uToUxK+p^688L;X1qavXoVWM ztf5zPP?eze_%*j{(1B^_xJjQ{^b@`_o*tPUFzWz2Cx+9i@6==ooYCMN%`z}q|37^? z=`Pk8)lP5`bl>0>|H5j zar-TQKzhAl%RAUDXNJH#*$C*FDod`9di$BxBsJx=@7nAE)61XL7Rzh+4S5dG*CT!~}0zKPQw6A8b-$H1|KW9;mZ-Sm#{iG-lXFotdw# zFiJ!GO>cUZXR5zYy)e;xaS=iSIbj4~zrrpGP=AdCEXOjSUs5?}D4A9bcF-mGU)d7; z=+UOQ+H$+W%tae<6E>csk;k<5!6P9o`|~iu72+_*SKSus_#^mBjE|=bl?+Q(N#H8h z0&u12dSI8=Sma2ds4TL*Y}bZhu1y{pEtmhBfNX!rRhhzoZkgS{Mgy$(&ausKwW5se z?;qn<(&>6v)wt7>TPBTfXA7- zD{CfxxvZ87Z^@Y@dn4O;|9HTVjjUs~q0%(BQNu z{9DLAzDw9X5tmSxyf{H32`Yi8U~Cx8{jN!!xmy9spUL#d&%|@1hP)6W0_~Ux>tUHt z0r9r1xb@VBn(`Y(23|bhcOH>i(2M#JKp;q${_ic&9QAtaZwEP3gOj{J^sBX=pwCkL zQN{o8jI*q(6K>V+z6=XL0CAc}Pl7a0f*5Z_X2Zgd8VN}lq7B4}jQPY`qLntXHi3}) zii|3GZ_y8ckHDrH-jX0=ul1ACNWz=>Kk|BE)A`%8s0G{4za97Y%OeE$Zr!PD_`ubLKv=U z)aem=9jnL-O=fOu$}`3tFFfm}{4Vh7K8pe})aGjOfjI2AC{A%iCbxJ~CGV-L8~cC^ z7$!Tm%lv48Av{~H;zIH*>RU>$b-!l8eTCVe@h?3*E%Bb|E@a#D?x>3~tP~ZQ-1HfY zBs#N<-O=gDGu|N^B6mQ*Hh~Ae!t4{dkSuY^69Y^C{qNbiV${OQu!rQ=+PtCdN*Amz;Kf_25A=BzcT|54AxxQg;Jf>YE~wt7d#%UD zfLZS!Zd7)NeTE8*+Pm223iysyhb*;Yzm z_ri1of}f-WBu}aY=^+crs%ytYlIp4=BkIP)hSc^hGzqKvWgt(g|8cDTFO`kNMhs>y zL|=mV*Mt3s?dFht15fQ|6SERZkx@t&t>YKRw+_OJEv!t6UDT)~9oMJ?c0oQHs{o}J zP*!qm;7>28&dFgtSqgd+};gKDI}wUSn02fT~vxv*A(7BWywW% z#JV5(1Fr4M8ld8AL{UyyC!&LNx0b_W?Jgj97|5f}aU9u&?EF_p4?FyC46=smN4W`P z!gytx+mip=KoNewJ_ul0gN5v?swJaXTjg&XA7p=ib>QPj~!nIio?s$%U{d7hcL1#lBoxoL+ zQP@7^2E&0Yex@AVlOJpS4ie+TQlE6=RUG;bb}O>0&`5ekX<=*xyalZ9#CK#Dla^~X zxdcrUM1)mXM z;%Ui;JwF1s+6i z8a{mH$llf+1Kp0qHyL72XZIc~NxpgezDYm8ISaFx=;ojKc8)ATZ}fOQV11ZQoS%cY zcsRi~`V~0E2-(9*tVZeKs5bz5&iJwyUS0v&GuZ)utJ!baY)*HJgA>AZ>wbj$hT7{^ z+(^4TkiSql`oQNY-Zj(zn%E-CTIX%>dgB7wLn3uWYU!Qy8T=0jZ1qod((m9G>`{qn z)npfnVG!qHPmJb}M7{0K)-pUQ3M_xjHxKEvDq`<^kA`3r2$t(AF;1_SVIlm_IiC{( zEG{(Rq;{Gj;1CX5(UD*|pVQv%tTi!?VV$49hdTq5^?m_zd8U@o1y__kt%cHa(S zMS_dMrS+wFO?1jlb~3RmFI}XZ+3qK}OzvN|_L7QLo8`b-utP(LR#$X>&z&ZWT9>Rj;TKpoHn`<%NVT~=X{dKfSlH-V(E6c=dNN_V{Ozs- z3&YVrk6JgcB*<5>fKN`u7{o6BA$qk$L75rziLI@>x3&{m1xT3sUFlp>+|wUTH=XZc zRmr0IJwGBIcLyz+xyyM$jp(3Czk_i!ZL{21CP1>w$bzM=sOLuuLD8dP1&wNgy6)Om z{B?Ngu5g-DvE0=_aS`-&+$-%3>n6iC)%9_^f~gSV(H`GOcAyvggm0^SvZFLtYOq&x zaUkZd+VeyT|n6+Oc z+7ewANab~#ajv4#ra79aX=YIU|U4enl(GCmd)FRf~x2&CwH|YF2{~x zlAV#tVp<LH#%C71;wa>Y!o&h;j6M!CbgC}Iq6-A-Hq-=}Qx#9O~~ zsx8wQ%&bsqGxJPt=z`|z5;HHB8zR%D&Q`4Yo=$rE;CTZ0#C6PQif#nDCz&SNIy1c5 zLhtiHc9~tzxP2Jmp$=aTh?C(ribu`>46iXfAn*^bQOm*)qXhAK|Kl>@bp`R&Bs1Oh za&%d%P|IS~mzNtS_Mdy{M%~im`}ntT1w8j({0Gwfi8eViX11J*F_^Oj-_bKLSRZh` ze1F>pyawvpz2n(WKe(ycA0N`~{{v2uj50{yg9A#5( z>yAc84@=IHpgT~&VmJN2h6LeU{1=3^KuHOjeT7&QyR8``ZP&u^bV{hK64nLV%^1H; zQ5Io5=1-KT{r_W_BIA+B?LJhgq_p+2fvY)0yF%V0{{QiId-ZU@KTs*C*Y>L)DifMm?@t^ELw)>+;15bi+4~ogwwzO- zTzWE@2uymkZ8;_a-1V>~fWV?= zKfDF221dK(eL>C(S-xqehG?NjU*T`Msjh}R-3zq@Gu;ng1Iylz<%+7~SD7%JtkC>- z8K)DtxaqV6X48{?fz^R{-t%DxoCMr&LBuo$A}f4Y09)t}7|#D=GyYQm*dO1&=AZo; z3R3Ui?LyDL?x8KdY)|wBdd5Ff{vXbQIMt*6K=9@R$bVv5$XyfgrgbdplG^YLTSeLP zMJruxGDhvJxSnh7-n4muehY-ZASyXrr^C5xviw(Pc%VT3LP1{}z7nrbBBZy91~s)r zGFkuD9}pdXDJHu76RZxf!s^K!I0&IiwsU6KZ(Zs5yf0pIOp)KZfwM*gc&$CIK9$m^ zx^5)P;etj?{?8!DG~|JyeP5+QqMNM{U+F z@7)YXr~X_L(8cn@x6Bp-gkyEZI7KLe(_s9-w~bpdJT*Tft+9PV@?c7zT@&<6Rau;V z)-$~!JR^Ibi==HvBS^>EV)T^m&%OC}H`a%kC%!FZFz1s9c$)e2ATTBvk{q|YK#1l8T>nMnUME) zoWYzw3NZ^iS7#cews3*9;|)tU=z$Bu&dfU#_xwaS;K9ZF8M&YA;wx`Z$8fDTI2Wzn z$B4}eDAvQ|MJ%^#hce2vq(9=u!Bf`bL+4v^V|NQ_5(+`2y2%;-SVu`7f_qjT_tJ+n?&lG4ZB4}kmv4%u*XpYj z)btK)a1-u*yWj$2m)Jj&+<7DCEZD|O?#RLYNZ>|fB|7vWyJT;aYu~>`dRI*O^Gv__ zweWzjS72Q!Tk1uzTCm+U0siH8V_65Ukhao-z?f;$$=rGgbf) z00cDprIj>>xLB1yIEAY6Nep!Ge+x-9Ws8V?wW?>F|0=dGTbD%Q;^JAx?3Fe!(VQm4 z`jZe{o@1DGx_q?Lmv#-RTKGdK_5bLh=W(Ma=7|YSO zin&nPd7^XScmNQ5kdPv((ws8bVLCOARvh1vPq-^MokGz7I04gJ`0KTSHlSV&l8*R! zu?CxD>Jj!HO z(Ydv_h}?Ltc0rfl=6G`GD{nGOe#3SnG^>#z_qD;F1^t~VVd|l4Qrlf|Oks;n zCf9^@Eq1t&5i5*_!>(Z$><(o03`}L+12T+?4QY*qTmyy-6aL*J&yNF=*4U$uMUW}< z0q**!bU_rvrm4_zL4}9Oy`J2iAS%)KPj>DoN&UhXOuXRHeTEmDBWnXx^7pw_J?Byn zL|(M+*>zbFvDb>_U)#$=?#spp(w$uUx|<#f%42>uu{t6q(AF6$-~=s(E1o0-Mk;s*rZi}xPG8};S!O-7peNL_r`eH?ZcyzLX!+K6=+9FJEX)}7J(vxpyZ9`Q%y0Quef{2I8vcHP0#9d_HTH~5&{4E}}~ zL4jnilYU+E=2}i}viLCR8dpzQh5>Jm=F%#sk#;fm0CA)NI0qkld@bYEI!L~5s*ro+ zi1jw;N@dsnu}~JHjQVkB6hV1Lb^a>#_|k)HK$z_vODGm^OEadI!-gEe=m*t{y@z^E z(EBIT=I@vLg|<9 z-VM}G#w!)ccGoP{1M9mE=ax^ilcB?t8tkTUGtD

3hm6&L&*QyQGN;C~5 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 PYxMgE(rQPRSgi;Et3azS literal 0 HcmV?d00001 diff --git a/src/Immersion/Resources/sword.glb b/src/Immersion/Resources/sword.glb new file mode 100644 index 0000000000000000000000000000000000000000..320f76bc6d60e8b25f51c55fd0c5a79fdfbfe36e GIT binary patch literal 22568 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 literal 0 HcmV?d00001 diff --git a/src/Immersion/Resources/terrain.png b/src/Immersion/Resources/terrain.png new file mode 100644 index 0000000000000000000000000000000000000000..ec634442c0418efbb0d359f6ef37d581929fd92c GIT binary patch literal 5361 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 M0o7H-DqFq#A2XpeivR!s literal 0 HcmV?d00001 diff --git a/src/flecs-cs b/src/flecs-cs new file mode 160000 index 0000000..1e36559 --- /dev/null +++ b/src/flecs-cs @@ -0,0 +1 @@ +Subproject commit 1e36559cffa5ab2fb755feef563c4294a6f32b0c diff --git a/src/gaemstone.Bloxel/BlockFacing.cs b/src/gaemstone.Bloxel/BlockFacing.cs new file mode 100644 index 0000000..6820400 --- /dev/null +++ b/src/gaemstone.Bloxel/BlockFacing.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Immutable; +using Silk.NET.Maths; + +namespace gaemstone.Bloxel; + +public enum BlockFacing +{ + East, // +X + West, // -X + Up, // +Y + Down, // -Y + South, // +Z + North, // -Z +} + +public static class BlockFacings +{ + public static readonly ImmutableHashSet Horizontals + = ImmutableHashSet.Create(BlockFacing.East , BlockFacing.West , + BlockFacing.South, BlockFacing.North); + + public static readonly ImmutableHashSet Verticals + = ImmutableHashSet.Create(BlockFacing.Up, BlockFacing.Down); + + public static readonly ImmutableHashSet All + = Horizontals.Union(Verticals); +} + +public static class BlockFacingExtensions +{ + public static void Deconstruct(this BlockFacing self, out int x, out int y, out int z) + => (x, y, z) = self switch { + BlockFacing.East => (+1, 0, 0), + BlockFacing.West => (-1, 0, 0), + BlockFacing.Up => ( 0, +1, 0), + BlockFacing.Down => ( 0, -1, 0), + BlockFacing.South => ( 0, 0, +1), + BlockFacing.North => ( 0, 0, -1), + _ => throw new ArgumentException( + $"'{self}' is not a valid BlockFacing", nameof(self)) + }; + + public static bool IsValid(this BlockFacing self) + => (self >= BlockFacing.East) && (self <= BlockFacing.North); + + public static BlockFacing GetOpposite(this BlockFacing self) + => (BlockFacing)((int)self ^ 0b1); + + public static Vector3D ToVector3(this BlockFacing self) + => self switch { + BlockFacing.East => Vector3D.UnitX, + BlockFacing.West => -Vector3D.UnitX, + BlockFacing.Up => Vector3D.UnitY, + BlockFacing.Down => -Vector3D.UnitY, + BlockFacing.South => Vector3D.UnitZ, + BlockFacing.North => -Vector3D.UnitZ, + _ => throw new ArgumentException( + $"'{self}' is not a valid BlockFacing", nameof(self)) + }; +} diff --git a/src/gaemstone.Bloxel/BlockPos.cs b/src/gaemstone.Bloxel/BlockPos.cs new file mode 100644 index 0000000..51d6ca3 --- /dev/null +++ b/src/gaemstone.Bloxel/BlockPos.cs @@ -0,0 +1,75 @@ +using System; +using Silk.NET.Maths; + +namespace gaemstone.Bloxel; + +public readonly struct BlockPos + : IEquatable +{ + public static readonly BlockPos Origin = default; + + public int X { get; } + public int Y { get; } + public int Z { get; } + + public BlockPos(int x, int y, int z) => (X, Y, Z) = (x, y, z); + public void Deconstruct(out int x, out int y, out int z) => (x, y, z) = (X, Y, Z); + + public Vector3D GetOrigin() => new(X, Y, Z); + public Vector3D GetCenter() => new(X + 0.5F, Y + 0.5F, Z + 0.5F); + + + public BlockPos Add(int x, int y, int z) => new(X + x, Y + y, Z + z); + public BlockPos Add(in BlockPos other) => new(X + other.X, Y + other.Y, Z + other.Z); + + public BlockPos Add(BlockFacing facing) + { var (x, y, z) = facing; return Add(x, y, z); } + public BlockPos Add(BlockFacing facing, int factor) + { var (x, y, z) = facing; return Add(x * factor, y * factor, z * factor); } + + public BlockPos Add(Neighbor neighbor) + { var (x, y, z) = neighbor; return Add(x, y, z); } + public BlockPos Add(Neighbor neighor, int factor) + { var (x, y, z) = neighor; return Add(x * factor, y * factor, z * factor); } + + + public BlockPos Subtract(int x, int y, int z) => new(X - x, Y - y, Z - z); + public BlockPos Subtract(in BlockPos other) => new(X - other.X, Y - other.Y, Z - other.Z); + + public BlockPos Subtract(BlockFacing facing) + { var (x, y, z) = facing; return Subtract(x, y, z); } + public BlockPos Subtract(BlockFacing facing, int factor) + { var (x, y, z) = facing; return Subtract(x * factor, y * factor, z * factor); } + + public BlockPos Subtract(Neighbor neighbor) + { var (x, y, z) = neighbor; return Subtract(x, y, z); } + public BlockPos Subtract(Neighbor neighor, int factor) + { var (x, y, z) = neighor; return Subtract(x * factor, y * factor, z * factor); } + + + public bool Equals(BlockPos other) + => (X == other.X) && (Y == other.Y) && (Z == other.Z); + public override bool Equals(object? obj) + => (obj is BlockPos pos) && Equals(pos); + + public override int GetHashCode() => HashCode.Combine(X, Y, Z); + public override string ToString() => $"BlockPos({X}:{Y}:{Z})"; + public string ToShortString() => $"{X}:{Y}:{Z}"; + + + public static BlockPos operator +(BlockPos left, BlockPos right) => left.Add(right); + public static BlockPos operator -(BlockPos left, BlockPos right) => left.Subtract(right); + public static BlockPos operator +(BlockPos left, BlockFacing right) => left.Add(right); + public static BlockPos operator -(BlockPos left, BlockFacing right) => left.Subtract(right); + public static BlockPos operator +(BlockPos left, Neighbor right) => left.Add(right); + public static BlockPos operator -(BlockPos left, Neighbor right) => left.Subtract(right); + + public static bool operator ==(BlockPos left, BlockPos right) => left.Equals(right); + public static bool operator !=(BlockPos left, BlockPos right) => !left.Equals(right); +} + +public static class BlockPosExtensions +{ + public static BlockPos ToBlockPos(this Vector3D self) + => new((int)MathF.Floor(self.X), (int)MathF.Floor(self.Y), (int)MathF.Floor(self.Z)); +} diff --git a/src/gaemstone.Bloxel/Chunk.cs b/src/gaemstone.Bloxel/Chunk.cs new file mode 100644 index 0000000..b700df9 --- /dev/null +++ b/src/gaemstone.Bloxel/Chunk.cs @@ -0,0 +1,17 @@ +using gaemstone.ECS; + +namespace gaemstone.Bloxel; + +[Component] +public readonly struct Chunk +{ + //

Length of the egde of a world chunk. + public const int LENGTH = 16; + // Amount of bit shifting to go from a BlockPos to a ChunkPos. + public const int BIT_SHIFT = 4; + // Amount of bit masking to go from a BlockPos to a chunk-relative BlockPos. + public const int BIT_MASK = 0b1111; + + public ChunkPos Position { get; } + public Chunk(ChunkPos pos) => Position = pos; +} diff --git a/src/gaemstone.Bloxel/ChunkPaletteStorage.cs b/src/gaemstone.Bloxel/ChunkPaletteStorage.cs new file mode 100644 index 0000000..48527e1 --- /dev/null +++ b/src/gaemstone.Bloxel/ChunkPaletteStorage.cs @@ -0,0 +1,188 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using gaemstone.ECS; + +namespace gaemstone.Bloxel; + +// Based on "Palette-based compression for chunked discrete voxel data" by /u/Longor1996 +// https://www.reddit.com/r/VoxelGameDev/comments/9yu8qy/palettebased_compression_for_chunked_discrete/ +[Component] +public class ChunkPaletteStorage +{ + const int Size = 16 * 16 * 16; + static readonly EqualityComparer COMPARER + = EqualityComparer.Default; + + BitArray? _data; + PaletteEntry[]? _palette; + int _usedPalettes; + int _indicesLength; + + + public T Default { get; } + + public T this[int x, int y, int z] + { + get => Get(x, y, z); + set => Set(x, y, z, value); + } + + public IEnumerable Blocks + => _palette?.Where(entry => !COMPARER.Equals(entry.Value, default!)) + .Select(entry => entry.Value!) + ?? Enumerable.Empty(); + + + public ChunkPaletteStorage(T @default) + => Default = @default; + + + T Get(int x, int y, int z) + { + if (_palette == null) return Default; + var entry = _palette[GetPaletteIndex(x, y, z)]; + return !COMPARER.Equals(entry.Value, default!) ? entry.Value : Default; + } + + void Set(int x, int y, int z, T value) + { + if (_palette == null) + { + if (COMPARER.Equals(value, Default)) return; + } + else + { + var index = GetIndex(x, y, z); + ref var current = ref _palette[GetPaletteIndex(index)]; + if (COMPARER.Equals(value, current.Value)) return; + + if (--current.RefCount == 0) + _usedPalettes--; + + var replace = Array.FindIndex(_palette, entry => COMPARER.Equals(value, entry.Value)); + if (replace != -1) + { + SetPaletteIndex(index, replace); + _palette[replace].RefCount += 1; + return; + } + + if (current.RefCount == 0) + { + current.Value = value; + current.RefCount = 1; + _usedPalettes++; + return; + } + } + + var newPaletteIndex = NewPaletteEntry(); + _palette![newPaletteIndex] = new PaletteEntry { Value = value, RefCount = 1 }; + SetPaletteIndex(x, y, z, newPaletteIndex); + _usedPalettes++; + } + + int NewPaletteEntry() + { + if (_palette != null) + { + int firstFree = Array.FindIndex(_palette, entry => + entry.Value == null || entry.RefCount == 0); + if (firstFree != -1) return firstFree; + } + + GrowPalette(); + return NewPaletteEntry(); + } + + void GrowPalette() + { + if (_palette == null) + { + _data = new(Size); + _palette = new PaletteEntry[2]; + _usedPalettes = 1; + _indicesLength = 1; + _palette[0] = new PaletteEntry { Value = Default, RefCount = Size }; + return; + } + + _indicesLength <<= 1; + + var oldIndicesLength = _indicesLength >> 1; + var newData = new BitArray(Size * _indicesLength); + for (var i = 0; i < Size; i++) + for (var j = 0; j < oldIndicesLength; j++) + newData.Set(i * _indicesLength + j, _data!.Get(i * oldIndicesLength + j)); + _data = newData; + + Array.Resize(ref _palette, 1 << _indicesLength); + } + + // public void FitPalette() { + // if (_usedPalettes > Mathf.NearestPo2(_usedPalettes) / 2) return; + + // // decode all indices + // int[] indices = new int[size]; + // for(int i = 0; i < indices.length; i++) { + // indices[i] = data.get(i * indicesLength, indicesLength); + // } + + // // Create new palette, halfing it in size + // indicesLength = indicesLength >> 1; + // PaletteEntry[] newPalette = new PaletteEntry[2 pow indicesLength]; + + // // We gotta compress the palette entries! + // int paletteCounter = 0; + // for(int pi = 0; pi < palette.length; pi++, paletteCounter++) { + // PaletteEntry entry = newPalette[paletteCounter] = palette[pi]; + + // // Re-encode the indices (find and replace; with limit) + // for(int di = 0, fc = 0; di < indices.length && fc < entry.refcount; di++) { + // if(pi == indices[di]) { + // indices[di] = paletteCounter; + // fc += 1; + // } + // } + // } + + // // Allocate new BitBuffer + // data = new BitBuffer(size * indicesLength); // the length is in bits, not bytes! + + // // Encode the indices + // for(int i = 0; i < indices.length; i++) { + // data.set(i * indicesLength, indicesLength, indices[i]); + // } + // } + + + int GetPaletteIndex(int x, int y, int z) + => GetPaletteIndex(GetIndex(x, y, z)); + int GetPaletteIndex(int index) + { + var paletteIndex = 0; + for (var i = 0; i < _indicesLength; i++) + paletteIndex |= (_data!.Get(index + i) ? 1 : 0) << i; + return paletteIndex; + } + + void SetPaletteIndex(int x, int y, int z, int paletteIndex) + => SetPaletteIndex(GetIndex(x, y, z), paletteIndex); + void SetPaletteIndex(int index, int paletteIndex) + { + for (var i = 0; i < _indicesLength; i++) + _data!.Set(index + i, (paletteIndex >> i & 0b1) == 0b1); + } + + int GetIndex(int x, int y, int z) + => (x | y << 4 | z << 8) * _indicesLength; + + + struct PaletteEntry + { + public T Value { get; set; } + public int RefCount { get; set; } + } +} diff --git a/src/gaemstone.Bloxel/ChunkPos.cs b/src/gaemstone.Bloxel/ChunkPos.cs new file mode 100644 index 0000000..4f1b193 --- /dev/null +++ b/src/gaemstone.Bloxel/ChunkPos.cs @@ -0,0 +1,81 @@ +using System; +using Silk.NET.Maths; + +namespace gaemstone.Bloxel; + +public readonly struct ChunkPos + : IEquatable +{ + public static readonly ChunkPos ORIGIN = new(0, 0, 0); + + public int X { get; } + public int Y { get; } + public int Z { get; } + + public ChunkPos(int x, int y, int z) => (X, Y, Z) = (x, y, z); + public void Deconstruct(out int x, out int y, out int z) => (x, y, z) = (X, Y, Z); + + public Vector3D GetOrigin() => new( + X << Chunk.BIT_SHIFT, Y << Chunk.BIT_SHIFT, Z << Chunk.BIT_SHIFT); + public Vector3D GetCenter() => new( + (X << Chunk.BIT_SHIFT) + Chunk.LENGTH / 2, + (Y << Chunk.BIT_SHIFT) + Chunk.LENGTH / 2, + (Z << Chunk.BIT_SHIFT) + Chunk.LENGTH / 2); + + + public ChunkPos Add(int x, int y, int z) + => new(X + x, Y + y, Z + z); + public ChunkPos Add(in ChunkPos other) + => new(X + other.X, Y + other.Y, Z + other.Z); + public ChunkPos Add(BlockFacing facing) + { var (x, y, z) = facing; return Add(x, y, z); } + public ChunkPos Add(Neighbor neighbor) + { var (x, y, z) = neighbor; return Add(x, y, z); } + + public ChunkPos Subtract(int x, int y, int z) + => new(X - x, Y - y, Z - z); + public ChunkPos Subtract(in ChunkPos other) + => new(X - other.X, Y - other.Y, Z - other.Z); + public ChunkPos Subtract(BlockFacing facing) + { var (x, y, z) = facing; return Subtract(x, y, z); } + public ChunkPos Subtract(Neighbor neighbor) + { var (x, y, z) = neighbor; return Subtract(x, y, z); } + + + public bool Equals(ChunkPos other) + => (X == other.X) && (Y == other.Y) && (Z == other.Z); + public override bool Equals(object? obj) + => (obj is ChunkPos pos) && Equals(pos); + + public override int GetHashCode() => HashCode.Combine(X, Y, Z); + public override string ToString() => $"ChunkPos ({X}:{Y}:{Z})"; + public string ToShortString() => $"{X}:{Y}:{Z}"; + + + public static ChunkPos operator +(ChunkPos left, ChunkPos right) => left.Add(right); + public static ChunkPos operator -(ChunkPos left, ChunkPos right) => left.Subtract(right); + public static ChunkPos operator +(ChunkPos left, BlockFacing right) => left.Add(right); + public static ChunkPos operator -(ChunkPos left, BlockFacing right) => left.Subtract(right); + public static ChunkPos operator +(ChunkPos left, Neighbor right) => left.Add(right); + public static ChunkPos operator -(ChunkPos left, Neighbor right) => left.Subtract(right); + + public static bool operator ==(ChunkPos left, ChunkPos right) => left.Equals(right); + public static bool operator !=(ChunkPos left, ChunkPos right) => !left.Equals(right); +} + +public static class ChunkPosExtensions +{ + public static ChunkPos ToChunkPos(this Vector3D pos) => new( + (int)MathF.Floor(pos.X) >> Chunk.BIT_SHIFT, + (int)MathF.Floor(pos.Y) >> Chunk.BIT_SHIFT, + (int)MathF.Floor(pos.Z) >> Chunk.BIT_SHIFT); + + public static ChunkPos ToChunkPos(this BlockPos self) => new( + self.X >> Chunk.BIT_SHIFT, self.Y >> Chunk.BIT_SHIFT, self.Z >> Chunk.BIT_SHIFT); + public static BlockPos ToChunkRelative(this BlockPos self) => new( + self.X & Chunk.BIT_MASK, self.Y & Chunk.BIT_MASK, self.Z & Chunk.BIT_MASK); + public static BlockPos ToChunkRelative(this BlockPos self, ChunkPos chunk) => new( + self.X - (chunk.X << Chunk.BIT_SHIFT), + self.Y - (chunk.Y << Chunk.BIT_SHIFT), + self.Z - (chunk.Z << Chunk.BIT_SHIFT)); +} diff --git a/src/gaemstone.Bloxel/Client/ChunkMeshGenerator.cs b/src/gaemstone.Bloxel/Client/ChunkMeshGenerator.cs new file mode 100644 index 0000000..d7bfb4c --- /dev/null +++ b/src/gaemstone.Bloxel/Client/ChunkMeshGenerator.cs @@ -0,0 +1,125 @@ +using System; +using System.Runtime.InteropServices; +using gaemstone.Client; +using gaemstone.ECS; +using Silk.NET.Maths; +using static flecs_hub.flecs; +using static gaemstone.Bloxel.WorldGen.BasicWorldGenerator; + +namespace gaemstone.Bloxel.Client; + +[Module] +public class ChunkMeshGenerator +{ + private const int StartingCapacity = 1024; + + private static readonly Vector3D[][] OffsetPerFacing = { + new Vector3D[]{ new(1,1,1), new(1,0,1), new(1,0,0), new(1,1,0) }, // East (+X) + new Vector3D[]{ new(0,1,0), new(0,0,0), new(0,0,1), new(0,1,1) }, // West (-X) + new Vector3D[]{ new(1,1,0), new(0,1,0), new(0,1,1), new(1,1,1) }, // Up (+Y) + new Vector3D[]{ new(1,0,1), new(0,0,1), new(0,0,0), new(1,0,0) }, // Down (-Y) + new Vector3D[]{ new(0,1,1), new(0,0,1), new(1,0,1), new(1,1,1) }, // South (+Z) + new Vector3D[]{ new(1,1,0), new(1,0,0), new(0,0,0), new(0,1,0) } // North (-Z) + }; + + private static readonly int[] TriangleIndices + = { 0, 1, 3, 1, 2, 3 }; + + + private ushort[] _indices = new ushort[StartingCapacity]; + private Vector3D[] _vertices = new Vector3D[StartingCapacity]; + private Vector3D[] _normals = new Vector3D[StartingCapacity]; + private Vector2D[] _uvs = new Vector2D[StartingCapacity]; + + [System] + public void GenerateChunkMeshes(Universe universe, Entity entity, + in Chunk chunk, ChunkPaletteStorage storage, + HasBasicWorldGeneration _1, [Not] Mesh _2) + { + var mesh = Generate(universe, chunk.Position, storage); + if (mesh is Mesh m) entity.Set(m); + else entity.Delete(); + } + + public Mesh? Generate(Universe universe, ChunkPos chunkPos, + ChunkPaletteStorage centerStorage) + { + // TODO: We'll need a way to get neighbors again. + // var storages = new ChunkPaletteStorage[3, 3, 3]; + // foreach (var (x, y, z) in Neighbors.ALL.Prepend(Neighbor.None)) + // if (_chunkStore.TryGetEntityID(chunkPos.Add(x, y, z), out var neighborID)) + // if (_storageStore.TryGet(neighborID, out var storage)) + // storages[x+1, y+1, z+1] = storage; + // var centerStorage = storages[1, 1, 1]; + + var storages = new ChunkPaletteStorage[3, 3, 3]; + storages[1, 1, 1] = centerStorage; + + var indexCount = 0; + var vertexCount = 0; + for (var x = 0; x < 16; x++) + for (var y = 0; y < 16; y++) + for (var z = 0; z < 16; z++) { + var block = new Entity(universe, centerStorage[x, y, z]); + if (block.IsNone) continue; + + var blockVertex = new Vector3D(x, y, z); + var textureCell = block.Get(); + + foreach (var facing in BlockFacings.All) { + if (!IsNeighborEmpty(storages, x, y, z, facing)) continue; + + if (_indices.Length <= indexCount + 6) + Array.Resize(ref _indices, _indices.Length << 1); + if (_vertices.Length <= vertexCount + 4) { + Array.Resize(ref _vertices, _vertices.Length << 1); + Array.Resize(ref _normals , _vertices.Length << 1); + Array.Resize(ref _uvs , _vertices.Length << 1); + } + + for (var i = 0; i < TriangleIndices.Length; i++) + _indices[indexCount++] = (ushort)(vertexCount + TriangleIndices[i]); + + var normal = facing.ToVector3(); + for (var i = 0; i < 4; i++) { + var offset = OffsetPerFacing[(int)facing][i]; + _vertices[vertexCount] = blockVertex + offset; + _normals[vertexCount] = normal; + _uvs[vertexCount] = i switch { + 0 => textureCell.TopLeft, + 1 => textureCell.BottomLeft, + 2 => textureCell.BottomRight, + 3 => textureCell.TopRight, + _ => throw new InvalidOperationException() + }; + vertexCount++; + } + } + } + + return (indexCount > 0) + ? MeshManager.Create(universe, + _indices.AsSpan(0, indexCount), _vertices.AsSpan(0, vertexCount), + _normals.AsSpan(0, vertexCount), _uvs.AsSpan(0, vertexCount)) + : null; + } + + static bool IsNeighborEmpty( + ChunkPaletteStorage[,,] storages, + int x, int y, int z, BlockFacing facing) + { + var cx = 1; var cy = 1; var cz = 1; + switch (facing) { + case BlockFacing.East : x += 1; if (x >= 16) cx += 1; break; + case BlockFacing.West : x -= 1; if (x < 0) cx -= 1; break; + case BlockFacing.Up : y += 1; if (y >= 16) cy += 1; break; + case BlockFacing.Down : y -= 1; if (y < 0) cy -= 1; break; + case BlockFacing.South : z += 1; if (z >= 16) cz += 1; break; + case BlockFacing.North : z -= 1; if (z < 0) cz -= 1; break; + } + var neighborChunk = storages[cx, cy, cz]; + if (neighborChunk == null) return true; + var neighborBlock = neighborChunk[x & 0b1111, y & 0b1111, z & 0b1111]; + return neighborBlock.Data.Data == 0; + } +} diff --git a/src/gaemstone.Bloxel/Neighbor.cs b/src/gaemstone.Bloxel/Neighbor.cs new file mode 100644 index 0000000..6e7643b --- /dev/null +++ b/src/gaemstone.Bloxel/Neighbor.cs @@ -0,0 +1,205 @@ +using System; +using System.Collections.Immutable; +using System.Text; +using Silk.NET.Maths; + +namespace gaemstone.Bloxel; + +[Flags] +public enum Neighbor : byte +{ + None = 0, + + // FACINGS + East = 0b000011, // +X + West = 0b000010, // -X + Up = 0b001100, // +Y + Down = 0b001000, // -Y + South = 0b110000, // +Z + North = 0b100000, // -Z + + // CARDINALS + SouthEast = South | East, // +X +Z + SouthWest = South | West, // -X +Z + NorthEast = North | East, // +X -Z + NorthWest = North | West, // -X -Z + + // ALL_AXIS_PLANES + UpEast = Up | East , // +X +Y + UpWest = Up | West , // -X +Y + UpSouth = Up | South, // +Z +Y + UpNorth = Up | North, // -Z +Y + + DownEast = Down | East , // +X -Y + DownWest = Down | West , // -X -Y + DownSouth = Down | South, // +Z -Y + DownNorth = Down | North, // -Z -Y + + // ALL + UpSouthEast = Up | South | East, // +X +Y +Z + UpSouthWest = Up | South | West, // -X +Y +Z + UpNorthEast = Up | North | East, // +X +Y -Z + UpNorthWest = Up | North | West, // -X +Y -Z + + DownSouthEast = Down | South | East, // +X -Y +Z + DownSouthWest = Down | South | West, // -X -Y +Z + DownNorthEast = Down | North | East, // +X -Y -Z + DownNorthWest = Down | North | West, // -X -Y -Z +} + +public static class Neighbors +{ + public static readonly ImmutableHashSet Horizontals + = ImmutableHashSet.Create(Neighbor.East , Neighbor.West , + Neighbor.South, Neighbor.North); + + public static readonly ImmutableHashSet Verticals + = ImmutableHashSet.Create(Neighbor.Up, Neighbor.Down); + + public static readonly ImmutableHashSet Facings + = Horizontals.Union(Verticals); + + public static readonly ImmutableHashSet Cardinals + = Horizontals.Union(new[] { + Neighbor.SouthEast, Neighbor.SouthWest, + Neighbor.NorthEast, Neighbor.NorthWest }); + + public static readonly ImmutableHashSet AllAxisPlanes + = Facings.Union(new[] { + Neighbor.SouthEast, Neighbor.SouthWest, + Neighbor.NorthEast, Neighbor.NorthWest, + Neighbor.UpEast , Neighbor.UpWest , + Neighbor.UpSouth , Neighbor.UpNorth , + Neighbor.DownEast , Neighbor.DownWest , + Neighbor.DownSouth, Neighbor.DownNorth }); + + public static readonly ImmutableHashSet All + = AllAxisPlanes.Union(new[] { + Neighbor.UpSouthEast, Neighbor.UpSouthWest, + Neighbor.UpNorthEast, Neighbor.UpNorthWest, + Neighbor.DownSouthEast, Neighbor.DownSouthWest, + Neighbor.DownNorthEast, Neighbor.DownNorthWest }); +} + +public static class NeighborExtensions +{ + const int SetBitX = 0b000010, ValueBitX = 0b000001; + const int SetBitY = 0b001000, ValueBitY = 0b000100; + const int SetBitZ = 0b100000, ValueBitZ = 0b010000; + public static void Deconstruct(this Neighbor self, out int x, out int y, out int z) + { + x = (((int)self & SetBitX) != 0) ? ((((int)self & ValueBitX) != 0) ? 1 : -1) : 0; + y = (((int)self & SetBitY) != 0) ? ((((int)self & ValueBitY) != 0) ? 1 : -1) : 0; + z = (((int)self & SetBitZ) != 0) ? ((((int)self & ValueBitZ) != 0) ? 1 : -1) : 0; + } + + + // public static Neighbor ToNeighbor(this Axis self, int v) + // { + // if ((v < -1) || (v > 1)) throw new ArgumentOutOfRangeException( + // nameof(v), v, $"{nameof(v)} (={v}) must be within (-1, 1)"); + // return self switch { + // Axis.X => (v > 0) ? Neighbor.East : Neighbor.West , + // Axis.Y => (v > 0) ? Neighbor.Up : Neighbor.Down , + // Axis.Z => (v > 0) ? Neighbor.South : Neighbor.North, + // _ => Neighbor.None + // }; + // } + + // public static Axis GetAxis(this Neighbor self) + // => self switch { + // Neighbor.East => Axis.X, + // Neighbor.West => Axis.X, + // Neighbor.Up => Axis.Y, + // Neighbor.Down => Axis.Y, + // Neighbor.South => Axis.Z, + // Neighbor.North => Axis.Z, + // _ => throw new ArgumentException(nameof(self), $"{self} is not one of FACINGS") + // }; + + + public static Neighbor ToNeighbor(this BlockFacing self) + => self switch { + BlockFacing.East => Neighbor.East , + BlockFacing.West => Neighbor.West , + BlockFacing.Up => Neighbor.Up , + BlockFacing.Down => Neighbor.Down , + BlockFacing.South => Neighbor.South, + BlockFacing.North => Neighbor.North, + _ => throw new ArgumentException( + $"'{self}' is not a valid BlockFacing", nameof(self)) + }; + + public static BlockFacing ToBlockFacing(this Neighbor self) + => self switch { + Neighbor.East => BlockFacing.East , + Neighbor.West => BlockFacing.West , + Neighbor.Up => BlockFacing.Up , + Neighbor.Down => BlockFacing.Down , + Neighbor.South => BlockFacing.South, + Neighbor.North => BlockFacing.North, + _ => throw new ArgumentException( + $"'{self}' can't be converted to a valid BlockFacing", nameof(self)) + }; + + + public static Neighbor ToNeighbor(this (int x, int y, int z) p) + { + var neighbor = Neighbor.None; + if (p.x != 0) { + if (p.x == 1) neighbor |= Neighbor.East; + else if (p.x == -1) neighbor |= Neighbor.West; + else throw new ArgumentOutOfRangeException( + nameof(p), p.x, $"{nameof(p)}.x (={p.x}) must be within (-1, 1)"); + } + if (p.y != 0) { + if (p.y == 1) neighbor |= Neighbor.Up; + else if (p.y == -1) neighbor |= Neighbor.Down; + else throw new ArgumentOutOfRangeException( + nameof(p), p.y, $"{nameof(p)}.y (={p.y}) must be within (-1, 1)"); + } + if (p.z != 0) { + if (p.z == 1) neighbor |= Neighbor.South; + else if (p.z == -1) neighbor |= Neighbor.North; + else throw new ArgumentOutOfRangeException( + nameof(p), p.z, $"{nameof(p)}.z (={p.z}) must be within (-1, 1)"); + } + return neighbor; + } + + public static Neighbor GetOpposite(this Neighbor self) + { var (x, y, z) = self; return (-x, -y, -z).ToNeighbor(); } + + + public static BlockPos ToProperPos(this Neighbor self) + { var (x, y, z) = self; return new(x, y, z); } + + public static Vector3D ToVector3(this Neighbor self) + { var (x, y, z) = self; return new(x, y, z); } + + + public static bool IsNone(this Neighbor self) + => (self == Neighbor.None); + + public static bool IsHorizontal(this Neighbor self) + => Neighbors.Horizontals.Contains(self); + public static bool IsVertical(this Neighbor self) + => Neighbors.Verticals.Contains(self); + public static bool IsCardinal(this Neighbor self) + => Neighbors.Cardinals.Contains(self); + public static bool IsFacing(this Neighbor self) + => Neighbors.Facings.Contains(self); + public static bool IsValid(this Neighbor self) + => Neighbors.All.Contains(self); + + + public static string ToShortString(this Neighbor self) + { + if (!self.IsValid()) return "-"; + var sb = new StringBuilder(3); + foreach (var chr in self.ToString()) + if ((chr >= 'A') && (chr <= 'Z')) // ASCII IsUpper + sb.Append(chr + 0x20); // ASCII ToLower + return sb.ToString(); + } +} diff --git a/src/gaemstone.Bloxel/Utility/ChunkedOctree.cs b/src/gaemstone.Bloxel/Utility/ChunkedOctree.cs new file mode 100644 index 0000000..d28a394 --- /dev/null +++ b/src/gaemstone.Bloxel/Utility/ChunkedOctree.cs @@ -0,0 +1,135 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace gaemstone.Bloxel.Utility; + +public class ChunkedOctree + where T : struct +{ + public delegate void UpdateAction(int level, ReadOnlySpan children, ref T parent); + public delegate float? WeightFunc(int level, ZOrder pos, T value); + + private static readonly int[] START_INDEX_LOOKUP = { + 0, 1, 9, 73, 585, 4681, 37449, 299593, 2396745, 19173961, 153391689 }; + + + private readonly IEqualityComparer _comparer = EqualityComparer.Default; + private readonly Dictionary _regions = new(); + + public int Depth { get; } + + public ChunkedOctree(int depth) + { + if (depth < 1) throw new ArgumentOutOfRangeException(nameof(depth), + $"{nameof(depth)} must be larger than 0"); + if (depth >= START_INDEX_LOOKUP.Length) throw new ArgumentOutOfRangeException(nameof(depth), + $"{nameof(depth)} must be smaller than {START_INDEX_LOOKUP.Length}"); + Depth = depth; + } + + public T Get(ChunkPos pos) + => Get(0, new(pos.X, pos.Y, pos.Z)); + public T Get(int level, ZOrder pos) + { + var region = _regions.GetValueOrDefault(pos >> Depth - level); + if (region == null) return default; + var localPos = pos & ~(~0L << (Depth - level) * 3); + return region[GetIndex(level, localPos)]; + } + private int GetIndex(int level, ZOrder localPos) + => START_INDEX_LOOKUP[Depth - level] + (int)localPos.Raw; + + public void Update(ChunkPos pos, UpdateAction update) + { + var zPos = new ZOrder(pos.X, pos.Y, pos.Z); + var localPos = zPos & ~(~0L << Depth * 3); + var regionPos = zPos >> Depth; + + if (!_regions.TryGetValue(regionPos, out var region)) + _regions.Add(regionPos, region = new T[START_INDEX_LOOKUP[Depth + 1] + 1]); + + var children = default(ReadOnlySpan); + for (var level = 0; level <= Depth; level++) + { + var index = GetIndex(level, localPos); + + var previous = region[index]; + update(0, children, ref region[index]); + if (_comparer.Equals(region[index], previous)) return; + + if (level == Depth) return; + children = region.AsSpan(GetIndex(level, localPos & ~0b111L), 8); + localPos >>= 1; + } + } + + public IEnumerable<(ChunkPos ChunkPos, T Value, float Weight)> Find( + WeightFunc weight, params ChunkPos[] searchFrom) + { + var enumerator = new Enumerator(this, weight); + foreach (var pos in searchFrom) enumerator.SearchFrom(new(pos.X, pos.Y, pos.Z)); + while (enumerator.MoveNext()) yield return enumerator.Current; + } + + public class Enumerator + : IEnumerator<(ChunkPos ChunkPos, T Value, float Weight)> + { + private readonly ChunkedOctree _octree; + private readonly WeightFunc _weight; + + private readonly HashSet _checkedRegions = new(); + private readonly PriorityQueue<(int Level, ZOrder Pos, T Value), float> _processing = new(); + private (ChunkPos ChunkPos, T Value, float Weight)? _current; + + internal Enumerator(ChunkedOctree octree, WeightFunc weight) + { _octree = octree; _weight = weight; _current = null; } + + public (ChunkPos ChunkPos, T Value, float Weight) Current + => _current ?? throw new InvalidOperationException(); + object IEnumerator.Current => Current; + + public bool MoveNext() + { + while (_processing.TryDequeue(out var element, out var weight)) + { + var (level, nodePos, value) = element; + if (level == 0) + { + _current = (new(nodePos.X, nodePos.Y, nodePos.Z), value, weight); + return true; + } + else for (var i = 0b000; i <= 0b111; i++) + PushNode(level - 1, nodePos << 1 | ZOrder.FromRaw(i)); + } + _current = null; + return false; + } + + public void Reset() => throw new NotSupportedException(); + public void Dispose() { } + + + internal void SearchFrom(ZOrder nodePos) + { + var regionPos = nodePos >> _octree.Depth; + for (var x = -1; x <= 1; x++) + for (var y = -1; y <= 1; y++) + for (var z = -1; z <= 1; z++) + SearchRegion(regionPos + new ZOrder(x, y, z)); + } + + private void SearchRegion(ZOrder regionPos) + { + if (_checkedRegions.Add(regionPos)) + PushNode(_octree.Depth, regionPos); + } + + private void PushNode(int level, ZOrder nodePos) + { + var value = _octree.Get(level, nodePos); + if (_weight(level, nodePos, value) is float weight) + _processing.Enqueue((level, nodePos, value), weight); + } + } +} diff --git a/src/gaemstone.Bloxel/Utility/ZOrder.cs b/src/gaemstone.Bloxel/Utility/ZOrder.cs new file mode 100644 index 0000000..2047a9b --- /dev/null +++ b/src/gaemstone.Bloxel/Utility/ZOrder.cs @@ -0,0 +1,154 @@ +using System; + +namespace gaemstone.Bloxel.Utility; + +// This struct wraps a primitive integer which represents an index into a space-filling curve +// called "Z-Order Curve" (https://en.wikipedia.org/wiki/Z-order_curve). Often, this is also +// referred to as Morton order, code, or encoding. +// +// This implementation purely focuses on 3 dimensions. +// +// By interleaving the 3 sub-elements into a single integer, some amount of packing can be +// achieved, at the loss of some bits per elements. For example, with a 64 bit integer, 21 +// bits per elements are available (2_097_152 distinct values), which may be enough to +// represent block coordinates in a bloxel game world. +// +// One upside of encoding separate coordinates into a single Z-Order index is that it can then +// be effectively used to index into octrees, and certain operations such as bitwise shifting +// are quite useful. +public readonly struct ZOrder + : IEquatable + , IComparable +{ + public const int ELEMENT_MIN = ~0 << BITS_PER_ELEMENT - 1; + public const int ELEMENT_MAX = ~ELEMENT_MIN; + + private const int BITS_SIZE = sizeof(long) * 8; + private const int BITS_PER_ELEMENT = BITS_SIZE / 3; + private const int MAX_USABLE_BITS = BITS_PER_ELEMENT * 3; + private const int SIGN_SHIFT = sizeof(int) * 8 - BITS_PER_ELEMENT; + private const long USABLE_MASK = ~(~0L << MAX_USABLE_BITS); + private const long COMPARE_MASK = ~(~0L << 3) << MAX_USABLE_BITS - 3; + + private static readonly ulong[] MASKS = { + 0b_00000000_00000000_00000000_00000000_00000000_00011111_11111111_11111111, // 0x1fffff + 0b_00000000_00011111_00000000_00000000_00000000_00000000_11111111_11111111, // 0x1f00000000ffff + 0b_00000000_00011111_00000000_00000000_11111111_00000000_00000000_11111111, // 0x1f0000ff0000ff + 0b_00010000_00001111_00000000_11110000_00001111_00000000_11110000_00001111, // 0x100f00f00f00f00f + 0b_00010000_11000011_00001100_00110000_11000011_00001100_00110000_11000011, // 0x10c30c30c30c30c3 + 0b_00010010_01001001_00100100_10010010_01001001_00100100_10010010_01001001, // 0x1249249249249249 + }; + + private static readonly long X_MASK = (long)MASKS[MASKS.Length - 1]; + private static readonly long Y_MASK = X_MASK << 1; + private static readonly long Z_MASK = X_MASK << 2; + private static readonly long XY_MASK = X_MASK | Y_MASK; + private static readonly long XZ_MASK = X_MASK | Z_MASK; + private static readonly long YZ_MASK = Y_MASK | Z_MASK; + + + public long Raw { get; } + + public int X => Decode(0); + public int Y => Decode(1); + public int Z => Decode(2); + + + private ZOrder(long value) + => Raw = value; + + public static ZOrder FromRaw(long value) + => new(value & USABLE_MASK); + + public ZOrder(int x, int y, int z) + { + if (x < ELEMENT_MIN || x > ELEMENT_MAX) throw new ArgumentOutOfRangeException(nameof(x)); + if (y < ELEMENT_MIN || y > ELEMENT_MAX) throw new ArgumentOutOfRangeException(nameof(y)); + if (z < ELEMENT_MIN || z > ELEMENT_MAX) throw new ArgumentOutOfRangeException(nameof(z)); + Raw = Split(x) | Split(y) << 1 | Split(z) << 2; + } + + public void Deconstruct(out int x, out int y, out int z) + => (x, y, z) = (X, Y, Z); + + + public ZOrder IncX() => FromRaw((Raw | YZ_MASK) + 1 & X_MASK | Raw & YZ_MASK); + public ZOrder IncY() => FromRaw((Raw | XZ_MASK) + (1 << 1) & Y_MASK | Raw & XZ_MASK); + public ZOrder IncZ() => FromRaw((Raw | XY_MASK) + (1 << 2) & Z_MASK | Raw & XY_MASK); + + public ZOrder DecX() => FromRaw((Raw & X_MASK) - 1 & X_MASK | Raw & YZ_MASK); + public ZOrder DecY() => FromRaw((Raw & Y_MASK) - (1 << 1) & Y_MASK | Raw & XZ_MASK); + public ZOrder DecZ() => FromRaw((Raw & Z_MASK) - (1 << 2) & Z_MASK | Raw & XY_MASK); + + public static ZOrder operator +(ZOrder left, ZOrder right) + { + var xSum = (left.Raw | YZ_MASK) + (right.Raw & X_MASK); + var ySum = (left.Raw | XZ_MASK) + (right.Raw & Y_MASK); + var zSum = (left.Raw | XY_MASK) + (right.Raw & Z_MASK); + return FromRaw(xSum & X_MASK | ySum & Y_MASK | zSum & Z_MASK); + } + + public static ZOrder operator -(ZOrder left, ZOrder right) + { + var xDiff = (left.Raw & X_MASK) - (right.Raw & X_MASK); + var yDiff = (left.Raw & Y_MASK) - (right.Raw & Y_MASK); + var zDiff = (left.Raw & Z_MASK) - (right.Raw & Z_MASK); + return FromRaw(xDiff & X_MASK | yDiff & Y_MASK | zDiff & Z_MASK); + } + + public static ZOrder operator &(ZOrder left, long right) => FromRaw(left.Raw & right); + public static ZOrder operator |(ZOrder left, long right) => FromRaw(left.Raw | right); + public static ZOrder operator ^(ZOrder left, long right) => FromRaw(left.Raw ^ right); + + public static ZOrder operator &(ZOrder left, ZOrder right) => new(left.Raw & right.Raw); + public static ZOrder operator |(ZOrder left, ZOrder right) => new(left.Raw | right.Raw); + public static ZOrder operator ^(ZOrder left, ZOrder right) => new(left.Raw ^ right.Raw); + + public static ZOrder operator <<(ZOrder left, int right) + { + if (right >= BITS_PER_ELEMENT) throw new ArgumentOutOfRangeException( + nameof(right), right, $"{nameof(right)} must be smaller than {BITS_PER_ELEMENT}"); + return FromRaw(left.Raw << right * 3); + } + public static ZOrder operator >>(ZOrder left, int right) + { + var result = left.Raw >> right * 3; + var mask = left.Raw >> MAX_USABLE_BITS - 3 << MAX_USABLE_BITS - right * 3; + for (var i = 0; i < right; i++) { result |= mask; mask <<= 3; } + return FromRaw(result); + } + + public int CompareTo(ZOrder other) => (Raw ^ COMPARE_MASK).CompareTo(other.Raw ^ COMPARE_MASK); + public bool Equals(ZOrder other) => Raw.Equals(other.Raw); + public override bool Equals(object? obj) => obj is ZOrder order && Equals(order); + public override int GetHashCode() => Raw.GetHashCode(); + public override string ToString() => $"<{X},{Y},{Z}>"; + + public static bool operator ==(ZOrder left, ZOrder right) => left.Equals(right); + public static bool operator !=(ZOrder left, ZOrder right) => !left.Equals(right); + + + private static long Split(int i) + { + var l = (ulong)i; + // l = l & Masks[0]; + l = (l | l << 32) & MASKS[1]; + l = (l | l << 16) & MASKS[2]; + l = (l | l << 8) & MASKS[3]; + l = (l | l << 4) & MASKS[4]; + l = (l | l << 2) & MASKS[5]; + return (long)l; + } + + private int Decode(int index) + { + var l = (ulong)Raw >> index; + l &= MASKS[5]; + l = (l ^ l >> 2) & MASKS[4]; + l = (l ^ l >> 4) & MASKS[3]; + l = (l ^ l >> 8) & MASKS[2]; + l = (l ^ l >> 16) & MASKS[1]; + l = (l ^ l >> 32) & MASKS[0]; + return (int)l << SIGN_SHIFT >> SIGN_SHIFT; + } +} diff --git a/src/gaemstone.Bloxel/WorldGen/BasicWorldGenerator.cs b/src/gaemstone.Bloxel/WorldGen/BasicWorldGenerator.cs new file mode 100644 index 0000000..354bb2b --- /dev/null +++ b/src/gaemstone.Bloxel/WorldGen/BasicWorldGenerator.cs @@ -0,0 +1,42 @@ +using System; +using gaemstone.ECS; +using static flecs_hub.flecs; + +namespace gaemstone.Bloxel.WorldGen; + +[Module] +public class BasicWorldGenerator +{ + private readonly FastNoiseLite _noise; + + public BasicWorldGenerator() + { + _noise = new(new Random().Next()); + _noise.SetNoiseType(FastNoiseLite.NoiseType.OpenSimplex2); + _noise.SetFractalType(FastNoiseLite.FractalType.Ridged); + _noise.SetFractalOctaves(4); + _noise.SetFractalGain(0.6F); + } + + [Tag] + public struct HasBasicWorldGeneration { } + + [System] + public void Populate(Universe universe, Entity entity, + in Chunk chunk, ChunkPaletteStorage storage, + [Not] HasBasicWorldGeneration _) + { + var stone = universe.Lookup("Stone"); + for (var lx = 0; lx < Chunk.LENGTH; lx++) + for (var ly = 0; ly < Chunk.LENGTH; ly++) + for (var lz = 0; lz < Chunk.LENGTH; lz++) { + var gx = chunk.Position.X << Chunk.BIT_SHIFT | lx; + var gy = chunk.Position.Y << Chunk.BIT_SHIFT | ly; + var gz = chunk.Position.Z << Chunk.BIT_SHIFT | lz; + var bias = Math.Clamp(gy / 32.0F + 1.0F, 0.0F, 1.0F); + if (_noise.GetNoise(gx, gy, gz) > bias) + storage[lx, ly, lz] = stone; + } + entity.Add(); + } +} diff --git a/src/gaemstone.Bloxel/WorldGen/SurfaceGrassGenerator.cs.disabled b/src/gaemstone.Bloxel/WorldGen/SurfaceGrassGenerator.cs.disabled new file mode 100644 index 0000000..a615398 --- /dev/null +++ b/src/gaemstone.Bloxel/WorldGen/SurfaceGrassGenerator.cs.disabled @@ -0,0 +1,57 @@ +using System.Collections.Generic; + +namespace gaemstone.Bloxel.WorldGen; + +// FIXME: There is an issue with this generator where it doesn't generate grass and dirt properly. +public class SurfaceGrassGenerator + : IWorldGenerator +{ + public static readonly string IDENTIFIER = nameof(SurfaceGrassGenerator); + + private const int AIR_BLOCKS_NEEDED = 12; + private const int DIRT_BLOCKS_BENEATH = 3; + + public string Identifier { get; } = IDENTIFIER; + + public IEnumerable Dependencies { get; } = new[]{ + BasicWorldGenerator.IDENTIFIER + }; + + public IEnumerable<(Neighbor, string)> NeighborDependencies { get; } = new[]{ + (Neighbor.Up, BasicWorldGenerator.IDENTIFIER) + }; + + public void Populate(Chunk chunk) + { + var up = chunk.Neighbors[Neighbor.Up]!; + for (var lx = 0; lx < Chunk.LENGTH; lx++) + for (var lz = 0; lz < Chunk.LENGTH; lz++) + { + var numAirBlocks = 0; + var blockIndex = 0; + for (var ly = Chunk.LENGTH + AIR_BLOCKS_NEEDED - 1; ly >= 0; ly--) + { + var block = ly >= Chunk.LENGTH + ? up.Storage[lx, ly - Chunk.LENGTH, lz] + : chunk.Storage[lx, ly, lz]; + if (block.IsAir) + { + numAirBlocks++; + blockIndex = 0; + } + else if (numAirBlocks >= AIR_BLOCKS_NEEDED || blockIndex > 0) + { + if (ly < Chunk.LENGTH) + { + if (blockIndex == 0) + chunk.Storage[lx, ly, lz] = Block.GRASS; + else if (blockIndex <= DIRT_BLOCKS_BENEATH) + chunk.Storage[lx, ly, lz] = Block.DIRT; + } + blockIndex++; + numAirBlocks = 0; + } + } + } + } +} diff --git a/src/gaemstone.Bloxel/gaemstone.Bloxel.csproj b/src/gaemstone.Bloxel/gaemstone.Bloxel.csproj new file mode 100644 index 0000000..d017c6b --- /dev/null +++ b/src/gaemstone.Bloxel/gaemstone.Bloxel.csproj @@ -0,0 +1,18 @@ + + + + net6.0 + disable + enable + + + + + + + + + + + + diff --git a/src/gaemstone.Client/Color.cs b/src/gaemstone.Client/Color.cs new file mode 100644 index 0000000..ba7bc4d --- /dev/null +++ b/src/gaemstone.Client/Color.cs @@ -0,0 +1,56 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Silk.NET.Maths; + +namespace gaemstone.Client; + +[StructLayout(LayoutKind.Explicit)] +public readonly struct Color + : IEquatable +{ + public static Color Transparent { get; } = default; + + public static Color Black { get; } = FromRGB(0x000000); + public static Color White { get; } = FromRGB(0xFFFFFF); + + [FieldOffset(0)] + public readonly uint Value; + + [FieldOffset(0)] + public readonly byte R; + [FieldOffset(1)] + public readonly byte G; + [FieldOffset(2)] + public readonly byte B; + [FieldOffset(3)] + public readonly byte A; + + private Color(uint value) + { Unsafe.SkipInit(out this); Value = value; } + private Color(byte r, byte g, byte b, byte a) + { Unsafe.SkipInit(out this); R = r; G = g; B = b; A = a; } + + public static Color FromRGBA(uint rgba) => new(rgba); + public static Color FromRGB(uint rgb) => new(rgb | 0xFF000000); + + public static Color FromRGBA(byte r, byte g, byte b, byte a) => new(r, g, b, a); + public static Color FromRGB(byte r, byte g, byte b) => new(r, g, b, 0xFF); + + public bool Equals(Color other) + => Value == other.Value; + public override bool Equals([NotNullWhen(true)] object? obj) + => (obj is Color color) && Equals(color); + public override int GetHashCode() + => Value.GetHashCode(); + public override string? ToString() + => $"Color(0x{Value:X8})"; + + public static bool operator ==(Color left, Color right) => left.Equals(right); + public static bool operator !=(Color left, Color right) => !left.Equals(right); + + public static implicit operator System.Drawing.Color(Color color) => System.Drawing.Color.FromArgb(color.A, color.R, color.G, color.B); + public static implicit operator Vector4D(Color color) => new(color.R / 255F, color.G / 255F, color.B / 255F, color.A / 255F); + public static implicit operator Vector4D(Color color) => new(color.R, color.G, color.B, color.A); +} diff --git a/src/gaemstone.Client/GLExtensions.cs b/src/gaemstone.Client/GLExtensions.cs new file mode 100644 index 0000000..d163860 --- /dev/null +++ b/src/gaemstone.Client/GLExtensions.cs @@ -0,0 +1,62 @@ +using System; +using System.Runtime.CompilerServices; +using Silk.NET.OpenGL; + +namespace gaemstone.Client; + +public static class GLExtensions +{ + public static uint CreateAndCompileShader(this GL GL, ShaderType type, string label, string source) + { + var shader = GL.CreateShader(type); + GL.ObjectLabel(ObjectIdentifier.Shader, shader, (uint)label.Length, label); + GL.ShaderSource(shader, source); + GL.CompileShader(shader); + GL.GetShader(shader, ShaderParameterName.CompileStatus, out var result); + if (result != (int)GLEnum.True) throw new Exception( + $"Failed compiling shader \"{label}\" ({shader}):\n{GL.GetShaderInfoLog(shader)}"); + return shader; + } + + public static uint CreateAndLinkProgram(this GL GL, string label, params uint[] shaders) + { + var program = GL.CreateProgram(); + GL.ObjectLabel(ObjectIdentifier.Program, program, (uint)label.Length, label); + foreach (var shader in shaders) GL.AttachShader(program, shader); + GL.LinkProgram(program); + foreach (var shader in shaders) GL.DetachShader(program, shader); + foreach (var shader in shaders) GL.DeleteShader(shader); + GL.GetProgram(program, ProgramPropertyARB.LinkStatus, out var result); + if (result != (int)GLEnum.True) throw new Exception( + $"Failed linking Program \"{label}\" ({program}):\n{GL.GetProgramInfoLog(program)}"); + return program; + } + + // These overloads are available because without them, the implicit casting + // (say from T[] to ReadOnlySpan) causes the generic type resolving to break. + public static uint CreateBufferFromData(this GL GL, T[] data, + BufferTargetARB target = BufferTargetARB.ArrayBuffer, + BufferUsageARB usage = BufferUsageARB.StaticDraw) + where T : unmanaged + => GL.CreateBufferFromData((ReadOnlySpan)data, target, usage); + public static uint CreateBufferFromData(this GL GL, ArraySegment data, + BufferTargetARB target = BufferTargetARB.ArrayBuffer, + BufferUsageARB usage = BufferUsageARB.StaticDraw) + where T : unmanaged + => GL.CreateBufferFromData((ReadOnlySpan)data, target, usage); + public static uint CreateBufferFromData(this GL GL, Span data, + BufferTargetARB target = BufferTargetARB.ArrayBuffer, + BufferUsageARB usage = BufferUsageARB.StaticDraw) + where T : unmanaged + => GL.CreateBufferFromData((ReadOnlySpan)data, target, usage); + public static uint CreateBufferFromData(this GL GL, ReadOnlySpan data, + BufferTargetARB target = BufferTargetARB.ArrayBuffer, + BufferUsageARB usage = BufferUsageARB.StaticDraw) + where T : unmanaged + { + var buffer = GL.GenBuffer(); + GL.BindBuffer(target, buffer); + GL.BufferData(target, (nuint)(data.Length * Unsafe.SizeOf()), data, usage); + return buffer; + } +} diff --git a/src/gaemstone.Client/Mesh.cs b/src/gaemstone.Client/Mesh.cs new file mode 100644 index 0000000..f0cacbf --- /dev/null +++ b/src/gaemstone.Client/Mesh.cs @@ -0,0 +1,14 @@ +using gaemstone.ECS; + +namespace gaemstone.Client; + +[Component] +public readonly struct Mesh +{ + public uint Handle { get; } + public int Count { get; } + public bool IsIndexed { get; } + + public Mesh(uint handle, int count, bool indexed = true) + { Handle = handle; Count = count; IsIndexed = indexed; } +} diff --git a/src/gaemstone.Client/MeshManager.cs b/src/gaemstone.Client/MeshManager.cs new file mode 100644 index 0000000..b2434fc --- /dev/null +++ b/src/gaemstone.Client/MeshManager.cs @@ -0,0 +1,104 @@ +using System; +using gaemstone.ECS; +using Silk.NET.Maths; +using Silk.NET.OpenGL; +using ModelRoot = SharpGLTF.Schema2.ModelRoot; + +namespace gaemstone.Client; + +public static class MeshManager +{ + const uint PositionAttribIndex = 0; + const uint NormalAttribIndex = 1; + const uint UvAttribIndex = 2; + + public static Mesh Load(Universe universe, string name) + { + ModelRoot root; + using (var stream = Resources.GetStream(name)) + root = ModelRoot.ReadGLB(stream, new()); + var primitive = root.LogicalMeshes[0].Primitives[0]; + + var indices = primitive.IndexAccessor; + var vertices = primitive.VertexAccessors["POSITION"]; + var normals = primitive.VertexAccessors["NORMAL"]; + + var GL = universe.Lookup().Get().GL; + var vao = GL.GenVertexArray(); + GL.BindVertexArray(vao); + + GL.CreateBufferFromData(indices.SourceBufferView.Content, + BufferTargetARB.ElementArrayBuffer); + + GL.CreateBufferFromData(vertices.SourceBufferView.Content); + GL.EnableVertexAttribArray(PositionAttribIndex); + unsafe { GL.VertexAttribPointer(PositionAttribIndex, 3, + (VertexAttribPointerType)vertices.Encoding, vertices.Normalized, + (uint)vertices.SourceBufferView.ByteStride, (void*)vertices.ByteOffset); } + + GL.CreateBufferFromData(normals.SourceBufferView.Content); + GL.EnableVertexAttribArray(NormalAttribIndex); + unsafe { GL.VertexAttribPointer(NormalAttribIndex, 3, + (VertexAttribPointerType)vertices.Encoding, vertices.Normalized, + (uint)vertices.SourceBufferView.ByteStride, (void*)vertices.ByteOffset); } + + var numVertices = primitive.IndexAccessor.Count; + return new(vao, numVertices); + } + + public static Mesh Create(Universe universe, + ReadOnlySpan indices, ReadOnlySpan> vertices, + ReadOnlySpan> normals, ReadOnlySpan> uvs) + { + var GL = universe.Lookup().Get().GL; + var vao = GL.GenVertexArray(); + GL.BindVertexArray(vao); + + GL.CreateBufferFromData(indices, BufferTargetARB.ElementArrayBuffer); + GL.CreateBufferFromData(vertices); + GL.EnableVertexAttribArray(PositionAttribIndex); + unsafe { GL.VertexAttribPointer(PositionAttribIndex, 3, + VertexAttribPointerType.Float, false, 0, (void*)0); } + if (!normals.IsEmpty) { + GL.CreateBufferFromData(normals); + GL.EnableVertexAttribArray(NormalAttribIndex); + unsafe { GL.VertexAttribPointer(NormalAttribIndex, 3, + VertexAttribPointerType.Float, false, 0, (void*)0); } + } + if (!uvs.IsEmpty) { + GL.CreateBufferFromData(uvs); + GL.EnableVertexAttribArray(UvAttribIndex); + unsafe { GL.VertexAttribPointer(UvAttribIndex, 2, + VertexAttribPointerType.Float, false, 0, (void*)0); } + } + + return new(vao, indices.Length); + } + + public static Mesh Create(Universe universe, ReadOnlySpan> vertices, + ReadOnlySpan> normals, ReadOnlySpan> uvs) + { + var GL = universe.Lookup().Get().GL; + var vao = GL.GenVertexArray(); + GL.BindVertexArray(vao); + + GL.CreateBufferFromData(vertices); + GL.EnableVertexAttribArray(PositionAttribIndex); + unsafe { GL.VertexAttribPointer(PositionAttribIndex, 3, + VertexAttribPointerType.Float, false, 0, (void*)0); } + if (!normals.IsEmpty) { + GL.CreateBufferFromData(normals); + GL.EnableVertexAttribArray(NormalAttribIndex); + unsafe { GL.VertexAttribPointer(NormalAttribIndex, 3, + VertexAttribPointerType.Float, false, 0, (void*)0); } + } + if (!uvs.IsEmpty) { + GL.CreateBufferFromData(uvs); + GL.EnableVertexAttribArray(UvAttribIndex); + unsafe { GL.VertexAttribPointer(UvAttribIndex, 2, + VertexAttribPointerType.Float, false, 0, (void*)0); } + } + + return new(vao, vertices.Length, false); + } +} diff --git a/src/gaemstone.Client/Modules/CameraModule.cs b/src/gaemstone.Client/Modules/CameraModule.cs new file mode 100644 index 0000000..522851b --- /dev/null +++ b/src/gaemstone.Client/Modules/CameraModule.cs @@ -0,0 +1,81 @@ +using System; +using gaemstone.ECS; +using Silk.NET.Input; +using Silk.NET.Maths; +using static gaemstone.Client.Input; + +namespace gaemstone.Client; + +[Module] +[DependsOn(typeof(Input))] +public class CameraModule +{ + [Component] + public struct Camera + { + public static readonly Camera Default2D = Create2D(); + public static readonly Camera Default3D = Create3D(80.0F); + + public static Camera Create2D(float nearPlane = -100.0F, float farPlane = 100.0F) + => new() { NearPlane = nearPlane, FarPlane = farPlane }; + public static Camera Create3D(float fieldOfView, float nearPlane = 0.1F, float farPlane = 200.0F) + => new() { FieldOfView = fieldOfView, NearPlane = nearPlane, FarPlane = farPlane }; + + public float FieldOfView { get; set; } + public float NearPlane { get; set; } + public float FarPlane { get; set; } + + public bool IsOrthographic => (FieldOfView == 0.0F); + } + + [Component] + public struct CameraViewport + { + public Vector4D ClearColor { get; set; } + public Rectangle Viewport { get; set; } + } + + [Component] + public struct CameraController + { + public float MouseSensitivity { get; set; } + public Vector2D? MouseGrabbedAt { get; set; } + } + + [System] + public static void UpdateCamera(TimeSpan delta, in Camera camera, + ref GlobalTransform transform, ref CameraController controller, + [Source(typeof(Game))] RawInput input) + { + var isMouseDown = input.IsDown(MouseButton.Right); + var isMouseGrabbed = controller.MouseGrabbedAt != null; + if (isMouseDown != isMouseGrabbed) { + if (isMouseDown) controller.MouseGrabbedAt = input.MousePosition; + else controller.MouseGrabbedAt = null; + } + + if (controller.MouseGrabbedAt is not Vector2D pos) return; + + var mouseMoved = input.MousePosition - pos; + input.Context!.Mice[0].Position = pos.ToSystem(); + + var dt = (float)delta.TotalSeconds; + var xMovement = mouseMoved.X * dt * controller.MouseSensitivity; + var yMovement = mouseMoved.Y * dt * controller.MouseSensitivity; + + if (camera.IsOrthographic) { + transform *= Matrix4X4.CreateTranslation(-xMovement, -yMovement, 0); + } else { + var speed = dt * (input.IsDown(Key.ShiftLeft) ? 12 : 4); + var forwardMovement = ((input.IsDown(Key.W) ? -1 : 0) + (input.IsDown(Key.S) ? 1 : 0)) * speed; + var sideMovement = ((input.IsDown(Key.A) ? -1 : 0) + (input.IsDown(Key.D) ? 1 : 0)) * speed; + + var curTranslation = new Vector3D(transform.Value.M41, transform.Value.M42, transform.Value.M43); + var yawRotation = Matrix4X4.CreateRotationY(-xMovement / 100, curTranslation); + var pitchRotation = Matrix4X4.CreateRotationX(-yMovement / 100); + var translation = Matrix4X4.CreateTranslation(sideMovement, 0, forwardMovement); + + transform = translation * pitchRotation * transform * yawRotation; + } + } +} diff --git a/src/gaemstone.Client/Modules/Input.cs b/src/gaemstone.Client/Modules/Input.cs new file mode 100644 index 0000000..b8d60f1 --- /dev/null +++ b/src/gaemstone.Client/Modules/Input.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using gaemstone.ECS; +using Silk.NET.Input; +using Silk.NET.Maths; +using static gaemstone.Client.Windowing; + +namespace gaemstone.Client; + +[Module] +[DependsOn(typeof(Windowing))] +public class Input +{ + [Component] + public class RawInput + { + internal IInputContext? Context { get; set; } + + public Dictionary Keyboard { get; } = new(); + public Dictionary MouseButtons { get; } = new(); + public Vector2D MousePosition { get; set; } + public float MouseWheel { get; set; } + public float MouseWheelDelta { get; set; } + + public bool IsDown(Key key) => Keyboard.GetValueOrDefault(key)?.IsDown == true; + public bool IsDown(MouseButton button) => MouseButtons.GetValueOrDefault(button)?.IsDown == true; + } + + public class ButtonState + { + public TimeSpan TimePressed; + public bool IsDown; + public bool Pressed; + public bool Released; + } + + [System(Phase.OnLoad)] + public static void ProcessInput(GameWindow window, RawInput input, TimeSpan delta) + { + window.Handle.DoEvents(); + + input.Context ??= window.Handle.CreateInput(); + + foreach (var state in input.Keyboard.Values.Concat(input.MouseButtons.Values)) { + if (state.IsDown) state.TimePressed += delta; + state.Pressed = state.Released = false; + } + + var keyboard = input.Context.Keyboards[0]; + foreach (var key in keyboard.SupportedKeys) { + var state = input.Keyboard.GetValueOrDefault(key); + if (keyboard.IsKeyPressed(key)) { + if (state == null) input.Keyboard.Add(key, state = new()); + if (!state.IsDown) state.Pressed = true; + state.IsDown = true; + } else if (state != null) { + if (state.IsDown) state.Released = true; + state.IsDown = false; + } + } + + var mouse = input.Context.Mice[0]; + foreach (var button in mouse.SupportedButtons) { + var state = input.MouseButtons.GetValueOrDefault(button); + if (mouse.IsButtonPressed(button)) { + if (state == null) input.MouseButtons.Add(button, state = new()); + if (!state.IsDown) state.Pressed = true; + state.IsDown = true; + } else if (state != null) { + if (state.IsDown) state.Released = true; + state.IsDown = false; + } + } + + input.MousePosition = mouse.Position.ToGeneric(); + + input.MouseWheelDelta += mouse.ScrollWheels[0].Y - input.MouseWheel; + input.MouseWheel = mouse.ScrollWheels[0].Y; + } +} diff --git a/src/gaemstone.Client/Modules/Renderer.cs b/src/gaemstone.Client/Modules/Renderer.cs new file mode 100644 index 0000000..8d8c533 --- /dev/null +++ b/src/gaemstone.Client/Modules/Renderer.cs @@ -0,0 +1,109 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using gaemstone.ECS; +using Silk.NET.Maths; +using Silk.NET.OpenGL; +using static gaemstone.Client.CameraModule; +using static gaemstone.Client.Windowing; + +namespace gaemstone.Client; + +[Module] +[DependsOn(typeof(Windowing))] +public class Renderer +{ + private readonly uint _program; + private readonly int _cameraMatrixUniform; + private readonly int _modelMatrixUniform; + + public Renderer(Universe universe) + { + var GL = universe.Lookup().Get().GL; + + GL.Enable(EnableCap.DebugOutputSynchronous); + GL.DebugMessageCallback(DebugCallback, 0); + + GL.Enable(EnableCap.CullFace); + GL.CullFace(CullFaceMode.Back); + + GL.Enable(EnableCap.DepthTest); + GL.DepthFunc(DepthFunction.Less); + + GL.Enable(EnableCap.Blend); + GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha); + + var vertexShaderSource = Resources.GetString("default.vs.glsl"); + var fragmentShaderSource = Resources.GetString("default.fs.glsl"); + + var vertexShader = GL.CreateAndCompileShader(ShaderType.VertexShader , "vertex" , vertexShaderSource); + var fragmentShader = GL.CreateAndCompileShader(ShaderType.FragmentShader, "fragment", fragmentShaderSource); + + _program = GL.CreateAndLinkProgram("program", vertexShader, fragmentShader); + _cameraMatrixUniform = GL.GetUniformLocation(_program, "cameraMatrix"); + _modelMatrixUniform = GL.GetUniformLocation(_program, "modelMatrix"); + } + + [System] + public void Render(Universe universe, Canvas canvas) + { + var GL = canvas.GL; + GL.UseProgram(_program); + GL.Viewport(default, canvas.Size); + GL.ClearColor(new Vector4D(0, 0, 0, 255)); + GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); + + Filter.RunOnce(universe, (in GlobalTransform transform, in Camera camera, CameraViewport? viewport) => { + var color = viewport?.ClearColor ?? new(0x4B, 0x00, 0x82, 255); + var bounds = viewport?.Viewport ?? new(default, canvas.Size); + + GL.Enable(EnableCap.ScissorTest); + GL.Viewport(bounds); GL.Scissor(bounds.Origin.X, bounds.Origin.Y, (uint)bounds.Size.X, (uint)bounds.Size.Y); + GL.ClearColor(color); GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); + GL.Disable(EnableCap.ScissorTest); + + // Get the camera's transform matrix and invert it. + Matrix4X4.Invert(transform, out var cameraTransform); + + // Create the camera's projection matrix. + var cameraProjection = camera.IsOrthographic + ? Matrix4X4.CreateOrthographic( + bounds.Size.X, -bounds.Size.Y, + camera.NearPlane, camera.FarPlane) + : Matrix4X4.CreatePerspectiveFieldOfView( + camera.FieldOfView * MathF.PI / 180, // Degrees => Radians + (float)bounds.Size.X / bounds.Size.Y, // Aspect Ratio + camera.NearPlane, camera.FarPlane); + + // Set the uniform to the combined transform and projection. + var cameraMatrix = cameraTransform * cameraProjection; + GL.UniformMatrix4(_cameraMatrixUniform, 1, false, in cameraMatrix.Row1.X); + + Filter.RunOnce(universe, (in GlobalTransform transform, in Mesh mesh, Texture? texture) => + { + // If entity has Texture, bind it now. + if (texture.HasValue) GL.BindTexture(texture.Value.Target, texture.Value.Handle); + + // Draw the mesh. + GL.UniformMatrix4(_modelMatrixUniform, 1, false, in transform.Value.Row1.X); + GL.BindVertexArray(mesh.Handle); + if (!mesh.IsIndexed) GL.DrawArrays(PrimitiveType.Triangles, 0, (uint)mesh.Count); + else unsafe { GL.DrawElements(PrimitiveType.Triangles, (uint)mesh.Count, DrawElementsType.UnsignedShort, null); } + + // If entity has Texture, unbind it after it has been rendered. + if (texture.HasValue) GL.BindTexture(texture.Value.Target, 0); + }); + }); + } + + [DebuggerStepThrough] + private static void DebugCallback(GLEnum source, GLEnum _type, int id, GLEnum _severity, + int length, nint _message, nint userParam) + { + var type = (DebugType)_type; + var severity = (DebugSeverity)_severity; + var message = Marshal.PtrToStringAnsi(_message, length); + Console.WriteLine($"[GLDebug] [{severity}] {type}/{id}: {message}"); + if (type == DebugType.DebugTypeError) throw new Exception(message); + } +} diff --git a/src/gaemstone.Client/Modules/Windowing.cs b/src/gaemstone.Client/Modules/Windowing.cs new file mode 100644 index 0000000..82d3345 --- /dev/null +++ b/src/gaemstone.Client/Modules/Windowing.cs @@ -0,0 +1,31 @@ +using gaemstone.ECS; +using Silk.NET.Maths; +using Silk.NET.OpenGL; +using Silk.NET.Windowing; + +namespace gaemstone.Client; + +[Module] +public class Windowing +{ + [Component] + public class Canvas + { + public GL GL { get; } + public Canvas(GL gl) => GL = gl; + + public Vector2D Size { get; set; } + public Color BackgroundColor { get; set; } + } + + [Component] + public class GameWindow + { + public IWindow Handle { get; } + public GameWindow(IWindow handle) => Handle = handle; + } + + [System(Phase.PreFrame)] + public static void ProcessWindow(GameWindow window, Canvas canvas) + => canvas.Size = window.Handle.Size; +} diff --git a/src/gaemstone.Client/Resources.cs b/src/gaemstone.Client/Resources.cs new file mode 100644 index 0000000..c68751f --- /dev/null +++ b/src/gaemstone.Client/Resources.cs @@ -0,0 +1,30 @@ +using System; +using System.IO; +using System.Reflection; + +namespace gaemstone.Client; + +public static class Resources +{ + public static Assembly ResourceAssembly { get; set; } = null!; + + public static Stream GetStream(string name) + => ResourceAssembly.GetManifestResourceStream( + ResourceAssembly.GetName().Name + ".Resources." + name) + ?? throw new ArgumentException($"Could not find embedded resource '{name}'"); + + public static string GetString(string name) + { + using var stream = GetStream(name); + using var reader = new StreamReader(stream); + return reader.ReadToEnd(); + } + + public static byte[] GetBytes(string name) + { + using var stream = GetStream(name); + using var memoryStream = new MemoryStream(); + stream.CopyTo(memoryStream); + return memoryStream.ToArray(); + } +} diff --git a/src/gaemstone.Client/Texture.cs b/src/gaemstone.Client/Texture.cs new file mode 100644 index 0000000..3a02fd0 --- /dev/null +++ b/src/gaemstone.Client/Texture.cs @@ -0,0 +1,14 @@ +using gaemstone.ECS; +using Silk.NET.OpenGL; + +namespace gaemstone.Client; + +[Component] +public readonly struct Texture +{ + public TextureTarget Target { get; } + public uint Handle { get; } + + public Texture(TextureTarget target, uint handle) + => (Target, Handle) = (target, handle); +} diff --git a/src/gaemstone.Client/TextureCoords4.cs b/src/gaemstone.Client/TextureCoords4.cs new file mode 100644 index 0000000..70cd3fe --- /dev/null +++ b/src/gaemstone.Client/TextureCoords4.cs @@ -0,0 +1,36 @@ +using System.Drawing; +using gaemstone.ECS; +using Silk.NET.Maths; + +namespace gaemstone.Client; + +[Component] +public readonly struct TextureCoords4 +{ + public Vector2D TopLeft { get; } + public Vector2D TopRight { get; } + public Vector2D BottomLeft { get; } + public Vector2D BottomRight { get; } + + public TextureCoords4(float x1, float y1, float x2, float y2) + { + TopLeft = new(x1, y1); + TopRight = new(x2, y1); + BottomLeft = new(x1, y2); + BottomRight = new(x2, y2); + } + + public static TextureCoords4 FromIntCoords(Size textureSize, Point origin, Size size) + => FromIntCoords(textureSize, origin.X, origin.Y, size.Width, size.Height); + public static TextureCoords4 FromIntCoords(Size textureSize, int x, int y, int width, int height) => new( + x / (float)textureSize.Width + 0.001F, + y / (float)textureSize.Height + 0.001F, + (x + width) / (float)textureSize.Width - 0.001F, + (y + height) / (float)textureSize.Height - 0.001F); + + public static TextureCoords4 FromGrid(int numCellsX, int numCellsY, int cellX, int cellY) => new( + cellX / (float)numCellsX + 0.001F, + cellY / (float)numCellsY + 0.001F, + (cellX + 1) / (float)numCellsX - 0.001F, + (cellY + 1) / (float)numCellsY - 0.001F); +} diff --git a/src/gaemstone.Client/TextureManager.cs b/src/gaemstone.Client/TextureManager.cs new file mode 100644 index 0000000..e44e4d8 --- /dev/null +++ b/src/gaemstone.Client/TextureManager.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.IO; +using gaemstone.ECS; +using Silk.NET.OpenGL; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; +using Size = System.Drawing.Size; + +namespace gaemstone.Client; + +public static class TextureManager +{ + private static readonly Dictionary _byTexture = new(); + private static readonly Dictionary _bySourceFile = new(); + + public static void Initialize(Universe universe) + { + var GL = universe.Lookup().Get().GL; + // Upload single-pixel white texture into texture slot 0, so when + // "no" texture is bound, we can still use the texture sampler. + GL.BindTexture(TextureTarget.Texture2D, 0); + Span pixel = stackalloc byte[4]; + pixel.Fill(255); + GL.TexImage2D(TextureTarget.Texture2D, 0, InternalFormat.Rgba, + 1, 1, 0, PixelFormat.Rgba, PixelType.UnsignedByte, in pixel[0]); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Nearest); + GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Nearest); + } + + public static Texture Load(Universe universe, string name) + { + using var stream = Resources.GetStream(name); + return CreateFromStream(universe, stream, name); + } + + public static Texture CreateFromStream(Universe universe, Stream stream, string? sourceFile = null) + { + var GL = universe.Lookup().Get().GL; + var texture = new Texture(TextureTarget.Texture2D, GL.GenTexture()); + GL.BindTexture(texture.Target, texture.Handle); + + var image = Image.Load(stream); + ref var origin = ref image.Frames[0].PixelBuffer[0, 0]; + + GL.TexImage2D(texture.Target, 0, (int)PixelFormat.Rgba, + (uint)image.Width, (uint)image.Height, 0, + PixelFormat.Rgba, PixelType.UnsignedByte, origin); + GL.TexParameter(texture.Target, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Nearest); + GL.TexParameter(texture.Target, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Nearest); + + var info = new TextureInfo(texture, sourceFile, new(image.Width, image.Height)); + _byTexture.Add(texture, info); + if (sourceFile != null) _bySourceFile.Add(sourceFile, info); + + GL.BindTexture(texture.Target, 0); + return texture; + } + + + public static TextureInfo? Lookup(Texture texture) + => _byTexture.TryGetValue(texture, out var value) ? value : null; + + public static TextureInfo? Lookup(string sourceFile) + => _bySourceFile.TryGetValue(sourceFile, out var value) ? value : null; +} + +public class TextureInfo +{ + public Texture Texture { get; } + public string? SourceFile { get; } + public Size Size { get; } + + public TextureInfo(Texture texture, string? sourceFile, Size size) + => (Texture, SourceFile, Size) = (texture, sourceFile, size); +} diff --git a/src/gaemstone.Client/gaemstone.Client.csproj b/src/gaemstone.Client/gaemstone.Client.csproj new file mode 100644 index 0000000..8d853d5 --- /dev/null +++ b/src/gaemstone.Client/gaemstone.Client.csproj @@ -0,0 +1,21 @@ + + + + net6.0 + disable + enable + true + + + + + + + + + + + + + + diff --git a/src/gaemstone/ECS/Attributes.cs b/src/gaemstone/ECS/Attributes.cs new file mode 100644 index 0000000..593ba40 --- /dev/null +++ b/src/gaemstone/ECS/Attributes.cs @@ -0,0 +1,12 @@ +using System; + +namespace gaemstone.ECS; + +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] +public class ComponentAttribute : Attribute { } + +[AttributeUsage(AttributeTargets.Struct)] +public class TagAttribute : Attribute { } + +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] +public class RelationAttribute : Attribute { } diff --git a/src/gaemstone/ECS/Entity.cs b/src/gaemstone/ECS/Entity.cs new file mode 100644 index 0000000..a87a800 --- /dev/null +++ b/src/gaemstone/ECS/Entity.cs @@ -0,0 +1,193 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using static flecs_hub.flecs; + +namespace gaemstone.ECS; + +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] +public class EntityAttribute : Attribute { } + +public unsafe readonly struct Entity +{ + public Universe Universe { get; } + public ecs_entity_t Value { get; } + + public EntityType Type => new(Universe, ecs_get_type(Universe, Value)); + public string Name => ecs_get_name(Universe, Value).ToStringAndFree(); + public string FullPath => ecs_get_path_w_sep(Universe, default, Value, ".", default).ToStringAndFree(); + + public bool IsNone => Value.Data == 0; + public bool IsAlive => ecs_is_alive(Universe, Value); + + public IEnumerable Children { get { + var term = new ecs_term_t { id = Universe.EcsChildOf & this }; + var iter = Iterator.FromTerm(Universe, term); + while (iter.Next()) + for (var i = 0; i < iter.Count; i++) + yield return iter.Entity(i); + } } + + public Entity(Universe universe, ecs_entity_t value) + { Universe = universe; Value = value; } + + public void ThrowIfNone() { if (IsNone) throw new InvalidOperationException("Entity isn't valid"); } + public void ThrowIfDead() { if (!IsAlive) throw new InvalidOperationException("Entity is dead"); } + + public void Delete() => ecs_delete(Universe, Value); + + + public Entity Add(ecs_id_t id) { ecs_add_id(Universe, this, id); return this; } + public Entity Add(Identifier id) { ecs_add_id(Universe, this, id); return this; } + public Entity Add(Entity relation, Entity target) => Add(relation & target); + + public Entity Add() + => Add(Universe.Lookup()); + public Entity Add() + => Add(Universe.Lookup(), Universe.Lookup()); + public Entity Add(Entity target) + => Add(Universe.Lookup(), target); + + + public Entity Override(ecs_id_t id) { ecs_override_id(Universe, this, id); return this; } + public Entity Override(Identifier id) { ecs_override_id(Universe, this, id); return this; } + public Entity Override(Entity relation, Entity target) => Override(relation & target); + + public Entity Override() + => Override(Universe.Lookup()); + public Entity Override() + => Override(Universe.Lookup(), Universe.Lookup()); + public Entity Override(Entity target) + => Override(Universe.Lookup(), target); + + + public void Remove(ecs_id_t id) => ecs_remove_id(Universe, this, id); + public void Remove(Identifier id) => ecs_remove_id(Universe, this, id); + public void Remove() => Remove(Universe.Lookup()); + + + public bool Has(ecs_id_t id) => ecs_has_id(Universe, this, id); + public bool Has(Identifier id) => ecs_has_id(Universe, this, id); + public bool Has(Entity relation, Entity target) => Has(relation & target); + + public bool Has() + => Has(Universe.Lookup()); + public bool Has() + => Has(Universe.Lookup(), Universe.Lookup()); + public bool Has(Entity target) + => Has(Universe.Lookup(), target); + + + /// + /// Gets a component value from this entity. If the component is a value + /// type, this will return a copy. If the component is a reference type, + /// it will return the reference itself. + /// When modifying a reference, consider calling . + /// + public T Get() + { + var comp = Universe.Lookup(); + var ptr = ecs_get_id(Universe, this, comp); + if (typeof(T).IsValueType) { + return Unsafe.Read(ptr); + } else { + var handle = (GCHandle)Unsafe.Read(ptr); + return (T)handle.Target!; + } + } + + /// + /// Gets a reference to a component value from this entity. Only works for + /// value types. When modifying, consider calling . + /// + public ref T GetRef() + where T : unmanaged + { + var comp = Universe.Lookup(); + var ptr = ecs_get_mut_id(Universe, this, comp); + return ref Unsafe.AsRef(ptr); + } + + /// + /// Marks a component as modified. Do this after getting a reference to + /// it with or , making sure change + /// detection will kick in. + /// + public void Modified() + { + var comp = Universe.Lookup(); + ecs_modified_id(Universe, this, comp); + } + + + public Entity Set(in T value) + where T : unmanaged + { + var comp = Universe.Lookup(); + var size = (ulong)Unsafe.SizeOf(); + fixed (T* ptr = &value) ecs_set_id(Universe, this, comp, size, ptr); + return this; + } + public Entity SetOverride(in T value) + where T : unmanaged + { + var comp = Universe.Lookup(); + var size = (ulong)Unsafe.SizeOf(); + ecs_add_id(Universe, this, Universe.ECS_OVERRIDE | comp); + fixed (T* ptr = &value) ecs_set_id(Universe, this, comp, size, ptr); + return this; + } + + public Entity Set(Type type, object obj) + { + var comp = Universe.Lookup(type); + var handle = (nint)GCHandle.Alloc(obj); + ecs_set_id(Universe, this, comp, (ulong)sizeof(nint), &handle); + // FIXME: Handle needs to be freed when component is removed! + return this; + } + public Entity Set(T obj) where T : class + => Set(typeof(T), obj); + public Entity SetOverride(T obj) + where T : class + { + var comp = Universe.Lookup(); + var handle = (nint)GCHandle.Alloc(obj); + ecs_add_id(Universe, this, Universe.ECS_OVERRIDE | comp); + ecs_set_id(Universe, this, comp, (ulong)sizeof(nint), &handle); + // FIXME: Handle needs to be freed when component is removed! + return this; + } + + + public static Identifier operator &(Entity first, Entity second) => Identifier.Pair(first, second); + public static Identifier operator &(ecs_entity_t first, Entity second) => Identifier.Pair(first, second); + public static Identifier operator |(ecs_id_t left, Entity right) => new(right.Universe, left | right.Value.Data); + + public static implicit operator ecs_id_t(Entity e) => e.Value.Data; + public static implicit operator ecs_entity_t(Entity e) => e.Value; + public static implicit operator Identifier(Entity e) => new(e.Universe, e); +} + +public unsafe readonly struct EntityType + : IEnumerable +{ + public Universe Universe { get; } + public unsafe ecs_type_t* Handle { get; } + + public int Count => Handle->count; + public Identifier this[int index] => new(Universe, Handle->array[index]); + + public EntityType(Universe universe, ecs_type_t* handle) + { Universe = universe; Handle = handle; } + + public IEnumerator GetEnumerator() + { for (var i = 0; i < Count; i++) yield return this[i]; } + IEnumerator IEnumerable.GetEnumerator() + => GetEnumerator(); + + public override string ToString() + => ecs_type_str(Universe, Handle).ToStringAndFree(); +} diff --git a/src/gaemstone/ECS/EntityDesc.cs.disabled b/src/gaemstone/ECS/EntityDesc.cs.disabled new file mode 100644 index 0000000..0a3e44b --- /dev/null +++ b/src/gaemstone/ECS/EntityDesc.cs.disabled @@ -0,0 +1,20 @@ +using static flecs_hub.flecs; + +namespace gaemstone.ECS; + +public struct EntityDesc +{ + public ecs_entity_desc_t Value; + + public string? Name { get => Value.name; set => Value.name.Set(value); } + public string? Symbol { get => Value.symbol; set => Value.symbol.Set(value); } + + public EntityDesc(params ecs_id_t[] ids) + { + Value = default; + for (var i = 0; i < ids.Length; i++) + Value.add[i] = ids[i]; + } + + public static explicit operator ecs_entity_desc_t(EntityDesc desc) => desc.Value; +} diff --git a/src/gaemstone/ECS/Filter.cs b/src/gaemstone/ECS/Filter.cs new file mode 100644 index 0000000..c2d77d8 --- /dev/null +++ b/src/gaemstone/ECS/Filter.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using gaemstone.Utility.IL; +using static flecs_hub.flecs; + +namespace gaemstone.ECS; + +public unsafe class Filter + : IEnumerable + , IDisposable +{ + public Universe Universe { get; } + public ecs_filter_t* Handle { get; } + + private Filter(Universe universe, ecs_filter_t* handle) + { Universe = universe; Handle = handle; } + + public Filter(Universe universe, ecs_filter_desc_t desc) + : this(universe, ecs_filter_init(universe, &desc)) { } + public Filter(Universe universe, string expression) + : this(universe, new ecs_filter_desc_t { expr = expression }) { } + + public static void RunOnce(Universe universe, Delegate action) + { + var gen = QueryActionGenerator.GetOrBuild(universe, action.Method); + using var filter = new Filter(universe, gen.Filter); + foreach (var iter in filter) gen.RunWithTryCatch(action.Target, iter); + } + + ~Filter() => Dispose(); + public void Dispose() { ecs_filter_fini(Handle); GC.SuppressFinalize(this); } + + public Iterator Iter() => new(Universe, IteratorType.Filter, ecs_filter_iter(Universe, this)); + public IEnumerator GetEnumerator() => Iter().GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public static implicit operator ecs_filter_t*(Filter q) => q.Handle; +} diff --git a/src/gaemstone/ECS/FlecsException.cs b/src/gaemstone/ECS/FlecsException.cs new file mode 100644 index 0000000..f4b8745 --- /dev/null +++ b/src/gaemstone/ECS/FlecsException.cs @@ -0,0 +1,17 @@ +using System; +using System.Diagnostics; + +namespace gaemstone.ECS; + +public class FlecsException : Exception +{ + public FlecsException() : base() { } + public FlecsException(string message) : base(message) { } +} + +public class FlecsAbortException : FlecsException +{ + private readonly string _stackTrace = new StackTrace(2, true).ToString(); + internal FlecsAbortException() : base("Abort was called by flecs") { } + public override string? StackTrace => _stackTrace; +} diff --git a/src/gaemstone/ECS/Identifier.cs b/src/gaemstone/ECS/Identifier.cs new file mode 100644 index 0000000..fa459ba --- /dev/null +++ b/src/gaemstone/ECS/Identifier.cs @@ -0,0 +1,53 @@ +using System; +using static flecs_hub.flecs; + +namespace gaemstone.ECS; + +public unsafe readonly struct Identifier +{ + public Universe Universe { get; } + public ecs_id_t Value { get; } + + public bool IsPair => ecs_id_is_pair(Value); + public IdentifierFlags Flags => (IdentifierFlags)(Value.Data & ECS_ID_FLAGS_MASK); + + public Identifier(Universe universe, ecs_id_t value) + { Universe = universe; Value = value; } + + public static Identifier Pair(Entity first, Entity second) + => new(first.Universe, Universe.ECS_PAIR | ((first.Value.Data << 32) + (uint)second.Value.Data)); + public static Identifier Pair(ecs_entity_t first, Entity second) + => new(second.Universe, Universe.ECS_PAIR | ((first.Data << 32) + (uint)second.Value.Data)); + + public (Entity, Entity) AsPair() + => (Universe.Lookup((ecs_id_t)((Value & ECS_COMPONENT_MASK) >> 32)), + Universe.Lookup((ecs_id_t)(Value & ECS_ENTITY_MASK))); + + // public Entity AsComponent() + // { + // var value = Value.Data & ECS_COMPONENT_MASK; + // return new Entity(Universe, new() { Data = value }); + // } + + public override string ToString() + => ecs_id_str(Universe, Value).ToStringAndFree(); + + + public static implicit operator ecs_id_t(Identifier e) => e.Value; + + public static Identifier operator |(ecs_id_t left, Identifier right) + => new(right.Universe, left | right.Value); + public static Identifier operator |(Identifier left, Identifier right) + => new(left.Universe, left.Value | right.Value); +} + +[Flags] +public enum IdentifierFlags : ulong +{ + Pair = 1ul << 63, + Override = 1ul << 62, + Toggle = 1ul << 61, + Or = 1ul << 60, + And = 1ul << 59, + Not = 1ul << 58, +} diff --git a/src/gaemstone/ECS/Iterator.cs b/src/gaemstone/ECS/Iterator.cs new file mode 100644 index 0000000..695061b --- /dev/null +++ b/src/gaemstone/ECS/Iterator.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using static flecs_hub.flecs; + +namespace gaemstone.ECS; + +public unsafe class Iterator + : IEnumerable +{ + public Universe Universe { get; } + public IteratorType? Type { get; } + public ecs_iter_t Value; + + public int Count => Value.count; + public TimeSpan DeltaTime => TimeSpan.FromSeconds(Value.delta_time); + public TimeSpan DeltaSystemTime => TimeSpan.FromSeconds(Value.delta_system_time); + + public Iterator(Universe universe, IteratorType? type, ecs_iter_t value) + { Universe = universe; Type = type; Value = value; } + + public static Iterator FromTerm(Universe universe, in ecs_term_t term) + { + fixed (ecs_term_t* ptr = &term) + return new(universe, IteratorType.Term, ecs_term_iter(universe, ptr)); + } + + public bool Next() + { + fixed (ecs_iter_t* ptr = &Value) + return Type switch { + IteratorType.Term => ecs_term_next(ptr), + IteratorType.Filter => ecs_filter_next(ptr), + IteratorType.Query => ecs_query_next(ptr), + IteratorType.Rule => ecs_rule_next(ptr), + _ => ecs_iter_next(ptr), + }; + } + + public Entity Entity(int index) + => new(Universe, Value.entities[index]); + + public Span Field(int index) + where T : unmanaged + { + fixed (ecs_iter_t* ptr = &Value) { + var size = (ulong)Unsafe.SizeOf(); + var pointer = ecs_field_w_size(ptr, size, index); + return new Span(pointer, Count); + } + } + + public SpanToRef FieldRef(int index) + where T : class => new(Field(index)); + + public bool FieldIsSet(int index) + { + fixed (ecs_iter_t* ptr = &Value) + return ecs_field_is_set(ptr, index); + } + + public bool FieldIs(int index) + where T : unmanaged + { + fixed (ecs_iter_t* ptr = &Value) { + var id = ecs_field_id(ptr, index); + var comp = Universe.Lookup(); + return id == comp.Value.Data; + } + } + + public readonly ref struct SpanToRef + { + private readonly Span _span; + internal SpanToRef(Span span) => _span = span; + public int Length => _span.Length; + public T this[int index] => (T)((GCHandle)_span[index]).Target!; + } + + // IEnumerable implementation + public IEnumerator GetEnumerator() { while (Next()) yield return this; } + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); +} + +public enum IteratorType +{ + Term, + Filter, + Query, + Rule, +} diff --git a/src/gaemstone/ECS/Module.cs b/src/gaemstone/ECS/Module.cs new file mode 100644 index 0000000..97513ac --- /dev/null +++ b/src/gaemstone/ECS/Module.cs @@ -0,0 +1,13 @@ +using System; + +namespace gaemstone.ECS; + +[AttributeUsage(AttributeTargets.Class)] +public class ModuleAttribute : Attribute { } + +[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] +public class DependsOnAttribute : Attribute +{ + public Type Target { get; } + public DependsOnAttribute(Type target) => Target = target; +} diff --git a/src/gaemstone/ECS/Observer.cs b/src/gaemstone/ECS/Observer.cs new file mode 100644 index 0000000..2b03487 --- /dev/null +++ b/src/gaemstone/ECS/Observer.cs @@ -0,0 +1,18 @@ +using System; + +namespace gaemstone.ECS; + +[AttributeUsage(AttributeTargets.Method)] +public class ObserverAttribute : Attribute +{ + public Event Event { get; } + public ObserverAttribute(Event @event) + => Event = @event; +} + +public enum Event +{ + OnAdd, + OnSet, + OnRemove, +} diff --git a/src/gaemstone/ECS/Query.cs b/src/gaemstone/ECS/Query.cs new file mode 100644 index 0000000..9677a17 --- /dev/null +++ b/src/gaemstone/ECS/Query.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using static flecs_hub.flecs; + +namespace gaemstone.ECS; + +public unsafe class Query + : IEnumerable + , IDisposable +{ + public Universe Universe { get; } + public ecs_query_t* Handle { get; } + + private Query(Universe universe, ecs_query_t* handle) + { Universe = universe; Handle = handle; } + + public Query(Universe universe, ecs_query_desc_t desc) + : this(universe, ecs_query_init(universe, &desc)) { } + public Query(Universe universe, ecs_filter_desc_t desc) + : this(universe, new ecs_query_desc_t { filter = desc }) { } + public Query(Universe universe, string expression) + : this(universe, new ecs_filter_desc_t { expr = expression }) { } + + ~Query() => Dispose(); + public void Dispose() { ecs_query_fini(this); GC.SuppressFinalize(this); } + + public Iterator Iter() => new(Universe, IteratorType.Query, ecs_query_iter(Universe, this)); + public IEnumerator GetEnumerator() => Iter().GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public static implicit operator ecs_query_t*(Query q) => q.Handle; +} diff --git a/src/gaemstone/ECS/System.cs b/src/gaemstone/ECS/System.cs new file mode 100644 index 0000000..05f8da3 --- /dev/null +++ b/src/gaemstone/ECS/System.cs @@ -0,0 +1,93 @@ +using System; + +namespace gaemstone.ECS; + +[AttributeUsage(AttributeTargets.Method)] +public class SystemAttribute : Attribute +{ + public string? Expression { get; set; } + public Phase Phase { get; set; } + + public SystemAttribute() : this(Phase.OnUpdate) { } + public SystemAttribute(Phase phase) => Phase = phase; +} + +[AttributeUsage(AttributeTargets.Parameter)] +public class SourceAttribute : Attribute +{ + public Type Type { get; } + public SourceAttribute(Type type) => Type = type; +} + +[AttributeUsage(AttributeTargets.Parameter)] +public class HasAttribute : Attribute { } + +[AttributeUsage(AttributeTargets.Parameter)] +public class NotAttribute : Attribute { } + +public enum Phase +{ + PreFrame, + + /// + /// This phase contains all the systems that load data into your ECS. + /// This would be a good place to load keyboard and mouse inputs. + /// + OnLoad, + + /// + /// Often the imported data needs to be processed. Maybe you want to associate + /// your keypresses with high level actions rather than comparing explicitly + /// in your game code if the user pressed the 'K' key. + /// + PostLoad, + + /// + /// Now that the input is loaded and processed, it's time to get ready to + /// start processing our game logic. Anything that needs to happen after + /// input processing but before processing the game logic can happen here. + /// This can be a good place to prepare the frame, maybe clean up some + /// things from the previous frame, etcetera. + /// + PreUpdate, + + /// + /// This is usually where the magic happens! This is where you put all of + /// your gameplay systems. By default systems are added to this phase. + /// + OnUpdate, + + /// + /// This phase was introduced to deal with validating the state of the game + /// after processing the gameplay systems. Sometimes you entities too close + /// to each other, or the speed of an entity is increased too much. + /// This phase is for righting that wrong. A typical feature to implement + /// in this phase would be collision detection. + /// + OnValidate, + + /// + /// When your game logic has been updated, and your validation pass has ran, + /// you may want to apply some corrections. For example, if your collision + /// detection system detected collisions in the OnValidate phase, + /// you may want to move the entities so that they no longer overlap. + /// + PostUpdate, + + /// + /// Now that all of the frame data is computed, validated and corrected for, + /// it is time to prepare the frame for rendering. Any systems that need to + /// run before rendering, but after processing the game logic should go here. + /// A good example would be a system that calculates transform matrices from + /// a scene graph. + /// + PreStore, + + /// + /// This is where it all comes together. Your frame is ready to be + /// rendered, and that is exactly what you would do in this phase. + /// + OnStore, + + PostFrame, +} diff --git a/src/gaemstone/ECS/Universe+Modules.cs b/src/gaemstone/ECS/Universe+Modules.cs new file mode 100644 index 0000000..d52f58d --- /dev/null +++ b/src/gaemstone/ECS/Universe+Modules.cs @@ -0,0 +1,140 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Reflection; +using gaemstone.Utility; + +namespace gaemstone.ECS; + +public unsafe partial class Universe +{ + public void RegisterModule() where T : class + => RegisterModule(typeof(T)); + public void RegisterModule(Type type) + { + var builder = new ModuleBuilder(this, type); + + builder.UnmetDependencies.ExceptWith(Modules._modules.Keys); + if (builder.UnmetDependencies.Count > 0) { + // If builder has unmet dependencies, defer the registration. + Modules._deferred.Add(type, builder); + } else { + // Otherwise register it right away, .. + Modules._modules.Add(type, new ModuleInfo(builder)); + // .. and tell other deferred modules this one is now loaded. + RemoveDependency(builder.Type); + } + } + + private void RemoveDependency(Type type) + { + var resolved = Modules._deferred.Values + .Where(d => d.UnmetDependencies.Remove(type) + && d.UnmetDependencies.Count == 0) + .ToArray(); + foreach (var builder in resolved) { + Modules._deferred.Remove(builder.Type); + Modules._modules.Add(type, new ModuleInfo(builder)); + RemoveDependency(builder.Type); + } + } + + public class UniverseModules + { + internal readonly Dictionary _modules = new(); + internal readonly Dictionary _deferred = new(); + + internal UniverseModules(Universe universe) { } + } + + public class ModuleInfo + { + public Universe Universe { get; } + public object Instance { get; } + + public IReadOnlyList Relations { get; } + public IReadOnlyList Components { get; } + public IReadOnlyList Tags { get; } + public IReadOnlyList Entities { get; } + public IReadOnlyList Systems { get; } + + internal ModuleInfo(ModuleBuilder builder) + { + Universe = builder.Universe; + Instance = builder.HasSimpleConstructor + ? Activator.CreateInstance(builder.Type)! + : Activator.CreateInstance(builder.Type, Universe)!; + + Relations = builder.Relations .Select(Universe.RegisterRelation ).ToImmutableList(); + Components = builder.Components.Select(Universe.RegisterComponent).ToImmutableList(); + Tags = builder.Tags .Select(Universe.RegisterTag ).ToImmutableList(); + Entities = builder.Entities .Select(Universe.RegisterEntity ).ToImmutableList(); + + Systems = builder.Systems.Select(s => Universe.RegisterSystem(Instance, s)).ToImmutableList(); + } + } + + public class ModuleBuilder + { + public Universe Universe { get; } + public Type Type { get; } + public IReadOnlyList DependsOn { get; } + public bool HasSimpleConstructor { get; } + + public IReadOnlyList Relations { get; } + public IReadOnlyList Components { get; } + public IReadOnlyList Tags { get; } + public IReadOnlyList Entities { get; } + public IReadOnlyList Systems { get; } + + public HashSet UnmetDependencies { get; } + + internal ModuleBuilder(Universe universe, Type type) + { + if (!type.IsClass || type.IsAbstract) throw new Exception( + "Module must be a non-abstract class"); + if (!type.Has()) throw new Exception( + "Module must be marked with ModuleAttribute"); + + Universe = universe; + Type = type; + DependsOn = type.GetMultiple() + .Select(d => d.Target).ToImmutableList(); + + HasSimpleConstructor = type.GetConstructor(Type.EmptyTypes) != null; + var hasUniverseConstructor = type.GetConstructor(new[] { typeof(Universe) }) != null; + if (!HasSimpleConstructor && !hasUniverseConstructor) throw new Exception( + $"Module {Type} must define a public constructor with either no parameters, or a single {nameof(Universe)} parameter"); + + var relations = new List(); + var components = new List(); + var tags = new List(); + var entities = new List(); + var systems = new List(); + + foreach (var nested in Type.GetNestedTypes()) { + if (nested.Has()) relations.Add(nested); + else if (nested.Has()) components.Add(nested); + else if (nested.Has()) tags.Add(nested); + else if (nested.Has()) entities.Add(nested); + } + foreach (var method in Type.GetMethods()) + if (method.Has()) + systems.Add(method); + + var elements = new IList[] { relations, components, tags, entities, systems }; + if (elements.Sum(l => l.Count) == 0) throw new Exception( + "Module must define at least one ECS related type or method"); + + Relations = relations.AsReadOnly(); + Components = components.AsReadOnly(); + Tags = tags.AsReadOnly(); + Entities = entities.AsReadOnly(); + Systems = systems.AsReadOnly(); + + UnmetDependencies = DependsOn.ToHashSet(); + } + } +} diff --git a/src/gaemstone/ECS/Universe+Systems.cs b/src/gaemstone/ECS/Universe+Systems.cs new file mode 100644 index 0000000..1266efc --- /dev/null +++ b/src/gaemstone/ECS/Universe+Systems.cs @@ -0,0 +1,167 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Threading; +using gaemstone.Utility; +using gaemstone.Utility.IL; +using static flecs_hub.flecs; + +namespace gaemstone.ECS; + +public unsafe partial class Universe +{ + public SystemInfo RegisterSystem(Action callback, string expression, + Phase? phase = null, string? name = null) + => RegisterSystem(name ?? callback.Method.Name, expression, phase ?? Phase.OnUpdate, new() { expr = expression }, callback); + public SystemInfo RegisterSystem(Action callback, ecs_filter_desc_t filter, + Phase? phase = null, string? name = null) + => RegisterSystem(name ?? callback.Method.Name, null, phase ?? Phase.OnUpdate, filter, callback); + + public SystemInfo RegisterSystem(string name, string? expression, + Phase phase, ecs_filter_desc_t filter, Action callback) + { + var _phase = Systems._phaseLookup[phase]; + var entityDesc = default(ecs_entity_desc_t); + entityDesc.name = name; + entityDesc.add[0] = !_phase.IsNone ? (EcsDependsOn & _phase) : default; + entityDesc.add[1] = _phase; + // TODO: Provide a nice way to create these entity descriptors. + + var systemDesc = default(ecs_system_desc_t); + systemDesc.entity = Create(entityDesc); + systemDesc.binding_ctx = (void*)UniverseSystems.CreateSystemCallbackContext(this, callback); + systemDesc.callback.Data.Pointer = &UniverseSystems.SystemCallback; + systemDesc.query.filter = filter; + + var entity = new Entity(this, ecs_system_init(Handle, &systemDesc)); + var system = new SystemInfo(this, entity, name, expression, phase, filter, callback); + Systems._systems.Add(system); + return system; + } + + public SystemInfo RegisterSystem(Delegate action) + { + var name = action.Method.Name; + var attr = action.Method.Get(); + var phase = attr?.Phase ?? Phase.OnUpdate; + + if (action is Action iterAction) { + if (attr?.Expression == null) throw new Exception( + "System must specify expression in SystemAttribute"); + return RegisterSystem(name, attr.Expression, phase, new() { expr = attr.Expression }, iterAction); + } else { + var method = action.GetType().GetMethod("Invoke")!; + var gen = QueryActionGenerator.GetOrBuild(this, method); + var filter = (attr?.Expression == null) ? gen.Filter : new() { expr = attr.Expression }; + return RegisterSystem(name, attr?.Expression, phase, filter, + iter => gen.RunWithTryCatch(action.Target, iter)); + } + } + + public SystemInfo RegisterSystem(object? instance, MethodInfo method) + { + var attr = method.Get(); + var phase = attr?.Phase ?? Phase.OnUpdate; + + var param = method.GetParameters(); + if (param.Length == 1 && param[0].ParameterType == typeof(Iterator)) { + if (attr?.Expression == null) throw new Exception( + "System must specify expression in SystemAttribute"); + var action = (Action)Delegate.CreateDelegate(typeof(Action), instance, method); + return RegisterSystem(method.Name, attr.Expression, phase, new() { expr = attr.Expression }, action); + } else { + var gen = QueryActionGenerator.GetOrBuild(this, method); + var filter = (attr?.Expression == null) ? gen.Filter : new() { expr = attr.Expression }; + return RegisterSystem(method.Name, attr?.Expression, phase, filter, + iter => gen.RunWithTryCatch(instance, iter)); + } + } + + public class UniverseSystems + : IReadOnlyCollection + { + public readonly struct SystemCallbackContext + { + public Universe Universe { get; } + public Action Callback { get; } + + public SystemCallbackContext(Universe universe, Action callback) + { Universe = universe; Callback = callback; } + } + + private static SystemCallbackContext[] _systemCallbackContexts = new SystemCallbackContext[64]; + private static int _systemCallbackContextsCount = 0; + + public static nint CreateSystemCallbackContext(Universe universe, Action callback) + { + var data = new SystemCallbackContext(universe, callback); + var count = Interlocked.Increment(ref _systemCallbackContextsCount); + if (count > _systemCallbackContexts.Length) + Array.Resize(ref _systemCallbackContexts, count * 2); + _systemCallbackContexts[count - 1] = data; + return count; + } + + public static SystemCallbackContext GetSystemCallbackContext(nint context) + => _systemCallbackContexts[(int)context - 1]; + + [UnmanagedCallersOnly] + internal static void SystemCallback(ecs_iter_t* iter) + { + var data = GetSystemCallbackContext((nint)iter->binding_ctx); + data.Callback(new Iterator(data.Universe, null, *iter)); + } + + + internal readonly List _systems = new(); + internal readonly Dictionary _phaseLookup = new(); + + internal UniverseSystems(Universe universe) + { + _phaseLookup.Add(Phase.PreFrame, new(universe, pinvoke_EcsPreFrame())); + _phaseLookup.Add(Phase.OnLoad, new(universe, pinvoke_EcsOnLoad())); + _phaseLookup.Add(Phase.PostLoad, new(universe, pinvoke_EcsPostLoad())); + _phaseLookup.Add(Phase.PreUpdate, new(universe, pinvoke_EcsPreUpdate())); + _phaseLookup.Add(Phase.OnUpdate, new(universe, pinvoke_EcsOnUpdate())); + _phaseLookup.Add(Phase.OnValidate, new(universe, pinvoke_EcsOnValidate())); + _phaseLookup.Add(Phase.PostUpdate, new(universe, pinvoke_EcsPostUpdate())); + _phaseLookup.Add(Phase.PreStore, new(universe, pinvoke_EcsPreStore())); + _phaseLookup.Add(Phase.OnStore, new(universe, pinvoke_EcsOnStore())); + _phaseLookup.Add(Phase.PostFrame, new(universe, pinvoke_EcsPostFrame())); + } + + // IReadOnlyCollection implementation + public int Count => _systems.Count; + public IEnumerator GetEnumerator() => _systems.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } + + public class SystemInfo + { + public Universe Universe { get; } + public Entity Entity { get; } + + public string Name { get; } + public string? Expression { get; } + + public Phase Phase { get; } + public ecs_filter_desc_t Filter { get; } + public Action Callback { get; } + + internal SystemInfo(Universe universe, Entity entity, string name, string? expression, + Phase phase, ecs_filter_desc_t filter, Action callback) + { + Universe = universe; + Entity = entity; + + Name = name; + Expression = expression; + + Phase = phase; + Filter = filter; + Callback = callback; + } + } +} diff --git a/src/gaemstone/ECS/Universe.cs b/src/gaemstone/ECS/Universe.cs new file mode 100644 index 0000000..56fe78a --- /dev/null +++ b/src/gaemstone/ECS/Universe.cs @@ -0,0 +1,184 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using gaemstone.Utility; +using static flecs_hub.flecs; + +namespace gaemstone.ECS; + +[Entity] +public struct Game { } + +[Component] +public unsafe partial class Universe +{ + // Roles + public static ecs_id_t ECS_PAIR { get; } = pinvoke_ECS_PAIR(); + public static ecs_id_t ECS_OVERRIDE { get; } = pinvoke_ECS_OVERRIDE(); + + // Relationships + public static ecs_entity_t EcsIsA { get; } = pinvoke_EcsIsA(); + public static ecs_entity_t EcsDependsOn { get; } = pinvoke_EcsDependsOn(); + public static ecs_entity_t EcsChildOf { get; } = pinvoke_EcsChildOf(); + public static ecs_entity_t EcsSlotOf { get; } = pinvoke_EcsSlotOf(); + + // Entity Tags + public static ecs_entity_t EcsPrefab { get; } = pinvoke_EcsPrefab(); + + + private readonly Dictionary _byType = new(); + + public ecs_world_t* Handle { get; } + + public UniverseSystems Systems { get; } + public UniverseModules Modules { get; } + + public Universe(string[]? args = null) + { + [UnmanagedCallersOnly] + static void Abort() => throw new FlecsAbortException(); + + ecs_os_set_api_defaults(); + var api = ecs_os_get_api(); + api.abort_ = new FnPtr_Void { Pointer = &Abort }; + ecs_os_set_api(&api); + + if (args?.Length > 0) { + var argv = Runtime.CStrings.CStringArray(args); + Handle = ecs_init_w_args(args.Length, argv); + Runtime.CStrings.FreeCStrings(argv, args.Length); + } else { + Handle = ecs_init(); + } + + Systems = new(this); + Modules = new(this); + + RegisterAll(typeof(Universe).Assembly); + } + + public bool Progress(TimeSpan delta) + { + if (Modules._deferred.Count > 0) throw new Exception( + "Modules with unmet dependencies: \n" + + string.Join(" \n", Modules._deferred.Values.Select( + m => m.Type + " is missing " + string.Join(", ", m.UnmetDependencies)))); + return ecs_progress(this, (float)delta.TotalSeconds); + } + + + public Entity Lookup() + => Lookup(typeof(T)); + public Entity Lookup(Type type) + => _byType.TryGetValue(type, out var e) ? new(this, e) : default; + + public Entity Lookup(string path) + => new(this, !path.Contains('.') ? ecs_lookup(this, path) + : ecs_lookup_path_w_sep(this, default, path, ".", default, true)); + public Entity Lookup(ecs_entity_t value) + => new(this, ecs_get_alive(this, value)); + + + public void RegisterAll(Assembly? from = null) + { + from ??= Assembly.GetEntryAssembly()!; + foreach (var type in from.GetTypes()) { + var isPartOfModule = type.DeclaringType?.Has() == true; + if (type.Has()) { + if (!isPartOfModule) RegisterRelation(type); + } else if (type.Has()) { + if (!isPartOfModule) RegisterComponent(type); + } else if (type.Has()) { + if (!isPartOfModule) RegisterTag(type); + } else if (type.Has()) { + if (!isPartOfModule) RegisterEntity(type); + } else if (type.Has()) + RegisterModule(type); + } + } + + public Entity RegisterRelation() + => RegisterRelation(typeof(T)); + public Entity RegisterRelation(Type type) + => throw new NotImplementedException(); + + public Entity RegisterComponent() + => RegisterComponent(typeof(T)); + public Entity RegisterComponent(Type type) + { + var typeInfo = default(ecs_type_info_t); + if (type.IsValueType) { + var wrapper = TypeWrapper.For(type); + if (!wrapper.IsUnmanaged) throw new Exception( + "Component struct must satisfy the unmanaged constraint. " + + "(Must not contain any reference types or structs that contain references.)"); + var structLayout = type.StructLayoutAttribute; + if (structLayout == null || structLayout.Value == LayoutKind.Auto) throw new Exception( + "Component struct must have a StructLayout attribute with LayoutKind sequential or explicit. " + + "This is to ensure that the struct fields are not reorganized by the C# compiler."); + typeInfo.size = wrapper.Size; + typeInfo.alignment = structLayout.Pack; + } else { + typeInfo.size = sizeof(nint); + typeInfo.alignment = sizeof(nint); + } + + var name = type.GetFriendlyName(); + var entityDesc = new ecs_entity_desc_t { name = name, symbol = name }; + var componentDesc = new ecs_component_desc_t { entity = Create(entityDesc), type = typeInfo }; + + var id = ecs_component_init(Handle, &componentDesc); + _byType[type] = id; + // TODO: SetHooks(hooks, id); + var entity = new Entity(this, id); + + if (type.Has()) { + if (type.IsValueType) entity.Add(entity); + else entity.Set(type, Activator.CreateInstance(type)!); + } + + return entity; + } + + public Entity RegisterTag() + where T : unmanaged + => RegisterTag(typeof(T)); + public Entity RegisterTag(Type type) + { + if (!type.IsValueType || type.IsPrimitive || type.GetFields().Length > 0) + throw new Exception("Tag must be an empty, used-defined struct."); + var entity = Create(type.GetFriendlyName()); + _byType.Add(type, entity); + return entity; + } + + public Entity RegisterEntity() + where T : unmanaged + => RegisterEntity(typeof(T)); + public Entity RegisterEntity(Type type) + { + if (!type.IsValueType || type.IsPrimitive || type.GetFields().Length > 0) + throw new Exception("Entity must be an empty, used-defined struct."); + var entity = Create(type.GetFriendlyName()); + _byType.Add(type, entity); + return entity; + } + + + public Entity Create() + => Create(new ecs_entity_desc_t()); + public Entity Create(string name) + => Create(new ecs_entity_desc_t { name = name }); + public Entity Create(ecs_entity_desc_t desc) + { + var entity = ecs_entity_init(Handle, &desc); + Debug.Assert(entity.Data != 0, "ECS_INVALID_PARAMETER"); + return new(this, entity); + } + + + public static implicit operator ecs_world_t*(Universe w) => w.Handle; +} diff --git a/src/gaemstone/GlobalTransform.cs b/src/gaemstone/GlobalTransform.cs new file mode 100644 index 0000000..d4b9d83 --- /dev/null +++ b/src/gaemstone/GlobalTransform.cs @@ -0,0 +1,13 @@ +using gaemstone.ECS; +using Silk.NET.Maths; + +namespace gaemstone; + +[Component] +public struct GlobalTransform +{ + public Matrix4X4 Value; + public GlobalTransform(Matrix4X4 value) => Value = value; + public static implicit operator GlobalTransform(in Matrix4X4 value) => new(value); + public static implicit operator Matrix4X4(in GlobalTransform index) => index.Value; +} diff --git a/src/gaemstone/Utility/CStringExtensions.cs b/src/gaemstone/Utility/CStringExtensions.cs new file mode 100644 index 0000000..660e0d1 --- /dev/null +++ b/src/gaemstone/Utility/CStringExtensions.cs @@ -0,0 +1,20 @@ +using System.Runtime.InteropServices; +using static flecs_hub.flecs; + +namespace gaemstone; + +internal static class CStringExtensions +{ + public static string ToStringAndFree(this Runtime.CString str) + { + var result = Marshal.PtrToStringAnsi(str)!; + Marshal.FreeHGlobal(str); + return result; + } + + public static void Set(this ref Runtime.CString str, string? value) + { + if (!str.IsNull) Marshal.FreeHGlobal(str); + str = (value != null) ? new(Marshal.StringToHGlobalAnsi(value)) : default; + } +} diff --git a/src/gaemstone/Utility/IL/ILGeneratorWrapper.cs b/src/gaemstone/Utility/IL/ILGeneratorWrapper.cs new file mode 100644 index 0000000..9a6c3ca --- /dev/null +++ b/src/gaemstone/Utility/IL/ILGeneratorWrapper.cs @@ -0,0 +1,291 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; +using System.Text; + +namespace gaemstone.Utility.IL; + +public class ILGeneratorWrapper +{ + private readonly DynamicMethod _method; + private readonly ILGenerator _il; + private readonly List _locals = new(); + private readonly List<(int Offset, int Indent, OpCode Code, object? Arg)> _instructions = new(); + private readonly Dictionary _labelToOffset = new(); + private readonly Stack _indents = new(); + + public ILGeneratorWrapper(DynamicMethod method) + { + _method = method; + _il = method.GetILGenerator(); + } + + public string ToReadableString() + { + var sb = new StringBuilder(); + sb.AppendLine("Parameters:"); + foreach (var (param, index) in _method.GetParameters().Select((p, i) => (p, i))) + sb.AppendLine($" Argument({index}, {param.ParameterType.Name})"); + sb.AppendLine("Return:"); + sb.AppendLine($" {_method.ReturnType.Name}"); + sb.AppendLine(); + + sb.AppendLine("Locals:"); + foreach (var local in _locals) + sb.AppendLine($" {local}"); + sb.AppendLine(); + + sb.AppendLine("Instructions:"); + foreach (var (offset, indent, code, arg) in _instructions) { + sb.Append(" "); + + // Append instruction offset. + if (offset < 0) sb.Append(" "); + else sb.Append($"0x{offset:X4} "); + + // Append instruction opcode. + if (code == OpCodes.Nop) sb.Append(" "); + else sb.Append($"{code.Name,-12}"); + + // Append indents. + for (var i = 0; i < indent; i++) + sb.Append("| "); + + // Append instruction argument. + if (code == OpCodes.Nop) sb.Append("// "); + switch (arg) { + case Label label: sb.Append($"Label(0x{_labelToOffset.GetValueOrDefault(label, -1):X4})"); break; + case not null: sb.Append(arg); break; + } + + sb.AppendLine(); + } + return sb.ToString(); + } + + + public IArgument Argument(int index) + { + var type = _method.GetParameters()[index].ParameterType; + if (type.IsByRefLike) return new ArgumentImpl(index, type); + return (IArgument)Activator.CreateInstance(typeof(ArgumentImpl<>).MakeGenericType(type), index)!; + } + public IArgument Argument(int index) => (IArgument)Argument(index); + + public ILocal Local(Type type, string? name = null) + { + var builder = _il.DeclareLocal(type); + var local = type.IsByRefLike ? new LocalImpl(builder, name) + : (ILocal)Activator.CreateInstance(typeof(LocalImpl<>).MakeGenericType(type), builder, name)!; + _locals.Add(local); + return local; + } + public ILocal Local(string? name = null) => (ILocal)Local(typeof(T), name); + public ILocal LocalArray(Type type, string? name = null) => (ILocal)Local(type.MakeArrayType(), name); + public ILocal LocalArray(string? name = null) => (ILocal)Local(typeof(T).MakeArrayType(), name); + + public Label DefineLabel() => _il.DefineLabel(); + public void MarkLabel(Label label) + { + _instructions.Add((-1, _indents.Count, OpCodes.Nop, label)); + _labelToOffset.Add(label, _il.ILOffset); + _il.MarkLabel(label); + } + + + private void AddInstr(OpCode code, object? arg = null) => _instructions.Add((_il.ILOffset, _indents.Count, code, arg)); + public void Comment(string comment) => _instructions.Add((-1, _indents.Count, OpCodes.Nop, comment)); + + internal void Emit(OpCode code) { AddInstr(code, null); _il.Emit(code); } + internal void Emit(OpCode code, int arg) { AddInstr(code, arg); _il.Emit(code, arg); } + internal void Emit(OpCode code, Type type) { AddInstr(code, type); _il.Emit(code, type); } + internal void Emit(OpCode code, Label label) { AddInstr(code, label); _il.Emit(code, label); } + internal void Emit(OpCode code, ILocal local) { AddInstr(code, local); _il.Emit(code, local.Builder); } + internal void Emit(OpCode code, IArgument arg) { AddInstr(code, arg); _il.Emit(code, arg.Index); } + internal void Emit(OpCode code, MethodInfo method) { AddInstr(code, method); _il.Emit(code, method); } + internal void Emit(OpCode code, ConstructorInfo constr) { AddInstr(code, constr); _il.Emit(code, constr); } + + public void LoadNull() => Emit(OpCodes.Ldnull); + public void LoadConst(int value) => Emit(OpCodes.Ldc_I4, value); + + public void Load(IArgument arg) => Emit(OpCodes.Ldarg, arg); + public void LoadAddr(IArgument arg) => Emit(OpCodes.Ldarga, arg); + + public void Load(ILocal local) => Emit(OpCodes.Ldloc, local); + public void LoadAddr(ILocal local) => Emit(OpCodes.Ldloca, local); + public void Store(ILocal local) => Emit(OpCodes.Stloc, local); + public void Set(ILocal local, int value) { LoadConst(value); Store(local); } + + public void LoadLength() { Emit(OpCodes.Ldlen); Emit(OpCodes.Conv_I4); } + public void LoadLength(IArgument array) { Load(array); LoadLength(); } + public void LoadLength(ILocal array) { Load(array); LoadLength(); } + + public void LoadObj(Type type) => Emit(OpCodes.Ldobj, type); + public void LoadObj(ILocal local) { LoadAddr(local); LoadObj(local.LocalType); } + public void LoadObj(IArgument arg) { LoadAddr(arg); LoadObj(arg.ArgumentType); } + + public void LoadElem(Type type) => Emit(OpCodes.Ldelem, type); + public void LoadElem(Type type, int index) { LoadConst(index); LoadElem(type); } + public void LoadElem(Type type, ILocal index) { Load(index); LoadElem(type); } + public void LoadElem(Type type, IArgument array, int index) { Load(array); LoadElem(type, index); } + public void LoadElem(Type type, IArgument array, ILocal index) { Load(array); LoadElem(type, index); } + public void LoadElem(Type type, ILocal array, int index) { Load(array); LoadElem(type, index); } + public void LoadElem(Type type, ILocal array, ILocal index) { Load(array); LoadElem(type, index); } + + public void LoadElemRef() => Emit(OpCodes.Ldelem_Ref); + public void LoadElemRef(int index) { LoadConst(index); LoadElemRef(); } + public void LoadElemRef(ILocal index) { Load(index); LoadElemRef(); } + public void LoadElemRef(IArgument array, int index) { Load(array); LoadElemRef(index); } + public void LoadElemRef(IArgument array, ILocal index) { Load(array); LoadElemRef(index); } + public void LoadElemRef(ILocal array, int index) { Load(array); LoadElemRef(index); } + public void LoadElemRef(ILocal array, ILocal index) { Load(array); LoadElemRef(index); } + + public void LoadElemEither(Type type) { if (type.IsValueType) LoadElem(type); else LoadElemRef(); } + public void LoadElemEither(Type type, int index) { if (type.IsValueType) LoadElem(type, index); else LoadElemRef(index); } + public void LoadElemEither(Type type, ILocal index) { if (type.IsValueType) LoadElem(type, index); else LoadElemRef(index); } + public void LoadElemEither(Type type, IArgument array, int index) { if (type.IsValueType) LoadElem(type, array, index); else LoadElemRef(array, index); } + public void LoadElemEither(Type type, IArgument array, ILocal index) { if (type.IsValueType) LoadElem(type, array, index); else LoadElemRef(array, index); } + public void LoadElemEither(Type type, ILocal array, int index) { if (type.IsValueType) LoadElem(type, array, index); else LoadElemRef(array, index); } + public void LoadElemEither(Type type, ILocal array, ILocal index) { if (type.IsValueType) LoadElem(type, array, index); else LoadElemRef(array, index); } + + public void LoadElemAddr(Type type) => Emit(OpCodes.Ldelema, type); + public void LoadElemAddr(Type type, int index) { LoadConst(index); LoadElemAddr(type); } + public void LoadElemAddr(Type type, ILocal index) { Load(index); LoadElemAddr(type); } + public void LoadElemAddr(Type type, IArgument array, int index) { Load(array); LoadElemAddr(type, index); } + public void LoadElemAddr(Type type, IArgument array, ILocal index) { Load(array); LoadElemAddr(type, index); } + public void LoadElemAddr(Type type, ILocal array, int index) { Load(array); LoadElemAddr(type, index); } + public void LoadElemAddr(Type type, ILocal array, ILocal index) { Load(array); LoadElemAddr(type, index); } + + public void Load(PropertyInfo info) => CallVirt(info.GetMethod!); + public void Load(ILocal obj, PropertyInfo info) { Load(obj); Load(info); } + public void Load(IArgument obj, PropertyInfo info) { Load(obj); Load(info); } + + public void Add() => Emit(OpCodes.Add); + public void Increment(ILocal local) { Load(local); LoadConst(1); Add(); Store(local); } + + public void Init(Type type) => Emit(OpCodes.Initobj, type); + public void Init() where T : struct => Emit(OpCodes.Initobj, typeof(T)); + + public void New(ConstructorInfo constructor) => Emit(OpCodes.Newobj, constructor); + public void New(Type type) => New(type.GetConstructors().Single()); + public void New(Type type, params Type[] paramTypes) => New(type.GetConstructor(paramTypes)!); + + public void Cast(Type type) => Emit(OpCodes.Castclass, type); + public void Cast() => Cast(typeof(T)); + + public void Goto(Label label) => Emit(OpCodes.Br, label); + public void GotoIfTrue(Label label) => Emit(OpCodes.Brtrue, label); + public void GotoIfFalse(Label label) => Emit(OpCodes.Brfalse, label); + + public void GotoIf(Label label, ILocal a, Comparison op, ILocal b) + { Load(a); Load(b); Emit(op.Code, label); } + public void GotoIfNull(Label label, ILocal local) + { Load(local); Emit(OpCodes.Brfalse, label); } + public void GotoIfNotNull(Label label, ILocal local) + { Load(local); Emit(OpCodes.Brtrue, label); } + + public void Call(MethodInfo method) => Emit(OpCodes.Call, method); + public void CallVirt(MethodInfo method) => Emit(OpCodes.Callvirt, method); + + public void Return() => Emit(OpCodes.Ret); + + + public IDisposable For(Action loadMax, out ILocal current) + { + var r = Random.Shared.Next(10000, 100000); + Comment($"INIT for loop {r}"); + + var curLocal = current = Local($"index_{r}"); + var maxLocal = Local($"length_{r}"); + + var bodyLabel = DefineLabel(); + var testLabel = DefineLabel(); + + Set(curLocal, 0); + loadMax(); Store(maxLocal); + + Comment($"BEGIN for loop {r}"); + Goto(testLabel); + MarkLabel(bodyLabel); + var indent = Indent(); + + return Block(() => { + Increment(curLocal); + MarkLabel(testLabel); + GotoIf(bodyLabel, curLocal, Comparison.LessThan, maxLocal); + indent.Dispose(); + Comment($"END for loop {r}"); + }); + } + + + public IDisposable Block(Action onClose) + => new BlockImpl(onClose); + public IDisposable Indent() + { + BlockImpl indent = null!; + indent = new(() => { if (_indents.Pop() != indent) throw new InvalidOperationException(); }); + _indents.Push(indent); + return indent; + } + + internal class BlockImpl : IDisposable + { + public Action OnClose { get; } + public BlockImpl(Action onClose) => OnClose = onClose; + public void Dispose() => OnClose(); + } + + + internal class ArgumentImpl : IArgument + { + public int Index { get; } + public Type ArgumentType { get; } + public ArgumentImpl(int index, Type type) { Index = index; ArgumentType = type; } + public override string ToString() => $"Argument({Index}, {ArgumentType.Name})"; + } + internal class ArgumentImpl : ArgumentImpl, IArgument + { public ArgumentImpl(int index) : base(index, typeof(T)) { } } + + internal class LocalImpl : ILocal + { + public LocalBuilder Builder { get; } + public string? Name { get; } + public Type LocalType => Builder.LocalType; + public LocalImpl(LocalBuilder builder, string? name) { Builder = builder; Name = name; } + public override string ToString() => $"Local({Builder.LocalIndex}, {LocalType.Name}){(Name != null ? $" // {Name}" : "")}"; + } + internal class LocalImpl : LocalImpl, ILocal + { public LocalImpl(LocalBuilder builder, string? name) : base(builder, name) { } } +} + +public class Comparison +{ + public static Comparison NotEqual { get; } = new(OpCodes.Bne_Un); + public static Comparison LessThan { get; } = new(OpCodes.Blt); + public static Comparison LessOrEq { get; } = new(OpCodes.Ble); + public static Comparison Equal { get; } = new(OpCodes.Beq); + public static Comparison GreaterOrEq { get; } = new(OpCodes.Bge); + public static Comparison GreaterThan { get; } = new(OpCodes.Bgt); + + public OpCode Code { get; } + private Comparison(OpCode code) => Code = code; +} + +public interface IArgument +{ + int Index { get; } + Type ArgumentType { get; } +} +public interface IArgument + : IArgument { } + +public interface ILocal +{ + LocalBuilder Builder { get; } + Type LocalType { get; } +} +public interface ILocal + : ILocal { } diff --git a/src/gaemstone/Utility/IL/QueryActionGenerator.cs b/src/gaemstone/Utility/IL/QueryActionGenerator.cs new file mode 100644 index 0000000..c345db0 --- /dev/null +++ b/src/gaemstone/Utility/IL/QueryActionGenerator.cs @@ -0,0 +1,327 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using gaemstone.ECS; +using static flecs_hub.flecs; + +namespace gaemstone.Utility.IL; + +public unsafe class QueryActionGenerator +{ + private static readonly PropertyInfo _iteratorUniverseProp = typeof(Iterator).GetProperty(nameof(Iterator.Universe))!; + private static readonly PropertyInfo _iteratorDeltaTimeProp = typeof(Iterator).GetProperty(nameof(Iterator.DeltaTime))!; + private static readonly PropertyInfo _iteratorCountProp = typeof(Iterator).GetProperty(nameof(Iterator.Count))!; + private static readonly MethodInfo _iteratorFieldMethod = typeof(Iterator).GetMethod(nameof(Iterator.Field))!; + private static readonly MethodInfo _iteratorFieldIsSetMethod = typeof(Iterator).GetMethod(nameof(Iterator.FieldIsSet))!; + private static readonly MethodInfo _iteratorEntityMethod = typeof(Iterator).GetMethod(nameof(Iterator.Entity))!; + + private static readonly MethodInfo _handleFromIntPtrMethod = typeof(GCHandle).GetMethod(nameof(GCHandle.FromIntPtr))!; + private static readonly PropertyInfo _handleTargetProp = typeof(GCHandle).GetProperty(nameof(GCHandle.Target))!; + + private static readonly ConditionalWeakTable _cache = new(); + private static readonly Dictionary, ILocal>> _uniqueParameters = new() { + [typeof(Iterator)] = (IL, iter, i) => { IL.Load(iter); }, + [typeof(Universe)] = (IL, iter, i) => { IL.Load(iter, _iteratorUniverseProp); }, + [typeof(TimeSpan)] = (IL, iter, i) => { IL.Load(iter, _iteratorDeltaTimeProp); }, + [typeof(Entity)] = (IL, iter, i) => { IL.Load(iter); IL.Load(i); IL.Call(_iteratorEntityMethod); }, + }; + + public Universe Universe { get; } + public MethodInfo Method { get; } + public ParamInfo[] Parameters { get; } + + public ecs_filter_desc_t Filter { get; } + public Action GeneratedAction { get; } + public string ReadableString { get; } + + public void RunWithTryCatch(object? instance, Iterator iter) + { + try { GeneratedAction(instance, iter); } catch { + Console.WriteLine("Exception occured while running:"); + Console.WriteLine(" " + Method); + Console.WriteLine(); + Console.WriteLine("Method's IL code:"); + Console.WriteLine(ReadableString); + Console.WriteLine(); + throw; + } + } + + public QueryActionGenerator(Universe universe, MethodInfo method) + { + Universe = universe; + Method = method; + + Parameters = method.GetParameters().Select(ParamInfo.Build).ToArray(); + if (!Parameters.Any(c => c.IsRequired && (c.Kind != ParamKind.Unique))) + throw new ArgumentException($"At least one parameter in {method} is required"); + + var filter = default(ecs_filter_desc_t); + var name = "<>Query_" + string.Join("_", Parameters.Select(p => p.UnderlyingType.Name)); + var genMethod = new DynamicMethod(name, null, new[] { typeof(object), typeof(Iterator) }); + var IL = new ILGeneratorWrapper(genMethod); + + var instanceArg = IL.Argument(0); + var iteratorArg = IL.Argument(1); + + var counter = 0; // Counter for fields actually part of the filter terms. + var fieldLocals = new ILocal[Parameters.Length]; + var tempLocals = new ILocal[Parameters.Length]; + + for (var i = 0; i < Parameters.Length; i++) { + var p = Parameters[i]; + if (p.Kind == ParamKind.Unique) continue; + + // Update the flecs filter to look for this type. + // Term index is 0-based and field index (used below) is 1-based, so increasing counter here works out. + ref var term = ref filter.terms[counter++]; + term.id = Universe.Lookup(p.UnderlyingType); + term.inout = p.Kind switch { + ParamKind.In => ecs_inout_kind_t.EcsIn, + ParamKind.Out => ecs_inout_kind_t.EcsOut, + ParamKind.Has or ParamKind.Not => ecs_inout_kind_t.EcsInOutNone, + _ => ecs_inout_kind_t.EcsInOut, + }; + term.oper = p.Kind switch { + ParamKind.Not => ecs_oper_kind_t.EcsNot, + _ when !p.IsRequired => ecs_oper_kind_t.EcsOptional, + _ => ecs_oper_kind_t.EcsAnd, + }; + if (p.Source != null) term.src = new() { id = Universe.Lookup(p.Source) }; + + // Create a Span local and initialize it to iterator.Field(i). + var spanType = typeof(Span<>).MakeGenericType(p.FieldType); + fieldLocals[i] = IL.Local(spanType, $"field_{counter}"); + if (p.Kind is ParamKind.Has or ParamKind.Not) { + // If a "has" or "not" parameter is a struct, we require a temporary local that + // we can later load onto the stack when loading the arguments for the action. + if (p.ParameterType.IsValueType) { + IL.Comment($"temp_{counter} = default({p.ParameterType});"); + tempLocals[i] = IL.Local(p.ParameterType); + IL.LoadAddr(tempLocals[i]); + IL.Init(tempLocals[i].LocalType); + } + } else if (p.IsRequired) { + IL.Comment($"field_{counter} = iterator.Field<{p.FieldType.Name}>({counter})"); + IL.Load(iteratorArg); + IL.LoadConst(counter); + IL.Call(_iteratorFieldMethod.MakeGenericMethod(p.FieldType)); + IL.Store(fieldLocals[i]); + } else { + IL.Comment($"field_{counter} = iterator.FieldIsSet({counter}) ? iterator.Field<{p.FieldType.Name}>({counter}) : default"); + var elseLabel = IL.DefineLabel(); + var doneLabel = IL.DefineLabel(); + IL.Load(iteratorArg); + IL.LoadConst(counter); + IL.Call(_iteratorFieldIsSetMethod); + IL.GotoIfFalse(elseLabel); + IL.Load(iteratorArg); + IL.LoadConst(counter); + IL.Call(_iteratorFieldMethod.MakeGenericMethod(p.FieldType)); + IL.Store(fieldLocals[i]); + IL.Goto(doneLabel); + IL.MarkLabel(elseLabel); + IL.LoadAddr(fieldLocals[i]); + IL.Init(spanType); + IL.MarkLabel(doneLabel); + } + + if (p.Kind == ParamKind.Nullable) { + IL.Comment($"temp_{counter} = default({p.ParameterType});"); + tempLocals[i] = IL.Local(p.ParameterType); + IL.LoadAddr(tempLocals[i]); + IL.Init(tempLocals[i].LocalType); + } + } + + // If there's any reference type parameters, we need to define a GCHandle local. + var hasReferenceType = Parameters + .Where(p => p.Kind != ParamKind.Unique) + .Any(p => !p.UnderlyingType.IsValueType); + var handleLocal = hasReferenceType ? IL.Local() : null; + + using (IL.For(() => IL.Load(iteratorArg, _iteratorCountProp), out var currentLocal)) { + if (!Method.IsStatic) + IL.Load(instanceArg); + for (var i = 0; i < Parameters.Length; i++) { + var p = Parameters[i]; + if (p.Kind == ParamKind.Unique) { + IL.Comment($"Unique parameter {p.ParameterType}"); + _uniqueParameters[p.ParameterType](IL, iteratorArg, currentLocal); + } else if (p.Kind is ParamKind.Has or ParamKind.Not) { + if (p.ParameterType.IsValueType) + IL.LoadObj(tempLocals[i]!); + else IL.LoadNull(); + } else { + var spanType = typeof(Span<>).MakeGenericType(p.FieldType); + var spanItemMethod = spanType.GetProperty("Item")!.GetMethod!; + var spanLengthMethod = spanType.GetProperty("Length")!.GetMethod!; + + IL.Comment($"Parameter {p.ParameterType}"); + if (p.IsByRef) { + IL.LoadAddr(fieldLocals[i]!); + IL.Load(currentLocal); + IL.Call(spanItemMethod); + } else if (p.IsRequired) { + IL.LoadAddr(fieldLocals[i]!); + IL.Load(currentLocal); + IL.Call(spanItemMethod); + IL.LoadObj(p.FieldType); + } else { + var elseLabel = IL.DefineLabel(); + var doneLabel = IL.DefineLabel(); + IL.LoadAddr(fieldLocals[i]!); + IL.Call(spanLengthMethod); + IL.GotoIfFalse(elseLabel); + IL.LoadAddr(fieldLocals[i]!); + IL.Load(currentLocal); + IL.Call(spanItemMethod); + IL.LoadObj(p.FieldType); + if (p.Kind == ParamKind.Nullable) + IL.New(p.ParameterType); + IL.Goto(doneLabel); + IL.MarkLabel(elseLabel); + if (p.Kind == ParamKind.Nullable) + IL.LoadObj(tempLocals[i]!); + else IL.LoadNull(); + IL.MarkLabel(doneLabel); + } + + if (!p.UnderlyingType.IsValueType) { + IL.Comment($"Convert nint to {p.UnderlyingType}"); + IL.Call(_handleFromIntPtrMethod); + IL.Store(handleLocal!); + IL.LoadAddr(handleLocal!); + IL.Call(_handleTargetProp.GetMethod!); + IL.Cast(p.UnderlyingType); + } + } + } + IL.Call(Method); + } + + IL.Return(); + + Filter = filter; + GeneratedAction = genMethod.CreateDelegate>(); + ReadableString = IL.ToReadableString(); + } + + public static QueryActionGenerator GetOrBuild(Universe universe, MethodInfo method) + =>_cache.GetValue(method, m => new QueryActionGenerator(universe, m)); + + public class ParamInfo + { + public ParameterInfo Info { get; } + public int Index { get; } + + public ParamKind Kind { get; } + public Type ParameterType { get; } + public Type UnderlyingType { get; } + public Type FieldType { get; } + + public Type? Source { get; } + + public bool IsRequired => (Kind < ParamKind.Nullable); + public bool IsByRef => (Kind >= ParamKind.In) && (Kind <= ParamKind.Ref); + + private ParamInfo( + ParameterInfo info, int index, ParamKind kind, + Type paramType, Type underlyingType) + { + Info = info; + Index = index; + + Kind = kind; + ParameterType = paramType; + UnderlyingType = underlyingType; + // Reference types have a backing type of nint - they're pointers. + FieldType = underlyingType.IsValueType ? underlyingType : typeof(nint); + + // If the underlying type has EntityAttribute, it's a singleton. + if (UnderlyingType.Has()) Source = underlyingType; + if (Info.Get() is SourceAttribute attr) Source = attr.Type; + } + + public static ParamInfo Build(ParameterInfo info, int index) + { + if (info.IsOptional) throw new ArgumentException("Optional parameters are not supported\nParameter: " + info); + if (info.ParameterType.IsArray) throw new ArgumentException("Arrays are not supported\nParameter: " + info); + if (info.ParameterType.IsPointer) throw new ArgumentException("Pointers are not supported\nParameter: " + info); + + if (_uniqueParameters.ContainsKey(info.ParameterType)) + return new(info, index, ParamKind.Unique, info.ParameterType, info.ParameterType); + + var isByRef = info.ParameterType.IsByRef; + var isNullable = info.IsNullable(); + + if (info.Has()) { + if (isByRef || isNullable) throw new ArgumentException( + "Parameter with NotAttribute must not be ByRef or nullable\nParameter: " + info); + return new(info, index, ParamKind.Not, info.ParameterType, info.ParameterType); + } + + if (info.Has() || info.ParameterType.Has()) { + if (isByRef || isNullable) throw new ArgumentException( + "Parameter with HasAttribute / TagAttribute must not be ByRef or nullable\nParameter: " + info); + return new(info, index, ParamKind.Has, info.ParameterType, info.ParameterType); + } + + var kind = ParamKind.Normal; + var underlyingType = info.ParameterType; + + if (info.IsNullable()) { + if (info.ParameterType.IsValueType) + underlyingType = Nullable.GetUnderlyingType(info.ParameterType)!; + kind = ParamKind.Nullable; + } + + if (info.ParameterType.IsByRef) { + if (kind == ParamKind.Nullable) throw new ArgumentException( + "ByRef and Nullable are not supported together\nParameter: " + info); + underlyingType = info.ParameterType.GetElementType()!; + if (!underlyingType.IsValueType) throw new ArgumentException( + "Reference types can't also be ByRef\nParameter: " + info); + kind = info.IsIn ? ParamKind.In + : info.IsOut ? ParamKind.Out + : ParamKind.Ref; + } + + if (underlyingType.IsPrimitive) throw new ArgumentException( + "Primitives are not supported\nParameter: " + info); + + return new(info, index, kind, info.ParameterType, underlyingType); + } + } + + public enum ParamKind + { + /// Parameter is not part of terms, handled uniquely, such as Universe and Entity. + Unique, + /// Passed by value. + Normal, + /// Struct passed with the "in" modifier. + In, + /// Struct passed with the "out" modifier. + Out, + /// Struct passed with the "ref" modifier. + Ref, + /// + /// Only checks for presence. + /// Manually applied with . + /// Automatically applied for types with . + /// + Has, + /// Struct passed as Nullable<T>. + Nullable, + /// + /// Only checks for absence. + /// Applied with . + /// + Not, + } +} diff --git a/src/gaemstone/Utility/RandomExtensions.cs b/src/gaemstone/Utility/RandomExtensions.cs new file mode 100644 index 0000000..be007b9 --- /dev/null +++ b/src/gaemstone/Utility/RandomExtensions.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; + +namespace gaemstone.Utility; + +public static class RandomExtensions +{ + public static bool NextBool(this Random rnd, double chance) + => rnd.NextDouble() < chance; + + public static double NextDouble(this Random rnd, double max) + => rnd.NextDouble() * max; + public static double NextDouble(this Random rnd, double min, double max) + => min + rnd.NextDouble() * (max - min); + + public static float NextFloat(this Random rnd) + => (float)rnd.NextDouble(); + public static float NextFloat(this Random rnd, float max) + => (float)rnd.NextDouble() * max; + public static float NextFloat(this Random rnd, float min, float max) + => min + (float)rnd.NextDouble() * (max - min); + + public static T Pick(this Random rnd, params T[] elements) + => elements[rnd.Next(elements.Length)]; + public static T Pick(this Random rnd, IReadOnlyList elements) + => elements[rnd.Next(elements.Count)]; + public static T Pick(this Random rnd, Span elements) + => elements[rnd.Next(elements.Length)]; + +#pragma warning disable CS8509 // Switch expression is not exhaustive. + public static T Pick(this Random rnd, T elem1, T elem2) + => rnd.Next(2) switch { 0 => elem1, 1 => elem2 }; + public static T Pick(this Random rnd, T elem1, T elem2, T elem3) + => rnd.Next(3) switch { 0 => elem1, 1 => elem2, 2 => elem3 }; + public static T Pick(this Random rnd, T elem1, T elem2, T elem3, T elem4) + => rnd.Next(4) switch { 0 => elem1, 1 => elem2, 2 => elem3, 3 => elem4 }; +#pragma warning restore CS8509 +} diff --git a/src/gaemstone/Utility/ReflectionExtensions.cs b/src/gaemstone/Utility/ReflectionExtensions.cs new file mode 100644 index 0000000..99882bc --- /dev/null +++ b/src/gaemstone/Utility/ReflectionExtensions.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace gaemstone.Utility; + +public static class ReflectionExtensions +{ + public static string GetFriendlyName(this Type type) + { + if (!type.IsGenericType) return type.Name; + var name = type.Name; + var sb = new StringBuilder(name[..name.IndexOf('`')]); + sb.Append('<'); + sb.AppendJoin(",", type.GenericTypeArguments.Select(GetFriendlyName)); + sb.Append('>'); + return sb.ToString(); + } + + public static T? Get(this MemberInfo member) + where T : Attribute => member.GetCustomAttribute(); + public static IEnumerable GetMultiple(this MemberInfo member) + where T : Attribute => member.GetCustomAttributes(); + public static bool Has(this MemberInfo member) + where T : Attribute => member.GetCustomAttribute() != null; + + public static T? Get(this ParameterInfo member) + where T : Attribute => member.GetCustomAttribute(); + public static IEnumerable GetMultiple(this ParameterInfo member) + where T : Attribute => member.GetCustomAttributes(); + public static bool Has(this ParameterInfo member) + where T : Attribute => member.GetCustomAttribute() != null; + + + public static bool IsNullable(this PropertyInfo property) => + IsNullable(property.PropertyType, property.DeclaringType, property.CustomAttributes); + public static bool IsNullable(this FieldInfo field) => + IsNullable(field.FieldType, field.DeclaringType, field.CustomAttributes); + public static bool IsNullable(this ParameterInfo parameter) => + IsNullable(parameter.ParameterType, parameter.Member, parameter.CustomAttributes); + + // https://stackoverflow.com/a/58454489 + static bool IsNullable(Type memberType, MemberInfo? declaringType, IEnumerable customAttributes) + { + if (memberType.IsValueType) return (Nullable.GetUnderlyingType(memberType) != null); + + var nullable = customAttributes.FirstOrDefault( + x => (x.AttributeType.FullName == "System.Runtime.CompilerServices.NullableAttribute")); + if ((nullable != null) && (nullable.ConstructorArguments.Count == 1)) { + var attributeArgument = nullable.ConstructorArguments[0]; + if (attributeArgument.ArgumentType == typeof(byte[])) { + var args = (ReadOnlyCollection)attributeArgument.Value!; + if ((args.Count > 0) && (args[0].ArgumentType == typeof(byte))) + return (byte)args[0].Value! == 2; + } else if (attributeArgument.ArgumentType == typeof(byte)) + return (byte)attributeArgument.Value! == 2; + } + + for (var type = declaringType; type != null; type = type.DeclaringType) { + var context = type.CustomAttributes.FirstOrDefault( + x => (x.AttributeType.FullName == "System.Runtime.CompilerServices.NullableContextAttribute")); + if ((context != null) && (context.ConstructorArguments.Count == 1) && + (context.ConstructorArguments[0].ArgumentType == typeof(byte))) + return (byte)context.ConstructorArguments[0].Value! == 2; + } + + // Couldn't find a suitable attribute. + return false; + } +} diff --git a/src/gaemstone/Utility/TypeWrapper.cs b/src/gaemstone/Utility/TypeWrapper.cs new file mode 100644 index 0000000..af44813 --- /dev/null +++ b/src/gaemstone/Utility/TypeWrapper.cs @@ -0,0 +1,229 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Reflection.Emit; +using System.Runtime.CompilerServices; + +namespace gaemstone.Utility; + +public interface ITypeWrapper +{ + Type Type { get; } + + int Size { get; } + bool IsUnmanaged { get; } + + IFieldWrapper GetFieldForAutoProperty(string propertyName); + IFieldWrapper GetFieldForAutoProperty(PropertyInfo property); + IFieldWrapper GetField(string fieldName); + IFieldWrapper GetField(FieldInfo field); +} + +public interface IFieldWrapper +{ + ITypeWrapper DeclaringType { get; } + FieldInfo FieldInfo { get; } + PropertyInfo? PropertyInfo { get; } + + Func ClassGetter { get; } + Action ClassSetter { get; } +} + +public static class TypeWrapper +{ + static readonly Dictionary _typeCache = new(); + + public static TypeWrapper For() + => TypeWrapper.Instance; + public static ITypeWrapper For(Type type) + { + if (!_typeCache.TryGetValue(type, out var wrapper)) + _typeCache.Add(type, wrapper = (ITypeWrapper)typeof(TypeWrapper<>) + .MakeGenericType(type).GetProperty("Instance", BindingFlags.Static | BindingFlags.NonPublic)! + .GetMethod!.Invoke(null, Array.Empty())!); + return wrapper; + } +} + +public class TypeWrapper : ITypeWrapper +{ + internal static TypeWrapper Instance { get; } = new(); + + readonly Dictionary _fieldCache = new(); + + public Type Type => typeof(TType); + + public int Size { get; } = Unsafe.SizeOf(); + public bool IsUnmanaged { get; } = !RuntimeHelpers.IsReferenceOrContainsReferences(); + + TypeWrapper() { } + + IFieldWrapper ITypeWrapper.GetFieldForAutoProperty(string propertyName) => GetFieldForAutoProperty(propertyName); + IFieldWrapper ITypeWrapper.GetFieldForAutoProperty(PropertyInfo property) => GetFieldForAutoProperty(property); + IFieldWrapper ITypeWrapper.GetField(string fieldName) => GetField(fieldName); + IFieldWrapper ITypeWrapper.GetField(FieldInfo field) => GetField(field); + + public IFieldWrapperForType GetFieldForAutoProperty(string propertyName) + { + var property = Type.GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + if (property == null) throw new MissingMemberException(Type.FullName, propertyName); + var field = Type.GetField($"<{property.Name}>k__BackingField", BindingFlags.Instance | BindingFlags.NonPublic); + if (field == null) throw new ArgumentException($"Could not find backing field for property {property}"); + return GetField(field, property); + } + public IFieldWrapperForType GetFieldForAutoProperty(PropertyInfo property) + { + if (property.DeclaringType != Type) throw new ArgumentException( + $"Specified PropertyInfo {property} needs to be a member of type {Type}", nameof(property)); + var field = Type.GetField($"<{property.Name}>k__BackingField", BindingFlags.Instance | BindingFlags.NonPublic); + if (field == null) throw new ArgumentException($"Could not find backing field for property {property}"); + return GetField(field, property); + } + + public IFieldWrapperForType GetField(string fieldName) + { + var field = Type.GetField(fieldName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + if (field == null) throw new MissingFieldException(Type.FullName, fieldName); + return GetField(field, null); + } + public IFieldWrapperForType GetField(FieldInfo field) + { + if (field.DeclaringType != Type) throw new ArgumentException( + $"Specified FieldInfo {field} needs to be a member of type {Type}", nameof(field)); + return GetField(field, null); + } + + + public FieldWrapper GetFieldForAutoProperty(string propertyName) + { + var property = Type.GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + if (property == null) throw new MissingMemberException(Type.FullName, propertyName); + var field = Type.GetField($"<{property.Name}>k__BackingField", BindingFlags.Instance | BindingFlags.NonPublic); + if (field == null) throw new ArgumentException($"Could not find backing field for property {property}"); + return GetField(field, property); + } + public FieldWrapper GetFieldForAutoProperty(PropertyInfo property) + { + if (property.DeclaringType != Type) throw new ArgumentException( + $"Specified PropertyInfo {property} needs to be a member of type {Type}", nameof(property)); + var field = Type.GetField($"<{property.Name}>k__BackingField", BindingFlags.Instance | BindingFlags.NonPublic); + if (field == null) throw new ArgumentException($"Could not find backing field for property {property}"); + return GetField(field, property); + } + + public FieldWrapper GetField(string fieldName) + { + var field = Type.GetField(fieldName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + if (field == null) throw new MissingFieldException(Type.FullName, fieldName); + return GetField(field, null); + } + public FieldWrapper GetField(FieldInfo field) + { + if (field.DeclaringType != Type) throw new ArgumentException( + $"Specified FieldInfo {field} needs to be a member of type {Type}", nameof(field)); + return GetField(field, null); + } + + + IFieldWrapperForType GetField(FieldInfo field, PropertyInfo? property) + { + if (_fieldCache.TryGetValue(field, out var cached)) return cached; + var type = typeof(FieldWrapper<>).MakeGenericType(typeof(TType), field.FieldType); + var wrapper = (IFieldWrapperForType)Activator.CreateInstance( + type, BindingFlags.Instance | BindingFlags.NonPublic, + null, new object?[] { this, field, property }, null)!; + _fieldCache.Add(field, wrapper); + return wrapper; + } + + FieldWrapper GetField(FieldInfo field, PropertyInfo? property) + { + if (_fieldCache.TryGetValue(field, out var cached)) return (FieldWrapper)cached; + if (field.FieldType != typeof(TField)) throw new ArgumentException( + $"FieldType ({field.FieldType}) does not match TField ({typeof(TField)})", nameof(TField)); + var wrapper = new FieldWrapper(this, field, property); + _fieldCache.Add(field, wrapper); + return wrapper; + } + + + public interface IFieldWrapperForType : IFieldWrapper + { + delegate object? ValueGetterAction(in TType obj); + delegate void ValueSetterAction(ref TType obj, object? value); + + Func IFieldWrapper.ClassGetter => (obj) => ClassGetter((TType)obj); + Action IFieldWrapper.ClassSetter => (obj, value) => ClassSetter((TType)obj, value); + new Func ClassGetter { get; } + new Action ClassSetter { get; } + + ValueGetterAction ByRefGetter { get; } + ValueSetterAction ByRefSetter { get; } + } + + public class FieldWrapper : IFieldWrapperForType + { + public delegate TField ValueGetterAction(in TType obj); + public delegate void ValueSetterAction(ref TType obj, TField value); + + Func? _classGetter; + Action? _classSetter; + ValueGetterAction? _byRefGetter; + ValueSetterAction? _byRefSetter; + + public ITypeWrapper DeclaringType { get; } + public FieldInfo FieldInfo { get; } + public PropertyInfo? PropertyInfo { get; } + + internal FieldWrapper(ITypeWrapper type, FieldInfo field, PropertyInfo? property) + { DeclaringType = type; FieldInfo = field; PropertyInfo = property; } + + Func IFieldWrapperForType.ClassGetter => (obj) => ClassGetter(obj); + Action IFieldWrapperForType.ClassSetter => (obj, value) => ClassSetter(obj, (TField)value!); + public Func ClassGetter => _classGetter ??= BuildGetter>(false); + public Action ClassSetter => _classSetter ??= BuildSetter>(false); + + IFieldWrapperForType.ValueGetterAction IFieldWrapperForType.ByRefGetter => (in TType obj) => ByRefGetter(in obj); + IFieldWrapperForType.ValueSetterAction IFieldWrapperForType.ByRefSetter => (ref TType obj, object? value) => ByRefSetter(ref obj, (TField)value!); + public ValueGetterAction ByRefGetter => _byRefGetter ??= BuildGetter(true); + public ValueSetterAction ByRefSetter => _byRefSetter ??= BuildSetter(true); + + + TDelegate BuildGetter(bool byRef) + where TDelegate : Delegate + { + if (DeclaringType.Type.IsValueType && !byRef) throw new InvalidOperationException( + $"Can't build getter for value type ({DeclaringType.Type}) without using ref"); + var method = new DynamicMethod( + $"Get_{DeclaringType.Type.Name}_{FieldInfo.Name}{(byRef ? "_ByRef" : "")}", + typeof(TField), new[] { byRef ? typeof(TType).MakeByRefType() : typeof(TType) }, + typeof(TType).Module, true); + var il = method.GetILGenerator(); + il.Emit(OpCodes.Ldarg_0); + if (byRef && !DeclaringType.Type.IsValueType) + il.Emit(OpCodes.Ldind_Ref); + il.Emit(OpCodes.Ldfld, FieldInfo); + il.Emit(OpCodes.Ret); + return method.CreateDelegate(); + } + + TDelegate BuildSetter(bool byRef) + where TDelegate : Delegate + { + if (DeclaringType.Type.IsValueType && !byRef) throw new InvalidOperationException( + $"Can't build setter for value type ({DeclaringType.Type}) without using ref"); + var method = new DynamicMethod( + $"Set_{DeclaringType.Type.Name}_{FieldInfo.Name}{(byRef ? "_ByRef" : "")}", + null, new[] { byRef ? typeof(TType).MakeByRefType() : typeof(TType), typeof(TField) }, + typeof(TType).Module, true); + var il = method.GetILGenerator(); + il.Emit(OpCodes.Ldarg_0); + if (byRef && !DeclaringType.Type.IsValueType) + il.Emit(OpCodes.Ldind_Ref); + il.Emit(OpCodes.Ldarg_1); + il.Emit(OpCodes.Stfld, FieldInfo); + il.Emit(OpCodes.Ret); + return method.CreateDelegate(); + } + } +} diff --git a/src/gaemstone/gaemstone.csproj b/src/gaemstone/gaemstone.csproj new file mode 100644 index 0000000..92bd2a3 --- /dev/null +++ b/src/gaemstone/gaemstone.csproj @@ -0,0 +1,18 @@ + + + + net6.0 + disable + enable + true + + + + + + + + + + +