From 53e52928a4d1689d25ec536ced5f555a875169a7 Mon Sep 17 00:00:00 2001 From: DWVoid Date: Wed, 30 Jan 2019 04:41:46 +0800 Subject: [PATCH 01/23] Reinit Client With Xenko Engine --- .gitignore | 20 +- Core/Core.csproj | 91 +---- Core/Core.xkpkg | 17 + Core/LogPort.cs | 19 + Core/Math/Vector.cs | 4 +- Core/Module/Module.cs | 20 +- Core/Network/Client.cs | 20 +- Core/Network/ConnectionHost.cs | 74 ++-- Core/Network/Protocol.cs | 16 +- Core/Network/Protocols.cs | 31 +- Core/Network/Server.cs | 26 +- Core/Properties/AssemblyInfo.cs | 54 --- Core/Services.cs | 4 +- Core/Utilities/RateController.cs | 22 +- Core/Utilities/StrictDispose.cs | 30 +- Game/Game.csproj | 92 ++--- Game/Game.xkpkg | 17 + Game/Network/Client.cs | 21 +- Game/Network/Protocols.cs | 39 +- Game/Network/Server.cs | 22 +- Game/Properties/AssemblyInfo.cs | 54 --- Game/TaskDispatcher.cs | 136 +++---- Game/Terrain/Block.cs | 54 +-- Game/Terrain/Matrix.cs | 2 +- Game/Utilities/OrderedList.cs | 8 +- Game/World/Chunk.cs | 32 +- Game/World/Player.cs | 64 ++-- Game/World/PlayerObject.cs | 14 +- Game/World/World.cs | 43 +-- Game/World/WorldTasks.cs | 93 ++--- Game/packages.config | 5 - {Modules/Main => Main}/Main.cs | 4 +- Main/Main.csproj | 24 ++ Main/Main.xkpkg | 17 + Modules/Main/Main.csproj | 59 --- Modules/Main/Properties/AssemblyInfo.cs | 54 --- NEWorld.Linux/NEWorld.Linux.csproj | 23 ++ NEWorld.Linux/NEWorld.Linux.xkpkg | 17 + NEWorld.Linux/NEWorldApp.cs | 13 + NEWorld.Linux/Resources/Icon.ico | Bin 0 -> 34750 bytes .../Assets/EffectCompileLog.xkeffectlog | 218 +++++++++++ NEWorld.Windows/NEWorld.Windows.csproj | 20 + NEWorld.Windows/NEWorld.Windows.xkpkg | 17 + NEWorld.Windows/NEWorldApp.cs | 13 + NEWorld.Windows/Resources/Icon.ico | Bin 0 -> 34750 bytes NEWorld.macOS/NEWorld.macOS.csproj | 23 ++ NEWorld.macOS/NEWorld.macOS.xkpkg | 17 + NEWorld.macOS/NEWorldApp.cs | 13 + NEWorld.macOS/Resources/Icon.ico | Bin 0 -> 34750 bytes NEWorld.sln | 72 ++-- NEWorld.sln.DotSettings.user | 23 +- NEWorld/Assets/GameSettings.xkgamesettings | 62 +++ NEWorld/Assets/GraphicsCompositor.xkgfxcomp | 203 ++++++++++ NEWorld/Assets/Ground Material.xkmat | 12 + NEWorld/Assets/Ground.xkpromodel | 12 + NEWorld/Assets/MainScene.xkscene | 132 +++++++ NEWorld/Assets/Skybox texture.xktex | 9 + NEWorld/Assets/Skybox.xksky | 5 + NEWorld/Assets/Sphere Material.xkmat | 12 + NEWorld/Assets/Sphere.xkpromodel | 11 + NEWorld/BasicCameraController.cs | 186 +++++++++ NEWorld/GameScene.cs | 340 ----------------- NEWorld/MainScript.cs | 242 ++++++++++++ NEWorld/NEWorld.csproj | 118 ++---- NEWorld/NEWorld.xkpkg | 18 + NEWorld/NkSdl.cs | 353 ------------------ NEWorld/Program.cs | 62 --- NEWorld/Properties/AssemblyInfo.cs | 54 --- NEWorld/Renderer/BlockTextures.cs | 153 -------- .../Renderer/{ChunkRenderer.cs => RdChunk.cs} | 98 ++--- NEWorld/Renderer/RdWorld.cs | 142 +++++++ NEWorld/Renderer/WorldRenderer.cs | 230 ------------ NEWorld/Resources/skybox_texture_ldr.dds | Bin 0 -> 1573012 bytes NEWorld/SDL2-CS.dll | Bin 88064 -> 0 bytes NEWorld/Widget.cs | 77 ---- NEWorld/WidgetManager.cs | 47 --- NEWorld/Window.cs | 152 -------- NEWorld/packages.config | 5 - NEWorldShell/Cli.cs | 6 +- NEWorldShell/Command.cs | 2 +- NEWorldShell/NEWorldShell.csproj | 4 + OpenGL/Basics.cs | 87 ----- OpenGL/ColorFormats.cs | 195 ---------- OpenGL/DataBuffer.cs | 171 --------- OpenGL/FrameBuffer.cs | 95 ----- OpenGL/Init.cs | 54 --- OpenGL/OpenGL.csproj | 69 ---- OpenGL/Others.cs | 93 ----- OpenGL/Properties/AssemblyInfo.cs | 54 --- OpenGL/RenderBuffer.cs | 72 ---- OpenGL/Shader.cs | 182 --------- OpenGL/Texture.cs | 146 -------- OpenGL/VertexArray.cs | 113 ------ 93 files changed, 2092 insertions(+), 3757 deletions(-) create mode 100644 Core/Core.xkpkg create mode 100644 Core/LogPort.cs delete mode 100644 Core/Properties/AssemblyInfo.cs create mode 100644 Game/Game.xkpkg delete mode 100644 Game/Properties/AssemblyInfo.cs delete mode 100644 Game/packages.config rename {Modules/Main => Main}/Main.cs (98%) create mode 100644 Main/Main.csproj create mode 100644 Main/Main.xkpkg delete mode 100644 Modules/Main/Main.csproj delete mode 100644 Modules/Main/Properties/AssemblyInfo.cs create mode 100644 NEWorld.Linux/NEWorld.Linux.csproj create mode 100644 NEWorld.Linux/NEWorld.Linux.xkpkg create mode 100644 NEWorld.Linux/NEWorldApp.cs create mode 100644 NEWorld.Linux/Resources/Icon.ico create mode 100644 NEWorld.Windows/Assets/EffectCompileLog.xkeffectlog create mode 100644 NEWorld.Windows/NEWorld.Windows.csproj create mode 100644 NEWorld.Windows/NEWorld.Windows.xkpkg create mode 100644 NEWorld.Windows/NEWorldApp.cs create mode 100644 NEWorld.Windows/Resources/Icon.ico create mode 100644 NEWorld.macOS/NEWorld.macOS.csproj create mode 100644 NEWorld.macOS/NEWorld.macOS.xkpkg create mode 100644 NEWorld.macOS/NEWorldApp.cs create mode 100644 NEWorld.macOS/Resources/Icon.ico create mode 100644 NEWorld/Assets/GameSettings.xkgamesettings create mode 100644 NEWorld/Assets/GraphicsCompositor.xkgfxcomp create mode 100644 NEWorld/Assets/Ground Material.xkmat create mode 100644 NEWorld/Assets/Ground.xkpromodel create mode 100644 NEWorld/Assets/MainScene.xkscene create mode 100644 NEWorld/Assets/Skybox texture.xktex create mode 100644 NEWorld/Assets/Skybox.xksky create mode 100644 NEWorld/Assets/Sphere Material.xkmat create mode 100644 NEWorld/Assets/Sphere.xkpromodel create mode 100644 NEWorld/BasicCameraController.cs delete mode 100644 NEWorld/GameScene.cs create mode 100644 NEWorld/MainScript.cs create mode 100644 NEWorld/NEWorld.xkpkg delete mode 100644 NEWorld/NkSdl.cs delete mode 100644 NEWorld/Program.cs delete mode 100644 NEWorld/Properties/AssemblyInfo.cs delete mode 100644 NEWorld/Renderer/BlockTextures.cs rename NEWorld/Renderer/{ChunkRenderer.cs => RdChunk.cs} (50%) create mode 100644 NEWorld/Renderer/RdWorld.cs delete mode 100644 NEWorld/Renderer/WorldRenderer.cs create mode 100644 NEWorld/Resources/skybox_texture_ldr.dds delete mode 100644 NEWorld/SDL2-CS.dll delete mode 100644 NEWorld/Widget.cs delete mode 100644 NEWorld/WidgetManager.cs delete mode 100644 NEWorld/Window.cs delete mode 100644 NEWorld/packages.config delete mode 100644 OpenGL/Basics.cs delete mode 100644 OpenGL/ColorFormats.cs delete mode 100644 OpenGL/DataBuffer.cs delete mode 100644 OpenGL/FrameBuffer.cs delete mode 100644 OpenGL/Init.cs delete mode 100644 OpenGL/OpenGL.csproj delete mode 100644 OpenGL/Others.cs delete mode 100644 OpenGL/Properties/AssemblyInfo.cs delete mode 100644 OpenGL/RenderBuffer.cs delete mode 100644 OpenGL/Shader.cs delete mode 100644 OpenGL/Texture.cs delete mode 100644 OpenGL/VertexArray.cs diff --git a/.gitignore b/.gitignore index 4fd1aef..64a0628 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,17 @@ .idea/ -*/bin -*/obj -.vs/ packages/ -/bin/Debug -/Modules/Main/obj/Debug +*.user +*.lock +*.lock.json +.vs/ +_ReSharper* +*.suo +*.VC.db +*.vshost.exe +*.manifest +*.sdf +[Bb]in/ +obj/ +*/[Bb]in/ +*/[Oo]bj/ +Cache/ diff --git a/Core/Core.csproj b/Core/Core.csproj index 8814d3f..dbd2fdf 100644 --- a/Core/Core.csproj +++ b/Core/Core.csproj @@ -1,75 +1,24 @@ - - - + + - Debug - AnyCPU - {ECB0E309-625F-4A24-926D-D1D23C1B7693} - Library - Properties - Core - Core - v4.7.1 - 512 - 7.2 + netstandard2.0 - - AnyCPU - true - full - false - ..\bin\Debug\ - DEBUG;TRACE - prompt - 4 - true - - - AnyCPU - pdbonly - true - ..\bin\Release\ - TRACE - prompt - 4 - true - - - - - ..\packages\MsgPack.Cli.1.0.0\lib\net46\MsgPack.dll - True - - - - - - - - - - - - - - - - - - - - - - + - + + + + + + + + + + + + + + + - - - \ No newline at end of file + diff --git a/Core/Core.xkpkg b/Core/Core.xkpkg new file mode 100644 index 0000000..2602096 --- /dev/null +++ b/Core/Core.xkpkg @@ -0,0 +1,17 @@ +!Package +SerializedVersion: {Assets: 3.1.0.0} +Meta: + Name: Core + Version: 1.0.0 + Authors: [] + Owners: [] + Dependencies: null +AssetFolders: + - Path: !dir Assets +ResourceFolders: + - !dir Resources +OutputGroupDirectories: {} +ExplicitFolders: [] +Bundles: [] +TemplateFolders: [] +RootAssets: [] diff --git a/Core/LogPort.cs b/Core/LogPort.cs new file mode 100644 index 0000000..f3f734d --- /dev/null +++ b/Core/LogPort.cs @@ -0,0 +1,19 @@ +namespace Core +{ + public static class LogPort + { + public static Xenko.Core.Diagnostics.Logger Logger { get; set; } + + public static void Debug(string str) + { + if (Logger != null) + { + Logger.Debug(str); + } + else + { + System.Console.WriteLine(str); + } + } + } +} diff --git a/Core/Math/Vector.cs b/Core/Math/Vector.cs index 7f7b417..a04cfab 100644 --- a/Core/Math/Vector.cs +++ b/Core/Math/Vector.cs @@ -110,9 +110,6 @@ public void Normalize() public static Vec3 operator /(Vec3 lhs, T rhs) => new Vec3(Divide(lhs.X, rhs), Divide(lhs.Y, rhs), Divide(lhs.Z, rhs)); - public T ChebyshevDistance(Vec3 rhs) => - (T) Max(Max(Abs(Substract(X, rhs.X)), Abs(Substract(Y, rhs.Y))), Abs(Substract(Z, rhs.Z))); - public bool Equals(Vec3 other) => EqualityComparer.Default.Equals(X, other.X) && EqualityComparer.Default.Equals(Y, other.Y) && EqualityComparer.Default.Equals(Z, other.Z); @@ -134,4 +131,5 @@ public override int GetHashCode() } } } + } \ No newline at end of file diff --git a/Core/Module/Module.cs b/Core/Module/Module.cs index 3651516..c70efdb 100644 --- a/Core/Module/Module.cs +++ b/Core/Module/Module.cs @@ -39,10 +39,10 @@ public class Modules { private Modules() { - _modules = new Dictionary>(); + modules = new Dictionary>(); } - public void SetBasePath(string path) => _basePath = path; + public void SetBasePath(string path) => basePath = path; public void Load(string moduleFile) { @@ -56,12 +56,12 @@ public void Load(string moduleFile) { var module = (IModule) Activator.CreateInstance(type); module.CoInitialize(); - _modules.Add(type.FullName ?? "", new KeyValuePair(module, assembly)); - Console.WriteLine($"Loaded Module : {type}"); + modules.Add(type.FullName ?? "", new KeyValuePair(module, assembly)); + LogPort.Debug($"Loaded Module : {type}"); } catch (Exception e) { - Console.WriteLine($"Module {type} Load Failure : {e}"); + LogPort.Debug($"Module {type} Load Failure : {e}"); } } } @@ -69,19 +69,19 @@ public void Load(string moduleFile) Services.ScanAssembly(assembly); } - public IModule this[string name] => _modules[name].Key; + public IModule this[string name] => modules[name].Key; public void UnloadAll() { - foreach (var module in _modules) + foreach (var module in modules) module.Value.Key.CoFinalize(); - _modules.Clear(); + modules.Clear(); } public static Modules Instance => Singleton.Instance; - private string _basePath = Path.Modules(); + private string basePath = Path.Modules(); - private readonly Dictionary> _modules; + private readonly Dictionary> modules; } } \ No newline at end of file diff --git a/Core/Network/Client.cs b/Core/Network/Client.cs index 99d6e97..26e6704 100644 --- a/Core/Network/Client.cs +++ b/Core/Network/Client.cs @@ -19,6 +19,7 @@ using System.Collections.Generic; using System.Net.Sockets; +using System.Threading.Tasks; using Core.Utilities; namespace Core.Network @@ -29,27 +30,28 @@ public Client(string address, int port) : base(address, port) { RegisterProtocol(Singleton.Instance); RegisterProtocol(new ProtocolFetchProtocol.Client()); - _connHost.AddConnection(this); + connHost.AddConnection(this); } - public void RegisterProtocol(Protocol newProtocol) => _connHost.RegisterProtocol(newProtocol); + public void RegisterProtocol(Protocol newProtocol) => connHost.RegisterProtocol(newProtocol); - public void NegotiateProtocols() + public async Task NegotiateProtocols() { var skvm = new Dictionary(); - foreach (var protocol in _connHost.Protocols) + foreach (var protocol in connHost.Protocols) skvm.Add(protocol.Name(), protocol); - foreach (var entry in ProtocolFetchProtocol.Client.Get(GetConnection())) + var reply = await ProtocolFetchProtocol.Client.Get(GetConnection()); + foreach (var entry in reply) skvm[entry.Key].Id = entry.Value; - _connHost.Protocols.Sort(ProtocolSorter); + connHost.Protocols.Sort(ProtocolSorter); } - public ConnectionHost.Connection GetConnection() => _connHost.GetConnection(0); + public ConnectionHost.Connection GetConnection() => connHost.GetConnection(0); - public new void Close() => _connHost.CloseAll(); + public new void Close() => connHost.CloseAll(); private static int ProtocolSorter(Protocol x, Protocol y) => Comparer.Default.Compare(x.Id, y.Id); - private readonly ConnectionHost _connHost = new ConnectionHost(); + private readonly ConnectionHost connHost = new ConnectionHost(); } } \ No newline at end of file diff --git a/Core/Network/ConnectionHost.cs b/Core/Network/ConnectionHost.cs index af63e76..918162f 100644 --- a/Core/Network/ConnectionHost.cs +++ b/Core/Network/ConnectionHost.cs @@ -23,6 +23,7 @@ using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; +using Xenko.Rendering; namespace Core.Network { @@ -32,11 +33,11 @@ public class Connection { public Connection(ulong cid, TcpClient client, ConnectionHost server) { - _cid = cid; - _client = client; - _server = server; + this.cid = cid; + this.client = client; + this.server = server; Stream = client.GetStream(); - _finalize = Start(); + finalize = Start(); } public bool Valid { get; private set; } @@ -44,39 +45,46 @@ public Connection(ulong cid, TcpClient client, ConnectionHost server) public void Close() { CloseDown(); - _finalize.Wait(); + finalize.Wait(); } private async Task Start() { Valid = true; + LogPort.Debug("Connection Start"); var headerCache = new byte[8]; // ["NWRC"] + Int32BE(Protocol Id) while (Valid) { try { - var bytesRead = await Stream.ReadAsync(headerCache, 0, 8); + int bytesRead; + do + { + bytesRead = await Stream.ReadAsync(headerCache, 0, 8); + } while (bytesRead != 8); + LogPort.Debug("Read Complete"); if (VerifyPackageValidity(headerCache, bytesRead)) try { - _server.Protocols[GetProtocolId(headerCache)].HandleRequest(Stream); + server.Protocols[GetProtocolId(headerCache)].HandleRequest(Stream); } catch (Exception e) { - Console.WriteLine(e.ToString()); + LogPort.Debug(e.ToString()); } else throw new Exception("Bad Package Recieved"); } catch (Exception e) { - if (_client.Connected == false) + if (client.Connected == false) break; - Console.WriteLine($"Encountering Exception {e}"); + LogPort.Debug($"Encountering Exception {e}"); throw; } } - + + LogPort.Debug("Connection CloseDown"); CloseDown(); } @@ -85,8 +93,8 @@ private void CloseDown() if (!Valid) return; Valid = false; Stream.Close(); // Cancellation Token Doesn't Work. Hard Close is adopted. - _client.Close(); - Interlocked.Increment(ref _server._invalidConnections); + client.Close(); + Interlocked.Increment(ref server.invalidConnections); } public NetworkStream Stream { get; } @@ -96,25 +104,25 @@ private void CloseDown() private static bool VerifyPackageValidity(byte[] head, int read) => head[0] == 'N' && head[1] == 'W' && head[2] == 'R' && head[3] == 'C' && read == 8; - private ulong _cid; - private readonly TcpClient _client; - private readonly ConnectionHost _server; - private readonly Task _finalize; + private ulong cid; + private readonly TcpClient client; + private readonly ConnectionHost server; + private readonly Task finalize; } public ConnectionHost() { - _clients = new Dictionary(); + clients = new Dictionary(); Protocols = new List(); } - public object Lock => _protocolLock; + public object Lock => protocolLock; private const double UtilizationThreshold = 0.75; public void RegisterProtocol(Protocol newProtocol) { - lock (_protocolLock) + lock (protocolLock) { Protocols.Add(newProtocol); } @@ -122,41 +130,41 @@ public void RegisterProtocol(Protocol newProtocol) public void SweepInvalidConnectionsIfNecessary() { - var utilization = 1.0 - (double) _invalidConnections / _clients.Count; + var utilization = 1.0 - (double) invalidConnections / clients.Count; if (utilization < UtilizationThreshold) SweepInvalidConnections(); } - public Connection GetConnection(ulong id) => _clients[id]; + public Connection GetConnection(ulong id) => clients[id]; private void SweepInvalidConnections() { - foreach (var hd in _clients.ToList()) + foreach (var hd in clients.ToList()) if (!hd.Value.Valid) { - _clients.Remove(hd.Key); - Interlocked.Decrement(ref _invalidConnections); + clients.Remove(hd.Key); + Interlocked.Decrement(ref invalidConnections); } } public void AddConnection(TcpClient conn) { - _clients.Add(_sessionIdTop, new Connection(_sessionIdTop, conn, this)); - ++_sessionIdTop; + clients.Add(sessionIdTop, new Connection(sessionIdTop, conn, this)); + ++sessionIdTop; } - private ulong _sessionIdTop; - private int _invalidConnections; + private ulong sessionIdTop; + private int invalidConnections; public readonly List Protocols; - private readonly Dictionary _clients; - private readonly object _protocolLock = new object(); + private readonly Dictionary clients; + private readonly object protocolLock = new object(); public void CloseAll() { - foreach (var hd in _clients) + foreach (var hd in clients) hd.Value.Close(); } - public int CountConnections() => _clients.Count - _invalidConnections; + public int CountConnections() => clients.Count - invalidConnections; } } \ No newline at end of file diff --git a/Core/Network/Protocol.cs b/Core/Network/Protocol.cs index c8eeee2..4431f93 100644 --- a/Core/Network/Protocol.cs +++ b/Core/Network/Protocol.cs @@ -95,29 +95,29 @@ public LohOptimizedAlloc(int size) { Size = size; if (size > LohThreshold) - _cache = new ThreadLocal(); + cache = new ThreadLocal(); } - public byte[] Get() => _cache == null ? new byte[Size] : _cache.Value ?? (_cache.Value = new byte[Size]); + public byte[] Get() => cache == null ? new byte[Size] : cache.Value ?? (cache.Value = new byte[Size]); public readonly int Size; - private readonly ThreadLocal _cache; + private readonly ThreadLocal cache; } - protected FixedLengthProtocol(int length) => _alloc = new LohOptimizedAlloc(length); + protected FixedLengthProtocol(int length) => alloc = new LohOptimizedAlloc(length); protected override byte[] PullRequestData(NetworkStream nstream) { // TODO : Provide Read Closure To All NetworkStream Readers - var ret = _alloc.Get(); + var ret = alloc.Get(); var read = 0; - while (read < _alloc.Size) - read += nstream.Read(ret, read, _alloc.Size - read); + while (read < alloc.Size) + read += nstream.Read(ret, read, alloc.Size - read); return ret; } - private readonly LohOptimizedAlloc _alloc; + private readonly LohOptimizedAlloc alloc; } public abstract class StubProtocol : Protocol diff --git a/Core/Network/Protocols.cs b/Core/Network/Protocols.cs index 1da960c..05d8abd 100644 --- a/Core/Network/Protocols.cs +++ b/Core/Network/Protocols.cs @@ -17,6 +17,7 @@ // along with NEWorld. If not, see . // +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Net.Sockets; @@ -31,32 +32,34 @@ public static class ProtocolFetchProtocol { public class Server : FixedLengthProtocol { - public Server(List protocols) : base(Size) => _protocols = protocols; + public Server(List protocols) : base(Size) => this.protocols = protocols; protected override void HandleRequestData(byte[] data, NetworkStream stream) { var request = SerialSend.UnpackSingleObject(data); var current = 0; - var reply = new KeyValuePair[_protocols.Count]; - foreach (var prot in _protocols) + var reply = new KeyValuePair[protocols.Count]; + foreach (var prot in protocols) reply[current++] = new KeyValuePair(prot.Name(), prot.Id); Send(stream, Reply(request, SerialReply.PackSingleObjectAsBytes(reply))); + LogPort.Debug($"Client Connected, Protocol Handshake Proceeding on Client Session{request}"); } public override string Name() => "FetchProtocols"; - private readonly List _protocols; + private readonly List protocols; } public class Client : StubProtocol { public override string Name() => "FetchProtocols"; - public static KeyValuePair[] Get(ConnectionHost.Connection conn) + public static async Task[]> Get(ConnectionHost.Connection conn) { var session = ProtocolReply.AllocSession(); Send(conn.Stream, Request(1, SerialSend.PackSingleObjectAsBytes(session.Key))); - return SerialReply.UnpackSingleObject(session.Value.Result); + var result = await session.Value; + return SerialReply.UnpackSingleObject(result); } } @@ -90,26 +93,26 @@ public static KeyValuePair> AllocSession() => private KeyValuePair> AllocSessionInternal() { - if (!_sessionIds.TryDequeue(out var newId)) - newId = Interlocked.Increment(ref _idTop) - 1; + if (!sessionIds.TryDequeue(out var newId)) + newId = Interlocked.Increment(ref idTop) - 1; var completionSource = new TaskCompletionSource(); - while (!_sessions.TryAdd(newId, completionSource)) ; + while (!sessions.TryAdd(newId, completionSource)) ; return new KeyValuePair>(newId, completionSource.Task); } private void SessionDispatch(int sessionId, byte[] dataSegment) { TaskCompletionSource completion; - while (!_sessions.TryRemove(sessionId, out completion)) ; + while (!sessions.TryRemove(sessionId, out completion)) ; completion.SetResult(dataSegment); - _sessionIds.Enqueue(sessionId); + sessionIds.Enqueue(sessionId); } - private int _idTop; - private readonly ConcurrentQueue _sessionIds = new ConcurrentQueue(); + private int idTop; + private readonly ConcurrentQueue sessionIds = new ConcurrentQueue(); - private readonly ConcurrentDictionary> _sessions = + private readonly ConcurrentDictionary> sessions = new ConcurrentDictionary>(); private static int GetSessionId(byte[] head) => head[0] << 24 | head[1] << 16 | head[2] << 8 | head[3]; diff --git a/Core/Network/Server.cs b/Core/Network/Server.cs index daa4ef6..4d2c89e 100644 --- a/Core/Network/Server.cs +++ b/Core/Network/Server.cs @@ -29,12 +29,12 @@ public class Server : TcpListener public Server(int port) : base(IPAddress.Any, port) { RegisterProtocol(Singleton.Instance); - RegisterProtocol(new ProtocolFetchProtocol.Server(_connHost.Protocols)); + RegisterProtocol(new ProtocolFetchProtocol.Server(connHost.Protocols)); } public void Run() { - lock (_connHost.Lock) + lock (connHost.Lock) { Boot(); ListenConnections().Wait(); @@ -49,15 +49,15 @@ public async Task RunAsync() ShutDown(); } - public void RegisterProtocol(Protocol newProtocol) => _connHost.RegisterProtocol(newProtocol); + public void RegisterProtocol(Protocol newProtocol) => connHost.RegisterProtocol(newProtocol); - public void StopServer() => _exit = true; + public void StopServer() => exit = true; - public int CountConnections() => _connHost.CountConnections(); + public int CountConnections() => connHost.CountConnections(); private void Boot() { - _exit = false; + exit = false; AssignProtocolIdentifiers(); Start(); } @@ -66,31 +66,31 @@ private void Boot() private async Task ListenConnections() { - while (!_exit) + while (!exit) { try { - _connHost.AddConnection(await AcceptTcpClientAsync()); + connHost.AddConnection(await AcceptTcpClientAsync()); } catch { // ignored } - _connHost.SweepInvalidConnectionsIfNecessary(); + connHost.SweepInvalidConnectionsIfNecessary(); } - _connHost.CloseAll(); + connHost.CloseAll(); } private void AssignProtocolIdentifiers() { var current = 0; - foreach (var protocol in _connHost.Protocols) + foreach (var protocol in connHost.Protocols) protocol.Id = current++; } - private bool _exit; - private readonly ConnectionHost _connHost = new ConnectionHost(); + private bool exit; + private readonly ConnectionHost connHost = new ConnectionHost(); } } \ No newline at end of file diff --git a/Core/Properties/AssemblyInfo.cs b/Core/Properties/AssemblyInfo.cs deleted file mode 100644 index c0fc6f9..0000000 --- a/Core/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,54 +0,0 @@ -// -// Core: AssemblyInfo.cs -// NEWorld: A Free Game with Similar Rules to Minecraft. -// Copyright (C) 2015-2018 NEWorld Team -// -// NEWorld is free software: you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published -// by the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// NEWorld is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY -// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General -// Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with NEWorld. If not, see . -// - -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Core")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Core")] -[assembly: AssemblyCopyright("Copyright © 2018")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("ECB0E309-625F-4A24-926D-D1D23C1B7693")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] \ No newline at end of file diff --git a/Core/Services.cs b/Core/Services.cs index d2098af..9d6fd93 100644 --- a/Core/Services.cs +++ b/Core/Services.cs @@ -58,7 +58,7 @@ private class DisposeList public readonly List List = new List(); } - static Services() => ScanAssembly(Assembly.GetCallingAssembly()); + static Services() => ScanAssembly(Assembly.GetExecutingAssembly()); public static void Inject() where TP : IDisposable => Inject(typeof(TP)); @@ -87,7 +87,7 @@ public static bool TryGet(string name, out TI ins) } catch (ServiceManagerException) { - ins = default; + ins = default(TI); return false; } } diff --git a/Core/Utilities/RateController.cs b/Core/Utilities/RateController.cs index 0ede70f..9d6d298 100644 --- a/Core/Utilities/RateController.cs +++ b/Core/Utilities/RateController.cs @@ -33,35 +33,35 @@ public struct RateController */ public RateController(int rate = 0) { - _rate = rate; - _last = _due = DateTime.Now; + this.rate = rate; + last = due = DateTime.Now; } /** * \brief Synchronize the internal timer with system clock. For cases that the timer doesn't keep up or forced resets */ - public void Sync() => _last = _due = DateTime.Now; + public void Sync() => last = due = DateTime.Now; /** * \brief Get elapsed time from the start of the tick, in milliseconds * \return Elapsed time from the start of the tick, in milliseconds */ - public int GetDeltaTimeMs() => (DateTime.Now - _last).Milliseconds; + public int GetDeltaTimeMs() => (DateTime.Now - last).Milliseconds; /** * \brief Check if the deadline of the current tick has pased * \return true if the deadline is passed, false otherwise */ - public bool IsDue() => _rate <= 0 || DateTime.Now >= _due; + public bool IsDue() => rate <= 0 || DateTime.Now >= due; /** * \brief Increase the internal timer by one tick. Sets the current due time as the starting time of the next tick */ public void IncreaseTimer() { - if (_rate <= 0) return; - _last = _due; - _due += TimeSpan.FromMilliseconds(1000 / _rate); + if (rate <= 0) return; + last = due; + due += TimeSpan.FromMilliseconds(1000 / rate); } /** @@ -70,13 +70,13 @@ public void IncreaseTimer() public void Yield() { if (!IsDue()) - Thread.Sleep(_due - DateTime.Now); + Thread.Sleep(due - DateTime.Now); else Sync(); IncreaseTimer(); } - private readonly int _rate; - private DateTime _due, _last; + private readonly int rate; + private DateTime due, last; } } \ No newline at end of file diff --git a/Core/Utilities/StrictDispose.cs b/Core/Utilities/StrictDispose.cs index 5d26503..08aefb3 100644 --- a/Core/Utilities/StrictDispose.cs +++ b/Core/Utilities/StrictDispose.cs @@ -27,24 +27,24 @@ public class StrictDispose : IDisposable protected T Inject(T target) where T : StrictDispose { - if (_first != null) - target._sibling = _first; - _first = target; + if (first != null) + target.sibling = first; + first = target; return target; } protected void Reject(T target, bool disposeNow = true) where T : StrictDispose { - if (target == _first) + if (target == first) { - _first = target._sibling; + first = target.sibling; return; } - var current = _first; - while (current._sibling != target) - current = current._sibling; - current._sibling = target._sibling; + var current = first; + while (current.sibling != target) + current = current.sibling; + current.sibling = target.sibling; if (disposeNow) target.Dispose(); @@ -56,23 +56,23 @@ protected virtual void Release() private void TravelRelease() { - for (var current = _first; current != null; current = current._sibling) + for (var current = first; current != null; current = current.sibling) current.Dispose(); } public void Dispose() { - if (_released) + if (released) return; TravelRelease(); Release(); - _released = true; + released = true; } - public bool Valid() => !_released; + public bool Valid() => !released; - private StrictDispose _first, _sibling; + private StrictDispose first, sibling; - private bool _released; + private bool released; } } \ No newline at end of file diff --git a/Game/Game.csproj b/Game/Game.csproj index 0c12459..1dccb82 100644 --- a/Game/Game.csproj +++ b/Game/Game.csproj @@ -1,81 +1,31 @@ - - - + + - Debug - AnyCPU - {F83C4945-ABFF-4856-94A3-D0D31C976A11} - Library - Properties - Game - Game - v4.7.1 - 512 + netstandard2.0 - - AnyCPU - true - full - false - ..\bin\Debug\ - DEBUG;TRACE - prompt - 4 + + true - - AnyCPU - pdbonly - true - ..\bin\Release\ - TRACE - prompt - 4 + + true + - - - ..\packages\MsgPack.Cli.1.0.0\lib\net46\MsgPack.dll - True - - - + + + + + + + + + + + - - - - - - - - - - - - - - - - - + - - - {ecb0e309-625f-4a24-926d-d1d23c1b7693} - Core - - - - - - - - \ No newline at end of file + diff --git a/Game/Game.xkpkg b/Game/Game.xkpkg new file mode 100644 index 0000000..cbfc060 --- /dev/null +++ b/Game/Game.xkpkg @@ -0,0 +1,17 @@ +!Package +SerializedVersion: {Assets: 3.1.0.0} +Meta: + Name: Game + Version: 1.0.0 + Authors: [] + Owners: [] + Dependencies: null +AssetFolders: + - Path: !dir Assets +ResourceFolders: + - !dir Resources +OutputGroupDirectories: {} +ExplicitFolders: [] +Bundles: [] +TemplateFolders: [] +RootAssets: [] diff --git a/Game/Network/Client.cs b/Game/Network/Client.cs index d4634d3..b8f8470 100644 --- a/Game/Network/Client.cs +++ b/Game/Network/Client.cs @@ -18,6 +18,7 @@ // using System; +using System.Threading.Tasks; using Core; namespace Game.Network @@ -25,13 +26,13 @@ namespace Game.Network [DeclareService("Game.Client")] public class Client : IDisposable { - public void Enable(string address, int port) + public async Task Enable(string address, int port) { - _client = new Core.Network.Client(address, port); - _client.RegisterProtocol(GetChunk = new GetChunk.Client(_client.GetConnection())); - _client.RegisterProtocol(GetAvailableWorldId = new GetAvailableWorldId.Client(_client.GetConnection())); - _client.RegisterProtocol(GetWorldInfo = new GetWorldInfo.Client(_client.GetConnection())); - _client.NegotiateProtocols(); + client = new Core.Network.Client(address, port); + client.RegisterProtocol(GetChunk = new GetChunk.Client(client.GetConnection())); + client.RegisterProtocol(GetAvailableWorldId = new GetAvailableWorldId.Client(client.GetConnection())); + client.RegisterProtocol(GetWorldInfo = new GetWorldInfo.Client(client.GetConnection())); + await client.NegotiateProtocols(); } public static GetChunk.Client GetChunk; @@ -40,15 +41,15 @@ public void Enable(string address, int port) public void Stop() { - _client.Close(); + client.Close(); } public void Dispose() { - _client?.Close(); - _client?.Dispose(); + client?.Close(); + client?.Dispose(); } - private Core.Network.Client _client; + private Core.Network.Client client; } } \ No newline at end of file diff --git a/Game/Network/Protocols.cs b/Game/Network/Protocols.cs index 75d579f..90293dd 100644 --- a/Game/Network/Protocols.cs +++ b/Game/Network/Protocols.cs @@ -20,11 +20,12 @@ using System.Collections.Generic; using System.Net.Sockets; using System.Threading; -using Core.Math; +using System.Threading.Tasks; using Core.Network; using Core.Utilities; using Game.World; using MsgPack.Serialization; +using Xenko.Core.Mathematics; namespace Game.Network { @@ -41,7 +42,7 @@ public Server() : base(Size) protected override void HandleRequestData(byte[] data, NetworkStream stream) { var request = From.UnpackSingleObject(data); - var chunkData = Get((uint) request[0], new Vec3(request[1], request[2], request[3])); + var chunkData = Get((uint) request[0], new Int3(request[1], request[2], request[3])); Send(stream, Request(Id)); Send(stream, data); Send(stream, chunkData); @@ -49,7 +50,7 @@ protected override void HandleRequestData(byte[] data, NetworkStream stream) private static readonly ThreadLocal LocalMemCache = new ThreadLocal(); - private static byte[] Get(uint worldId, Vec3 position) + private static byte[] Get(uint worldId, Int3 position) { // TODO: empty chunk optimization var world = Singleton.Instance.Worlds.Get(worldId); @@ -82,13 +83,13 @@ public class Client : FixedLengthProtocol { public override string Name() => "GetChunk"; - public Client(ConnectionHost.Connection conn) : base(32768 * 4 + Size) => _stream = conn.Stream; + public Client(ConnectionHost.Connection conn) : base(32768 * 4 + Size) => stream = conn.Stream; protected override void HandleRequestData(byte[] data, NetworkStream stream) { var req = From.UnpackSingleObject(data); var srv = Singleton.Instance; - var chk = new Chunk(new Vec3(req[1], req[2], req[3]), srv.Worlds.Get((uint) req[0])); + var chk = new Chunk(new Int3(req[1], req[2], req[3]), srv.Worlds.Get((uint) req[0])); for (var i = Size; i < 32768 * 4 + Size; i += 4) { ref var block = ref chk.Blocks[(i - Size) >> 2]; @@ -100,13 +101,13 @@ protected override void HandleRequestData(byte[] data, NetworkStream stream) srv.TaskDispatcher.Add(new World.World.ResetChunkTask((uint) req[0], chk)); } - public void Call(uint worldId, Vec3 position) + public void Call(uint worldId, Int3 position) { var data = new[] {(int) worldId, position.X, position.Y, position.Z}; - Send(_stream, Request(Id, From.PackSingleObjectAsBytes(data))); + Send(stream, Request(Id, From.PackSingleObjectAsBytes(data))); } - private readonly NetworkStream _stream; + private readonly NetworkStream stream; } private static readonly MessagePackSerializer From = MessagePackSerializer.Get(); @@ -132,18 +133,19 @@ protected override void HandleRequestData(byte[] data, NetworkStream stream) public class Client : StubProtocol { - public Client(ConnectionHost.Connection conn) => _stream = conn.Stream; + public Client(ConnectionHost.Connection conn) => stream = conn.Stream; public override string Name() => "GetAvailableWorldId"; - public uint[] Call() + public async Task Call() { var session = ProtocolReply.AllocSession(); - Send(_stream, Request(Id, SerialSend.PackSingleObjectAsBytes(session.Key))); - return SerialReply.UnpackSingleObject(session.Value.Result); + Send(stream, Request(Id, SerialSend.PackSingleObjectAsBytes(session.Key))); + var result = await session.Value; + return SerialReply.UnpackSingleObject(result); } - private readonly NetworkStream _stream; + private readonly NetworkStream stream; } private static readonly MessagePackSerializer SerialSend = MessagePackSerializer.Get(); @@ -175,18 +177,19 @@ protected override void HandleRequestData(byte[] data, NetworkStream stream) public class Client : StubProtocol { - public Client(ConnectionHost.Connection conn) => _stream = conn.Stream; + public Client(ConnectionHost.Connection conn) => stream = conn.Stream; public override string Name() => "GetWorldInfo"; - public Dictionary Call(uint wid) + public async Task> Call(uint wid) { var session = ProtocolReply.AllocSession(); - Send(_stream, Request(Id, SerialSend.PackSingleObjectAsBytes(new[] {session.Key, (int) wid}))); - return SerialReply.UnpackSingleObject(session.Value.Result); + Send(stream, Request(Id, SerialSend.PackSingleObjectAsBytes(new[] {session.Key, (int) wid}))); + var result = await session.Value; + return SerialReply.UnpackSingleObject(result); } - private readonly NetworkStream _stream; + private readonly NetworkStream stream; } private static readonly MessagePackSerializer SerialSend = MessagePackSerializer.Get(); diff --git a/Game/Network/Server.cs b/Game/Network/Server.cs index a8b533a..576ca14 100644 --- a/Game/Network/Server.cs +++ b/Game/Network/Server.cs @@ -29,33 +29,33 @@ public class Server : IDisposable { public void Enable(int port) { - _server = new Core.Network.Server(port); - _server.RegisterProtocol(new GetChunk.Server()); - _server.RegisterProtocol(new GetAvailableWorldId.Server()); - _server.RegisterProtocol(new GetWorldInfo.Server()); + server = new Core.Network.Server(port); + server.RegisterProtocol(new GetChunk.Server()); + server.RegisterProtocol(new GetAvailableWorldId.Server()); + server.RegisterProtocol(new GetWorldInfo.Server()); Singleton.Instance.Worlds.Add("test world"); } public void Run() { - _wait = _server.RunAsync(); + wait = server.RunAsync(); } - public int CountConnections() => _server.CountConnections(); + public int CountConnections() => server.CountConnections(); public void Stop() { - _server.StopServer(); - _wait.Wait(); + server.StopServer(); + wait.Wait(); } - private Task _wait; - private Core.Network.Server _server; + private Task wait; + private Core.Network.Server server; public void Dispose() { Stop(); - _wait?.Dispose(); + wait?.Dispose(); } } } \ No newline at end of file diff --git a/Game/Properties/AssemblyInfo.cs b/Game/Properties/AssemblyInfo.cs deleted file mode 100644 index 3609eca..0000000 --- a/Game/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,54 +0,0 @@ -// -// Game: AssemblyInfo.cs -// NEWorld: A Free Game with Similar Rules to Minecraft. -// Copyright (C) 2015-2018 NEWorld Team -// -// NEWorld is free software: you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published -// by the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// NEWorld is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY -// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General -// Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with NEWorld. If not, see . -// - -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Game")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Game")] -[assembly: AssemblyCopyright("Copyright © 2018")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("F83C4945-ABFF-4856-94A3-D0D31C976A11")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] \ No newline at end of file diff --git a/Game/TaskDispatcher.cs b/Game/TaskDispatcher.cs index 4a94d30..b359b3b 100644 --- a/Game/TaskDispatcher.cs +++ b/Game/TaskDispatcher.cs @@ -69,73 +69,73 @@ public class TaskDispatcher */ public TaskDispatcher(int threadNumber, ChunkService chunkService) { - _threadNumber = threadNumber; - _chunkService = chunkService; + this.threadNumber = threadNumber; + this.chunkService = chunkService; TimeUsed = new int[threadNumber]; - _mutex = new object(); - _readOnlyTasks = new List(); - _nextReadOnlyTasks = new List(); - _regularReadOnlyTasks = new List(); - _readWriteTasks = new List(); - _nextReadWriteTasks = new List(); - _regularReadWriteTasks = new List(); - _renderTasks = new List(); - _nextRenderTasks = new List(); + mutex = new object(); + readOnlyTasks = new List(); + nextReadOnlyTasks = new List(); + regularReadOnlyTasks = new List(); + readWriteTasks = new List(); + nextReadWriteTasks = new List(); + regularReadWriteTasks = new List(); + renderTasks = new List(); + nextRenderTasks = new List(); } ~TaskDispatcher() { - if (!_shouldExit) Stop(); + if (!shouldExit) Stop(); } public void Start() { - _shouldExit = false; - _roundNumber = 0; - _numberOfUnfinishedThreads = _threadNumber; - _threads = new List(_threadNumber); - for (var i = 0; i < _threadNumber; ++i) + shouldExit = false; + roundNumber = 0; + numberOfUnfinishedThreads = threadNumber; + threads = new List(threadNumber); + for (var i = 0; i < threadNumber; ++i) { var i1 = i; var trd = new Thread(() => { Worker(i1); }); trd.Start(); - _threads.Add(trd); + threads.Add(trd); } } public void Add(IReadOnlyTask task) { - lock (_mutex) - _nextReadOnlyTasks.Add(task); + lock (mutex) + nextReadOnlyTasks.Add(task); } public void Add(IReadWriteTask task) { - lock (_mutex) - _nextReadWriteTasks.Add(task); + lock (mutex) + nextReadWriteTasks.Add(task); } public void Add(IRenderTask task) { - lock (_mutex) - _nextRenderTasks.Add(task); + lock (mutex) + nextRenderTasks.Add(task); } public void AddRegular(IReadOnlyTask task) { - lock (_mutex) - _regularReadOnlyTasks.Add(task); + lock (mutex) + regularReadOnlyTasks.Add(task); } public void AddRegular(IReadWriteTask task) { - lock (_mutex) - _regularReadWriteTasks.Add(task); + lock (mutex) + regularReadWriteTasks.Add(task); } - public int GetRegularReadOnlyTaskCount() => _regularReadOnlyTasks.Count; + public int GetRegularReadOnlyTaskCount() => regularReadOnlyTasks.Count; - public int GetRegularReadWriteTaskCount() => _regularReadWriteTasks.Count; + public int GetRegularReadWriteTaskCount() => regularReadWriteTasks.Count; /** * \brief Process render tasks. @@ -143,12 +143,12 @@ public void AddRegular(IReadWriteTask task) */ public void ProcessRenderTasks() { - lock (_mutex) + lock (mutex) { - foreach (var task in _renderTasks) - task.Task(_chunkService); - _renderTasks.Clear(); - Generic.Swap(ref _renderTasks, ref _nextRenderTasks); + foreach (var task in renderTasks) + task.Task(chunkService); + renderTasks.Clear(); + Generic.Swap(ref renderTasks, ref nextRenderTasks); } } @@ -156,77 +156,77 @@ public void ProcessRenderTasks() public void Stop() { - _shouldExit = true; - foreach (var thread in _threads) + shouldExit = true; + foreach (var thread in threads) thread.Join(); } private void Worker(int threadId) { var meter = new RateController(30); - while (!_shouldExit) + while (!shouldExit) { // A tick starts - var currentRoundNumber = _roundNumber; + var currentRoundNumber = roundNumber; // Process read-only work. - for (var i = threadId; i < _readOnlyTasks.Count; i += _threadNumber) + for (var i = threadId; i < readOnlyTasks.Count; i += threadNumber) { - _readOnlyTasks[i].Task(_chunkService); + readOnlyTasks[i].Task(chunkService); } // Finish the tick TimeUsed[threadId] = meter.GetDeltaTimeMs(); // The last finished thread is responsible to do writing jobs - if (Interlocked.Decrement(ref _numberOfUnfinishedThreads) == 0) + if (Interlocked.Decrement(ref numberOfUnfinishedThreads) == 0) { // All other threads have finished? - foreach (var task in _readWriteTasks) + foreach (var task in readWriteTasks) { - task.Task(_chunkService); + task.Task(chunkService); } // ...and finish up! - _readOnlyTasks.Clear(); - _readWriteTasks.Clear(); - foreach (var task in _regularReadOnlyTasks) - _nextReadOnlyTasks.Add(task.Clone()); - foreach (var task in _regularReadWriteTasks) - _nextReadWriteTasks.Add(task.Clone()); - Generic.Swap(ref _readOnlyTasks, ref _nextReadOnlyTasks); - Generic.Swap(ref _readWriteTasks, ref _nextReadWriteTasks); + readOnlyTasks.Clear(); + readWriteTasks.Clear(); + foreach (var task in regularReadOnlyTasks) + nextReadOnlyTasks.Add(task.Clone()); + foreach (var task in regularReadWriteTasks) + nextReadWriteTasks.Add(task.Clone()); + Generic.Swap(ref readOnlyTasks, ref nextReadOnlyTasks); + Generic.Swap(ref readWriteTasks, ref nextReadWriteTasks); // Limit UPS meter.Yield(); // Time to move to next tick! // Notify other threads that we are good to go - _numberOfUnfinishedThreads = _threadNumber; - Interlocked.Increment(ref _roundNumber); + numberOfUnfinishedThreads = threadNumber; + Interlocked.Increment(ref roundNumber); } else { meter.Yield(); // Wait for other threads... - while (_roundNumber == currentRoundNumber) + while (roundNumber == currentRoundNumber) Thread.Yield(); } } } // TODO: replace it with lock-free structure. - private readonly object _mutex; - private List _readOnlyTasks, _nextReadOnlyTasks; - private readonly List _regularReadOnlyTasks; - private List _readWriteTasks, _nextReadWriteTasks; - private readonly List _regularReadWriteTasks; - private List _renderTasks, _nextRenderTasks; - private List _threads; - private readonly int _threadNumber; - private int _roundNumber; - private int _numberOfUnfinishedThreads; - private bool _shouldExit; - - private readonly ChunkService _chunkService; + private readonly object mutex; + private List readOnlyTasks, nextReadOnlyTasks; + private readonly List regularReadOnlyTasks; + private List readWriteTasks, nextReadWriteTasks; + private readonly List regularReadWriteTasks; + private List renderTasks, nextRenderTasks; + private List threads; + private readonly int threadNumber; + private int roundNumber; + private int numberOfUnfinishedThreads; + private bool shouldExit; + + private readonly ChunkService chunkService; } } \ No newline at end of file diff --git a/Game/Terrain/Block.cs b/Game/Terrain/Block.cs index 47cf893..13934ca 100644 --- a/Game/Terrain/Block.cs +++ b/Game/Terrain/Block.cs @@ -18,8 +18,8 @@ // using System.Collections.Generic; -using Core.Math; using Game.World; +using Xenko.Core.Mathematics; namespace Game.Terrain { @@ -32,7 +32,7 @@ public unsafe struct BlockTexCoord public interface IBlockRenderer { void FlushTexture(IBlockTextures textures); - void Render(IVertexBuilder target, Chunk chunk, Vec3 pos); + void Render(IVertexBuilder target, Chunk chunk, Int3 pos); } public interface IVertexBuilder @@ -50,47 +50,47 @@ public class DefaultBlockRenderer : IBlockRenderer { public DefaultBlockRenderer(uint[] data) { - _tex = new BlockTexCoord[6]; + tex = new BlockTexCoord[6]; for (var i = 0; i < 6; ++i) - _tex[i].Pos = data[i]; + tex[i].Pos = data[i]; } public unsafe void FlushTexture(IBlockTextures textures) { for (var i = 0; i < 6; ++i) - fixed (float* tex = _tex[0].D) - textures.GetTexturePos(tex, _tex[i].Pos); + fixed (float* tex = this.tex[0].D) + textures.GetTexturePos(tex, this.tex[i].Pos); } - public unsafe void Render(IVertexBuilder target, Chunk chunk, Vec3 pos) + public unsafe void Render(IVertexBuilder target, Chunk chunk, Int3 pos) { var worldpos = chunk.Position * Chunk.Size + pos; var curr = chunk[pos]; BlockData[] neighbors = { pos.X == Chunk.Size - 1 - ? chunk.World.GetBlock(new Vec3(worldpos.X + 1, worldpos.Y, worldpos.Z)) - : chunk[new Vec3(pos.X + 1, pos.Y, pos.Z)], + ? chunk.World.GetBlock(new Int3(worldpos.X + 1, worldpos.Y, worldpos.Z)) + : chunk[new Int3(pos.X + 1, pos.Y, pos.Z)], pos.X == 0 - ? chunk.World.GetBlock(new Vec3(worldpos.X - 1, worldpos.Y, worldpos.Z)) - : chunk[new Vec3(pos.X - 1, pos.Y, pos.Z)], + ? chunk.World.GetBlock(new Int3(worldpos.X - 1, worldpos.Y, worldpos.Z)) + : chunk[new Int3(pos.X - 1, pos.Y, pos.Z)], pos.Y == Chunk.Size - 1 - ? chunk.World.GetBlock(new Vec3(worldpos.X, worldpos.Y + 1, worldpos.Z)) - : chunk[new Vec3(pos.X, pos.Y + 1, pos.Z)], + ? chunk.World.GetBlock(new Int3(worldpos.X, worldpos.Y + 1, worldpos.Z)) + : chunk[new Int3(pos.X, pos.Y + 1, pos.Z)], pos.Y == 0 - ? chunk.World.GetBlock(new Vec3(worldpos.X, worldpos.Y - 1, worldpos.Z)) - : chunk[new Vec3(pos.X, pos.Y - 1, pos.Z)], + ? chunk.World.GetBlock(new Int3(worldpos.X, worldpos.Y - 1, worldpos.Z)) + : chunk[new Int3(pos.X, pos.Y - 1, pos.Z)], pos.Z == Chunk.Size - 1 - ? chunk.World.GetBlock(new Vec3(worldpos.X, worldpos.Y, worldpos.Z + 1)) - : chunk[new Vec3(pos.X, pos.Y, pos.Z + 1)], + ? chunk.World.GetBlock(new Int3(worldpos.X, worldpos.Y, worldpos.Z + 1)) + : chunk[new Int3(pos.X, pos.Y, pos.Z + 1)], pos.Z == 0 - ? chunk.World.GetBlock(new Vec3(worldpos.X, worldpos.Y, worldpos.Z - 1)) - : chunk[new Vec3(pos.X, pos.Y, pos.Z - 1)] + ? chunk.World.GetBlock(new Int3(worldpos.X, worldpos.Y, worldpos.Z - 1)) + : chunk[new Int3(pos.X, pos.Y, pos.Z - 1)] }; // Right if (AdjacentTest(curr, neighbors[0])) - fixed (float* tex = _tex[0].D) + fixed (float* tex = this.tex[0].D) target.AddPrimitive(4, tex[0], tex[1], 0.5f, 0.5f, 0.5f, pos.X + 1.0f, pos.Y + 1.0f, pos.Z + 1.0f, tex[0], tex[3], 0.5f, 0.5f, 0.5f, pos.X + 1.0f, pos.Y + 0.0f, pos.Z + 1.0f, @@ -100,7 +100,7 @@ public unsafe void Render(IVertexBuilder target, Chunk chunk, Vec3 pos) // Left if (AdjacentTest(curr, neighbors[1])) - fixed (float* tex = _tex[1].D) + fixed (float* tex = this.tex[1].D) target.AddPrimitive(4, tex[0], tex[1], 0.5f, 0.5f, 0.5f, pos.X + 0.0f, pos.Y + 1.0f, pos.Z + 0.0f, tex[0], tex[3], 0.5f, 0.5f, 0.5f, pos.X + 0.0f, pos.Y + 0.0f, pos.Z + 0.0f, @@ -110,7 +110,7 @@ public unsafe void Render(IVertexBuilder target, Chunk chunk, Vec3 pos) // Top if (AdjacentTest(curr, neighbors[2])) - fixed (float* tex = _tex[2].D) + fixed (float* tex = this.tex[2].D) target.AddPrimitive(4, tex[0], tex[1], 1.0f, 1.0f, 1.0f, pos.X + 0.0f, pos.Y + 1.0f, pos.Z + 0.0f, tex[0], tex[3], 1.0f, 1.0f, 1.0f, pos.X + 0.0f, pos.Y + 1.0f, pos.Z + 1.0f, @@ -120,7 +120,7 @@ public unsafe void Render(IVertexBuilder target, Chunk chunk, Vec3 pos) // Bottom if (AdjacentTest(curr, neighbors[3])) - fixed (float* tex = _tex[3].D) + fixed (float* tex = this.tex[3].D) target.AddPrimitive(4, tex[0], tex[1], 1.0f, 1.0f, 1.0f, pos.X + 0.0f, pos.Y + 0.0f, pos.Z + 1.0f, tex[0], tex[3], 1.0f, 1.0f, 1.0f, pos.X + 0.0f, pos.Y + 0.0f, pos.Z + 0.0f, @@ -130,7 +130,7 @@ public unsafe void Render(IVertexBuilder target, Chunk chunk, Vec3 pos) // Front if (AdjacentTest(curr, neighbors[4])) - fixed (float* tex = _tex[4].D) + fixed (float* tex = this.tex[4].D) target.AddPrimitive(4, tex[0], tex[1], 0.7f, 0.7f, 0.7f, pos.X + 0.0f, pos.Y + 1.0f, pos.Z + 1.0f, tex[0], tex[3], 0.7f, 0.7f, 0.7f, pos.X + 0.0f, pos.Y + 0.0f, pos.Z + 1.0f, @@ -140,7 +140,7 @@ public unsafe void Render(IVertexBuilder target, Chunk chunk, Vec3 pos) // Back if (AdjacentTest(curr, neighbors[5])) - fixed (float* tex = _tex[5].D) + fixed (float* tex = this.tex[5].D) target.AddPrimitive(4, tex[0], tex[1], 0.7f, 0.7f, 0.7f, pos.X + 1.0f, pos.Y + 1.0f, pos.Z + 0.0f, tex[0], tex[3], 0.7f, 0.7f, 0.7f, pos.X + 1.0f, pos.Y + 0.0f, pos.Z + 0.0f, @@ -151,12 +151,12 @@ public unsafe void Render(IVertexBuilder target, Chunk chunk, Vec3 pos) static bool AdjacentTest(BlockData a, BlockData b) => a.Id != 0 && !Blocks.Index[b.Id].IsOpaque && a.Id != b.Id; - private readonly BlockTexCoord[] _tex; + private readonly BlockTexCoord[] tex; } public static class BlockRenderers { - public static void Render(IVertexBuilder target, int id, Chunk chunk, Vec3 pos) + public static void Render(IVertexBuilder target, int id, Chunk chunk, Int3 pos) { if (Renderers.Count > 0 && Renderers[id] != null) Renderers[id].Render(target, chunk, pos); diff --git a/Game/Terrain/Matrix.cs b/Game/Terrain/Matrix.cs index 3d16927..43447ab 100644 --- a/Game/Terrain/Matrix.cs +++ b/Game/Terrain/Matrix.cs @@ -52,7 +52,7 @@ public static void ApplyPerspective(float fov, float aspect, float zNear, float public static void ModelRotate(float degree, Vec3 axis) => _model *= Mat4F.Rotation(degree, Conv(axis)); - public static void ModelTranslate(Vec3 diff) => _model *= Mat4F.Translation(Conv(diff)); + public static void ModelSetTranslate(Vec3 diff) => _model = Mat4F.Translation(Conv(diff)); public static Mat4F Get() => _model * _view * _projection; diff --git a/Game/Utilities/OrderedList.cs b/Game/Utilities/OrderedList.cs index 09c642d..468c220 100644 --- a/Game/Utilities/OrderedList.cs +++ b/Game/Utilities/OrderedList.cs @@ -56,11 +56,11 @@ public class OrderedListIntBaseEnum : IEnumerator> { public OrderedListIntBaseEnum(OrderedListIntBase host) => _base = host; - public bool MoveNext() => ++_position < _base.Size; + public bool MoveNext() => ++position < _base.Size; - public void Reset() => _position = -1; + public void Reset() => position = -1; - public KeyValuePair Current => _base.Data[_position]; + public KeyValuePair Current => _base.Data[position]; object IEnumerator.Current => Current; @@ -68,7 +68,7 @@ public void Dispose() { } - private int _position = -1; + private int position = -1; private readonly OrderedListIntBase _base; } diff --git a/Game/World/Chunk.cs b/Game/World/Chunk.cs index bfd30cb..b089c06 100644 --- a/Game/World/Chunk.cs +++ b/Game/World/Chunk.cs @@ -20,13 +20,13 @@ using System; using System.Collections.Generic; using System.Threading; -using Core.Math; +using Xenko.Core.Mathematics; namespace Game.World { public class Chunk { - public delegate void Generator(Vec3 chunkPos, BlockData[] chunkData, int daylightBrightness); + public delegate void Generator(Int3 chunkPos, BlockData[] chunkData, int daylightBrightness); // Chunk size private static bool _chunkGeneratorLoaded; @@ -48,7 +48,7 @@ public static void SetGenerator(Generator gen) } } - public Chunk(Vec3 position, World world) + public Chunk(Int3 position, World world) { Position = position; World = world; @@ -59,13 +59,13 @@ public Chunk(Vec3 position, World world) // TODO: somehow avoid it! not safe. public bool IsUpdated { get; set; } - public Vec3 Position { get; } + public Int3 Position { get; } public World World { get; } public BlockData[] Blocks { get; } - public BlockData this[Vec3 pos] + public BlockData this[Int3 pos] { get => Blocks[pos.X * Size * Size + pos.Y * Size + pos.Z]; set @@ -83,36 +83,36 @@ public void Build(int daylightBrightness) } // Reference Counting - public void MarkRequest() => _mLastRequestTime = DateTime.Now.Ticks; + public void MarkRequest() => mLastRequestTime = DateTime.Now.Ticks; public bool CheckReleaseable() => - DateTime.Now - new DateTime(Interlocked.Read(ref _mLastRequestTime)) > TimeSpan.FromSeconds(10); + DateTime.Now - new DateTime(Interlocked.Read(ref mLastRequestTime)) > TimeSpan.FromSeconds(10); // For Garbage Collection - private long _mLastRequestTime; + private long mLastRequestTime; } [Serializable] - public class ChunkManager : Dictionary, Chunk> + public class ChunkManager : Dictionary { - public bool IsLoaded(Vec3 chunkPos) => ContainsKey(chunkPos); + public bool IsLoaded(Int3 chunkPos) => ContainsKey(chunkPos); // Convert world position to chunk coordinate (one axis) public static int GetAxisPos(int pos) => pos >> Chunk.SizeLog2; // Convert world position to chunk coordinate (all axes) - public static Vec3 GetPos(Vec3 pos) => - new Vec3(GetAxisPos(pos.X), GetAxisPos(pos.Y), GetAxisPos(pos.Z)); + public static Int3 GetPos(Int3 pos) => + new Int3(GetAxisPos(pos.X), GetAxisPos(pos.Y), GetAxisPos(pos.Z)); // Convert world position to block coordinate in chunk (one axis) public static int GetBlockAxisPos(int pos) => pos & (Chunk.Size - 1); // Convert world position to block coordinate in chunk (all axes) - public static Vec3 GetBlockPos(Vec3 pos) => - new Vec3(GetBlockAxisPos(pos.X), GetBlockAxisPos(pos.Y), GetBlockAxisPos(pos.Z)); + public static Int3 GetBlockPos(Int3 pos) => + new Int3(GetBlockAxisPos(pos.X), GetBlockAxisPos(pos.Y), GetBlockAxisPos(pos.Z)); - public BlockData GetBlock(Vec3 pos) => this[GetPos(pos)][GetBlockPos(pos)]; + public BlockData GetBlock(Int3 pos) => this[GetPos(pos)][GetBlockPos(pos)]; - public void SetBlock(Vec3 pos, BlockData block) => this[GetPos(pos)][GetBlockPos(pos)] = block; + public void SetBlock(Int3 pos, BlockData block) => this[GetPos(pos)][GetBlockPos(pos)] = block; } } \ No newline at end of file diff --git a/Game/World/Player.cs b/Game/World/Player.cs index 9cb6a3d..cafde90 100644 --- a/Game/World/Player.cs +++ b/Game/World/Player.cs @@ -28,28 +28,28 @@ private class PlayerUpdateTask : IReadOnlyTask { public PlayerUpdateTask(Player player, uint worldId) { - _player = player; - _worldId = worldId; + this.player = player; + this.worldId = worldId; } - public void Task(ChunkService srv) => _player.Update(srv.Worlds.Get(_worldId)); + public void Task(ChunkService srv) => player.Update(srv.Worlds.Get(worldId)); IReadOnlyTask IReadOnlyTask.Clone() => (IReadOnlyTask) MemberwiseClone(); - private readonly Player _player; - private readonly uint _worldId; + private readonly Player player; + private readonly uint worldId; } public Player(uint worldId) : base(worldId) => Singleton.Instance.TaskDispatcher.AddRegular(new PlayerUpdateTask(this, WorldId)); - public void Accelerate(Vec3 acceleration) => _speed += acceleration; + public void Accelerate(Vec3 acceleration) => speed += acceleration; - public void AccelerateRotation(Vec3 acceleration) => _rotationSpeed += acceleration; + public void AccelerateRotation(Vec3 acceleration) => rotationSpeed += acceleration; - public void SetSpeed(Vec3 speed) => _speed = speed; + public void SetSpeed(Vec3 speed) => this.speed = speed; - public Vec3 PositionDelta => _positionDelta; + public Vec3 PositionDelta => positionDelta; public Vec3 RotationDelta { get; private set; } @@ -57,8 +57,8 @@ public override void Render() { } - private Vec3 _speed, _rotationSpeed; - private Vec3 _positionDelta; + private Vec3 speed, rotationSpeed; + private Vec3 positionDelta; public override void Update(World world) { @@ -69,42 +69,42 @@ public override void Update(World world) private void Move(World world) { - _positionDelta = Mat4D.Rotation(Rotation.Y, new Vec3(0.0f, 1.0f, 0.0f)).Transform(_speed, 0.0).Key; - var originalDelta = _positionDelta; - var hitboxes = world.GetHitboxes(Hitbox.Expand(_positionDelta)); + positionDelta = Mat4D.Rotation(Rotation.Y, new Vec3(0.0f, 1.0f, 0.0f)).Transform(speed, 0.0).Key; + var originalDelta = positionDelta; + var hitboxes = world.GetHitboxes(Hitbox.Expand(positionDelta)); foreach (var curr in hitboxes) - _positionDelta.X = Hitbox.MaxMoveOnXclip(curr, _positionDelta.X); - MoveHitbox(new Vec3(_positionDelta.X, 0.0, 0.0)); - if (_positionDelta.X != originalDelta.X) _speed.X = 0.0; + positionDelta.X = Hitbox.MaxMoveOnXclip(curr, positionDelta.X); + MoveHitbox(new Vec3(positionDelta.X, 0.0, 0.0)); + if (positionDelta.X != originalDelta.X) speed.X = 0.0; foreach (var curr in hitboxes) - _positionDelta.Z = Hitbox.MaxMoveOnZclip(curr, _positionDelta.Z); - MoveHitbox(new Vec3(0.0, 0.0, _positionDelta.Z)); - if (_positionDelta.Z != originalDelta.Z) _speed.Z = 0.0; + positionDelta.Z = Hitbox.MaxMoveOnZclip(curr, positionDelta.Z); + MoveHitbox(new Vec3(0.0, 0.0, positionDelta.Z)); + if (positionDelta.Z != originalDelta.Z) speed.Z = 0.0; foreach (var curr in hitboxes) - _positionDelta.Y = Hitbox.MaxMoveOnYclip(curr, _positionDelta.Y); - MoveHitbox(new Vec3(0.0, _positionDelta.Y, 0.0)); - if (_positionDelta.Y != originalDelta.Y) _speed.Y = 0.0; + positionDelta.Y = Hitbox.MaxMoveOnYclip(curr, positionDelta.Y); + MoveHitbox(new Vec3(0.0, positionDelta.Y, 0.0)); + if (positionDelta.Y != originalDelta.Y) speed.Y = 0.0; - Position += _positionDelta; + Position += positionDelta; } private static readonly bool RotationInteria = false; private void RotationMove() { - if (Rotation.X + _rotationSpeed.X > 90.0) - _rotationSpeed.X = 90.0 - Rotation.X; - if (Rotation.X + _rotationSpeed.X < -90.0) - _rotationSpeed.X = -90.0 - Rotation.X; - Rotation += _rotationSpeed; - RotationDelta = _rotationSpeed; + if (Rotation.X + rotationSpeed.X > 90.0) + rotationSpeed.X = 90.0 - Rotation.X; + if (Rotation.X + rotationSpeed.X < -90.0) + rotationSpeed.X = -90.0 - Rotation.X; + Rotation += rotationSpeed; + RotationDelta = rotationSpeed; if (RotationInteria) - _rotationSpeed *= 0.6; + rotationSpeed *= 0.6; else - _rotationSpeed *= 0; + rotationSpeed *= 0; } } } \ No newline at end of file diff --git a/Game/World/PlayerObject.cs b/Game/World/PlayerObject.cs index 6d9f108..5bf6d89 100644 --- a/Game/World/PlayerObject.cs +++ b/Game/World/PlayerObject.cs @@ -28,8 +28,8 @@ public class PlayerObject : Object public PlayerObject(uint worldId) : base(worldId, new Vec3(), new Vec3(), new Vec3(1.0, 1.0, 1.0), new Aabb()) { - _height = 1.6; - _width = 0.6; + height = 1.6; + width = 0.6; Speed = 0.2; RefreshHitbox(); } @@ -41,14 +41,14 @@ public PlayerObject(uint worldId) : public double Speed { get; set; } - private readonly double _height; - private readonly double _width; - private Vec3 _hitboxSize; + private readonly double height; + private readonly double width; + private Vec3 hitboxSize; private void RefreshHitbox() { - _hitboxSize = new Vec3(_width, _height, _width); - Hitbox = new Aabb(-_hitboxSize / 2, _hitboxSize / 2); + hitboxSize = new Vec3(width, height, width); + Hitbox = new Aabb(-hitboxSize / 2, hitboxSize / 2); } public override void Render() diff --git a/Game/World/World.cs b/Game/World/World.cs index eae95df..a3bc549 100644 --- a/Game/World/World.cs +++ b/Game/World/World.cs @@ -22,6 +22,7 @@ using System.Linq; using Core.Math; using Game.Utilities; +using Xenko.Core.Mathematics; namespace Game.World { @@ -53,42 +54,42 @@ public World(string name) // Alias declearations for Chunk management public int GetChunkCount() => Chunks.Count; - public Chunk GetChunk(ref Vec3 chunkPos) => Chunks[chunkPos]; + public Chunk GetChunk(ref Int3 chunkPos) => Chunks[chunkPos]; - public bool IsChunkLoaded(Vec3 chunkPos) => Chunks.IsLoaded(chunkPos); + public bool IsChunkLoaded(Int3 chunkPos) => Chunks.IsLoaded(chunkPos); - public void DeleteChunk(Vec3 chunkPos) => Chunks.Remove(chunkPos); + public void DeleteChunk(Int3 chunkPos) => Chunks.Remove(chunkPos); public static int GetChunkAxisPos(int pos) => ChunkManager.GetAxisPos(pos); - public static Vec3 GetChunkPos(Vec3 pos) => ChunkManager.GetPos(pos); + public static Int3 GetChunkPos(Int3 pos) => ChunkManager.GetPos(pos); public static int GetBlockAxisPos(int pos) => ChunkManager.GetBlockAxisPos(pos); - public static Vec3 GetBlockPos(ref Vec3 pos) => ChunkManager.GetBlockPos(pos); + public static Int3 GetBlockPos(ref Int3 pos) => ChunkManager.GetBlockPos(pos); - public BlockData GetBlock(Vec3 pos) => Chunks.GetBlock(pos); + public BlockData GetBlock(Int3 pos) => Chunks.GetBlock(pos); - public void SetBlock(ref Vec3 pos, BlockData block) => Chunks.SetBlock(pos, block); + public void SetBlock(ref Int3 pos, BlockData block) => Chunks.SetBlock(pos, block); - private Chunk InsertChunk(ref Vec3 pos, Chunk chunk) + private Chunk InsertChunk(ref Int3 pos, Chunk chunk) { if (!Chunks.IsLoaded(pos)) Chunks.Add(pos, chunk); else // TODO : FIX THIS GOD DAMNED ERROR, IT SHOULD NOT HAPPEN - Console.WriteLine($"Warning: Dumplicate Chunk Adding on [{pos.X},{pos.Y},{pos.Z}]"); + Core.LogPort.Debug($"Warning: Dumplicate Chunk Adding on [{pos.X},{pos.Y},{pos.Z}]"); return Chunks[pos]; } - private static readonly Vec3[] Delta = + private static readonly Int3[] Delta = { - new Vec3(1, 0, 0), new Vec3(-1, 0, 0), - new Vec3(0, 1, 0), new Vec3(0, -1, 0), - new Vec3(0, 0, 1), new Vec3(0, 0, -1) + new Int3(1, 0, 0), new Int3(-1, 0, 0), + new Int3(0, 1, 0), new Int3(0, -1, 0), + new Int3(0, 0, 1), new Int3(0, 0, -1) }; - public Chunk InsertChunkAndUpdate(Vec3 pos, Chunk chunk) + public Chunk InsertChunkAndUpdate(Int3 pos, Chunk chunk) { var ret = InsertChunk(ref pos, chunk); foreach (var dt in Delta) @@ -97,14 +98,14 @@ public Chunk InsertChunkAndUpdate(Vec3 pos, Chunk chunk) return ret; } - public Chunk ResetChunk(ref Vec3 pos, Chunk ptr) => Chunks[pos] = ptr; + public Chunk ResetChunk(ref Int3 pos, Chunk ptr) => Chunks[pos] = ptr; - private readonly Vec3 _hitboxOffset = new Vec3(1.0, 1.0, 1.0); + private readonly Vec3 hitboxOffset = new Vec3(1.0, 1.0, 1.0); public List GetHitboxes(Aabb range) { var res = new List(); - Vec3 curr; + Int3 curr; for (curr.X = (int) Math.Floor(range.Min.X); curr.X < (int) Math.Ceiling(range.Max.X); curr.X++) { for (curr.Y = (int) Math.Floor(range.Min.Y); curr.Y < (int) Math.Ceiling(range.Max.Y); curr.Y++) @@ -117,7 +118,7 @@ public List GetHitboxes(Aabb range) if (GetBlock(curr).Id == 0) continue; var currd = new Vec3(curr.X, curr.Y, curr.Z); - res.Add(new Aabb(currd, currd + _hitboxOffset)); + res.Add(new Aabb(currd, currd + hitboxOffset)); } } } @@ -127,7 +128,7 @@ public List GetHitboxes(Aabb range) public void UpdateChunkLoadStatus() { - lock (_mutex) + lock (mutex) { foreach (var kvPair in Chunks.ToList()) if (kvPair.Value.CheckReleaseable()) @@ -138,9 +139,9 @@ public void UpdateChunkLoadStatus() protected static uint IdCount; // All Chunks (Chunk array) - private readonly object _mutex = new object(); + private readonly object mutex = new object(); - private void ResetChunkAndUpdate(Vec3 pos, Chunk chunk) + private void ResetChunkAndUpdate(Int3 pos, Chunk chunk) { ResetChunk(ref pos, chunk); foreach (var dt in Delta) diff --git a/Game/World/WorldTasks.cs b/Game/World/WorldTasks.cs index de96631..b816e5f 100644 --- a/Game/World/WorldTasks.cs +++ b/Game/World/WorldTasks.cs @@ -17,9 +17,11 @@ // along with NEWorld. If not, see . // +using System; using Core.Math; using Game.Network; using Game.Utilities; +using Xenko.Core.Mathematics; namespace Game.World { @@ -27,8 +29,8 @@ public partial class World { private const int MaxChunkLoadCount = 64, MaxChunkUnloadCount = 64; - private static readonly Vec3 MiddleOffset = - new Vec3(Chunk.Size / 2 - 1, Chunk.Size / 2 - 1, Chunk.Size / 2 - 1); + private static readonly Int3 MiddleOffset = + new Int3(Chunk.Size / 2 - 1, Chunk.Size / 2 - 1, Chunk.Size / 2 - 1); public class ResetChunkTask : IReadWriteTask { @@ -39,20 +41,20 @@ public class ResetChunkTask : IReadWriteTask */ public ResetChunkTask(uint worldId, Chunk chunk) { - _worldId = worldId; - _chunk = chunk; + this.worldId = worldId; + this.chunk = chunk; } public IReadWriteTask Clone() => (IReadWriteTask) MemberwiseClone(); public void Task(ChunkService srv) { - var world = srv.Worlds.Get(_worldId); - world.ResetChunkAndUpdate(_chunk.Position, _chunk); + var world = srv.Worlds.Get(worldId); + world.ResetChunkAndUpdate(chunk.Position, chunk); } - private readonly uint _worldId; - private readonly Chunk _chunk; + private readonly uint worldId; + private readonly Chunk chunk; } private class UnloadChunkTask : IReadWriteTask @@ -62,10 +64,10 @@ private class UnloadChunkTask : IReadWriteTask * \param world the target world * \param chunkPosition the position of the chunk */ - public UnloadChunkTask(World world, Vec3 chunkPosition) + public UnloadChunkTask(World world, Int3 chunkPosition) { - _world = world; - _chunkPosition = chunkPosition; + this.world = world; + this.chunkPosition = chunkPosition; } public IReadWriteTask Clone() => (IReadWriteTask) MemberwiseClone(); @@ -73,11 +75,11 @@ public UnloadChunkTask(World world, Vec3 chunkPosition) public void Task(ChunkService srv) { //TODO: for multiplayer situation, it should decrease ref counter instead of deleting - _world.DeleteChunk(_chunkPosition); + world.DeleteChunk(chunkPosition); } - private readonly World _world; - private readonly Vec3 _chunkPosition; + private readonly World world; + private readonly Int3 chunkPosition; } private class BuildOrLoadChunkTask : IReadOnlyTask @@ -91,20 +93,20 @@ private class AddToWorldTask : IReadWriteTask */ public AddToWorldTask(uint worldId, Chunk chunk) { - _worldId = worldId; - _chunk = chunk; + this.worldId = worldId; + this.chunk = chunk; } public IReadWriteTask Clone() => (IReadWriteTask) MemberwiseClone(); public void Task(ChunkService srv) { - var world = srv.Worlds.Get(_worldId); - world.InsertChunkAndUpdate(_chunk.Position, _chunk); + var world = srv.Worlds.Get(worldId); + world.InsertChunkAndUpdate(chunk.Position, chunk); } - private readonly uint _worldId; - private readonly Chunk _chunk; + private readonly uint worldId; + private readonly Chunk chunk; } /** @@ -112,10 +114,10 @@ public void Task(ChunkService srv) * \param world the target world * \param chunkPosition the position of the chunk */ - public BuildOrLoadChunkTask(World world, Vec3 chunkPosition) + public BuildOrLoadChunkTask(World world, Int3 chunkPosition) { - _world = world; - _chunkPosition = chunkPosition; + this.world = world; + this.chunkPosition = chunkPosition; } public IReadOnlyTask Clone() => (IReadOnlyTask) MemberwiseClone(); @@ -123,43 +125,43 @@ public BuildOrLoadChunkTask(World world, Vec3 chunkPosition) public void Task(ChunkService srv) { // TODO: should try to load from local first - var chunk = new Chunk(_chunkPosition, _world); - srv.TaskDispatcher.Add(new AddToWorldTask(_world.Id, chunk)); + var chunk = new Chunk(chunkPosition, world); + srv.TaskDispatcher.Add(new AddToWorldTask(world.Id, chunk)); } - private readonly World _world; - private readonly Vec3 _chunkPosition; + private readonly World world; + private readonly Int3 chunkPosition; } private class LoadUnloadDetectorTask : IReadOnlyTask { public LoadUnloadDetectorTask(World world, Player player) { - _player = player; - _world = world; + this.player = player; + this.world = world; } public void Task(ChunkService cs) { - var loadList = new OrderedListIntLess>(MaxChunkLoadCount); + var loadList = new OrderedListIntLess(MaxChunkLoadCount); var unloadList = new OrderedListIntGreater(MaxChunkUnloadCount); - var playerPos = _player.Position; - var position = new Vec3((int) playerPos.X, (int) playerPos.Y, (int) playerPos.Z); - GenerateLoadUnloadList(_world, position, 4, loadList, unloadList); + var playerPos = player.Position; + var position = new Int3((int) playerPos.X, (int) playerPos.Y, (int) playerPos.Z); + GenerateLoadUnloadList(world, position, 4, loadList, unloadList); foreach (var loadPos in loadList) { - cs.TaskDispatcher.Add(new BuildOrLoadChunkTask(_world, loadPos.Value)); + cs.TaskDispatcher.Add(new BuildOrLoadChunkTask(world, loadPos.Value)); if (!cs.IsAuthority) { - Client.GetChunk.Call(_world.Id, loadPos.Value); + Client.GetChunk.Call(world.Id, loadPos.Value); } } foreach (var unloadChunk in unloadList) { // add a unload task. - cs.TaskDispatcher.Add(new UnloadChunkTask(_world, unloadChunk.Value.Position)); + cs.TaskDispatcher.Add(new UnloadChunkTask(world, unloadChunk.Value.Position)); } } @@ -172,8 +174,8 @@ public void Task(ChunkService cs) * \param loadList (Output) Chunk load list [position, distance] * \param unloadList (Output) Chunk unload list [pointer, distance] */ - private static void GenerateLoadUnloadList(World world, Vec3 centerPos, int loadRange, - OrderedListIntLess> loadList, OrderedListIntGreater unloadList) + private static void GenerateLoadUnloadList(World world, Int3 centerPos, int loadRange, + OrderedListIntLess loadList, OrderedListIntGreater unloadList) { // centerPos to chunk coords var centerCPos = GetChunkPos(centerPos); @@ -182,8 +184,8 @@ private static void GenerateLoadUnloadList(World world, Vec3 centerPos, int { var curPos = chunk.Value.Position; // Out of load range, pending to unload - if (centerCPos.ChebyshevDistance(curPos) > loadRange) - unloadList.Insert((curPos * Chunk.Size + MiddleOffset - centerPos).LengthSqr(), chunk.Value); + if (ChebyshevDistance(centerCPos, curPos) > loadRange) + unloadList.Insert((curPos * Chunk.Size + MiddleOffset - centerPos).LengthSquared(), chunk.Value); } for (var x = centerCPos.X - loadRange; x <= centerCPos.X + loadRange; x++) @@ -192,20 +194,23 @@ private static void GenerateLoadUnloadList(World world, Vec3 centerPos, int { for (var z = centerCPos.Z - loadRange; z <= centerCPos.Z + loadRange; z++) { - var position = new Vec3(x, y, z); + var position = new Int3(x, y, z); // In load range, pending to load if (!world.IsChunkLoaded(position)) - loadList.Insert((position * Chunk.Size + MiddleOffset - centerPos).LengthSqr(), + loadList.Insert((position * Chunk.Size + MiddleOffset - centerPos).LengthSquared(), position); } } } } + // TODO: Remove Type1 Clone + private static int ChebyshevDistance(Int3 l, Int3 r) => Math.Max(Math.Max(Math.Abs(l.X - r.X), Math.Abs(l.Y - r.Y)), Math.Abs(l.Z - r.Z)); + public IReadOnlyTask Clone() => (IReadOnlyTask) MemberwiseClone(); - private readonly Player _player; - private readonly World _world; + private readonly Player player; + private readonly World world; } public void RegisterChunkTasks(ChunkService cs, Player player) => diff --git a/Game/packages.config b/Game/packages.config deleted file mode 100644 index 712ef9f..0000000 --- a/Game/packages.config +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/Modules/Main/Main.cs b/Main/Main.cs similarity index 98% rename from Modules/Main/Main.cs rename to Main/Main.cs index d8be8ce..0564715 100644 --- a/Modules/Main/Main.cs +++ b/Main/Main.cs @@ -18,10 +18,10 @@ // using Core; -using Core.Math; using Core.Module; using Game.Terrain; using Game.World; +using Xenko.Core.Mathematics; namespace Main { @@ -71,7 +71,7 @@ private static double PerlinNoise2D(double x, double y) return total; } - public static void Generator(Vec3 pos, BlockData[] blocks, int daylightBrightness) + public static void Generator(Int3 pos, BlockData[] blocks, int daylightBrightness) { for (var x = 0; x < Chunk.Size; x++) for (var z = 0; z < Chunk.Size; z++) diff --git a/Main/Main.csproj b/Main/Main.csproj new file mode 100644 index 0000000..521e4f8 --- /dev/null +++ b/Main/Main.csproj @@ -0,0 +1,24 @@ + + + + netstandard2.0 + + + + + + + + + + + + + + + + + + + + diff --git a/Main/Main.xkpkg b/Main/Main.xkpkg new file mode 100644 index 0000000..ccdb5b2 --- /dev/null +++ b/Main/Main.xkpkg @@ -0,0 +1,17 @@ +!Package +SerializedVersion: {Assets: 3.1.0.0} +Meta: + Name: Main + Version: 1.0.0 + Authors: [] + Owners: [] + Dependencies: null +AssetFolders: + - Path: !dir Assets +ResourceFolders: + - !dir Resources +OutputGroupDirectories: {} +ExplicitFolders: [] +Bundles: [] +TemplateFolders: [] +RootAssets: [] diff --git a/Modules/Main/Main.csproj b/Modules/Main/Main.csproj deleted file mode 100644 index baea11a..0000000 --- a/Modules/Main/Main.csproj +++ /dev/null @@ -1,59 +0,0 @@ - - - - - Debug - AnyCPU - {645C6E67-C0BB-4885-914B-50679E3382FC} - Library - Properties - Main - Main - v4.7.1 - 512 - - - AnyCPU - true - full - false - ..\..\bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - ..\..\bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - {ecb0e309-625f-4a24-926d-d1d23c1b7693} - Core - - - {f83c4945-abff-4856-94a3-d0d31c976a11} - Game - - - - - \ No newline at end of file diff --git a/Modules/Main/Properties/AssemblyInfo.cs b/Modules/Main/Properties/AssemblyInfo.cs deleted file mode 100644 index e98f03a..0000000 --- a/Modules/Main/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,54 +0,0 @@ -// -// Main: AssemblyInfo.cs -// NEWorld: A Free Game with Similar Rules to Minecraft. -// Copyright (C) 2015-2018 NEWorld Team -// -// NEWorld is free software: you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published -// by the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// NEWorld is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY -// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General -// Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with NEWorld. If not, see . -// - -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Main")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Main")] -[assembly: AssemblyCopyright("Copyright © 2018")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("645C6E67-C0BB-4885-914B-50679E3382FC")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] \ No newline at end of file diff --git a/NEWorld.Linux/NEWorld.Linux.csproj b/NEWorld.Linux/NEWorld.Linux.csproj new file mode 100644 index 0000000..8df7186 --- /dev/null +++ b/NEWorld.Linux/NEWorld.Linux.csproj @@ -0,0 +1,23 @@ + + + + netcoreapp2.1 + linux-x64 + Resources\Icon.ico + WinExe + NEWorld.Linux + + ..\Bin\Linux\$(Configuration)\ + false + + + true + + + + + + + + + diff --git a/NEWorld.Linux/NEWorld.Linux.xkpkg b/NEWorld.Linux/NEWorld.Linux.xkpkg new file mode 100644 index 0000000..228fc02 --- /dev/null +++ b/NEWorld.Linux/NEWorld.Linux.xkpkg @@ -0,0 +1,17 @@ +!Package +SerializedVersion: {Assets: 3.1.0.0} +Meta: + Name: NEWorld.Linux + Version: 1.0.0 + Authors: [] + Owners: [] + Dependencies: null +AssetFolders: + - Path: !dir Assets +ResourceFolders: + - !dir Resources +OutputGroupDirectories: {} +ExplicitFolders: [] +Bundles: [] +TemplateFolders: [] +RootAssets: [] diff --git a/NEWorld.Linux/NEWorldApp.cs b/NEWorld.Linux/NEWorldApp.cs new file mode 100644 index 0000000..7bb5187 --- /dev/null +++ b/NEWorld.Linux/NEWorldApp.cs @@ -0,0 +1,13 @@ +namespace NEWorld.Linux +{ + class NEWorldApp + { + static void Main(string[] args) + { + using (var game = new Xenko.Engine.Game()) + { + game.Run(); + } + } + } +} diff --git a/NEWorld.Linux/Resources/Icon.ico b/NEWorld.Linux/Resources/Icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..29256adf2605509afb9ba10e5616eec8e8a7086f GIT binary patch literal 34750 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 + + + net461 + Resources\Icon.ico + WinExe + NEWorld.Windows + + ..\Bin\Windows\$(Configuration)\ + false + + + true + + + + + + + diff --git a/NEWorld.Windows/NEWorld.Windows.xkpkg b/NEWorld.Windows/NEWorld.Windows.xkpkg new file mode 100644 index 0000000..70f35a9 --- /dev/null +++ b/NEWorld.Windows/NEWorld.Windows.xkpkg @@ -0,0 +1,17 @@ +!Package +SerializedVersion: {Assets: 3.1.0.0} +Meta: + Name: NEWorld.Windows + Version: 1.0.0 + Authors: [] + Owners: [] + Dependencies: null +AssetFolders: + - Path: !dir Assets +ResourceFolders: + - !dir Resources +OutputGroupDirectories: {} +ExplicitFolders: [] +Bundles: [] +TemplateFolders: [] +RootAssets: [] diff --git a/NEWorld.Windows/NEWorldApp.cs b/NEWorld.Windows/NEWorldApp.cs new file mode 100644 index 0000000..9a381b6 --- /dev/null +++ b/NEWorld.Windows/NEWorldApp.cs @@ -0,0 +1,13 @@ +namespace NEWorld.Windows +{ + class NEWorldApp + { + static void Main(string[] args) + { + using (var game = new Xenko.Engine.Game()) + { + game.Run(); + } + } + } +} diff --git a/NEWorld.Windows/Resources/Icon.ico b/NEWorld.Windows/Resources/Icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..29256adf2605509afb9ba10e5616eec8e8a7086f GIT binary patch literal 34750 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 + + + netcoreapp2.1 + osx-x64 + Resources\Icon.ico + WinExe + NEWorld.macOS + + ..\Bin\macOS\$(Configuration)\ + false + + + true + + + + + + + + + diff --git a/NEWorld.macOS/NEWorld.macOS.xkpkg b/NEWorld.macOS/NEWorld.macOS.xkpkg new file mode 100644 index 0000000..b087809 --- /dev/null +++ b/NEWorld.macOS/NEWorld.macOS.xkpkg @@ -0,0 +1,17 @@ +!Package +SerializedVersion: {Assets: 3.1.0.0} +Meta: + Name: NEWorld.macOS + Version: 1.0.0 + Authors: [] + Owners: [] + Dependencies: null +AssetFolders: + - Path: !dir Assets +ResourceFolders: + - !dir Resources +OutputGroupDirectories: {} +ExplicitFolders: [] +Bundles: [] +TemplateFolders: [] +RootAssets: [] diff --git a/NEWorld.macOS/NEWorldApp.cs b/NEWorld.macOS/NEWorldApp.cs new file mode 100644 index 0000000..b3c270c --- /dev/null +++ b/NEWorld.macOS/NEWorldApp.cs @@ -0,0 +1,13 @@ +namespace NEWorld.macOS +{ + class NEWorldApp + { + static void Main(string[] args) + { + using (var game = new Xenko.Engine.Game()) + { + game.Run(); + } + } + } +} diff --git a/NEWorld.macOS/Resources/Icon.ico b/NEWorld.macOS/Resources/Icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..29256adf2605509afb9ba10e5616eec8e8a7086f GIT binary patch literal 34750 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 True - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="I" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> - <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="False" Prefix="Keyboard" Suffix="" Style="AaBb"><ExtraRule Prefix="Touch" Suffix="" Style="AaBb" /></Policy> True - True \ No newline at end of file + True + <AssemblyExplorer> + <Assembly Path="C:\Users\yshli\.nuget\packages\xenko.graphics\3.1.0.1-beta01-0406\ref\netstandard2.0\Xenko.Graphics.dll" /> + <Assembly Path="C:\Users\yshli\.nuget\packages\xenko.engine\3.1.0.1-beta01-0406\ref\netstandard2.0\Xenko.Engine.dll" /> + <Assembly Path="C:\Users\yshli\.nuget\packages\xenko.core.mathematics\3.1.0.1-beta01-0406\lib\netstandard2.0\Xenko.Core.Mathematics.dll" /> + <Assembly Path="C:\Program Files\dotnet\sdk\NuGetFallbackFolder\netstandard.library\2.0.3\build\netstandard2.0\ref\netstandard.dll" /> + <Assembly Path="C:\Program Files\dotnet\shared\Microsoft.NETCore.App\2.1.7\System.Net.Sockets.dll" /> + <Assembly Path="C:\Program Files\dotnet\shared\Microsoft.NETCore.App\2.1.7\System.Console.dll" /> +</AssemblyExplorer> \ No newline at end of file diff --git a/NEWorld/Assets/GameSettings.xkgamesettings b/NEWorld/Assets/GameSettings.xkgamesettings new file mode 100644 index 0000000..299d48d --- /dev/null +++ b/NEWorld/Assets/GameSettings.xkgamesettings @@ -0,0 +1,62 @@ +!GameSettingsAsset +Id: ea39345b-fe46-475a-b61e-d2e3e67a6cce +SerializedVersion: {Xenko: 2.1.0.3} +Tags: [] +DefaultScene: 9dff7916-ac70-450a-aed9-fa776976f005:MainScene +GraphicsCompositor: 3156586f-3baa-4f44-bcd7-0d72a73d2f2e:GraphicsCompositor +Defaults: + - !Xenko.Audio.AudioEngineSettings,Xenko.Audio + HrtfSupport: false + - !Xenko.Assets.EditorSettings,Xenko.Assets + RenderingMode: LDR + - !Xenko.Navigation.NavigationSettings,Xenko.Navigation + EnableDynamicNavigationMesh: false + IncludedCollisionGroups: AllFilter + BuildSettings: + CellHeight: 0.2 + CellSize: 0.3 + TileSize: 32 + MinRegionArea: 2 + RegionMergeArea: 20 + MaxEdgeLen: 12.0 + MaxEdgeError: 1.3 + DetailSamplingDistance: 6.0 + MaxDetailSamplingError: 1.0 + Groups: + - Id: 20b0f359-171f-4eae-9bc0-9f80749efb8f + Name: New group + AgentSettings: + Height: 1.0 + MaxClimb: 0.25 + MaxSlope: {Radians: 0.7853982} + Radius: 0.5 + - !Xenko.Physics.PhysicsSettings,Xenko.Physics + Flags: None + MaxSubSteps: 1 + FixedTimeStep: 0.0166666675 + - !Xenko.Graphics.RenderingSettings,Xenko.Graphics + DefaultBackBufferWidth: 1280 + DefaultBackBufferHeight: 720 + AdaptBackBufferToScreen: false + DefaultGraphicsProfile: Level_11_0 + ColorSpace: Linear + DisplayOrientation: LandscapeRight + - !Xenko.Streaming.StreamingSettings,Xenko.Engine + ManagerUpdatesInterval: 0:00:00:00.0330000 + ResourceLiveTimeout: 0:00:00:08.0000000 + - !Xenko.Assets.Textures.TextureSettings,Xenko.Assets + TextureQuality: Fast +Overrides: [] +PlatformFilters: + - PowerVR SGX 54[0-9] + - Adreno \(TM\) 2[0-9][0-9] + - Adreno (TM) 320 + - Adreno (TM) 330 + - Adreno \(TM\) 4[0-9][0-9] + - NVIDIA Tegra + - Intel(R) HD Graphics + - ^Mali\-4 + - ^Mali\-T6 + - ^Mali\-T7 +SplashScreenTexture: d26edb11-10bd-403c-b3c2-9c7fcccf25e5:XenkoDefaultSplashScreen +SplashScreenColor: {R: 0, G: 0, B: 0, A: 255} diff --git a/NEWorld/Assets/GraphicsCompositor.xkgfxcomp b/NEWorld/Assets/GraphicsCompositor.xkgfxcomp new file mode 100644 index 0000000..781abb2 --- /dev/null +++ b/NEWorld/Assets/GraphicsCompositor.xkgfxcomp @@ -0,0 +1,203 @@ +!GraphicsCompositorAsset +Id: 3156586f-3baa-4f44-bcd7-0d72a73d2f2e +SerializedVersion: {Xenko: 2.1.0.2} +Tags: [] +Archetype: 823a81bf-bac0-4552-9267-aeed499c40df:DefaultGraphicsCompositorLevel10 +Cameras: + de2e75c3b2b23e54162686363f3f138e: + Id: dce133b3-fc2c-4978-8f3c-8a7bc66947f0 + Name: Main +RenderStages: + 47116750c1a5d449b4ad3625f71439b3: + Id: e3a6abbb-fcd8-4308-82a2-fad7de6a5bcc + Name: Opaque + EffectSlotName: Main + SortMode: !SortModeStateChange {} + 9105a30fee026d4893472b6aee83d035: + Id: 8676a7c8-a5c8-4aee-929c-33492ebd06e6 + Name: Transparent + EffectSlotName: Main + SortMode: !BackToFrontSortMode {} + 554e52c061404d4684dd7c4c70f70e0e: + Id: 9772c403-9a1b-4340-9923-4adce150286c + Name: ShadowMapCaster + EffectSlotName: ShadowMapCaster + SortMode: !FrontToBackSortMode {} + 5a50638f5c514dc490c8c4f57cc88b57: + Id: c3ba6884-d8c8-421d-bc5f-56fd74ea493f + Name: ShadowMapCasterParaboloid + EffectSlotName: ShadowMapCasterParaboloid + SortMode: !FrontToBackSortMode {} + bc1a77d2ab254a6e920f86cff65cd75e: + Id: 6d7b2d22-48c7-4dbd-906e-d166d1ae2cd8 + Name: ShadowMapCasterCubeMap + EffectSlotName: ShadowMapCasterCubeMap + SortMode: !FrontToBackSortMode {} + 33d9d311a1a65601da9ef56775477f95: + Id: 2f4fde52-5da1-4114-987e-28fa0febbbe4 + Name: GBuffer + EffectSlotName: GBuffer + SortMode: !FrontToBackSortMode {} +RenderFeatures: + d8fb80b0e7995140a46bca8dc36ee8a2: !Xenko.Rendering.MeshRenderFeature,Xenko.Engine + RenderStageSelectors: + 44cf4a95ef82544e9ce3c6507d5569a9: !Xenko.Rendering.MeshTransparentRenderStageSelector,Xenko.Engine + OpaqueRenderStage: ref!! e3a6abbb-fcd8-4308-82a2-fad7de6a5bcc + TransparentRenderStage: ref!! 8676a7c8-a5c8-4aee-929c-33492ebd06e6 + EffectName: XenkoForwardShadingEffect + 6f7224048750e7260ea87c444f74b32c: !Xenko.Rendering.Shadows.ShadowMapRenderStageSelector,Xenko.Engine + ShadowMapRenderStage: ref!! 9772c403-9a1b-4340-9923-4adce150286c + EffectName: XenkoForwardShadingEffect.ShadowMapCaster + b60663d7cb46417a94341a39c3bc1a12: !Xenko.Rendering.Shadows.ShadowMapRenderStageSelector,Xenko.Engine + ShadowMapRenderStage: ref!! c3ba6884-d8c8-421d-bc5f-56fd74ea493f + EffectName: XenkoForwardShadingEffect.ShadowMapCasterParaboloid + f5533b1249b942df8a8aba311cd79532: !Xenko.Rendering.Shadows.ShadowMapRenderStageSelector,Xenko.Engine + ShadowMapRenderStage: ref!! 6d7b2d22-48c7-4dbd-906e-d166d1ae2cd8 + EffectName: XenkoForwardShadingEffect.ShadowMapCasterCubeMap + 106341b76db9fcda6a033dad16aa708b: !Xenko.Rendering.MeshTransparentRenderStageSelector,Xenko.Engine + OpaqueRenderStage: ref!! 2f4fde52-5da1-4114-987e-28fa0febbbe4 + EffectName: XenkoForwardShadingEffect.ShadowMapCaster + PipelineProcessors: + d70f5aee0616e4ab25081ceaf643290c: !Xenko.Rendering.MeshPipelineProcessor,Xenko.Engine + TransparentRenderStage: ref!! 8676a7c8-a5c8-4aee-929c-33492ebd06e6 + 26c899b17f88c21ab13bf60a7220ccd1: !Xenko.Rendering.ShadowMeshPipelineProcessor,Xenko.Engine + ShadowMapRenderStage: ref!! 9772c403-9a1b-4340-9923-4adce150286c + ff51170a7d1a4761b73ef6a5c9f0cba2: !Xenko.Rendering.ShadowMeshPipelineProcessor,Xenko.Engine + ShadowMapRenderStage: ref!! c3ba6884-d8c8-421d-bc5f-56fd74ea493f + DepthClipping: true + ae4336b0a9514e8488e8e0ccbcef25f4: !Xenko.Rendering.ShadowMeshPipelineProcessor,Xenko.Engine + ShadowMapRenderStage: ref!! 6d7b2d22-48c7-4dbd-906e-d166d1ae2cd8 + DepthClipping: true + RenderFeatures: + 86b959cbdf51a1438d4973177c77c627: !Xenko.Rendering.TransformRenderFeature,Xenko.Engine {} + 8e0351fee9883922648a11016224b195: !Xenko.Rendering.SkinningRenderFeature,Xenko.Engine {} + f5a2017030ba4b28784e804807ce7628: !Xenko.Rendering.Materials.MaterialRenderFeature,Xenko.Engine {} + 83fea7526ebe4893a5bad953d0502bfd: !Xenko.Rendering.Shadows.ShadowCasterRenderFeature,Xenko.Engine {} + 65743b4380f4cc43b2b4bdc23cd0c07c: !Xenko.Rendering.Lights.ForwardLightingRenderFeature,Xenko.Engine + LightRenderers: + 7ac2775468f53c4399b2f3f6357c85c9: !Xenko.Rendering.Lights.LightAmbientRenderer,Xenko.Engine {} + 7b68f9cd17404a4ba9e5f7df72e3b48d: !Xenko.Rendering.Lights.LightDirectionalGroupRenderer,Xenko.Engine {} + 411fdcfb9fc388449a0443173dfa3f27: !Xenko.Rendering.Lights.LightSkyboxRenderer,Xenko.Engine {} + facdcd5b543cf1c6bdf2138aab6cc473: !Xenko.Rendering.Lights.LightClusteredPointSpotGroupRenderer,Xenko.Engine {} + 79582329a9cf466e960f8920f579de9b: !Xenko.Rendering.Lights.LightPointGroupRenderer,Xenko.Engine {} + cf0c6bd4198b4cc4aaaab5b54870bdfd: !Xenko.Rendering.Lights.LightSpotGroupRenderer,Xenko.Engine {} + 451af18f3f5c4187cf3fe5f33feb46b1: !Xenko.Rendering.LightProbes.LightProbeRenderer,Xenko.Engine {} + ShadowMapRenderer: !Xenko.Rendering.Shadows.ShadowMapRenderer,Xenko.Engine + Renderers: + 7c3d3d4c86834c3551bacde2527b3836: !Xenko.Rendering.Shadows.LightDirectionalShadowMapRenderer,Xenko.Engine + ShadowCasterRenderStage: ref!! 9772c403-9a1b-4340-9923-4adce150286c + 1c204b09435636256a3fcfd6f9ddb347: !Xenko.Rendering.Shadows.LightSpotShadowMapRenderer,Xenko.Engine + ShadowCasterRenderStage: ref!! 9772c403-9a1b-4340-9923-4adce150286c + 7c8c69ce27034b4c8bbcab0bcdfe954b: !Xenko.Rendering.Shadows.LightPointShadowMapRendererParaboloid,Xenko.Engine + ShadowCasterRenderStage: ref!! c3ba6884-d8c8-421d-bc5f-56fd74ea493f + d59ef45dd99e49d3af3887763d153aa7: !Xenko.Rendering.Shadows.LightPointShadowMapRendererCubeMap,Xenko.Engine + ShadowCasterRenderStage: ref!! 6d7b2d22-48c7-4dbd-906e-d166d1ae2cd8 + 28e9bf54a5adbe063f59fb17acb2723e: !Xenko.Rendering.Sprites.SpriteRenderFeature,Xenko.Engine + RenderStageSelectors: + d74665cff080638a2439c4422e542d85: !Xenko.Rendering.Sprites.SpriteTransparentRenderStageSelector,Xenko.Engine + OpaqueRenderStage: ref!! e3a6abbb-fcd8-4308-82a2-fad7de6a5bcc + TransparentRenderStage: ref!! 8676a7c8-a5c8-4aee-929c-33492ebd06e6 + EffectName: Test + 60780391e205770513fdd53e07279a01: !Xenko.Rendering.Background.BackgroundRenderFeature,Xenko.Engine + RenderStageSelectors: + 11c8b8ccb522e3cd1dd6688016062a6d: !Xenko.Rendering.SimpleGroupToRenderStageSelector,Xenko.Engine + RenderStage: ref!! e3a6abbb-fcd8-4308-82a2-fad7de6a5bcc + EffectName: Test + 93933ad00d0c357d4915ad462cbfd04c: !Xenko.Rendering.UI.UIRenderFeature,Xenko.UI + RenderStageSelectors: + 14a071694411235038a102ac3794bb4d: !Xenko.Rendering.SimpleGroupToRenderStageSelector,Xenko.Engine + RenderStage: ref!! 8676a7c8-a5c8-4aee-929c-33492ebd06e6 + EffectName: Test + 9013eab3ea0ef6c98bf133b86c173d45: !Xenko.Particles.Rendering.ParticleEmitterRenderFeature,Xenko.Particles + RenderStageSelectors: + af1bd241305893ef8ff7952184e1cb0b: !Xenko.Particles.Rendering.ParticleEmitterTransparentRenderStageSelector,Xenko.Particles + OpaqueRenderStage: ref!! e3a6abbb-fcd8-4308-82a2-fad7de6a5bcc + TransparentRenderStage: ref!! 8676a7c8-a5c8-4aee-929c-33492ebd06e6 + EffectName: null + PipelineProcessors: {} +SharedRenderers: + 60459475d3a3adaf2d1ba5d99913ca75: !Xenko.Rendering.Compositing.ForwardRenderer,Xenko.Engine + Id: eae2968e-f8b9-4541-9f09-291e8b21e99b + Clear: + Id: 4bc4b2ca-027e-4e4a-94cb-2912709bef5f + Color: {R: 0.40491876, G: 0.411895424, B: 0.43775, A: 1.0} + LightProbes: true + OpaqueRenderStage: ref!! e3a6abbb-fcd8-4308-82a2-fad7de6a5bcc + TransparentRenderStage: ref!! 8676a7c8-a5c8-4aee-929c-33492ebd06e6 + ShadowMapRenderStages: + fc4d1e0de5c2b0bbc27bcf96e9a848fd: ref!! 9772c403-9a1b-4340-9923-4adce150286c + GBufferRenderStage: ref!! 2f4fde52-5da1-4114-987e-28fa0febbbe4 + PostEffects: !PostProcessingEffects ref!! 88b0e076-6c98-4c42-9e90-3f331a5dbf04 + LightShafts: null + VRSettings: + Enabled: false + RequiredApis: {} + Overlays: {} + SubsurfaceScatteringBlurEffect: null + MSAALevel: None + MSAAResolver: {} + d5b2e71c088247e21556decdce138d96: !Xenko.Rendering.Compositing.ForwardRenderer,Xenko.Engine + Id: 1e68a169-6a0f-4984-af52-9d9e784286fb + Clear: + Id: 66a42307-1985-4316-871a-768449238c11 + Color: {R: 0.40491876, G: 0.411895424, B: 0.43775, A: 1.0} + LightProbes: true + OpaqueRenderStage: ref!! e3a6abbb-fcd8-4308-82a2-fad7de6a5bcc + TransparentRenderStage: ref!! 8676a7c8-a5c8-4aee-929c-33492ebd06e6 + ShadowMapRenderStages: + 2323a99a8a983e182f318e55604659b0: ref!! 9772c403-9a1b-4340-9923-4adce150286c + GBufferRenderStage: ref!! 2f4fde52-5da1-4114-987e-28fa0febbbe4 + PostEffects: null + LightShafts: null + VRSettings: + Enabled: false + RequiredApis: {} + Overlays: {} + SubsurfaceScatteringBlurEffect: null + MSAALevel: None + MSAAResolver: {} + 34ecb9b2633eacfc439ba8744fe05102: !PostProcessingEffects + Id: 88b0e076-6c98-4c42-9e90-3f331a5dbf04 + AmbientOcclusion: + Enabled: false + LocalReflections: + Enabled: false + ResolvePassResolution: Full + DepthResolution: Half + DepthOfField: + Enabled: false + DOFAreas: {X: 0.5, Y: 6.0, Z: 50.0, W: 200.0} + BrightFilter: + Color: {R: 1.0, G: 1.0, B: 1.0} + Bloom: + Distortion: {X: 1.0, Y: 1.0} + Afterimage: + Enabled: false + LightStreak: + Attenuation: 0.7 + LensFlare: {} + ColorTransforms: + Transforms: + 1e06f805f8b2e949a06c30d45fe413ef: !ToneMap + Operator: !ToneMapHejl2Operator {} + c57351444609d14ea258b3f511ec8a74: !FilmGrain + Enabled: false + e86e22e9a5d65545b8b55fca26e0afee: !Vignetting + Enabled: false + Color: {R: 0.0, G: 0.0, B: 0.0} + Antialiasing: !FXAAEffect {} + ee80a20a9bd99f2d70711114e15fe7ca: !Xenko.Rendering.Compositing.DebugRenderer,Xenko.Engine + Id: 5d84ec9b-04da-4738-970f-e7fc4fd99c07 + DebugRenderStages: {} +Game: !Xenko.Rendering.Compositing.SceneCameraRenderer,Xenko.Engine + Id: 76fe87cf-f574-4ad6-85b8-e9a9586be0e2 + Camera: ref!! dce133b3-fc2c-4978-8f3c-8a7bc66947f0 + Child: !Xenko.Rendering.Compositing.SceneRendererCollection,Xenko.Engine + Id: 82568e46-92e7-421a-8dca-114a74e0cd69 + Children: + d39c5ddbf8b7d5ca02bafb6496b1cc3c: !Xenko.Rendering.Compositing.ForwardRenderer,Xenko.Engine ref!! eae2968e-f8b9-4541-9f09-291e8b21e99b + 01d338078e9b21121ead0868932613dd: !Xenko.Rendering.Compositing.DebugRenderer,Xenko.Engine ref!! 5d84ec9b-04da-4738-970f-e7fc4fd99c07 + RenderMask: All +SingleView: !Xenko.Rendering.Compositing.ForwardRenderer,Xenko.Engine ref!! 1e68a169-6a0f-4984-af52-9d9e784286fb +Editor: !Xenko.Rendering.Compositing.ForwardRenderer,Xenko.Engine ref!! eae2968e-f8b9-4541-9f09-291e8b21e99b +BlockPositions: {} diff --git a/NEWorld/Assets/Ground Material.xkmat b/NEWorld/Assets/Ground Material.xkmat new file mode 100644 index 0000000..5b31bda --- /dev/null +++ b/NEWorld/Assets/Ground Material.xkmat @@ -0,0 +1,12 @@ +!MaterialAsset +Id: 55fec3e9-4b2d-4c97-b731-c372d86a4116 +SerializedVersion: {Xenko: 2.0.0.0} +Tags: [] +Attributes: + Diffuse: !MaterialDiffuseMapFeature + DiffuseMap: !ComputeColor + Value: {R: 0.141176477, G: 0.141176477, B: 0.141176477, A: 1.0} + DiffuseModel: !MaterialDiffuseLambertModelFeature {} + Overrides: + UVScale: {X: 1.0, Y: 1.0} +Layers: {} diff --git a/NEWorld/Assets/Ground.xkpromodel b/NEWorld/Assets/Ground.xkpromodel new file mode 100644 index 0000000..b62c07e --- /dev/null +++ b/NEWorld/Assets/Ground.xkpromodel @@ -0,0 +1,12 @@ +!ProceduralModelAsset +Id: 816675f4-c442-4d9f-b3cf-c15382ca0e27 +SerializedVersion: {Xenko: 2.0.0.0} +Tags: [] +Type: !PlaneProceduralModel + Size: {X: 10.0, Y: 10.0} + Tessellation: {X: 1, Y: 1} + Scale: {X: 1.0, Y: 1.0, Z: 1.0} + UvScale: {X: 1.0, Y: 1.0} + LocalOffset: {X: 0.0, Y: 0.0, Z: 0.0} + MaterialInstance: + Material: 55fec3e9-4b2d-4c97-b731-c372d86a4116:Ground Material diff --git a/NEWorld/Assets/MainScene.xkscene b/NEWorld/Assets/MainScene.xkscene new file mode 100644 index 0000000..ba35014 --- /dev/null +++ b/NEWorld/Assets/MainScene.xkscene @@ -0,0 +1,132 @@ +!SceneAsset +Id: 9dff7916-ac70-450a-aed9-fa776976f005 +SerializedVersion: {Xenko: 3.1.0.1} +Tags: [] +ChildrenIds: [] +Offset: {X: 0.0, Y: 0.0, Z: 0.0} +Hierarchy: + RootParts: + - ref!! 8d346802-8fe6-4d18-957c-c6dd8404ea84 + - ref!! 1cc17873-cca9-48fe-a61f-a1d061e26959 + - ref!! b1058700-11a7-4296-bb63-2fefdb5f28b7 + - ref!! ffafa74d-ee74-4f21-aeba-f6565b12674d + - ref!! 4255a2e2-6b21-4e69-94cd-af3e5d8235b5 + - ref!! 57897b9f-c148-4d0f-a291-1a81b3b30d7c + - ref!! b1588acf-0fcd-4377-bf5d-6c81dcf34484 + Parts: + - Entity: + Id: 1cc17873-cca9-48fe-a61f-a1d061e26959 + Name: Directional light + Components: + 1fd11c5daf8481b885d448424af7cbe3: !TransformComponent + Id: 2e13dd0e-ce0b-4194-87b1-355fb6805b1a + Position: {X: 0.0, Y: 2.0, Z: 0.0} + Rotation: {X: 1.131334E-08, Y: -0.9659258, Z: -0.258819044, W: -4.222196E-08} + Scale: {X: 1.0, Y: 1.0, Z: 1.0} + Children: {} + e709a18ae6b4e34290801582f4038c7d: !LightComponent + Id: 7a3bb3cc-c0fe-4549-9ae0-928248e556f0 + Type: !LightDirectional + Color: !ColorRgbProvider + Value: {R: 1.0, G: 1.0, B: 1.0} + Shadow: + Enabled: true + Filter: !LightShadowMapFilterTypePcf + FilterSize: Filter5x5 + Size: Large + DepthRange: {} + PartitionMode: !LightDirectionalShadowMap.PartitionLogarithmic {} + ComputeTransmittance: false + BiasParameters: {} + - Entity: + Id: 4255a2e2-6b21-4e69-94cd-af3e5d8235b5 + Name: Ground + Components: + 9006dbffc8f216b10892cc70f3fb67f7: !TransformComponent + Id: 91f18c07-871d-4622-a137-7728cb87fca6 + Position: {X: 0.0, Y: 0.0, Z: 0.0} + Rotation: {X: 0.0, Y: 0.0, Z: 0.0, W: 1.0} + Scale: {X: 1.0, Y: 1.0, Z: 1.0} + Children: {} + 6c8fc5687a473e6e9938458d5d96decc: !ModelComponent + Id: a8d01fe3-9443-49ec-92c8-c5a921db176c + Model: 816675f4-c442-4d9f-b3cf-c15382ca0e27:Ground + Materials: {} + - Entity: + Id: 57897b9f-c148-4d0f-a291-1a81b3b30d7c + Name: Sphere + Components: + c560157b4d71df9713af8c324a6ff505: !TransformComponent + Id: c1c745a0-5c33-48cd-98ca-e61b2b56c7b2 + Position: {X: 0.0, Y: 0.5, Z: 0.0} + Rotation: {X: 0.0, Y: 0.0, Z: 0.0, W: 1.0} + Scale: {X: 1.0, Y: 1.0, Z: 1.0} + Children: {} + 3eb6684a91bc04721ae11b10c9c7c2c7: !ModelComponent + Id: 50f99228-00d8-48a0-bce8-dcbcf37848cc + Model: 6312d397-4e68-4b04-9a19-232f32bf733b:Sphere + Materials: {} + - Entity: + Id: 8d346802-8fe6-4d18-957c-c6dd8404ea84 + Name: Camera + Components: + 0b5d8ce12fcc7ca2525fabc42a2ad694: !TransformComponent + Id: 1f13a162-81b0-4c11-92d1-b3aa99419e70 + Position: {X: 2.6, Y: 0.6, Z: -1.0} + Rotation: {X: 0.0, Y: 0.829037547, Z: 0.0, W: 0.5591929} + Scale: {X: 1.0, Y: 1.0, Z: 1.0} + Children: {} + 38edbf9cc2da98344d46080a3315ea6e: !CameraComponent + Id: ad927bbe-0362-46f3-bf19-c11527de8a86 + Name: null + Projection: Perspective + Slot: dce133b3-fc2c-4978-8f3c-8a7bc66947f0 + 84e3e1914c52c7d890fe96ba860bf21f: !NEWorld.BasicCameraController,NEWorld + Id: 0ae3e97b-49cb-4d05-983b-e3c306612767 + KeyboardMovementSpeed: {X: 5.0, Y: 5.0, Z: 5.0} + TouchMovementSpeed: {X: 40.0, Y: 40.0, Z: 20.0} + SpeedFactor: 5.0 + KeyboardRotationSpeed: {X: 3.0, Y: 3.0} + MouseRotationSpeed: {X: 90.0, Y: 60.0} + TouchRotationSpeed: {X: 60.0, Y: 40.0} + - Entity: + Id: b1058700-11a7-4296-bb63-2fefdb5f28b7 + Name: Skybox + Components: + b651e16182f7eb9a4ca2a00d05f58bf0: !TransformComponent + Id: 3b9b0222-d576-43da-ab44-472344780fd3 + Position: {X: 0.0, Y: 2.0, Z: -2.0} + Rotation: {X: 0.0, Y: 0.0, Z: 0.0, W: 1.0} + Scale: {X: 1.0, Y: 1.0, Z: 1.0} + Children: {} + b770b44f804ee2b92e0cf2e21cb516aa: !BackgroundComponent + Id: 9a17a387-5b6c-4dde-9185-771f3dd4b737 + Texture: d5bf7b66-7df1-475b-8721-f4755a9c6f96:Skybox texture + - Entity: + Id: b1588acf-0fcd-4377-bf5d-6c81dcf34484 + Name: MainScript + Components: + 357a6c8e8c9612c3b0cb2f1e11a2c6f7: !TransformComponent + Id: 1d035ffe-fc7c-4f08-8a73-a80b9427ffb4 + Position: {X: 0.0, Y: 0.0, Z: 0.0} + Rotation: {X: 0.0, Y: 0.0, Z: 0.0, W: 1.0} + Scale: {X: 1.0, Y: 1.0, Z: 1.0} + Children: {} + d80b4d8f8e4c7517fb33931ae02a1875: !NEWorld.MainScript,NEWorld + Id: ad46feb5-e564-418b-8912-d79cfbc7a12d + - Entity: + Id: ffafa74d-ee74-4f21-aeba-f6565b12674d + Name: Ambient light + Components: + 79053e6520eb8792554a10e8b4b67849: !TransformComponent + Id: 484f1a7f-af76-49ca-a6ba-1581287e98ce + Position: {X: -2.0, Y: 2.0, Z: 0.0} + Rotation: {X: 0.0, Y: 0.0, Z: 0.0, W: 1.0} + Scale: {X: 1.0, Y: 1.0, Z: 1.0} + Children: {} + da4d8f8e6c59aa339ef3d33b7c2a36a7: !LightComponent + Id: dc510114-c9ef-490a-bc7d-528bb0f3362f + Type: !LightAmbient + Color: !ColorRgbProvider + Value: {R: 0.647058845, G: 0.7882353, B: 0.9411765} + Intensity: 0.1 diff --git a/NEWorld/Assets/Skybox texture.xktex b/NEWorld/Assets/Skybox texture.xktex new file mode 100644 index 0000000..18f3c7b --- /dev/null +++ b/NEWorld/Assets/Skybox texture.xktex @@ -0,0 +1,9 @@ +!Texture +Id: d5bf7b66-7df1-475b-8721-f4755a9c6f96 +SerializedVersion: {Xenko: 2.0.0.0} +Tags: [] +Source: !file ../Resources/skybox_texture_ldr.dds +IsCompressed: false +Type: !ColorTextureType + UseSRgbSampling: false + ColorKeyColor: {R: 255, G: 0, B: 255, A: 255} diff --git a/NEWorld/Assets/Skybox.xksky b/NEWorld/Assets/Skybox.xksky new file mode 100644 index 0000000..5d4e50f --- /dev/null +++ b/NEWorld/Assets/Skybox.xksky @@ -0,0 +1,5 @@ +!SkyboxAsset +Id: 1472c9e4-c26d-4e13-b2b6-01222389e22b +SerializedVersion: {Xenko: 2.0.0.0} +Tags: [] +CubeMap: d5bf7b66-7df1-475b-8721-f4755a9c6f96:Skybox texture diff --git a/NEWorld/Assets/Sphere Material.xkmat b/NEWorld/Assets/Sphere Material.xkmat new file mode 100644 index 0000000..9b4b2e9 --- /dev/null +++ b/NEWorld/Assets/Sphere Material.xkmat @@ -0,0 +1,12 @@ +!MaterialAsset +Id: 4296e382-6ca8-40e8-982b-4a427e56f687 +SerializedVersion: {Xenko: 2.0.0.0} +Tags: [] +Attributes: + Diffuse: !MaterialDiffuseMapFeature + DiffuseMap: !ComputeColor + Value: {R: 0.549019635, G: 0.549019635, B: 0.549019635, A: 1.0} + DiffuseModel: !MaterialDiffuseLambertModelFeature {} + Overrides: + UVScale: {X: 1.0, Y: 1.0} +Layers: {} diff --git a/NEWorld/Assets/Sphere.xkpromodel b/NEWorld/Assets/Sphere.xkpromodel new file mode 100644 index 0000000..1d027c4 --- /dev/null +++ b/NEWorld/Assets/Sphere.xkpromodel @@ -0,0 +1,11 @@ +!ProceduralModelAsset +Id: 6312d397-4e68-4b04-9a19-232f32bf733b +SerializedVersion: {Xenko: 2.0.0.0} +Tags: [] +Type: !SphereProceduralModel + Tessellation: 30 + Scale: {X: 1.0, Y: 1.0, Z: 1.0} + UvScale: {X: 1.0, Y: 1.0} + LocalOffset: {X: 0.0, Y: 0.0, Z: 0.0} + MaterialInstance: + Material: 4296e382-6ca8-40e8-982b-4a427e56f687:Sphere Material diff --git a/NEWorld/BasicCameraController.cs b/NEWorld/BasicCameraController.cs new file mode 100644 index 0000000..12e15e0 --- /dev/null +++ b/NEWorld/BasicCameraController.cs @@ -0,0 +1,186 @@ +using System; +using Xenko.Core; +using Xenko.Core.Mathematics; +using Xenko.Engine; +using Xenko.Input; + +namespace NEWorld +{ + /// + /// A script that allows to move and rotate an entity through keyboard, mouse and touch input to provide basic camera navigation. + /// + /// + /// The entity can be moved using W, A, S, D, Q and E, arrow keys or dragging/scaling using multi-touch. + /// Rotation is achieved using the Numpad, the mouse while holding the right mouse button, or dragging using single-touch. + /// + public class BasicCameraController : SyncScript + { + private const float MaximumPitch = MathUtil.PiOverTwo * 0.99f; + + private Vector3 upVector; + private Vector3 translation; + private float yaw; + private float pitch; + + public Vector3 KeyboardMovementSpeed { get; set; } = new Vector3(5.0f); + + public Vector3 TouchMovementSpeed { get; set; } = new Vector3(40, 40, 20); + + public float SpeedFactor { get; set; } = 5.0f; + + public Vector2 KeyboardRotationSpeed { get; set; } = new Vector2(3.0f); + + public Vector2 MouseRotationSpeed { get; set; } = new Vector2(90.0f, 60.0f); + + public Vector2 TouchRotationSpeed { get; set; } = new Vector2(60.0f, 40.0f); + + public override void Start() + { + base.Start(); + + // Default up-direction + upVector = Vector3.UnitY; + + // Configure touch input + if (!Platform.IsWindowsDesktop) + { + Input.Gestures.Add(new GestureConfigDrag()); + Input.Gestures.Add(new GestureConfigComposite()); + } + } + + public override void Update() + { + ProcessInput(); + UpdateTransform(); + } + + private void ProcessInput() + { + translation = Vector3.Zero; + yaw = 0; + pitch = 0; + + // Move with keyboard + if (Input.IsKeyDown(Keys.W) || Input.IsKeyDown(Keys.Up)) + { + translation.Z = -KeyboardMovementSpeed.Z; + } + else if (Input.IsKeyDown(Keys.S) || Input.IsKeyDown(Keys.Down)) + { + translation.Z = KeyboardMovementSpeed.Z; + } + + if (Input.IsKeyDown(Keys.A) || Input.IsKeyDown(Keys.Left)) + { + translation.X = -KeyboardMovementSpeed.X; + } + else if (Input.IsKeyDown(Keys.D) || Input.IsKeyDown(Keys.Right)) + { + translation.X = KeyboardMovementSpeed.X; + } + + if (Input.IsKeyDown(Keys.Q)) + { + translation.Y = -KeyboardMovementSpeed.Y; + } + else if (Input.IsKeyDown(Keys.E)) + { + translation.Y = KeyboardMovementSpeed.Y; + } + + // Alternative translation speed + if (Input.IsKeyDown(Keys.LeftShift) || Input.IsKeyDown(Keys.RightShift)) + { + translation *= SpeedFactor; + } + + // Rotate with keyboard + if (Input.IsKeyDown(Keys.NumPad2)) + { + pitch = KeyboardRotationSpeed.X; + } + else if (Input.IsKeyDown(Keys.NumPad8)) + { + pitch = -KeyboardRotationSpeed.X; + } + + if (Input.IsKeyDown(Keys.NumPad4)) + { + yaw = KeyboardRotationSpeed.Y; + } + else if (Input.IsKeyDown(Keys.NumPad6)) + { + yaw = -KeyboardRotationSpeed.Y; + } + + // Rotate with mouse + if (Input.IsMouseButtonDown(MouseButton.Right)) + { + Input.LockMousePosition(); + Game.IsMouseVisible = false; + + yaw = -Input.MouseDelta.X * MouseRotationSpeed.X; + pitch = -Input.MouseDelta.Y * MouseRotationSpeed.Y; + } + else + { + Input.UnlockMousePosition(); + Game.IsMouseVisible = true; + } + + // Handle gestures + foreach (var gestureEvent in Input.GestureEvents) + { + switch (gestureEvent.Type) + { + // Rotate by dragging + case GestureType.Drag: + var drag = (GestureEventDrag)gestureEvent; + var dragDistance = drag.DeltaTranslation; + yaw = -dragDistance.X * TouchRotationSpeed.X; + pitch = -dragDistance.Y * TouchRotationSpeed.Y; + break; + + // Move along z-axis by scaling and in xy-plane by multi-touch dragging + case GestureType.Composite: + var composite = (GestureEventComposite)gestureEvent; + translation.X = -composite.DeltaTranslation.X * TouchMovementSpeed.X; + translation.Y = -composite.DeltaTranslation.Y * TouchMovementSpeed.Y; + translation.Z = -(float)Math.Log(composite.DeltaScale + 1) * TouchMovementSpeed.Z; + break; + } + } + } + + private void UpdateTransform() + { + var elapsedTime = (float)Game.UpdateTime.Elapsed.TotalSeconds; + + translation *= elapsedTime; + yaw *= elapsedTime; + pitch *= elapsedTime; + + // Get the local coordinate system + var rotation = Matrix.RotationQuaternion(Entity.Transform.Rotation); + + // Enforce the global up-vector by adjusting the local x-axis + var right = Vector3.Cross(rotation.Forward, upVector); + var up = Vector3.Cross(right, rotation.Forward); + + // Stabilize + right.Normalize(); + up.Normalize(); + + // Adjust pitch. Prevent it from exceeding up and down facing. Stabilize edge cases. + var currentPitch = MathUtil.PiOverTwo - (float)Math.Acos(Vector3.Dot(rotation.Forward, upVector)); + pitch = MathUtil.Clamp(currentPitch + pitch, -MaximumPitch, MaximumPitch) - currentPitch; + + // Move in local coordinates + Entity.Transform.Position += Vector3.TransformCoordinate(translation, rotation); + + // Yaw around global up-vector, pitch and roll in local space + Entity.Transform.Rotation *= Quaternion.RotationAxis(right, pitch) * Quaternion.RotationAxis(upVector, yaw); + } + } +} diff --git a/NEWorld/GameScene.cs b/NEWorld/GameScene.cs deleted file mode 100644 index a2a26ad..0000000 --- a/NEWorld/GameScene.cs +++ /dev/null @@ -1,340 +0,0 @@ -// -// NEWorld: GameScene.cs -// NEWorld: A Free Game with Similar Rules to Minecraft. -// Copyright (C) 2015-2018 NEWorld Team -// -// NEWorld is free software: you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published -// by the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// NEWorld is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY -// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General -// Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with NEWorld. If not, see . -// - -using System; -using System.Threading; -using Core; -using Core.Math; -using Core.Utilities; -using Game; -using Game.Network; -using Game.Terrain; -using Game.World; -using NEWorld.Renderer; -using OpenGL; -using SDL2; -using static NuklearSharp.Nuklear; - -namespace NEWorld -{ - public class GameScene - { - private class PutBlockTask : IReadWriteTask - { - public PutBlockTask(uint worldId, Vec3 blockPosition, ushort blockId) - { - _worldId = worldId; - _blockPosition = blockPosition; - _blockId = blockId; - } - - public void Task(ChunkService srv) - { - srv.Worlds.Get(_worldId).SetBlock(ref _blockPosition, new BlockData(_blockId)); - } - - public IReadWriteTask Clone() => (IReadWriteTask) MemberwiseClone(); - - private readonly uint _worldId; - private Vec3 _blockPosition; - private readonly ushort _blockId; - } - - private class PlayerControlTask : IReadOnlyTask - { - private const double SelectDistance = 5.0; - private const double SelectPrecision = 200.0; - - public PlayerControlTask(Player player) - { - _player = player; - } - - public unsafe void Task(ChunkService cs) - { - const double speed = 0.1; - - // TODO: Read keys from the configuration file - var state = Window.GetKeyBoardState(); - if (state[(int) SDL.SDL_Scancode.SDL_SCANCODE_UP] != 0) - _player.AccelerateRotation(new Vec3(1, 0.0, 0.0)); - if (state[(int) SDL.SDL_Scancode.SDL_SCANCODE_DOWN] != 0 && _player.Rotation.X > -90) - _player.AccelerateRotation(new Vec3(-1, 0.0, 0.0)); - if (state[(int) SDL.SDL_Scancode.SDL_SCANCODE_RIGHT] != 0) - _player.AccelerateRotation(new Vec3(0.0, -1, 0.0)); - if (state[(int) SDL.SDL_Scancode.SDL_SCANCODE_LEFT] != 0) - _player.AccelerateRotation(new Vec3(0.0, 1, 0.0)); - if (state[(int) SDL.SDL_Scancode.SDL_SCANCODE_W] != 0) - _player.Accelerate(new Vec3(0.0, 0.0, -speed)); - if (state[(int) SDL.SDL_Scancode.SDL_SCANCODE_S] != 0) - _player.Accelerate(new Vec3(0.0, 0.0, speed)); - if (state[(int) SDL.SDL_Scancode.SDL_SCANCODE_A] != 0) - _player.Accelerate(new Vec3(-speed, 0.0, 0.0)); - if (state[(int) SDL.SDL_Scancode.SDL_SCANCODE_D] != 0) - _player.Accelerate(new Vec3(speed, 0.0, 0.0)); - if (state[(int) SDL.SDL_Scancode.SDL_SCANCODE_E] != 0) - _player.Accelerate(new Vec3(0.0, 0.0, -speed * 10)); - if (state[(int) SDL.SDL_Scancode.SDL_SCANCODE_SPACE] != 0) - _player.Accelerate(new Vec3(0.0, 2 * speed, 0.0)); - _player.Accelerate(new Vec3(0.0, -2 * speed, 0.0)); - - // Handle left-click events - HandleLeftClickEvent(cs); - // mGUIWidgets.update(); - } - - private void HandleLeftClickEvent(ChunkService cs) - { - if (!Window.GetInstance().GetMouseMotion().Left) return; - // Selection - var world = cs.Worlds.Get(_player.WorldId); - var trans = new Mat4D(1.0f); - var position = _player.Position; - var rotation = _player.Rotation; - trans *= Mat4D.Rotation(rotation.Y, new Vec3(0.0, 1.0, 0.0)); - trans *= Mat4D.Rotation(rotation.X, new Vec3(1.0, 0.0, 0.0)); - trans *= Mat4D.Rotation(rotation.Z, new Vec3(0.0, 0.0, 1.0)); - var dir = trans.Transform(new Vec3(0.0, 0.0, -1.0), 0.0f).Key; - dir.Normalize(); - - for (double i = 0.0f; i < SelectDistance; i += 1.0f / SelectPrecision) - { - var pos = position + dir * i; - var blockPos = new Vec3((int) Math.Floor(pos.X), (int) Math.Floor(pos.Y), - (int) Math.Floor(pos.Z)); - try - { - if (world.GetBlock(blockPos).Id == 0) continue; - cs.TaskDispatcher.Add(new PutBlockTask(_player.WorldId, blockPos, 0)); - break; - } - catch - { - break; - } - } - } - - public IReadOnlyTask Clone() => (IReadOnlyTask) MemberwiseClone(); - - private readonly Player _player; - } - - private class UpsCounter : IReadOnlyTask - { - public UpsCounter(object updateCounter) => _updateCounter = updateCounter; - - public void Task(ChunkService srv) => Generic.Increase(_updateCounter); - - public IReadOnlyTask Clone() => (IReadOnlyTask) MemberwiseClone(); - - private readonly object _updateCounter; - } - - // GameScene update frequency - public const int UpdateFrequency = 30; - - private static bool IsClient() - { - return true; - } - - public GameScene(string name, Window window) - { - _window = window; - _player = new Player(0); - _guiWidgets = new WidgetManager(_window.GetNkContext()); - - _player.Position = new Vec3(-16.0, 48.0, 32.0); - _player.Rotation = new Vec3(-45.0, -22.5, 0.0); - Window.LockCursor(); - - if (IsClient()) - { - Singleton.Instance.IsAuthority = false; - } - else - { - // Initialize server - var server = Services.Get("Game.Server"); - server.Enable(31111); - server.Run(); - } - - // Initialize connection - Services.Get("Game.Client").Enable("127.0.0.1", 31111); - - _currentWorld = Singleton.Instance.Worlds.Get(RequestWorld()); - _worldRenderer = new WorldRenderer(_currentWorld, 4); - - // Initialize update events - _currentWorld.RegisterChunkTasks(Singleton.Instance, _player); - _worldRenderer.RegisterTask(Singleton.Instance, _player); - Singleton.Instance.TaskDispatcher.AddRegular(new PlayerControlTask(_player)); - Singleton.Instance.TaskDispatcher.AddRegular(new UpsCounter(_upsCounter)); - - // Initialize rendering - _texture = BlockTextures.BuildAndFlush(); - BlockRenderers.FlushTextures(Services.Get("BlockTextures")); - Gl.Enable(Gl.DepthTest); - Gl.DepthFunc(Gl.Lequal); - - // Initialize Widgets - _guiWidgets.Add(new WidgetCallback( - "Debug", nk_rect_(20, 20, 300, 300), - NK_WINDOW_BORDER | NK_WINDOW_MOVABLE | NK_WINDOW_SCALABLE | - NK_WINDOW_CLOSABLE | NK_WINDOW_MINIMIZABLE | NK_WINDOW_TITLE, ctx => - { - if (_rateCounterScheduler.IsDue()) - { - // Update FPS & UPS - _fpsLatest = _fpsCounter; - _upsLatest = (uint) _upsCounter; - _fpsCounter = 0; - Generic.MultiplyBy(ref _upsCounter, 0u); - _rateCounterScheduler.IncreaseTimer(); - } - - ctx.LayoutRowDynamic(15, 1); - ctx.Label($"NEWorld {"0.5.0 Alpha"} ({39})", NK_TEXT_LEFT); - ctx.Label($"FPS {_fpsLatest}, UPS {_upsLatest}", NK_TEXT_LEFT); - ctx.Label($"Position: x {_player.Position.X} y {_player.Position.Y} z {_player.Position.Z}", - NK_TEXT_LEFT); - ctx.Label($"GUI Widgets: {_guiWidgets.Count}", NK_TEXT_LEFT); - ctx.Label($"Chunks Loaded: {_currentWorld.GetChunkCount()}", NK_TEXT_LEFT); - ctx.Label("Modules Loaded: %zu", NK_TEXT_LEFT); - ctx.Label("Update threads workload:", NK_TEXT_LEFT); - var dispatcher = Singleton.Instance.TaskDispatcher; - var threadId = 0; - foreach (var timeReal in dispatcher.TimeUsed) - { - var time = Math.Max(timeReal, 0L); - ctx.Label($"Thread {threadId++}: {time} ms {time / 33.3333}", NK_TEXT_LEFT); - } - - ctx.Label( - $"Regular Tasks: read {dispatcher.GetRegularReadOnlyTaskCount()} write {dispatcher.GetRegularReadWriteTaskCount()}", - NK_TEXT_LEFT); - })); - - Singleton.Instance.TaskDispatcher.Start(); - } - - ~GameScene() - { - Singleton.Instance.TaskDispatcher.Stop(); - } - - public void Render() - { - Singleton.Instance.TaskDispatcher.ProcessRenderTasks(); - - // Camera control by mouse - const double mouseSensitivity = 0.3; - - var mouse = Window.GetInstance().GetMouseMotion(); - _player.AccelerateRotation(new Vec3(-mouse.Y * mouseSensitivity, -mouse.X * mouseSensitivity, 0.0)); - - Gl.ClearColor(0.6f, 0.9f, 1.0f, 1.0f); - Gl.ClearDepth(1.0f); - Gl.Enable(Gl.DepthTest); - Gl.Enable(Gl.CullFace); - Gl.CullFaceOption(Gl.Back); - - var timeDelta = _updateScheduler.GetDeltaTimeMs() / 1000.0 * UpdateFrequency; - if (timeDelta > 1.0) timeDelta = 1.0; - var playerRenderedPosition = _player.Position - _player.PositionDelta * (1.0 - timeDelta); - var playerRenderedRotation = _player.Rotation - _player.RotationDelta * (1.0 - timeDelta); - - _texture.Use(0); - Gl.Clear(Gl.ColorBufferBit | Gl.DepthBufferBit); - Gl.Viewport(0, 0, _window.GetWidth(), _window.GetHeight()); - Matrix.RestoreProjection(); - Matrix.ApplyPerspective(70.0f, (float) _window.GetWidth() / _window.GetHeight(), 0.1f, 3000.0f); - Matrix.ViewRotate((float) -playerRenderedRotation.X, new Vec3(1.0f, 0.0f, 0.0f)); - Matrix.ViewRotate((float) -playerRenderedRotation.Y, new Vec3(0.0f, 1.0f, 0.0f)); - Matrix.ViewRotate((float) -playerRenderedRotation.Z, new Vec3(0.0f, 0.0f, 1.0f)); - Matrix.ViewTranslate(-playerRenderedPosition); - - // Render - _worldRenderer.Render(_player.Position); - // mPlayer.render(); - - Gl.Disable(Gl.DepthTest); - - _guiWidgets.Render(); - - _fpsCounter++; - } - - private uint RequestWorld() - { - // TODO: change this - if (IsClient()) - { - var worldIds = Client.GetAvailableWorldId.Call(); - if (worldIds.Length == 0) - { - throw new Exception("The server didn't response with any valid worlds."); - } - - var worldInfo = Client.GetWorldInfo.Call(worldIds[0]); - - Singleton.Instance.Worlds.Add(worldInfo["name"]); - } - - // It's a simple wait-until-we-have-a-world procedure now. - // But it should be changed into get player information - // and get the world id from it. - while (Singleton.Instance.Worlds.Get(0) == null) - Thread.Yield(); - return 0; - } - - // Local server - private readonly Server _server; - - // Window - private readonly Window _window; - - // Texture test - private readonly Texture _texture; - - // Player - private readonly Player _player; - - // Widget manager - private readonly WidgetManager _guiWidgets; - - // Update scheduler - private RateController _updateScheduler = new RateController(UpdateFrequency); - - // Rate counters - private uint _fpsCounter, _fpsLatest, _upsLatest; - private object _upsCounter = new uint(); - - // Current world - private readonly World _currentWorld; - - // World renderer - private readonly WorldRenderer _worldRenderer; - - private RateController _rateCounterScheduler = new RateController(1); - } -} \ No newline at end of file diff --git a/NEWorld/MainScript.cs b/NEWorld/MainScript.cs new file mode 100644 index 0000000..eaf7aac --- /dev/null +++ b/NEWorld/MainScript.cs @@ -0,0 +1,242 @@ +// +// NEWorld: GameScene.cs +// NEWorld: A Free Game with Similar Rules to Minecraft. +// Copyright (C) 2015-2018 NEWorld Team +// +// NEWorld is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// NEWorld is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General +// Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with NEWorld. If not, see . +// + +using Core.Math; +using Core.Utilities; +using Game; +using Game.Network; +using Game.Terrain; +using Game.World; +using System; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using Xenko.Core.Diagnostics; +using Xenko.Core.Mathematics; +using Xenko.Engine; +using Xenko.Games; +using Xenko.Graphics; +using Xenko.Rendering; +using Buffer = Xenko.Graphics.Buffer; + +namespace NEWorld +{ + public static class Context + { + private static IGame _game; + + public static IGame Game + { + get => _game; + set + { + _game = value; + IndexBuffer = IndexBufferBuilder.Build(); + } + } + + public static Scene OperatingScene { get; set; } + + public static GraphicsDevice GraphicsDevice => Game.GraphicsDevice; + + public static GraphicsContext GraphicsContext => Game.GraphicsContext; + + public static CommandList CommandList => Game.GraphicsContext.CommandList; + + public static readonly VertexDeclaration VertexLayout = new VertexDeclaration( + VertexElement.TextureCoordinate(), + VertexElement.Color(), + VertexElement.Position() + ); + + public static Buffer IndexBuffer { get; private set; } + } + + public static class IndexBufferBuilder + { + public static Buffer Build() + { + var idx = new int[262144 / 2 * 3]; + var cnt = 0; + for (var i = 0; i < 262144 / 4; ++i) + { + var b = i * 4; + idx[cnt++] = b; + idx[cnt++] = b + 1; + idx[cnt++] = b + 2; + idx[cnt++] = b; + idx[cnt++] = b + 2; + idx[cnt++] = b + 3; + } + + return Buffer.Index.New(Context.GraphicsDevice, idx); + } + } + + public class VertexBuilder : IVertexBuilder + { + public VertexBuilder(int size) => Data = new float[size]; + + public void AddPrimitive(int verts, params float[] data) + { + Count += verts; + data.CopyTo(Data, Size); + Size += data.Length; + } + + public Mesh Dump() + { + return new Mesh + { + Draw = + { + DrawCount = 1, + IndexBuffer = new IndexBufferBinding(Context.IndexBuffer, true, Count / 2 * 3), + PrimitiveType = PrimitiveType.TriangleList, + StartLocation = 0, + VertexBuffers = new[] + { + new VertexBufferBinding(Buffer.Vertex.New(Context.Game.GraphicsDevice, Data), Context.VertexLayout, Count) + } + } + }; + } + + public int Size; + public int Count; + public readonly float[] Data; + } + + public class MainScript : SyncScript + { + private void InitializeModules() + { + Core.Services.ScanAssembly(Assembly.Load("Core")); + Core.Services.ScanAssembly(Assembly.Load("Game")); + Core.Module.Modules.Instance.Load("Main"); + } + + private void InitializeContext() + { + Context.Game = Game; + Context.OperatingScene = SceneSystem.SceneInstance.RootScene; + Core.LogPort.Logger = Log; + Log.ActivateLog(LogMessageType.Debug); + } + + private void EstablishChunkService() + { + if (IsClient()) + { + Singleton.Instance.IsAuthority = false; + } + else + { + // Initialize server + server = Core.Services.Get("Game.Server"); + server.Enable(31111); + server.Run(); + } + } + + private async Task EstablishGameConnection() + { + await Core.Services.Get("Game.Client").Enable("127.0.0.1", 31111); + } + + private void LoadPlayer() + { + player = new Player(0) + { + Position = new Vec3(-16.0, 48.0, 32.0), Rotation = new Vec3(-45.0, -22.5, 0.0) + }; + } + + private async Task EnterCurrentWorld() + { + currentWorld = Singleton.Instance.Worlds.Get(await RequestWorld()); + } + + private void StartTerrainRenderService() + { + rdWorld = new Renderer.RdWorld(currentWorld, player, 4); + } + + private async void Initialize() + { + InitializeContext(); + InitializeModules(); + EstablishChunkService(); + await EstablishGameConnection(); + LoadPlayer(); + await EnterCurrentWorld(); + StartTerrainRenderService(); + } + + public override void Start() + { + Initialize(); + } + + private static async Task RequestWorld() + { + // TODO: change this + if (IsClient()) + { + var worldIds = await Client.GetAvailableWorldId.Call(); + if (worldIds.Length == 0) + { + throw new Exception("The server didn't response with any valid worlds."); + } + + var worldInfo = await Client.GetWorldInfo.Call(worldIds[0]); + + Singleton.Instance.Worlds.Add(worldInfo["name"]); + } + + // It's a simple wait-until-we-have-a-world procedure now. + // But it should be changed into get player information + // and get the world id from it. + while (Singleton.Instance.Worlds.Get(0) == null) + Thread.Yield(); + return 0; + } + + private static bool IsClient() + { + return true; + } + + public override void Update() + { + Singleton.Instance.TaskDispatcher.ProcessRenderTasks(); + } + + // Local server + private Server server; + + // Player + private Player player; + + // Current world + private World currentWorld; + + private Renderer.RdWorld rdWorld; + } +} diff --git a/NEWorld/NEWorld.csproj b/NEWorld/NEWorld.csproj index 03c40a3..03baf5c 100644 --- a/NEWorld/NEWorld.csproj +++ b/NEWorld/NEWorld.csproj @@ -1,89 +1,49 @@ - - - + + - Debug - AnyCPU - {553FF3F0-7F88-4821-BDA1-282CE6B7DDE7} - Exe - Properties - NEWorld - NEWorld - v4.7.1 - 512 + netstandard2.0 - - x64 - true - full - false - ..\bin\Debug\ - DEBUG;TRACE - prompt - 4 - true - false - - - x64 - pdbonly - true - ..\bin\Release\ - TRACE - prompt - 4 - true - false - - - - ..\packages\NuklearSharp.0.2.0.9\lib\netstandard1.1\NuklearSharp.dll - True - - - SDL2-CS.dll - - - - + - - - - - - - - - - + + + + + + + + + + + - + + + + - - {ecb0e309-625f-4a24-926d-d1d23c1b7693} - Core - - - {f83c4945-abff-4856-94a3-d0d31c976a11} - Game - - - {4eb6c361-3e67-4a4e-8b2b-9c8d29d8f852} - OpenGL - + + True + True + VoxelBasic.xkfx + + + True + True + VoxelTraditional.xksl + + - + + XenkoShaderKeyGenerator + VoxelBasic.cs + + + XenkoShaderKeyGenerator + VoxelTraditional.cs + - - - \ No newline at end of file + diff --git a/NEWorld/NEWorld.xkpkg b/NEWorld/NEWorld.xkpkg new file mode 100644 index 0000000..1d76998 --- /dev/null +++ b/NEWorld/NEWorld.xkpkg @@ -0,0 +1,18 @@ +!Package +SerializedVersion: {Assets: 3.1.0.0} +Meta: + Name: NEWorld + Version: 1.0.0 + Authors: [] + Owners: [] + Dependencies: null +AssetFolders: + - Path: !dir Assets + - Path: !dir Effects +ResourceFolders: + - !dir Resources +OutputGroupDirectories: {} +ExplicitFolders: [] +Bundles: [] +TemplateFolders: [] +RootAssets: [] diff --git a/NEWorld/NkSdl.cs b/NEWorld/NkSdl.cs deleted file mode 100644 index ede90f3..0000000 --- a/NEWorld/NkSdl.cs +++ /dev/null @@ -1,353 +0,0 @@ -// -// NEWorld: NkSdl.cs -// NEWorld: A Free Game with Similar Rules to Minecraft. -// Copyright (C) 2015-2018 NEWorld Team -// -// NEWorld is free software: you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published -// by the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// NEWorld is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY -// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General -// Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with NEWorld. If not, see . -// - -using System; -using System.Collections.Generic; -using System.Runtime.InteropServices; -using Core; -using Core.Math; -using NuklearSharp; -using OpenGL; -using SDL2; -using static NuklearSharp.Nuklear; - -namespace NEWorld -{ - public class NkSdl : BaseContext, IDisposable - { - public unsafe NkSdl(IntPtr win) - { - _win = win; - Ctx.clip.copy = ClipBoardCopy; - Ctx.clip.paste = ClipboardPaste; - _prog = new Program(); - using (Shader vertex = new Shader(Gl.VertexShader, @" -#version 450 core -layout(shared, row_major) uniform; -layout (std140, binding = 0) uniform MVP { mat4 ProjMtx; }; -layout (location = 0) in vec2 Position; -layout (location = 1) in vec2 TexCoord; -layout (location = 2) in vec4 Color; -out vec2 Frag_UV; -out vec4 Frag_Color; -void main() { - Frag_UV = TexCoord; - Frag_Color = Color; - gl_Position = ProjMtx * vec4(Position.xy, 0, 1); -}"), - fragment = new Shader(Gl.FragmentShader, @" -#version 450 core -precision mediump float; -layout (binding = 1) uniform sampler2D Texture; -in vec2 Frag_UV; -in vec4 Frag_Color; -out vec4 Out_Color; -void main(){ - Out_Color = Frag_Color * texture(Texture, Frag_UV.st); -}")) - _prog.Link(new[] {vertex, fragment}); - /* buffer setup */ - _ubo = new DataBuffer(16 * sizeof(float)); - _vao = new VertexArray(); - - _vao.EnableAttrib(0); - _vao.EnableAttrib(1); - _vao.EnableAttrib(2); - _vao.AttribFormat(0, 2, Gl.Float, false, 0); - _vao.AttribFormat(1, 2, Gl.Float, false, 2 * sizeof(float)); - _vao.AttribFormat(2, 4, Gl.UnsignedByte, true, 4 * sizeof(float)); - _vao.AttribBinding(0, 0); - _vao.AttribBinding(1, 0); - _vao.AttribBinding(2, 0); - ConvertConfig.vertex_layout = VertexLayout; - ConvertConfig.vertex_size = 4 * sizeof(float) + 4; - _textures = new List(); - _atlas = new FontAtlasWrapper(this); - var defaultFont = _atlas.AddDefaultFont(16.0f); - _atlas.Bake(); - SetFont(defaultFont); - StyleDefault(); - CreateMaskTexture(); - } - - private void CreateMaskTexture() - { - var newTex = new Texture(1, PixelInternalFormats.Rgba8, new Vec2(1, 1)); - newTex.Image(0, new Rect(0, 0, 1, 1), PixelTypes.Rgba, PixelDataFormats.UnsignedByte, - new byte[] {255, 255, 255, 255}); - _textures.Add(newTex); - _mask = newTex.Raw(); - } - - private static readonly nk_draw_vertex_layout_element[] VertexLayout = - { - new nk_draw_vertex_layout_element - { - attribute = NK_VERTEX_POSITION, - format = NK_FORMAT_FLOAT, - offset = 0 - }, - new nk_draw_vertex_layout_element - { - attribute = NK_VERTEX_COLOR, - format = NK_FORMAT_R8G8B8A8, - offset = 4 * sizeof(float) - }, - new nk_draw_vertex_layout_element - { - attribute = NK_VERTEX_TEXCOORD, - format = NK_FORMAT_FLOAT, - offset = 2 * sizeof(float) - }, - new nk_draw_vertex_layout_element - { - attribute = NK_VERTEX_ATTRIBUTE_COUNT - } - }; - - - private static unsafe void ClipBoardCopy(nk_handle handle, char* c, int length) => - Gl.Utf8ToManaged((IntPtr) c); - - private static unsafe void ClipboardPaste(nk_handle usr, nk_text_edit edit) - { - var text = Gl.Utf8ToNative(SDL.SDL_GetClipboardText()); - var ptr = Marshal.AllocHGlobal(text.Length * sizeof(byte)); - Marshal.Copy(text, 0, ptr, text.Length); - nk_textedit_paste(edit, (char*) ptr, nk_strlen((char*) ptr)); - Marshal.FreeHGlobal(ptr); - } - - public unsafe void HandleEvent(ref SDL.SDL_Event evt) - { - switch (evt.type) - { - case SDL.SDL_EventType.SDL_KEYUP: - case SDL.SDL_EventType.SDL_KEYDOWN: - HandleKeyDownEvent(evt); - return; - case SDL.SDL_EventType.SDL_MOUSEBUTTONDOWN: - case SDL.SDL_EventType.SDL_MOUSEBUTTONUP: - { - /* mouse button */ - var down = evt.type == SDL.SDL_EventType.SDL_MOUSEBUTTONDOWN; - int x = evt.button.x, y = evt.button.y; - if (evt.button.button == SDL.SDL_BUTTON_LEFT) - { - if (evt.button.clicks > 1) - InputButton(NK_BUTTON_DOUBLE, x, y, down); - InputButton(NK_BUTTON_LEFT, x, y, down); - } - else if (evt.button.button == SDL.SDL_BUTTON_MIDDLE) - InputButton(NK_BUTTON_MIDDLE, x, y, down); - else if (evt.button.button == SDL.SDL_BUTTON_RIGHT) - InputButton(NK_BUTTON_RIGHT, x, y, down); - - return; - } - case SDL.SDL_EventType.SDL_MOUSEMOTION: - /* mouse motion */ - if (Ctx.input.mouse.grabbed != 0) - { - int x = (int) Ctx.input.mouse.prev.x, y = (int) Ctx.input.mouse.prev.y; - InputMotion(x + evt.motion.xrel, y + evt.motion.yrel); - } - else InputMotion(evt.motion.x, evt.motion.y); - - return; - case SDL.SDL_EventType.SDL_TEXTINPUT: - fixed (byte* ptr = evt.text.text) - InputGlyph(Gl.Utf8ToManaged((IntPtr) ptr)); - return; - case SDL.SDL_EventType.SDL_MOUSEWHEEL: - /* mouse wheel */ - nk_vec2 scroll; - scroll.x = evt.wheel.x; - scroll.y = evt.wheel.y; - InputScroll(scroll); - return; - } - } - - private unsafe void HandleKeyDownEvent(SDL.SDL_Event evt) - { - /* key events */ - bool down = evt.type == SDL.SDL_EventType.SDL_KEYDOWN; - var state = (byte*) SDL.SDL_GetKeyboardState(out var dummy); - if (state == null) return; - switch (evt.key.keysym.sym) - { - case SDL.SDL_Keycode.SDLK_RSHIFT: - case SDL.SDL_Keycode.SDLK_LSHIFT: - InputKey(NK_KEY_SHIFT, down); - break; - case SDL.SDL_Keycode.SDLK_DELETE: - InputKey(NK_KEY_DEL, down); - break; - case SDL.SDL_Keycode.SDLK_RETURN: - InputKey(NK_KEY_ENTER, down); - break; - case SDL.SDL_Keycode.SDLK_TAB: - InputKey(NK_KEY_TAB, down); - break; - case SDL.SDL_Keycode.SDLK_BACKSPACE: - InputKey(NK_KEY_BACKSPACE, down); - break; - case SDL.SDL_Keycode.SDLK_HOME: - InputKey(NK_KEY_TEXT_START, down); - InputKey(NK_KEY_SCROLL_START, down); - break; - case SDL.SDL_Keycode.SDLK_END: - InputKey(NK_KEY_TEXT_END, down); - InputKey(NK_KEY_SCROLL_END, down); - break; - case SDL.SDL_Keycode.SDLK_PAGEDOWN: - InputKey(NK_KEY_SCROLL_DOWN, down); - break; - case SDL.SDL_Keycode.SDLK_PAGEUP: - InputKey(NK_KEY_SCROLL_UP, down); - break; - case SDL.SDL_Keycode.SDLK_z: - InputKey(NK_KEY_TEXT_UNDO, down & state[(int) SDL.SDL_Scancode.SDL_SCANCODE_LCTRL] != 0); - break; - case SDL.SDL_Keycode.SDLK_r: - InputKey(NK_KEY_TEXT_REDO, down & state[(int) SDL.SDL_Scancode.SDL_SCANCODE_LCTRL] != 0); - break; - case SDL.SDL_Keycode.SDLK_c: - InputKey(NK_KEY_COPY, down & state[(int) SDL.SDL_Scancode.SDL_SCANCODE_LCTRL] != 0); - break; - case SDL.SDL_Keycode.SDLK_v: - InputKey(NK_KEY_PASTE, down & state[(int) SDL.SDL_Scancode.SDL_SCANCODE_LCTRL] != 0); - break; - case SDL.SDL_Keycode.SDLK_x: - InputKey(NK_KEY_CUT, down & state[(int) SDL.SDL_Scancode.SDL_SCANCODE_LCTRL] != 0); - break; - case SDL.SDL_Keycode.SDLK_b: - InputKey(NK_KEY_TEXT_LINE_START, down & state[(int) SDL.SDL_Scancode.SDL_SCANCODE_LCTRL] != 0); - break; - case SDL.SDL_Keycode.SDLK_e: - InputKey(NK_KEY_TEXT_LINE_END, down & state[(int) SDL.SDL_Scancode.SDL_SCANCODE_LCTRL] != 0); - break; - case SDL.SDL_Keycode.SDLK_UP: - InputKey(NK_KEY_UP, down); - break; - case SDL.SDL_Keycode.SDLK_DOWN: - InputKey(NK_KEY_DOWN, down); - break; - case SDL.SDL_Keycode.SDLK_LEFT when state[(int) SDL.SDL_Scancode.SDL_SCANCODE_LCTRL] != 0: - InputKey(NK_KEY_TEXT_WORD_LEFT, down); - break; - case SDL.SDL_Keycode.SDLK_LEFT: - InputKey(NK_KEY_LEFT, down); - break; - case SDL.SDL_Keycode.SDLK_RIGHT when state[(int) SDL.SDL_Scancode.SDL_SCANCODE_LCTRL] != 0: - InputKey(NK_KEY_TEXT_WORD_RIGHT, down); - break; - case SDL.SDL_Keycode.SDLK_RIGHT: - InputKey(NK_KEY_RIGHT, down); - break; - default: - return; - } - } - - public override int CreateTexture(int width, int height, byte[] data) - { - var newTex = new Texture(1, PixelInternalFormats.Rgba8, new Vec2(width, height)); - newTex.Image(0, new Rect(0, 0, width, height), PixelTypes.Rgba, PixelDataFormats.UnsignedByte, data); - _textures.Add(newTex); - return (int) newTex.Raw(); - } - - protected override void BeginDraw() - { - SDL.SDL_GetWindowSize(_win, out var width, out _height); - SDL.SDL_GL_GetDrawableSize(_win, out var displayWidth, out var displayHeight); - var othro = Mat4F.Ortho(0, width, 0, _height, 0, 1000); - - _scale.x = displayWidth / (float) width; - _scale.y = displayHeight / (float) _height; - - /* setup global state */ - Gl.Viewport(0, 0, displayWidth, displayHeight); - Gl.Enable(Gl.Blend); - Gl.BlendEquation(Gl.FuncAdd); - Gl.BlendFunc(Gl.SrcAlpha, Gl.OneMinusSrcAlpha); - Gl.Disable(Gl.CullFace); - Gl.Disable(Gl.DepthTest); - Gl.Enable(Gl.ScissorTest); - - /* setup program */ - _prog.Use(); - _prog.Uniform(1, 0); - _ubo.DataSection(0, othro.Data); - } - - protected override void SetBuffers(byte[] vertices, ushort[] indices, int indicesCount, int vertexCount) - { - _verts = new DataBuffer(); - _element = new DataBuffer(); - _vao.Use(); - _verts.AllocateWith(vertices, vertexCount * 20); - _element.AllocateWith(indices, indicesCount); - _vao.BindBuffer(0, _verts, 0, 5 * sizeof(float)); - _ubo.BindBase(Gl.UniformBuffer, 0); - _element.Bind(Gl.ElementArrayBuffer); - } - - protected override void Draw(int x, int y, int w, int h, int textureId, int startIndex, int primitiveCount) - { - Texture.UseRaw(0, textureId != 0 ? (uint) textureId : _mask); - //Gl.Scissor((int) (x * _scale.x), (int) ((_height - (y + h)) * _scale.y), - // (int) (w * _scale.x), (int) (h * _scale.y)); - Gl.DrawElements(Gl.Triangles, primitiveCount * 3, Gl.UnsignedShort, (IntPtr) startIndex); - } - - protected override void EndDraw() - { - _verts.Dispose(); - _element.Dispose(); - Gl.Disable(Gl.Blend); - Gl.Disable(Gl.ScissorTest); - } - - private readonly DataBuffer _ubo; - private readonly VertexArray _vao; - private readonly Program _prog; - private readonly IntPtr _win; - private DataBuffer _verts, _element; - private nk_vec2 _scale; - private int _height; - private readonly List _textures; - private readonly FontAtlasWrapper _atlas; - private uint _mask; - - public void Dispose() - { - _ubo?.Dispose(); - _vao?.Dispose(); - _prog?.Dispose(); - _verts?.Dispose(); - _element?.Dispose(); - if (_textures != null) - foreach (var texture in _textures) - texture.Dispose(); - } - } -} \ No newline at end of file diff --git a/NEWorld/Program.cs b/NEWorld/Program.cs deleted file mode 100644 index ac23e87..0000000 --- a/NEWorld/Program.cs +++ /dev/null @@ -1,62 +0,0 @@ -// -// NEWorld: Program.cs -// NEWorld: A Free Game with Similar Rules to Minecraft. -// Copyright (C) 2015-2018 NEWorld Team -// -// NEWorld is free software: you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published -// by the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// NEWorld is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY -// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General -// Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with NEWorld. If not, see . -// - -using System.Reflection; -using Core; -using Core.Module; -using SDL2; - -namespace NEWorld -{ - internal class NEWorld - { - public NEWorld() - { - Window.GetInstance("NEWorld", 852, 480); - Services.ScanAssembly(Assembly.Load("Game")); - Services.ScanAssembly(Assembly.GetCallingAssembly()); - Modules.Instance.Load("Main"); - } - - public void Run() - { - var fps = 60; - var shouldLimitFps = true; - var delayPerFrame = (uint) (1000 / fps - 0.5); - var window = Window.GetInstance("NEWorld", 852, 480); - var game = new GameScene("TestWorld", window); - while (!window.ShouldQuit()) - { - // Update - window.PollEvents(); - // Render - game.Render(); - window.SwapBuffers(); - if (shouldLimitFps) - SDL.SDL_Delay(delayPerFrame); - } - } - - public static void Main(string[] args) - { - var instance = new NEWorld(); - instance.Run(); - } - } -} \ No newline at end of file diff --git a/NEWorld/Properties/AssemblyInfo.cs b/NEWorld/Properties/AssemblyInfo.cs deleted file mode 100644 index 7724407..0000000 --- a/NEWorld/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,54 +0,0 @@ -// -// NEWorld: AssemblyInfo.cs -// NEWorld: A Free Game with Similar Rules to Minecraft. -// Copyright (C) 2015-2018 NEWorld Team -// -// NEWorld is free software: you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published -// by the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// NEWorld is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY -// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General -// Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with NEWorld. If not, see . -// - -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("NEWorld")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("NEWorld")] -[assembly: AssemblyCopyright("Copyright © 2018")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("553FF3F0-7F88-4821-BDA1-282CE6B7DDE7")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] \ No newline at end of file diff --git a/NEWorld/Renderer/BlockTextures.cs b/NEWorld/Renderer/BlockTextures.cs deleted file mode 100644 index 4e885a8..0000000 --- a/NEWorld/Renderer/BlockTextures.cs +++ /dev/null @@ -1,153 +0,0 @@ -// -// NEWorld: BlockTextures.cs -// NEWorld: A Free Game with Similar Rules to Minecraft. -// Copyright (C) 2015-2018 NEWorld Team -// -// NEWorld is free software: you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published -// by the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// NEWorld is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY -// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General -// Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with NEWorld. If not, see . -// - -using System; -using System.Collections.Generic; -using Core; -using Core.Math; -using Core.Utilities; -using Game.Terrain; -using OpenGL; -using SDL2; - -namespace NEWorld.Renderer -{ - [DeclareService("BlockTextures")] - public class BlockTextures : IBlockTextures, IDisposable - { - private unsafe class RawTexture - { - public RawTexture(string filename) => Surface = (SDL.SDL_Surface*) SDL_image.IMG_Load(filename); - - ~RawTexture() => SDL.SDL_FreeSurface((IntPtr) Surface); - - public SDL.SDL_Surface* Surface { get; } - } - - public BlockTextures() - { - SDL_image.IMG_Init(SDL_image.IMG_InitFlags.IMG_INIT_PNG); - } - - public void Dispose() - { - SDL_image.IMG_Quit(); - } - - private static int Capacity() - { - var w = CapacityRaw() / _pixelPerTexture; - return w * w; - } - - private static int CapacityRaw() - { - int cap = 2048; - //glGetIntegerv(GL_MAX_TEXTURE_SIZE, &cap); - return cap; - } - - public static void SetWidthPerTex(int wid) => _pixelPerTexture = wid; - - public static int GetWidthPerTex() => _pixelPerTexture; - - public static unsafe Texture BuildAndFlush() - { - var count = RawTexs.Count; - _texturePerLine = 1 << (int) Math.Ceiling(Math.Log(Math.Ceiling(Math.Sqrt(count))) / Math.Log(2)); - var wid = _texturePerLine * _pixelPerTexture; - - var mask = EndianCheck.BigEndian - ? new uint[] {0xff000000, 0x00ff0000, 0x0000ff00, 0x000000ff} - : new uint[] {0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000}; - - var s = (SDL.SDL_Surface*) SDL.SDL_CreateRGBSurface(0, wid, wid, 32, mask[0], mask[1], mask[2], mask[3]); - for (var i = 0; i < count; ++i) - { - var x = i % _texturePerLine; - var y = i / _texturePerLine; - SDL.SDL_Rect r; - r.x = x * _pixelPerTexture; - r.y = y * _pixelPerTexture; - r.w = r.h = _pixelPerTexture; - SDL.SDL_BlitScaled((IntPtr) RawTexs[i].Surface, IntPtr.Zero, (IntPtr) s, (IntPtr) (&r)); - } - - RawTexs.Clear(); - var levels = (int) (Math.Log(_pixelPerTexture) / Math.Log(2)); - var ret = new Texture(levels, PixelInternalFormats.Rgba8, new Vec2(wid, wid)) - { - MinifyingFilter = Texture.Filter.NearestMipmapNearest, - MagnificationFilter = Texture.Filter.Nearest - }; - Build2DMipmaps(ret, wid, wid, (int) (Math.Log(_pixelPerTexture) / Math.Log(2)), (byte*) s->pixels); - return ret; - } - - private static int Align(int x, int al) - { - return x % al == 0 ? x : (x / al + 1) * al; - } - - private static unsafe void Build2DMipmaps(Texture tex, int w, int h, int level, byte* src) - { - var scale = 1; - var cur = new byte[w * h * 4]; - for (var i = 0; i <= level; i++) - { - int curW = w / scale, curH = h / scale; - for (var y = 0; y < curH; y++) - for (var x = 0; x < curW; x++) - for (var col = 0; col < 4; col++) - { - var sum = 0; - for (var yy = 0; yy < scale; yy++) - for (var xx = 0; xx < scale; xx++) - sum += src[(y * scale + yy) * Align(w * 4, 4) + (x * scale + xx) * 4 + col]; - cur[y * Align(curW * 4, 4) + x * 4 + col] = (byte) (sum / (scale * scale)); - } - - tex.Image(i, new Rect(0, 0, curW, curH), PixelTypes.Rgba, PixelDataFormats.Byte, cur); - scale *= 2; - } - } - - public uint Add(string path) - { - if (RawTexs.Count >= Capacity()) - throw new Exception("Too Many Textures"); - RawTexs.Add(new RawTexture(path)); - return (uint) (RawTexs.Count - 1); - } - - public unsafe void GetTexturePos(float* pos, uint id) - { - var percentagePerTexture = 1.0f / _texturePerLine; - var x = id % _texturePerLine; - var y = id / _texturePerLine; - pos[0] = percentagePerTexture * x; - pos[1] = percentagePerTexture * y; - pos[2] = percentagePerTexture * (x + 1); - pos[3] = percentagePerTexture * (y + 1); - } - - private static int _pixelPerTexture = 32, _texturePerLine = 8; - private static readonly List RawTexs = new List(); - } -} \ No newline at end of file diff --git a/NEWorld/Renderer/ChunkRenderer.cs b/NEWorld/Renderer/RdChunk.cs similarity index 50% rename from NEWorld/Renderer/ChunkRenderer.cs rename to NEWorld/Renderer/RdChunk.cs index 5fb640e..890952a 100644 --- a/NEWorld/Renderer/ChunkRenderer.cs +++ b/NEWorld/Renderer/RdChunk.cs @@ -1,5 +1,4 @@ -// -// NEWorld: ChunkRenderer.cs +// NEWorld: GameScene.cs // NEWorld: A Free Game with Similar Rules to Minecraft. // Copyright (C) 2015-2018 NEWorld Team // @@ -17,36 +16,15 @@ // along with NEWorld. If not, see . // -using System; -using System.Runtime.InteropServices; -using Core.Math; -using Core.Utilities; using Game.Terrain; using Game.World; -using OpenGL; +using Xenko.Core.Mathematics; +using Xenko.Engine; +using Xenko.Rendering; + namespace NEWorld.Renderer { - public class VertexBuilder : IVertexBuilder - { - public VertexBuilder(int size) => Data = Marshal.AllocHGlobal(size * sizeof(float)); - - ~VertexBuilder() => Marshal.FreeHGlobal(Data); - - public void AddPrimitive(int verts, params float[] data) - { - VertCount += verts; - Marshal.Copy(data, 0, Data + Size * sizeof(float), data.Length); - Size += data.Length; - } - - public ConstDataBuffer Dump() => VertCount > 0 ? new ConstDataBuffer(Size * sizeof(float), Data) : null; - - public int Size; - public int VertCount; - public readonly IntPtr Data; - } - /** * \brief It stores all the render data (VA) used to render a chunk. * But it does not involve OpenGL operations so it can be @@ -65,13 +43,12 @@ public ChunkRenderData() * Does not involve OpenGL functions. * \param chunk the chunk to be rendered. */ - public void Generate(Chunk chunk) + public void Generate(Game.World.Chunk chunk) { - // TODO: merge face rendering - var tmp = new Vec3(); - for (tmp.X = 0; tmp.X < Chunk.Size; ++tmp.X) - for (tmp.Y = 0; tmp.Y < Chunk.Size; ++tmp.Y) - for (tmp.Z = 0; tmp.Z < Chunk.Size; ++tmp.Z) + var tmp = new Int3(); + for (tmp.X = 0; tmp.X < Game.World.Chunk.Size; ++tmp.X) + for (tmp.Y = 0; tmp.Y < Game.World.Chunk.Size; ++tmp.Y) + for (tmp.Z = 0; tmp.Z < Game.World.Chunk.Size; ++tmp.Z) { var b = chunk[tmp]; var target = Blocks.Index[b.Id].IsTranslucent ? VaTranslucent : VaOpacity; @@ -89,9 +66,14 @@ public void Generate(Chunk chunk) * VBO that we need to render. It can be generated from a * ChunkRenderData */ - public class ChunkRenderer : StrictDispose + public class RdChunk { - public ChunkRenderer(ChunkRenderData data) => Update(data); + public RdChunk(ChunkRenderData data, Vector3 chunkPosition) + { + Update(data); + Entity = new Entity(); + Entity.GetOrCreate().Position = chunkPosition * Game.World.Chunk.Size; + } /** * \brief Generate VBO from VA. Note that this function will call @@ -101,44 +83,12 @@ public class ChunkRenderer : StrictDispose */ public void Update(ChunkRenderData data) { - Release(); - _buffer = data.VaOpacity.Dump(); - _bufferTrans = data.VaTranslucent.Dump(); - _normCount = data.VaOpacity.VertCount; - _transCount = data.VaTranslucent.VertCount; + var model = new Model(); + model.Add(data.VaOpacity.Dump()); + model.Add(data.VaTranslucent.Dump()); + Entity.GetOrCreate().Model = model; } - - protected override void Release() - { - _buffer?.Dispose(); - _bufferTrans?.Dispose(); - } - - // Draw call - public void Render(Vec3 c, WorldRenderer rd) - { - if (_buffer != null) - { - Matrix.ModelTranslate(c * Chunk.Size); - rd.FlushMatrix(); - rd.RenderBuffer(_buffer, _normCount); - Matrix.ModelTranslate(-c * Chunk.Size); - } - } - - public void RenderTrans(Vec3 c, WorldRenderer rd) - { - if (_bufferTrans != null) - { - Matrix.ModelTranslate(c * Chunk.Size); - rd.FlushMatrix(); - rd.RenderBuffer(_bufferTrans, _transCount); - Matrix.ModelTranslate(-c * Chunk.Size); - } - } - - // Vertex buffer object - private int _normCount, _transCount; - private ConstDataBuffer _buffer, _bufferTrans; + + public Entity Entity { get; } } -} \ No newline at end of file +} diff --git a/NEWorld/Renderer/RdWorld.cs b/NEWorld/Renderer/RdWorld.cs new file mode 100644 index 0000000..268441a --- /dev/null +++ b/NEWorld/Renderer/RdWorld.cs @@ -0,0 +1,142 @@ +// +// NEWorld: GameScene.cs +// NEWorld: A Free Game with Similar Rules to Minecraft. +// Copyright (C) 2015-2018 NEWorld Team +// +// NEWorld is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// NEWorld is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General +// Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with NEWorld. If not, see . +// + +using System; +using Game; +using Game.World; +using System.Collections.Generic; +using System.Linq; +using Core.Utilities; +using Xenko.Core.Mathematics; + +namespace NEWorld.Renderer +{ + public class RdWorld + { + public const int MaxChunkRenderCount = 4; + + private class RenderDetectorTask : IReadOnlyTask + { + public RenderDetectorTask(RdWorld rdWorldRenderer, uint currentWorldId, Player player) + { + this.rdWorldRenderer = rdWorldRenderer; + this.currentWorldId = currentWorldId; + this.player = player; + } + + public void Task(ChunkService cs) + { + var counter = 0; + // TODO: improve performance by adding multiple instances of this and set a step when itering the chunks. + var position = player.Position; + var positionInt = new Int3((int) position.X, (int) position.Y, (int) position.Z); + var chunkpos = World.GetChunkPos(positionInt); + var world = cs.Worlds.Get(currentWorldId); + foreach (var c in world.Chunks) + { + var chunk = c.Value; + var chunkPosition = chunk.Position; + // In render range, pending to render + if (chunk.IsUpdated && ChebyshevDistance(chunkpos, chunkPosition) <= rdWorldRenderer.RenderDist) + { + if (NeighbourChunkLoadCheck(world, chunkPosition)) + { + // TODO: maybe build a VA pool can speed this up. + var crd = new ChunkRenderData(); + crd.Generate(chunk); + cs.TaskDispatcher.Add(new VboGenerateTask(world, chunkPosition, crd, + rdWorldRenderer.chunkRenderers)); + if (++counter == MaxChunkRenderCount) break; + } + } + } + } + + // TODO: Remove Type1 Clone + private static int ChebyshevDistance(Int3 l, Int3 r) => Math.Max(Math.Max(Math.Abs(l.X - r.X), Math.Abs(l.Y - r.Y)), Math.Abs(l.Z - r.Z)); + + public IReadOnlyTask Clone() => (IReadOnlyTask) MemberwiseClone(); + + private static readonly Int3[] Delta = + { + new Int3(1, 0, 0), new Int3(-1, 0, 0), + new Int3(0, 1, 0), new Int3(0, -1, 0), + new Int3(0, 0, 1), new Int3(0, 0, -1) + }; + + private static bool NeighbourChunkLoadCheck(World world, Int3 pos) => + Delta.All(p => world.IsChunkLoaded(pos + p)); + + private readonly RdWorld rdWorldRenderer; + private readonly uint currentWorldId; + private readonly Player player; + } + + private class VboGenerateTask : IRenderTask + { + public VboGenerateTask(World world, Int3 position, ChunkRenderData crd, + Dictionary chunkRenderers) + { + this.world = world; + this.position = position; + chunkRenderData = crd; + this.chunkRenderers = chunkRenderers; + } + + public void Task(ChunkService srv) + { + if (!world.Chunks.TryGetValue(position, out var chunk)) return; + chunk.IsUpdated = false; + if (chunkRenderers.TryGetValue(position, out var it)) + { + it.Update(chunkRenderData); + } + else + { + var renderer = new RdChunk(chunkRenderData, new Vector3(position.X, position.Y, position.Z)); + Context.OperatingScene.Entities.Add(renderer.Entity); + chunkRenderers.Add(position, renderer); + } + } + + public IRenderTask Clone() => (IRenderTask) MemberwiseClone(); + + private readonly World world; + private readonly Int3 position; + private readonly ChunkRenderData chunkRenderData; + private readonly Dictionary chunkRenderers; + } + + public RdWorld(World world, Player player, int renderDistance) + { + this.world = world; + RenderDist = renderDistance; + chunkRenderers = new Dictionary(); + Singleton.Instance.TaskDispatcher.AddRegular(new RenderDetectorTask(this, world.Id, player)); + } + + private readonly World world; + + // Ranges + public readonly int RenderDist; + + // Chunk Renderers + private readonly Dictionary chunkRenderers; + } +} diff --git a/NEWorld/Renderer/WorldRenderer.cs b/NEWorld/Renderer/WorldRenderer.cs deleted file mode 100644 index b74e033..0000000 --- a/NEWorld/Renderer/WorldRenderer.cs +++ /dev/null @@ -1,230 +0,0 @@ -// -// NEWorld: WorldRenderer.cs -// NEWorld: A Free Game with Similar Rules to Minecraft. -// Copyright (C) 2015-2018 NEWorld Team -// -// NEWorld is free software: you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published -// by the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// NEWorld is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY -// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General -// Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with NEWorld. If not, see . -// - -using System; -using System.Collections.Generic; -using System.Linq; -using Core.Math; -using Game; -using Game.Terrain; -using Game.World; -using OpenGL; - -namespace NEWorld.Renderer -{ - /** - * \brief Manage the VBO of a world. It includes ChunkRenderer. - */ - public class WorldRenderer : IDisposable - { - public const int MaxChunkRenderCount = 4; - - private class RenderDetectorTask : IReadOnlyTask - { - public RenderDetectorTask(WorldRenderer worldRenderer, uint currentWorldId, Player player) - { - _worldRenderer = worldRenderer; - _currentWorldId = currentWorldId; - _player = player; - } - - public void Task(ChunkService cs) - { - var counter = 0; - // TODO: improve performance by adding multiple instances of this and set a step when itering the chunks. - var position = _player.Position; - var positionInt = new Vec3((int) position.X, (int) position.Y, (int) position.Z); - var chunkpos = World.GetChunkPos(positionInt); - var world = cs.Worlds.Get(_currentWorldId); - foreach (var c in world.Chunks) - { - var chunk = c.Value; - var chunkPosition = chunk.Position; - // In render range, pending to render - if (chunk.IsUpdated && chunkpos.ChebyshevDistance(chunkPosition) <= _worldRenderer.RenderDist) - { - if (NeighbourChunkLoadCheck(world, chunkPosition)) - { - // TODO: maybe build a VA pool can speed this up. - var crd = new ChunkRenderData(); - crd.Generate(chunk); - cs.TaskDispatcher.Add(new VboGenerateTask(world, chunkPosition, crd, - _worldRenderer._chunkRenderers)); - if (counter++ == 3) break; - } - } - } - } - - public IReadOnlyTask Clone() => (IReadOnlyTask) MemberwiseClone(); - - private static readonly Vec3[] Delta = - { - new Vec3(1, 0, 0), new Vec3(-1, 0, 0), - new Vec3(0, 1, 0), new Vec3(0, -1, 0), - new Vec3(0, 0, 1), new Vec3(0, 0, -1) - }; - - private static bool NeighbourChunkLoadCheck(World world, Vec3 pos) => - Delta.All(p => world.IsChunkLoaded(pos + p)); - - private readonly WorldRenderer _worldRenderer; - private readonly uint _currentWorldId; - private readonly Player _player; - } - - private class VboGenerateTask : IRenderTask - { - public VboGenerateTask(World world, Vec3 position, ChunkRenderData crd, - Dictionary, ChunkRenderer> chunkRenderers) - { - _world = world; - _position = position; - _chunkRenderData = crd; - _chunkRenderers = chunkRenderers; - } - - public void Task(ChunkService srv) - { - if (!_world.Chunks.TryGetValue(_position, out var chunk)) return; - chunk.IsUpdated = false; - if (_chunkRenderers.TryGetValue(_position, out var it)) - { - it.Update(_chunkRenderData); - } - else - { - _chunkRenderers.Add(_position, new ChunkRenderer(_chunkRenderData)); - } - } - - public IRenderTask Clone() => (IRenderTask) MemberwiseClone(); - - private readonly World _world; - private readonly Vec3 _position; - private readonly ChunkRenderData _chunkRenderData; - private readonly Dictionary, ChunkRenderer> _chunkRenderers; - } - - public WorldRenderer(World world, int renderDistance) - { - _world = world; - RenderDist = renderDistance; - _chunkRenderers = new Dictionary, ChunkRenderer>(); - _prog = new Program(); - using (Shader vertex = new Shader(Gl.VertexShader, @" -#version 450 core -layout(shared, row_major) uniform; -layout (std140, binding = 0) uniform vertexMvp { mat4 ProjMtx; }; -layout (location = 0) in vec3 Position; -layout (location = 1) in vec2 TexCoord; -layout (location = 2) in vec4 Color; -out vec2 Frag_UV; -out vec4 Frag_Color; -void main() { - Frag_UV = TexCoord; - Frag_Color = Color; - gl_Position = ProjMtx * vec4(Position.xyz, 1); -}"), - fragment = new Shader(Gl.FragmentShader, @" -#version 450 core -precision mediump float; -layout (binding = 1) uniform sampler2D Texture; -in vec2 Frag_UV; -in vec4 Frag_Color; -out vec4 Out_Color; -void main(){ - Out_Color = vec4(1.0, 1.0, 1.0, 1.0); //Frag_Color * texture(Texture, Frag_UV.st); -}")) - _prog.Link(new[] {vertex, fragment}); - - _ubo = new DataBuffer(16 * sizeof(float)); - _vao = new VertexArray(); - - _vao.EnableAttrib(0); - _vao.EnableAttrib(1); - _vao.EnableAttrib(2); - _vao.AttribFormat(0, 3, Gl.Float, false, 5 * sizeof(float)); - _vao.AttribFormat(1, 2, Gl.Float, false, 0); - _vao.AttribFormat(2, 3, Gl.Float, false, 2 * sizeof(float)); - _vao.AttribBinding(0, 0); - _vao.AttribBinding(1, 0); - _vao.AttribBinding(2, 0); - } - - public void RenderBuffer(ConstDataBuffer buffer, int verts) - { - _vao.BindBuffer(0, buffer, 0, 8 * sizeof(float)); - Gl.DrawArrays(Gl.Quads, 0, verts); - } - - // Render all chunks - private int Render(Vec3 position) - { - var chunkPending = new List, ChunkRenderer>>(); - - var chunkpos = World.GetChunkPos(position); - _vao.Use(); - _ubo.BindBase(Gl.UniformBuffer, 0); - foreach (var c in _chunkRenderers) - { - if (chunkpos.ChebyshevDistance(c.Key) > RenderDist) continue; - c.Value.Render(c.Key, this); - chunkPending.Add(c); - } - - Gl.Enable(Gl.Blend); - Gl.BlendFunc(Gl.SrcAlpha, Gl.OneMinusSrcAlpha); - foreach (var c in chunkPending) - { - c.Value.RenderTrans(c.Key, this); - } - - Gl.Disable(Gl.Blend); - return chunkPending.Count; - } - - public int Render(Vec3 v) => Render(new Vec3((int) v.X, (int) v.Y, (int) v.Z)); - - public void RegisterTask(ChunkService chunkService, Player player) => - chunkService.TaskDispatcher.AddRegular(new RenderDetectorTask(this, _world.Id, player)); - - public void FlushMatrix() => _ubo.DataSection(0, Matrix.Get().Data); - - private readonly World _world; - - // Ranges - public readonly int RenderDist; - - // Chunk Renderers - private readonly Dictionary, ChunkRenderer> _chunkRenderers; - private readonly Program _prog; - private readonly VertexArray _vao; - private readonly DataBuffer _ubo; - - public void Dispose() - { - _prog?.Dispose(); - _vao?.Dispose(); - _ubo?.Dispose(); - foreach (var renderer in _chunkRenderers) - renderer.Value.Dispose(); - } - } -} \ No newline at end of file diff --git a/NEWorld/Resources/skybox_texture_ldr.dds b/NEWorld/Resources/skybox_texture_ldr.dds new file mode 100644 index 0000000000000000000000000000000000000000..952d8185dc5c019e0e8e42b5eaf1f950fc386e82 GIT binary patch literal 1573012 zcmeFa?XPv$S>DTe`EWiQsnz*}#1U##6e&^^9ZOMEs}@!*LcnSW$*BoMH8KP&L#X96 z)XG~8DF(|W)N(Pk45k)F4>3lD)aWS`l&Tf~*V*U3#u~q|u5r&XU)J9HdF+H?>DE2w zTx;*O<`~!Sei?J_$3OBT|L;Hg`uBa`_x*!^_`iPN_x&$1kp1uHVH_R)?;piG<43>m zM}GQ&|7Y|=|HnU!v;SZJ%KsgQ|67=TP9LrR9{jm)UOo6kwVw^2?4ysrc;)uMzkL3x z+2>-M?Pubg+fT zh&>$d!+to%*&h1*)dw!HAB^|@fZ6vy`1z~O{z)9)syz|E4|_bud-l0_&u!Y?=kYq< z@xtrR^XJF^w4ZCu{UvMFz4wRXXWbr-W7$7@vf5MOW!t*y_*Wd*J*P_Oaf9X{*;GXmEtc}>b*ZlkEkH#0y0dZ5FSZY4ccbC(T ztlcjE&i)1b=NM4qz$*W1F(7l#vGebBzzYAwfR2B~fTQ(4eSpV+(gW=9pJKoVQw=c8 z|2N@3$AIhbuUJ3~IHDnYPuAl?v-g#M=Z{!$+x+`y92sAOf8#yRXP)nC@lUQF`BL+H z4A`zKOZ_kQ-E2SLW6=+|`Cfp=o4tVc2H+o09LRkD$6Y))?*p(W(Cq~r?FYKO0P*iJ zAaedE%~BsA{&NiQnxN$VTmxu7;Nw`#zxV%nd@27qJ}l~h=>LAWnsI(qXVPc4A3z;I zUc2>pu*a7eB^LOYb9;X8_h0xr^Dy!;S3Y+dAD#Eb`*NP=xb^q+`P+PVJtZ}!`HuTR z3~=@}UhuzG2e2;)_jvgC8UXg?JO2BV@vk^=p$5rM=(y!C^!}dkek3wR6|^6xPK?(uN%ek%qf{^t6#6NMM@lPGF$H%R|zQ*;@^Y@Vd%Dm4J^M=2e ze`j7}xW`|(zqi<5s@!uAYL)-GA0Ym_7_hb<=rKU|2e$ZEELe*H1O9zau;qVZz@_^E z9sjBWHu8Ve0OWu7tM&h|54eDTtxe{8?>O`SaO4E){~P3g^y}I#<6Gx@kI$o=uf1>Y z_h0z>fO(%I<_&)%{$bwzh5dcUe`3Gn|DTWE-_3h~8Uy_K0Ivl!en5Kw#a;*>wsmh% zF~REqj|21okEsVZo3nqa0lpT`2Ar+j6Fk}rC{}cP0n`J2PjCN3_%KJ&eIyv6?w>VekR8vlv|-Lpd9qx_3|tj&YP z##`gxxo6z5f1uxY?Kt<0limvd9s_dz=eXVz=z4)J|5FD9|64I2^Pg*g>+nx6aLIlE zbwKL}r~wA~zwQO<*#PM7XjrId>VLxz_f7Jjb|BHWOz(M}Cb~nlYEe_o7egL&U9!`q?ZqNV5J=fK^kJrlh zw)xMz=i^KHA2ILeGXKB$($#}*z89czjsvw0D0>3BADHYM57K^s_W(YIe>{D_(fxqP z{U5IO5%vML>HzKq_;W$67SMhGbpSrq1J(=N_veGJ6%P{s^qBIY<(pdWg%|yR_0q%F zZo6LC`@Hme{{9?aJD-o{)aRTroZs^Oyv=7#68j(c(YXKT>}yONKpe>Y>)2wzI{$S) zfW3fR2M`0`9#0*x!@u?e%6_2nuQ;%~AL#ruzApc@AD|bIAJzlc{rezl0D6Jz^55MP z2s|ONqix}`;@n3U2-<#{m{-*JMegL|=m;{WA7 z28e%;0VDn`27F9Cfz<%#{BQlhRt$i9_Z|aw`T=r3es@0*=H0`8>j%jF@+tq@UI6<6 zyZo~kSe^&ixgX%Q8sppGUooJ>fqTP0+;sd?PjAJ5`^7)sC3*4BcOUs% zYXN<@&pAFH8~bPeEe60pF<_1V8Uw_?$ABUKz8{$D0Oy`D{L5d5f3E}b^8wDikBI?g zKX8YCivic$5A+z2_5*w^eayP&{l2|pxNrVF;6E`}Y5<*6T)0>K=g(X@9x?CdZp^>4 zJ{n(#f7eAGXWsL%`2Vr9J@|`aA4_`y>Idip@Hrk>FQC62_F6#Wb3C|r`vE)~D8JYb zWKST+0ggv8pwt1{3mEJNhidoIA@!Kin= z@SL+lZN2G(OZF1=Jb|0e`*9xgbDfXxyY_kdZa3yR{|;*U>|y=7{k_(Xb;e5zSs#1H zKN`8l?Z@M|SnNmt_%p`-N(`V5SmWP%0qqN{^M7ym1Bd|~|6T{I?Fp{&-(rB*0x^bt ze2M|y3mn@I*jZEOUt_d@A2Ggl4*um!3^4w0aX-N0fRDZ2E}R+BlX?*;n&f6e^>?*+== z2lYJx&jqm`xZr;;1~~s3(+3>oUva=Xa3TLW25hlDdd>MSG2s64?|j}5 z{{8drIseW*7;vutjsJVN zA28(KV*qtOKJ&kb0sgnbgZ!WBfW!O~1Gd&tud(j#(TDtq@vU>2f9cd~*Bj=@%-i1a zZRC9a9eba5>-itf*Zg()_x1Mio?c+T_4qaKukruXD_0LbxnjQ<=e-uV-}?dV2fF9J zAU^F6(g)yMJYXLH5C7Q{4;FO*`vHUd0agQiP_;nZ2gG*!0q@QB*4E>J#{kA#+^@Yx zJQ(c<+(JKa>%84CJ{|r(9ur4nc)!j5Uh8;71O6U(Z$9dMSeGB&leTjYivP_RV7-9& zrw-Wa2W(I9z1R-USd_si$x>(9q0p1JT&;0;mK*zsgfX9KH`!oN;7_gH6Yaamn_}%=k7=1|% zfKKq}2fxPo+IhHd{&xF;!}<38%s;G;{B`Sl){Aw-Z?QglP5if7U*moL(Pra+D+Vm? z4cdNyVt_M07>j?80q=|b0D6E?9pL$&an}o6#{d3)KwU3lz-W#A96E82mgLtsUpwEp zug?deaq`~#h0FW(J-BXp=3>iVmw#VBANyJ#8F%dO^Y83y{BW=@d#KuGKQP)4pbj|F z58U+m0N)2-%sv4AI{N{mI)HPF7@+%sDF*oQ&VJyc9?(7@R-O&8e&ERZqaBX{jMtdo zJRfnuds^pVG`?S4-qz=guEEdwwJ*k+n+swr>)`&#n&$U-jjuf6Io^8twO;>+f^)3h ztExJMe|+PAZBHQY2htC0@vpj|_5=51KLGaS6aU;79QFfSbwK97^#Z~FPi({h_?OpS zK&%gz+1GJnzr_IKUu&JUkdNUX?^@IN61|r9O5<4k?J&8|8S(LLae2RfkAHW@T%-Jg z`3wL34b5R*10AzIdHwc|o&5{@d&L8|m;az^bB+J4UO@K=*7gJT_^&a*dx8749{~G{ z{XpLf81(|f{ea5KjMJ1p)t(h?-#eX^*MZR`Fw31|HQ!T zcVeo~y>ow4{#%{D-`{8MVgE4yTQT64_5+9m_j5mB$p2mpp#E<@o(B;7NBECeK<;<% zb-*J3Q`_BSKfq(aZS(KUh@so!@_zju|Nf(Kt9>%}93SK#-m~A!_xbh4eSGeL{Gao^ zn0M=*UyZ)?kzanLGx`Ab10IS#z^!{$;JW((woka;4;=V=0rmjg>v_ShAL#%8f?f-B z@!&`=;Pn7|f_wj70QErB0^Sc00~S3S{O3C0NG~AmxZZLu((#SrfokDoV#ssq`)!=R z@9-G5wMqSw`cYB+%CGoqaV1>{r%=%mH7XaG5_od>i*y=|D_+W zdxJb5;Cq6``siK(bpUaouLo}M?+FqE(!VFX%Ku&+koN-`|LOTa-wV_j{^hM7$m_>C zc6&S5i2=^P##yIF_1)g*Y%sPMkaOsJ$v?5kztfQS+|zSi2l&tP$uaYOly}~@#yxpI zJ+F&ynh#CEzI>-QX)f#0kAI@_Z!v)Uj~DwM1G@ab#Xm9N0RJ8Xdj9pyaO87;UYLJ3 z1n;?DeGI--2EK7yn0MK{=;nHQf+jeVXqhU=pq{^33QgZxwDAL2i_ z|4_A(|HXfe0dS8e_Y(u!{Q;gCzF+$RYcU}6pJPDE|NOlWb-?EHL8CoE=U?N-KXpKg zxt{-H?EGtdh<~pG_H>o?=*PFtfAEueyX07L0fs$B@H=>&+k$ChW{h|K-MDpYE9!8_Xn9P z&!@zJdl3`(o&I?m=kIgycx^tIFMOWQtz+k%G2G)<@_W-k=mYkz)8A9)7hiJznSI58 zA^#Qw;2yu&56I66<>vz~`TOCB0pj0dz>xp7UO=@#t^wem`vQso&F6!T)B(=F##{FW zGyf3-;GY=K)_s)!;oob4E&YvNL&x~6`|I-4elGKP$+5=^{hW?3`FXwfAn&{{U%!r- zn~@*QX+8(*?%_TAgZxwDclkp(gu2qb=lfdctJXi+4*%2ubx#29@#KH`V&dOpz=(g3 z0Xy|S{1XFK`S%#$|E6H(|9pTW@^}q8^49MEsJ05A;8Gwsx z`%8Jf?f2h+|Iys!b9?i9gn5qvEBqho???Q@e&ZeP@sG=SU0b6?l)B&|0U_UU|0gHa%NG~vaK42wA6R(K} z!G5_vC?7EZ^H`v<|JzOF-*NL^;5z+4*0aX~A748jT*%{Xe~w?nXq>PAI>*ku#vR}B z47c_8%k@0%0aN4Oc7Ok&Pe(uc>0rKD)Tr!Tg{C|96$eKAZ}kJlzT!bw3yk&yy%vCb z_bCQY4=nlDUO;+2U=#y#JwQJ&;C~bYZe>4U?ODOx4=DeeDF!U{63>5n&Y%uxaUk?3 z4PHmb`Wf=wh!vf?Z9Z@N_wgOqzQ4|Mjq=x>C;i+O_r!pm=Xc;9f2h7EcaZDlTfXn> z`!n~%fZ*S9*(1;FYaa$5Ajc5P-1}T@;cgfHekhBx@}%>`}g^G^|4=< zk1w60%zV!GaF6#`9b;e5((7rB74u{LWrz58?mPAs2X^>}{i^4}zsG=_|2baS56Cf~ z@z0*X+J3-V3`qRzejzo0{JH*5|DNC_`vDdMoOvH_^1l@WGXK6GFp2?b%`^Xt{eYyW z<^F*2f1NtO*~nw@blbe%_V0uF{N2}%Z^A!}Z~3%dt*?*Tu8b@9L;lJ0?qR>>d(ZvG zf6Gba{pvlB#rTmgzS^7b1sv!Hy1ySty+G*)xG#ubyFbYDg5C>goZz_K5>!G3`4 z34BB~K{fp?F>wHY?S7y=BXHUM!PXDfJps=j8m~S-l>33}#DWnUa0F-X7ti+^edp_* z8|C`e%b)uCA1XOrdHrO0J=n)->lnGdjEVcs_SzWc@b59;Tn|+KRR?%2;Cq1w?hnE~ z9`5lS|6T{Eu4*yY#$FHbe88vT|Qp#IOk_5&mS{ocUg zen9MFUIQfly&q5waOQubAMhBkwI2|50Nghp&kLw8Sml2&1~~s^9P1?hvApiC4Ifu6 zE&G8~1HgUr_lbXJFpm`*bnZxu$Ul4O@pX9jpKqKT-$edULoRfPQbI z2e_2S`~7}r`8s3fYR#v99`5Bo7_q_GFZqA8j-1PDuVeUk=6&4m<0bAnKhmYnNw;C- z{g1?0_UONU{p!&#p6%n&3%GT)fbR{A;($FTKwaRqKtx+#9I=5B_jI^P1(vH=Y3;q{- z0)D?RdREm1ZEqlsJ>Ol2m+z*Z@oUXvcyIarV1I9G(bfgN<=57c^YD)E>U;J0!R^S% z^Z3d=eB&PoePSQ}spsO~#^|l^Z!y5x*LZP%;KCTd{Q>p_T0hWYK;?fe2IM_~R1b)M zY{7r&4-)?u-XHAvulEb-1>_s|!~v}XYcg85e9miut$hLXg0|=fR1<`)#{uU*#;q?P z4%~8o@P3O2nR7pedG`nTC(jR=XN|V_hj;ldzq7ALeoy?PEqu!NaF2KPhy4F)zvixOl(ud(-d3b>W@$(YWw`o-?oSOO0RSE%Uy( zU!T@zk^k4YCk8C~`^rDO%O~dj81Cg&--~&-lK+1?{sy+%qv9Wn7%=32D+YKUz}RAd z^RIFHyMeYZ5dW`1agTW)z}WW$2K;l6u<)N_0Cm56o)sF^{~iz0vx15Ru;1+mko)C* zZM0T7-{)ia$CKyRe9{*>#4qcBvzE(qgP#ALe^|IFZ|}FC=emyimHxi;_LubcYwMTu zJ>28zGrph~r1XH+x1sLDE;Q0Xe58Pxwz@8ECn!tO3)A+o{q8QwIfg`a%vEjP? zfZ{^!37U_+0QL#jeD^GIs(nv?UtqNXIn@3A4!~0b;Jp?YJRg+%0q4K$4|pA*vDX4?^?=V`+83lAILCp0eOf(09MAbb zA5#a=4@fuYY2;Hq(AclBAm1;{aiHhlYlYMPSNFFpTgTA`#oFdo%cLm;s1kW zzh2jS-aAnZ#dG-hMSVY7gM7}pC$Hlh|AYP>jo_Vo#>T$l0NjiHj(_qz+D+U)Ag!;+KPxcW4^U6w{ebbWIB;=40RPkhEByd_g7O{%7WV_-pFIJer#xmZEe60p z{eZ{jlK*eg50n@X{G+F=GaYCC{aC%K?nhV4?CW@)f3cza1$BR5^d0uj zb^PldU2o!_-U0 zMgFH3s4)QU@r{3a0-h5f4zLH1eT@MRg(v?n_%HoH><2tmZB+l~I$(u=-xKf{u;8D1 zfH*LkGe4JNfb)MEujGH$H0S$#O#W})_#f2*&ih~t`|dpsZ1W#6V2`i0*RRX}+V8vW z`P9#6{+<0Yjyb#$|Cx6`{&cKm9Qj_h$~_u}|El*#pN*OygMIf_ z3+Nuf$ITQA7W;+y{y?q?tQO!If#!pM?i1qMzF_MI^sIoaJ}*E$;LiwHU+}Sr57lz4zz5cOQ+tp7(L9=kq-HTpb;g zFJ%W4B`Y6_9=5s#hI^?-F_ho;?=VEXE zc%AviJM%t1a{o^2UNw+;_hZ%g`FWju|8Bv*Yn3s29`XN`%D=||#R9JZ7zh8K$T1-E z@AnEb|6U6a1LSo-pc>B!bo_Vy0NmrZ>wv~RbwKd{F^d6<{7-yj&X_~|h<}R#uz&U& z{2z$}aNm67ZRrgiqR%z&TF>K%1I|Vnry9U{I*s9f&ELzo(D&|{eH|~ZC$8~E^*r;8 zch*Pawff$ehkbeHUE|i**Kx-`^}T%7A;;)c9t{t;rw({j{A0Nm7{mebj}7y``1cr) zYXNEie2N49yntdr+aDwb9QpSItQN@ppY9KwVu0en>F)=u{)c~gpFif6d3Mj9K=?EF z)B*K5jM@kI+IM4W0nhy!H}0dBEaS>Q+FRqE^O^Z{3>$Fc`ve+a&i`6m(0PvyypMAn zZ~GDReooiHHI4jYzm$1e^WvVlUh}1|kN1uGdt&^^S51t#Cl2iH=eZ6wcFnq$SH_L~ z90R-_ApZ~iq8?a0FA(t{#{lXAVt{+A1ymDI2jI)|0`1x1h!6Azc|X8nz=Pfsr2RtQ z8!Yz+V}IZQ8S~}yNepnG=ih39Q(s`uTibf)xXF-y>z^y1v-f@UI&p39`qp0expU^*{W|*k)Xx*IiQo9jJL`&X{d{Pl zjMw^la(&j9YtP5%6x~*z_q%kgyoWvd)i=)hpBNy2pcYV%(A5IAH>ld+bg!WDuNW|j z1J1t2)C1NJcu&whKkR=$v=#@PecKmYy*~i|9s@L&%&*V6#(90L7Fh7Fd$c<-pwt9t zNFIGP5C8I^NzX}H!#ZBm``URpa=*bJb;HOjHt0Mtg7N6RYv&g6$G_KTymnoq^P~Cl z`P8`a&%CEK@wFP{c(})(?$_&Cos%7^@3ZFo7+pH|KO4HN=KRCFJo+#Euf>2-97r_) z{NwZfVCe<6dVge`0~hfdT)_hkAhrv?s8}KXc9e zYfa#NagX8VKG1KcYd><^ZB5PO6?M@$@OKHo3c>j_dnz15- z)%ic8d-mGD$7&97?f7`ac%1XPp7AQ{BfaZfE76)S8O&j@sLDtiv z9zcuuj(usDe3ke||KDq|0KT*L>-BNRzvjw#*O+<5r#zq6N5?I<$Jp1ljl23D?yGm! z8B<@%JO8zwhj;v_uUC%$*w06wpLOoFaO6YS$3IF8XmMa=e{d8B2E76OK#K#J|4~oC z{ec59!1+Jz4d&;Ec}@UdYXRmV&(9(T`akMF~^_xzu72Rd@^{MUXSJ)%$da4-MSUwGlX2cEbm4t(admLb#HGy8NCuljO)CZ^flDw0>=cYyNx`+*`7gB%F)x-3}=EWB| z_Py|X-*55ys3wYY)bs8mmgf1)$LD@twf&L$-r_uKp1o_tSpVoH^*#E`-gWKcCnNV~ zdn)q(uLtk4gK?nk54Jr*V_$KAdj%_T0RHhg4s`!70{uS;_VxzSzJUL2Vf*(5@;!n} z;=q9a-58)X^)>eO&;09{7$BeGfY$>WXMJ}3QyaL(8Mjyf|L%?V%+pqWy^VOV_51Z& z{yvLizYdL!f36+R{Fu-4sbin>6Z5cd{QH{un)+J%*!7@s<^IIH_VcjQeiJ!f-t+sS zpBMk+uUy|}-Z>8LAC&!4@c-{B{|DlL>VUF8*!Bhn`Ct12qrHLD8^AxFIMDz92rB=I z0SDuN^S}7};!zy%7^yLHNnB0*vlgt6*8s!*fOSU;u9ZA?P1QKyTJ$lx9rADbUgW)& z|1&p!Z1G@^&wKrP$Gd;Wl`*X2eJ%!L=F#WY#~uINuhTl^wd0s|#;}UtIrS-59kXl4)6?- zykY^hfcy6Ejt~d@9sy%wLFx(onSn(-P%Q9%K=a33$}`^{cNr@Nn2r8@fkjP#F31nr zXH5|cQeSZH6GD@rV~l*Z>Vpscn=<-$RKt$to>$hs!pJ-?sEeSN>+9c{TsgPD0}opH_UXU0#@%sr&PrAd2bde;6k zzY)1#2JhML)C2I3-@I2a>I+gFpcWX#f%f+VUJt-No*0ns6A%OF4_X|c7HDxGm}f2z z!~y5OjH4FtdVsp1ivwsyKJ(AwNF#SNLZh#5>R0%6IiV{1apGdG1-0?0p@L`z7Z!=*vYjq<_BXMQu-mt7hMR2S6V0QTiCdY+hC!1e~5f5v?sQ2T-07mWBP7I4iG|KK0L zhy$>XAH@KzOt)Qdu9^M5wtv3g%Ts+1@A7NRy9Uvv zJltE(KQS-vvCw?z9~1L#pZTreSVkPk`QMMJ1?1@qY=5BeAIGO>i0a=JQ1_2~-Wxa& z2ikrB{Nw2h()IbZs@{QneB-}8H&Ex5dG@(y%sR!Ijrb?FUu%CL*97P=`xXa+e`&eq zwx)aKy@9p;f_QEB*#hIP#e(JmOl*xrgIUqsm=aJX_xC zg8uIe&OO69R&+gaFs0*GC%i}F(Rb?3)i_7IJ6~72UgBdnS5{9kmm}|M;A>O)&N<$h zXWg66xTu4Zl8N$%s+8pw>Q|W2~PYg2FNz| z2R7;f;=r;ecr{|e$yyvB2FTmBmoagG7$9%^A`k!4%RT?Z+riqjIKW!wwT@%hZ+@{q zaJ~oB#R2KgbvV+g&NcRH3_$npxAqIHJ{D(P?zH235f?_E1?$x8XU;8dbuplF@7JsG zi2JoU)Onw4V}8Us=f-(HV*fw~k!-T?Ls|B3;Z#sS+SgnvBj<68_+O+Xy* z`G9|XiUZ;wQyoy^t*ytR9`N|hy4w0T?rRO8ywLXrXa1$DMGV-B1DSu4Ov(B#DVZ5{;3U5ap2r9 zuJZ5Nblr~fzw=WdAfMeeu7{mi=gSJ2L)%5qq^aObB8CXp~Z*X{jz+)t1;z6nhyfP$2>0&k3p^IIdxvR% z;OoJ^VgPYK9`^AYG2q-6uvd7#e&%M_7cj5P^GY1heS_|v0r4C5-J=DxAwSZP#e$q~ znl9V(;ive}_($L9UEVp^8!!3?_0Gm5_4TSBT`|Dp0P#Tl`~3nPr&s|0c=`f- zivhkrpt)pz;UAyk0QU{gaUhuXIxek$+ao+03)+36q|3xVdd@m8$C-t#m@wpDF#t0j zhxJ3Q$Mf;t&+|FeJJW09e0_;^pA+WEz4M-HdjI=bjXVC~oVAqC>&E~RO>l|H=bcJwKp5g2KOI0C52J@%cG|jhf&T1J3bJF`(n0Jp!LwxF7jg8|VKN11kUY zZ@C_Dt@s!%$$L#;{ClsNHH)5I>zm`u3^Ac`o;Ymh_xR1c=i|MfPoL||kG`|cZSbC7 z(>&$5^Lfo<=fA{m*2&k-$MEj!&p5o8cN6z8zv$hiIcMH;6k1I_^Y83G`fpxY)caj6 z9V~tRKfKeK;sN!*Fdnc!fLCu|{Q>a+ue#t+Z@@i+!5#s7hw`6{IDieGB_Ot~><{=} z;i)D#?~Rt&KrHZe(3p7O^?=59?@(jvJN!ct58PV5a1Hs`V}Xwcdj+R^3G4d>7sm*3 z<;*#Y_hKBM_0jlDx6E%o=9*T$_3ov9AH~z3S6yEXzVW^mHg0`^-Py0i zu^T$dtfRNYw=~(+^uHSYtl5ciagIgql=k6({D@EabHsnH3A#9-8Ugl;kGg;uFo*;6 z1;l~Xnt*y>ut(the=7O{*=h{nI=TK72fP=8f9AKV4c6j-)@dUKP}jk~{E&b0hP>%) zbH9*yV6h)HQ8)c0_Y*IL-x zH16Y^YIt8ujm!GlJ^99d>E+eW8~;zoF`T}y=Oe>QxxU>^qD*ZLW5 z@SlGv@;_E<0q37FHNoe^KPLX!H>`UEpN$xRc@01u=$^qeDc@cXn$v*G5q7Z zy}}gR+B^(m!8s1}@wJNqS`XI7*Nd^@fa18t1Ng`1IMD9*n8sXhm&bzsK9sY-c#9=? zd#}9b{xVK6%D+3;<9@4tKlSgP>&1IDVy?JfTZ`b^`*-+%_?LBG?qsZUtLepkm*d5} zn`uhgGUgY#e$mUH`2WPS(bHCI+<*DC;65xfK;K+F+~7a|a`2BS4h;GMVt{;OpBMoD z)x$oX7$C10(4HsISjK+9$VV&?^KPGseE}KYf3a7XuV2T+LB)YZJ>YvK%k}8ihxLMg zd0*=oqXT(q!};&V${m>N4_#(Yev+pSkRR2z>74W8W3lyKanI*E{~wF|@88eIN3K)v zp?`02H1nS4)#qNdJZna=z#tyv_|WQt6bHJxV52{HO#1_Fqnf~D1N#JR-|!R< zxE{p?Y8!kP6Fn9T;=!Y;sm#3o8m^hgfQ2RsmidADC7rfroxaFp-TaG;z z49AiGQaso`*89)q`S+${@x1-nE3fCe-H$ykYRtTt@8&f4_8Q*jUgN^K_wZi7YnMXCoBe@ng&x7RRr^NQpx2un|68#jGtk8WtACvvjfZjJ%)a7)=iuv($Dfl} z&);qBn0(%RFz?sz^AuxepZY!XPYo~M`ge_$*GK#t@2#J&ePZSx&fSyOu<)jMLa9}1gGou`Cz=@pLrw>$d_0U{3{03 zIN)n_tythOfN{}PXshzy=`V7S$ADIksxD31F2|PVy4=^8u%35~DL-$y|5OJgPKPn$ zg7^4mZH*h>sbA06JDL-nt9883CH#90?{i=3c-E$^Rm1^m_`H8tnfH8<@5hPztf5ur z>EXSm_xhc2Fizi#zU9R_+~Ys<>v10YcNzyPzY)w-6aQ|F|J)PQ7y$ox?+42MKH^|Gd!9gj!CG&i7=YFN0qPx}AC0^CNesZ37-0M>2DJMs z{W=oQeeJt3+CWDup8iq3b*87WBOC#LFQraICJI4;xYbC$?N|^x%-lspwIBn z%;UZ9pI;a2F;~qrFLo^E>D}d>{W6YuKh(dAecunZp550y^AG#3qiziIXfS)2$A`ZC z85wkrZ`_OZks14n0blq}cRPFX_o5bX``ve&|-(zhJ!IQ_uZ%+ke;J^M!rk)!<)NM163I z0g3~QSYW+DxsO0BsCxve6P$n52<#QeFX{vJ2G}4LP#54A&ldUIlyTo5C_efeQy&lm z@U2duKOhdsdknC>LTLeA;FS+tQ=9t*iV0b-)T22L)Z?-bZ80I&yBu#a7CDsHhdqXJ z?9rH@_ih-Uv*r2D@>~3uYj@Ut{3{U$im8t`BkorJ@$lFuqGt28D&w^G)7G^4flCiW}xTklQ&-zaM!-IUumA<#GaWLifh1Y_8S&akYzq86e zabPtTQ=AF;)PzCZB20NJ$&4g>&)lU`}f+PiTTddubXAwwXR+L&ido2=jq|a z{8yuotcJFl){Os7msO{&cUKQD*5SYE{ITD7Bc884*=K+A&2Im!uo1j_j}7~uRqq|v z_^)xGV?ScS7yi>b%{&G$zWx0IPAY z+O>RqpWKi8XXfvgkDYgM&9&k?)-(G$_PO-=u4DCVZh5^Hyyw~;?&Vz{jE#N80JJ49 z?PWds@sa-h#JspanR8LtNc^|Gx60Pje-OF<_cu&&A?*?PvG)pPytP-LeFN16gM9+d zd9P5$za4*HU~%B_7vH+V%C!!91fK)?pVjA#h^P1)?-xY8SN)fD!m;v)8Tmzi6dQ6q zI=ug!D_Dno;fF{7jOAob-d@7Hb!gmseaGL=ojzWUL6}R&JN?peH^m|wJJU9xo_|u!WeP3%2@oZ*inWr zdEer*tQTK=w|EWKpN{dsxb8L&X?~pbJeMVxr}=OEKO1p5+n3{}s*hcs>^MUoqRL-&ucoa{EFrrlr=u+a6WsojShxT)(659qvE( zA0iKiHE!VNrQpcyOaJ-qm7B)_jT`&K0D1*tKpcAv@cjafJqBo8*e3?iD_AUO`-FAx zkiI}(*HXO2fX02Ek2+8Ak5~Nk7|`|!Sd$cwwPwDitTWyZ&Z=*mN3QR3d#z!cZnM7gapfKE@y<_S$Jp22-Lrqx^Z%tc((xcZboBx8 zp!%mHE?{M!;MH%(y@fE_NA&TNy0=(uw3ql~#0FWu4vup@qdI3bCTPyAHyDkn6XZ{O z1`*qBJs*qu4J+$TjhFTf(1iC6iVe9&_@rt|v!P};VnnN7scVBBXJm_|_seUZv)gy) zda~b|i+IiFQsXX{7rvJ<+SiD6+w+e_UYEHxeEisNhE4{y&>Z<3pSiZ<$A9Ne)bDS0 z_W9txx5SD+t1}pjf446w9w-K21OB~6$g$v=s1sx@2D}#c5U@ckfc@s9Ch%tr;2+;T zW1yO##xvpo^$z<5yRl$*zuHTC+vG7tNo{f#sKt&Sfs|JfH}Z@yaNAFkmZU(Y}O+rhf5Fz^1v{}V5LE42Tu z6ZoC8s&g6hxrhhMd)+^z-jY`h;PqJ<2mhZn z^O)ahrN#sA9i%tqm9A%~{I@t@>?;nm{8jZ`bnd#(W9K7{VQkG`hui!!`rSIW_8F>g z^xA5jldmTq`#j`cJ@PyAh!^{x`nvABoh&o&>r-m?0rPELgLg6CS-tOuPL%UuedHJV zlP1liRX6y5;-xrW?8JTJKbRNySmq`3?tEzs`}i-t8T?~84(K@5102J@j$vQEyO%(( zU~vHU@!lKg`;|Qeivgn^LFb%*dV`bi=d<)h#NL5-{xxp#y6hXAdbCyktxqU%q1K14 zv9-Dqy?*Mss0AzrH2z1usm^6S67#E^yxXs&%N7W;ot|$^Q`C;`M>=iiR5O@;^6OE{hF!vhzqj=nZO=h9^SUX1 z=ykl0{oMNRi0|7z>w4dtYaaPFPu6q!IiF{ZSJ!1ww`(m~*Hyp6xa&*f%=hT{$(P@X zI{suMO{05xINtM5zxGb_mt8V9}{UZyyJc|7pFgVH~wUIF&;@ZWr@ z5yZdS7xbN@KPaaA3Dzt4JhXAt3O;Av3zdF|Ua9VzsHXBY%Duu95uas>0VNL5FW^&+ z0RPR~^O>jpgVVlI)+NX7`2+N_d)+iv9B>v-<8}7enZD#_PW&q`m(TlZ z{A_2|o9p}5__pRh^=t8a;yv=R!n*b7%;U%_cRyzK+24%g#C@*WoqIIk-gCydUq9Rf7~q~* zK;Ixg+&_p~0rrcJ`0z{^{L?qcYcKJcKX|vZ!oOlbHTch-xuM=^eUJ4Hg@5an)H9v? zs#A@n_3{1-{_(9|gMY<;#t{p$e&`FE5B*U?W-raQ{Dh|KJ_2z8-Me>I=yS7geQ?@u z_*AfXZ|$_d+3NDI$LDo+?#;!rv+dWI$BTK=d~M8ao9kd++&8m&eX(}&8mzPa3qR=B zv!?u*J{>RaVLf}X+;m;@Ue-Na;9*{@5A6@*yxBMY^lz@*I`$O@a(_^dTP(2sL&X5E z4MtR;2d7Ln68u66NR%-eQ<-p}{BZ?E%j{Ch5Eyx_gogRTo7JL^SHp|8(()@#t2?{azQ zxM+F6Jq(O|X8ue6`4k7_NA~>N-?`ed=l(S6gKCNe&qYj-t;L1sqF0cye~^8S38iM( z+(URbzIV0XkKW)9Pd2)Cohv;~ox9O|enCBxEcOg#T|6kqX}tz zx0kT3&&NuQsyg*#3;kchO1yVe7w?@DqkHc;`ue5cpXC` z;NR!js1Mv4`!xo@fAh7EY4Z|&g3sU65kKA9UP6rnd3~Pz-B`0QVs`G!oPCWI<8ut? zjWLJbTlowETkhoX%QC_p3hgpZBzDW4JrY>21DGHI9B?@%j6v__lhT^?il? z7c@`RVy<9&%|G$mZ$|BQva$x1^=_Sh}F@C~z42c-U?^$m4T(Z7qvsu#W--xF)~LgoLNxK>OtzwQJ>k<6iv#`!oCSz2-}-ju_C^CHQ8o+&AuPF4r2v z`p}zRif&#F{d9KTvzEr3cjtU*!~ode^TdI#MXcy*fD{k(nt!LgegXFJqn?JIM{`>0Bh^UNs1xwczs4S$eSD4s zy&k9&@UA6|bG_ilr&w^#P15eNK0WDNT9?5AKJ$N@$BGU7J|A=LHaypnJ=eSEop%`b zInLw7`lOnjb;LXKU-(b)?`15k*Nn4|#@w%QzN+KIyZHaon{NG{+lhH`kDd5hWALT_ z@|~;B{^C>*5D#9AV`6~(3lS4~yBo*nnqW6B)c%3*v$dC?_iXJYC{}pyaN0|Zxv?53 zkDsMyT8Rf9pPm1;@zYV~eIYc2p_^5|(NAWr>i9|ZqR}^CPyEgsaepFugc=XB|9qTx z%dbEFqH^ZFv;29N9{YDcjZbr+c_?#naQ>*%SeHXyYq`TWIzT7RwvVYxm3O2$EbDP} z{OlinJ9K@rR{w_9HO~1Fj*fU|?YZDk*0CS4;AOLyf_?G7W~VrC;$JbK*q?v<&pdeTaEb-zxhr!R@$%Vfnrqep@AZ|&9-mob_pCo&{JTB*d!bXe z=npIc*5!el6BE z?tPzA8E)8oqahpC@UKLqtM}seff`7x1a4Rp?^2@kI&pYLmFq!;9mY4!RgC? z8O)o#@@|R&sXkc7g1ZqP&i2hezZ?4raUAgg^LX%E@tJO0aiM(ww4cCr{i!-^`M_(9ko8bzImMW@J#5&?WtFzKd7cSpnURF_-3I=uPJ>j{XUf~G%wv_ zzxdLdS8h+lF($5@{WAU!`hPs1?1|qQv%kjjwa)7^!uU*QU1NP$yvBYl8b7tL6T`ZG zvFwcd+^vl})?;11u&~O#_>WwDVm|&|j6aBVEjHEzdAsC{&=fi%*Wl5a{MVvxhx;%8 zv7WsjS+C!Y{a^vz(_h1T=A2{r!i#ygSN`jFt}w*`@xNxO6}}m-_jYDqaRB~DUhzQZ zNBr~s-0K=R=6bWw@lVGdKQ-rTrZr*R@T_b0ISz2lz5@OWzaJV4BL*z%$#~sz3}f`W z$XC`|j9&9TLp*&xoAD2m_tO)b%6E%v!SlUG*R*wxd6H-DHht#3t`F+@=ryo;Q2-ufB67BL-yu>fLzVEXRgE z7HBU)J;l;S@j&OO5whnRR68&p`4@Ek!91q<8O4NlZX+IOjrzTn?Y#u)fcj3J7|{DW zasF)2ymlw}<*C)Ukb-Wz`< z>PR>~;-9YuybLsfDV#KAN>+k0}+^=2x==?K(aE_(V*0D4H>>s`r z@j2TmX0vW3hQ}JSp1#(3{9I@QoyZrytIwMH#<$*yd*QNgZ<=e@`A6ef_idl8?zfG2 zalXX4A2$wbj(+`bg6aR(o5zCJqBd}QJ^BU308I4)F`)R$KXJglKKqq-Pxf+*W!-lh z#0BD%{ODR=jQYUsw;~?6(FZl()`j&s`MiGZIM!V2j;Ve_EAr@Q-M=00dHbxx(5bY# zW<}G|a;*#IT7_ak;@x1r8Jlt}qZ;dHmXcj(H8b{~``8qLvKKigVlNNIS=J_CxPcfm_ zpftK=Uy9ddYr2P%>-gJZHUFOLe3!MK8C^GXGV*yYo%g&RqvO1uYirMWbmAU8jXXMB z^^w;O>AJoT_P-e{%f!hZOa8hzbUxqw+wWYhnc~75@j5n&6L0*tzq#t|ZXBQO_y4N@ zJpXKsU%h)a|DJ1Ok7pY1&D~28JKeq+?7NNDtUI?65731BZ>Z;sn((MS_eXcGWLd{+ z$0G*(+*kha&K0)C*!?))zC(>?{vCb1cAca1WuD%Qx$5lLTx(rqzE*qV7yk5|D>_YB z4$0@mx$7;DU9V!7BE0;O_n6%XixN&U@FJUw8JOyYtr7-aO~mwN~Bw<@L2==|Vaf>7$(s zExl-l)}*_UHC@*JcckGp-E$sB>J;D z`_O!|CR#hKSGR_P*w31O^Uv3HAZ z??ima<}u;Uf4>{A-)%9#+1L0^viv&7eE)oXM~;2ow&ps&rZ}KATr;h)G|*Y4^*dJh_V z<(;>$WE}7NJMrGLz4WJdu4LZ~K1OyaFYm|qzxb^a_wtKtw7KwkIgihCy*0NlN9>k; zGuhGgeD%MC4hA;T&hP)_JE1YG*V`L+|2p(|wvlEzhpyeDef)@NKbLvpICIGHxBo79 zH+%Pg{`FOFcjNeO#DtT<|Edq>->#-O@b-6l<8$$RKYYi2#DZ@{{lc}&``nzy!TVpC zCH~*I`|X(jlbzxK>ooSSM{JP27HjY3F~P?RJyrgR1FydG*44{@_V%j1^k>0)82W~P z{L2vwu$O`t&f~?H^ogwAAVy`9Eq_TMZz9clP{?oH|4Ei^9%us0*0ZW&zRGk^K`-Mm%xjAdMZixX{*z8&*r_E#}ByYpZ3ny^k|5C3Z(oybc+ zng2ihi?^;Y#Qs#O7ueFS0F2s5_CpJG@bNTw8 z-|1?%vOdQDD6Z#qhVzksy=ZBy8|Pn|!^C`NO}9Pg=sJ6JFE0*Y|69S#$TC}N408VN z-`%}BYFqph6ZpMH{o?yp-r+v`JU@5-CgwVf7*NKEf7W8;&+B$t*Q|$2k6|A#O>WsM z@%oyHhb?x#7hWsZxG@KLZnowU{x4lKf4_4t-m{G~v~^DGqtTO2Puwqcyxc3(d$RsV z%=@{c-0{Be1pjVFV?p=6|MBkCT`X$;yEm|R|MuN0w|9c=YujJF-Qw`p8m+$em+|w_ z3-op;j*r`$fAOs=w>zQrY;V5v<`vfIJI=qJP4VQ<;(WFDTMT$LzL#71p8C$%=z4f< z;1_e1=JWNxh~FLSf#tP%^S``xg`HkY>!~%yEN`!A;_bU{U%eCWTeG+S>Yb}C+t>1v z`7Q=|@&DJ++q|62{Px?svX@ zwQ5nD-_7>7@qSr8&vES4-QBP}ukXhEx#id2x%<}D!Xgj6Q|;})eCtZaarSqjJ{%g_ zi~YPiGLEy4y{S8$y%ERN-hTb+_1m^TiQj4V+PB`ga?@)Z`}^+In9ss5KIc#0IDKDv z*=^59?2o=4^N;mw5$EIHEcV7QTGm>QTGm>QTGm>QTGm>QTGm>QTG zm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTG zm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTG zm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTG zm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTG zm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTG zm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTG zm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTG zm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTG zm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTG zm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTG zm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTG zm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTG zm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTG zm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTG zm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTG zm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTG zm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTG zm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTG zm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>QTGm>Q4< zKJd@Juwzff>#`?L_VDMgKJddU_E4O^(0(A^-`PL?{M8RWc((74acBP|jxVw&;{CG6 z58CJAeYa^xK9}z(f9dt@yx-?9=GN!>2QOT!vbH}Qddc>1oWpkY6z_c`*^k6A_VE~Z z_H-PZ{pgpjK7jpLjDNhhABtnQ55=+UgO8rvdm%{Q=^D`wL@&Yts8wjTiL*^)3E7IkNj1 z=W$dY96iVHxn-Wa?^^Ra*PeYapXbl#_SiVq_aQTSf0*yIaG?H2YcRjyJ=Xx%?|a{` z+`p0k3;XK*mGeFCe^NQ$js9P`Ka75#p8rU_Z#}=)`x-w`Om%;?T<>!X|DNkd^}h0d z$NmeE_g`%G(dhTx#Q2uAeHYfn{h_s`Kd}8luLo`z2gp_ExbRO-%X-&w=HudHxIO9> z54PTW8{cW;cZ}vh=Tg4c{An(aG4Jm)_8*G17XPp>e@G+tKJ;~6{^1@UjQbv-_WodB zzGJ`C`_8_`;=i)*{8RVefd5mzR}9$UANEIi-|PL%zT$wfud%bQ@fQ0n|Fag?ss-R* zKD4s7FF+i)P=oM~N7tQq1}^43v$e(YbzaNA|CW#a+KqeWhq%f7U7!D0U-9qkUzdN^ z+g|=B=gY&q`^3Mqe~f=)AO2zgXuofJexCnN{2Tj<1LS-B7W+f9fLhaF2h_`Oi8#%D*%3<30ZUUf(ePC;pxN zqx@UX5C8Q1UGC5P@3EixU$DQP|Hb|x{`36%`cSLkcVmI-JdXzl>H)=t3u^*@zjRHG zlP`HM=fth!8^?=V&gXmIXX{#md!Gx^C`0qWis2dE9!xY^_F{q}mk4$JkrX831b z4l~dEH|~i6vF2X$@2-E+h->G%{BPuYp7o*bZ~J}h`CY>Q(R|)0>#zcbGm z_T?|(ztsFY`+ZyND;6005B@^j|IhIk=CfxVch~;_|My^j5WRQ&I~$oBj^A&5?|mP> zx4d6p9WxL38}sk%U$@_vcFWuf4JWV`@?$QazFh(d;OR2-=6O~ z^Z${k1IYXMMeg5Xf34o{>i+Xyf8Osa^*;RPyw5TG2m2TC&-&dK|Gr0XgE|16<3~(% z=imm8_IP{0y)MqVhI`Hb!>ZqIA^)Qt`33L!+-3Ze_wn~_zd!MRVtZ zEhHvv`P+^MTc4F*3*Phh=a}ofINvj`ZO*Ct!;AmJ>&p6uM-%dgd9U2({r*9|_nN;v z-%r0Uzgh1q_eYN}^Sv)-rf-(Y{O{)c_# zd}CkZ!~B!`FX{Ic1C0G#@0a`g)cb3BpL###{Re}6S!4fb41oLWS@(*KY72fzkYvD>|dwfFSUPVzwrNKu}0XDULbSt$6E8-;-5HREMKhc_}R=y zJ{~bPJV!jZ7x{mK_xxS*_s_={&k=D}zRbB|vf1u>rZtCuba4^?_3wMg{VV%@zUSBW z`?=4*TL0Jk{M7xKdyaGc5C65_&-veL|6%>V#r}KH@00iOANcW@^JYy8HS7imZ}=fVAf8^(qaEAT{}z3|GxX8cYi(I)_lNn9J$>)_H|l+Hf9=2LhkM`aALV=ahkNpV+wVL7J^zq*?(xZw z@_lChUibUI_)^sQ*wBawAWpFE*Ny|?A4~fKoo;Vue;~61 zXLvY-MQ49B{z0*AHhNEY&iLmu@&4JI%is6nm#fBJlU>i3WEzry}Sf6q_ef3N%f4fYiSR{3u+pz?o{{Q=JlyZVCt z>gfZrN5{d>#SCox44kd`j$P&NWX`_E`SpCf_Bm_kZ+xxHTl2>LZS?!s<)51WQvRv? z@!j9^*Zuyr+`pFp;h&n{{POwU$n(DEPu@Su|Gn<_8~^8=PwwC4ztjT3zxD@i!atf& z9Jnq1i3b}jUx$@7?l_vsWGt5ErH>~%yR;(Ky*5!|Aq4k#9Yue_2*zbGI zzu51KdVlL4Kb(&|><{uidB4l`qkq5aNWJfW$6N02-QxZImhb8LU%7hl$rXF*l@d!k z-c$Wm_5wTxcr9?WCU8x-R?rXrP#p07K(#CT*3~zi*ZqNf{P!&u(RJlKlu0DKgj<>{>k<7Yy7X({3GslZovNcM8Chm{)3f&>ZxHI=-AiZU}yn@ zfBa~@^Euaw#;zlcclGG}$2jxv$65P2{`<`TZPxp&TVBt+-uc-1FWTDA|LC*q_c{A( z{I9XU*Y6K%e)2x|`G)y_tLA5aAHS&iZ})lrey>03{>*i*jEJO6Nx5B|G<4>9_xAF4(i;8|-t=bLZq$UVb| z50}&fSs&69u>ijt6TEIveUSDD{Mci`X#Cw4A4b>U=lt5seN~^UgJaeK@66}*8y&-Z z@jdV83QxT+UwBuqzuE7n9svH?=Uc1yS89IpJ%01w?>n-`m-GH5{l4$(G4|(qHNLRl ze<+xjJzTBhKk6y?Z$8$6wUKY!Gj{$phJE}<4_oKZ7Fu(EC=PgkFo*->LT6+&Rvggz z_j^1TeRrMXIvfAY%R%OuYoC9OSs(n?TI#iq{bj%J+R_;Q$@?Aq!+O8+{}XYquUNg` zzsLVd-iLenQ@-E$_q#^?!@GO({%!vKK6yX$@A;lF`98d}uNaW||6t6q4F2_ZmA*#1 z{L>T2D+cW8BfpNW@L7N8a@C{r>}xE53-@abeK+#&*QIfLmWug^HCdgz!D~MMtcm-) ze)-t>FS%kr|HFRE|Kc87?Dr-9;T}KA|10cM@5BBk|J40^`~9#lU-&=J@006Cet3V+ zYJQl<8~YDh-f#I|^U2(XhyUh-e~$qd@vj(=^{{vB+VU|v+|@2RU-6lb(Xq#a?}q$8 zx(3eO2LHYedAzrt@Zajhb^goW@JFrR@o(BJI?epIydU}hC*yuU+~YI<8|=e>-|wgV z5BHb-~o z)B^Mb*QpE88F9eF?pYwff$##jnw?{lM7gm9aC= zI_!D4mv8Jxe^@kf?jzAp_wRRj&+l5yW3Tts_lx<=yB`nO_j+IB;r_lq)1UKwW}dpA zyuZ`$^LKo!?0dgom?zic*YE9Xk3aYOkNom0S8gBriCRD9x#bxCtB3#Q7yW_O*bM&h z9s{lu2V9Hj6MrZUI2(B!43Rh2{B3Z1-QVFciSO6(pRY&92blNwFqe3rZ;dbEKC`bl zFyJ5dykmha(zz&`n&7%<%5hkf#X<{#eOH}+NYpP%i?f4_ey@54WN|B`+` za(?djo&86CCHjBykHNhA#6Nw(wfWC+U{6o^^{hv9igzs!$0J7U99$js*Jb(0&y9G` z->dL%-~Tr1ea&T_XO8oFuWsd_ZR&Cr18I23)q^#fAg`PtYKgO*BSuc*L>E`-Z45H zdFgh@yr17YpCcj@lyWl-vpI`fd@fQ1<^S$+fe|XRSHu=9N{eCd( zf6qhSf8X`{8~oQ^f0h5p`^x<#|Cc$`zKQ-;LNV3if!6_RV`9Ta`vY}vU~7NiI=z9k zDUDJKWWO654)D=<^M3n&jm7qn?|9%nd2Zl-kMGgzFpoFp>F@E}-;@8x^!f7WXiaCF zzs~)A<^RFo?|a?Po?rLx^^TbLI^V}F@2}VU%K0CSy#6t>@^5ug<7YqW_u1F4{vq}F zZYk$?`Ch&Mzv}I@XIl9$vB3Bz7BKfC&$?CL`hr+f^?Iy3zS|pc{;3b{<^F(c{@NVn z{Ho)7#sBED4zRs-Z9WG+rk)z{y>$-e@y5RP{aANA{EPj@ziViuF`aAs{0HyvtNy3% z-}rkTxW_mC$@_c#KHSUi+}|(#zOk=3(EWS-&i}!F-`QWy_w4y8=O5u;JwfgZ4)g|! z;q(P94x|{+^B+9|+-INTz|ndD9cS-a_wg1h!`DVk9zCZxarC`w@4IFG^E_>_K6-71 zd-nX{9#7tH{5$&^@9|HckM};GG2C0;ALW0TcVGC2{UiAw{ty29+@AYa`ETs!=lhoZ zKKzS)jk&LnZ~32^Kl9I=wLYM-PYlR$Ag@naKlqnd4Dh`HY2Yyb9uGVgj5O%yvYtoB z?-~DVOkZ;TmiQmdi_ZC69vO@Ol=J-{(i3a*J^&)-?_g>zK8cmf`8e=QRB;6zSnrf{L^vXto2B$ zl@|Q_z5wGvZ;)fQ9~0NBZ+$^%po~Kot{K--_x#XWJ#e%ySktw$kU7c6_k-P=en$Sz z9Q$?pcx_(t`BsBf{>k~qzShpy68l5P-govjrY~x7pymEr1GK&% z@lSui9^w7oA5c7KnzV7&?cTAo;A6PS{-%81>z~ix*RN?b-kYDr9H)B!Uhv=8SN(Z_ z{}uNA{ysH++uwtG`8D>v-WUI{-_`ufdf)HuZ`S*MZ@<<2JlB`*@1=Ub_4_sNH~!%s zzs3JE#y>Sv=HKHj_Xl7fe?biJ{ed!8U06-|qR;;u`-5xsz@DD->mw#c=iusI^Lx|Z zukShf{%hy*eB@*2zs$MT#B7Cs*3@%FPWBjUkr zcrky|-^2I1$+hvb%vmnP9Dnho#uzQ(Kd zK6yVgPo3|+V_)_Dx#lnT^mSjK{~wR~dpEzQ#~AKQ&i8zu>iwwwd43;X`u*JJ)9a78 zhxg`R4enn%*%x15=bt%cj$0h4eZg8BI9d~A{%aiIz5)9L${8L9oPCWK{8JZn8m=(` zec!~x*~s6N;Y)sg^!Yk>lWWt@=6Um6uW_l-&TF#h_nmuJ~D zcm9?0H|u@n{XPC+zun&(^H1LI*dOinb?k5S`!(moJV5cM+|T!%Kg#>W0Qj%MVQ_L?0_t|gquYN7@&oOZzufL8>C$QhV@n7OV)c9TlT(n;xZQqm+XYKtszUlR8 zjvD(K@AD7)@{4-k)^?Nsw$FT!|Ex4DW|!{|#5!~_dt-lKR1f6% z;QAr2m|xbT=e>c8`-9tv4>#rSe*8Q+zWCbT^Bm94{ro)NUB1`4!Srrz(glC@Kh zsXg(=zSsMk{r=h>AH3tcTrb|gd9vv5AE|N8YSw~qaY10Ob199ZR_HDFEfi}h5GU@?GtK%SalD-JmS zKCUsqdxLR*Q1=DdBkXcp&2=3QagO8r!Q%b?46Y5o$nWZ>&X)Uo_-Ec5|E$d#?|vTk zmGc*R8N>jzh33pV|GWJ@Io>_o%PZ$+{ww!lAO5NL5AmPbZ`>RImh<7BoWJ0oyr1iP z=U(HQ_jmdK>DVK|jD6yO`U8&vdB3GQW}W2Qx~4q>=b!Ob96(nq9v$MnH#p`0vzAw% zC-Rs87x??l=e__Q^0ncS++;pG&+`+L}xz{}2@87?;{^R_YK0lc6_|NOIwtk#% z`+T8`qx`S1e{20d?C<6OsP~6CpI#sSVP78Z@y`EKvEL{Dqrb<<`}mUobIvFC%RB$x z>j(Q={EPj=hfIPK;yzdLt@kiu0_6I&3YlVHJivwO0_}F>_^$EEz*y;_?lzZus zycC}Mh}5mw6DRPi_Y=hpxpT|kFP@1_Ti->m(f4%TbNu;Up5`Xk`{jPV&F8iAJ>0jt z&^3_8=tf#f^(XmWez)E~a(^$?`}FqiS98Ai_>AG)y))1AyfyD{*8ASy!@Toe#&JLY z>GM7PkH?;#+oN$@Ec*UOW&ftP2fY9BIN-6s;(+*fTda@quQ*U@HP#c~X@L5m#sO-9 z>&AhUkKiAFBmO;iimjtDAv1bOjL1KGI9sZsDG`GY3e2>MYuZwks{iFFF zoow)buj+m0o-zEly*^lXPtJGlHSXAto}YTZ=6vx_u9t89{#wq@{PQf2d}3d5pfC^n zk@GkC&$R&Yz-j_#zl=V|7|_n+7jaz&l5;98#dCLpG!J*{>wPD?EUMl?iVY9!F?Q{>atI2QowcNkw<9t27SJI8w_+FdSwiepMv7EnHPg`Sh zfaUz9ULxnjbG#oZ{l1=634OYaG^ul0yE>M(+u!S-p5ul0=7V$pH~LD=@67wS<34J8 z{p~*eef9MB_=}$3&$WIW=lgku`Ka$dUai&pTiibz^+2`8J@JtExE2TUd|Pj@;9q+M z!M|*|uBUUY9#GtOJv80azJNNx`Dfg;SM$%BMn~t-xxDjV$J7dNBmUiT9C&Z|KiBn{ z_k7HCuphFxW}gp@M|0-q^4vT78n<;c?sd-B+H(FK&#e7MK2)%oOmxPSD^5d*>&{O`qq z%s z|KWa=`=v?eAI-X!JN{p6f49pXpXYv!TmFapuIAVMeb4{(e!lpxeZKNO?3ezYybt^2 ze7y4y_wp1L$z@Jc0CzcHLjX z0sOD|#2|I{o+m!&eLmj%xgq0xhtYSufEDiHKgR&gE%Ut=2RsI7oZ^7<@6QuZ2X6KU zt}Ts=_FRiG&U($qS>JnnjF{RuCw^6D?{R$H*9-p}pTDT@_pUF$?(>u9PRFe;W8UjJ z_}X~v9`Jvz^#}aJe%2Ak@b5ZYVPE{i{V?}?{-@q=eZFe_Fz289`Fr&~wf-LeuwQHa z^8D@~=Tq+wbN_*y|7dWJwYaBiPo@~~z!Sl&j2Pf^o5#KetPATj@(~Lzd7fZc3!p9Z zw$S4sXN`2MbJBX&|Jw0E<`&N%#LwO4xdq>Q-)rr9*3PfZ73cGOvkvmCjr+vEv)|?a zmM21cBkgcbIzx9^k2<#8|3JxWD|J8GSN>mP-}%3-`}r{6^8LBjcm81>KV)CIpL}mU zKD9nMzp(%HH?JNIE7$5ZkB^xX^SM9xXv77q^;*6T#DVNtzn_VIpqcLPY*=WAx?s&0 z&AA3O&NXRwY|j?3UqCGI+;>TASYzq^cs^gFj$vEA_4mxv%6#TIugvFl^1b}i{k**1 z!R%Vj&l(cA109 z<3Nl1{XPM4!ShAdmW~%W$936pkB(KhqV4dmeIJiFc|Y=ev ztFh<$80R(G@Xo$|s`ud@-}U#}-y7BX z&OGCq_qX^j{r%cKJ?i|O9^cuAdH0LIPh=iyEP#JJaX|hQ3!V>ujsXk)JwCH$9t$%6 ztuBxbA_ic!E^z)CyT-Eqy5p?XqG5DhJ-XlXYYZLX@4b9a{@#1nnd^JF$1|sid*+?B zVXcPzv&Qni{u*cgT|=&|I)?vqo=SafW*@z`yl?C)28e%Z{qS%v-`&r1_SxH$hj%>n z{>J^h!~1&&YJKtVyfgmHzrAxMTky|3)Oq>nFT|cntpU6aILCqKqc%9j0L6h#{#h?y z!?NBr_m@}@zawkF^l{eF`R|VWT-#q>(IdKS&z~*yt(v#m9vl8TT)mfUANg$m&U@oB z4=FyvJ$`GRqaO3MIgRuEe2ejU-828_1|1E&_=o+oMlA-wKitcs`JDTIQTOw!ZQai& z=R5a|;a)zmKm7ar`W(+{pYQp8)Z^RUp89)u$HP7T!6)PTN7l`W%@OnGKJ5=M|HR_n zrx?MS%I8?mvFpNO0X@OchxSJI6Uj&T5)a5>?$P(Ix89-E%65*p@Lu`RcN(69bNV9l zHZS@&9r*VhieK52f&1awsFq_LeQh)EtiNj{<$2eZbhlvMb;=m_UE{URe@Hprtn~N6 zzWjCm|9X#nG5$8cvm4&e`%3KHcNTjD^!&m8C!@D_gY(Y+TIBv}5B^fH{ZzG49MCzR zC+1F``EoqavS2lws&ueJ94p67YS%J&`n@o#Z%+|Mihe%|9-yhPhw4 z2yMbX+~b}9n)i8jxBPu>@DBSW|L0mC*4U(^5KW}qyFY#~etKJv? z3-&GFi}_~%F7Zzs*opG>+ zu*+2B_j}Fw*6-i_ekm5zJij&<@Xx&E`OL?y&exjw8m*04@5DWM;xPB<5N*0%eJuV{ zk3ZsH|8Kqa|JS3p&-;5F_sadUGy59*-rgqrt9yIS|LGpSxPLTqylj>I#QkT2eOb9* zK}{3>A+ysw)Y^d9FsciDKACTMVy}DFrF!Bw>q=dKXWg4eALNOVcWqfLXt~Jhg9m;} zHEK20tI4PnR(-?+^~BhsR%mrXt*hV5;_lD`>82=FmK4PX=Kt5Y#BKUWHy1Y5yt&0!$Di-X0 zu3oQoy?^&D{+WjsD~Xw1EVX?-&9Tq@+8X&fR`y5SH+=-}{x^Du*hibrf6DpP_#OXn zv6k;u^ON)2GrP#kGR*lTIa((zRUN{J^6lx|Hppg&Xp|xJ<`K*%^3VY z^vkg?Qf(Lue14sO9}^3R&-hdi_`SqYEa+kYxxzJ+b>pJiI2Lhx>QJ{bU0S1Bq0$jC16}d!+5Fx^<70R+D%CaWPfDUbf zwrqm29D=e{I%;VXv?vpdsSvbj6Q*%3io&&M8aI+PxcvDShD|EK{p)BqaR0Y&~rKZA9F zGGB~?{aQt_raYPwmROg`B=PP*Y}Bc#XEQ0vu?7E7IT~UuQ0D; zO8ztV{FYSYpL#0kjr{X(Txq>e?&+JgK3Ok$au46l`Nz1lzdwn4t^=^Qm;HT@dtK{u zzOQi4T0rjUAB*=;8~l5voCDNG?1@^-b>ZO`@i&yC75|Q)|@K8%~}?c?9>eCZeC-|zfl{G0RE$bEI4uUcwYm;F8dp7$$#EBlsT4(NT0arBRUg74<@E)KrCcS~z~ynB!RXxH+mxkfzTp8pQyz<)R23;U=6Rqmk| zvftBG%YVDhSN>v5(I=}aKyv%;--|MjsY`MP=-k*fE zYkaZ~uk1hb#>esh1^RAh4gaiftOf8|2Ur7IkG%m6H30mpO@1%XvE!JI)dJZYl=cX@ z9;ohman@%|$GHx$%{tJngG(&+S!=k9dB4czxsH$V-@HpO-Mw2I^PL-?YCz7L=2e(y z?zJ`}?zr#v_s6+^z&~|C?%`EGh|~Ta{DXTM*R6wZ`qVYGuKFhv;rr~r*Zbez>dpV0 z?@!#{cYFIKYkc7z>^~WEJ=xFPgZ*dT{G?-_b)dAT>ArCyC(KjDEBlCh{FlBL;Fyk~ zjxA%3JA8qEJ;wb3J#{zfRkw(#>z?-8wQlcNaF6c?@(rQ+p4ip}d{5B%_M+^g#dfx*7n!5t?n)?Iu$8S2_`o7)gaotbf{kQlSd+I?wwZnBL_?JCCxu>sk zPd!(-Cl}=ZE5G@Eun%khcQ-lL!;}57?|Pi~)Be84J=vey+ZVV8`>-DOXb1CZdA-8- zJ9^Z-;J&SagZ%>QB|Y;p>eo9OFrA#v9|S2tfd!1&lZ z@*MM0;9c{|?|JZ;>ve3pxHN_wXB~U8u3lIN_b2b~p<{8+Vt^Iy@v0ne9b{*>){;d-BHwXUdu};t$(5(%~zk1Yw zG5*`XDPZ5gb;2W2$7!eoy|n?_)J66s4aKn@`%wqDF39_YzGj1otZV9>o*c~ay}FQi z%xi7kPn;CDYj)nZ;~dXTb2r*^`j2b)-t;LyIe*9Y@xRCG@n0FQ-uROJk`6fE3;+0r z->vTvtM)h_sXiHV&gUH0=>Ek0J-LSu-VgtqJ8c|o{rg?-TR#7WH!;_f`_j7pDXi(q zKjLPMUuPe6pj)e;cC~95tpQ*8&G7_w z)&b-ne)fNxr1e0wrT^{n;lE!l|822W4+eEWbx#)P=dd(jFweiTpWwb?kI9W|k~ zratLA=su>2kNh`vAk}~#|FU-KY5|V1&cbsX#OwD%@UO;oLE|y@9Czx1I>}!2q%}al z73BD+*~9)6+8*Vjn5{AG0sLUphwqbMB70jWLt`Sdn>*RUSXK zG0tRO$G_D9|87s;%`u{{p61AWTI*BC;J-irrTzWX-k!|UgZuyIXdU<1+ruX2dw$1n zzUQUy`D7p5)4IKViF+Ia_un~M_umfldxX{k#C+ebF1#D-grhz7n^7m#WWT_i_Po{t z#yVEN7T*Wd^m~Eq6Icfx4o&n7dZBhoo^^p<=Og+1BK4d157c|jPt}Vr1wU$Cy_(z_tWEGzQ=ZrbIoySOmM8N=Z}Qf@#Hwu_ty7$uIKs^dZM=I$-T@`hdnLV z`3LU1`MuM7Fo2jGkQ4Eb2kV`E6aNkV(R#ekoa=RsPsWQrukW$PFL3{CFb{h!xPSf+ z`quTkeTQbvl4}6lO%C3RT(lN-pvQjH0jv@Dtw4RR25>CkIX<1ozCde0yH{xI0QU)< zPyA!g!0(5H?+4Hsxu;icHt`$rP4Q@%|2)qr4`VSoQ}z)z$I0fNp3kcIvA*+Me)mI- zjGU7DK`!GQjxpEp%74o|*@v&O&++c~*S$UCPD7$AYHh$hb=sfrd;F7mcrqZ|W9}qB zPsBVAQ}&Gg2A{pVk1@g-iKjAKSl?l-`Z& zqR!7@eg^kk<6{gt*Ta*0`tF+^bpiG>_o^x39@NbHQLz|2jSxH?~;=IL^uQn*rIU*KY?6|NLG6T~S+F56FM^zZ7)< z7RR*)bh=;o?+`kcu8{w(Z}Q(-;MlBpRsOpgn&x@Ut>&I%!g1kPi6{3Q@7`V>no#zk z9n}!EBwn?MxW_%$`BU7t-}3NHkKA+|3H#0bKk_%c=HKMw-|4{O`yKziPH)a{m8n~n>CI2cAtQCPq8*Icl7eDps5Y9 zM)<0j*5lIpK>ya*{QE>5Z^WrC^!E<(Zw;`25M6PO(e-8%V+{(8s%AA-jU$$|!XTcg z5hcFB-PoMG^xQ)+mp=rSX-oRlyc^!JVtHQIJhhzbUaZM$8iTH0xBI(peb2pp9^X9v z!Zzm))s$+jiEI0MuJL7lQDzGn@v4XyOP9pF1c>?JyF#ebI&dj(jH|IzOU&~)aX>je6-dXUe} zV6iqg&plqMF@s~dU%j@lpWj`1N8B>69?a{WjCtqUUdN`1xtCY&Ilk(-#&7xW^djG% zoB3VlnnrizpL=`ibb@>8zQ_OX#JrjJ_QE@Bfqu_Z_7Qjd=k-0-`H6YV@8rHZzk`4B zp62?@{|kTkY5aEr@r{Cp8u0%H|Nri2A!?GUKxEMR$#=OXrXWp*jtX6>mV3-o?_j^-KG@I9qs{Mm!MT3V<2+C9>6?B1?+5>#9lzl>^FRMqAM^S% zVc=fZ{N$d#;~jf^S?4nb^J@I=K%dtq!*@*ef%^vLCwcB0IzRdSAifX0+M3jYZcKGO z7cuKT{Xidi|Df}U>Wp#TKcFV4wK~>1p>gedLf!{Ad*KpK7dlqzU+I{>Ycb}WPM`D1 zJk#rVM67$g=6g@#oZ}1+<_F*J^7@|ZPw_g(ce-r9a}DM<>YKj6ysq!b`M`61pV#!# zZr1j}_p@TkdKc?FUtk=pzwn2xeK+QNv9JAydpBxNMZbC`-zJ*13ffWAXnAcg=lg-? znDwM4d77^P{*YPu?)Pb<4qfao_0Io|qkNt|+lfR4a1(A==w*Gz) zeE(K!;GcDS;`;%#KwZ!`bHv#Gfwcg>)1`brz#by@3O!>zXlnu(7`4J4Sb54Z`J*53 zFD##Oj`z*aoobW&{I1>iyN>s?rbj*HUaawLt~K}V9FMgdzw^*HV;J*1$Fy4qa&5@( zc>I1Z%tJdh?#aLIZ5#gc`X20aZyX-nD-Vo2?u*Q)^*#6Vg}>Kv9Zx^KpFhs^WL`b^ z2lq5E-`M-%!;broLmxZt<2?{}@74ZyMcgMeb)cyQ!awqqz8#>gIUcO9V!zOhMbzSs zebxZ-pFQ_;)Ir5lNA!7rFlhI*THrs`gNLIIsEvJ3AVcb>xa;=S++VNpv3D7Jp7yI2 zga6+9PVM0}pWvL_3-dY#94Gn#?|Ga#*JpjSZPgZI)u3v#;XkkMg?-ck@(!QbXxg0X zyZyYfA2Ijy@SE)Ck8?e_|Jr}J8}t0Z$UQxnS9|t5AIE;xEP= z2J2znZ{XX4>@7xPy_PZk+h{tf-|N_C4S4Q5pTwRd`rVqoPZP7g(R01i)`ZOe)3HW) zD)tJr!TKPN2mkFs`?m(xXR<%=@;wnc=)N6xbpU^7lxU#h)`vCdR|YK%IN_5GEY z&tQ%An>_^9y9FahnXf@RY5?~bB|f(}#`_n2^j-VJes}Mrd!^%hcX>cA#G0Iq?BlEa z>zHxx*RtQ^y&Zqf?W&JvKF6G*n#y_;{+m60%RP16o8zc;YCk-=f9~6#+`z~UIBHlD z{=5A=_ww+%mrwk6-|(jP^IX%zr}zQvy zOhX=UT=G%cKcEI=>*|57ubO(?t{2*WgFybX=QwlR;T!(BZ=l!tqR~;zC9h&WDVWSZ z+;7se4scyck2M3e5C7=Pv1d^0xM59*^SUP1xH#Wd))QbCY?5Kdgq{b^$MyYRBKAyh1pAW{L=QsP-b3IRwH9gny^aI|(e{fFAypwx+tSyxP z?r#k3nxx~Ob%5;Cvo0jx<*%?;&|08l(ZqRwAniQ1XzTh88aVv-3-S#y>F2B82>q#L z{pIyRYLk0JLh|@^M!rX0CGc9!ugQCSR*Q| zo%>q!kz98^zZ>6sre`kck9lv`Z|>02{P*_pajvQTNA{nN z`JDU8&QI;@PPocXW(dqb=j4mBY1*76Updj*X<{`K2pVJuJ|v=(rkaP%L< ze&S%L0UiI0MJrr0z^leI?#(y&c8qUI?OX*v;9tKb$@NZ^ZC7{t^7an z+F{N|9!q&{axFEw882{O8ABXb_Mr*R57dXU-|%12SThe{J)7q%{WdN!*T>l_|D0Qu zF=Zcd$2z#@9$tLMea!V>e879!&nxR){89Y2HDw?1*ZwH_AokMx$A3GZ{jSAa$H3=Y zp#8!-A9tqw*W%vZA;u!F4+ibQw}fLoKwT*9A;$E?eFS|?r#$}nH)CyEu`w=c=N=E9 z=32A1Zm(y|xf}Q3vGMyIzi(iVQ|s5%apvGjtl8Dt8p?dBXO7j^#)jiauE#w0md&@O z0`ID^p$Z&Pr#aM#M-qP>wu1R6aW5)H_wK3Yl4aI2~YoS)QDIs(72CKO={fxds56zPy9~I zX|0igQ7_*Y4ttE?{kM+xM8vRhX16@w=O29n>)ihge5Ka$ojdlqR|o%$!)q>cjNEto zcK(}@^ewo={joe@r#K69q?j1@o>I7;)XQ4sXfRfMrbG~8?nDp)U z1%79E;+w&{TFXD{!2bTYCdaxQydV2^_!gpVat>d5S7p74xo>aR@L;}_*Y4P0{~q6b zq=xtYdwJ!)ja3UxJXp`C{VCX&?^$48*YMqUFx?ZkF<4jKBUUyLzx>Ca^(=q0BmXZ4 z@&0PPzr~zVNUhV6DdN=kJWh>PHt`kZ==c37ntQBvzt<~wcjn}Yh0y(w!0J$TXe?;(CS zj;%M=3EV5kJXU_r;Ty-WU*+SOdA{3Ruen4#%zNzlWKYL&g3n`!b~o=w{2bPgp@HF= z{&-CF)aVTC%Qr1$KYzbd{W7NJOFZCM<{$faUDJ2mxBTmTUWzmSWInI!ySA{8I`FOU z|Df*IxXJsrC2|}YVWW62)HXa)`DUjV?H+C&qw`N<2s_G zrAAM0#`>YPp*Bl;?H_vz`-#@(GNISyHSwu!@XX(8%wsjLeVK}}XUf;`-RY-uc;vmd zU)QnX_|cD!J+vWn$ss=O>7lQ$1^bO5hE?V(9Bn=#XRqP5@{f7V|gd>Cz7yf68$d0j(jE$HG{ zGriOra?E;qtOKzp`>xm@MjXa*een>SS|QR4JaAv0>_^wH4~ z$2|4>%$u<;6?^)PV0gqbBjBU^_JHpYsRo?8P1bvTLcdz;@!b4bZvTwV?XfwL#x$<%+uPo?x`8muf2Ea_xfI?_s;pr`9r?P_Uwc4N#ppn zXsdzwW*tc%bA@V#+|y^h6`7Ck;Z+?A`=|kAoxJDu{E@36>zTpd2G7Af*`DLc{_DYa zW8j`~%RJeKf2~z0N(*9d%gu{eM4O(tPij ztmQLP${OeLIgGz?_ukFXVm%*>)0Fi$e|Vhd!TQ_Le*3Tb_N^aa&u}#MgP!|~&daw5 ztpytE`@iz%pT^%L#r`b#E*SIuYOEQ^Km2gL75juZPSbj=<9v*Rby*)!H}qfoZqx_0 z!F+PqTa>;r@Yq-#cp;cj10!nBMtju09juEz7jZV!10Kt@0mpfav3mZFUZ2&(6aCY7 zK@9HIO1WX)N?zA%FJN3S9x^^0H~P%{aoj^A?Y><#lJ%roQ~qCp4%_wXD}NUH?d-rm z=W*4#GQe1wCqrbb9dkL%ztw}XFb37$IZe9TR+ zs{MIrw`cu5ylS1XGBMz%{Z2=|elOTlQ|8|c)@g73<-ORmNBf7_`s`=E$vynLpG6(` z>%P4n_v~5w9fAFu>wE7kHA3s(A-)^mI1W~-1K@wm$Ui;T4K)w;)pWgw_-lU@{Hx_! zAZrI*M`)~iuB}&V$92AAtbCEfOL^@JV)l_9%-7xx$2B)O)*Lc^G4ik0tIaV^dHib3 z*Yu$5lm9WjDf>e$ANJ=Z?UNhu&$#53{f6&go~)NV`G4!PxUR8S;|JF?@(|7-=DHx2JOmz zos6q?-)lvWpdU2$t>(ENp=S*%y>oLc>cg92 zzxRXBV*exd{fJvT)PcKE59&4;#~5eSfSO^?p?Xm*9pX=-CJc>r;kWiKJcs?51%#pZI|+-xkQ}v4c=>GgX0`WdW>;x{Bb<% z2KvIWqt1?NcC1&aMS3tlN7KB9?9bsT^G`1O9?aGa?1SsM_D4UucLVD`u4@hA0f5lgr{^;$k3zKJL5U4Fk(8#$sK^El2qugl{#)^5grLNnx= z{_U90)l`eMShYRHL}sbR9gc&`5C10HPvY3oYMg)gw?Dlp*++3)jr`NU7td6CAMFbN zIR1WX@5S0djd{>xnx`DI4rKq%4?pYpM;&~ zJ|6H-ZO`%F2o`FFIzaBO#j`fm`V8LZyf?1-)}Nzp9=M;IZ}P5V){E0Pb8esxvVZ$7 z^rd#>|DE(7I>q3LZTv9j4w_&o_CJ z_12u6gLRCP8pjVlk8e#E$K%k}r()3CVIK?+EoN<&bj)MaJ-xDlc)(HOE%W%nPl7M8 zkN)<@F@K8*+hkn*o&WUH8@0R9R=X4TR?}yV#e9$aeskN*m*#d}d^g5JEcizasM%X_ zu4d4RYG-K-y@G!=^1sxRpX^_cy~QOPWF$Q>hqp8 zN!`NF)w*8SaU)DCXUZaCJ%^a=-}z6$Z5UZs#>Zl1AMwY3d#`A@A8hCTD%3CAM7}0$9nMIU){Z_8OMCgzY}YTnn7pp{NR&`nsF!2 zAMNe$-?^c^73+d2Tf)vYUR!!Uyg!aNUUGBD(|8SjCu(%h()f&xCC{lIN}A!ZgRc5| z`|#dpp~s_Ty`ru9MqBlt`5;F%pLqo1AB&ND^`HFRy&KxyxR&;59RKw1e$unMe;@O| z*e5@Y-;P-B&qq-&#_W5so>BWK+G?7sQvA`6kZY|0pGI5lZnTdzfVF`B{iqEv9dnJ} zkNN<6FZK{)R;vN;-Tk1RJoozt>vCZ$^AuZ17&ucfM2e zUU)aLUTus#z4L?bB|GL7xqdHrSJQDB8^3Ox(|kak)IId4dONb;)B)(TXMO&aeQ=Oj z$=qkY1}p~8pQs7zWM2J&`Fl5?{b$;bBR<-ve;@TgjJ2ZV2Yrb;@x2klyWWj8it?Y| zmu;;Hy*P4<8bG`AAMf7K4&!p*A2pz4J2^D^aG1Md7JcyA3gfb7rpAHO3dpq@PF^` z7g($?&fTJb^5djr$@@`I1AzCL^qee4WS8Y%5U(=hik9eThCH#|hWroLEf9>;s!@l-e z--WT9A7dXiV35zY9@oZWcwE>=9l$tO*vD~YADYV7?#=(FKmF{62G%ll+A^*xf;~Muop2x4(=3F4YHvbQGbyyd4{QofKy0C#>L&KcQ;H&dL z*~lKhkva8N@Gt#s=KmjjSY8LjyJ~Jq@gSc+jNi~^)q(>59Pbj}ag5rj@{cvZiR%EJ z`w`!R3Fl;=Ue6U{@}GTWEdch_GXHt5y&K;)W?RZ#lgId8au-%QS2`Bo@$QO!95Jn1 z!^wK?!Nu zs5NT2ru>6{_>XRE&Ve;x4Oj!#fHhzZSOeC8HDC=`1J-~wU=3IU)_^r&4Oj!#fHhzZ zSOeC8HDC=`1J-~wU=3IU)_^r&4Oj!#fHhzZSOeC8HDC=`1J-~wU=3IU)_^r&4Oj!# zfHhzZSOeC8HDC=`1J-~wU=3IU)_^r&4Oj!#fHhzZSOeC8HDC=`1J-~wU=3IU)_^r& z4Oj!#fHhzZSOeC8HDC=`1J-~wU=3IU)_^r&4Oj!#fHhzZSOeC8HDC=`1J-~wU=3IU z)_^r&4Oj!#fHhzZSOeC8HDC=`1J-~wU=3IU)_^r&4Oj!#fHhzZSOeC8HDC=`1J-~w zU=3IU)_^r&4Oj!#fHhzZSOeC8HDC=`1J-~wU=3IU)_^r&4Oj!#fHhzZSOeC8HDC=` z1J-~wU=3IU)_^r&4Oj!#fHhzZSOeC8HDC=`1J-~wU=3IU)_^r&4Oj!#fHhzZSOeC8 zHDC=`1J-~wU=3IU)_^r&4Oj!#fHhzZSOeC8HDC=`1J-~wU=3IU)_^r&4Oj!#fHhzZ zSOeC8HDC=`1J-~wU=3IU)_^r&4Oj!#fHhzZSOeC8HDC=`1J-~wU=3IU)_^r&4Oj!# zfHhzZSOeC8HDC=`1J-~wU=3IU)_^r&4Oj!#fHhzZSOeC8HDC=`1J-~wU=3IU)_^r& z4Oj!#fHhzZSOeC8HDC=`1J-~wU=3IU)_^r&4Oj!#fHhzZSOeC8HDC=`1J-~wU=3IU z)_^r&4Oj!#fHhzZSOeC8HDC=`1J-~wU=3IU)_^r&4Oj!#fHhzZSOeC8HDC=`1J-~w zU=3IU)_^r&4Oj!#fHhzZSOeC8HDC=`1J-~wU=3IU)_^r&4Oj!#fHhzZSOeC8HDC=` z1J-~wU=3IU)_^r&4Oj!#fHhzZSOeC8HDC=`1J-~wU=3IU)_^r&4Oj!#fHhzZSOeC8 zHDC=`1J-~wU=3IU)_^r&4Oj!#fHhzZSOeC8HDC=`1J-~wU=3IU)_^r&4Oj!#fHhzZ zSOeC8HDC=`1J-~wU=3IU)_^r&4Oj!#fHhzZSOeC8HDC=`1J-~wU=3IU)_^r&4Oj!# zfHhzZSOeC8HDC=`1J-~wU=3IU)_^r&4Oj!#fHhzZSOeC8HDC=`1J-~wU=3IU)_^r& z4Oj!#fHhzZSOeC8HDC=u13&vOzBXYm#(A+vp1=9oDf{Pf4ejBGH?+^iy~gan{%6nM z%(jQ(+W+C&?78UE{cKm3Z)jkFX2!IKb6aY?xfeNtpW+VZmDMJm;(O}6u#fwV^+Ef} zc;lSZ^8YjO9N6esVyv^7|1;N{E95_GVx%5WXV4+ytjAsXCsSh#?#llQW&YW(l8+p1 znUhl9IVJ=CS=;mYYWr66|7^=He2#f+Ymc>wK5OY(;CMSo>0nH|9U*KewCtU&n7MzFPj3v8@?C_x-eH+%o>DAJl<} zv)*zI*mADx?p5MSkC91ua@y5_Iee!zepf#V^Zmv=ckkHVH?OCDCi1RUUEgz@cISEN zd{?`kd_NQOso2j)d_SAMYZEgMwLC37zH@z{&eFPox>T(q&U(&zU)`?N0oH`B4%8Sg z9WTuHoa@iTKkI7FC-YpL=dX@`>bo}Q3j5$;5BC4*#C`R=#(VUo$bT(2WS==?d(5AS zf7F3?O`w|0dOa)us0HMnzFQBJI4>O&_VHVC&pLAk{(Ebz+WgNtoc+c4r-q?T#&i4$ z{DaX;8)}2r3dZ!c+OdfLTK=wtf7M>Q9?1N29%CI?J>O^k$Ysfc-Hw0ddItCWUA}LA z{>JSd`*Cm0=jSxvwfJoO*R;9xc;cV@z~4{Cda()rr5v*+oN@jyXb>7z&1OBHm4C2H zj(cAC=Q<&Ka=$U(tb_EuT(vb+*HhgZi@9FSH+8baLvOeqKJi+c=jI#--(Pybjdfv8 zzNW5e#)f0HWPJ77mGy%SF=2c7@1+)VLaI$zy;DdS`DhWt~rQVV+QqZXWtf3Q5pJkBZijCbO{TNgzRbPX_= z?=^>rcjLdrdg<5<{-J4Tb4=SjXa48qiTNrqug9wV>sVfZ|5nGDe|{gJ&-GyS{7a7M z$$72?JfHo>{CC{PeVZ6HFmgj1{QJCJ`BzQMjT8UW<*DBMFXO+I=f(WzvDSL9!5ot{ zEA0~hWCHBxI4P-?OLq$ej)!^U)QGp z2faTWpORP3Yw>`8)k;ZE8}m;N+B%SV$vkD=uC1N>o8M=!zdwn29fQ&s@tBS!Iq)8w?Rz);jtG^rh5ahb#1)o{wV*=UM9WIV1K_l zpE@=iuQ6ZYzoeJav1`-+gWjLg7)>2R9l$m4v-np%ZNR*0Uipu>#y{&o=Iz{V<+~Jj z>-%;+vwpr;zH70LPa4xw&DUcOIzJEk`>Pg8akW0ww3v@CtQV{Y%8$e)=JeR!^+2Ch ziU<7jedx9J&5CnAwXNJUp25G)4-dNjAN2R97QjB&1X>5G{8Ml6s>vLm$Uk|3zheHm zEwSF(#3%EwIUb7>{}@x&c8;(52VMUU`unREatz(fn*XyNwFWHI zeC9>lQ3I4ajadh_=AZYa*MF;rxSQ`0ui{&Crg3Qu5V|Z>p&SP8k3HP~e<3SzKXT1$Hnb!hnm$c1eC-4u}+8U5~B&YOS@!zdA z;<@_F^?%P}9_VxaLNkLL%Q21z$0z&Srnf29GNWu?%I6w^evG}tZ5xlQC$_1(fi|;# zwM~tyKau~o2IT+V7~F%^E&1=*$GwGrJbPRIHCK#<^+B%V9LJ;PZ=2r84mr9M*IPbU zYs76EkD9J{e4t6{cFb?YfA9w$do`ftpEY3V{Li{U&vi!6EBBkN_nEK6JL_!r%>T9N ziTp0{%G!Sy@87ehqkTVFdcP%k!nM~n-qc~%C)=a-LF)kHb?bp1r>G6G7AVEJKIk>5 zA^CpW&i8m{a!)_OKl4q0ZF-oaXXQP|J@&!M8up)Re@~BlF{ZB_Tl3tp-zNU`{gFGg z$8vaW<4qmHlX>-{{6q8Po_+=Y?VdoHRh~=n82`AwyO+-S8lN=(ll791JZjFEOU<*! z*QN*6F14&0pNbdp4~EGx<8ykg9b?Svxfox4$FY2r>cO>*w`!3!fF9aaEjMvV^E`$c zu#*2&14_*6u`&L;`Coa@@zVYd=6k+7eaczRZ*E_k9;n$dU5}kpHk$aHJa73dGLL&Q zhF`#c&J%KbZR4HwsO`o4gMkhBC!_dQm%az^yMWdJjl20ie%r)L`!iV2a9snB_e*?p zelL0C^3vn2^LZ^+o%Xb@x}O_A8I0V|mg~XXytLOb*R`>jI(}|FLBl0&^H}Dgn;(@M zjThDja@_hw{G*O+!awKsnfz-FJ@eU*Q(qTDlH$Rrq+8ZgE`&UM#v4Y7)U<(~1x{Lg&R?>#KGpE)3XkY@=s>SKKu#%OAV;;kK-Nx zWFLO%^LjOl{a0V%U-P8#*5k4Ce$WuLHOKe(S3S>-&Hr$XAhlr581Nd7oz{V+99D7&9@BqmHe{OZBq$dg@$jfU+=J56GALh4p}(XU{sZntx>AMI8AqmSrI?&}2qITx4aU$w%x%D?JRU)_3CkU(3VNe6GEoTA((v@A0pC ztHqb*<9>gBE$=)&GXLw?a@^PIXX)>2x@QegR*LgKxmw9TYrsjo^V#r;@7yNu>NN*T z^SAVRjC0MPhaTUYL$ar~;O}?7Zu|S`Smkjnw4s5TK5%@g-qv2PoFL9T4cf{g;#21E zCBM<<71sB;fBAiseZ*_?HTC=2*m5oy2uK*cxga+K-ZO9HkX$aA%TKu$&*3xSbAM7B@4lS@}zrEY! zUA>NN$Gd2t_is`!%*THl&)>rSo45}Q_fntZd>z~QI=#=D{$2e0NudVxG!gpNe?Oo) zxl>K+5EEx%wb%w;e1a&@l4hw`rQAm+gyj0JjSP+-}AZ-?F;a&b6+XW{QO+h zpEKHj70(*8&HKXl&2^!a8yq`(y(sBMj+-H>00$qCg9J=KAttkKF)34AAMJiYk4|5|Ed+?p1PvWYC7C{zRULm zV`LwGl7FrP$a9H#J;v)6toP9mu2XVaq0)$orz zXia22xG4Y7OR53X8GY8{)_TwPyGH)WK78)))@{u<$3T5=yfXV2;y*J!)*fMX^SL;l zrTqVw(q_E*GsoUNpF<6}IRDg8TL)BYj8&6c>pkC(tZ@x6YyRh4j=5dudc>UX)$@Az zw8r3b>2v?KZfkBeX6}2uYpv$E?W`frQzZ{QFXUNyToZ#|GOTg!p1c<4H7;F0b-wf) z9$T7^;+jBny?cG2j#OI`Q-`Wejko53{KJz$^_kbfzYkK*Yw=9}&pqEWugp38fPan` z{e}4FTy{qO$t8S_d2H_STnF;+@_F(-i6WoR0Br& z2iG;`aa_4)JTm`Zf`85THSte$vZii@|hvF>B-iA5sY24W&ZPfuc`+a4~{_|8@4yE z57e2|0M#OOs@m0f&w8MYGA`DG(jMT7`DaeyHP=n7wr=I!MfG}vJXEo z|Lfd;uK8bc$~<@e%=}YpnR~V;G!4yXBMW3@iN6i}EAxCe9$U>n^UiTm_7U&OKeZ=y zK(&gP+|%#HKiQ|ByRO#U`4 zp|1(P^Z7j2vrQhZo!`CthdeMRC7<&-*)G)pj^D2JfZ7aBO8@;3b)EITbvxI90sq4G zoO74ppX?9jd*=8I^Sx?@n%anevJbyEU)O$rFwcB2);ciBd#=YiRyu}^cij&_pB4VO z4%nLad_VB7HDC_oW7lxr=Y8|@wr;advIY$1e`O!>&hx+OX(j*Ex%zA8bMO9XE^>@I zu!?_-7rCe3S})x?l)AhR`PUk-HShU;_OBn zyC>0HF@IxzvOaJuIkwsFRu8yWO`2A{BVNl-{yo>}+yjiQ?dP1+`|Zs?@^KCPLnGA9 z3jS5gjLrXeePN#9!F~3*7R=^9uLD>I&dopOIIRJh-?eS!6?69-#>cMF`|Zttb^hnv z55M#LPwweY!hSAPQks?{7{H~)`c{O=JtU%9R9BVNpZt^vF8kGZPy_W;%WT7L5HGp}

a-!J#5~lj$2QiQ}>yNt=nLjJkQw==(XqKpEkt<~`rf{EvS#fDSnK!^_-0Cx-T@LHO(8pR8`} zwH_ee_nlc+HOKkbN&Isku=#wS*Q+D*KWaecb?Y|x*BYQ}{Zh>NpTFC>Kly&Bfz|nc zhwpuPyp{bE`DYEdEdN_`K6XFO|G4IS{44jl9io}r z5B;V8-WU3v)Qnt1ozZ7aZqEZ*EBVYQuKDW5rq3wdAIESF`*)stpf#6c0l#D{n`^lb zpg$-7tN~;JKF^P9+nmQaub1Y0J%;P}8~C-)&%f7qMSMyASIqZWf2;%N;XgB}?LGO& zbMpQ9?DO)^d@K7W@ed7<{aw}r6Z3zr0hyPzZ7@ux;g$7L%sRmuu=e@+_sT!sy~sau zM%#Fe#%9@@`|@ONvUpMS6KTjd{nrJecbxSzm3IM@0AeEfs8G4@M*W?q-J$v1ue zeeTm9HSl;(%{6PW`o-&iW&eKWpX&f+y%e8|f3S~QaLM@}V}AnwHTKUp|C2@d%;UM+ z+85R)t_9$a>~0Zf{?X=mz%L!o?r%R}{~Y{hy^{mZi+R3W+vYq@u1n18FL`l7SI2xJB_!{c>f;ClCsA5+`Q-Ku^;r>-+lSlymJk368~Ha?#e&+0BO!U7ys8d z|5y3v+8;jZ@|MQ2#@v6x{I8lroZEeNx0w4fPvnd~^LXwy`+76 zaqJ}ibq}zm)|z{%ma(rj|J{H8bP4`B)+h4Md4^tfi1 zna6Xt*$;YhPe1iO>&~+ta9mF2AL~M||NDRY--~~(0eL;tefMV_h;iAnCa+xoQ)l$& z=O0{=HTZM$o}Z_EVc*C;{5rm;eh>aR7VszY{~*l&;90-*mEv=K|Etdb`nSL9o&P!S zOzCs`^WcqqT_gYO8@>5I{w*7NuI&H02gvK?wQXI`gY6RYdQ7kP{6CmaFvpO6#M|>d zbx&}mb8eoG&)v@Th}_dpz0bPyI`@12559A~$UH0SrTBiI|GCcF;QJrzfcpLG0lCr| zuqK|W7i0I><9RB7n(NHI`Jdo_&isGB^S{ylA3CJ|s7bV^^t=8%Iit^fDwFwK zslA4O+>iaB-!})E>r(zt-v3hyJ8Qwo{8JaZ@?ZJ;KllD%BlEJhop~;; z`Sn=7e(m$~@A>VYK7S8s{x{S3?SF^we_Z#|2g6G=L!GH7`|y|JUzsHLrDJ54*DT>5 z_v7z*@9g)@L1tga0r8TtY_6paF#p3Y!9Q8poBuiM|IF*wZM-Aj7j^I4{BxW+?x$!* zenb6f4bXW1dXRY|kK~)YYP%MbZ=UD9cpN@IZ{N1&GRLe1IL>i6nSbcvQv6@j{J%Bt z`F>=d{@na?oHycMHK}^!@pJPs+>a~6Q!#7B*me8zp7TYvnNND=n%vW$#6P&FE-p3y zb6(86tZnOh-t+&V#&f(e<}#O`5>t27W%gOm`?tr~-QwJN`6uU^R~}>T)h`}H&I4Q* z-p~A_7U*8T6tAr}Q@>}O8=e34-vDjAC*wGC+&AK%8l+CC(fxVPJZ{1G*gf*|_H8qt z*)!kSpTs{k0lypn(Da4B|IL~I&-M4eOY@JhW(`okQ9a0dW&8Z~U|&ABejny(%q#OJ z@vh^I_$>V6TXuziofD_#&!y*=uK)FV#7mzyb-li8_50tM=KoR+*nod%m-Bzt@z%WD zZ}%(ZO^+%2h*$Hk>?1z+{LguiTukw^^gMaSynd_Zf7XDF$3(}QwO{9hQ#2*N$yzo4 z%hn$`Uu%VH^-2Cgef7F1bd{13Z{^95HuX}*0`!7AuI!9mp{tx!gz(2=* zWB#d4)h^@vZNA?6eV8Zp%xB53=ARm%--Unb89q2vBM0al#vSm;KL9{*OnG>`XXD=liE*8<*ZiORKj1%dqTffFdZ0PoW6b29)Q8ppjf2tE znv{E})|Cm?h_!w#ziXe*-|KtkV`%gI>fGvBG3J==!nVN$0{x&(9l;B`Tg248vOgy#LsS0J{OkMW z_sZwcUY%1NE5lD1=KstyIj{MAedcxRHu+}_sPd0|BDXo; z8@E*}WS@R#{!2XQG0u}y%679Y9)BW$)j_|LV-b^r4Hur^Z5`RnQ{*IDPN>GBNL z0j@2x5B;y!@W?&L7TL?s((}9qo-xnE^LWkk8vcg&pwH*g&i&cEo%78$$7T2VU9`|V zi#k*<{HvA`XT5LTR#w5TvfRYv9G>j!8X*0~{P)+4oL8v3?005>EYfGbGLygL!2cXh_qt|t-g7>gXU)Bi$;J4mE|XU+pJTnR>{t0G@7Y)Q$30mC zGOt^=xi@0ou9<(bPyIoYS+5(nGq?G9`TV)txj&n?$v<U2n}xz8_fyue11n zKIVKi-5*5E`;dRu7yA7CwQc4Jo;5&y_xC{5S*`)p?>zrc=}YG&YIV$STpu>)ZR`Gx z`*qO|Ru6|DGJGS7v+P`^dJ=`^r9IUekKLmp+4W?$6q` z=BiW!di+XC{>1QGTSBnAIGEyU;HoV>9M2TpzMNRqM(I;`_z$j(iu!eD9o(i?_$L!*gUmylRm#^$MSP-MUTw$$rPX ztl^tublQqXr23i1k@`_SWx{@0atLYd~pC zx?`-@>TePMHEviJ$XxbY)CS&D@1@5W!>_Tc=fA*wr>{6i?%_*X&3fLtJ;}e$=ZN!s zpWFHR5&lsNwtk;{zfw*$=Q3 zJ|4{fndLeBm+FD;B~m@$K7fAf_nW$()&j)&d+eaCwO~$sW$T5j4Va(M zP}W=KeoC)v&yzFG`+0rdTmPTJdx?MChczJa&-%lDt$n|xzaQ)eCdQ9-yYU?NF@Np) zcK^n|>5Ppn&*@x0p`XnEx#xRwM((QobMDVPFKzQatN|7N(I50}DZgv4XZ|@ZQ}w&Y zKgXRqD0%ZW&eyf)r!=NG#<9^&)4g{5ML?(7zfmVlladX zK%2U`c73~l=*?4&lM(Ql6Jy&-DPG zoBG7M#Qx{^+q;c1QsWrb{4D;drR-19r}^K9|2#HqhaSepxTXsnR~<3lTfgKcdvaCf zpIk2GdFl0h4m@f=g@5!H@342i^8GnZ*>gPec#mnLIY%8$`MI^g{BOhm)OhfGXmD(d zvmV&a`e8fkZR>XC9&NG@pLuH-R3?}5y!3i~7UI+=?JMG~^D}inj?Ddo#+X_d(~F*)(%;(i%0Kv-QS^IlsA;(~6jIFcm_owQE z)}IRhV>MuH{?`7UwOuulW7X7v-w8cVUa$O*#k!Wjb=OiKv@c`*ubo?)zw~<@t5VGI zreCX{wZG5IvkhiO`6qW%eC~ao{h&`ZfbT)i_oH8%zqP-o4zjP+fJvT{9w)D_aq*xW zUz)Fp>ofBhSB_;KW3<=mXYKFF0X;dPAI$&cjDByvr|!>wWZ%{SzK43gEBxC0t^IvT z6Jy6Fd7jYc;P(#-=e74-ny-oL$HuO7ZmoXS{$5#7Mi}?_&y1lx#plHNe-`H-8nMOq z*FL7T0N#b~rSGlr+I*h-_nceuymPMhpq@0=~z) z@_A~po4&?V`n|WEA-*f-I zq{*pc&^fQEX@2hUrMzx^{oH=)b$EBS88aVC{kg|`?enN}>OT8!{?GidJ;mR|d2p@F zmtwLHzZw7Nf13aEyQ94}zvuq_QoWvXJy^l-)HB|i=cV^!Kj1ayd-53DTl;g5_uA)` z=6vO#;h(IR{3%SzZxZjHd-(aApAqY7gVq3z7uN%McJte|4s>;{us>jpEcvzh_wV~P zR_h94=Vx6GN}A;{a9-1W$9?8yVOv?vaj>tfBL@Fk13LauC-U#n-h_YFwZuMhptTS& zb4PD|uG44EU!ITidAFD4n>tmmnlAEB9^h9pR$^A>d@@f@{^@)CmukS;{tf>7#8d+` zSBy26*5figPUXw|Z<|kQmDvVcw_#BeXw2QzRG{D0h{yB_ipPz&X=~4+bKQ! z{IVP=|IXdA+?DbLjjCpI-0@G{XTKr;iGA=-&gp+P*8gfejyh4Y!Jc4sJ;?8ie55sj zwwYhn*(qNdBOhC@^9Ln|Q~J{LU|qFZjI-WXw$pn5bJ6}8%=0wa--B&sy%dvw`dkB= z{eeDn7yelj3w0pZQ_X)Veo%Dk_uQwZl{A%)sV0leQ_t`#dC$LB7Bl;(0WJUJxx~C4 z<8|4y4s_oK_}tu|)oslcW2pn0>m28CLEBt72WvD?(p1)+wtM_jzv_45KUi<}_nh}z zpE*|6OL4wFw>$pZ=WSlk2HyuYcM)r@r(*MO_SdkF-$FxEI@9wF|4FkwO>fLUIc$Bm z?mvMg*(5t(0T*roES*qz<9b2n6|9-q?Z8c7% z`#>u=u9~UoYH*yo%z9nfCP&$m_r#;Jx<{<*fsTLl1^uJfzV6CDa}F=eYpi2A7MJvJ z$}#gh&hOg%oH8b*-;8NN IMvFeZUO5QUs$`a$2{bs%2@=q@JU|#RZ=g<%K1Gxs| z_gUR$o@aU;%Tr=#2fr(6sB~IyZ=GexSaVHqGqTqt0DS){VveOQ3HNPZ2x~hurlA>cvtolgEip7>wZ48Li?xj zTg31==CS+H^PO`Z&|aJ0!SBf?#tHFSeQfl5uH}=*-|u^W!*AF39Z#$0du6>8pUyvP zL9ee-zdqL*>cH*cpSqdrSqE@EwZ>!gagLh5q-U*}(}yM3&bi;zHAQ}zKju|)Ja>F} z-A4SLT1QF@$gv*#;3n~&+w1517dM;#bKjb_@Q*sstXt%LucjV%<4K+D;-7T@{?g+yc>Z3xYw!D@^F8(c z4-)^%K^vcke_{Tgf9>XH$E^2lV0nGW^+fjuh`BeQAM6iujU4cLjWxu5y1u%8rZLHS zP3_FL@_>0W^Rm31_ViO3#Cz87dHApF?@O%zi&zJ!=`~shYVpS32TJuq z_eeQ^`fm%0e_zb?cy1e7!?m|TbM9k(obq^cT=Vgn?X0(+He)}V+gZ=++qxEyxW=U( zSEiNqQmoe^#(n7Zo{Z1HzpfcF|6Dg#eRWV>rrtzV0??+t+ILyjlMd4=z|k9#!n+4+~fJ=jwgGpD6?iTO3h@C@xk zjtBb#)-U>X^VBrrJmvNpcDMK~YfSDJ+r9Nc z>7CCz2IPJ-&v}5p)m3+Y9=AWtmuvHo^SiRWp68|dEiq6!R$1SJD>4Va1;cCZp?zUY z_ThW{bM2z9shjRTSOju#%Jy~fPd>yeOXN%a8g{q%t@fo9 z7x|a>i}5?>7RA+*{v8W4`N@H~(i{AGyX%)fi=aMm(i& zo>w+BJ_n zB{B0=inL4^S$@0CDb2s%y^EU()ZryGtX>;_nPq<_`Ul#b^kdz-r^dLtNK!$ z`Np$x9W9^dalNh-^Bwe=`8u}M^LN?vKKb~Xw%$`?-cye;R?isMBc^Fv<5SMpe#85! z*BHM;duyB0_ul7~YG-JiPixL(J&lCzSpv8u-jnG2GFPk(_Upc_IDh^8p|9%j=A15AKS9;5Awa3W9FBh$KloVydGZ~Gw1jn z<5PXs_vY;qlVfxHDcqNS!{^=t{!bk{|8BkhAM_k<&U@yK9F`cJaqNHk`pwV&k0;n; z@tel<8SKj%pWn~ztN)wt?i#<%8d$?Z?YGJtIXr=3u%B&dEvSA&U>|GmP&3r^Sa#Zn4c+ITOZI3-;lK+n2W z&*v`Yf9!pbi`zWb?z_h?Yk+)1_Zp9Ju;3gyT*9zk`#%TEYfNha`?AJ&-xZ=?-S-9N zjb3xfW4G1wJr{W7mw94LkK;N2rDOS;T08$%pIdq+&(HR^b=-2lRg=oEjFsl3mhZX8 z$?zI>xA-mlk^S8Fn7Zzy^@WZN$3l;79oNPl&*Aece(L@2_w!Tv>CSV^G><&ChcAuk zjPdOH%YG*QPaV(97u#DfyyhP4$6UYk{RjDEUhfmXG#{s4kMY58IZljuKRwT5=kWLY z`0AXGcr2$JnSnA zCAR8Y!OFfi&(8YE^+I{NK>SbQng2NYM871rYp(q#kz3g3cQ!r;eocSp{+4;9@68Pt z7~jG3$us(bIP!8yo10f&w{$!&`EJd#neTIZj_<^C!G6z9;6%q{X?#xpm-LN#TkKOl zyZy_=I9%+yTfqO=xM6&7zBWz|EdMk0)?**qm0U9K%rV=~yl z-l@3;KAGMZ&qkYlxL>`#GvAlTa(>(%YG%uvFz+0P`&DzwmcDZ_m*5$kPI1li|I31F zNBGx%jm3PIsWF>6zh4(9+Uz2m9H^?vciz)TcdNE{ixl0FdiviyPqE$yFJdG z`~A}MYUA*r93SXuY>YGKOWM4z^WW@oYWf-tF+Gpb^LX}po-zEn3}b|6sb4cT z*YI0?mviMYKS#$I=h)Vm$JFyYJ;&p}K(_n7m!CBJtLZsYw+c^W%* z?(y9Ed1LQZJ9it$b;~{@FM7XPo=V5J9>1ykmHKeXF}yE+$M@El$1d&JC-sQ;w5j}G z`}sX{qvJqr@Of-sgV%oMnEl<#pIZ-G_1pI;*O7CN=hn~jxR%Ftue>ZCzgJ@af6W&2 z>*9CX^EcO=s|T@fd8~5|^7(VObAPU_{c7-4GVLeN>#=ji=N_-A=bh`l=ROBxqu1uK zVtcLUGxR<@c1fSdEB7UzKX*I#XUjImBHvGsajb9+<8%7lw^Q2Z9 zT)V`U+`GBGu=8oFKwT$hjb1|0NQ_nrGg?bw3L+{NRL638cug$aH z=oe%7wfUI%y|T7N{Ka_QIgK?#YsnVxQM?b3TKId<#xySF~G=6BZXWd3u1ZhL#@eAcu-#Zm42 zB>zvw`e2PkKJ*#2ew=c=mJ2;TslT=3WxcLj^WOIS<^HeL)5Pyj;biPL%4RWE_Wuv5 z1&Fnc>$J^yNB(Q?W&YEiG;37b6MEade%@!#SMAM-bAF2qf+6Mbl$g9e75BPkV?R%M zNBvEzJGH#d86&+WkC)r~fAhLcIaaOAiScef7rLdL#5w0$vNPtdnSJzatnc}p@;)>E zLR?2fO_(!Add+RpqWgOO+N4^k#ky`K_w-f%v-VGI-%tEc<)Ba#$bRRe2K2^bq9&NB z^D}jua!%$|Q)4l>|3c`PwrKvF(tqjvgT#N~{X6ct4oLi$#)@MKf19*;mcB+FKVLKC zi|1}p?!Y8+$2ob4pPau}^Y>cp-);3Ato**gnh_c(jpY(8l&)=N&O zy%xV8vv0-up?x8K1FP>NFL_Q`em(jq_J2ma!oKu>y?)cPUx@Y@?dRk9YQ4V1xi#vgX<9mEIb$Hb1n3hZD*7DQ;{S?cY)v4>J&gc6rZz-muFvmse)_*(%InniQ|H(ASM$Vt_5ER|0}y3V?DM+ zLt9*%G+pCg81FHZj)S)^g5TCFyXT0NfAmAG=~L=&^<8Rt*o^2q|F2K zeGS)-t>@>m{C`2#|BT`1TRk_29F}r?Ja(+@YsY)9-zWa(@{!n5&NMy;^ZE?sAF=jj zG@kq3SHu52Jq+iiDLv=&^H^b>AY*8gF~;z7=WE_)-TIj|ksft1>HW5~YxB}6$3yc| z>*>;Y;uUE z$lNwZSY+W<^=v#&V{9beO((zx)af$D#V*{RUn_jo(b?f`xwtS}kPU)wl z!+b0l;99t*^UOo7oq3w%|NjmK_p&*)XD0voeQSAQ9!Kjy&i~jLn*Ug%Z_T{kFY$ES z@=fhzO^vmM_fD_vG3q+&eQtZe|6Xiwb>9>D&-q~9ay|#`v2o!!jw$?Y)8nAO%Gwt3 zEz60H(lP8Y}0ucqRYjo_-epWTfP8GyCX!>7Df04E}X(P-0$>E#kkX zfu*|A>#v=k+ulFTbuF%q>)7!L?x`!)8e`Qbxu+lSui7ugx1IN?{+HfakEMFh%PrPc zV$8pKj?<(c(?acBO>cVqw)1uE{b#OgV>IXZOx8;pqSjQ0y*Tts_P?aMF2z%d`LErV z$F(1&c*ty%$@VOJM%rYcH=sb`^l^qa!=pm{}kTyZaCd5>-O+-Z^iyxEavTplRXk`+JLWr9rOIPT44>s_khpu*|aqm z5f5{hzKbjwi{{$2E<=5g^}$@7@Vc$_>icc`jgR9r<~f(c_vUl7_vAlmy{Q8&7s`po zoAUolQ9EF$AB?rVMO{fXK;K2qc6O#b<<%m>#*IbVbC2b$|9 zo*JJ*O|S5;y2$!rduzS=e%sa)jFXPzSUjBT3tH*;7sdw~lyiyqPR}j#)HyvlsQCf^ zQ*8fo%;64bNlqXFj)r<;Pxl>t+jERZsH!jI|?& z%u_X&Q5RS*;D_G}CXO{Vf@41IsUvvkj5Pti(IC9o8f`bfz0bbK4eI3z_K_dWe=`Oc z6X89_W&UNpZuB7S8O(QcIGd+K`f+g+YtC-{FX&i8C1=4*8$*8)5{AM3UA`;74) z=a?t@L4Gs;r~$M&W0%K!(C*iStfztggniV3FVarcx%oflkhv6}^FJ7$R1120^TAjP z_dHkA5H*(kK%X*)QN!v7{AWJUo?=SQ_t-}**qQ&Y#QoLs^GEw$de>ABG*>xBu62*l z%>QXTCTju5d6qw}BdIg)C#XZ6`+NM0W}A5(?Kw5!LE&HXvmj>PyKzXobDZc`@n6%H z=ur93dM&jF-`>fDFg?mpy6!8{?%9jc-qRBQc(&Gn*?s4Gt1ry|Ifu&sAg+xUjgX zU5W?ud796<^VE6bS9zH;cYihbYb+T5D(qLXy&cEKEMK47W530@bUpjg_G{`L^nHsl z^Hj@YI$jzRJ(kC_s{?cNfNRFIRXo?-xW12gz9WZyPmJv_!&|@F9U~48(7kE#Nd2CA`e|wpF zU!2c5()KR=XAQ8O*e}ihc|AJNrD~S(33EOVS?%W*_ZI{yFyWrCP9x|NdO2x@MgD*q#3^`R2WX^DkCR-;eLS#BajC zj&Uh2>7+eIO{vZ_F3$7R?+mY8kQMrQyr=be!|yx6amC30uf=)9^gLesU3WY`Z@kTS zQLpbj%D?7MYXIYR{@3wf%yAp=uVY=(f*w0*{jc-?H2;}}FUA_-Py^`eY<2TEIXuPZ z{YGl%zpYQV--v!{t;+OY%srb{-Fj{qlH+tUW|jQ zuLQHS;r#neHZoRSK|7#KHz)8KNjy^%t?Gp;PJ`&fN|iM6#T<+ ztY@JCjy3#feZYC@XpZJe*DPE=kP|Y5HsgcO-_eHu{zVxbtS1uR$6CXB1AjEV*Sz@1 zI^RY8Zv1ncvY!}Z)dFH=AMrq2eKi-FBgUG`V$5++FXPr@AGILHSZy}{SyyP6c+ba9`E;KiqGv;y9b6nKtx&IXYsUy`^DbC!p zy%GP%deGlfO!296`6)5)-)DUI?;?)pci_L2?|h8ow9u1(?i)G(KO8!O_5M4#narzJ zRo9GlO-vq+$I`w%^ChegSRCQH#`}`vr~{>E_KxNL4!$q+>)!Fb`!#^M(p=7tW!&=o z&vE9sa~@dav-V1D@Q>p=^MB+H>jKn<1DoV_iuwND;<(pfy_xJk>)aL|`C{If(}g_e zam?eN+r9NYbTfzXn9Con*uQ-7?HjehdLDb?-u#%_GZ=gPrP$-Q_GI{3_HtYY`!^HD z>t^1U{ebV)0ltUUgR%Ffdo6q`5@==nL6oEar?I#&~Rhkk$-YPm9~sF?GEK zCwh;G`a+KTdx#_F;9sxF@vi(cH)H-B{L6eX$$y3WrXIvPHtY4EWtr1!43HCYll&9G z7fg9W++&~Yj`_L#kKF_3+Gpqfu%GaQdcb#tujQfpJHsJoIiHWd_)e+;!av4lss`v- zC)V@afc9eEvtGxvjdN@3!UFy?+jH8h`PX-x%0)h(b9q9$G^RNANaz5zczvJw59S{g zQ@tMNvkL#p1LDLy+R798eqn| zHMM|wJk)_lqaHNI8o+Vjxae3-#qAuRdO)n2nbOxu=f@bBb57R`EBT-N8^#Vyo|H58 z18cGo)3Iyf(7+>Nlj}q3sPp~(=3~v@t?{>O+Pm>h`)Jj5Ewr8Hcz#dQH8j`Nou9#= z)`6#fc^&Yk7j! zd*L0m-gS9s>p-^-z`jUx*w%p({~Qa{f&ShAW5+t)dSQEVE`WAQnvy-jeak=iM$Nbs z=e6f5|1I<2p2qRw*y`GU_WaL1CHW8Tx$mUbj{Z0&HT! z_J#R|{b2jaFz`8t@l&oL`|4SDx;ijc1LB;%YZJHJGcP$`Olr#dmHV{grncdn~-#SE3F3>aSMp*Wx(r*ZcPPOYh#) z?1`7(y?OHAjM#6yd-Igo)4v(*e|xm2BYtMeUU~24nKd?b|8&0Tqx5<7{o>t`16VCb zIiF3Ak>|=dV2pI!7;{`7#TeJdAIGQklg?)?KJ-h=^n6kFF3mokKZkwfw6&aXa!)@w zexVS@wjv-@?bF~g6zNhq*^+=u0(lxYy4Gdng z4>kUU`7Wn%jQJ+-*-wpEul5V~jUEbfhw5p-KQ&lkKl86Tug;5PKR9|avDDN7VZWLG z8|J}%*sHJt>)-g`=9yEibU&#JngZ6!G4BnA1G1k$NN3Mr_W6vh0 zwY-Ug>legY&O04s=Aj`m&MS?F)~Gx6&}nPBeub7t^&WHLQ^AB`dceP?INBa9EET3|%K5K3a&T(yN zei!zU59Ud|*4UHye=)|Hmc~5mA?u~%p7RWKMlb9`le6Y~u7!nv%#+}O%z&GQ9rD!9 zyI@}#%zWl{!~Hjdw%Q-K|SU;wRIqm zXCCh&|6CW8>Og^coqwoR)vv}y{xcVIxhh?E3e!t|Q+j4TmfvTrJ;6WoNk7his@=^v z3j3%5Bm6gON16Mfo$h<{jJY5Dt6qz9KedjzFU|eUdcWnK-1PSI!Px)L-uu5?c3o$l z|3E`S|I{$hFwisvg27-yn=x%hv|8O_6s5(el@X($j3z=E%?M58E|e%yqK@pM61j_V zl!&q`DN?2qr9#D(GH+Zi@tQIv%93T|ByOB99tP$wxwD>q_OtJ^*V%iYbM8I&eM{2* zVX^kv=lfdEXYIYueM=fK=NNt3I9Ge-H;nyV&0P!EovH!h`B($Y^F8QU=U4~Q+JJGH zr~%pdk6OSwte!Q%yyqV|)*9gTkbNI4)q!6!jWucjbxN=7qdkj%&kOiMpXaQ!y@cgm zj^#b|dHnOv*)GLG#(&1o`!fH?Ni@`K=1uu`+%xagLRvRQ{vR~$z!LweFKu%?vU%#m zX5_!fKC}&;NA}4AJdK>t2mTA(vkoZhXz$GW%o^eE$LFYdz6U+uGkbT&1FZIzSv6A-K*O(h%-r%+Fw^!$lHN&&5+dQV^SL2?wWXnG4 zfWAMOSKo{^V$O9vePx_yKAC5F)j}HUsTmzl*jE1Mvd{5N`2Tt10LFE4n&%7u$`aaP zd?#p^@jJfnZ^tPWspAn(mMq&mP@u?AG;1gry-{4;ON zDZJDH>VTd)se0{A28i{AXi5$1LiXdX{mYnos<5ybPjN4j67 z7Vvqib%5_1YoNxXS_c?c@nQUrc}R7jlr#2eo?{J|{2t)-M4fq`#(!Ch7HWZN+p~}D z6Xzd%vkt7zI>&tV7wjFkrtv7XGyZ+db=&8uvducr_5Q3epZbBP-lE@!{q((jg8MM; zm&SYafk&EgtsHyC_u1xkpRs$S`{BN|=d%7%9q@Aj#GuGO>LqI@YiVIF;PcU}-N>KT z0BchR)J1jN_j*$8X?w(cUJptd*E+B||C3DT^Ko69FKdHo%;(>1+UKoKs%3n$hI+5@ z^?8`se$eWC$sR)Pp#^1|ZI1EYW4yQVhxVw9MWGH*r_^m2^Do(&|I+61GlM;8KG@6V zVH50ozMeJa)T&JXwvF*-FMRPzX3u}g`WyDb7cblKgN1AV_GP<{Sz*qAK7AIn{rZ44 z0G{&!ediDZ#v;ZDu|mv(F_!R*uf!cWU_RhS-v>2^%&&OrprjG>&8`R3p=#CZc*eHp zXBaEN=yuH9_N~D#uUonY_s#eI`E~pARvVOkw5$AU9JS3j&-6a8%s2grf7Oe&<9JW4 zL3bJd)F$;B=lkTIKIwf4_cFityd^Fp?_mD{XlXssl=9BrQA8}7zs8-O{`F4g8}9ggS86#`cE2c=pOh zjpqd)YC)_mxTib^>p*%|)~rGeXw3x>BaK<&zp@_CT!itTa{%Ve=aYGMp1OdiX5hV^ zW^LCr%zp5Wws6mO;w09AEyFoy_iI1-*Z0WU_RbjhKWFa`8-2gmz8#s@93h9ATePd} zLldeOY6u>>ieo-Cm*)Hh?x|aH4_ycTg?)^FRqn|R*~hxujNH?wvA#N|&-s7xoblbV z7XP7!827kWoMZ4g=)?B`JS(4@?-9Me?`RGXW5ZY>c8p`@8E@2FmlNhGtJ|7W$GvL7 z>ttvCy>3<0wRWrp87o!;4$isV_p=M^b6~e&tYgXJeeL+fxsT1h+o;iE`u85;?{^yc z4!Kutur1tkUI`z@d-I8V=opIhGr?QCvi@5{MBElxh3KHmPD0CJ=` zOLc&`_1@{@A={=sh_;^(h}MGc_SLUHU+V+7an@oF`AlaAD&kdEq=d zeRsp!JAB8xoUPBvePAD~gL$xBvM*oVygXqq*?v9uMY|3SwE%Sj_hKE;Zv|;S6221* z-wgR4>Af%qz`Mpez&OAoHi(gro7Mmy@ASwrqX3pVN3mEUc3IC`A z571Z#_8q@U=NGsqS0(1OZ#K(1SFrnicBbjQN^L&pDz&f_8{fx#L=XNSupF`u`23Ex zp@9nf9RF2QNpJD{zSm=m{U=T1s`tP?SpXL{W`Ywk10TM<$8UGwUwI4d$YdJ7J@?=l z_Fo5^c1F6rGGH2^WH)d0lS$2)x&h~ES>S7;~x+16a| z!#{QB^?0c5SQARjY2RFqdEDphd8h32b`0&{T(hoDdGZd{#fp4uz1KFhKwUWh=&!uB zfjvh1f{pL6FyH4KbpQFj^6?yhXXhL`j`?Y=jkPyw0N9HL9x;cfy<+@M+Ic&sWHZj? zeQN6l%t!dV!OL6#-vso1Lp#m`I1gfd408ebUcxzn&IPb8$m@aS3prxWVh)kVTAoAg zS55e~X-hSwT0=X{2cX9(&0_zd>OZH zZgU){a9`(t%RaQnwP|tOpE2Jj4`4s=5B5vk$MrqArzejb*E9B&@e9WMT+{Q=mb$?E z@V;TKU^R&I0lm%0mhke&-u69uujK1z&z5A>Hu{>U3l*_ zrFt9jPi@vTdsvWB#d0 zj#0tKwXEuSG~bVNeX<|;FR;(`cJMm(8<*rUF-mq@tbfIr_SQNc`*_`03wYo3y&%>A zeb!P9(03ixfWkL{uoiIN1Q_q}_>bK1?*@ljz&z4d`KK->_@~aOJ7_RiNu%u3dT_SfTmq7To(Zw&H%fX}V(f;Qe!-+5F6+}{bL z?*Ww>Fz~K5S#o7_0OpaNc^-M{f}VB2^rd5pYAuZaqQ%KN06hzvC()i_eeY+`+O}(3 zIrs5&+pN3v*|%KgO#O&+tOd9Bh^-CP9{Uy3&lkivoAWUqYXx{dAEH?RKQV6R&{*zcQ%{lSFs zIC!~Ex~|^K?#<_5JxaeF#QA{!emHy&kaxBBMhrA2`r80vr?EsktFsnw=7D)h<3IDJ zxwPDBuF+O43~l#KNVQaGQ-}0vKHzkl`c><@^3po*gzL+!ZytPJefL_s7Pr{XIKzkW zzL1kt6Hv>S@~?WKhUo2k6Ldz+!Do8JdSBQ_do<^d{6qI6{^RfY96RB|xDWQ_8$OwP z*~VtF|Jv_e+rY^EYsN1uFi-#11pBBBIEKf^Yv4NRy1Y-U19%2LPpScU);JeX{?UHk z-cjovvSS~Y;b z9kLDt{-p*W1{#x42c!ldhKwnES`Wx?16q%fqhQP%eXIe0aCaVF3 z^@84$&w}TIr8xlBDfaB-A3lx$@m<3_z+#{^z|R9%M_C5~|EL2p4`5EPpWM4e< z%sQZ27}|w7qiTxnSO=&>`bGSE?a$@MAE&ipDgSzIt&RI(AH09&mA4=^=%yX|}6WZB&KaBZTZGAh~v%hV;e^D&{ zR?e{zp6s77c3|1}y|26-*7n)@zQE^MW1RMe@vL_K53g_3bYH2>>%f=l3)cnwKJW^j z(b%W)J@{VwuHkzCVjwX=j2N%r@y$ejAN29oI)L10uF%f%DS1ZDk^7(n>H*$sWlCRe zK9K3NpyfmJll~roER$Vsd+%$>)b`4}`*^h2$G#9>*5E4t+wncir{>w%2mi`G+DR{Q z4DouZ@z3#Tyx!mHIpZI?H&Y(?_jP3E<@kLsuoT!I_y=>GlP4xI&I|YFzGmaR*&Aw( zeXRp*zhTD&i|6q=tOK|Q*9EvYp99Y{)Brr=wg%uG_&%rsoCol|5rbf{28{Se4V4!$M;PVX)1XFEfH9)mg_+F@5q&~%aJ$t>czCFe7KKl>MK4RhH#qEbI&amun zbrb)ZBj0v4p7}2D@7c#(GK?WIt_1!u_k_jyJ~as+w3^NNw`2cv9OtNc;h*zlvI1@b z|B3s7s}cK=`@p5KNnYs_&*b`#jBhb=Pw$WCb?A8?tpj-ti2TRr=6mqH_?{#F5fh6K zVwBeai>cHAjWudO`W_J11CPaAC675kqXs;TIe=Gyqj@6D6voeSp%S{#6Ptczw=R-iU0IF z-=JscI`A*-bNnY0@Zf~ZjQIZp`^F!?@1=1c<1q~DcC7C??hE6}yKi5(e0`(l_j8-q z<$Y2ei0cnN*DLmnVf=Ue<6ZD>`mU$}Y5b3IVVt%#!1zZjV+~-OlV=`cO-7Cq`)Gsz zMhyu3H)}veSLquP)}V!PDb@vOS2cZb{^zi(HDb>3OO7#aA6Q@yzVMdCXRwD}M6CTB z%Q(&EV-wrTyR{!N<}zaSWJ;n-ZCi4D-X^X~jKa4w({ilBO@&@+wZ{gp&Z0GxE zKa24!+H=r2e&09U)3`1^^X0dVkGI43vorSF8ZE5#@%vjea?km=uG!E3;f;K3zkFkJ z{*P{KE?l{p?9D4jo0eU${j~FcbhJ5d*I*5R5BHF73VaSe7oQUz&(8ON$G5@so|V53 zV65nUJQ-{HPzR(2Fi-HP)uA3RpPUE4n+{;C|IkD*r-0-l3CAvB z@idNIz&_pw%&TqrXWgCPJ;m9MbL?YYm$8p+$$47uQy0_;eAJHWDQJpXE3ohR#~4N( z!-Icv509}B{F8~7&tJ=UdD*_{EB~)yu5aTlnZuY2{=pk8ik5F?G*b{;U)Rm7O= z!~1!F&lmHUYC3Yud^;@^`8Un9zYk35lR6&8Hq|=XpE&+)KJ7+C^WFtWz^xbk@6 za%4WT53ZH}H~+*qzuLCMeVhk`Yp|BU+dcH2d=5SrpOeqc_h1dcJ8BJJ99S0-Bdq}; zhVC~3tpSOD)MLvJa-=mN@sE5m$5I2R1A6L&no%u{#usQ!$Dd+b`On9?z`w8%Y5}%e zW9Vs&=}Yd(XXkt9`xV-&@h{A$oEi7bBl3zi^9fT{_{KD0wEL05tQIPOz-;J?N` z`KPAIKQ#{z{>cRSCo`|ucnXfl6!|A}@MJNtANkM5crdR9{&id1rG1ziL<{o?+(T-B zJ_~AqJ}aIb@4)v#4anZr-d$sX8lbTXb0*B2P(v#AId0YpmCcIv- zo%BSF>6lYwUhBdF{tJAhv65UZVR6o}yzVZ(!1#lr?0ydZne`xbdya=DRQ8?_i?6 zXy3!an)tkNalyt#Gt8NF&d+r<*aB$@E0TVVEW>p4R* znpdg;iT_FsK#aBKA_vSz#(%5YkjymslF{-(Ybx zcPMc`=lrQ-?DvlcqUkg1yRbbs4(T|?lAiIVU&_Dc+l~Fu2DJj+!1A#l-=418e1B_6 zi_oPR$E(18`1{^Co?%>z<9~^Lj{RhX^Js9yu^()af6n*8A(;6VM4Yv*rJa)q%m9{QG%7_>bd0b*=1U{38n-8^H;gfminV`y1I0 z-{#`D&-r@d-ngC1`_yrIP25N8O?U>W0eD881L(Wp-T0n-Z_WYW5gW!Sc*G7dRQ@f- zS_8;G=K=fm6*}K2W(!D+ji`&a@pYf zLA&ohwwPzUCu?KPYy2vlYy2O4;FgD1@b|Ovdl|l&z4p}`!SMGsXKh{$J7=7Q zHMh+9$vu2n-&e-?%hzvhU|0X*xM@7DRuga!y(en{<^ti_>>1DDS zTj3!Fhy`K-LyXdQfWkZgF%GegbAYF1E`VGhN5Rtf0Mza6_Z!Xws|Q0CTFK0p4O= zz%hCbufywNO`tVE`47(~{G$fwyCnXl2A~F(_^;J~#6RZ&$cNN`CoM;L4cN}zPzN|4 zVBSmPLFkLxfrd)jVqcYe>b9h5^wn7K{S!5z#N-mLYsZ$ZTk0F}@1MW4U5g3(8M_&t zc~Fm>(W?24^#D4cCI@HF!PL0Dc>&)&SHfsR1MYF%RJThHn9W4lvA< ziu_w_1OJ=@SnSC@ec(T<0eL<(*VQ>d)CROe9o6c!H+)Og94SlNO95~nrJ(O)`# zZeNpsyd&PJ^sd_HW3*tq#y#>i#Xk0{9*X=!OEf?BWBiZfK6M#>w<@sDbwB6*iG7a! zael14eCdyFrg?vu`{QqN7O;=Ge&QHo|4keB|1uie^wOW5gKMCU#5yoF55V^T@Xs26 zchGk#tpgB??L1)MzqSrwoP&@2gZ;$6cY zU*mc9Jfu{c(5LTn=yqv6W(+@qIvs5?pTBl_^BC;cuLOJC=7g{(Oee5xzHj>BdY|Kd zw%!-^pS7_GmVLka-M2R{*x$I&UbMe`d&$N;*csyI@#bd)PENvO{yA*ROH8Yk#c{)%x{W4$*e;#dx39V__dTsq&9+zrnWbBlpUF z#(y01RZD5T53Lp0e@@2zpjqJ`y7v6PEOUP4|LoWB{cOxW_$O=pCQlB*CC2E$KbaMl zuNm_&uz&5SY+)adi|2;<098#y`fbFArlG*(d)T z^T@xl@5lb|O-+8|2WMn%ihYdd7^G{zI*>8j#iug&M&7>l{G&XAR&Spi~3M zKWl)-DvbXYL#+W-{^L9VIno;NB-aB4{!s&0IdNJV-7$o z=yfzVI4!P9_dRLmmF-F!YpRx&@{#prYdM?i+uHsyTi4V0d;a4#9>B8oeq>&`f6C_i zw5Nr8u0O#)H94$PuiNj}_#Jz&@VhpC-?ndDu<-XUSog=d|Em{oY-q22Z9C_GzNNqOar}deIsAh& z&p(+YqhuCy`ozC+e|>^|)Q0Qdx;t2`1GomS$LsTcr~%4VTd)NuD|?X{=BSsMDH`7q1sQONU~1*X#P{ zD{U7zFUFxV_A{pNiFvktUYIZVQ9ZZseJ<4>f5gvS!)e`&Q|K_*vHZAdwI)EBr>jqx~^q!Y&J)kwf&jAAe zG6&#$^1TE9h((G2;v8W3jfHbA=0S7AeChbleEJ&T^^mwvb4LGtz;vaWqYk52t(I!Q zcPfHi>k;eAK^+cTybV9);sO!$A@)&YFqH2)|6Rs;AAAjQqi z0W{WgY5>0jFt5xr>%P}Rk^k)bfa!>OLmg=FPtB^PYq}5pbJ*cAtONUU?D%UwU-8(> z#`1i7#Ew;a>{qXD9=f(9d((E*S*hgnD2A0FYA5kBF!7)xUb_1+8l4F zyr$*wZO#9@=@s^Zja@M6ncw_5-fh469c|8jZLo8HXsj3?`pus#;qUEWm#*1* z|IhI^Iooe>{Cywa>~!r8hHn`2v?Jp>*gNJ+7Weab_)rJrn*e_oz&*F$0*7b9v++6k z-1rvA_Zhwe4)6NP`AG}bN3C!UndvG1jSty}5Bx)q!ap_pBF6qW?$^KhZ}|^ne}n(w?|XCp z$v%9>e;mKb@+AAL2d)OBc>uo$SdGzpq6VNAp(cU<*1r$n`?21Y_#gciU@>GoLk&QU zWxVZspymQKAj?<2Kek$+#(LoSw{4vR zu+FgVunukK07IR^GvnEnf2{$0?-Kv58Zcf5)2>6@8h(_wlM!V>b6*scd7gQw)ijGPM|Bi9r zGS&fjuphnw48I+=ehc8=0}N|`+g7uNzX!lO;9dA$d{4Y{nE%V)1Mu5O#y?{jV~ZFw z-kJmE1vv`Fydj5T%ro;or4QBvt_#ArLcOh_U4IRkD}bG~IOKJd_0YC^JS@iJcJYr| zo%1#t&;9%Ey3HJ@7w%aDs`-s$KJ`GaTJh{Z^weqr^Yn8@~nk-vSY< zS`A>F8TYjQin&3KS~Y-qrfq+9@=&f z^}POCKJf3~ai49*n;!WPo69}t`_ut_;-BLO#}nwv^B>23j{Sjuj#uuxpXVQAAIE;r zzaRU-5yn**IRkh6EsnqWt-?QQgw%j3{#gU~TzpQ{D?E2t1IQde$A9wgYe1EMi|vSi ze0SkDn3$th4Pc(>v-Ln>O+fDHRcpmsu#dJ2=dQ&euk$c?SLV4LJ&#-BcU~hE-goj^ z;9hu7ZL8IfnSuZ6n2&jX9QQ-sr5fc`Cj{yhNW#@|mESH>A0u@Aoo zN(~6?vktI!GoLZXWIyB`xu+HQrbEdXICKaEfCS|FJ#E+aAw0d2Np2 zHE*$f985Qi`3ZBiQvS$0Jb3@o+nGJ{B^%diXRS>;hk4{M=0j7@8uzgDy)W|5?@|7H zf6y)drY-)RPw=+xf5pcASMB$H*lSn=+wXm_vsnM*?_~Vi|C-JJ%`RRW*8li>o6GoH zfBU@;Hu`(NZ(+?I=j}TGC)W%4$2WkRcD);RU6}s{z~{g-;n`55@T_=d*l7H>cf@=0 zy%7V(1F?ApF_M_QB>y%TF~xV)^gBTOTMMlL%u}iZ%p>y(&wQKrdYGIKm=>Ri*3u>S z73T_HQm+&`% zQUe(0S`8TSpXLIwo-@y}26#PCC!;z59SQra0q`YlvadQ%Xt7Y*cbXix?p=D;QeTDd zVtg#7n9GOxd?CIXdu1POKfh1;N0P9Ye|JyudYW&Ch@OJ==|KuMvARGTn{7Vf`{!29gIby!j9Dw;` zekJG7fYt!61CwraZD9J!v}fAXZ8EQ(IxgzG#YBmrQeTVBli1gKhpBzSd5V+k(^zV( zQyV!E8|5u>&%83%$a~_Sx{xuS+M$-9tH8gE`~1D1noMg|&p*e08ULsF2m9n7^Xbz3 z|Frn$zXeF^{{sJ91Mods{BsQu=KqM1)Bx7eS#to(4fDjjF^|b>uB{GG2h;_1l6lqu z^GSD(eeGAx)^wfqPjS-Pzc1sCx9qu2aNpy`aLLB=`L}J1kM_lD_RY@PMZ-SW^<+;; z-B05_$A*HZhN!8GfBXLQyjfzN-}`>=#^%iL-%R$(A7D*ub}?F9|C4{t{kisk^mR$l*y?|;40au6)mWz7}x_YAC%+ti18 zKwXGdwz{E?DmtSUlb1QD@l(50Qa=OK66y^ z1OH$@@vmb*MH_S2e;&W}MegBWFnvO!)a*+Q_G|oulU?|~c4U8}+u|SJ^YIOE&i6n6 z``=3Z4>e%;{-LKBL}S-5cqG^0o6j(2y`;$zoadz3DjZo!hdB< z%e3!mz)5j-6IKr&xB2-KHb&F7YjwuZ$8W(l z=4sBJvizzsuT{@+0p9eY`pMc{XJX8Wmgb_EliJ)gSmgeUtzTi`n-=sO*Zt%^c=>z( zFeZk7?_*=j30{`3RnQ zk~*My%;o~f`9wWXEuro8wp}MI&~5gefgB9q7sv^?3G5g{;3%*ZxN3h>nC3G-A2o>Q zuvYnP_RsU%=MB#-?Bm_W^>jhp9P23;HfMV>8uRtEcRAG-DYWA2~$$GB(T!8z{-6EKXCtBn7JIRLoM)&p*?k9|e&Xi@ zxAv7I8}qmJ!tWiK-i|ge{{CQTU5c?^=Kats*nbUw=Wq5Z{?3)-KgNDH{=f0{Tbnno z-rl@n<3H>IxG}4(|G^v9-B)kgdi*G{Yffq%{e;#`Y)U|uv=p{=>J+&=3ps{^8m z)UP@!=nWcF4Xbv2TaQcaIh>S^0dMFhcf0!1wRi0U&p21>-c;LjZQB~R6sLF`xu?(8 zZ_JhQj&>=pv5(xtKNrRhVISHN_Mdm`LwkvT=u*ag<)7?l{6qH~|H*#j|2)3^IsP%8 z>i54n{Da%T|1LEE=Lq|_R*8Rp`_u6s&&u(i?}2v;H307ji!}f-2qygd8bJQx5#wNv zf8+u+06EHXrux#|3@0 zcs*_LhNU>ic9tLIzQH`|f#)9VkN7A1)KwhwIp!q(smZwJC;MUUAIE*L{|f%CpN##7 z;$P|Lao5@OPOpPzvTWWaID5Eti2-l>|j zv#|sG3;WO(+4tjq&}HPG^VTZ=9P@S@|MR~Amic&`|7ZN`9AHijz%ex8KaBn1w?Da` z&Hu@N__x3I?|(5z*=hVoOoV^V0W_9j{0IMy8W8v|)d1vD*k=u3{;3Pg%jLG}zaX*?jl*U{`ET{Pk#8(QDfHPt*SzFfv$P04Be8H#st@8oS>&#OJ@XtaUZhi2K zeJ|KogL?j44tN}RM0+hR{dIVswY`qfOWGQ%#ckxu8TjAU`Yrd&d&WPX4DdN zmH3zWzOoNJCjOz@692+Jv>*5<50QQH&%dGJc*^yE;?407E;C-O4uD~;0cmXK81Fpm z1kX#?1N(dMkM986ZvYkkEjC5|SpzJ-FAM*u1%58TJTY&~W33KQ2g%DE0ow6;+P5Ao zVS&f4&8A)t_uw|~cba|{^Y|S%nah#o?-{X-edJcgezY@PMCOxbBLCD>;9uAe+A|HJ z286N5+G*UM%RkwNFYr(H;cNV7a{w9p!7e$b55MC%=Cvm9IC{^&j{Q^PKb{#ijO>s2 zw|5TXzsv!u{Kpzl@a5BI)g&v40}Czw4OpOeqc_uzZI!QTPnJHL)O0KWrRO=O%HHy=;LnDJ)(GtWH5 z+%b=(9A~wGx=`QJRNO}$(hr)&w}ipipS;)mXq%j@UrUM%kNA}PuemRc+3oR~aohSW z`=|jajJXZwWqNh&C;lV*(0*bcJWTO#d@28^ z15N(H?NZiD$DaiMu?CQTc+`MbU{X^V=V~1w|Iss#^rJdZ(ggcdL$R$|L)+_dtJ`f2 z(7v>W$mS5rgz-mK*5t2rEzDJTuM_k7uH&}GYG`ly#~kfhi@BQ5M|G~poYFVAf6=r< z%lMD%lY5T&s>!6)s9n`_n(IRA826NajEf8T$M}Cq#(&g;>~8_r;9uCswYA>h-f8T| z9021#=KyiuQXKzf4!}5sIY6NX1pcK4XsqYd0Ok>%H2{859iV2+d%dX^LpzT9V;#r& zK$Uy4a2iY=@SgTJ^P$GT2OHN+t)UvX=iF~~RxzLB@)|Ap~CFeo+P#QFF0|0(|M9d!+m_^+%3LL4~{ zSXu`xUz$tbF4h6nNl*)++GIRLA*7ecO6xR2Nd(2TypRfHf*05X4xVQ1`tj+Uj z=VaX|1fnEH|!N@=TWsK2Sr`xy<+jsttYw()zybnE}1JCs4U%=uwfZF&E z{-p*W28;*1ehWbCG?r-l^*|PX%K>u4e8KyC`fb*POe68z&t!d=qff0N1-;K9~G7%er@_hsWWVl^kGtz$5^6GIM}V|ZmBZLf#04c$b8 zp47bF!ZydJFy3!9`wGAFV_qBQ{Hpc9f1LLRCTjf0Z+~EGF8{0nC&~X%1M+Wxod3gP zZo~Hs_0HaX#6RZ&aV>xtY7MB(2bdSl723>W@bc}3c~@=tcHm#M6m-?nUTL55Uub(C zTH7Vo#(m3pe$M{=Xo8$G#CUi1pxW_q?7K%G_KLM%xh&@R&U6XhWv(Bzj5V#Z zvzYfn`xpbkgV^~$zq5f|xPCX-n}2b~*uR@>ivLSkqr+_74!dH%=c!Gt2lUv>c5b*1 zuQ{>)ALjpjRy_CiJ@{Vwu4pq3hzZQcD%FD+S0C@uw&qImxXtOdmWO<+7D`%iYlbzo z8mHWOcj6z{;&t(xpR@cMVE(+UV^jPi7J+|X z0~p8X8RrrA%nS1rz7=lshv*Sfv$wOi-!!M2Zc zY5PUXkGIxbKJ0V6pRx60ZpWLKO*^#2J#|H$!AIty$=6Juu#A6>`_MEjj``%@kA>LI z$9{ZI&v^p>v&#hbDW{i=JQUQ z*mur&r=2wopS3X^b}m}dy{+-zz*^td`D)}Pc>JcWX21ErW&FV|MoZ%|$9*u{Fs=hi zKJeZ+H(ZzZiJs5F=X%qgF?;6pcLAtn`CI^V9mF7*#RsuDckNEFv)Arg+{{ojC8ksH zXP)Rs^}CQ`J%&0-x{) z_x!Ve>ijStkQz|rpU>;xBm8|(UITnQ$Ui*!PqDO^rnLd$&De`ajx=YZ`9WchpgN#V z=#!389mv-YUVlZs%A8?eCU)hrcHX|vv*>*LH=Qp{nq%JP=PxxZpSxpjH_Z8%)0ZvZ zYM~z*@W#2M^PF#vv=({ix}Nj>9fAXm&=;NRsm^?40Atw1+P zM^RT}z1_AkXIm4BTFu6=2?kcI6^qzh>%QdM{(EjO?PP7RE9<59B>$)p za!-!`BmVi^d=I`?oC9zz@P=)tcpxqoKV1hfj>*^J&bfiB1I%Oan&V1)q?Z{Q3p$J6 zk&-^EW7dvbkgK&g<#p`8!~W8V+Q2Q?Eg9==)%%+5^CRqLxu4me%`Y*wIG$VEXStRY zfBrfc-&)W+xHlePiFdZijrg~0tffuP_wB#saj9XKZC}e;=Z1ZLj=)+F=L39B_%s)g zck%Cw7%(RA8Y{H3xq-zMF>d8xw2$+H=y%aXb`I)>YHmW0XKX*Lr1f&&N%MHv`<=1> z&UC_Ob7Z{kOkbHg$e+e!%B3k7vwkfUg7kuCWF(7Qsuc4E!^ev8FQ4 zjQeCga5+VeecrL1G2YTpO?Q?4m#rV>bza}jc{%jhe8&EtbNgBTyf54T+o5ekJQx@6 z7xRtWiIsAzeaL^yvUz=hebpVcs6KFyIc>BHrg1fD{ep1-3*Yq|^KXKgXyhJT1^!FS zYoGFOtY320Vjuhb@uPa6_bRmcoU8+UkL2}TV+;_76c-mS#xZ!S1sZF%H7BF-A32m7 z(8_(SHc&sIMu^se?tHCqb-?R;db`dK#!;}9kEi?b`|?%$oA$k|{=DTnu%OoZ*p{|k zK9H-a9CLqBD+T8DIBHIPrcq%Z{mMSZ_Qbt%plxCQ&B**L?#W__Vf*iWUy63se9L&; zs1ESDyw8MZ9l$#z_WfKy-dp3MF>A$EbHKLdYDt@#XnAN$$C@JlC5Kt#Y(IWq z`4jth>zS>`XNZ@*JL~5xZ~VO~wj_s3j@2(ajb?e{loiQg*k9~%E=?}%Z$V*Yx1FSJMV13p`<5Be^lt?#OBjg7Q5 zmZ2?c2FAbTHJ70+Yc|dM674KGR<#`3RzqfS5RaR^E}30pa^7G4Q~U2zX1474mb{BU zwpeS8{nx~1$J+jT6R*SUT-sUN9@}`$&angD>m_K*H1|fu$Ues}uh+o8>fE<6KB@`( zZ;_>vkK-|U^qgY*@;mPv=iY>StpSxbu2H;ptUGC*PfWEJfhi+RP@bOG> zj&-1xm(XwXuXEqXbE>)4_O=$}nhBbsw!9auQFGp_mP5O+{`6cNcH8srx988Fi~P#_ z`B==^9>vRI{+d`bA9fshKWG2;v~1foWNF-hUeq#u*|`3OGwKmO%R2mppi-T{-?>Z~k!&UawXQ(sSrD+B@Lc`5t^PzGvn&CTL3y z&{(3aYbeCr=Ob)Ot=3$kUCep*T_M-a>!zb*p8vwOYBjWFEQ7Y^FrdePF>FtH*(_zV_aue%6E-yA)T8yT&}rmFB3HH}`#J ztbtrZ)YZs){l)E(UUy*re&CSf=*Zi9o?T$0xJ6EP?GJq0e{1%Qnpu9&|K88tKTt}PtjV>hJ91-SBeSyim~+Z zW}Seyd}yxFp3JY+3Dv{6z132v6*b+({#|ulJ8ubV{#euz@>{DZiO>tFnK`>cW~WGd_gnh-LqcWh91?RRn;@wBi1>e$M-cca=)E-56o9N_gt>Y zeCb;LeueG4CJgHlzL&mhXfqB4&)C7U2579&e$!iJt;L*4QoAL8EiD zJo}iXyt(6-@;!NcU=SS6GrlL|GWWdtaqT%>&i)6B*4`z)t9_=|H_cR!RmKb2PRC+R zEe7sqvtD2?jQQWSvHwt3y64Qk@clsEHN1B#HjRCZJ93Z=wV=ZPuKiw5Q{zuvxAS-* zC(6y-Yhqju=SvM(o9EWEod2f%_iL8gk^LL^hWYv3u6Zn-_ZG&L2}|0FI!oL_o9~S0 z@RyHmzT2=%#)?{Eonx)A^tScAZ5*pzy=!bY?7Hn6*}K-a)JoTB+?UU!&l=iz2fQ2K z%e|+@0_|4purG^$AqPHRQ~AaIk~Vhkv-zZIwx#W`Zz(VF_!fst_bq02;WJ-2vi}Zj z#e94&e9Qj3mtrB7sOyNUpXzb)4E}9n+S_8z7uE%TKWt~ux*KStR3t-=FO2F!6I1@K z-LrkpuG{t@&EF5(*)z$r;<+&w(RT{9!rq_nt+5F0R_w4Z)>(SwAXv%(DuB4&lo#J8`XmX|N2f#X@i)&&xjS-gxiB&6FK||HI8bHg$dMU-Aw&tbWnL+`!c{jTPIBY01C+ z-S;gwYD2DAlaa$Lr;=;rT=&gsH?_C0b`08H#>)~G!?9C*+y0Bjcg>dGk9=Qz$F6Di zRcG7}fB7Bz|JU1`t7V+$(YNA{eazuy%k7jw8{{7TPqFQc+|w)jZ@+80v+-`qJoCEU zVrPoIo5ps_jy~9rebkNC^|^2AIrSZHqGm}A&{+8PR9vzD+PfbF+tz?>twzp#K0}VR z2G}uEI>G*=tBLW4I#u7&HyBVRJTK&k$1LTs#x?pe_Vcv*R~A}P!F?wNpsSd z<{cB7-s)XhxSO#8Zg%C4=he8L+t2I3U;57QJn%Kcac|jk)9?;g?f9(?95YT!J;rGr zgZFJ;xBRQAUao&HXCHdhX&T3cU!nk?QIMa{w5%V;WQ`UTFprsk| zPtRkiy(#at%;Twh&qIqH?o-~hy$jcR{#2XypX0CJJ+^u}wrlU+vGuf?n`6g%%XoXw z_?P^VjpfMaB8yy;Z}_7R>UPugs#aq9wlO`&z|`%?4E!7;e|P@Y_%7I8>pP^C?ukA= z*PM6YF{lqA1~cMSiZl8&7ic$gSIG60R{XdCjg_=l>+||0|68vt+$k~xj^;4OW2QJR z?dQ2QUt)QeK4t&<_pCm+`8VR_`*a)eZ&@jyEln)l7c?gGZ)lKX<1U^oz#so;+DiQ0 zv-3{FO3yOq9dHcadzYBu9M*%;dZbai7w6i>5}czoprt+T(^{a~EoUiL{$P{LjmMskUATS!zYV;@dn;Y+T!oW?b3bc^!a#< z<}NXo^*YhY%{Gt7DRMiN|0R0Dv8u(PO`YD2??&4sWOYJul1ZqKd@j`y2)Z*4NGTz|xQJkGxd zxraY4jQgc{-2VQtFj@3?J&P%f~2RE}zUZyym~wRxP3J>jZVU_0X}_fSRt$s!XjddcVf`@At<90#ypb3WBp_Cs6rrP`~tKQvvcW$Zh> zPcgL1{ucXT-#=P?SYSW?$%h-*F3&Re9IXL3M`Nb3M7tJu?bjNOe$DAr{&hdJLmg39 zUT<6OsmqdP^S+w?!A6T6?DM?&?MZ$e@5i>ScI(}vjoR_|Y=36cQk_q{tCwd410 zZ`AI5-?r1(p2w-wfA0s!o6J76ZM7fTc4j&M?|pc81N+eGKkWU#z7y<&zrDMGz3+_u z^j?qH{+s{SwjcfY-iG!!fA_&g?MJq)_G8mO?H~UBgUvtuV{YVecYn`#ZrQ;5uGja^ zi+AAr!0WrBt+7Cxae@zVg;$eUmvX^8(bw|Hyubg$yA$KXR!{G%&e(Q(ENQmZ7qstq zP*%`Zmdb6PWn5ZXy4w^T0pGKB)os9sj5q zS_AIcc)g$ojNV7y74Pli!ELZUwBZMv!#`@Yn$G9YCinEz3H_8_{^mz_H?$x9=uQ}; zyk>v=cQ(Gxw12Q#p!So0e1D_nc_RPxKlzCr-?0DJ#(M0BAM8W(E#q;+d97<>AD@HX zKd-(E+WM|&PsK^c{vX-cudzlA2sx0PFpu<_&kt;_$A0<`ZOovd4tTw&mcsatF=m2) z>UHpSjR*c64uu$sIz%lQws#`!;e zng_;p0JT8>H`cEPy{YN=!!e0owaT{FyXQg2O0<9S_r|x_|7UF&dE0Wm<+k0|VjuhF zUS~Lt&%x)?XGPn;kAGhu55_5ZWBkXq#-bs%8gtD>s0A)x%p-j*-<$)eR)+RgOVE~T zEwr5;Gpz>g4muvRJl49hfOf_TSQC~)dx}5DD;N$ON3O{^x&J5s^q+0I^%!&wbPaS3 zbPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3 zbPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3 zbPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3 zbPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3 zbPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3 zbPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3 zbPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3 zbPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3 zbPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3 zbPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3 zbPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3 zbPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3 zbPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3 zbPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3 zbPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3 zbPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3 zbPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3 zbPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3 zbPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3bPaS3 zbPaS3bPaS3bPaS3bPaS3bPYfQ|NZ~?{GRrlo&W6CK5yH!e`Eds=9#vA&i1cp|H7_C z`|qqhY5#*AWA-mTJNSQYekc3ac3!uWkHNxxA&c`{zyXQKw7wW_ltXRUWCT!(Q?Yxbr2 zu;a-xd@awb^i!KnUs}(T3HW{I<8uBK*I-|5>iAWjP2bhG5gVz0%{ba|*ni-hQafOO zRxOD9bDT-@31}PRQ99-vjQ&!rPl*4Lme`l+(X^YiDmqq8SKDjUi2I%Yg)z}sqK%kq zyqSZ!{zUjMX*l(n-n9m-J)bWzi@qt=r}p!@r@?=Uozwx=gZqGg)t=XDmGy-jr0Zyn zz%@AjvVB_o@A^Iy=lOVQtaoW2gnw$$>$JFiLi|th%>55qZCGMBkF}uY_pT9kkLoor zUyyGHtfk>^4eq)kuJ=J0u%UjxA zVPB0o^n=YMEbnqG>jr)6d9bg_@vi$2b20Ky&%c*Qb)dk%=K6$dfoi+e{y5c!)_YFv zTb1!ru2l=gw&R^TqgO2+g8wPL$RYhMOfNZy_bK_(vz|2n%uDjJW+>&iq=^IeP3e8< z{!*R5HQ29h_O<-f@q9j9gKhd!ANTF*PnG{Lf48wFX{pdYY5q&xY2Ol-cR5z?)oQaA z+%Nq5wHRx`0a-6;cUKNh`n*H1yvsY3Vz_D_bpY=*azF9kVomogW%}RQJRG+4yt&8o z*;o%+e)U>WT4TuC0c$tb_OxD*`QB6?Y6p%v;TqEKXK`ojr*c%=&%B?A<_bD&ae#f~ zW-ez-k0bZqFTKvx@q7>Or{4cyc~Slg^Ev7QxGh_LFTIoRYsF;s{LJLPrWfd}RTnz{C(pm;qn5W? z|LXcOmRi^JTK6pY?J2&N?kDH)WMBQ#>rEZ+-)GLY#;6s`o%Zd?e@#c!VCVnv{I_zj zbl*-Inqa(j%{ltlW2X38x?fpG`#-#t+rNL&>O!=C_kzs}8n*P_yBv>LXg#R4TQNV? zeXKV&Z@~A4+B|jb{?htb`?QUFvu^q4@W=W6huS8`r<%J4=QSPr{#`V`VCOy>)rjn`yq#3 zU#)FwlzxfU=N_wb_!5)k75+bX+k=0-Zn|&epU*V+9p)a#IJSH%AE&OL9r*VQw6?)C z_LFUGv+odI@6&p|efOPq{#x3?zLqYrZ?4Yg9!JiSkMnb7Q`?8)Ki$LL5$gy(&)oNz zd)!ofOZzovr>!6V_aXc8@JT!WwDYH0Drs%1Z?4Yg9!Jj7I$qZ6iAm{O$p62z^?=&w z`+#3BWY@P^#AitJgIJ&Z_T2Z~_c-K7b2z7c+M4V4Kg`-~PVRXOx!zZQbIMbG^DUmA|<<;Bj+xHut#1J#{~^ephbL-jjcFpZx!1 zb-;}L|10Ak#=23eDS4lJCjY#X+ZKoW?fU_9ub#Twe|1)CRlCa%g+r{kA&?*YKwI;yi+0pKo4!CjUN; z*baGc{A(>ZiFz=l&pG?oVg=VZbPZVRS?9!M&M}AJyLHVH`z3AG`u5~s+0(XXl-q~k zpVtoFttaL&Uz#(-I#7#Swcj+)wG?>QZwVh2{^#htc8v0fV-C$f?&0srzS=V$vcJXZ zA@)^u*qFQOIeYLQzRMYJ;67upFh+C#V*dSnVCgpI5B7XCtx?*p)d|jX=xfK-`*l7M zzbBMxcCGIan(5cPKfcu<54F6P`oV4Z-ex*V-`z@MSG|uqrmyRE`wd<>AxE>~5IFi!NXc$N1tXVGiz_3cv5Yki&J`-kDVxv1r`)bF*y?ZiEG zRcF1VN9ve9XgrSLH4e~!@bR0>dtYLDNneG3z7Ku%-CYeR#kAeWJk8*rIaXiNhW5?X zUO(=1>V@X2)Ye?jX|K$GN%v%;`eYk?;7f!@xScv4@&h| zYQUTvFFB@jeL7svjr)>Z&N(*YzofNxAIJUh+dIZK8QV(?_`ZYiPu}7GEFEP7*@Ji~k5|0DXOUyluKH$N!e!;WG9oabF~OkqBuF|M@_ znfLtK%D=Vej435upHv^}g}+y+&mVIV+jC-fU&>)0OJ zaV#g-fqybz@H{Sg;eT{(;lA`N=v%^i>DU7Qr8uL{=cBZ}mL|5>*@6E#I_ELq4(*fR z?-2Lkd#vRT%zsHUzOSagQvcriuW_aQB`#B+=fA*wngd|pEBSmOU1y>W=yCdNC8n43 zE#SWte_t22+^#)t#Ia~Co%idM=TeFPwevSzPg$?&X-WU8{3~O#+rmHC&RDm7kD+fL zV?A--5xCCaJR*Ec!1n}iaSpMhChT*p)|1Nj1l0iByVebU|Ec+1ulsR1uND{f@A{t1 z+muhgt5X;IXlu#2X^iwd#BFjzjxtZ?;CEyf+_!A@b@f>aZN5jzx4thTK1<>`_gLmR z`dTfY(oJc9NvqUx&6m#kB=yze!v0b$O0l2ntL1t1@9SQNweimLU*ZFOe!L!S&*Z;J?H)d7tA?n*ULJVjVy~V!M?0+VK

rE_Jc)b{*w`?UDy{k<AYYx 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 literal 0 HcmV?d00001 diff --git a/NEWorld/SDL2-CS.dll b/NEWorld/SDL2-CS.dll deleted file mode 100644 index 2d4862514a0c5023f07c6afd237d45b1a0601118..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 88064 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 PcI8m?-~atPmcaiHS(;Q; diff --git a/NEWorld/Widget.cs b/NEWorld/Widget.cs deleted file mode 100644 index f54e14f..0000000 --- a/NEWorld/Widget.cs +++ /dev/null @@ -1,77 +0,0 @@ -// -// NEWorld: Widget.cs -// NEWorld: A Free Game with Similar Rules to Minecraft. -// Copyright (C) 2015-2018 NEWorld Team -// -// NEWorld is free software: you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published -// by the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// NEWorld is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY -// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General -// Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with NEWorld. If not, see . -// - -using NuklearSharp; - -namespace NEWorld -{ - // widget base class - public abstract class Widget - { - public Widget(string name, Nuklear.nk_rect size, uint flags) - { - Name = name; - _size = size; - _flags = flags; - Open = true; - } - - public void _render(NkSdl ctx) - { - if (ctx.Begin(Name, _size, _flags)) - { - Render(ctx); - ctx.End(); - } - } - - public abstract void Update(); - - public bool Open { get; set; } - - public string Name { get; } - - protected abstract void Render(NkSdl ctx); - - private readonly uint _flags; - private readonly Nuklear.nk_rect _size; - } - - // callback style widget - public class WidgetCallback : Widget - { - public delegate void RenderCallback(NkSdl ctx); - - public delegate void UpdateCallback(); - - public WidgetCallback(string name, Nuklear.nk_rect size, uint flags, - RenderCallback renderFunc, UpdateCallback updateFunc = null) : base(name, size, flags) - { - _renderFunc = renderFunc; - _updateFunc = updateFunc; - } - - protected override void Render(NkSdl ctx) => _renderFunc(ctx); - - public override void Update() => _updateFunc?.Invoke(); - - private readonly RenderCallback _renderFunc; - private readonly UpdateCallback _updateFunc; - } -} \ No newline at end of file diff --git a/NEWorld/WidgetManager.cs b/NEWorld/WidgetManager.cs deleted file mode 100644 index e08e424..0000000 --- a/NEWorld/WidgetManager.cs +++ /dev/null @@ -1,47 +0,0 @@ -// -// NEWorld: WidgetManager.cs -// NEWorld: A Free Game with Similar Rules to Minecraft. -// Copyright (C) 2015-2018 NEWorld Team -// -// NEWorld is free software: you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published -// by the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// NEWorld is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY -// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General -// Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with NEWorld. If not, see . -// - -using System.Collections.Generic; - -namespace NEWorld -{ - internal class WidgetManager : Dictionary - { - public WidgetManager(NkSdl nkctx) => _mNkContext = nkctx; - - public void Render() - { - foreach (var widget in this) - widget.Value._render(_mNkContext); - _mNkContext.End(); - // TODO: add an option to adjust the arguments - _mNkContext.Draw(); - } - - public void Update() - { - foreach (var widget in this) - widget.Value.Update(); - } - - public void Add(Widget widget) => Add(widget.Name, widget); - - private readonly NkSdl _mNkContext; - } -} \ No newline at end of file diff --git a/NEWorld/Window.cs b/NEWorld/Window.cs deleted file mode 100644 index c07de57..0000000 --- a/NEWorld/Window.cs +++ /dev/null @@ -1,152 +0,0 @@ -// -// NEWorld: Window.cs -// NEWorld: A Free Game with Similar Rules to Minecraft. -// Copyright (C) 2015-2018 NEWorld Team -// -// NEWorld is free software: you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published -// by the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// NEWorld is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY -// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General -// Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with NEWorld. If not, see . -// - -using System; -using OpenGL; -using SDL2; - -namespace NEWorld -{ - public struct MouseState - { - public int X, Y; - public bool Left, Mid, Right, Relative; - } - - public class Window - { - private Window(string title, int width, int height) - { - _title = title; - _width = width; - _height = height; - if (SDL.SDL_Init(SDL.SDL_INIT_VIDEO) < 0) - throw new Exception("SDL could not initialize! SDL_Error: " + SDL.SDL_GetError()); - SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_CONTEXT_MAJOR_VERSION, 4); - SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_CONTEXT_MINOR_VERSION, 5); - SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_STENCIL_SIZE, 8); - SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_CONTEXT_PROFILE_MASK, - (int) SDL.SDL_GLprofile.SDL_GL_CONTEXT_PROFILE_CORE); - - _window = SDL.SDL_CreateWindow(_title, 100, 100, _width, _height, - SDL.SDL_WindowFlags.SDL_WINDOW_OPENGL | SDL.SDL_WindowFlags.SDL_WINDOW_RESIZABLE); - _context = SDL.SDL_GL_CreateContext(_window); - SDL.SDL_GL_SetSwapInterval(0); // VSync - MakeCurrentDraw(); - Gl.Init(SDL.SDL_GL_GetProcAddress); - _nuklearContext = new NkSdl(_window); - } - - ~Window() - { - SDL.SDL_DestroyWindow(_window); - SDL.SDL_GL_DeleteContext(_context); - SDL.SDL_Quit(); - } - - public void MakeCurrentDraw() => SDL.SDL_GL_MakeCurrent(_window, _context); - - public void SwapBuffers() => SDL.SDL_GL_SwapWindow(_window); - - public static unsafe byte* GetKeyBoardState() => (byte*) SDL.SDL_GetKeyboardState(out var number); - - public int GetWidth() => _width; - - public int GetHeight() => _height; - - public void PollEvents() - { - if (SDL.SDL_GetRelativeMouseMode() == SDL.SDL_bool.SDL_TRUE) - { - var buttons = SDL.SDL_GetRelativeMouseState(out _mouse.X, out _mouse.Y); - _mouse.Left = (buttons & SDL.SDL_BUTTON_LEFT) != 0; - _mouse.Right = (buttons & SDL.SDL_BUTTON_RIGHT) != 0; - _mouse.Mid = (buttons & SDL.SDL_BUTTON_MIDDLE) != 0; - _mouse.Relative = true; - } - else - { - _prevMouse = _mouse; - var buttons = SDL.SDL_GetMouseState(out _mouse.X, out _mouse.Y); - _mouse.Left = (buttons & SDL.SDL_BUTTON_LEFT) != 0; - _mouse.Right = (buttons & SDL.SDL_BUTTON_RIGHT) != 0; - _mouse.Mid = (buttons & SDL.SDL_BUTTON_MIDDLE) != 0; - if (_mouse.Relative) _prevMouse = _mouse; - _mouse.Relative = false; - } - - _nuklearContext.InputBegin(); - while (SDL.SDL_PollEvent(out var e) != 0) - { - _nuklearContext.HandleEvent(ref e); - switch (e.type) - { - case SDL.SDL_EventType.SDL_QUIT: - _shouldQuit = true; - break; - case SDL.SDL_EventType.SDL_WINDOWEVENT: - switch (e.window.windowEvent) - { - case SDL.SDL_WindowEventID.SDL_WINDOWEVENT_RESIZED: - case SDL.SDL_WindowEventID.SDL_WINDOWEVENT_SIZE_CHANGED: - _width = e.window.data1; - _height = e.window.data2; - break; - } - - break; - } - } - - _nuklearContext.InputEnd(); - } - - public bool ShouldQuit() => _shouldQuit; - - public NkSdl GetNkContext() => _nuklearContext; - - /** - * \brief Get the relative motion of mouse - * \return The relative motion of mouse - */ - public MouseState GetMouseMotion() - { - var res = _mouse; - res.X -= _prevMouse.X; - res.Y -= _prevMouse.Y; - return res; - } - - public static void LockCursor() => SDL.SDL_SetRelativeMouseMode(SDL.SDL_bool.SDL_TRUE); - - public static void UnlockCursor() => SDL.SDL_SetRelativeMouseMode(SDL.SDL_bool.SDL_FALSE); - - private static Window _win; - - public static Window GetInstance(string title = "", int width = 0, int height = 0) => - _win ?? (_win = new Window(title, width, height)); - - private readonly string _title; - private int _width, _height; - private MouseState _mouse, _prevMouse; - private bool _shouldQuit; - private readonly IntPtr _window, _context; - private readonly NkSdl _nuklearContext; - } -} \ No newline at end of file diff --git a/NEWorld/packages.config b/NEWorld/packages.config deleted file mode 100644 index bfbdfc9..0000000 --- a/NEWorld/packages.config +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/NEWorldShell/Cli.cs b/NEWorldShell/Cli.cs index 2321da3..c725643 100644 --- a/NEWorldShell/Cli.cs +++ b/NEWorldShell/Cli.cs @@ -58,7 +58,7 @@ private void InitBuiltinCommands() cmd => { Services.Get("Game.Server").Stop(); - Console.WriteLine("Server RPC stopped."); + LogPort.Debug("Server RPC stopped."); _commands.SetRunningStatus(false); return new CommandExecuteStat(true, ""); }); @@ -73,7 +73,7 @@ private void InitBuiltinCommands() _commands.RegisterCommand("server.connections", new CommandInfo("internal", "Count Connections."), cmd => { - Console.WriteLine(Services.Get("Game.Server").CountConnections()); + LogPort.Debug($"{Services.Get("Game.Server").CountConnections()}"); return new CommandExecuteStat(true, ""); }); @@ -100,7 +100,7 @@ private void InitBuiltinCommands() GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce; GC.Collect(); GC.WaitForFullGCComplete(); - Console.WriteLine("GC Completed"); + LogPort.Debug("GC Completed"); return new CommandExecuteStat(true, ""); }); } diff --git a/NEWorldShell/Command.cs b/NEWorldShell/Command.cs index 0683794..f39395d 100644 --- a/NEWorldShell/Command.cs +++ b/NEWorldShell/Command.cs @@ -95,7 +95,7 @@ public void InputLoop() _waitingForInput = false; var result = HandleCommand(new Command(input)); if (result.Info != "") - Console.WriteLine(result.Info); + Core.LogPort.Debug(result.Info); } } diff --git a/NEWorldShell/NEWorldShell.csproj b/NEWorldShell/NEWorldShell.csproj index 3960773..4f23bb2 100644 --- a/NEWorldShell/NEWorldShell.csproj +++ b/NEWorldShell/NEWorldShell.csproj @@ -52,6 +52,10 @@ {f83c4945-abff-4856-94a3-d0d31c976a11} Game + + {15bdbfd8-daea-4324-9b09-7ab531bfe5ef} + Main + - \ No newline at end of file diff --git a/OpenGL/Others.cs b/OpenGL/Others.cs deleted file mode 100644 index ceb52c8..0000000 --- a/OpenGL/Others.cs +++ /dev/null @@ -1,93 +0,0 @@ -// -// OpenGL: Others.cs -// NEWorld: A Free Game with Similar Rules to Minecraft. -// Copyright (C) 2015-2018 NEWorld Team -// -// NEWorld is free software: you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published -// by the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// NEWorld is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY -// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General -// Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with NEWorld. If not, see . -// - -using System; - -namespace OpenGL -{ - static partial class Gl - { - // Draw - public delegate void DrawArraysProc(uint mode, int first, int count); - - public delegate void DrawElementsProc(uint mode, int count, uint type, IntPtr indicies); - - public static DrawArraysProc DrawArrays; - - public static DrawElementsProc DrawElements; - - // Context Op - public delegate void ViewportProc(int x, int y, int width, int height); - - public delegate void ClearColorProc(float red, float green, float blue, float alpha); - - public delegate void ClearDepthProc(float depth); - - public delegate void ClearProc(uint mask); - - public delegate void LineWidthProc(float width); - - public delegate void EnableProc(uint cap); - - public delegate void DisableProc(uint cap); - - public delegate void DepthFuncProc(uint cap); - - public delegate void CullFaceProc(uint cap); - - public delegate void BlendEquationProc(uint mode); - - public delegate void BlendFuncProc(uint sfactor, uint dfactor); - - public delegate void ScissorProc(int x, int y, int width, int height); - - public static ViewportProc Viewport; - public static ClearColorProc ClearColor; - public static ClearProc Clear; - public static ClearDepthProc ClearDepth; - public static LineWidthProc LineWidth; - public static EnableProc Enable; - public static DisableProc Disable; - public static DepthFuncProc DepthFunc; - public static CullFaceProc CullFaceOption; - public static BlendEquationProc BlendEquation; - public static BlendFuncProc BlendFunc; - public static ScissorProc Scissor; - - static partial void InitOthers() - { - // Draw - DrawArrays = Get("glDrawArrays"); - // Context - Viewport = Get("glViewport"); - ClearColor = Get("glClearColor"); - ClearDepth = Get("glClearDepth"); - Clear = Get("glClear"); - LineWidth = Get("glLineWidth"); - Enable = Get("glEnable"); - Disable = Get("glDisable"); - BlendEquation = Get("glBlendEquation"); - BlendFunc = Get("glBlendFunc"); - Scissor = Get("glScissor"); - DrawElements = Get("glDrawElements"); - DepthFunc = Get("glDepthFunc"); - CullFaceOption = Get("glCullFace"); - } - } -} \ No newline at end of file diff --git a/OpenGL/Properties/AssemblyInfo.cs b/OpenGL/Properties/AssemblyInfo.cs deleted file mode 100644 index 7090d74..0000000 --- a/OpenGL/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,54 +0,0 @@ -// -// OpenGL: AssemblyInfo.cs -// NEWorld: A Free Game with Similar Rules to Minecraft. -// Copyright (C) 2015-2018 NEWorld Team -// -// NEWorld is free software: you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published -// by the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// NEWorld is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY -// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General -// Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with NEWorld. If not, see . -// - -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("OpenGL")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("OpenGL")] -[assembly: AssemblyCopyright("Copyright © 2018")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("4EB6C361-3E67-4A4E-8B2B-9C8D29D8F852")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] \ No newline at end of file diff --git a/OpenGL/RenderBuffer.cs b/OpenGL/RenderBuffer.cs deleted file mode 100644 index 7c89185..0000000 --- a/OpenGL/RenderBuffer.cs +++ /dev/null @@ -1,72 +0,0 @@ -// -// OpenGL: RenderBuffer.cs -// NEWorld: A Free Game with Similar Rules to Minecraft. -// Copyright (C) 2015-2018 NEWorld Team -// -// NEWorld is free software: you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published -// by the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// NEWorld is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY -// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General -// Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with NEWorld. If not, see . -// - -using Core.Math; -using Core.Utilities; - -namespace OpenGL -{ - public static partial class Gl - { - public const uint RenderBuffer = 0x8D41; - - internal unsafe delegate void CreateRenderbuffersProc(int n, uint* renderbuffers); - - internal unsafe delegate void DeleteRenderbuffersProc(int n, uint* renderbuffers); - - internal delegate void NamedRenderbufferStorageProc(uint renderbuffer, uint format, int width, int height); - - internal static CreateRenderbuffersProc CreateRenderbuffers; - internal static DeleteRenderbuffersProc DeleteRenderbuffers; - internal static NamedRenderbufferStorageProc NamedRenderbufferStorage; - - static partial void InitRenderBuffer() - { - CreateRenderbuffers = Get("glCreateRenderbuffers"); - DeleteRenderbuffers = Get("glDeleteRenderbuffers"); - NamedRenderbufferStorage = Get("glNamedRenderbufferStorage"); - } - } - - public class RenderBuffer : StrictDispose - { - public unsafe RenderBuffer() - { - fixed (uint* addr = &_hdc) - { - Gl.CreateRenderbuffers(1, addr); - } - } - - protected override unsafe void Release() - { - fixed (uint* addr = &_hdc) - { - Gl.DeleteRenderbuffers(1, addr); - } - } - - public void SetStorage(PixelInternalFormats fmt, Vec2 size) => - Gl.NamedRenderbufferStorage(_hdc, (uint) fmt, size.X, size.Y); - - public uint Raw() => _hdc; - - private uint _hdc; - } -} \ No newline at end of file diff --git a/OpenGL/Shader.cs b/OpenGL/Shader.cs deleted file mode 100644 index 4269f98..0000000 --- a/OpenGL/Shader.cs +++ /dev/null @@ -1,182 +0,0 @@ -// -// OpenGL: Shader.cs -// NEWorld: A Free Game with Similar Rules to Minecraft. -// Copyright (C) 2015-2018 NEWorld Team -// -// NEWorld is free software: you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published -// by the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// NEWorld is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY -// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General -// Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with NEWorld. If not, see . -// - -using System; -using System.Collections.Generic; -using System.Runtime.InteropServices; -using Core.Utilities; - -namespace OpenGL -{ - static partial class Gl - { - public const uint FragmentShader = 0x8B30; - public const uint VertexShader = 0x8B31; - public const uint GeometryShader = 0x8DD9; - internal const uint InfoLogLength = 0x8B84; - internal const uint CompileStatus = 0x8B81; - internal const uint LinkStatus = 0x8B82; - - internal delegate void CompileShaderProc(uint shader); - - internal delegate uint CreateProgramProc(); - - internal delegate uint CreateShaderProc(uint type); - - internal delegate void DeleteProgramProc(uint program); - - internal delegate void DeleteShaderProc(uint shader); - - internal unsafe delegate void GetProgramInfoLogProc(uint prog, int bufSize, int* length, char* log); - - internal unsafe delegate void GetProgramivProc(uint program, uint pname, int* param); - - internal unsafe delegate void GetShaderInfoLogProc(uint shader, int bufSize, int* length, char* log); - - internal unsafe delegate void GetShaderivProc(uint shader, uint pname, int* param); - - internal delegate void AttachShaderProc(uint program, uint shader); - - internal delegate void LinkProgramProc(uint program); - - internal unsafe delegate void ShaderSourceProc(uint shader, int count, char** str, int* length); - - internal delegate void UseProgramProc(uint program); - - internal delegate void ProgramUniform1IProc(uint program, int location, int v0); - - internal static CompileShaderProc CompileShader; - internal static CreateProgramProc CreateProgram; - internal static CreateShaderProc CreateShader; - internal static DeleteProgramProc DeleteProgram; - internal static DeleteShaderProc DeleteShader; - internal static GetProgramInfoLogProc GetProgramInfoLog; - internal static GetProgramivProc GetProgramiv; - internal static GetShaderInfoLogProc GetShaderInfoLog; - internal static GetShaderivProc GetShaderiv; - internal static AttachShaderProc AttachShader; - internal static LinkProgramProc LinkProgram; - internal static ShaderSourceProc ShaderSource; - internal static UseProgramProc UseProgram; - internal static ProgramUniform1IProc ProgramUniform1I; - - static partial void InitShader() - { - CompileShader = Get("glCompileShader"); - CreateProgram = Get("glCreateProgram"); - CreateShader = Get("glCreateShader"); - DeleteProgram = Get("glDeleteProgram"); - DeleteShader = Get("glDeleteShader"); - GetProgramInfoLog = Get("glGetProgramInfoLog"); - GetProgramiv = Get("glGetProgramiv"); - GetShaderInfoLog = Get("glGetShaderInfoLog"); - GetShaderiv = Get("glGetShaderiv"); - AttachShader = Get("glAttachShader"); - LinkProgram = Get("glLinkProgram"); - ShaderSource = Get("glShaderSource"); - UseProgram = Get("glUseProgram"); - ProgramUniform1I = Get("glProgramUniform1iEXT"); - } - } - - public class Shader : StrictDispose - { - public unsafe Shader(uint eShaderType, string strFileData) - { - var shader = Gl.CreateShader(eShaderType); - var file = Gl.Utf8ToNative(strFileData); - var ptr = Marshal.AllocHGlobal(file.Length); - Marshal.Copy(file, 0, ptr, file.Length); - var cString = (char*) ptr; - Gl.ShaderSource(shader, 1, &cString, null); - Marshal.FreeHGlobal(ptr); - Gl.CompileShader(shader); - int status; - Gl.GetShaderiv(shader, Gl.CompileStatus, &status); - if (status == 0) - { - int infoLogLength; - Gl.GetShaderiv(shader, Gl.InfoLogLength, &infoLogLength); - var strInfoLog = Marshal.AllocHGlobal(infoLogLength + 1); - Gl.GetShaderInfoLog(shader, infoLogLength, null, (char*) strInfoLog); - var infoLog = Gl.Utf8ToManaged(strInfoLog); - Marshal.FreeHGlobal(strInfoLog); - throw new Exception("Compile failure in " + GetShaderTypeString(eShaderType) + " shader:" + infoLog); - } - - _hdc = shader; - } - - private static string GetShaderTypeString(uint type) - { - switch (type) - { - case Gl.VertexShader: - return "vertex"; - case Gl.GeometryShader: - return "geometry"; - case Gl.FragmentShader: - return "fragment"; - default: - return "unknown type"; - } - } - - protected override void Release() => Gl.DeleteShader(_hdc); - - public uint Raw() => _hdc; - - private readonly uint _hdc; - } - - public class Program : StrictDispose - { - public unsafe void Link(IEnumerable shaderList) - { - var programId = Gl.CreateProgram(); - foreach (var sd in shaderList) - Gl.AttachShader(programId, sd.Raw()); - Gl.LinkProgram(programId); - int status; - Gl.GetProgramiv(programId, Gl.LinkStatus, &status); - if (status == 0) - { - int infoLogLength; - Gl.GetProgramiv(programId, Gl.InfoLogLength, &infoLogLength); - var strInfoLog = Marshal.AllocHGlobal(infoLogLength + 1); - Gl.GetProgramInfoLog(programId, infoLogLength, null, (char*) strInfoLog); - var infoLog = Gl.Utf8ToManaged(strInfoLog); - Marshal.FreeHGlobal(strInfoLog); - throw new Exception("Linker failure: " + infoLog); - } - - _hdc = programId; - } - - protected override void Release() => Gl.DeleteProgram(_hdc); - - public void Use() => Gl.UseProgram(_hdc); - - public void Uniform(int location, int val) => Gl.ProgramUniform1I(_hdc, location, val); - - public uint Raw() => _hdc; - - private uint _hdc; - } -} \ No newline at end of file diff --git a/OpenGL/Texture.cs b/OpenGL/Texture.cs deleted file mode 100644 index 35cebea..0000000 --- a/OpenGL/Texture.cs +++ /dev/null @@ -1,146 +0,0 @@ -// -// OpenGL: Texture.cs -// NEWorld: A Free Game with Similar Rules to Minecraft. -// Copyright (C) 2015-2018 NEWorld Team -// -// NEWorld is free software: you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published -// by the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// NEWorld is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY -// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General -// Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with NEWorld. If not, see . -// - -using System; -using Core; -using Core.Math; -using Core.Utilities; - -namespace OpenGL -{ - public static partial class Gl - { - internal const uint Texture2D = 0x0DE1; - - internal unsafe delegate void CreateTexturesProc(uint target, int n, uint* textures); - - internal unsafe delegate void DeleteTexturesProc(int n, uint* textures); - - internal delegate void TextureParameteriProc(uint texture, uint pname, int param); - - internal delegate void TextureParameterfProc(uint texture, uint pname, float param); - - internal delegate void BindTextureUnitProc(uint unit, uint texture); - - internal delegate void TextureStorage2DProc(uint texture, int levels, uint format, int width, int height); - - internal delegate void TextureSubImage2DProc(uint texture, int level, int xoffset, int yoffset, int width, - int height, uint format, uint type, IntPtr pixels); - - internal static CreateTexturesProc CreateTextures; - internal static DeleteTexturesProc DeleteTextures; - internal static TextureParameteriProc TextureParameteri; - internal static TextureParameterfProc TextureParameterf; - internal static BindTextureUnitProc BindTextureUnit; - internal static TextureStorage2DProc TextureStorage2D; - internal static TextureSubImage2DProc TextureSubImage2D; - - static partial void InitTexture() - { - CreateTextures = Get("glCreateTextures"); - DeleteTextures = Get("glDeleteTextures"); - TextureParameteri = Get("glTextureParameteri"); - TextureParameterf = Get("glTextureParameterf"); - BindTextureUnit = Get("glBindTextureUnit"); - TextureStorage2D = Get("glTextureStorage2D"); - TextureSubImage2D = Get("glTextureSubImage2D"); - } - } - - public class Texture : StrictDispose - { - public unsafe Texture(int levels, PixelInternalFormats internalFormat, Vec2 size) - { - fixed (uint* addr = &_hdc) - { - Gl.CreateTextures(Gl.Texture2D, 1, addr); - } - - Gl.TextureStorage2D(_hdc, levels, (uint) internalFormat, size.X, size.Y); - } - - protected override unsafe void Release() - { - fixed (uint* addr = &_hdc) - { - Gl.DeleteTextures(1, addr); - } - } - - public enum Filter - { - Nearest = 0x2600, - Linear = 0x2601, - NearestMipmapNearest = 0x2700, - LinearMipmapNearest = 0x2701, - NearestMipmapLinear = 0x2702, - LinearMipmapLinear = 0x2703 - } - - public enum Warp - { - ClampToEdge = 0x812F, - ClampToBorder = 0x812D, - MirroredRepeat = 0x8370, - Repeat = 0x2901, - MirrorClampToEdge = 0x8743 - } - - private void SetParameter(uint name, int param) => Gl.TextureParameteri(_hdc, name, param); - - private void SetParameter(uint name, float param) => Gl.TextureParameterf(_hdc, name, param); - - public Filter MinifyingFilter - { - set => SetParameter(0x2801, (int) value); - } - - public Filter MagnificationFilter - { - set => SetParameter(0x2800, (int) value); - } - - public Warp WarpS - { - set => SetParameter(0x2802, (int) value); - } - - public Warp WarpT - { - set => SetParameter(0x2803, (int) value); - } - - public void Use(uint slot) => Gl.BindTextureUnit(slot, _hdc); - - public static void UseRaw(uint slot, uint handle) => Gl.BindTextureUnit(slot, handle); - - public unsafe void Image(int level, Rect area, PixelTypes format, PixelDataFormats type, byte[] data) - { - fixed (byte* ptr = &data[0]) - { - Gl.TextureSubImage2D(_hdc, level, area.Min.X, area.Min.Y, area.Delta.X, area.Delta.Y, (uint) format, - (uint) type, (IntPtr) ptr); - } - } - - public uint Raw() => _hdc; - - private uint _hdc; - } -} \ No newline at end of file diff --git a/OpenGL/VertexArray.cs b/OpenGL/VertexArray.cs deleted file mode 100644 index 133318b..0000000 --- a/OpenGL/VertexArray.cs +++ /dev/null @@ -1,113 +0,0 @@ -// -// OpenGL: VertexArray.cs -// NEWorld: A Free Game with Similar Rules to Minecraft. -// Copyright (C) 2015-2018 NEWorld Team -// -// NEWorld is free software: you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published -// by the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// NEWorld is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY -// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General -// Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with NEWorld. If not, see . -// - -using System; -using Core.Utilities; - -namespace OpenGL -{ - public static partial class Gl - { - internal unsafe delegate void CreateVertexArraysProc(int n, uint* arrays); - - internal unsafe delegate void DeleteVertexArraysProc(int n, uint* arrays); - - internal delegate void BindVertexArrayProc(uint array); - - internal delegate void EnableVertexArrayAttribProc(uint vaobj, uint index); - - internal delegate void DisableVertexArrayAttribProc(uint vaobj, uint index); - - internal delegate void VertexArrayAttribFormatProc(uint vaobj, uint attribindex, int size, uint type, - byte normalized, uint relativeoffset); - - internal delegate void VertexArrayAttribBindingProc(uint vaobj, uint attribindex, uint bindingindex); - - internal delegate void VertexArrayVertexBufferProc(uint vaobj, uint bindingindex, uint buffer, - UIntPtr offset, int stride); - - internal static CreateVertexArraysProc CreateVertexArrays; - internal static DeleteVertexArraysProc DeleteVertexArrays; - internal static BindVertexArrayProc BindVertexArray; - internal static EnableVertexArrayAttribProc EnableVertexArrayAttrib; - internal static DisableVertexArrayAttribProc DisableVertexArrayAttrib; - internal static VertexArrayAttribFormatProc VertexArrayAttribFormat; - internal static VertexArrayAttribBindingProc VertexArrayAttribBinding; - internal static VertexArrayVertexBufferProc VertexArrayVertexBuffer; - - static partial void InitVertexArray() - { - DeleteVertexArrays = Get("glDeleteVertexArrays"); - BindVertexArray = Get("glBindVertexArray"); - EnableVertexArrayAttrib = Get("glEnableVertexArrayAttrib"); - DisableVertexArrayAttrib = Get("glDisableVertexArrayAttrib"); - VertexArrayAttribFormat = - Get("glVertexArrayAttribFormat"); - VertexArrayAttribBinding = - Get("glVertexArrayAttribBinding"); - VertexArrayVertexBuffer = Get("glVertexArrayVertexBuffer"); - CreateVertexArrays = Get("glCreateVertexArrays"); - } - } - - public class VertexArray : StrictDispose - { - public unsafe VertexArray() - { - fixed (uint* addr = &_hdc) - { - Gl.CreateVertexArrays(1, addr); - } - } - - protected override unsafe void Release() - { - fixed (uint* addr = &_hdc) - { - Gl.DeleteVertexArrays(1, addr); - } - } - - public void Use() => Gl.BindVertexArray(_hdc); - - public void EnableAttrib(uint index) => Gl.EnableVertexArrayAttrib(_hdc, index); - - public void DisableAttrib(uint index) => Gl.DisableVertexArrayAttrib(_hdc, index); - - public void AttribFormat(uint index, int size, uint type, bool normalized, uint relativeOffset) - { - byte norm = 0; - if (normalized) norm = 1; - Gl.VertexArrayAttribFormat(_hdc, index, size, type, norm, relativeOffset); - } - - public void AttribBinding(uint attribIndex, uint bufferIndex) => - Gl.VertexArrayAttribBinding(_hdc, attribIndex, bufferIndex); - - public void BindBuffer(uint index, DataBuffer buffer, uint offset, int stride) => - Gl.VertexArrayVertexBuffer(_hdc, index, buffer.Raw(), (UIntPtr) offset, stride); - - public void BindBuffer(uint index, ConstDataBuffer buffer, uint offset, int stride) => - Gl.VertexArrayVertexBuffer(_hdc, index, buffer.Raw(), (UIntPtr) offset, stride); - - public uint Raw() => _hdc; - - private uint _hdc; - } -} \ No newline at end of file From 131af5249606a8127468af6defd7472c60df74b8 Mon Sep 17 00:00:00 2001 From: DWVoid Date: Wed, 30 Jan 2019 05:21:14 +0800 Subject: [PATCH 02/23] Full Automatic Service Discovery --- Core/Module/Module.cs | 2 - Core/Network/ConnectionHost.cs | 3 -- Core/Network/Protocols.cs | 1 - Core/Services.cs | 89 +++++++++++++++++++++++++--------- NEWorld/MainScript.cs | 2 - NEWorldShell/Program.cs | 1 - 6 files changed, 67 insertions(+), 31 deletions(-) diff --git a/Core/Module/Module.cs b/Core/Module/Module.cs index c70efdb..9c98e44 100644 --- a/Core/Module/Module.cs +++ b/Core/Module/Module.cs @@ -65,8 +65,6 @@ public void Load(string moduleFile) } } } - - Services.ScanAssembly(assembly); } public IModule this[string name] => modules[name].Key; diff --git a/Core/Network/ConnectionHost.cs b/Core/Network/ConnectionHost.cs index 918162f..6aa04a8 100644 --- a/Core/Network/ConnectionHost.cs +++ b/Core/Network/ConnectionHost.cs @@ -51,7 +51,6 @@ public void Close() private async Task Start() { Valid = true; - LogPort.Debug("Connection Start"); var headerCache = new byte[8]; // ["NWRC"] + Int32BE(Protocol Id) while (Valid) { @@ -62,7 +61,6 @@ private async Task Start() { bytesRead = await Stream.ReadAsync(headerCache, 0, 8); } while (bytesRead != 8); - LogPort.Debug("Read Complete"); if (VerifyPackageValidity(headerCache, bytesRead)) try { @@ -84,7 +82,6 @@ private async Task Start() } } - LogPort.Debug("Connection CloseDown"); CloseDown(); } diff --git a/Core/Network/Protocols.cs b/Core/Network/Protocols.cs index 05d8abd..c90fbf7 100644 --- a/Core/Network/Protocols.cs +++ b/Core/Network/Protocols.cs @@ -42,7 +42,6 @@ protected override void HandleRequestData(byte[] data, NetworkStream stream) foreach (var prot in protocols) reply[current++] = new KeyValuePair(prot.Name(), prot.Id); Send(stream, Reply(request, SerialReply.PackSingleObjectAsBytes(reply))); - LogPort.Debug($"Client Connected, Protocol Handshake Proceeding on Client Session{request}"); } public override string Name() => "FetchProtocols"; diff --git a/Core/Services.cs b/Core/Services.cs index 9d6fd93..bcc9000 100644 --- a/Core/Services.cs +++ b/Core/Services.cs @@ -25,16 +25,22 @@ namespace Core { public sealed class DeclareServiceAttribute : Attribute { - public DeclareServiceAttribute(string name) => Name = name; - public readonly string Name; + + public DeclareServiceAttribute(string name) + { + Name = name; + } } public sealed class ServiceDependencyAttribute : Attribute { - public ServiceDependencyAttribute(params string[] dependencies) => Dependencies = dependencies; - public readonly string[] Dependencies; + + public ServiceDependencyAttribute(params string[] dependencies) + { + Dependencies = dependencies; + } } [Serializable] @@ -47,20 +53,23 @@ public ServiceManagerException(string message, Exception innerException) : base( public static class Services { - private class DisposeList - { - ~DisposeList() - { - foreach (var disposable in List) - disposable.Dispose(); - } + // Only for conflict resolve for multi-thread load + private static HashSet _processed = new HashSet(); - public readonly List List = new List(); - } + private static readonly DisposeList Dispose = new DisposeList(); + private static readonly Dictionary Ready = new Dictionary(); + private static readonly Dictionary Providers = new Dictionary(); + private static readonly Dictionary Dependencies = new Dictionary(); - static Services() => ScanAssembly(Assembly.GetExecutingAssembly()); + static Services() + { + UpdateDomainAssemblies(); + } - public static void Inject() where TP : IDisposable => Inject(typeof(TP)); + public static void Inject() where TP : IDisposable + { + Inject(typeof(TP)); + } public static TI Get(string name) { @@ -78,6 +87,33 @@ public static TI Get(string name) } } + private static void UpdateDomainAssemblies() + { + AppDomain.CurrentDomain.AssemblyLoad += OnAssemblyLoadServiceRegisterAgent; + var snapshot = AppDomain.CurrentDomain.GetAssemblies(); + foreach (var assembly in snapshot) + if (!CheckIfAssemblyProcessed(assembly)) + ScanAssembly(assembly); + + lock (_processed) + { + _processed = null; + } + } + + private static bool CheckIfAssemblyProcessed(Assembly assembly) + { + lock (_processed) + { + return _processed?.Contains(assembly.GetName()) ?? false; + } + } + + private static void OnAssemblyLoadServiceRegisterAgent(object sender, AssemblyLoadEventArgs args) + { + ScanAssembly(args.LoadedAssembly); + } + public static bool TryGet(string name, out TI ins) { try @@ -92,13 +128,16 @@ public static bool TryGet(string name, out TI ins) } } - public static void ScanAssembly(Assembly assembly) + private static void ScanAssembly(Assembly assembly) { - foreach (var type in assembly.GetExportedTypes()) + lock (assembly) { + _processed?.Add(assembly.GetName(true)); + } + + foreach (var type in assembly.GetExportedTypes()) if (type.IsDefined(typeof(DeclareServiceAttribute), false)) Inject(type); - } } private static void Inject(Type tp) @@ -122,9 +161,15 @@ private static object CreateService(string name) return instance; } - private static readonly DisposeList Dispose = new DisposeList(); - private static readonly Dictionary Ready = new Dictionary(); - private static readonly Dictionary Providers = new Dictionary(); - private static readonly Dictionary Dependencies = new Dictionary(); + private class DisposeList + { + public readonly List List = new List(); + + ~DisposeList() + { + foreach (var disposable in List) + disposable.Dispose(); + } + } } } \ No newline at end of file diff --git a/NEWorld/MainScript.cs b/NEWorld/MainScript.cs index eaf7aac..7afa80a 100644 --- a/NEWorld/MainScript.cs +++ b/NEWorld/MainScript.cs @@ -127,8 +127,6 @@ public class MainScript : SyncScript { private void InitializeModules() { - Core.Services.ScanAssembly(Assembly.Load("Core")); - Core.Services.ScanAssembly(Assembly.Load("Game")); Core.Module.Modules.Instance.Load("Main"); } diff --git a/NEWorldShell/Program.cs b/NEWorldShell/Program.cs index e490e8d..e2c8c43 100644 --- a/NEWorldShell/Program.cs +++ b/NEWorldShell/Program.cs @@ -28,7 +28,6 @@ internal class Program { public static void Main(string[] args) { - Services.ScanAssembly(Assembly.Load("Game")); Modules.Instance.Load("Main"); var cli = new ServerCommandLine(); var server = Services.Get("Game.Server"); From 099207fbc0349b6376d3f664ef8124f0c8439cb0 Mon Sep 17 00:00:00 2001 From: DWVoid Date: Thu, 31 Jan 2019 03:53:51 +0800 Subject: [PATCH 03/23] Make Things Render --- Core/Generic.cs | 176 +++++++-- Core/LogPort.cs | 15 +- Core/Math/Mat4.cs | 80 ++-- Core/Math/Vector.cs | 77 ++-- Core/Module/Module.cs | 25 +- Core/Network/Client.cs | 55 ++- Core/Network/ConnectionHost.cs | 369 +++++++++++++----- Core/Network/Protocol.cs | 98 +---- Core/Network/Protocols.cs | 129 +++--- Core/Network/Server.cs | 54 ++- Core/Path.cs | 10 +- Core/Services.cs | 7 +- Core/Utilities/EndianCheck.cs | 6 +- Core/Utilities/RateController.cs | 15 +- Core/Utilities/Singleton.cs | 80 ++-- Core/Utilities/StrictDispose.cs | 32 +- Game/ChunkService.cs | 7 +- Game/Network/Client.cs | 35 +- Game/Network/Protocols.cs | 85 ++-- Game/Network/Server.cs | 2 +- Game/TaskDispatcher.cs | 173 ++++---- Game/Terrain/Block.cs | 48 +-- Game/World/Chunk.cs | 8 +- Game/World/Player.cs | 2 - Game/World/World.cs | 8 +- Game/World/WorldTasks.cs | 70 ++-- NEWorld.sln | 1 + NEWorld.sln.DotSettings.user | 11 +- NEWorld/Assets/Ground.xkpromodel | 12 - NEWorld/Assets/MainScene.xkscene | 33 +- .../{Ground Material.xkmat => Material.xkmat} | 8 +- NEWorld/Assets/Sphere Material.xkmat | 12 - NEWorld/Assets/Sphere.xkpromodel | 11 - NEWorld/Effects/VertexTextureTerrain.cs | 23 ++ NEWorld/Effects/VertexTextureTerrain.xksl | 17 + NEWorld/MainScript.cs | 60 +-- NEWorld/NEWorld.csproj | 32 +- NEWorld/Renderer/RdChunk.cs | 46 +-- NEWorld/Renderer/RdTextures.cs | 18 + NEWorld/Renderer/RdWorld.cs | 4 - NEWorld/Renderer/VertexBuilder.cs | 50 +++ NEWorldShell/NEWorldShell.csproj | 4 +- NEWorldShell/Program.cs | 5 +- 43 files changed, 1156 insertions(+), 857 deletions(-) delete mode 100644 NEWorld/Assets/Ground.xkpromodel rename NEWorld/Assets/{Ground Material.xkmat => Material.xkmat} (54%) delete mode 100644 NEWorld/Assets/Sphere Material.xkmat delete mode 100644 NEWorld/Assets/Sphere.xkpromodel create mode 100644 NEWorld/Effects/VertexTextureTerrain.cs create mode 100644 NEWorld/Effects/VertexTextureTerrain.xksl create mode 100644 NEWorld/Renderer/RdTextures.cs create mode 100644 NEWorld/Renderer/VertexBuilder.cs diff --git a/Core/Generic.cs b/Core/Generic.cs index 277dacb..1b4291e 100644 --- a/Core/Generic.cs +++ b/Core/Generic.cs @@ -21,38 +21,150 @@ namespace Core { public static class Generic { - public static dynamic Cast(dynamic a) => (T) a; - public static dynamic Add(dynamic a, dynamic b) => a + b; - public static dynamic Substract(dynamic a, dynamic b) => a - b; - public static dynamic Multiply(dynamic a, dynamic b) => a * b; - public static dynamic Divide(dynamic a, dynamic b) => a / b; - public static dynamic Modulus(dynamic a, dynamic b) => a % b; - public static dynamic AddBy(dynamic a, dynamic b) => a += b; - public static dynamic SubstractBy(dynamic a, dynamic b) => a -= b; - public static dynamic MultiplyBy(ref dynamic a, dynamic b) => a *= b; - public static dynamic DivideBy(dynamic a, dynamic b) => a /= b; - public static dynamic ModulusBy(dynamic a, dynamic b) => a %= b; - public static dynamic Square(dynamic a) => a * a; - public static dynamic Negate(dynamic a) => -a; - public static dynamic Increase(dynamic a) => ++a; - public static dynamic Decrease(dynamic a) => --a; - public static dynamic IncreaseAfter(dynamic a) => a++; - public static dynamic DecreaseAfter(dynamic a) => a--; - - public static bool Less(dynamic a, dynamic b) => a < b; - public static bool LessEqual(dynamic a, dynamic b) => a <= b; - public static bool Larger(dynamic a, dynamic b) => a > b; - public static bool LargerEqual(dynamic a, dynamic b) => a >= b; - public static bool Equal(dynamic a, dynamic b) => a == b; - - public static double Sqrt(dynamic a) => System.Math.Sqrt((double) a); - public static double Sin(dynamic a) => System.Math.Sin((double) a); - public static double Cos(dynamic a) => System.Math.Cos((double) a); - public static double Tan(dynamic a) => System.Math.Tan((double) a); - public static double Abs(dynamic a) => System.Math.Abs(a); - - public static dynamic Min(dynamic a, dynamic b) => Less(a, b) ? a : b; - public static dynamic Max(dynamic a, dynamic b) => Larger(a, b) ? a : b; + public static dynamic Cast(dynamic a) + { + return (T) a; + } + + public static dynamic Add(dynamic a, dynamic b) + { + return a + b; + } + + public static dynamic Substract(dynamic a, dynamic b) + { + return a - b; + } + + public static dynamic Multiply(dynamic a, dynamic b) + { + return a * b; + } + + public static dynamic Divide(dynamic a, dynamic b) + { + return a / b; + } + + public static dynamic Modulus(dynamic a, dynamic b) + { + return a % b; + } + + public static dynamic AddBy(dynamic a, dynamic b) + { + return a += b; + } + + public static dynamic SubstractBy(dynamic a, dynamic b) + { + return a -= b; + } + + public static dynamic MultiplyBy(ref dynamic a, dynamic b) + { + return a *= b; + } + + public static dynamic DivideBy(dynamic a, dynamic b) + { + return a /= b; + } + + public static dynamic ModulusBy(dynamic a, dynamic b) + { + return a %= b; + } + + public static dynamic Square(dynamic a) + { + return a * a; + } + + public static dynamic Negate(dynamic a) + { + return -a; + } + + public static dynamic Increase(dynamic a) + { + return ++a; + } + + public static dynamic Decrease(dynamic a) + { + return --a; + } + + public static dynamic IncreaseAfter(dynamic a) + { + return a++; + } + + public static dynamic DecreaseAfter(dynamic a) + { + return a--; + } + + public static bool Less(dynamic a, dynamic b) + { + return a < b; + } + + public static bool LessEqual(dynamic a, dynamic b) + { + return a <= b; + } + + public static bool Larger(dynamic a, dynamic b) + { + return a > b; + } + + public static bool LargerEqual(dynamic a, dynamic b) + { + return a >= b; + } + + public static bool Equal(dynamic a, dynamic b) + { + return a == b; + } + + public static double Sqrt(dynamic a) + { + return System.Math.Sqrt((double) a); + } + + public static double Sin(dynamic a) + { + return System.Math.Sin((double) a); + } + + public static double Cos(dynamic a) + { + return System.Math.Cos((double) a); + } + + public static double Tan(dynamic a) + { + return System.Math.Tan((double) a); + } + + public static double Abs(dynamic a) + { + return System.Math.Abs(a); + } + + public static dynamic Min(dynamic a, dynamic b) + { + return Less(a, b) ? a : b; + } + + public static dynamic Max(dynamic a, dynamic b) + { + return Larger(a, b) ? a : b; + } public static void MinEqual(dynamic a, dynamic b) { diff --git a/Core/LogPort.cs b/Core/LogPort.cs index f3f734d..35f45af 100644 --- a/Core/LogPort.cs +++ b/Core/LogPort.cs @@ -1,19 +1,18 @@ -namespace Core +using System; +using Xenko.Core.Diagnostics; + +namespace Core { public static class LogPort { - public static Xenko.Core.Diagnostics.Logger Logger { get; set; } + public static Logger Logger { get; set; } public static void Debug(string str) { if (Logger != null) - { Logger.Debug(str); - } else - { - System.Console.WriteLine(str); - } + Console.WriteLine(str); } } -} +} \ No newline at end of file diff --git a/Core/Math/Mat4.cs b/Core/Math/Mat4.cs index f7ad55a..0c811fd 100644 --- a/Core/Math/Mat4.cs +++ b/Core/Math/Mat4.cs @@ -41,10 +41,7 @@ public Mat4F(float x) public static Mat4F operator +(Mat4F lhs, Mat4F rhs) { var result = lhs; - for (var i = 0; i < 16; ++i) - { - result.Data[i] += rhs.Data[i]; - } + for (var i = 0; i < 16; ++i) result.Data[i] += rhs.Data[i]; return result; } @@ -151,9 +148,8 @@ public Mat4F Inverse(float[] data) { var p = i; for (var j = i + 1; j < 4; j++) - { - if (System.Math.Abs(Data[j * 4 + i]) > System.Math.Abs(Data[p * 4 + i])) p = j; - } + if (System.Math.Abs(Data[j * 4 + i]) > System.Math.Abs(Data[p * 4 + i])) + p = j; res.SwapRows(i, p); SwapRows(i, p); @@ -167,30 +163,34 @@ public Mat4F Inverse(float[] data) } for (var i = 3; i >= 0; i--) + for (uint j = 0; j < i; j++) { - for (uint j = 0; j < i; j++) - { - res.MultAndAdd((uint) i, j, -Data[j * 4 + i]); - MultAndAdd((uint) i, j, -Data[j * 4 + i]); - } + res.MultAndAdd((uint) i, j, -Data[j * 4 + i]); + MultAndAdd((uint) i, j, -Data[j * 4 + i]); } return this; } // Construct a translation matrix - public static Mat4F Translation(Vec3 delta) => new Mat4F(1.0f) + public static Mat4F Translation(Vec3 delta) { - Data = + return new Mat4F(1.0f) { - [3] = delta.X, - [7] = delta.Y, - [11] = delta.Z - } - }; + Data = + { + [3] = delta.X, + [7] = delta.Y, + [11] = delta.Z + } + }; + } // Construct a identity matrix - public static Mat4F Identity() => new Mat4F(1.0f); + public static Mat4F Identity() + { + return new Mat4F(1.0f); + } // Construct a rotation matrix public static Mat4F Rotation(float degrees, Vec3 vec) @@ -287,10 +287,7 @@ public Mat4D(double x) public static Mat4D operator +(Mat4D lhs, Mat4D rhs) { var result = lhs; - for (var i = 0; i < 16; ++i) - { - result.Data[i] += rhs.Data[i]; - } + for (var i = 0; i < 16; ++i) result.Data[i] += rhs.Data[i]; return result; } @@ -397,9 +394,8 @@ public Mat4D Inverse(double[] data) { var p = i; for (var j = i + 1; j < 4; j++) - { - if (System.Math.Abs(Data[j * 4 + i]) > System.Math.Abs(Data[p * 4 + i])) p = j; - } + if (System.Math.Abs(Data[j * 4 + i]) > System.Math.Abs(Data[p * 4 + i])) + p = j; res.SwapRows(i, p); SwapRows(i, p); @@ -413,30 +409,34 @@ public Mat4D Inverse(double[] data) } for (var i = 3; i >= 0; i--) + for (uint j = 0; j < i; j++) { - for (uint j = 0; j < i; j++) - { - res.MultAndAdd((uint) i, j, -Data[j * 4 + i]); - MultAndAdd((uint) i, j, -Data[j * 4 + i]); - } + res.MultAndAdd((uint) i, j, -Data[j * 4 + i]); + MultAndAdd((uint) i, j, -Data[j * 4 + i]); } return this; } // Construct a translation matrix - public static Mat4D Translation(Vec3 delta) => new Mat4D(1.0f) + public static Mat4D Translation(Vec3 delta) { - Data = + return new Mat4D(1.0f) { - [3] = delta.X, - [7] = delta.Y, - [11] = delta.Z - } - }; + Data = + { + [3] = delta.X, + [7] = delta.Y, + [11] = delta.Z + } + }; + } // Construct a identity matrix - public static Mat4D Identity() => new Mat4D(1.0f); + public static Mat4D Identity() + { + return new Mat4D(1.0f); + } // Construct a rotation matrix public static Mat4D Rotation(double degrees, Vec3 vec) diff --git a/Core/Math/Vector.cs b/Core/Math/Vector.cs index a04cfab..1652c14 100644 --- a/Core/Math/Vector.cs +++ b/Core/Math/Vector.cs @@ -34,9 +34,15 @@ public Vec2(T x, T y) public T X, Y; - public double LengthSqr() => Square(X) + Square(Y); + public double LengthSqr() + { + return Square(X) + Square(Y); + } - public double Length() => System.Math.Sqrt(LengthSqr()); + public double Length() + { + return System.Math.Sqrt(LengthSqr()); + } public void Normalize() { @@ -45,14 +51,20 @@ public void Normalize() Y = Divide(Y, length); } - public static Vec2 operator +(Vec2 lhs, Vec2 rhs) => - new Vec2(Add(lhs.X, rhs.X), Add(lhs.Y, rhs.Y)); + public static Vec2 operator +(Vec2 lhs, Vec2 rhs) + { + return new Vec2(Add(lhs.X, rhs.X), Add(lhs.Y, rhs.Y)); + } - public static Vec2 operator -(Vec2 lhs, Vec2 rhs) => - new Vec2(Substract(lhs.X, rhs.X), Substract(lhs.Y, rhs.Y)); + public static Vec2 operator -(Vec2 lhs, Vec2 rhs) + { + return new Vec2(Substract(lhs.X, rhs.X), Substract(lhs.Y, rhs.Y)); + } - public bool Equals(Vec2 other) => - EqualityComparer.Default.Equals(X, other.X) && EqualityComparer.Default.Equals(Y, other.Y); + public bool Equals(Vec2 other) + { + return EqualityComparer.Default.Equals(X, other.X) && EqualityComparer.Default.Equals(Y, other.Y); + } public override bool Equals(object obj) { @@ -82,9 +94,15 @@ public Vec3(T x, T y, T z) public T Y; public T Z; - public T LengthSqr() => Square(X) + Square(Y) + Square(Z); + public T LengthSqr() + { + return Square(X) + Square(Y) + Square(Z); + } - public double Length() => Sqrt(LengthSqr()); + public double Length() + { + return Sqrt(LengthSqr()); + } public void Normalize() { @@ -95,24 +113,36 @@ public void Normalize() } - public static Vec3 operator +(Vec3 lhs, Vec3 rhs) => - new Vec3(Add(lhs.X, rhs.X), Add(lhs.Y, rhs.Y), Add(lhs.Z, rhs.Z)); + public static Vec3 operator +(Vec3 lhs, Vec3 rhs) + { + return new Vec3(Add(lhs.X, rhs.X), Add(lhs.Y, rhs.Y), Add(lhs.Z, rhs.Z)); + } - public static Vec3 operator -(Vec3 lhs, Vec3 rhs) => - new Vec3(Substract(lhs.X, rhs.X), Substract(lhs.Y, rhs.Y), Substract(lhs.Z, rhs.Z)); + public static Vec3 operator -(Vec3 lhs, Vec3 rhs) + { + return new Vec3(Substract(lhs.X, rhs.X), Substract(lhs.Y, rhs.Y), Substract(lhs.Z, rhs.Z)); + } - public static Vec3 operator -(Vec3 lhs) => - new Vec3(Negate(lhs.X), Negate(lhs.Y), Negate(lhs.Z)); + public static Vec3 operator -(Vec3 lhs) + { + return new Vec3(Negate(lhs.X), Negate(lhs.Y), Negate(lhs.Z)); + } - public static Vec3 operator *(Vec3 lhs, T rhs) => - new Vec3(Multiply(lhs.X, rhs), Multiply(lhs.Y, rhs), Multiply(lhs.Z, rhs)); + public static Vec3 operator *(Vec3 lhs, T rhs) + { + return new Vec3(Multiply(lhs.X, rhs), Multiply(lhs.Y, rhs), Multiply(lhs.Z, rhs)); + } - public static Vec3 operator /(Vec3 lhs, T rhs) => - new Vec3(Divide(lhs.X, rhs), Divide(lhs.Y, rhs), Divide(lhs.Z, rhs)); + public static Vec3 operator /(Vec3 lhs, T rhs) + { + return new Vec3(Divide(lhs.X, rhs), Divide(lhs.Y, rhs), Divide(lhs.Z, rhs)); + } - public bool Equals(Vec3 other) => - EqualityComparer.Default.Equals(X, other.X) && EqualityComparer.Default.Equals(Y, other.Y) && - EqualityComparer.Default.Equals(Z, other.Z); + public bool Equals(Vec3 other) + { + return EqualityComparer.Default.Equals(X, other.X) && EqualityComparer.Default.Equals(Y, other.Y) && + EqualityComparer.Default.Equals(Z, other.Z); + } public override bool Equals(object obj) { @@ -131,5 +161,4 @@ public override int GetHashCode() } } } - } \ No newline at end of file diff --git a/Core/Module/Module.cs b/Core/Module/Module.cs index 9c98e44..24a2a9b 100644 --- a/Core/Module/Module.cs +++ b/Core/Module/Module.cs @@ -37,21 +37,30 @@ public class DeclareModuleAttribute : Attribute public class Modules { + private readonly Dictionary> modules; + + private string basePath = Path.Modules(); + private Modules() { modules = new Dictionary>(); } - public void SetBasePath(string path) => basePath = path; + public IModule this[string name] => modules[name].Key; + + public static Modules Instance => Singleton.Instance; + + public void SetBasePath(string path) + { + basePath = path; + } public void Load(string moduleFile) { var assembly = Assembly.Load(moduleFile); var possibleTypes = assembly.GetExportedTypes(); foreach (var type in possibleTypes) - { if (type.IsDefined(typeof(DeclareModuleAttribute), false) && typeof(IModule).IsAssignableFrom(type)) - { try { var module = (IModule) Activator.CreateInstance(type); @@ -63,23 +72,13 @@ public void Load(string moduleFile) { LogPort.Debug($"Module {type} Load Failure : {e}"); } - } - } } - public IModule this[string name] => modules[name].Key; - public void UnloadAll() { foreach (var module in modules) module.Value.Key.CoFinalize(); modules.Clear(); } - - public static Modules Instance => Singleton.Instance; - - private string basePath = Path.Modules(); - - private readonly Dictionary> modules; } } \ No newline at end of file diff --git a/Core/Network/Client.cs b/Core/Network/Client.cs index 26e6704..23e9e0f 100644 --- a/Core/Network/Client.cs +++ b/Core/Network/Client.cs @@ -17,41 +17,66 @@ // along with NEWorld. If not, see . // +using System; using System.Collections.Generic; using System.Net.Sockets; using System.Threading.Tasks; -using Core.Utilities; namespace Core.Network { - public class Client : TcpClient + public sealed class Client : IDisposable { - public Client(string address, int port) : base(address, port) + private readonly ConnectionHost.Connection connection; + private readonly List protocols; + + public Client(string address, int port) { - RegisterProtocol(Singleton.Instance); - RegisterProtocol(new ProtocolFetchProtocol.Client()); - connHost.AddConnection(this); + var client = new TcpClient(address, port); + protocols = new List(); + RegisterProtocol(new Reply()); + RegisterProtocol(new Handshake.Client()); + connection = ConnectionHost.Add(client, protocols); } - public void RegisterProtocol(Protocol newProtocol) => connHost.RegisterProtocol(newProtocol); + public void Dispose() + { + Close(); + } - public async Task NegotiateProtocols() + public void RegisterProtocol(Protocol newProtocol) + { + protocols.Add(newProtocol); + } + + public async Task HandShake() { var skvm = new Dictionary(); - foreach (var protocol in connHost.Protocols) + foreach (var protocol in protocols) skvm.Add(protocol.Name(), protocol); - var reply = await ProtocolFetchProtocol.Client.Get(GetConnection()); + var reply = await Handshake.Get(GetConnection().Session); foreach (var entry in reply) skvm[entry.Key].Id = entry.Value; - connHost.Protocols.Sort(ProtocolSorter); + protocols.Sort(ProtocolSorter); } - public ConnectionHost.Connection GetConnection() => connHost.GetConnection(0); + public Session.Send CreateMessage(uint protocol) + { + return GetConnection().Session.CreateMessage(protocol); + } - public new void Close() => connHost.CloseAll(); + public void Close() + { + connection.Close(); + } - private static int ProtocolSorter(Protocol x, Protocol y) => Comparer.Default.Compare(x.Id, y.Id); + public ConnectionHost.Connection GetConnection() + { + return connection; + } - private readonly ConnectionHost connHost = new ConnectionHost(); + private static int ProtocolSorter(Protocol x, Protocol y) + { + return Comparer.Default.Compare(x.Id, y.Id); + } } } \ No newline at end of file diff --git a/Core/Network/ConnectionHost.cs b/Core/Network/ConnectionHost.cs index 6aa04a8..58ce732 100644 --- a/Core/Network/ConnectionHost.cs +++ b/Core/Network/ConnectionHost.cs @@ -19,149 +19,330 @@ using System; using System.Collections.Generic; -using System.Linq; +using System.Diagnostics; +using System.IO; using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; -using Xenko.Rendering; namespace Core.Network { - public class ConnectionHost + public sealed class Session : IDisposable { - public class Connection + private readonly TcpClient conn; + private readonly NetworkStream ios; + private readonly MemoryStream writeBuffer = new MemoryStream(new byte[8192], 0, 8192, true, true); + private MemoryStream buffer; + private byte[] storage = new byte[8192]; + + internal Session(TcpClient io) + { + conn = io; + ios = io.GetStream(); + buffer = new MemoryStream(storage, 0, storage.Length, false, true); + } + + public bool Live => conn.Connected; + + public void Dispose() { - public Connection(ulong cid, TcpClient client, ConnectionHost server) + ios.Close(); + } + + internal Receive WaitMessage() + { + return new Receive(this); + } + + public Send CreateMessage(uint protocol) + { + return new Send(this, protocol); + } + + public sealed class Receive : IDisposable + { + private Stream ios; + + internal Receive(Session s) { - this.cid = cid; - this.client = client; - this.server = server; - Stream = client.GetStream(); - finalize = Start(); + Session = s; + ios = Session.ios; + Raw = Session.storage; } - public bool Valid { get; private set; } + public byte[] Raw { get; private set; } - public void Close() + public Session Session { get; } + + public void Dispose() { - CloseDown(); - finalize.Wait(); } - private async Task Start() + internal async Task LoadExpected(int length) { - Valid = true; - var headerCache = new byte[8]; // ["NWRC"] + Int32BE(Protocol Id) - while (Valid) + if (length == 0) return; + + if (length > Raw.Length) { - try - { - int bytesRead; - do - { - bytesRead = await Stream.ReadAsync(headerCache, 0, 8); - } while (bytesRead != 8); - if (VerifyPackageValidity(headerCache, bytesRead)) - try - { - server.Protocols[GetProtocolId(headerCache)].HandleRequest(Stream); - } - catch (Exception e) - { - LogPort.Debug(e.ToString()); - } - else - throw new Exception("Bad Package Recieved"); - } - catch (Exception e) - { - if (client.Connected == false) - break; - LogPort.Debug($"Encountering Exception {e}"); - throw; - } + Session.storage = Raw = + new byte[1 << (int) System.Math.Ceiling(System.Math.Log(length) / System.Math.Log(2))]; + Session.buffer = new MemoryStream(Raw, 0, Raw.Length, false, true); } - - CloseDown(); + else + { + Session.buffer.Seek(0, SeekOrigin.Begin); + } + + await ReadAsync(Raw, 0, length); + ios = Session.buffer; } - private void CloseDown() + internal async Task Wait() { - if (!Valid) return; - Valid = false; - Stream.Close(); // Cancellation Token Doesn't Work. Hard Close is adopted. - client.Close(); - Interlocked.Increment(ref server.invalidConnections); + await ReadAsync(Raw, 0, 8); + if (!VerifyPackageValidity(Raw)) + throw new Exception("Bad Package Received"); + return GetProtocolId(Raw); } - public NetworkStream Stream { get; } + private static int GetProtocolId(byte[] head) + { + return (head[4] << 24) | (head[5] << 16) | (head[6] << 8) | head[7]; + } - private static int GetProtocolId(byte[] head) => head[4] << 24 | head[5] << 16 | head[6] << 8 | head[7]; + private static bool VerifyPackageValidity(byte[] head) + { + return head[0] == 'N' && head[1] == 'W' && head[2] == 'R' && head[3] == 'C'; + } - private static bool VerifyPackageValidity(byte[] head, int read) => - head[0] == 'N' && head[1] == 'W' && head[2] == 'R' && head[3] == 'C' && read == 8; + public byte ReadU8() + { + var ret = ios.ReadByte(); + if (ret >= 0) + return (byte) ret; + throw new EndOfStreamException(); + } - private ulong cid; - private readonly TcpClient client; - private readonly ConnectionHost server; - private readonly Task finalize; + public char ReadChar() + { + return (char) ReadU16(); + } + + public ushort ReadU16() + { + return (ushort) ((ReadU8() << 8) | ReadU8()); + } + + public uint ReadU32() + { + return (uint) ((ReadU16() << 16) | ReadU16()); + } + + public ulong ReadU64() + { + return (ReadU32() << 32) | ReadU32(); + } + + public void Read(byte[] buffer, int begin, int end) + { + while (begin != end) + { + var read = ios.Read(buffer, begin, end - begin); + if (read > 0) + begin += read; + else + throw new EndOfStreamException(); + } + } + + public async Task ReadAsync(byte[] buffer, int begin, int end) + { + while (begin != end) begin += await ios.ReadAsync(buffer, begin, end - begin); + } } - public ConnectionHost() + public sealed class Send : IDisposable { - clients = new Dictionary(); - Protocols = new List(); - } + private readonly MemoryStream buffer; + private readonly NetworkStream ios; - public object Lock => protocolLock; + internal Send(Session session, uint protocol) + { + Session = session; + ios = Session.ios; + buffer = Session.writeBuffer; + Write((byte) 'N'); + Write((byte) 'W'); + Write((byte) 'R'); + Write((byte) 'C'); + Write(protocol); + } - private const double UtilizationThreshold = 0.75; + public Session Session { get; } - public void RegisterProtocol(Protocol newProtocol) - { - lock (protocolLock) + public void Dispose() + { + FlushBuffer(); + } + + public void Write(byte val) + { + buffer.WriteByte(val); + } + + public void Write(char val) { - Protocols.Add(newProtocol); + Write((byte) (val >> 8)); + Write((byte) (val & 0xFF)); + } + + public void Write(ushort val) + { + Write((byte) (val >> 8)); + Write((byte) (val & 0xFF)); + } + + public void Write(uint val) + { + Write((ushort) (val >> 16)); + Write((ushort) (val & 0xFFFF)); + } + + public void Write(ulong val) + { + Write((uint) (val >> 32)); + Write((uint) (val & 0xFFFFFFFF)); + } + + public void Write(byte[] input, int begin, int end) + { + FlushBuffer(); + ios.Write(input, begin, end - begin); + } + + public async Task ReadAsync(byte[] input, int begin, int end) + { + FlushBuffer(); + await ios.WriteAsync(input, begin, end - begin); + } + + private void FlushBuffer() + { + if (buffer.Length > 0) + { + ios.Write(buffer.GetBuffer(), 0, (int) buffer.Seek(0, SeekOrigin.Current)); + buffer.Seek(0, SeekOrigin.Begin); + } + } + + public void Write(ArraySegment bytes) + { + FlushBuffer(); + Debug.Assert(bytes.Array != null, "bytes.Array != null"); + ios.Write(bytes.Array, bytes.Offset, bytes.Count); } } + } - public void SweepInvalidConnectionsIfNecessary() + [DeclareService("Core.Network.ConnectionHost")] + public sealed class ConnectionHost : IDisposable + { + private const double UtilizationThreshold = 0.25; + private static int _connectionCounter; + private static List _connections; + + static ConnectionHost() { - var utilization = 1.0 - (double) invalidConnections / clients.Count; - if (utilization < UtilizationThreshold) - SweepInvalidConnections(); + _connections = new List(); } - public Connection GetConnection(ulong id) => clients[id]; + public void Dispose() + { + foreach (var hd in _connections) + hd.Close(); + } - private void SweepInvalidConnections() + private static void SweepInvalidConnectionsIfNecessary() { - foreach (var hd in clients.ToList()) - if (!hd.Value.Valid) - { - clients.Remove(hd.Key); - Interlocked.Decrement(ref invalidConnections); - } + var utilization = (double) _connectionCounter / _connections.Count; + if (utilization < UtilizationThreshold) + SweepInvalidConnections(); } - public void AddConnection(TcpClient conn) + private static void SweepInvalidConnections() { - clients.Add(sessionIdTop, new Connection(sessionIdTop, conn, this)); - ++sessionIdTop; + var swap = new List(); + foreach (var hd in _connections) + if (hd.Valid) + swap.Add(hd); + _connections = swap; } - private ulong sessionIdTop; - private int invalidConnections; - public readonly List Protocols; - private readonly Dictionary clients; - private readonly object protocolLock = new object(); + public static Connection Add(TcpClient conn, List protocols) + { + var connect = new Connection(conn, protocols); + _connections.Add(connect); + return connect; + } - public void CloseAll() + public static int CountConnections() { - foreach (var hd in clients) - hd.Value.Close(); + return _connectionCounter; } - public int CountConnections() => clients.Count - invalidConnections; + public sealed class Connection + { + private readonly Task finalize; + private readonly List protocols; + internal readonly Session Session; + + public Connection(TcpClient client, List protocols) + { + this.protocols = protocols; + Session = new Session(client); + finalize = Start(); + } + + public bool Valid { get; private set; } + + public void Close() + { + CloseDown(); + finalize.Wait(); + } + + private async Task Start() + { + Valid = true; + Interlocked.Increment(ref _connectionCounter); + while (Valid) + try + { + var message = Session.WaitMessage(); + await ProcessRequest(await message.Wait(), message); + } + catch (Exception e) + { + if (Session.Live) LogPort.Debug($"Encountering Exception {e}"); + } + + CloseDown(); + } + + private async Task ProcessRequest(int protocol, Session.Receive message) + { + var handle = protocols[protocol]; + await message.LoadExpected(handle.Expecting); + handle.HandleRequest(message); + } + + private void CloseDown() + { + if (!Valid) return; + Valid = false; + Interlocked.Decrement(ref _connectionCounter); + SweepInvalidConnectionsIfNecessary(); + } + } } } \ No newline at end of file diff --git a/Core/Network/Protocol.cs b/Core/Network/Protocol.cs index 4431f93..5f29b07 100644 --- a/Core/Network/Protocol.cs +++ b/Core/Network/Protocol.cs @@ -17,112 +17,30 @@ // along with NEWorld. If not, see . // -using System; -using System.Net.Sockets; -using System.Threading; - namespace Core.Network { public abstract class Protocol { - public int Id { get; set; } - - public abstract string Name(); - - public abstract void HandleRequest(NetworkStream nstream); - - protected static byte[] Request(int protocol) => new[] - { - (byte) 'N', (byte) 'W', (byte) 'R', (byte) 'C', - (byte) (protocol >> 24), - (byte) (protocol >> 16 & 0xFF), - (byte) (protocol >> 18 & 0xFF), - (byte) (protocol & 0xFF) - }; - - protected static byte[] Request(int protocol, ArraySegment message) => Concat(message, new[] - { - (byte) 'N', (byte) 'W', (byte) 'R', (byte) 'C', - (byte) (protocol >> 24), - (byte) (protocol >> 16 & 0xFF), - (byte) (protocol >> 18 & 0xFF), - (byte) (protocol & 0xFF) - }); - - protected static byte[] Reply(int requestSession, ArraySegment message) => Concat(message, new[] - { - (byte) 'N', (byte) 'W', (byte) 'R', (byte) 'C', - (byte) 0, (byte) 0, (byte) 0, (byte) 0, - (byte) (requestSession >> 24), - (byte) (requestSession >> 16 & 0xFF), - (byte) (requestSession >> 18 & 0xFF), - (byte) (requestSession & 0xFF), - (byte) (message.Count >> 24), - (byte) (message.Count >> 16 & 0xFF), - (byte) (message.Count >> 18 & 0xFF), - (byte) (message.Count & 0xFF) - }); - - protected static void Send(NetworkStream stream, byte[] data) => stream.Write(data, 0, data.Length); - - private static byte[] Concat(ArraySegment message, byte[] head) - { - var final = new byte[head.Length + message.Count]; - head.CopyTo(final, 0); - Array.Copy(message.Array ?? throw new InvalidOperationException(), message.Offset, final, head.Length, - message.Count); - return final; - } - } + public uint Id { get; set; } - public abstract class StandardProtocol : Protocol - { - protected abstract void HandleRequestData(byte[] data, NetworkStream stream); + public int Expecting { get; protected set; } - protected abstract byte[] PullRequestData(NetworkStream nstream); + public abstract string Name(); - public sealed override void HandleRequest(NetworkStream nstream) => - HandleRequestData(PullRequestData(nstream), nstream); + public abstract void HandleRequest(Session.Receive request); } - public abstract class FixedLengthProtocol : StandardProtocol + public abstract class FixedLengthProtocol : Protocol { - private class LohOptimizedAlloc + protected FixedLengthProtocol(int length) { - private const int LohThreshold = 80000; - - public LohOptimizedAlloc(int size) - { - Size = size; - if (size > LohThreshold) - cache = new ThreadLocal(); - } - - public byte[] Get() => cache == null ? new byte[Size] : cache.Value ?? (cache.Value = new byte[Size]); - - public readonly int Size; - - private readonly ThreadLocal cache; - } - - protected FixedLengthProtocol(int length) => alloc = new LohOptimizedAlloc(length); - - protected override byte[] PullRequestData(NetworkStream nstream) - { - // TODO : Provide Read Closure To All NetworkStream Readers - var ret = alloc.Get(); - var read = 0; - while (read < alloc.Size) - read += nstream.Read(ret, read, alloc.Size - read); - return ret; + Expecting = length; } - - private readonly LohOptimizedAlloc alloc; } public abstract class StubProtocol : Protocol { - public override void HandleRequest(NetworkStream nstream) + public override void HandleRequest(Session.Receive request) { } } diff --git a/Core/Network/Protocols.cs b/Core/Network/Protocols.cs index c90fbf7..37f1e5c 100644 --- a/Core/Network/Protocols.cs +++ b/Core/Network/Protocols.cs @@ -20,102 +20,121 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; -using Core.Utilities; using MsgPack.Serialization; namespace Core.Network { - public static class ProtocolFetchProtocol + public static class Handshake { + private static readonly MessagePackSerializer[]> SerialReply = + MessagePackSerializer.Get[]>(); + + internal static async Task[]> Get(Session conn) + { + var session = Reply.AllocSession(); + using (var message = conn.CreateMessage(1)) + { + message.Write(session.Key); + } + + var result = await session.Value; + return SerialReply.UnpackSingleObject(result); + } + public class Server : FixedLengthProtocol { - public Server(List protocols) : base(Size) => this.protocols = protocols; + private readonly List protocols; - protected override void HandleRequestData(byte[] data, NetworkStream stream) + public Server(List protocols) : base(4) { - var request = SerialSend.UnpackSingleObject(data); - var current = 0; - var reply = new KeyValuePair[protocols.Count]; - foreach (var prot in protocols) - reply[current++] = new KeyValuePair(prot.Name(), prot.Id); - Send(stream, Reply(request, SerialReply.PackSingleObjectAsBytes(reply))); + this.protocols = protocols; } - public override string Name() => "FetchProtocols"; + public override void HandleRequest(Session.Receive request) + { + var session = request.ReadU32(); + var current = 0; + var reply = new KeyValuePair[protocols.Count]; + foreach (var protocol in protocols) + reply[current++] = new KeyValuePair(protocol.Name(), protocol.Id); + Reply.Send(request.Session, session, SerialReply.PackSingleObjectAsBytes(reply)); + } - private readonly List protocols; + public override string Name() + { + return "FetchProtocols"; + } } public class Client : StubProtocol { - public override string Name() => "FetchProtocols"; - - public static async Task[]> Get(ConnectionHost.Connection conn) + public override string Name() { - var session = ProtocolReply.AllocSession(); - Send(conn.Stream, Request(1, SerialSend.PackSingleObjectAsBytes(session.Key))); - var result = await session.Value; - return SerialReply.UnpackSingleObject(result); + return "FetchProtocols"; } } - - private static readonly MessagePackSerializer SerialSend = MessagePackSerializer.Get(); - - private static readonly MessagePackSerializer[]> SerialReply = - MessagePackSerializer.Get[]>(); - - private static readonly int Size = SerialSend.PackSingleObject(0).Length; } - public sealed class ProtocolReply : Protocol + public sealed class Reply : Protocol { - private ProtocolReply() + private static int _idTop; + private static readonly ConcurrentQueue SessionIds = new ConcurrentQueue(); + + private static readonly ConcurrentDictionary> Sessions = + new ConcurrentDictionary>(); + + public override string Name() { + return "Reply"; } - public override string Name() => "Reply"; - - public override void HandleRequest(NetworkStream nstream) + public override void HandleRequest(Session.Receive request) { - var extraHead = new byte[8]; - nstream.Read(extraHead, 0, extraHead.Length); - var dataSegment = new byte[GetSessionLength(extraHead)]; - nstream.Read(dataSegment, 0, dataSegment.Length); - SessionDispatch(GetSessionId(extraHead), dataSegment); + var session = request.ReadU32(); + var length = request.ReadU32(); + var dataSegment = new byte[length]; + request.Read(dataSegment, 0, dataSegment.Length); + SessionDispatch(session, dataSegment); } - public static KeyValuePair> AllocSession() => - Singleton.Instance.AllocSessionInternal(); + public static void Send(Session dialog, uint session, ArraySegment payload) + { + using (var message = dialog.CreateMessage(0)) + { + message.Write(session); + message.Write((uint) payload.Count); + message.Write(payload); + } + } - private KeyValuePair> AllocSessionInternal() + public static KeyValuePair> AllocSession() { - if (!sessionIds.TryDequeue(out var newId)) - newId = Interlocked.Increment(ref idTop) - 1; + if (!SessionIds.TryDequeue(out var newId)) + newId = (uint) (Interlocked.Increment(ref _idTop) - 1); var completionSource = new TaskCompletionSource(); - while (!sessions.TryAdd(newId, completionSource)) ; - return new KeyValuePair>(newId, completionSource.Task); + while (!Sessions.TryAdd(newId, completionSource)) ; + return new KeyValuePair>(newId, completionSource.Task); } - private void SessionDispatch(int sessionId, byte[] dataSegment) + private static void SessionDispatch(uint sessionId, byte[] dataSegment) { TaskCompletionSource completion; - while (!sessions.TryRemove(sessionId, out completion)) ; + while (!Sessions.TryRemove(sessionId, out completion)) ; completion.SetResult(dataSegment); - sessionIds.Enqueue(sessionId); + SessionIds.Enqueue(sessionId); } - private int idTop; - private readonly ConcurrentQueue sessionIds = new ConcurrentQueue(); - - private readonly ConcurrentDictionary> sessions = - new ConcurrentDictionary>(); - - private static int GetSessionId(byte[] head) => head[0] << 24 | head[1] << 16 | head[2] << 8 | head[3]; + private static int GetSessionId(byte[] head) + { + return (head[0] << 24) | (head[1] << 16) | (head[2] << 8) | head[3]; + } - private static int GetSessionLength(byte[] head) => head[4] << 24 | head[5] << 16 | head[6] << 8 | head[7]; + private static int GetSessionLength(byte[] head) + { + return (head[4] << 24) | (head[5] << 16) | (head[6] << 8) | head[7]; + } } } \ No newline at end of file diff --git a/Core/Network/Server.cs b/Core/Network/Server.cs index 4d2c89e..c10704c 100644 --- a/Core/Network/Server.cs +++ b/Core/Network/Server.cs @@ -17,29 +17,22 @@ // along with NEWorld. If not, see . // +using System.Collections.Generic; using System.Net; using System.Net.Sockets; using System.Threading.Tasks; -using Core.Utilities; namespace Core.Network { public class Server : TcpListener { - public Server(int port) : base(IPAddress.Any, port) - { - RegisterProtocol(Singleton.Instance); - RegisterProtocol(new ProtocolFetchProtocol.Server(connHost.Protocols)); - } + private readonly List protocols; - public void Run() + public Server(int port) : base(IPAddress.Any, port) { - lock (connHost.Lock) - { - Boot(); - ListenConnections().Wait(); - ShutDown(); - } + protocols = new List(); + RegisterProtocol(new Reply()); + RegisterProtocol(new Handshake.Server(protocols)); } public async Task RunAsync() @@ -49,48 +42,45 @@ public async Task RunAsync() ShutDown(); } - public void RegisterProtocol(Protocol newProtocol) => connHost.RegisterProtocol(newProtocol); - - public void StopServer() => exit = true; + public void RegisterProtocol(Protocol newProtocol) + { + protocols.Add(newProtocol); + } - public int CountConnections() => connHost.CountConnections(); + public int CountConnections() + { + return ConnectionHost.CountConnections(); + } private void Boot() { - exit = false; AssignProtocolIdentifiers(); Start(); } - private void ShutDown() => Stop(); + public void ShutDown() + { + Stop(); + } private async Task ListenConnections() { - while (!exit) - { + while (Active) try { - connHost.AddConnection(await AcceptTcpClientAsync()); + ConnectionHost.Add(await AcceptTcpClientAsync(), protocols); } catch { // ignored } - - connHost.SweepInvalidConnectionsIfNecessary(); - } - - connHost.CloseAll(); } private void AssignProtocolIdentifiers() { - var current = 0; - foreach (var protocol in connHost.Protocols) + var current = 0u; + foreach (var protocol in protocols) protocol.Id = current++; } - - private bool exit; - private readonly ConnectionHost connHost = new ConnectionHost(); } } \ No newline at end of file diff --git a/Core/Path.cs b/Core/Path.cs index f009587..f7f0abf 100644 --- a/Core/Path.cs +++ b/Core/Path.cs @@ -23,8 +23,14 @@ namespace Core { public static class Path { - public static string Asset(string group) => AppContext.BaseDirectory + "/Assets/" + group + "/"; + public static string Asset(string group) + { + return AppContext.BaseDirectory + "/Assets/" + group + "/"; + } - public static string Modules() => AppContext.BaseDirectory; + public static string Modules() + { + return AppContext.BaseDirectory; + } } } \ No newline at end of file diff --git a/Core/Services.cs b/Core/Services.cs index bcc9000..216b586 100644 --- a/Core/Services.cs +++ b/Core/Services.cs @@ -66,11 +66,6 @@ static Services() UpdateDomainAssemblies(); } - public static void Inject() where TP : IDisposable - { - Inject(typeof(TP)); - } - public static TI Get(string name) { try @@ -156,7 +151,7 @@ private static object CreateService(string name) CreateService(dependent); var provider = Providers[name]; var instance = Activator.CreateInstance(provider); - Dispose.List.Add((IDisposable) instance); + if (typeof(IDisposable).IsAssignableFrom(Providers[name])) Dispose.List.Add((IDisposable) instance); Ready.Add(name, instance); return instance; } diff --git a/Core/Utilities/EndianCheck.cs b/Core/Utilities/EndianCheck.cs index 65ffcc4..32b587f 100644 --- a/Core/Utilities/EndianCheck.cs +++ b/Core/Utilities/EndianCheck.cs @@ -25,6 +25,9 @@ public static class EndianCheck private static bool _isEndianChecked; + public static bool BigEndian => IsBigEndian(); + public static bool LittleEndian => !IsBigEndian(); + private static bool IsBigEndian() { if (!_isEndianChecked) @@ -36,8 +39,5 @@ private static bool IsBigEndian() return _isBigEndian; } - - public static bool BigEndian => IsBigEndian(); - public static bool LittleEndian => !IsBigEndian(); } } \ No newline at end of file diff --git a/Core/Utilities/RateController.cs b/Core/Utilities/RateController.cs index 9d6d298..a525cb4 100644 --- a/Core/Utilities/RateController.cs +++ b/Core/Utilities/RateController.cs @@ -40,19 +40,28 @@ public RateController(int rate = 0) /** * \brief Synchronize the internal timer with system clock. For cases that the timer doesn't keep up or forced resets */ - public void Sync() => last = due = DateTime.Now; + public void Sync() + { + last = due = DateTime.Now; + } /** * \brief Get elapsed time from the start of the tick, in milliseconds * \return Elapsed time from the start of the tick, in milliseconds */ - public int GetDeltaTimeMs() => (DateTime.Now - last).Milliseconds; + public int GetDeltaTimeMs() + { + return (DateTime.Now - last).Milliseconds; + } /** * \brief Check if the deadline of the current tick has pased * \return true if the deadline is passed, false otherwise */ - public bool IsDue() => rate <= 0 || DateTime.Now >= due; + public bool IsDue() + { + return rate <= 0 || DateTime.Now >= due; + } /** * \brief Increase the internal timer by one tick. Sets the current due time as the starting time of the next tick diff --git a/Core/Utilities/Singleton.cs b/Core/Utilities/Singleton.cs index 87ab555..c88fe5e 100644 --- a/Core/Utilities/Singleton.cs +++ b/Core/Utilities/Singleton.cs @@ -25,16 +25,16 @@ namespace Core.Utilities { ///

- /// Represents errors that occur while creating a singleton. + /// Represents errors that occur while creating a singleton. /// /// - /// http://msdn.microsoft.com/en-us/library/ms229064(VS.80).aspx + /// http://msdn.microsoft.com/en-us/library/ms229064(VS.80).aspx /// [Serializable] public class SingletonException : Exception { /// - /// Initializes a new instance with a specified error message. + /// Initializes a new instance with a specified error message. /// /// The message that describes the error. public SingletonException(string message) @@ -43,12 +43,12 @@ public SingletonException(string message) } /// - /// Initializes a new instance with a reference to the inner - /// exception that is the cause of this exception. + /// Initializes a new instance with a reference to the inner + /// exception that is the cause of this exception. /// /// - /// The exception that is the cause of the current exception, - /// or a null reference if no inner exception is specified. + /// The exception that is the cause of the current exception, + /// or a null reference if no inner exception is specified. /// public SingletonException(Exception innerException) : base(null, innerException) @@ -56,13 +56,13 @@ public SingletonException(Exception innerException) } /// - /// Initializes a new instance with a specified error message and a - /// reference to the inner exception that is the cause of this exception. + /// Initializes a new instance with a specified error message and a + /// reference to the inner exception that is the cause of this exception. /// /// The message that describes the error. /// - /// The exception that is the cause of the current exception, - /// or a null reference if no inner exception is specified. + /// The exception that is the cause of the current exception, + /// or a null reference if no inner exception is specified. /// public SingletonException(string message, Exception innerException) : base(message, innerException) @@ -71,32 +71,47 @@ public SingletonException(string message, Exception innerException) } /// - /// Manages the single instance of a class. + /// Manages the single instance of a class. /// /// - /// Generic variant of the strategy presented here : http://geekswithblogs.net/akraus1/articles/90803.aspx. - /// Prefered to http://www.yoda.arachsys.com/csharp/singleton.html, where static initialization doesn't allow - /// proper handling of exceptions, and doesn't allow retrying type initializers initialization later - /// (once a type initializer fails to initialize in .NET, it can't be re-initialized again). + /// Generic variant of the strategy presented here : http://geekswithblogs.net/akraus1/articles/90803.aspx. + /// Prefered to http://www.yoda.arachsys.com/csharp/singleton.html, where static initialization doesn't allow + /// proper handling of exceptions, and doesn't allow retrying type initializers initialization later + /// (once a type initializer fails to initialize in .NET, it can't be re-initialized again). /// /// Type of the singleton class. public static class Singleton where T : class { + #region Constructors + + /// + /// Type-initializer to prevent type to be marked with beforefieldinit. + /// + /// + /// This simply makes sure that static fields initialization occurs + /// when Instance is called the first time and not before. + /// + static Singleton() + { + } + + #endregion Constructors + #region Fields /// - /// The single instance of the target class. + /// The single instance of the target class. /// /// - /// The volatile keyword makes sure to remove any compiler optimization that could make concurrent - /// threads reach a race condition with the double-checked lock pattern used in the Instance property. - /// See http://www.bluebytesoftware.com/blog/PermaLink,guid,543d89ad-8d57-4a51-b7c9-a821e3992bf6.aspx + /// The volatile keyword makes sure to remove any compiler optimization that could make concurrent + /// threads reach a race condition with the double-checked lock pattern used in the Instance property. + /// See http://www.bluebytesoftware.com/blog/PermaLink,guid,543d89ad-8d57-4a51-b7c9-a821e3992bf6.aspx /// private static volatile T _instance; /// - /// The dummy object used for locking. + /// The dummy object used for locking. /// // ReSharper disable once StaticMemberInGenericType private static readonly object Lock = new object(); @@ -104,26 +119,10 @@ public static class Singleton #endregion Fields - #region Constructors - - /// - /// Type-initializer to prevent type to be marked with beforefieldinit. - /// - /// - /// This simply makes sure that static fields initialization occurs - /// when Instance is called the first time and not before. - /// - static Singleton() - { - } - - #endregion Constructors - - #region Properties /// - /// Gets the single instance of the class. + /// Gets the single instance of the class. /// public static T Instance { @@ -147,7 +146,10 @@ private static bool Test() return !StrictDisposable || Valid(_instance); } - private static bool Valid(dynamic obj) => obj.Valid(); + private static bool Valid(dynamic obj) + { + return obj.Valid(); + } private static T ConstructInstance() { diff --git a/Core/Utilities/StrictDispose.cs b/Core/Utilities/StrictDispose.cs index 08aefb3..5f0ac3e 100644 --- a/Core/Utilities/StrictDispose.cs +++ b/Core/Utilities/StrictDispose.cs @@ -23,7 +23,23 @@ namespace Core.Utilities { public class StrictDispose : IDisposable { - ~StrictDispose() => Dispose(); + private StrictDispose first, sibling; + + private bool released; + + public void Dispose() + { + if (released) + return; + TravelRelease(); + Release(); + released = true; + } + + ~StrictDispose() + { + Dispose(); + } protected T Inject(T target) where T : StrictDispose { @@ -60,19 +76,9 @@ private void TravelRelease() current.Dispose(); } - public void Dispose() + public bool Valid() { - if (released) - return; - TravelRelease(); - Release(); - released = true; + return !released; } - - public bool Valid() => !released; - - private StrictDispose first, sibling; - - private bool released; } } \ No newline at end of file diff --git a/Game/ChunkService.cs b/Game/ChunkService.cs index f6cbc66..e02e050 100644 --- a/Game/ChunkService.cs +++ b/Game/ChunkService.cs @@ -41,13 +41,18 @@ protected ChunkService(bool isAuthority) { IsAuthority = isAuthority; Worlds = new WorldManager(); - TaskDispatcher = new TaskDispatcher(4, this); + TaskDispatcher = Core.Services.Get("Game.TaskDispatcher"); } private ChunkService() : this(true) { } + public void EnableDispatcher() + { + TaskDispatcher.Start(this); + } + public TaskDispatcher TaskDispatcher { get; } public WorldManager Worlds { get; } diff --git a/Game/Network/Client.cs b/Game/Network/Client.cs index b8f8470..deb7a58 100644 --- a/Game/Network/Client.cs +++ b/Game/Network/Client.cs @@ -20,36 +20,41 @@ using System; using System.Threading.Tasks; using Core; +using Core.Network; namespace Game.Network { [DeclareService("Game.Client")] public class Client : IDisposable { - public async Task Enable(string address, int port) - { - client = new Core.Network.Client(address, port); - client.RegisterProtocol(GetChunk = new GetChunk.Client(client.GetConnection())); - client.RegisterProtocol(GetAvailableWorldId = new GetAvailableWorldId.Client(client.GetConnection())); - client.RegisterProtocol(GetWorldInfo = new GetWorldInfo.Client(client.GetConnection())); - await client.NegotiateProtocols(); - } - public static GetChunk.Client GetChunk; public static GetAvailableWorldId.Client GetAvailableWorldId; public static GetWorldInfo.Client GetWorldInfo; - public void Stop() + private static Core.Network.Client _client; + + public void Dispose() { - client.Close(); + _client?.Dispose(); } - public void Dispose() + public async Task Enable(string address, int port) + { + _client = new Core.Network.Client(address, port); + _client.RegisterProtocol(GetChunk = new GetChunk.Client()); + _client.RegisterProtocol(GetAvailableWorldId = new GetAvailableWorldId.Client()); + _client.RegisterProtocol(GetWorldInfo = new GetWorldInfo.Client()); + await _client.HandShake(); + } + + public static Session.Send CreateMessage(uint protocol) { - client?.Close(); - client?.Dispose(); + return _client.CreateMessage(protocol); } - private Core.Network.Client client; + public void Stop() + { + _client.Close(); + } } } \ No newline at end of file diff --git a/Game/Network/Protocols.cs b/Game/Network/Protocols.cs index 90293dd..88955f6 100644 --- a/Game/Network/Protocols.cs +++ b/Game/Network/Protocols.cs @@ -18,7 +18,6 @@ // using System.Collections.Generic; -using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; using Core.Network; @@ -39,13 +38,15 @@ public Server() : base(Size) public override string Name() => "GetChunk"; - protected override void HandleRequestData(byte[] data, NetworkStream stream) + public override void HandleRequest(Session.Receive stream) { - var request = From.UnpackSingleObject(data); + var request = From.UnpackSingleObject(stream.Raw); var chunkData = Get((uint) request[0], new Int3(request[1], request[2], request[3])); - Send(stream, Request(Id)); - Send(stream, data); - Send(stream, chunkData); + using (var message = stream.Session.CreateMessage(Id)) + { + message.Write(stream.Raw, 0, Size); + message.Write(chunkData, 0, chunkData.Length); + } } private static readonly ThreadLocal LocalMemCache = new ThreadLocal(); @@ -83,10 +84,13 @@ public class Client : FixedLengthProtocol { public override string Name() => "GetChunk"; - public Client(ConnectionHost.Connection conn) : base(32768 * 4 + Size) => stream = conn.Stream; + public Client() : base(32768 * 4 + Size) + { + } - protected override void HandleRequestData(byte[] data, NetworkStream stream) + public override void HandleRequest(Session.Receive request) { + var data = request.Raw; var req = From.UnpackSingleObject(data); var srv = Singleton.Instance; var chk = new Chunk(new Int3(req[1], req[2], req[3]), srv.Worlds.Get((uint) req[0])); @@ -98,16 +102,17 @@ protected override void HandleRequestData(byte[] data, NetworkStream stream) block.Data = (uint) (data[i + 2] << 8 | data[i + 3]); } - srv.TaskDispatcher.Add(new World.World.ResetChunkTask((uint) req[0], chk)); + srv.TaskDispatcher.Add(new World.World.ResetChunkTask(chk)); } public void Call(uint worldId, Int3 position) { var data = new[] {(int) worldId, position.X, position.Y, position.Z}; - Send(stream, Request(Id, From.PackSingleObjectAsBytes(data))); + using (var message = Network.Client.CreateMessage(Id)) + { + message.Write(From.PackSingleObjectAsBytes(data)); + } } - - private readonly NetworkStream stream; } private static readonly MessagePackSerializer From = MessagePackSerializer.Get(); @@ -118,14 +123,14 @@ public static class GetAvailableWorldId { public class Server : FixedLengthProtocol { - public Server() : base(Size) + public Server() : base(4) { } - protected override void HandleRequestData(byte[] data, NetworkStream stream) + public override void HandleRequest(Session.Receive request) { - var request = SerialSend.UnpackSingleObject(data); - Send(stream, Reply(request, SerialReply.PackSingleObjectAsBytes(new uint[] {0}))); + var session = request.ReadU32(); + Reply.Send(request.Session, session, SerialReply.PackSingleObjectAsBytes(new uint[] {0})); } public override string Name() => "GetAvailableWorldId"; @@ -133,43 +138,37 @@ protected override void HandleRequestData(byte[] data, NetworkStream stream) public class Client : StubProtocol { - public Client(ConnectionHost.Connection conn) => stream = conn.Stream; - public override string Name() => "GetAvailableWorldId"; public async Task Call() { - var session = ProtocolReply.AllocSession(); - Send(stream, Request(Id, SerialSend.PackSingleObjectAsBytes(session.Key))); + var session = Reply.AllocSession(); + using (var message = Network.Client.CreateMessage(Id)) + { + message.Write(session.Key); + } var result = await session.Value; return SerialReply.UnpackSingleObject(result); } - - private readonly NetworkStream stream; } - private static readonly MessagePackSerializer SerialSend = MessagePackSerializer.Get(); - private static readonly MessagePackSerializer SerialReply = MessagePackSerializer.Get(); - - private static readonly int Size = SerialSend.PackSingleObject(0).Length; } public static class GetWorldInfo { public class Server : FixedLengthProtocol { - public Server() : base(Size) + public Server() : base(8) { } - protected override void HandleRequestData(byte[] data, NetworkStream stream) + public override void HandleRequest(Session.Receive stream) { - var ret = new Dictionary(); - var request = SerialSend.UnpackSingleObject(data); - var world = Singleton.Instance.Worlds.Get((uint) request[1]); - ret.Add("name", world.Name); - Send(stream, Reply(request[0], SerialReply.PackSingleObjectAsBytes(ret))); + var request = stream.ReadU32(); + var world = Singleton.Instance.Worlds.Get(stream.ReadU32()); + var ret = new Dictionary {{"name", world.Name}}; + Reply.Send(stream.Session, request, SerialReply.PackSingleObjectAsBytes(ret)); } public override string Name() => "GetWorldInfo"; @@ -177,26 +176,22 @@ protected override void HandleRequestData(byte[] data, NetworkStream stream) public class Client : StubProtocol { - public Client(ConnectionHost.Connection conn) => stream = conn.Stream; - public override string Name() => "GetWorldInfo"; public async Task> Call(uint wid) { - var session = ProtocolReply.AllocSession(); - Send(stream, Request(Id, SerialSend.PackSingleObjectAsBytes(new[] {session.Key, (int) wid}))); + var session = Reply.AllocSession(); + using (var message = Network.Client.CreateMessage(Id)) + { + message.Write(session.Key); + message.Write(wid); + } var result = await session.Value; return SerialReply.UnpackSingleObject(result); - } - - private readonly NetworkStream stream; + } } - - private static readonly MessagePackSerializer SerialSend = MessagePackSerializer.Get(); - + private static readonly MessagePackSerializer> SerialReply = MessagePackSerializer.Get>(); - - private static readonly int Size = SerialSend.PackSingleObject(new int[2]).Length; } } \ No newline at end of file diff --git a/Game/Network/Server.cs b/Game/Network/Server.cs index 576ca14..2d4d9eb 100644 --- a/Game/Network/Server.cs +++ b/Game/Network/Server.cs @@ -45,7 +45,7 @@ public void Run() public void Stop() { - server.StopServer(); + server.ShutDown(); wait.Wait(); } diff --git a/Game/TaskDispatcher.cs b/Game/TaskDispatcher.cs index b359b3b..0ea4b0b 100644 --- a/Game/TaskDispatcher.cs +++ b/Game/TaskDispatcher.cs @@ -17,6 +17,7 @@ // along with NEWorld. If not, see . // +using System; using System.Collections.Generic; using System.Threading; using Core; @@ -36,7 +37,6 @@ namespace Game public interface IReadOnlyTask { void Task(ChunkService srv); - IReadOnlyTask Clone(); } /** @@ -47,7 +47,6 @@ public interface IReadOnlyTask public interface IReadWriteTask { void Task(ChunkService srv); - IReadWriteTask Clone(); } /** @@ -57,20 +56,39 @@ public interface IReadWriteTask public interface IRenderTask { void Task(ChunkService srv); - IRenderTask Clone(); } - public class TaskDispatcher + [DeclareService("Game.TaskDispatcher")] + public class TaskDispatcher : IDisposable { + // TODO: replace it with lock-free structure. + private readonly object mutex; + private readonly List regularReadOnlyTasks; + private readonly List regularReadWriteTasks; + private readonly List threads; + + private ChunkService chunkService; + private List readOnlyTasks, nextReadOnlyTasks; + private List readWriteTasks, nextReadWriteTasks; + private List renderTasks, nextRenderTasks; + private readonly Barrier barrier; + private RateController meter = new RateController(30); + private bool shouldExit; + + // Automatic Creation. We reserve one virtual processor for main thread + public TaskDispatcher() : this(Environment.ProcessorCount - 1) + { + } + /** * \brief Initialize the dispatcher and start threads. - * \param threadNumber The number of threads in the thread pool + * \param threads.Count The number of threads in the thread pool * \param chunkService the chunk service that the dispatcher binds to */ - public TaskDispatcher(int threadNumber, ChunkService chunkService) + private TaskDispatcher(int threadNumber) { - this.threadNumber = threadNumber; - this.chunkService = chunkService; + barrier = new Barrier(threadNumber); + threads = new List(threadNumber); TimeUsed = new int[threadNumber]; mutex = new object(); readOnlyTasks = new List(); @@ -83,18 +101,24 @@ public TaskDispatcher(int threadNumber, ChunkService chunkService) nextRenderTasks = new List(); } + public int[] TimeUsed { get; } + + public void Dispose() + { + ReleaseUnmanagedResources(); + GC.SuppressFinalize(this); + } + ~TaskDispatcher() { - if (!shouldExit) Stop(); + ReleaseUnmanagedResources(); } - public void Start() + public void Start(ChunkService srv) { + chunkService = srv; shouldExit = false; - roundNumber = 0; - numberOfUnfinishedThreads = threadNumber; - threads = new List(threadNumber); - for (var i = 0; i < threadNumber; ++i) + for (var i = 0; i < TimeUsed.Length; ++i) { var i1 = i; var trd = new Thread(() => { Worker(i1); }); @@ -106,36 +130,52 @@ public void Start() public void Add(IReadOnlyTask task) { lock (mutex) + { nextReadOnlyTasks.Add(task); + } } public void Add(IReadWriteTask task) { lock (mutex) + { nextReadWriteTasks.Add(task); + } } public void Add(IRenderTask task) { lock (mutex) + { nextRenderTasks.Add(task); + } } public void AddRegular(IReadOnlyTask task) { lock (mutex) + { regularReadOnlyTasks.Add(task); + } } public void AddRegular(IReadWriteTask task) { lock (mutex) + { regularReadWriteTasks.Add(task); + } } - public int GetRegularReadOnlyTaskCount() => regularReadOnlyTasks.Count; + public int GetRegularReadOnlyTaskCount() + { + return regularReadOnlyTasks.Count; + } - public int GetRegularReadWriteTaskCount() => regularReadWriteTasks.Count; + public int GetRegularReadWriteTaskCount() + { + return regularReadWriteTasks.Count; + } /** * \brief Process render tasks. @@ -152,81 +192,58 @@ public void ProcessRenderTasks() } } - public int[] TimeUsed { get; } - - public void Stop() + public void Reset() { - shouldExit = true; - foreach (var thread in threads) - thread.Join(); + if (!shouldExit) + { + shouldExit = true; + foreach (var thread in threads) + thread.Join(); + // TODO: Clear the queues + } } private void Worker(int threadId) { - var meter = new RateController(30); while (!shouldExit) { - // A tick starts - var currentRoundNumber = roundNumber; - // Process read-only work. - for (var i = threadId; i < readOnlyTasks.Count; i += threadNumber) - { - readOnlyTasks[i].Task(chunkService); - } - - // Finish the tick - TimeUsed[threadId] = meter.GetDeltaTimeMs(); - + ProcessReadonlyTasks(threadId); // The last finished thread is responsible to do writing jobs - if (Interlocked.Decrement(ref numberOfUnfinishedThreads) == 0) - { - // All other threads have finished? - foreach (var task in readWriteTasks) - { - task.Task(chunkService); - } - - // ...and finish up! - readOnlyTasks.Clear(); - readWriteTasks.Clear(); - foreach (var task in regularReadOnlyTasks) - nextReadOnlyTasks.Add(task.Clone()); - foreach (var task in regularReadWriteTasks) - nextReadWriteTasks.Add(task.Clone()); - Generic.Swap(ref readOnlyTasks, ref nextReadOnlyTasks); - Generic.Swap(ref readWriteTasks, ref nextReadWriteTasks); - - // Limit UPS - meter.Yield(); - - // Time to move to next tick! - // Notify other threads that we are good to go - numberOfUnfinishedThreads = threadNumber; - Interlocked.Increment(ref roundNumber); - } - else + if (barrier.ParticipantsRemaining == 0) { - meter.Yield(); - // Wait for other threads... - while (roundNumber == currentRoundNumber) - Thread.Yield(); + QueueSwap(); + ProcessReadWriteTasks(); + meter.Yield(); // Rate Control } + TimeUsed[threadId] = meter.GetDeltaTimeMs(); + barrier.SignalAndWait(); } } - // TODO: replace it with lock-free structure. - private readonly object mutex; - private List readOnlyTasks, nextReadOnlyTasks; - private readonly List regularReadOnlyTasks; - private List readWriteTasks, nextReadWriteTasks; - private readonly List regularReadWriteTasks; - private List renderTasks, nextRenderTasks; - private List threads; - private readonly int threadNumber; - private int roundNumber; - private int numberOfUnfinishedThreads; - private bool shouldExit; + private void ProcessReadonlyTasks(int i) + { + for (;i < regularReadOnlyTasks.Count; i += threads.Count) regularReadOnlyTasks[i].Task(chunkService); + for (i -= regularReadOnlyTasks.Count; i < readOnlyTasks.Count; i += threads.Count) readOnlyTasks[i].Task(chunkService); + } - private readonly ChunkService chunkService; + private void ProcessReadWriteTasks() + { + foreach (var task in regularReadWriteTasks) task.Task(chunkService); + foreach (var task in readWriteTasks) task.Task(chunkService); + } + + private void QueueSwap() + { + readOnlyTasks.Clear(); + readWriteTasks.Clear(); + Generic.Swap(ref readOnlyTasks, ref nextReadOnlyTasks); + Generic.Swap(ref readWriteTasks, ref nextReadWriteTasks); + } + + private void ReleaseUnmanagedResources() + { + Reset(); + barrier?.Dispose(); + } } } \ No newline at end of file diff --git a/Game/Terrain/Block.cs b/Game/Terrain/Block.cs index 13934ca..5dcb783 100644 --- a/Game/Terrain/Block.cs +++ b/Game/Terrain/Block.cs @@ -92,60 +92,60 @@ public unsafe void Render(IVertexBuilder target, Chunk chunk, Int3 pos) if (AdjacentTest(curr, neighbors[0])) fixed (float* tex = this.tex[0].D) target.AddPrimitive(4, - tex[0], tex[1], 0.5f, 0.5f, 0.5f, pos.X + 1.0f, pos.Y + 1.0f, pos.Z + 1.0f, - tex[0], tex[3], 0.5f, 0.5f, 0.5f, pos.X + 1.0f, pos.Y + 0.0f, pos.Z + 1.0f, - tex[2], tex[3], 0.5f, 0.5f, 0.5f, pos.X + 1.0f, pos.Y + 0.0f, pos.Z + 0.0f, - tex[2], tex[1], 0.5f, 0.5f, 0.5f, pos.X + 1.0f, pos.Y + 1.0f, pos.Z + 0.0f + tex[0], tex[1], 0.5f, pos.X + 1.0f, pos.Y + 1.0f, pos.Z + 1.0f, + tex[0], tex[3], 0.5f, pos.X + 1.0f, pos.Y + 0.0f, pos.Z + 1.0f, + tex[2], tex[3], 0.5f, pos.X + 1.0f, pos.Y + 0.0f, pos.Z + 0.0f, + tex[2], tex[1], 0.5f, pos.X + 1.0f, pos.Y + 1.0f, pos.Z + 0.0f ); // Left if (AdjacentTest(curr, neighbors[1])) fixed (float* tex = this.tex[1].D) target.AddPrimitive(4, - tex[0], tex[1], 0.5f, 0.5f, 0.5f, pos.X + 0.0f, pos.Y + 1.0f, pos.Z + 0.0f, - tex[0], tex[3], 0.5f, 0.5f, 0.5f, pos.X + 0.0f, pos.Y + 0.0f, pos.Z + 0.0f, - tex[2], tex[3], 0.5f, 0.5f, 0.5f, pos.X + 0.0f, pos.Y + 0.0f, pos.Z + 1.0f, - tex[2], tex[1], 0.5f, 0.5f, 0.5f, pos.X + 0.0f, pos.Y + 1.0f, pos.Z + 1.0f + tex[0], tex[1], 0.5f, pos.X + 0.0f, pos.Y + 1.0f, pos.Z + 0.0f, + tex[0], tex[3], 0.5f, pos.X + 0.0f, pos.Y + 0.0f, pos.Z + 0.0f, + tex[2], tex[3], 0.5f, pos.X + 0.0f, pos.Y + 0.0f, pos.Z + 1.0f, + tex[2], tex[1], 0.5f, pos.X + 0.0f, pos.Y + 1.0f, pos.Z + 1.0f ); // Top if (AdjacentTest(curr, neighbors[2])) fixed (float* tex = this.tex[2].D) target.AddPrimitive(4, - tex[0], tex[1], 1.0f, 1.0f, 1.0f, pos.X + 0.0f, pos.Y + 1.0f, pos.Z + 0.0f, - tex[0], tex[3], 1.0f, 1.0f, 1.0f, pos.X + 0.0f, pos.Y + 1.0f, pos.Z + 1.0f, - tex[2], tex[3], 1.0f, 1.0f, 1.0f, pos.X + 1.0f, pos.Y + 1.0f, pos.Z + 1.0f, - tex[2], tex[1], 1.0f, 1.0f, 1.0f, pos.X + 1.0f, pos.Y + 1.0f, pos.Z + 0.0f + tex[0], tex[1], 1.0f, pos.X + 0.0f, pos.Y + 1.0f, pos.Z + 0.0f, + tex[0], tex[3], 1.0f, pos.X + 0.0f, pos.Y + 1.0f, pos.Z + 1.0f, + tex[2], tex[3], 1.0f, pos.X + 1.0f, pos.Y + 1.0f, pos.Z + 1.0f, + tex[2], tex[1], 1.0f, pos.X + 1.0f, pos.Y + 1.0f, pos.Z + 0.0f ); // Bottom if (AdjacentTest(curr, neighbors[3])) fixed (float* tex = this.tex[3].D) target.AddPrimitive(4, - tex[0], tex[1], 1.0f, 1.0f, 1.0f, pos.X + 0.0f, pos.Y + 0.0f, pos.Z + 1.0f, - tex[0], tex[3], 1.0f, 1.0f, 1.0f, pos.X + 0.0f, pos.Y + 0.0f, pos.Z + 0.0f, - tex[2], tex[3], 1.0f, 1.0f, 1.0f, pos.X + 1.0f, pos.Y + 0.0f, pos.Z + 0.0f, - tex[2], tex[1], 1.0f, 1.0f, 1.0f, pos.X + 1.0f, pos.Y + 0.0f, pos.Z + 1.0f + tex[0], tex[1], 1.0f, pos.X + 0.0f, pos.Y + 0.0f, pos.Z + 1.0f, + tex[0], tex[3], 1.0f, pos.X + 0.0f, pos.Y + 0.0f, pos.Z + 0.0f, + tex[2], tex[3], 1.0f, pos.X + 1.0f, pos.Y + 0.0f, pos.Z + 0.0f, + tex[2], tex[1], 1.0f, pos.X + 1.0f, pos.Y + 0.0f, pos.Z + 1.0f ); // Front if (AdjacentTest(curr, neighbors[4])) fixed (float* tex = this.tex[4].D) target.AddPrimitive(4, - tex[0], tex[1], 0.7f, 0.7f, 0.7f, pos.X + 0.0f, pos.Y + 1.0f, pos.Z + 1.0f, - tex[0], tex[3], 0.7f, 0.7f, 0.7f, pos.X + 0.0f, pos.Y + 0.0f, pos.Z + 1.0f, - tex[2], tex[3], 0.7f, 0.7f, 0.7f, pos.X + 1.0f, pos.Y + 0.0f, pos.Z + 1.0f, - tex[2], tex[1], 0.7f, 0.7f, 0.7f, pos.X + 1.0f, pos.Y + 1.0f, pos.Z + 1.0f + tex[0], tex[1], 0.7f, pos.X + 0.0f, pos.Y + 1.0f, pos.Z + 1.0f, + tex[0], tex[3], 0.7f, pos.X + 0.0f, pos.Y + 0.0f, pos.Z + 1.0f, + tex[2], tex[3], 0.7f, pos.X + 1.0f, pos.Y + 0.0f, pos.Z + 1.0f, + tex[2], tex[1], 0.7f, pos.X + 1.0f, pos.Y + 1.0f, pos.Z + 1.0f ); // Back if (AdjacentTest(curr, neighbors[5])) fixed (float* tex = this.tex[5].D) target.AddPrimitive(4, - tex[0], tex[1], 0.7f, 0.7f, 0.7f, pos.X + 1.0f, pos.Y + 1.0f, pos.Z + 0.0f, - tex[0], tex[3], 0.7f, 0.7f, 0.7f, pos.X + 1.0f, pos.Y + 0.0f, pos.Z + 0.0f, - tex[2], tex[3], 0.7f, 0.7f, 0.7f, pos.X + 0.0f, pos.Y + 0.0f, pos.Z + 0.0f, - tex[2], tex[1], 0.7f, 0.7f, 0.7f, pos.X + 0.0f, pos.Y + 1.0f, pos.Z + 0.0f + tex[0], tex[1], 0.7f, pos.X + 1.0f, pos.Y + 1.0f, pos.Z + 0.0f, + tex[0], tex[3], 0.7f, pos.X + 1.0f, pos.Y + 0.0f, pos.Z + 0.0f, + tex[2], tex[3], 0.7f, pos.X + 0.0f, pos.Y + 0.0f, pos.Z + 0.0f, + tex[2], tex[1], 0.7f, pos.X + 0.0f, pos.Y + 1.0f, pos.Z + 0.0f ); } diff --git a/Game/World/Chunk.cs b/Game/World/Chunk.cs index b089c06..500201b 100644 --- a/Game/World/Chunk.cs +++ b/Game/World/Chunk.cs @@ -48,12 +48,13 @@ public static void SetGenerator(Generator gen) } } - public Chunk(Int3 position, World world) + public Chunk(Int3 position, World world, bool build = true) { Position = position; World = world; Blocks = new BlockData[BlocksSize]; - Build(world.DaylightBrightness); + if (build) + Build(world.DaylightBrightness); } // TODO: somehow avoid it! not safe. @@ -91,8 +92,7 @@ public bool CheckReleaseable() => // For Garbage Collection private long mLastRequestTime; } - - [Serializable] + public class ChunkManager : Dictionary { public bool IsLoaded(Int3 chunkPos) => ContainsKey(chunkPos); diff --git a/Game/World/Player.cs b/Game/World/Player.cs index cafde90..f8b7843 100644 --- a/Game/World/Player.cs +++ b/Game/World/Player.cs @@ -34,8 +34,6 @@ public PlayerUpdateTask(Player player, uint worldId) public void Task(ChunkService srv) => player.Update(srv.Worlds.Get(worldId)); - IReadOnlyTask IReadOnlyTask.Clone() => (IReadOnlyTask) MemberwiseClone(); - private readonly Player player; private readonly uint worldId; } diff --git a/Game/World/World.cs b/Game/World/World.cs index a3bc549..1be2e08 100644 --- a/Game/World/World.cs +++ b/Game/World/World.cs @@ -74,12 +74,8 @@ public World(string name) private Chunk InsertChunk(ref Int3 pos, Chunk chunk) { - if (!Chunks.IsLoaded(pos)) - Chunks.Add(pos, chunk); - else - // TODO : FIX THIS GOD DAMNED ERROR, IT SHOULD NOT HAPPEN - Core.LogPort.Debug($"Warning: Dumplicate Chunk Adding on [{pos.X},{pos.Y},{pos.Z}]"); - return Chunks[pos]; + Chunks.Add(pos, chunk); + return chunk; } private static readonly Int3[] Delta = diff --git a/Game/World/WorldTasks.cs b/Game/World/WorldTasks.cs index b816e5f..9e7c542 100644 --- a/Game/World/WorldTasks.cs +++ b/Game/World/WorldTasks.cs @@ -18,7 +18,6 @@ // using System; -using Core.Math; using Game.Network; using Game.Utilities; using Xenko.Core.Mathematics; @@ -36,24 +35,18 @@ public class ResetChunkTask : IReadWriteTask { /** * \brief Add a constructed chunk into world. - * \param worldID the target world's id * \param chunk the target chunk */ - public ResetChunkTask(uint worldId, Chunk chunk) + public ResetChunkTask(Chunk chunk) { - this.worldId = worldId; this.chunk = chunk; } - public IReadWriteTask Clone() => (IReadWriteTask) MemberwiseClone(); - public void Task(ChunkService srv) { - var world = srv.Worlds.Get(worldId); - world.ResetChunkAndUpdate(chunk.Position, chunk); + chunk.World.ResetChunkAndUpdate(chunk.Position, chunk); } - - private readonly uint worldId; + private readonly Chunk chunk; } @@ -70,8 +63,6 @@ public UnloadChunkTask(World world, Int3 chunkPosition) this.chunkPosition = chunkPosition; } - public IReadWriteTask Clone() => (IReadWriteTask) MemberwiseClone(); - public void Task(ChunkService srv) { //TODO: for multiplayer situation, it should decrease ref counter instead of deleting @@ -84,31 +75,6 @@ public void Task(ChunkService srv) private class BuildOrLoadChunkTask : IReadOnlyTask { - private class AddToWorldTask : IReadWriteTask - { - /** - * \brief Add a constructed chunk into world. - * \param worldID the target world's id - * \param chunk the target chunk - */ - public AddToWorldTask(uint worldId, Chunk chunk) - { - this.worldId = worldId; - this.chunk = chunk; - } - - public IReadWriteTask Clone() => (IReadWriteTask) MemberwiseClone(); - - public void Task(ChunkService srv) - { - var world = srv.Worlds.Get(worldId); - world.InsertChunkAndUpdate(chunk.Position, chunk); - } - - private readonly uint worldId; - private readonly Chunk chunk; - } - /** * \brief Given a chunk, it will try to load it or build it * \param world the target world @@ -120,13 +86,9 @@ public BuildOrLoadChunkTask(World world, Int3 chunkPosition) this.chunkPosition = chunkPosition; } - public IReadOnlyTask Clone() => (IReadOnlyTask) MemberwiseClone(); - public void Task(ChunkService srv) { - // TODO: should try to load from local first - var chunk = new Chunk(chunkPosition, world); - srv.TaskDispatcher.Add(new AddToWorldTask(world.Id, chunk)); + srv.TaskDispatcher.Add(new ResetChunkTask(new Chunk(chunkPosition, world))); } private readonly World world; @@ -135,6 +97,26 @@ public void Task(ChunkService srv) private class LoadUnloadDetectorTask : IReadOnlyTask { + private class AddToWorldTask : IReadWriteTask + { + /** + * \brief Add a constructed chunk into world. + * \param worldID the target world's id + * \param chunk the target chunk + */ + public AddToWorldTask(Chunk chunk) + { + this.chunk = chunk; + } + + public void Task(ChunkService srv) + { + chunk.World.InsertChunkAndUpdate(chunk.Position, chunk); + } + + private readonly Chunk chunk; + } + public LoadUnloadDetectorTask(World world, Player player) { this.player = player; @@ -151,6 +133,8 @@ public void Task(ChunkService cs) foreach (var loadPos in loadList) { + // load a fake chunk + cs.TaskDispatcher.Add(new AddToWorldTask(new Chunk(loadPos.Value, world, false))); cs.TaskDispatcher.Add(new BuildOrLoadChunkTask(world, loadPos.Value)); if (!cs.IsAuthority) { @@ -207,8 +191,6 @@ private static void GenerateLoadUnloadList(World world, Int3 centerPos, int load // TODO: Remove Type1 Clone private static int ChebyshevDistance(Int3 l, Int3 r) => Math.Max(Math.Max(Math.Abs(l.X - r.X), Math.Abs(l.Y - r.Y)), Math.Abs(l.Z - r.Z)); - public IReadOnlyTask Clone() => (IReadOnlyTask) MemberwiseClone(); - private readonly Player player; private readonly World world; } diff --git a/NEWorld.sln b/NEWorld.sln index b3ff3a1..93a1eb7 100644 --- a/NEWorld.sln +++ b/NEWorld.sln @@ -1,3 +1,4 @@ + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.28307.271 diff --git a/NEWorld.sln.DotSettings.user b/NEWorld.sln.DotSettings.user index 4d81f0b..620e1d7 100644 --- a/NEWorld.sln.DotSettings.user +++ b/NEWorld.sln.DotSettings.user @@ -1,10 +1,15 @@  + 2 True <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> - <Policy Inspect="False" Prefix="Keyboard" Suffix="" Style="AaBb"><ExtraRule Prefix="Touch" Suffix="" Style="AaBb" /></Policy> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> True True <AssemblyExplorer> @@ -14,4 +19,6 @@ <Assembly Path="C:\Program Files\dotnet\sdk\NuGetFallbackFolder\netstandard.library\2.0.3\build\netstandard2.0\ref\netstandard.dll" /> <Assembly Path="C:\Program Files\dotnet\shared\Microsoft.NETCore.App\2.1.7\System.Net.Sockets.dll" /> <Assembly Path="C:\Program Files\dotnet\shared\Microsoft.NETCore.App\2.1.7\System.Console.dll" /> + <Assembly Path="C:\Program Files\dotnet\shared\Microsoft.NETCore.App\2.1.7\System.Threading.Thread.dll" /> + <Assembly Path="C:\Users\yshli\.nuget\packages\xenko\3.1.0.1-beta01-0406\lib\netstandard2.0\Xenko.dll" /> </AssemblyExplorer> \ No newline at end of file diff --git a/NEWorld/Assets/Ground.xkpromodel b/NEWorld/Assets/Ground.xkpromodel deleted file mode 100644 index b62c07e..0000000 --- a/NEWorld/Assets/Ground.xkpromodel +++ /dev/null @@ -1,12 +0,0 @@ -!ProceduralModelAsset -Id: 816675f4-c442-4d9f-b3cf-c15382ca0e27 -SerializedVersion: {Xenko: 2.0.0.0} -Tags: [] -Type: !PlaneProceduralModel - Size: {X: 10.0, Y: 10.0} - Tessellation: {X: 1, Y: 1} - Scale: {X: 1.0, Y: 1.0, Z: 1.0} - UvScale: {X: 1.0, Y: 1.0} - LocalOffset: {X: 0.0, Y: 0.0, Z: 0.0} - MaterialInstance: - Material: 55fec3e9-4b2d-4c97-b731-c372d86a4116:Ground Material diff --git a/NEWorld/Assets/MainScene.xkscene b/NEWorld/Assets/MainScene.xkscene index ba35014..287d17a 100644 --- a/NEWorld/Assets/MainScene.xkscene +++ b/NEWorld/Assets/MainScene.xkscene @@ -10,8 +10,6 @@ Hierarchy: - ref!! 1cc17873-cca9-48fe-a61f-a1d061e26959 - ref!! b1058700-11a7-4296-bb63-2fefdb5f28b7 - ref!! ffafa74d-ee74-4f21-aeba-f6565b12674d - - ref!! 4255a2e2-6b21-4e69-94cd-af3e5d8235b5 - - ref!! 57897b9f-c148-4d0f-a291-1a81b3b30d7c - ref!! b1588acf-0fcd-4377-bf5d-6c81dcf34484 Parts: - Entity: @@ -38,34 +36,6 @@ Hierarchy: PartitionMode: !LightDirectionalShadowMap.PartitionLogarithmic {} ComputeTransmittance: false BiasParameters: {} - - Entity: - Id: 4255a2e2-6b21-4e69-94cd-af3e5d8235b5 - Name: Ground - Components: - 9006dbffc8f216b10892cc70f3fb67f7: !TransformComponent - Id: 91f18c07-871d-4622-a137-7728cb87fca6 - Position: {X: 0.0, Y: 0.0, Z: 0.0} - Rotation: {X: 0.0, Y: 0.0, Z: 0.0, W: 1.0} - Scale: {X: 1.0, Y: 1.0, Z: 1.0} - Children: {} - 6c8fc5687a473e6e9938458d5d96decc: !ModelComponent - Id: a8d01fe3-9443-49ec-92c8-c5a921db176c - Model: 816675f4-c442-4d9f-b3cf-c15382ca0e27:Ground - Materials: {} - - Entity: - Id: 57897b9f-c148-4d0f-a291-1a81b3b30d7c - Name: Sphere - Components: - c560157b4d71df9713af8c324a6ff505: !TransformComponent - Id: c1c745a0-5c33-48cd-98ca-e61b2b56c7b2 - Position: {X: 0.0, Y: 0.5, Z: 0.0} - Rotation: {X: 0.0, Y: 0.0, Z: 0.0, W: 1.0} - Scale: {X: 1.0, Y: 1.0, Z: 1.0} - Children: {} - 3eb6684a91bc04721ae11b10c9c7c2c7: !ModelComponent - Id: 50f99228-00d8-48a0-bce8-dcbcf37848cc - Model: 6312d397-4e68-4b04-9a19-232f32bf733b:Sphere - Materials: {} - Entity: Id: 8d346802-8fe6-4d18-957c-c6dd8404ea84 Name: Camera @@ -101,7 +71,7 @@ Hierarchy: Children: {} b770b44f804ee2b92e0cf2e21cb516aa: !BackgroundComponent Id: 9a17a387-5b6c-4dde-9185-771f3dd4b737 - Texture: d5bf7b66-7df1-475b-8721-f4755a9c6f96:Skybox texture + Texture: null - Entity: Id: b1588acf-0fcd-4377-bf5d-6c81dcf34484 Name: MainScript @@ -114,6 +84,7 @@ Hierarchy: Children: {} d80b4d8f8e4c7517fb33931ae02a1875: !NEWorld.MainScript,NEWorld Id: ad46feb5-e564-418b-8912-d79cfbc7a12d + Material: a0b217f1-284e-4307-abd6-b42f56bedff1:Material - Entity: Id: ffafa74d-ee74-4f21-aeba-f6565b12674d Name: Ambient light diff --git a/NEWorld/Assets/Ground Material.xkmat b/NEWorld/Assets/Material.xkmat similarity index 54% rename from NEWorld/Assets/Ground Material.xkmat rename to NEWorld/Assets/Material.xkmat index 5b31bda..58655e3 100644 --- a/NEWorld/Assets/Ground Material.xkmat +++ b/NEWorld/Assets/Material.xkmat @@ -1,11 +1,13 @@ !MaterialAsset -Id: 55fec3e9-4b2d-4c97-b731-c372d86a4116 +Id: a0b217f1-284e-4307-abd6-b42f56bedff1 SerializedVersion: {Xenko: 2.0.0.0} Tags: [] Attributes: Diffuse: !MaterialDiffuseMapFeature - DiffuseMap: !ComputeColor - Value: {R: 0.141176477, G: 0.141176477, B: 0.141176477, A: 1.0} + DiffuseMap: !ComputeShaderClassColor + MixinReference: VertexTextureTerrain + Generics: {} + CompositionNodes: {} DiffuseModel: !MaterialDiffuseLambertModelFeature {} Overrides: UVScale: {X: 1.0, Y: 1.0} diff --git a/NEWorld/Assets/Sphere Material.xkmat b/NEWorld/Assets/Sphere Material.xkmat deleted file mode 100644 index 9b4b2e9..0000000 --- a/NEWorld/Assets/Sphere Material.xkmat +++ /dev/null @@ -1,12 +0,0 @@ -!MaterialAsset -Id: 4296e382-6ca8-40e8-982b-4a427e56f687 -SerializedVersion: {Xenko: 2.0.0.0} -Tags: [] -Attributes: - Diffuse: !MaterialDiffuseMapFeature - DiffuseMap: !ComputeColor - Value: {R: 0.549019635, G: 0.549019635, B: 0.549019635, A: 1.0} - DiffuseModel: !MaterialDiffuseLambertModelFeature {} - Overrides: - UVScale: {X: 1.0, Y: 1.0} -Layers: {} diff --git a/NEWorld/Assets/Sphere.xkpromodel b/NEWorld/Assets/Sphere.xkpromodel deleted file mode 100644 index 1d027c4..0000000 --- a/NEWorld/Assets/Sphere.xkpromodel +++ /dev/null @@ -1,11 +0,0 @@ -!ProceduralModelAsset -Id: 6312d397-4e68-4b04-9a19-232f32bf733b -SerializedVersion: {Xenko: 2.0.0.0} -Tags: [] -Type: !SphereProceduralModel - Tessellation: 30 - Scale: {X: 1.0, Y: 1.0, Z: 1.0} - UvScale: {X: 1.0, Y: 1.0} - LocalOffset: {X: 0.0, Y: 0.0, Z: 0.0} - MaterialInstance: - Material: 4296e382-6ca8-40e8-982b-4a427e56f687:Sphere Material diff --git a/NEWorld/Effects/VertexTextureTerrain.cs b/NEWorld/Effects/VertexTextureTerrain.cs new file mode 100644 index 0000000..02bf025 --- /dev/null +++ b/NEWorld/Effects/VertexTextureTerrain.cs @@ -0,0 +1,23 @@ +// +// Do not edit this file yourself! +// +// This code was generated by Xenko Shader Mixin Code Generator. +// To generate it yourself, please install Xenko.VisualStudio.Package .vsix +// and re-save the associated .xkfx. +// + +using System; +using Xenko.Core; +using Xenko.Rendering; +using Xenko.Graphics; +using Xenko.Shaders; +using Xenko.Core.Mathematics; +using Buffer = Xenko.Graphics.Buffer; + +namespace Xenko.Rendering +{ + public static partial class VertexTextureTerrainKeys + { + public static readonly ObjectParameterKey MeshTextureSampler = ParameterKeys.NewObject(); + } +} diff --git a/NEWorld/Effects/VertexTextureTerrain.xksl b/NEWorld/Effects/VertexTextureTerrain.xksl new file mode 100644 index 0000000..2406cad --- /dev/null +++ b/NEWorld/Effects/VertexTextureTerrain.xksl @@ -0,0 +1,17 @@ +shader VertexTextureTerrain : ComputeColor +{ + SamplerState MeshTextureSampler + { + Filter = MIN_MAG_MIP_LINEAR; + AddressU = Wrap; + AddressV = Wrap; + }; + // stage Texture2D Almg; + stage stream float2 TexCoord : TEXCOORD0; + stage stream float Color : COLOR0; + + override float4 Compute() + { + return float4(streams.Color, streams.Color, streams.Color, streams.Color);//Almg.Sample(MeshTextureSampler, streams.TexCoord); + } +}; diff --git a/NEWorld/MainScript.cs b/NEWorld/MainScript.cs index 7afa80a..2b0e34f 100644 --- a/NEWorld/MainScript.cs +++ b/NEWorld/MainScript.cs @@ -21,10 +21,8 @@ using Core.Utilities; using Game; using Game.Network; -using Game.Terrain; using Game.World; using System; -using System.Reflection; using System.Threading; using System.Threading.Tasks; using Xenko.Core.Diagnostics; @@ -47,10 +45,12 @@ public static IGame Game set { _game = value; - IndexBuffer = IndexBufferBuilder.Build(); + IndexBuffer = new IndexBufferBinding(IndexBufferBuilder.Build(), true, 262144 / 2 * 3); } } + public static Material Material { get; set; } + public static Scene OperatingScene { get; set; } public static GraphicsDevice GraphicsDevice => Game.GraphicsDevice; @@ -61,11 +61,11 @@ public static IGame Game public static readonly VertexDeclaration VertexLayout = new VertexDeclaration( VertexElement.TextureCoordinate(), - VertexElement.Color(), + VertexElement.Color(), VertexElement.Position() ); - public static Buffer IndexBuffer { get; private set; } + public static IndexBufferBinding IndexBuffer { get; private set; } } public static class IndexBufferBuilder @@ -77,54 +77,22 @@ public static Buffer Build() for (var i = 0; i < 262144 / 4; ++i) { var b = i * 4; - idx[cnt++] = b; - idx[cnt++] = b + 1; idx[cnt++] = b + 2; + idx[cnt++] = b + 1; idx[cnt++] = b; - idx[cnt++] = b + 2; idx[cnt++] = b + 3; + idx[cnt++] = b + 2; + idx[cnt++] = b; } return Buffer.Index.New(Context.GraphicsDevice, idx); } } - public class VertexBuilder : IVertexBuilder - { - public VertexBuilder(int size) => Data = new float[size]; - - public void AddPrimitive(int verts, params float[] data) - { - Count += verts; - data.CopyTo(Data, Size); - Size += data.Length; - } - - public Mesh Dump() - { - return new Mesh - { - Draw = - { - DrawCount = 1, - IndexBuffer = new IndexBufferBinding(Context.IndexBuffer, true, Count / 2 * 3), - PrimitiveType = PrimitiveType.TriangleList, - StartLocation = 0, - VertexBuffers = new[] - { - new VertexBufferBinding(Buffer.Vertex.New(Context.Game.GraphicsDevice, Data), Context.VertexLayout, Count) - } - } - }; - } - - public int Size; - public int Count; - public readonly float[] Data; - } - public class MainScript : SyncScript { + public Material Material; + private void InitializeModules() { Core.Module.Modules.Instance.Load("Main"); @@ -133,7 +101,8 @@ private void InitializeModules() private void InitializeContext() { Context.Game = Game; - Context.OperatingScene = SceneSystem.SceneInstance.RootScene; + Context.Material = Material; + Context.OperatingScene = Entity.Scene; Core.LogPort.Logger = Log; Log.ActivateLog(LogMessageType.Debug); } @@ -168,12 +137,15 @@ private void LoadPlayer() private async Task EnterCurrentWorld() { + var chunkService = Singleton.Instance; currentWorld = Singleton.Instance.Worlds.Get(await RequestWorld()); + currentWorld.RegisterChunkTasks(chunkService, player); + chunkService.EnableDispatcher(); } private void StartTerrainRenderService() { - rdWorld = new Renderer.RdWorld(currentWorld, player, 4); + rdWorld = new Renderer.RdWorld(currentWorld, player, 2); } private async void Initialize() diff --git a/NEWorld/NEWorld.csproj b/NEWorld/NEWorld.csproj index 03baf5c..7a290e9 100644 --- a/NEWorld/NEWorld.csproj +++ b/NEWorld/NEWorld.csproj @@ -1,49 +1,39 @@ - netstandard2.0 - + + true + + + true + - - - - - + True True - VoxelBasic.xkfx - - - True - True - VoxelTraditional.xksl + VertexTextureTerrain.xksl - - - XenkoShaderKeyGenerator - VoxelBasic.cs - - + XenkoShaderKeyGenerator - VoxelTraditional.cs + VertexTextureTerrain.cs - + \ No newline at end of file diff --git a/NEWorld/Renderer/RdChunk.cs b/NEWorld/Renderer/RdChunk.cs index 890952a..09f5e71 100644 --- a/NEWorld/Renderer/RdChunk.cs +++ b/NEWorld/Renderer/RdChunk.cs @@ -22,7 +22,6 @@ using Xenko.Engine; using Xenko.Rendering; - namespace NEWorld.Renderer { /** @@ -32,33 +31,33 @@ namespace NEWorld.Renderer */ public class ChunkRenderData { - public ChunkRenderData() - { - VaOpacity = new VertexBuilder(262144 * (2 + 3 + 0 + 3)); - VaTranslucent = new VertexBuilder(262144 * (2 + 3 + 0 + 3)); - } + public Model Model { get; private set; } /** * \brief Generate the render data, namely VA, from a chunk. * Does not involve OpenGL functions. * \param chunk the chunk to be rendered. */ - public void Generate(Game.World.Chunk chunk) + public void Generate(Chunk chunk) { + var vaOpacity = new VertexBuilder(262144 * (2 + 1 + 3)); + var vaTranslucent = new VertexBuilder(262144 * (2 + 1 + 3)); var tmp = new Int3(); - for (tmp.X = 0; tmp.X < Game.World.Chunk.Size; ++tmp.X) - for (tmp.Y = 0; tmp.Y < Game.World.Chunk.Size; ++tmp.Y) - for (tmp.Z = 0; tmp.Z < Game.World.Chunk.Size; ++tmp.Z) + for (tmp.X = 0; tmp.X < Chunk.Size; ++tmp.X) + for (tmp.Y = 0; tmp.Y < Chunk.Size; ++tmp.Y) + for (tmp.Z = 0; tmp.Z < Chunk.Size; ++tmp.Z) { var b = chunk[tmp]; - var target = Blocks.Index[b.Id].IsTranslucent ? VaTranslucent : VaOpacity; + var target = Blocks.Index[b.Id].IsTranslucent ? vaTranslucent : vaOpacity; BlockRenderers.Render(target, b.Id, chunk, tmp); } - } - - public VertexBuilder VaOpacity { get; } // {262144, VertexFormat(2, 3, 0, 3)}; - public VertexBuilder VaTranslucent { get; } //{262144, VertexFormat(2, 3, 0, 3)}; + var mesh0 = vaOpacity.Dump(); + var mesh1 = vaTranslucent.Dump(); + Model = (mesh0 != null && mesh1 != null) ? new Model {new MaterialInstance(Context.Material)} : null; + if (mesh0 != null) Model?.Add(mesh0); + if (mesh1 != null) Model?.Add(mesh1); + } } /** @@ -70,11 +69,13 @@ public class RdChunk { public RdChunk(ChunkRenderData data, Vector3 chunkPosition) { - Update(data); Entity = new Entity(); - Entity.GetOrCreate().Position = chunkPosition * Game.World.Chunk.Size; + Update(data); + Entity.GetOrCreate().Position = chunkPosition * Chunk.Size; } + public Entity Entity { get; } + /** * \brief Generate VBO from VA. Note that this function will call * OpenGL functions and thus can be only called from the @@ -83,12 +84,11 @@ public RdChunk(ChunkRenderData data, Vector3 chunkPosition) */ public void Update(ChunkRenderData data) { - var model = new Model(); - model.Add(data.VaOpacity.Dump()); - model.Add(data.VaTranslucent.Dump()); - Entity.GetOrCreate().Model = model; + var model = data.Model; + if (model != null) + Entity.GetOrCreate().Model = data.Model; + else + Entity.Remove(); } - - public Entity Entity { get; } } } diff --git a/NEWorld/Renderer/RdTextures.cs b/NEWorld/Renderer/RdTextures.cs new file mode 100644 index 0000000..15c39c7 --- /dev/null +++ b/NEWorld/Renderer/RdTextures.cs @@ -0,0 +1,18 @@ +using Core; +using Game.Terrain; + +namespace NEWorld.Renderer +{ + [DeclareService("BlockTextures")] + public class RdTextures : IBlockTextures + { + public uint Add(string path) + { + return 0; + } + + public unsafe void GetTexturePos(float* pos, uint id) + { + } + } +} \ No newline at end of file diff --git a/NEWorld/Renderer/RdWorld.cs b/NEWorld/Renderer/RdWorld.cs index 268441a..42b4786 100644 --- a/NEWorld/Renderer/RdWorld.cs +++ b/NEWorld/Renderer/RdWorld.cs @@ -71,8 +71,6 @@ public void Task(ChunkService cs) // TODO: Remove Type1 Clone private static int ChebyshevDistance(Int3 l, Int3 r) => Math.Max(Math.Max(Math.Abs(l.X - r.X), Math.Abs(l.Y - r.Y)), Math.Abs(l.Z - r.Z)); - public IReadOnlyTask Clone() => (IReadOnlyTask) MemberwiseClone(); - private static readonly Int3[] Delta = { new Int3(1, 0, 0), new Int3(-1, 0, 0), @@ -115,8 +113,6 @@ public void Task(ChunkService srv) } } - public IRenderTask Clone() => (IRenderTask) MemberwiseClone(); - private readonly World world; private readonly Int3 position; private readonly ChunkRenderData chunkRenderData; diff --git a/NEWorld/Renderer/VertexBuilder.cs b/NEWorld/Renderer/VertexBuilder.cs new file mode 100644 index 0000000..d857989 --- /dev/null +++ b/NEWorld/Renderer/VertexBuilder.cs @@ -0,0 +1,50 @@ +using System; +using System.Runtime.InteropServices; +using Game.Terrain; +using Xenko.Graphics; +using Xenko.Rendering; +using Buffer = Xenko.Graphics.Buffer; + +namespace NEWorld.Renderer +{ + public class VertexBuilder : IVertexBuilder + { + public VertexBuilder(int size) => data = Marshal.AllocHGlobal(size * sizeof(float)); + + ~VertexBuilder() => Marshal.FreeHGlobal(data); + + public void AddPrimitive(int verts, params float[] data) + { + count += verts; + Marshal.Copy(data, 0, this.data + size * sizeof(float), data.Length); + size += data.Length; + } + + public Mesh Dump() + { + return count > 0 + ? new Mesh + { + Draw = new MeshDraw() + { + DrawCount = count / 2 * 3, + IndexBuffer = Context.IndexBuffer, + PrimitiveType = PrimitiveType.TriangleList, + StartLocation = 0, + VertexBuffers = new[] + { + new VertexBufferBinding(Buffer.Vertex.New(Context.Game.GraphicsDevice, new DataPointer(data, size * sizeof(float))), + Context.VertexLayout, count) + } + }, + MaterialIndex = 0 + } + : null; + } + + private int size; + private int count; + private readonly IntPtr data; + } + +} \ No newline at end of file diff --git a/NEWorldShell/NEWorldShell.csproj b/NEWorldShell/NEWorldShell.csproj index 4f23bb2..015cae8 100644 --- a/NEWorldShell/NEWorldShell.csproj +++ b/NEWorldShell/NEWorldShell.csproj @@ -17,7 +17,7 @@ true full false - ..\bin\Debug\ + ..\Bin\Windows\Debug\ DEBUG;TRACE prompt 4 @@ -26,7 +26,7 @@ AnyCPU pdbonly true - ..\bin\Release\ + ..\Bin\Windows\Release\ TRACE prompt 4 diff --git a/NEWorldShell/Program.cs b/NEWorldShell/Program.cs index e2c8c43..98df9cb 100644 --- a/NEWorldShell/Program.cs +++ b/NEWorldShell/Program.cs @@ -17,14 +17,13 @@ // along with NEWorld. If not, see . // -using System.Reflection; using Core; using Core.Module; using Game.Network; namespace NEWorldShell { - internal class Program + internal static class Program { public static void Main(string[] args) { @@ -36,4 +35,4 @@ public static void Main(string[] args) cli.Start(); } } -} \ No newline at end of file +} From 641565aa9855b3d112987e5fa1f690809d2453da Mon Sep 17 00:00:00 2001 From: DWVoid Date: Thu, 31 Jan 2019 19:17:58 +0800 Subject: [PATCH 04/23] Get Things Working --- Core/Core.csproj | 18 +- Core/Generic.cs | 155 -------- Core/LogPort.cs | 2 +- Core/Math/Mat4.cs | 357 +----------------- Core/Math/Vector.cs | 164 -------- Core/Network/Client.cs | 13 +- Core/Network/ConnectionHost.cs | 44 ++- Core/Network/Protocols.cs | 9 +- Core/Rect.cs | 78 ---- Core/Services.cs | 2 +- Core/Utilities/EndianCheck.cs | 43 --- Core/Utilities/Singleton.cs | 17 +- Core/Utilities/StrictDispose.cs | 84 ----- Core/packages.config | 5 - Game/ChunkService.cs | 13 +- Game/Game.csproj | 13 - Game/Network/Protocols.cs | 70 ++-- Game/Network/Server.cs | 32 +- Game/TaskDispatcher.cs | 15 +- Game/Terrain/Block.cs | 84 +++-- Game/Terrain/Matrix.cs | 63 ---- Game/Utilities/Aabb.cs | 12 +- Game/Utilities/OrderedList.cs | 51 ++- Game/World/Blocks.cs | 6 +- Game/World/Chunk.cs | 87 +++-- Game/World/Object.cs | 25 +- Game/World/Player.cs | 71 ++-- Game/World/PlayerObject.cs | 21 +- Game/World/World.cs | 106 ++++-- Game/World/WorldTasks.cs | 101 +++-- Main/Main.cs | 104 +++-- Main/Main.csproj | 13 - NEWorld.Linux/NEWorld.Linux.csproj | 2 +- NEWorld.Linux/NEWorldApp.cs | 6 +- .../Assets/EffectCompileLog.xkeffectlog | 92 +++++ NEWorld.Windows/NEWorldApp.cs | 6 +- NEWorld.macOS/NEWorld.macOS.csproj | 2 +- NEWorld.macOS/NEWorldApp.cs | 6 +- NEWorld.sln.DotSettings.user | 3 + NEWorld/Assets/GameSettings.xkgamesettings | 25 -- NEWorld/Assets/GraphicsCompositor.xkgfxcomp | 8 +- NEWorld/Assets/MainScene.xkscene | 39 +- NEWorld/Assets/Material.xkmat | 6 +- NEWorld/Assets/Sphere.xkpromodel | 12 + NEWorld/BasicCameraController.cs | 68 +--- NEWorld/Effects/VertexTextureTerrain.xksl | 3 +- NEWorld/MainScript.cs | 63 ++-- NEWorld/NEWorld.csproj | 12 +- NEWorld/Renderer/RdChunk.cs | 35 +- NEWorld/Renderer/RdWorld.cs | 85 +++-- NEWorld/Renderer/VertexBuilder.cs | 43 ++- NEWorldShell/Cli.cs | 6 +- NEWorldShell/Command.cs | 48 +-- NEWorldShell/NEWorldShell.csproj | 3 +- NEWorldShell/Program.cs | 2 +- 55 files changed, 862 insertions(+), 1591 deletions(-) delete mode 100644 Core/Math/Vector.cs delete mode 100644 Core/Rect.cs delete mode 100644 Core/Utilities/EndianCheck.cs delete mode 100644 Core/Utilities/StrictDispose.cs delete mode 100644 Core/packages.config delete mode 100644 Game/Terrain/Matrix.cs create mode 100644 NEWorld/Assets/Sphere.xkpromodel diff --git a/Core/Core.csproj b/Core/Core.csproj index dbd2fdf..7396d2f 100644 --- a/Core/Core.csproj +++ b/Core/Core.csproj @@ -5,20 +5,8 @@ - - - - - - - - - - - - - - - + + + diff --git a/Core/Generic.cs b/Core/Generic.cs index 1b4291e..f5389cd 100644 --- a/Core/Generic.cs +++ b/Core/Generic.cs @@ -21,161 +21,6 @@ namespace Core { public static class Generic { - public static dynamic Cast(dynamic a) - { - return (T) a; - } - - public static dynamic Add(dynamic a, dynamic b) - { - return a + b; - } - - public static dynamic Substract(dynamic a, dynamic b) - { - return a - b; - } - - public static dynamic Multiply(dynamic a, dynamic b) - { - return a * b; - } - - public static dynamic Divide(dynamic a, dynamic b) - { - return a / b; - } - - public static dynamic Modulus(dynamic a, dynamic b) - { - return a % b; - } - - public static dynamic AddBy(dynamic a, dynamic b) - { - return a += b; - } - - public static dynamic SubstractBy(dynamic a, dynamic b) - { - return a -= b; - } - - public static dynamic MultiplyBy(ref dynamic a, dynamic b) - { - return a *= b; - } - - public static dynamic DivideBy(dynamic a, dynamic b) - { - return a /= b; - } - - public static dynamic ModulusBy(dynamic a, dynamic b) - { - return a %= b; - } - - public static dynamic Square(dynamic a) - { - return a * a; - } - - public static dynamic Negate(dynamic a) - { - return -a; - } - - public static dynamic Increase(dynamic a) - { - return ++a; - } - - public static dynamic Decrease(dynamic a) - { - return --a; - } - - public static dynamic IncreaseAfter(dynamic a) - { - return a++; - } - - public static dynamic DecreaseAfter(dynamic a) - { - return a--; - } - - public static bool Less(dynamic a, dynamic b) - { - return a < b; - } - - public static bool LessEqual(dynamic a, dynamic b) - { - return a <= b; - } - - public static bool Larger(dynamic a, dynamic b) - { - return a > b; - } - - public static bool LargerEqual(dynamic a, dynamic b) - { - return a >= b; - } - - public static bool Equal(dynamic a, dynamic b) - { - return a == b; - } - - public static double Sqrt(dynamic a) - { - return System.Math.Sqrt((double) a); - } - - public static double Sin(dynamic a) - { - return System.Math.Sin((double) a); - } - - public static double Cos(dynamic a) - { - return System.Math.Cos((double) a); - } - - public static double Tan(dynamic a) - { - return System.Math.Tan((double) a); - } - - public static double Abs(dynamic a) - { - return System.Math.Abs(a); - } - - public static dynamic Min(dynamic a, dynamic b) - { - return Less(a, b) ? a : b; - } - - public static dynamic Max(dynamic a, dynamic b) - { - return Larger(a, b) ? a : b; - } - - public static void MinEqual(dynamic a, dynamic b) - { - if (Less(b, a)) a = b; - } - - public static void MaxEqual(dynamic a, dynamic b) - { - if (Larger(b, a)) a = b; - } - public static void Swap(ref T a, ref T b) { var t = a; diff --git a/Core/LogPort.cs b/Core/LogPort.cs index 35f45af..a7be568 100644 --- a/Core/LogPort.cs +++ b/Core/LogPort.cs @@ -5,7 +5,7 @@ namespace Core { public static class LogPort { - public static Logger Logger { get; set; } + public static Logger Logger { private get; set; } public static void Debug(string str) { diff --git a/Core/Math/Mat4.cs b/Core/Math/Mat4.cs index 0c811fd..7b61041 100644 --- a/Core/Math/Mat4.cs +++ b/Core/Math/Mat4.cs @@ -18,255 +18,10 @@ // using System.Collections.Generic; +using Xenko.Core.Mathematics; namespace Core.Math { - public struct Mat4F - { - private const float Pi = 3.1415926535897932f; - - public float[] Data; - - public Mat4F(float x) - { - Data = new[] - { - x, 0.0f, 0.0f, 0.0f, - 0.0f, x, 0.0f, 0.0f, - 0.0f, 0.0f, x, 0.0f, - 0.0f, 0.0f, 0.0f, x - }; - } - - public static Mat4F operator +(Mat4F lhs, Mat4F rhs) - { - var result = lhs; - for (var i = 0; i < 16; ++i) result.Data[i] += rhs.Data[i]; - - return result; - } - - public static Mat4F operator *(Mat4F lhs, Mat4F rhs) - { - var res = new Mat4F(0.0f); - res.Data[0] = lhs.Data[0] * rhs.Data[0] + lhs.Data[1] * rhs.Data[4] + lhs.Data[2] * rhs.Data[8] + - lhs.Data[3] * rhs.Data[12]; - res.Data[1] = lhs.Data[0] * rhs.Data[1] + lhs.Data[1] * rhs.Data[5] + lhs.Data[2] * rhs.Data[9] + - lhs.Data[3] * rhs.Data[13]; - res.Data[2] = lhs.Data[0] * rhs.Data[2] + lhs.Data[1] * rhs.Data[6] + lhs.Data[2] * rhs.Data[10] + - lhs.Data[3] * rhs.Data[14]; - res.Data[3] = lhs.Data[0] * rhs.Data[3] + lhs.Data[1] * rhs.Data[7] + lhs.Data[2] * rhs.Data[11] + - lhs.Data[3] * rhs.Data[15]; - res.Data[4] = lhs.Data[4] * rhs.Data[0] + lhs.Data[5] * rhs.Data[4] + lhs.Data[6] * rhs.Data[8] + - lhs.Data[7] * rhs.Data[12]; - res.Data[5] = lhs.Data[4] * rhs.Data[1] + lhs.Data[5] * rhs.Data[5] + lhs.Data[6] * rhs.Data[9] + - lhs.Data[7] * rhs.Data[13]; - res.Data[6] = lhs.Data[4] * rhs.Data[2] + lhs.Data[5] * rhs.Data[6] + lhs.Data[6] * rhs.Data[10] + - lhs.Data[7] * rhs.Data[14]; - res.Data[7] = lhs.Data[4] * rhs.Data[3] + lhs.Data[5] * rhs.Data[7] + lhs.Data[6] * rhs.Data[11] + - lhs.Data[7] * rhs.Data[15]; - res.Data[8] = lhs.Data[8] * rhs.Data[0] + lhs.Data[9] * rhs.Data[4] + lhs.Data[10] * rhs.Data[8] + - lhs.Data[11] * rhs.Data[12]; - res.Data[9] = lhs.Data[8] * rhs.Data[1] + lhs.Data[9] * rhs.Data[5] + lhs.Data[10] * rhs.Data[9] + - lhs.Data[11] * rhs.Data[13]; - res.Data[10] = lhs.Data[8] * rhs.Data[2] + lhs.Data[9] * rhs.Data[6] + lhs.Data[10] * rhs.Data[10] + - lhs.Data[11] * rhs.Data[14]; - res.Data[11] = lhs.Data[8] * rhs.Data[3] + lhs.Data[9] * rhs.Data[7] + lhs.Data[10] * rhs.Data[11] + - lhs.Data[11] * rhs.Data[15]; - res.Data[12] = lhs.Data[12] * rhs.Data[0] + lhs.Data[13] * rhs.Data[4] + lhs.Data[14] * rhs.Data[8] + - lhs.Data[15] * rhs.Data[12]; - res.Data[13] = lhs.Data[12] * rhs.Data[1] + lhs.Data[13] * rhs.Data[5] + lhs.Data[14] * rhs.Data[9] + - lhs.Data[15] * rhs.Data[13]; - res.Data[14] = lhs.Data[12] * rhs.Data[2] + lhs.Data[13] * rhs.Data[6] + lhs.Data[14] * rhs.Data[10] + - lhs.Data[15] * rhs.Data[14]; - res.Data[15] = lhs.Data[12] * rhs.Data[3] + lhs.Data[13] * rhs.Data[7] + lhs.Data[14] * rhs.Data[11] + - lhs.Data[15] * rhs.Data[15]; - return res; - } - - // Swap row r1, row r2 - public void SwapRows(uint r1, uint r2) - { - Generic.Swap(ref Data[r1 * 4 + 0], ref Data[r2 * 4 + 0]); - Generic.Swap(ref Data[r1 * 4 + 1], ref Data[r2 * 4 + 1]); - Generic.Swap(ref Data[r1 * 4 + 2], ref Data[r2 * 4 + 2]); - Generic.Swap(ref Data[r1 * 4 + 3], ref Data[r2 * 4 + 3]); - } - - // Row r *= k - public void MultRow(uint r, float k) - { - Data[r * 4 + 0] *= k; - Data[r * 4 + 1] *= k; - Data[r * 4 + 2] *= k; - Data[r * 4 + 3] *= k; - } - - // Row dst += row src * k - public void MultAndAdd(uint src, uint dst, float k) - { - Data[dst * 4 + 0] += Data[src * 4 + 0] * k; - Data[dst * 4 + 1] += Data[src * 4 + 1] * k; - Data[dst * 4 + 2] += Data[src * 4 + 2] * k; - Data[dst * 4 + 3] += Data[src * 4 + 3] * k; - } - - - // Get transposed matrix - public Mat4F Transposed() - { - return new Mat4F(0.0f) - { - Data = - { - [0] = Data[0], - [1] = Data[4], - [2] = Data[8], - [3] = Data[12], - [4] = Data[1], - [5] = Data[5], - [6] = Data[9], - [7] = Data[13], - [8] = Data[2], - [9] = Data[6], - [10] = Data[10], - [11] = Data[14], - [12] = Data[3], - [13] = Data[7], - [14] = Data[11], - [15] = Data[15] - } - }; - } - - // Inverse matrix - public Mat4F Inverse(float[] data) - { - Data = data; - var res = Identity(); - for (uint i = 0; i < 4; i++) - { - var p = i; - for (var j = i + 1; j < 4; j++) - if (System.Math.Abs(Data[j * 4 + i]) > System.Math.Abs(Data[p * 4 + i])) - p = j; - - res.SwapRows(i, p); - SwapRows(i, p); - res.MultRow(i, 1.0f / Data[i * 4 + i]); - MultRow(i, 1.0f / Data[i * 4 + i]); - for (var j = i + 1; j < 4; j++) - { - res.MultAndAdd(i, j, -Data[j * 4 + i]); - MultAndAdd(i, j, -Data[j * 4 + i]); - } - } - - for (var i = 3; i >= 0; i--) - for (uint j = 0; j < i; j++) - { - res.MultAndAdd((uint) i, j, -Data[j * 4 + i]); - MultAndAdd((uint) i, j, -Data[j * 4 + i]); - } - - return this; - } - - // Construct a translation matrix - public static Mat4F Translation(Vec3 delta) - { - return new Mat4F(1.0f) - { - Data = - { - [3] = delta.X, - [7] = delta.Y, - [11] = delta.Z - } - }; - } - - // Construct a identity matrix - public static Mat4F Identity() - { - return new Mat4F(1.0f); - } - - // Construct a rotation matrix - public static Mat4F Rotation(float degrees, Vec3 vec) - { - vec.Normalize(); - var alpha = degrees * Pi / 180.0f; - var s = (float) System.Math.Sin(alpha); - var c = (float) System.Math.Cos(alpha); - var t = 1.0f - c; - return new Mat4F(0.0f) - { - Data = - { - [0] = t * vec.X * vec.X + c, - [1] = t * vec.X * vec.Y - s * vec.Z, - [2] = t * vec.X * vec.Z + s * vec.Y, - [4] = t * vec.X * vec.Y + s * vec.Z, - [5] = t * vec.Y * vec.Y + c, - [6] = t * vec.Y * vec.Z - s * vec.X, - [8] = t * vec.X * vec.Z - s * vec.Y, - [9] = t * vec.Y * vec.Z + s * vec.X, - [10] = t * vec.Z * vec.Z + c, - [15] = 1.0f - } - }; - } - - // Construct a perspective projection matrix - public static Mat4F Perspective(float fov, float aspect, float zNear, float zFar) - { - var f = 1.0f / System.Math.Tan(fov * Pi / 180.0 / 2.0); - var a = zNear - zFar; - return new Mat4F(0.0f) - { - Data = - { - [0] = (float) (f / aspect), - [5] = (float) f, - [10] = (zFar + zNear) / a, - [11] = 2.0f * zFar * zNear / a, - [14] = -1.0f - } - }; - } - - // Construct an orthogonal projection matrix - public static Mat4F Ortho(float left, float right, float top, float bottom, float zNear, float zFar) - { - var a = right - left; - var b = top - bottom; - var c = zFar - zNear; - return new Mat4F(0.0f) - { - Data = - { - [0] = 2.0f / a, - [3] = -(right + left) / a, - [5] = 2.0f / b, - [7] = -(top + bottom) / b, - [10] = -2.0f / c, - [11] = -(zFar + zNear) / c, - [15] = 1.0f - } - }; - } - - public KeyValuePair, float> Transform(Vec3 vec, float w) - { - var res = new Vec3(Data[0] * vec.X + Data[1] * vec.Y + Data[2] * vec.Z + Data[3] * w, - Data[4] * vec.X + Data[5] * vec.Y + Data[6] * vec.Z + Data[7] * w, - Data[8] * vec.X + Data[9] * vec.Y + Data[10] * vec.Z + Data[11] * w); - var rw = Data[12] * vec.X + Data[13] * vec.Y + Data[14] * vec.Z + Data[15] * w; - return new KeyValuePair, float>(res, rw); - } - } - public struct Mat4D { private const double Pi = 3.1415926535897932f; @@ -330,34 +85,6 @@ public Mat4D(double x) return res; } - // Swap row r1, row r2 - public void SwapRows(uint r1, uint r2) - { - Generic.Swap(ref Data[r1 * 4 + 0], ref Data[r2 * 4 + 0]); - Generic.Swap(ref Data[r1 * 4 + 1], ref Data[r2 * 4 + 1]); - Generic.Swap(ref Data[r1 * 4 + 2], ref Data[r2 * 4 + 2]); - Generic.Swap(ref Data[r1 * 4 + 3], ref Data[r2 * 4 + 3]); - } - - // Row r *= k - public void MultRow(uint r, double k) - { - Data[r * 4 + 0] *= k; - Data[r * 4 + 1] *= k; - Data[r * 4 + 2] *= k; - Data[r * 4 + 3] *= k; - } - - // Row dst += row src * k - public void MultAndAdd(uint src, uint dst, double k) - { - Data[dst * 4 + 0] += Data[src * 4 + 0] * k; - Data[dst * 4 + 1] += Data[src * 4 + 1] * k; - Data[dst * 4 + 2] += Data[src * 4 + 2] * k; - Data[dst * 4 + 3] += Data[src * 4 + 3] * k; - } - - // Get transposed matrix public Mat4D Transposed() { @@ -385,41 +112,8 @@ public Mat4D Transposed() }; } - // Inverse matrix - public Mat4D Inverse(double[] data) - { - Data = data; - var res = Identity(); - for (uint i = 0; i < 4; i++) - { - var p = i; - for (var j = i + 1; j < 4; j++) - if (System.Math.Abs(Data[j * 4 + i]) > System.Math.Abs(Data[p * 4 + i])) - p = j; - - res.SwapRows(i, p); - SwapRows(i, p); - res.MultRow(i, 1.0f / Data[i * 4 + i]); - MultRow(i, 1.0f / Data[i * 4 + i]); - for (var j = i + 1; j < 4; j++) - { - res.MultAndAdd(i, j, -Data[j * 4 + i]); - MultAndAdd(i, j, -Data[j * 4 + i]); - } - } - - for (var i = 3; i >= 0; i--) - for (uint j = 0; j < i; j++) - { - res.MultAndAdd((uint) i, j, -Data[j * 4 + i]); - MultAndAdd((uint) i, j, -Data[j * 4 + i]); - } - - return this; - } - // Construct a translation matrix - public static Mat4D Translation(Vec3 delta) + public static Mat4D Translation(Double3 delta) { return new Mat4D(1.0f) { @@ -439,7 +133,7 @@ public static Mat4D Identity() } // Construct a rotation matrix - public static Mat4D Rotation(double degrees, Vec3 vec) + public static Mat4D Rotation(double degrees, Double3 vec) { vec.Normalize(); var alpha = degrees * Pi / 180.0f; @@ -464,52 +158,13 @@ public static Mat4D Rotation(double degrees, Vec3 vec) }; } - // Construct a perspective projection matrix - public static Mat4D Perspective(double fov, double aspect, double zNear, double zFar) - { - var f = 1.0f / System.Math.Tan(fov * Pi / 180.0 / 2.0); - var a = zNear - zFar; - return new Mat4D(0.0f) - { - Data = - { - [0] = f / aspect, - [5] = f, - [10] = (zFar + zNear) / a, - [11] = 2.0f * zFar * zNear / a, - [14] = -1.0f - } - }; - } - - // Construct an orthogonal projection matrix - public static Mat4D Ortho(double left, double right, double top, double bottom, double zNear, double zFar) - { - var a = right - left; - var b = top - bottom; - var c = zFar - zNear; - return new Mat4D(0.0f) - { - Data = - { - [0] = 2.0f / a, - [3] = -(right + left) / a, - [5] = 2.0f / b, - [7] = -(top + bottom) / b, - [10] = -2.0f / c, - [11] = -(zFar + zNear) / c, - [15] = 1.0f - } - }; - } - - public KeyValuePair, double> Transform(Vec3 vec, double w) + public KeyValuePair Transform(Double3 vec, double w) { - var res = new Vec3(Data[0] * vec.X + Data[1] * vec.Y + Data[2] * vec.Z + Data[3] * w, + var res = new Double3(Data[0] * vec.X + Data[1] * vec.Y + Data[2] * vec.Z + Data[3] * w, Data[4] * vec.X + Data[5] * vec.Y + Data[6] * vec.Z + Data[7] * w, Data[8] * vec.X + Data[9] * vec.Y + Data[10] * vec.Z + Data[11] * w); var rw = Data[12] * vec.X + Data[13] * vec.Y + Data[14] * vec.Z + Data[15] * w; - return new KeyValuePair, double>(res, rw); + return new KeyValuePair(res, rw); } } } \ No newline at end of file diff --git a/Core/Math/Vector.cs b/Core/Math/Vector.cs deleted file mode 100644 index 1652c14..0000000 --- a/Core/Math/Vector.cs +++ /dev/null @@ -1,164 +0,0 @@ -// -// Core: Vector.cs -// NEWorld: A Free Game with Similar Rules to Minecraft. -// Copyright (C) 2015-2018 NEWorld Team -// -// NEWorld is free software: you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published -// by the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// NEWorld is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY -// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General -// Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with NEWorld. If not, see . -// - -using System; -using System.Collections.Generic; - -namespace Core.Math -{ - using static Generic; - - public struct Vec2 : IEquatable> - { - public Vec2(T x, T y) - { - X = x; - Y = y; - } - - public T X, Y; - - public double LengthSqr() - { - return Square(X) + Square(Y); - } - - public double Length() - { - return System.Math.Sqrt(LengthSqr()); - } - - public void Normalize() - { - object length = Cast(Length()); - X = Divide(X, length); - Y = Divide(Y, length); - } - - public static Vec2 operator +(Vec2 lhs, Vec2 rhs) - { - return new Vec2(Add(lhs.X, rhs.X), Add(lhs.Y, rhs.Y)); - } - - public static Vec2 operator -(Vec2 lhs, Vec2 rhs) - { - return new Vec2(Substract(lhs.X, rhs.X), Substract(lhs.Y, rhs.Y)); - } - - public bool Equals(Vec2 other) - { - return EqualityComparer.Default.Equals(X, other.X) && EqualityComparer.Default.Equals(Y, other.Y); - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - return obj is Vec2 && Equals((Vec2) obj); - } - - public override int GetHashCode() - { - unchecked - { - return (EqualityComparer.Default.GetHashCode(X) * 397) ^ EqualityComparer.Default.GetHashCode(Y); - } - } - } - - public struct Vec3 : IEquatable> - { - public Vec3(T x, T y, T z) - { - X = x; - Y = y; - Z = z; - } - - public T X; - public T Y; - public T Z; - - public T LengthSqr() - { - return Square(X) + Square(Y) + Square(Z); - } - - public double Length() - { - return Sqrt(LengthSqr()); - } - - public void Normalize() - { - object length = Cast(Length()); - X = Divide(X, length); - Y = Divide(Y, length); - Z = Divide(Z, length); - } - - - public static Vec3 operator +(Vec3 lhs, Vec3 rhs) - { - return new Vec3(Add(lhs.X, rhs.X), Add(lhs.Y, rhs.Y), Add(lhs.Z, rhs.Z)); - } - - public static Vec3 operator -(Vec3 lhs, Vec3 rhs) - { - return new Vec3(Substract(lhs.X, rhs.X), Substract(lhs.Y, rhs.Y), Substract(lhs.Z, rhs.Z)); - } - - public static Vec3 operator -(Vec3 lhs) - { - return new Vec3(Negate(lhs.X), Negate(lhs.Y), Negate(lhs.Z)); - } - - public static Vec3 operator *(Vec3 lhs, T rhs) - { - return new Vec3(Multiply(lhs.X, rhs), Multiply(lhs.Y, rhs), Multiply(lhs.Z, rhs)); - } - - public static Vec3 operator /(Vec3 lhs, T rhs) - { - return new Vec3(Divide(lhs.X, rhs), Divide(lhs.Y, rhs), Divide(lhs.Z, rhs)); - } - - public bool Equals(Vec3 other) - { - return EqualityComparer.Default.Equals(X, other.X) && EqualityComparer.Default.Equals(Y, other.Y) && - EqualityComparer.Default.Equals(Z, other.Z); - } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - return obj is Vec3 vec3 && Equals(vec3); - } - - public override int GetHashCode() - { - unchecked - { - var hashCode = EqualityComparer.Default.GetHashCode(X); - hashCode = (hashCode * 397) ^ EqualityComparer.Default.GetHashCode(Y); - hashCode = (hashCode * 397) ^ EqualityComparer.Default.GetHashCode(Z); - return hashCode; - } - } - } -} \ No newline at end of file diff --git a/Core/Network/Client.cs b/Core/Network/Client.cs index 23e9e0f..63ec21a 100644 --- a/Core/Network/Client.cs +++ b/Core/Network/Client.cs @@ -40,7 +40,13 @@ public Client(string address, int port) public void Dispose() { - Close(); + ReleaseUnmanagedResources(); + GC.SuppressFinalize(this); + } + + ~Client() + { + ReleaseUnmanagedResources(); } public void RegisterProtocol(Protocol newProtocol) @@ -78,5 +84,10 @@ private static int ProtocolSorter(Protocol x, Protocol y) { return Comparer.Default.Compare(x.Id, y.Id); } + + private void ReleaseUnmanagedResources() + { + Close(); + } } } \ No newline at end of file diff --git a/Core/Network/ConnectionHost.cs b/Core/Network/ConnectionHost.cs index 58ce732..e71ab7b 100644 --- a/Core/Network/ConnectionHost.cs +++ b/Core/Network/ConnectionHost.cs @@ -46,7 +46,13 @@ internal Session(TcpClient io) public void Dispose() { - ios.Close(); + Dispose(true); + GC.SuppressFinalize(this); + } + + ~Session() + { + Dispose(false); } internal Receive WaitMessage() @@ -59,6 +65,23 @@ public Send CreateMessage(uint protocol) return new Send(this, protocol); } + private void ReleaseUnmanagedResources() + { + ios.Close(); + } + + private void Dispose(bool disposing) + { + ReleaseUnmanagedResources(); + if (disposing) + { + conn?.Dispose(); + ios?.Dispose(); + writeBuffer?.Dispose(); + buffer?.Dispose(); + } + } + public sealed class Receive : IDisposable { private Stream ios; @@ -258,8 +281,13 @@ static ConnectionHost() public void Dispose() { - foreach (var hd in _connections) - hd.Close(); + ReleaseUnmanagedResources(); + GC.SuppressFinalize(this); + } + + ~ConnectionHost() + { + ReleaseUnmanagedResources(); } private static void SweepInvalidConnectionsIfNecessary() @@ -290,6 +318,12 @@ public static int CountConnections() return _connectionCounter; } + private void ReleaseUnmanagedResources() + { + foreach (var hd in _connections) + hd.Close(); + } + public sealed class Connection { private readonly Task finalize; @@ -315,7 +349,7 @@ private async Task Start() { Valid = true; Interlocked.Increment(ref _connectionCounter); - while (Valid) + while (Valid && Session.Live) try { var message = Session.WaitMessage(); @@ -323,7 +357,7 @@ private async Task Start() } catch (Exception e) { - if (Session.Live) LogPort.Debug($"Encountering Exception {e}"); + if (Session.Live) {LogPort.Debug($"Encountering Exception {e}"); } } CloseDown(); diff --git a/Core/Network/Protocols.cs b/Core/Network/Protocols.cs index 37f1e5c..e22fd81 100644 --- a/Core/Network/Protocols.cs +++ b/Core/Network/Protocols.cs @@ -22,15 +22,12 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using MsgPack.Serialization; +using MessagePack; namespace Core.Network { public static class Handshake { - private static readonly MessagePackSerializer[]> SerialReply = - MessagePackSerializer.Get[]>(); - internal static async Task[]> Get(Session conn) { var session = Reply.AllocSession(); @@ -40,7 +37,7 @@ internal static async Task[]> Get(Session conn) } var result = await session.Value; - return SerialReply.UnpackSingleObject(result); + return MessagePackSerializer.Deserialize[]>(result); } public class Server : FixedLengthProtocol @@ -59,7 +56,7 @@ public override void HandleRequest(Session.Receive request) var reply = new KeyValuePair[protocols.Count]; foreach (var protocol in protocols) reply[current++] = new KeyValuePair(protocol.Name(), protocol.Id); - Reply.Send(request.Session, session, SerialReply.PackSingleObjectAsBytes(reply)); + Reply.Send(request.Session, session, MessagePackSerializer.SerializeUnsafe(reply)); } public override string Name() diff --git a/Core/Rect.cs b/Core/Rect.cs deleted file mode 100644 index 0163e24..0000000 --- a/Core/Rect.cs +++ /dev/null @@ -1,78 +0,0 @@ -// -// Core: Rect.cs -// NEWorld: A Free Game with Similar Rules to Minecraft. -// Copyright (C) 2015-2018 NEWorld Team -// -// NEWorld is free software: you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published -// by the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// NEWorld is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY -// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General -// Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with NEWorld. If not, see . -// - -using Core.Math; - -namespace Core -{ - using static Generic; - - public struct Rect - { - public Vec2 Min, Max; - - public Rect(Vec2 min, Vec2 max) - { - Min = min; - Max = max; - } - - public Rect(T minX, T minY, T maxX, T maxY) - { - Min = new Vec2(minX, minY); - Max = new Vec2(maxX, maxY); - } - - public Vec2 Delta => Max - Min; - - public Rect(Vec2 start, params Vec2[] args) - { - object minX = start.X, minY = start.Y; - object maxX = start.X, maxY = start.Y; - foreach (var point in args) - { - object boxX = point.X, boxY = point.Y; - MinEqual(minX, boxX); - MinEqual(minY, boxY); - MaxEqual(maxX, boxX); - MaxEqual(maxY, boxY); - } - - Min = new Vec2((T) minX, (T) minY); - Max = new Vec2((T) maxX, (T) maxY); - } - - public Rect(params T[] args) - { - object minX = args[0], minY = args[1]; - object maxX = args[0], maxY = args[1]; - for (var i = 2; i < args.Length; ++i) - { - object boxX = args[i++], boxY = args[i]; - MinEqual(minX, boxX); - MinEqual(minY, boxY); - MaxEqual(maxX, boxX); - MaxEqual(maxY, boxY); - } - - Min = new Vec2((T) minX, (T) minY); - Max = new Vec2((T) maxX, (T) maxY); - } - } -} \ No newline at end of file diff --git a/Core/Services.cs b/Core/Services.cs index 216b586..22af4d7 100644 --- a/Core/Services.cs +++ b/Core/Services.cs @@ -100,7 +100,7 @@ private static bool CheckIfAssemblyProcessed(Assembly assembly) { lock (_processed) { - return _processed?.Contains(assembly.GetName()) ?? false; + return (bool) _processed?.Contains(assembly.GetName()); } } diff --git a/Core/Utilities/EndianCheck.cs b/Core/Utilities/EndianCheck.cs deleted file mode 100644 index 32b587f..0000000 --- a/Core/Utilities/EndianCheck.cs +++ /dev/null @@ -1,43 +0,0 @@ -// -// Core: EndianCheck.cs -// NEWorld: A Free Game with Similar Rules to Minecraft. -// Copyright (C) 2015-2018 NEWorld Team -// -// NEWorld is free software: you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published -// by the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// NEWorld is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY -// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General -// Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with NEWorld. If not, see . -// - -namespace Core.Utilities -{ - public static class EndianCheck - { - private static bool _isBigEndian; - - private static bool _isEndianChecked; - - public static bool BigEndian => IsBigEndian(); - public static bool LittleEndian => !IsBigEndian(); - - private static bool IsBigEndian() - { - if (!_isEndianChecked) - { - _isEndianChecked = true; - const int nCheck = 0x01aa; - _isBigEndian = (nCheck & 0xff) == 0x01; - } - - return _isBigEndian; - } - } -} \ No newline at end of file diff --git a/Core/Utilities/Singleton.cs b/Core/Utilities/Singleton.cs index c88fe5e..5053fd3 100644 --- a/Core/Utilities/Singleton.cs +++ b/Core/Utilities/Singleton.cs @@ -128,29 +128,16 @@ public static T Instance { get { - if (Test()) return _instance; + if (_instance != null) return _instance; lock (Lock) { - if (!Test()) _instance = ConstructInstance(); + if (_instance == null) _instance = ConstructInstance(); } return _instance; } } - private static readonly bool StrictDisposable = typeof(T).IsSubclassOf(typeof(StrictDispose)); - - private static bool Test() - { - if (_instance == null) return false; - return !StrictDisposable || Valid(_instance); - } - - private static bool Valid(dynamic obj) - { - return obj.Valid(); - } - private static T ConstructInstance() { ConstructorInfo constructor; diff --git a/Core/Utilities/StrictDispose.cs b/Core/Utilities/StrictDispose.cs deleted file mode 100644 index 5f0ac3e..0000000 --- a/Core/Utilities/StrictDispose.cs +++ /dev/null @@ -1,84 +0,0 @@ -// -// Core: StrictDispose.cs -// NEWorld: A Free Game with Similar Rules to Minecraft. -// Copyright (C) 2015-2018 NEWorld Team -// -// NEWorld is free software: you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published -// by the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// NEWorld is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY -// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General -// Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with NEWorld. If not, see . -// - -using System; - -namespace Core.Utilities -{ - public class StrictDispose : IDisposable - { - private StrictDispose first, sibling; - - private bool released; - - public void Dispose() - { - if (released) - return; - TravelRelease(); - Release(); - released = true; - } - - ~StrictDispose() - { - Dispose(); - } - - protected T Inject(T target) where T : StrictDispose - { - if (first != null) - target.sibling = first; - first = target; - return target; - } - - protected void Reject(T target, bool disposeNow = true) where T : StrictDispose - { - if (target == first) - { - first = target.sibling; - return; - } - - var current = first; - while (current.sibling != target) - current = current.sibling; - current.sibling = target.sibling; - - if (disposeNow) - target.Dispose(); - } - - protected virtual void Release() - { - } - - private void TravelRelease() - { - for (var current = first; current != null; current = current.sibling) - current.Dispose(); - } - - public bool Valid() - { - return !released; - } - } -} \ No newline at end of file diff --git a/Core/packages.config b/Core/packages.config deleted file mode 100644 index 712ef9f..0000000 --- a/Core/packages.config +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/Game/ChunkService.cs b/Game/ChunkService.cs index e02e050..61d16f0 100644 --- a/Game/ChunkService.cs +++ b/Game/ChunkService.cs @@ -17,6 +17,7 @@ // along with NEWorld. If not, see . // +using Core; using Game.World; namespace Game @@ -41,22 +42,22 @@ protected ChunkService(bool isAuthority) { IsAuthority = isAuthority; Worlds = new WorldManager(); - TaskDispatcher = Core.Services.Get("Game.TaskDispatcher"); + TaskDispatcher = Services.Get("Game.TaskDispatcher"); } private ChunkService() : this(true) { } - public void EnableDispatcher() - { - TaskDispatcher.Start(this); - } - public TaskDispatcher TaskDispatcher { get; } public WorldManager Worlds { get; } public bool IsAuthority { set; get; } + + public void EnableDispatcher() + { + TaskDispatcher.Start(this); + } } } \ No newline at end of file diff --git a/Game/Game.csproj b/Game/Game.csproj index 1dccb82..7955c02 100644 --- a/Game/Game.csproj +++ b/Game/Game.csproj @@ -12,19 +12,6 @@ true - - - - - - - - - - - - - diff --git a/Game/Network/Protocols.cs b/Game/Network/Protocols.cs index 88955f6..b32a553 100644 --- a/Game/Network/Protocols.cs +++ b/Game/Network/Protocols.cs @@ -23,24 +23,31 @@ using Core.Network; using Core.Utilities; using Game.World; -using MsgPack.Serialization; +using MessagePack; using Xenko.Core.Mathematics; namespace Game.Network { public static class GetChunk { + private static readonly int Size = MessagePackSerializer.SerializeUnsafe(new int[4]).Count; + public class Server : FixedLengthProtocol { + private static readonly ThreadLocal LocalMemCache = new ThreadLocal(); + public Server() : base(Size) { } - public override string Name() => "GetChunk"; + public override string Name() + { + return "GetChunk"; + } public override void HandleRequest(Session.Receive stream) { - var request = From.UnpackSingleObject(stream.Raw); + var request = MessagePackSerializer.Deserialize(stream.Raw); var chunkData = Get((uint) request[0], new Int3(request[1], request[2], request[3])); using (var message = stream.Session.CreateMessage(Id)) { @@ -49,8 +56,6 @@ public override void HandleRequest(Session.Receive stream) } } - private static readonly ThreadLocal LocalMemCache = new ThreadLocal(); - private static byte[] Get(uint worldId, Int3 position) { // TODO: empty chunk optimization @@ -82,24 +87,27 @@ private static byte[] Get(uint worldId, Int3 position) public class Client : FixedLengthProtocol { - public override string Name() => "GetChunk"; - public Client() : base(32768 * 4 + Size) { } + public override string Name() + { + return "GetChunk"; + } + public override void HandleRequest(Session.Receive request) { var data = request.Raw; - var req = From.UnpackSingleObject(data); + var req = MessagePackSerializer.Deserialize(data); var srv = Singleton.Instance; var chk = new Chunk(new Int3(req[1], req[2], req[3]), srv.Worlds.Get((uint) req[0])); for (var i = Size; i < 32768 * 4 + Size; i += 4) { ref var block = ref chk.Blocks[(i - Size) >> 2]; - block.Id = (ushort) (data[i] << 4 | data[i + 1] >> 4); + block.Id = (ushort) ((data[i] << 4) | (data[i + 1] >> 4)); block.Brightness = (byte) (data[i + 1] | 0xF); - block.Data = (uint) (data[i + 2] << 8 | data[i + 3]); + block.Data = (uint) ((data[i + 2] << 8) | data[i + 3]); } srv.TaskDispatcher.Add(new World.World.ResetChunkTask(chk)); @@ -110,13 +118,10 @@ public void Call(uint worldId, Int3 position) var data = new[] {(int) worldId, position.X, position.Y, position.Z}; using (var message = Network.Client.CreateMessage(Id)) { - message.Write(From.PackSingleObjectAsBytes(data)); + message.Write(MessagePackSerializer.SerializeUnsafe(data)); } } } - - private static readonly MessagePackSerializer From = MessagePackSerializer.Get(); - private static readonly int Size = From.PackSingleObject(new int[4]).Length; } public static class GetAvailableWorldId @@ -130,15 +135,21 @@ public Server() : base(4) public override void HandleRequest(Session.Receive request) { var session = request.ReadU32(); - Reply.Send(request.Session, session, SerialReply.PackSingleObjectAsBytes(new uint[] {0})); + Reply.Send(request.Session, session, MessagePackSerializer.SerializeUnsafe(new uint[] {0})); } - public override string Name() => "GetAvailableWorldId"; + public override string Name() + { + return "GetAvailableWorldId"; + } } public class Client : StubProtocol { - public override string Name() => "GetAvailableWorldId"; + public override string Name() + { + return "GetAvailableWorldId"; + } public async Task Call() { @@ -147,12 +158,11 @@ public async Task Call() { message.Write(session.Key); } + var result = await session.Value; - return SerialReply.UnpackSingleObject(result); + return MessagePackSerializer.Deserialize(result); } } - - private static readonly MessagePackSerializer SerialReply = MessagePackSerializer.Get(); } public static class GetWorldInfo @@ -168,15 +178,21 @@ public override void HandleRequest(Session.Receive stream) var request = stream.ReadU32(); var world = Singleton.Instance.Worlds.Get(stream.ReadU32()); var ret = new Dictionary {{"name", world.Name}}; - Reply.Send(stream.Session, request, SerialReply.PackSingleObjectAsBytes(ret)); + Reply.Send(stream.Session, request, MessagePackSerializer.SerializeUnsafe(ret)); } - public override string Name() => "GetWorldInfo"; + public override string Name() + { + return "GetWorldInfo"; + } } public class Client : StubProtocol { - public override string Name() => "GetWorldInfo"; + public override string Name() + { + return "GetWorldInfo"; + } public async Task> Call(uint wid) { @@ -186,12 +202,10 @@ public async Task> Call(uint wid) message.Write(session.Key); message.Write(wid); } + var result = await session.Value; - return SerialReply.UnpackSingleObject(result); - } + return MessagePackSerializer.Deserialize>(result); + } } - - private static readonly MessagePackSerializer> SerialReply = - MessagePackSerializer.Get>(); } } \ No newline at end of file diff --git a/Game/Network/Server.cs b/Game/Network/Server.cs index 2d4d9eb..e1c4533 100644 --- a/Game/Network/Server.cs +++ b/Game/Network/Server.cs @@ -27,6 +27,14 @@ namespace Game.Network [DeclareService("Game.Server")] public class Server : IDisposable { + private Core.Network.Server server; + + private Task wait; + + ~Server() + { + Dispose(false); + } public void Enable(int port) { server = new Core.Network.Server(port); @@ -41,7 +49,10 @@ public void Run() wait = server.RunAsync(); } - public int CountConnections() => server.CountConnections(); + public int CountConnections() + { + return server.CountConnections(); + } public void Stop() { @@ -49,13 +60,24 @@ public void Stop() wait.Wait(); } - private Task wait; - private Core.Network.Server server; + private void ReleaseUnmanagedResources() + { + Stop(); + } + + private void Dispose(bool disposing) + { + ReleaseUnmanagedResources(); + if (disposing) + { + wait?.Dispose(); + } + } public void Dispose() { - Stop(); - wait?.Dispose(); + Dispose(true); + GC.SuppressFinalize(this); } } } \ No newline at end of file diff --git a/Game/TaskDispatcher.cs b/Game/TaskDispatcher.cs index 0ea4b0b..c46c3c0 100644 --- a/Game/TaskDispatcher.cs +++ b/Game/TaskDispatcher.cs @@ -61,6 +61,8 @@ public interface IRenderTask [DeclareService("Game.TaskDispatcher")] public class TaskDispatcher : IDisposable { + private readonly Barrier barrier; + // TODO: replace it with lock-free structure. private readonly object mutex; private readonly List regularReadOnlyTasks; @@ -68,11 +70,10 @@ public class TaskDispatcher : IDisposable private readonly List threads; private ChunkService chunkService; + private RateController meter = new RateController(30); private List readOnlyTasks, nextReadOnlyTasks; private List readWriteTasks, nextReadWriteTasks; private List renderTasks, nextRenderTasks; - private readonly Barrier barrier; - private RateController meter = new RateController(30); private bool shouldExit; // Automatic Creation. We reserve one virtual processor for main thread @@ -209,12 +210,13 @@ private void Worker(int threadId) { ProcessReadonlyTasks(threadId); // The last finished thread is responsible to do writing jobs - if (barrier.ParticipantsRemaining == 0) + if (barrier.ParticipantsRemaining == 1) { QueueSwap(); ProcessReadWriteTasks(); meter.Yield(); // Rate Control } + TimeUsed[threadId] = meter.GetDeltaTimeMs(); barrier.SignalAndWait(); } @@ -222,20 +224,21 @@ private void Worker(int threadId) private void ProcessReadonlyTasks(int i) { - for (;i < regularReadOnlyTasks.Count; i += threads.Count) regularReadOnlyTasks[i].Task(chunkService); - for (i -= regularReadOnlyTasks.Count; i < readOnlyTasks.Count; i += threads.Count) readOnlyTasks[i].Task(chunkService); + for (; i < regularReadOnlyTasks.Count; i += threads.Count) regularReadOnlyTasks[i].Task(chunkService); + for (i -= regularReadOnlyTasks.Count; i < readOnlyTasks.Count; i += threads.Count) + readOnlyTasks[i].Task(chunkService); } private void ProcessReadWriteTasks() { foreach (var task in regularReadWriteTasks) task.Task(chunkService); foreach (var task in readWriteTasks) task.Task(chunkService); + readWriteTasks.Clear(); } private void QueueSwap() { readOnlyTasks.Clear(); - readWriteTasks.Clear(); Generic.Swap(ref readOnlyTasks, ref nextReadOnlyTasks); Generic.Swap(ref readWriteTasks, ref nextReadWriteTasks); } diff --git a/Game/Terrain/Block.cs b/Game/Terrain/Block.cs index 5dcb783..881ad49 100644 --- a/Game/Terrain/Block.cs +++ b/Game/Terrain/Block.cs @@ -19,6 +19,7 @@ using System.Collections.Generic; using Game.World; +using Xenko.Core.Annotations; using Xenko.Core.Mathematics; namespace Game.Terrain @@ -37,6 +38,7 @@ public interface IBlockRenderer public interface IVertexBuilder { + //void Rect(Int3 position, int face, Int2 tex, int rotation) void AddPrimitive(int verts, params float[] data); } @@ -48,6 +50,8 @@ public interface IBlockTextures public class DefaultBlockRenderer : IBlockRenderer { + private readonly BlockTexCoord[] tex; + public DefaultBlockRenderer(uint[] data) { tex = new BlockTexCoord[6]; @@ -59,7 +63,9 @@ public unsafe void FlushTexture(IBlockTextures textures) { for (var i = 0; i < 6; ++i) fixed (float* tex = this.tex[0].D) + { textures.GetTexturePos(tex, this.tex[i].Pos); + } } public unsafe void Render(IVertexBuilder target, Chunk chunk, Int3 pos) @@ -87,75 +93,90 @@ public unsafe void Render(IVertexBuilder target, Chunk chunk, Int3 pos) ? chunk.World.GetBlock(new Int3(worldpos.X, worldpos.Y, worldpos.Z - 1)) : chunk[new Int3(pos.X, pos.Y, pos.Z - 1)] }; - + // Data: float2 tex; float lightcoeff; float3 normal, // Right if (AdjacentTest(curr, neighbors[0])) fixed (float* tex = this.tex[0].D) + { target.AddPrimitive(4, - tex[0], tex[1], 0.5f, pos.X + 1.0f, pos.Y + 1.0f, pos.Z + 1.0f, - tex[0], tex[3], 0.5f, pos.X + 1.0f, pos.Y + 0.0f, pos.Z + 1.0f, - tex[2], tex[3], 0.5f, pos.X + 1.0f, pos.Y + 0.0f, pos.Z + 0.0f, - tex[2], tex[1], 0.5f, pos.X + 1.0f, pos.Y + 1.0f, pos.Z + 0.0f + tex[0], tex[1], pos.X + 1.0f, pos.Y + 1.0f, pos.Z + 1.0f, + tex[0], tex[3], pos.X + 1.0f, pos.Y + 0.0f, pos.Z + 1.0f, + tex[2], tex[3], pos.X + 1.0f, pos.Y + 0.0f, pos.Z + 0.0f, + tex[2], tex[1], pos.X + 1.0f, pos.Y + 1.0f, pos.Z + 0.0f ); + } // Left if (AdjacentTest(curr, neighbors[1])) fixed (float* tex = this.tex[1].D) + { target.AddPrimitive(4, - tex[0], tex[1], 0.5f, pos.X + 0.0f, pos.Y + 1.0f, pos.Z + 0.0f, - tex[0], tex[3], 0.5f, pos.X + 0.0f, pos.Y + 0.0f, pos.Z + 0.0f, - tex[2], tex[3], 0.5f, pos.X + 0.0f, pos.Y + 0.0f, pos.Z + 1.0f, - tex[2], tex[1], 0.5f, pos.X + 0.0f, pos.Y + 1.0f, pos.Z + 1.0f + tex[0], tex[1], pos.X + 0.0f, pos.Y + 1.0f, pos.Z + 0.0f, + tex[0], tex[3], pos.X + 0.0f, pos.Y + 0.0f, pos.Z + 0.0f, + tex[2], tex[3], pos.X + 0.0f, pos.Y + 0.0f, pos.Z + 1.0f, + tex[2], tex[1], pos.X + 0.0f, pos.Y + 1.0f, pos.Z + 1.0f ); + } // Top if (AdjacentTest(curr, neighbors[2])) fixed (float* tex = this.tex[2].D) + { target.AddPrimitive(4, - tex[0], tex[1], 1.0f, pos.X + 0.0f, pos.Y + 1.0f, pos.Z + 0.0f, - tex[0], tex[3], 1.0f, pos.X + 0.0f, pos.Y + 1.0f, pos.Z + 1.0f, - tex[2], tex[3], 1.0f, pos.X + 1.0f, pos.Y + 1.0f, pos.Z + 1.0f, - tex[2], tex[1], 1.0f, pos.X + 1.0f, pos.Y + 1.0f, pos.Z + 0.0f + tex[0], tex[1], pos.X + 0.0f, pos.Y + 1.0f, pos.Z + 0.0f, + tex[0], tex[3], pos.X + 0.0f, pos.Y + 1.0f, pos.Z + 1.0f, + tex[2], tex[3], pos.X + 1.0f, pos.Y + 1.0f, pos.Z + 1.0f, + tex[2], tex[1], pos.X + 1.0f, pos.Y + 1.0f, pos.Z + 0.0f ); + } // Bottom if (AdjacentTest(curr, neighbors[3])) fixed (float* tex = this.tex[3].D) + { target.AddPrimitive(4, - tex[0], tex[1], 1.0f, pos.X + 0.0f, pos.Y + 0.0f, pos.Z + 1.0f, - tex[0], tex[3], 1.0f, pos.X + 0.0f, pos.Y + 0.0f, pos.Z + 0.0f, - tex[2], tex[3], 1.0f, pos.X + 1.0f, pos.Y + 0.0f, pos.Z + 0.0f, - tex[2], tex[1], 1.0f, pos.X + 1.0f, pos.Y + 0.0f, pos.Z + 1.0f + tex[0], tex[1],pos.X + 0.0f, pos.Y + 0.0f, pos.Z + 1.0f, + tex[0], tex[3], pos.X + 0.0f, pos.Y + 0.0f, pos.Z + 0.0f, + tex[2], tex[3], pos.X + 1.0f, pos.Y + 0.0f, pos.Z + 0.0f, + tex[2], tex[1], pos.X + 1.0f, pos.Y + 0.0f, pos.Z + 1.0f ); + } // Front if (AdjacentTest(curr, neighbors[4])) fixed (float* tex = this.tex[4].D) + { target.AddPrimitive(4, - tex[0], tex[1], 0.7f, pos.X + 0.0f, pos.Y + 1.0f, pos.Z + 1.0f, - tex[0], tex[3], 0.7f, pos.X + 0.0f, pos.Y + 0.0f, pos.Z + 1.0f, - tex[2], tex[3], 0.7f, pos.X + 1.0f, pos.Y + 0.0f, pos.Z + 1.0f, - tex[2], tex[1], 0.7f, pos.X + 1.0f, pos.Y + 1.0f, pos.Z + 1.0f + tex[0], tex[1], pos.X + 0.0f, pos.Y + 1.0f, pos.Z + 1.0f, + tex[0], tex[3], pos.X + 0.0f, pos.Y + 0.0f, pos.Z + 1.0f, + tex[2], tex[3], pos.X + 1.0f, pos.Y + 0.0f, pos.Z + 1.0f, + tex[2], tex[1], pos.X + 1.0f, pos.Y + 1.0f, pos.Z + 1.0f ); + } // Back if (AdjacentTest(curr, neighbors[5])) fixed (float* tex = this.tex[5].D) + { target.AddPrimitive(4, - tex[0], tex[1], 0.7f, pos.X + 1.0f, pos.Y + 1.0f, pos.Z + 0.0f, - tex[0], tex[3], 0.7f, pos.X + 1.0f, pos.Y + 0.0f, pos.Z + 0.0f, - tex[2], tex[3], 0.7f, pos.X + 0.0f, pos.Y + 0.0f, pos.Z + 0.0f, - tex[2], tex[1], 0.7f, pos.X + 0.0f, pos.Y + 1.0f, pos.Z + 0.0f + tex[0], tex[1], pos.X + 1.0f, pos.Y + 1.0f, pos.Z + 0.0f, + tex[0], tex[3], pos.X + 1.0f, pos.Y + 0.0f, pos.Z + 0.0f, + tex[2], tex[3], pos.X + 0.0f, pos.Y + 0.0f, pos.Z + 0.0f, + tex[2], tex[1], pos.X + 0.0f, pos.Y + 1.0f, pos.Z + 0.0f ); + } } - static bool AdjacentTest(BlockData a, BlockData b) => a.Id != 0 && !Blocks.Index[b.Id].IsOpaque && a.Id != b.Id; - - private readonly BlockTexCoord[] tex; + private static bool AdjacentTest(BlockData a, BlockData b) + { + return a.Id != 0 && !Blocks.Index[b.Id].IsOpaque && a.Id != b.Id; + } } public static class BlockRenderers { + private static readonly List Renderers = new List(); + public static void Render(IVertexBuilder target, int id, Chunk chunk, Int3 pos) { if (Renderers.Count > 0 && Renderers[id] != null) @@ -171,12 +192,7 @@ public static void Add(int pos, IBlockRenderer blockRenderer) public static void FlushTextures(IBlockTextures textures) { - foreach (var x in Renderers) - { - x?.FlushTexture(textures); - } + foreach (var x in Renderers) x?.FlushTexture(textures); } - - private static readonly List Renderers = new List(); } } \ No newline at end of file diff --git a/Game/Terrain/Matrix.cs b/Game/Terrain/Matrix.cs deleted file mode 100644 index 43447ab..0000000 --- a/Game/Terrain/Matrix.cs +++ /dev/null @@ -1,63 +0,0 @@ -// -// Game: Matrix.cs -// NEWorld: A Free Game with Similar Rules to Minecraft. -// Copyright (C) 2015-2018 NEWorld Team -// -// NEWorld is free software: you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published -// by the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// NEWorld is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY -// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General -// Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with NEWorld. If not, see . -// - -using Core.Math; - -namespace Game.Terrain -{ - public static class Matrix - { - public static void RestoreModel() => _model = Mat4F.Identity(); - public static void RestoreView() => _view = Mat4F.Identity(); - public static void RestoreProjection() => _projection = Mat4F.Identity(); - - private static Mat4F _model = Mat4F.Identity(), _view = Mat4F.Identity(), _projection = Mat4F.Identity(); - - public static void ApplyPerspective(float fov, float aspect, float zNear, float zFar) => - _projection *= Mat4F.Perspective(fov, aspect, zNear, zFar); - - public static void ViewRotate(float degree, Vec3 axis) => _view *= Mat4F.Rotation(degree, axis); - - public static void ViewTranslate(Vec3 diff) => _view *= Mat4F.Translation(diff); - - public static void ModelRotate(float degree, Vec3 axis) => _model *= Mat4F.Rotation(degree, axis); - - public static void ModelTranslate(Vec3 diff) => _model *= Mat4F.Translation(diff); - - public static void ViewRotate(float degree, Vec3 axis) => _view *= Mat4F.Rotation(degree, Conv(axis)); - - public static void ViewTranslate(Vec3 diff) => _view *= Mat4F.Translation(Conv(diff)); - - public static void ModelRotate(float degree, Vec3 axis) => _model *= Mat4F.Rotation(degree, Conv(axis)); - - public static void ViewRotate(float degree, Vec3 axis) => _view *= Mat4F.Rotation(degree, Conv(axis)); - - public static void ViewTranslate(Vec3 diff) => _view *= Mat4F.Translation(Conv(diff)); - - public static void ModelRotate(float degree, Vec3 axis) => _model *= Mat4F.Rotation(degree, Conv(axis)); - - public static void ModelSetTranslate(Vec3 diff) => _model = Mat4F.Translation(Conv(diff)); - - public static Mat4F Get() => _model * _view * _projection; - - private static Vec3 Conv(Vec3 v) => new Vec3((float) v.X, (float) v.Y, (float) v.Z); - - private static Vec3 Conv(Vec3 v) => new Vec3(v.X, v.Y, v.Z); - } -} \ No newline at end of file diff --git a/Game/Utilities/Aabb.cs b/Game/Utilities/Aabb.cs index 5ae4008..e7f40d9 100644 --- a/Game/Utilities/Aabb.cs +++ b/Game/Utilities/Aabb.cs @@ -18,16 +18,16 @@ // using System; -using Core.Math; +using Xenko.Core.Mathematics; namespace Game.Utilities { public struct Aabb { // Min bound, Max bound - public Vec3 Min, Max; + public Double3 Min, Max; - public Aabb(Vec3 min, Vec3 max) + public Aabb(Double3 min, Double3 max) { Min = min; Max = max; @@ -106,9 +106,9 @@ public double MaxMoveOnZclip(Aabb box, double orgmove) } // Get expanded Aabb - public Aabb Expand(Vec3 arg) + public Aabb Expand(Double3 arg) { - Aabb res = this; + var res = this; if (arg.X > 0.0) res.Max.X += arg.X; @@ -129,7 +129,7 @@ public Aabb Expand(Vec3 arg) } // Move Aabb - public void Move(Vec3 arg) + public void Move(Double3 arg) { Min += arg; Max += arg; diff --git a/Game/Utilities/OrderedList.cs b/Game/Utilities/OrderedList.cs index 468c220..cde9d3c 100644 --- a/Game/Utilities/OrderedList.cs +++ b/Game/Utilities/OrderedList.cs @@ -25,6 +25,9 @@ namespace Game.Utilities { public class OrderedListIntBase : IEnumerable> { + public readonly KeyValuePair[] Data; + public readonly int FixedSize; + protected OrderedListIntBase(int fixedSize) { Size = 0; @@ -32,6 +35,18 @@ protected OrderedListIntBase(int fixedSize) Data = new KeyValuePair[fixedSize]; } + public int Size { get; private set; } + + public IEnumerator> GetEnumerator() + { + return new OrderedListIntBaseEnum(this); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + protected void InsertBase(int first, int key, TD data) { if (first > Size || first >= FixedSize) return; @@ -41,24 +56,32 @@ protected void InsertBase(int first, int key, TD data) Data[first] = new KeyValuePair(key, data); } - public void Clear() => Size = 0; - - public int Size { get; private set; } - public readonly int FixedSize; - public readonly KeyValuePair[] Data; - - public IEnumerator> GetEnumerator() => new OrderedListIntBaseEnum(this); - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public void Clear() + { + Size = 0; + } } public class OrderedListIntBaseEnum : IEnumerator> { - public OrderedListIntBaseEnum(OrderedListIntBase host) => _base = host; + private readonly OrderedListIntBase _base; - public bool MoveNext() => ++position < _base.Size; + private int position = -1; - public void Reset() => position = -1; + public OrderedListIntBaseEnum(OrderedListIntBase host) + { + _base = host; + } + + public bool MoveNext() + { + return ++position < _base.Size; + } + + public void Reset() + { + position = -1; + } public KeyValuePair Current => _base.Data[position]; @@ -67,10 +90,6 @@ public class OrderedListIntBaseEnum : IEnumerator> public void Dispose() { } - - private int position = -1; - - private readonly OrderedListIntBase _base; } public class OrderedListIntLess : OrderedListIntBase diff --git a/Game/World/Blocks.cs b/Game/World/Blocks.cs index 0e64d6b..e35b64c 100644 --- a/Game/World/Blocks.cs +++ b/Game/World/Blocks.cs @@ -57,6 +57,9 @@ public static class Blocks { private static readonly BlockType Air = new BlockType("Air", false, false, false, 0); + public static readonly BlockType[] Index; + private static ushort _count; + static Blocks() { Index = new BlockType[1 << 12]; @@ -68,8 +71,5 @@ public static ushort Register(BlockType block) Index[_count] = block; return _count++; } - - public static readonly BlockType[] Index; - private static ushort _count; } } \ No newline at end of file diff --git a/Game/World/Chunk.cs b/Game/World/Chunk.cs index 500201b..0317463 100644 --- a/Game/World/Chunk.cs +++ b/Game/World/Chunk.cs @@ -28,25 +28,16 @@ public class Chunk { public delegate void Generator(Int3 chunkPos, BlockData[] chunkData, int daylightBrightness); - // Chunk size - private static bool _chunkGeneratorLoaded; - private static Generator _chunkGen; public const int BlocksSize = 0b1000000000000000; public const int SizeLog2 = 5; public const int Size = 0b100000; - public static void SetGenerator(Generator gen) - { - if (!_chunkGeneratorLoaded) - { - _chunkGen = gen; - _chunkGeneratorLoaded = true; - } - else - { - throw new Exception("Chunk Generator Alreadly Loaded"); - } - } + // Chunk size + private static bool _chunkGeneratorLoaded; + private static Generator _chunkGen; + + // For Garbage Collection + private long mLastRequestTime; public Chunk(Int3 position, World world, bool build = true) { @@ -76,6 +67,19 @@ public BlockData this[Int3 pos] } } + public static void SetGenerator(Generator gen) + { + if (!_chunkGeneratorLoaded) + { + _chunkGen = gen; + _chunkGeneratorLoaded = true; + } + else + { + throw new Exception("Chunk Generator Alreadly Loaded"); + } + } + // Build chunk public void Build(int daylightBrightness) { @@ -84,35 +88,56 @@ public void Build(int daylightBrightness) } // Reference Counting - public void MarkRequest() => mLastRequestTime = DateTime.Now.Ticks; - - public bool CheckReleaseable() => - DateTime.Now - new DateTime(Interlocked.Read(ref mLastRequestTime)) > TimeSpan.FromSeconds(10); + public void MarkRequest() + { + mLastRequestTime = DateTime.Now.Ticks; + } - // For Garbage Collection - private long mLastRequestTime; + public bool CheckReleaseable() + { + return DateTime.Now - new DateTime(Interlocked.Read(ref mLastRequestTime)) > TimeSpan.FromSeconds(10); + } } - + public class ChunkManager : Dictionary { - public bool IsLoaded(Int3 chunkPos) => ContainsKey(chunkPos); + public bool IsLoaded(Int3 chunkPos) + { + return ContainsKey(chunkPos); + } // Convert world position to chunk coordinate (one axis) - public static int GetAxisPos(int pos) => pos >> Chunk.SizeLog2; + public static int GetAxisPos(int pos) + { + return pos >> Chunk.SizeLog2; + } // Convert world position to chunk coordinate (all axes) - public static Int3 GetPos(Int3 pos) => - new Int3(GetAxisPos(pos.X), GetAxisPos(pos.Y), GetAxisPos(pos.Z)); + public static Int3 GetPos(Int3 pos) + { + return new Int3(GetAxisPos(pos.X), GetAxisPos(pos.Y), GetAxisPos(pos.Z)); + } // Convert world position to block coordinate in chunk (one axis) - public static int GetBlockAxisPos(int pos) => pos & (Chunk.Size - 1); + public static int GetBlockAxisPos(int pos) + { + return pos & (Chunk.Size - 1); + } // Convert world position to block coordinate in chunk (all axes) - public static Int3 GetBlockPos(Int3 pos) => - new Int3(GetBlockAxisPos(pos.X), GetBlockAxisPos(pos.Y), GetBlockAxisPos(pos.Z)); + public static Int3 GetBlockPos(Int3 pos) + { + return new Int3(GetBlockAxisPos(pos.X), GetBlockAxisPos(pos.Y), GetBlockAxisPos(pos.Z)); + } - public BlockData GetBlock(Int3 pos) => this[GetPos(pos)][GetBlockPos(pos)]; + public BlockData GetBlock(Int3 pos) + { + return this[GetPos(pos)][GetBlockPos(pos)]; + } - public void SetBlock(Int3 pos, BlockData block) => this[GetPos(pos)][GetBlockPos(pos)] = block; + public void SetBlock(Int3 pos, BlockData block) + { + this[GetPos(pos)][GetBlockPos(pos)] = block; + } } } \ No newline at end of file diff --git a/Game/World/Object.cs b/Game/World/Object.cs index 6dd4840..7c16eca 100644 --- a/Game/World/Object.cs +++ b/Game/World/Object.cs @@ -17,20 +17,26 @@ // along with NEWorld. If not, see . // -using Core.Math; using Game.Utilities; +using Xenko.Core.Mathematics; namespace Game.World { public abstract class Object { + public Aabb Hitbox; + + public Double3 Position; + public Double3 Rotation; + public Double3 Scale; + protected Object(uint worldId) { WorldId = worldId; - Scale = new Vec3(1.0, 1.0, 1.0); + Scale = new Double3(1.0, 1.0, 1.0); } - protected Object(uint worldId, Vec3 position, Vec3 rotation, Vec3 scale, Aabb hitbox) + protected Object(uint worldId, Double3 position, Double3 rotation, Double3 scale, Aabb hitbox) { WorldId = worldId; Position = position; @@ -39,15 +45,14 @@ protected Object(uint worldId, Vec3 position, Vec3 rotation, Vec Hitbox = hitbox; } - public void MoveHitbox(Vec3 delta) => Hitbox.Move(delta); + public uint WorldId { get; } + + public void MoveHitbox(Double3 delta) + { + Hitbox.Move(delta); + } public abstract void Render(); public abstract void Update(World world); - - public Vec3 Position; - public Vec3 Rotation; - public Vec3 Scale; - public Aabb Hitbox; - public uint WorldId { get; } } } \ No newline at end of file diff --git a/Game/World/Player.cs b/Game/World/Player.cs index f8b7843..2470a41 100644 --- a/Game/World/Player.cs +++ b/Game/World/Player.cs @@ -19,78 +19,76 @@ using Core.Math; using Core.Utilities; +using Xenko.Core.Mathematics; namespace Game.World { public class Player : PlayerObject { - private class PlayerUpdateTask : IReadOnlyTask - { - public PlayerUpdateTask(Player player, uint worldId) - { - this.player = player; - this.worldId = worldId; - } + private static readonly bool RotationInteria = false; + private Double3 positionDelta; - public void Task(ChunkService srv) => player.Update(srv.Worlds.Get(worldId)); + private Double3 speed, rotationSpeed; - private readonly Player player; - private readonly uint worldId; - } - - public Player(uint worldId) : base(worldId) => + public Player(uint worldId) : base(worldId) + { Singleton.Instance.TaskDispatcher.AddRegular(new PlayerUpdateTask(this, WorldId)); + } - public void Accelerate(Vec3 acceleration) => speed += acceleration; + public Double3 PositionDelta => positionDelta; - public void AccelerateRotation(Vec3 acceleration) => rotationSpeed += acceleration; + public Double3 RotationDelta { get; private set; } - public void SetSpeed(Vec3 speed) => this.speed = speed; + public void Accelerate(Double3 acceleration) + { + speed += acceleration; + } - public Vec3 PositionDelta => positionDelta; + public void AccelerateRotation(Double3 acceleration) + { + rotationSpeed += acceleration; + } - public Vec3 RotationDelta { get; private set; } + public void SetSpeed(Double3 speed) + { + this.speed = speed; + } public override void Render() { } - private Vec3 speed, rotationSpeed; - private Vec3 positionDelta; - public override void Update(World world) { Move(world); RotationMove(); - Accelerate(new Vec3(0.0, -0.1, 0.0)); // Gravity + Accelerate(new Double3(0.0, -0.1, 0.0)); // Gravity } private void Move(World world) { - positionDelta = Mat4D.Rotation(Rotation.Y, new Vec3(0.0f, 1.0f, 0.0f)).Transform(speed, 0.0).Key; + positionDelta = Mat4D.Rotation(Rotation.Y, new Double3(0.0f, 1.0f, 0.0f)).Transform(speed, 0.0).Key; var originalDelta = positionDelta; var hitboxes = world.GetHitboxes(Hitbox.Expand(positionDelta)); foreach (var curr in hitboxes) positionDelta.X = Hitbox.MaxMoveOnXclip(curr, positionDelta.X); - MoveHitbox(new Vec3(positionDelta.X, 0.0, 0.0)); + MoveHitbox(new Double3(positionDelta.X, 0.0, 0.0)); if (positionDelta.X != originalDelta.X) speed.X = 0.0; foreach (var curr in hitboxes) positionDelta.Z = Hitbox.MaxMoveOnZclip(curr, positionDelta.Z); - MoveHitbox(new Vec3(0.0, 0.0, positionDelta.Z)); + MoveHitbox(new Double3(0.0, 0.0, positionDelta.Z)); if (positionDelta.Z != originalDelta.Z) speed.Z = 0.0; foreach (var curr in hitboxes) positionDelta.Y = Hitbox.MaxMoveOnYclip(curr, positionDelta.Y); - MoveHitbox(new Vec3(0.0, positionDelta.Y, 0.0)); + MoveHitbox(new Double3(0.0, positionDelta.Y, 0.0)); if (positionDelta.Y != originalDelta.Y) speed.Y = 0.0; Position += positionDelta; } - private static readonly bool RotationInteria = false; - private void RotationMove() { if (Rotation.X + rotationSpeed.X > 90.0) @@ -104,5 +102,22 @@ private void RotationMove() else rotationSpeed *= 0; } + + private class PlayerUpdateTask : IReadOnlyTask + { + private readonly Player player; + private readonly uint worldId; + + public PlayerUpdateTask(Player player, uint worldId) + { + this.player = player; + this.worldId = worldId; + } + + public void Task(ChunkService srv) + { + player.Update(srv.Worlds.Get(worldId)); + } + } } } \ No newline at end of file diff --git a/Game/World/PlayerObject.cs b/Game/World/PlayerObject.cs index 5bf6d89..913bd11 100644 --- a/Game/World/PlayerObject.cs +++ b/Game/World/PlayerObject.cs @@ -18,15 +18,19 @@ // using System; -using Core.Math; using Game.Utilities; +using Xenko.Core.Mathematics; namespace Game.World { public class PlayerObject : Object { + private readonly double height; + private readonly double width; + private Double3 hitboxSize; + public PlayerObject(uint worldId) : - base(worldId, new Vec3(), new Vec3(), new Vec3(1.0, 1.0, 1.0), new Aabb()) + base(worldId, new Double3(), new Double3(), new Double3(1.0, 1.0, 1.0), new Aabb()) { height = 1.6; width = 0.6; @@ -34,20 +38,19 @@ public PlayerObject(uint worldId) : RefreshHitbox(); } - public void Rotate(Vec3 rotation) => Rotation += rotation; - // Body direction, head direction is `mRotation` in class Object - public Vec3 Direction { get; set; } + public Double3 Direction { get; set; } public double Speed { get; set; } - private readonly double height; - private readonly double width; - private Vec3 hitboxSize; + public void Rotate(Double3 rotation) + { + Rotation += rotation; + } private void RefreshHitbox() { - hitboxSize = new Vec3(width, height, width); + hitboxSize = new Double3(width, height, width); Hitbox = new Aabb(-hitboxSize / 2, hitboxSize / 2); } diff --git a/Game/World/World.cs b/Game/World/World.cs index 1be2e08..48ef3e2 100644 --- a/Game/World/World.cs +++ b/Game/World/World.cs @@ -20,7 +20,6 @@ using System; using System.Collections.Generic; using System.Linq; -using Core.Math; using Game.Utilities; using Xenko.Core.Mathematics; @@ -28,6 +27,20 @@ namespace Game.World { public partial class World { + private static readonly Int3[] Delta = + { + new Int3(1, 0, 0), new Int3(-1, 0, 0), + new Int3(0, 1, 0), new Int3(0, -1, 0), + new Int3(0, 0, 1), new Int3(0, 0, -1) + }; + + protected static uint IdCount; + + private readonly Double3 hitboxOffset = new Double3(1.0, 1.0, 1.0); + + // All Chunks (Chunk array) + private readonly object mutex = new object(); + public World(string name) { Name = name; @@ -52,25 +65,55 @@ public World(string name) public ChunkManager Chunks { get; } // Alias declearations for Chunk management - public int GetChunkCount() => Chunks.Count; + public int GetChunkCount() + { + return Chunks.Count; + } - public Chunk GetChunk(ref Int3 chunkPos) => Chunks[chunkPos]; + public Chunk GetChunk(ref Int3 chunkPos) + { + return Chunks[chunkPos]; + } - public bool IsChunkLoaded(Int3 chunkPos) => Chunks.IsLoaded(chunkPos); + public bool IsChunkLoaded(Int3 chunkPos) + { + return Chunks.IsLoaded(chunkPos); + } - public void DeleteChunk(Int3 chunkPos) => Chunks.Remove(chunkPos); + public void DeleteChunk(Int3 chunkPos) + { + Chunks.Remove(chunkPos); + } - public static int GetChunkAxisPos(int pos) => ChunkManager.GetAxisPos(pos); + public static int GetChunkAxisPos(int pos) + { + return ChunkManager.GetAxisPos(pos); + } - public static Int3 GetChunkPos(Int3 pos) => ChunkManager.GetPos(pos); + public static Int3 GetChunkPos(Int3 pos) + { + return ChunkManager.GetPos(pos); + } - public static int GetBlockAxisPos(int pos) => ChunkManager.GetBlockAxisPos(pos); + public static int GetBlockAxisPos(int pos) + { + return ChunkManager.GetBlockAxisPos(pos); + } - public static Int3 GetBlockPos(ref Int3 pos) => ChunkManager.GetBlockPos(pos); + public static Int3 GetBlockPos(ref Int3 pos) + { + return ChunkManager.GetBlockPos(pos); + } - public BlockData GetBlock(Int3 pos) => Chunks.GetBlock(pos); + public BlockData GetBlock(Int3 pos) + { + return Chunks.GetBlock(pos); + } - public void SetBlock(ref Int3 pos, BlockData block) => Chunks.SetBlock(pos, block); + public void SetBlock(ref Int3 pos, BlockData block) + { + Chunks.SetBlock(pos, block); + } private Chunk InsertChunk(ref Int3 pos, Chunk chunk) { @@ -78,13 +121,6 @@ private Chunk InsertChunk(ref Int3 pos, Chunk chunk) return chunk; } - private static readonly Int3[] Delta = - { - new Int3(1, 0, 0), new Int3(-1, 0, 0), - new Int3(0, 1, 0), new Int3(0, -1, 0), - new Int3(0, 0, 1), new Int3(0, 0, -1) - }; - public Chunk InsertChunkAndUpdate(Int3 pos, Chunk chunk) { var ret = InsertChunk(ref pos, chunk); @@ -94,29 +130,26 @@ public Chunk InsertChunkAndUpdate(Int3 pos, Chunk chunk) return ret; } - public Chunk ResetChunk(ref Int3 pos, Chunk ptr) => Chunks[pos] = ptr; - - private readonly Vec3 hitboxOffset = new Vec3(1.0, 1.0, 1.0); + public Chunk ResetChunk(ref Int3 pos, Chunk ptr) + { + return Chunks[pos] = ptr; + } public List GetHitboxes(Aabb range) { var res = new List(); Int3 curr; for (curr.X = (int) Math.Floor(range.Min.X); curr.X < (int) Math.Ceiling(range.Max.X); curr.X++) + for (curr.Y = (int) Math.Floor(range.Min.Y); curr.Y < (int) Math.Ceiling(range.Max.Y); curr.Y++) + for (curr.Z = (int) Math.Floor(range.Min.Z); curr.Z < (int) Math.Ceiling(range.Max.Z); curr.Z++) { - for (curr.Y = (int) Math.Floor(range.Min.Y); curr.Y < (int) Math.Ceiling(range.Max.Y); curr.Y++) - { - for (curr.Z = (int) Math.Floor(range.Min.Z); curr.Z < (int) Math.Ceiling(range.Max.Z); curr.Z++) - { - // TODO: BlockType::getAABB - if (!IsChunkLoaded(GetChunkPos(curr))) - continue; - if (GetBlock(curr).Id == 0) - continue; - var currd = new Vec3(curr.X, curr.Y, curr.Z); - res.Add(new Aabb(currd, currd + hitboxOffset)); - } - } + // TODO: BlockType::getAABB + if (!IsChunkLoaded(GetChunkPos(curr))) + continue; + if (GetBlock(curr).Id == 0) + continue; + var currd = new Double3(curr.X, curr.Y, curr.Z); + res.Add(new Aabb(currd, currd + hitboxOffset)); } return res; @@ -132,11 +165,6 @@ public void UpdateChunkLoadStatus() } } - protected static uint IdCount; - - // All Chunks (Chunk array) - private readonly object mutex = new object(); - private void ResetChunkAndUpdate(Int3 pos, Chunk chunk) { ResetChunk(ref pos, chunk); diff --git a/Game/World/WorldTasks.cs b/Game/World/WorldTasks.cs index 9e7c542..3b653ed 100644 --- a/Game/World/WorldTasks.cs +++ b/Game/World/WorldTasks.cs @@ -31,8 +31,15 @@ public partial class World private static readonly Int3 MiddleOffset = new Int3(Chunk.Size / 2 - 1, Chunk.Size / 2 - 1, Chunk.Size / 2 - 1); + public void RegisterChunkTasks(ChunkService cs, Player player) + { + cs.TaskDispatcher.AddRegular(new LoadUnloadDetectorTask(this, player)); + } + public class ResetChunkTask : IReadWriteTask { + private readonly Chunk chunk; + /** * \brief Add a constructed chunk into world. * \param chunk the target chunk @@ -46,12 +53,14 @@ public void Task(ChunkService srv) { chunk.World.ResetChunkAndUpdate(chunk.Position, chunk); } - - private readonly Chunk chunk; } private class UnloadChunkTask : IReadWriteTask { + private readonly Int3 chunkPosition; + + private readonly World world; + /** * \brief Given a chunk, it will try to unload it (decrease a ref) * \param world the target world @@ -68,13 +77,14 @@ public void Task(ChunkService srv) //TODO: for multiplayer situation, it should decrease ref counter instead of deleting world.DeleteChunk(chunkPosition); } - - private readonly World world; - private readonly Int3 chunkPosition; } private class BuildOrLoadChunkTask : IReadOnlyTask { + private readonly Int3 chunkPosition; + + private readonly World world; + /** * \brief Given a chunk, it will try to load it or build it * \param world the target world @@ -90,32 +100,12 @@ public void Task(ChunkService srv) { srv.TaskDispatcher.Add(new ResetChunkTask(new Chunk(chunkPosition, world))); } - - private readonly World world; - private readonly Int3 chunkPosition; } private class LoadUnloadDetectorTask : IReadOnlyTask { - private class AddToWorldTask : IReadWriteTask - { - /** - * \brief Add a constructed chunk into world. - * \param worldID the target world's id - * \param chunk the target chunk - */ - public AddToWorldTask(Chunk chunk) - { - this.chunk = chunk; - } - - public void Task(ChunkService srv) - { - chunk.World.InsertChunkAndUpdate(chunk.Position, chunk); - } - - private readonly Chunk chunk; - } + private readonly Player player; + private readonly World world; public LoadUnloadDetectorTask(World world, Player player) { @@ -136,17 +126,12 @@ public void Task(ChunkService cs) // load a fake chunk cs.TaskDispatcher.Add(new AddToWorldTask(new Chunk(loadPos.Value, world, false))); cs.TaskDispatcher.Add(new BuildOrLoadChunkTask(world, loadPos.Value)); - if (!cs.IsAuthority) - { - Client.GetChunk.Call(world.Id, loadPos.Value); - } + if (!cs.IsAuthority) Client.GetChunk.Call(world.Id, loadPos.Value); } foreach (var unloadChunk in unloadList) - { // add a unload task. cs.TaskDispatcher.Add(new UnloadChunkTask(world, unloadChunk.Value.Position)); - } } /** @@ -169,33 +154,47 @@ private static void GenerateLoadUnloadList(World world, Int3 centerPos, int load var curPos = chunk.Value.Position; // Out of load range, pending to unload if (ChebyshevDistance(centerCPos, curPos) > loadRange) - unloadList.Insert((curPos * Chunk.Size + MiddleOffset - centerPos).LengthSquared(), chunk.Value); + unloadList.Insert((curPos * Chunk.Size + MiddleOffset - centerPos).LengthSquared(), + chunk.Value); } for (var x = centerCPos.X - loadRange; x <= centerCPos.X + loadRange; x++) + for (var y = centerCPos.Y - loadRange; y <= centerCPos.Y + loadRange; y++) + for (var z = centerCPos.Z - loadRange; z <= centerCPos.Z + loadRange; z++) { - for (var y = centerCPos.Y - loadRange; y <= centerCPos.Y + loadRange; y++) - { - for (var z = centerCPos.Z - loadRange; z <= centerCPos.Z + loadRange; z++) - { - var position = new Int3(x, y, z); - // In load range, pending to load - if (!world.IsChunkLoaded(position)) - loadList.Insert((position * Chunk.Size + MiddleOffset - centerPos).LengthSquared(), - position); - } - } + var position = new Int3(x, y, z); + // In load range, pending to load + if (!world.IsChunkLoaded(position)) + loadList.Insert((position * Chunk.Size + MiddleOffset - centerPos).LengthSquared(), + position); } } // TODO: Remove Type1 Clone - private static int ChebyshevDistance(Int3 l, Int3 r) => Math.Max(Math.Max(Math.Abs(l.X - r.X), Math.Abs(l.Y - r.Y)), Math.Abs(l.Z - r.Z)); + private static int ChebyshevDistance(Int3 l, Int3 r) + { + return Math.Max(Math.Max(Math.Abs(l.X - r.X), Math.Abs(l.Y - r.Y)), Math.Abs(l.Z - r.Z)); + } - private readonly Player player; - private readonly World world; - } + private class AddToWorldTask : IReadWriteTask + { + private readonly Chunk chunk; - public void RegisterChunkTasks(ChunkService cs, Player player) => - cs.TaskDispatcher.AddRegular(new LoadUnloadDetectorTask(this, player)); + /** + * \brief Add a constructed chunk into world. + * \param worldID the target world's id + * \param chunk the target chunk + */ + public AddToWorldTask(Chunk chunk) + { + this.chunk = chunk; + } + + public void Task(ChunkService srv) + { + chunk.World.InsertChunkAndUpdate(chunk.Position, chunk); + } + } + } } } \ No newline at end of file diff --git a/Main/Main.cs b/Main/Main.cs index 0564715..83b1e9c 100644 --- a/Main/Main.cs +++ b/Main/Main.cs @@ -30,17 +30,63 @@ public class Main : IModule { private static ushort _grassId, _rockId, _dirtId, _sandId, _waterId; + public void CoInitialize() + { + _grassId = Blocks.Register(new BlockType("Grass", true, false, true, 2)); + _rockId = Blocks.Register(new BlockType("Rock", true, false, true, 2)); + _dirtId = Blocks.Register(new BlockType("Dirt", true, false, true, 2)); + _sandId = Blocks.Register(new BlockType("Sand", true, false, true, 2)); + _waterId = Blocks.Register(new BlockType("Water", false, true, false, 2)); + Chunk.SetGenerator(WorldGen.Generator); + RendererInit(); + } + + public void CoFinalize() + { + } + + public void OnMemoryWarning() + { + } + + private static void RendererInit() + { + if (!Services.TryGet("BlockTextures", out var textures)) return; + var path = Path.Asset("Infinideas.Main") + "blocks/"; + uint[] id = + { + textures.Add(path + "grass_top.png"), + textures.Add(path + "grass_round.png"), + textures.Add(path + "dirt.png"), + textures.Add(path + "rock.png"), + textures.Add(path + "sand.png"), + textures.Add(path + "water.png") + }; + + var grass = new DefaultBlockRenderer(new[] {id[1], id[1], id[0], id[2], id[1], id[1]}); + var rock = new DefaultBlockRenderer(new[] {id[3], id[3], id[3], id[3], id[3], id[3]}); + var dirt = new DefaultBlockRenderer(new[] {id[2], id[2], id[2], id[2], id[2], id[2]}); + var sand = new DefaultBlockRenderer(new[] {id[4], id[4], id[4], id[4], id[4], id[4]}); + var water = new DefaultBlockRenderer(new[] {id[5], id[5], id[5], id[5], id[5], id[5]}); + + BlockRenderers.Add(_grassId, grass); + BlockRenderers.Add(_rockId, rock); + BlockRenderers.Add(_dirtId, dirt); + BlockRenderers.Add(_sandId, sand); + BlockRenderers.Add(_waterId, water); + } + private static class WorldGen { - private static int Seed { get; } = 1025; private const double NoiseScaleX = 64.0; private const double NoiseScaleZ = 64.0; + private static int Seed { get; } = 1025; private static double Noise(int x, int y) { var xx = x * 107 + y * 13258953287; - xx = xx >> 13 ^ xx; - return (xx * (xx * xx * 15731 + 789221) + 1376312589 & 0x7fffffff) / 16777216.0; + xx = (xx >> 13) ^ xx; + return ((xx * (xx * xx * 15731 + 789221) + 1376312589) & 0x7fffffff) / 16777216.0; } private static double InterpolatedNoise(double x, double y) @@ -86,17 +132,11 @@ public static void Generator(Int3 pos, BlockData[] blocks, int daylightBrightnes if (y <= height) { if (y == height) - { block.Id = underWater ? _sandId : _grassId; - } else if (y >= height - 3) - { block.Id = underWater ? _sandId : _dirtId; - } else - { block.Id = _rockId; - } block.Brightness = 0; block.Data = 0; @@ -111,51 +151,5 @@ public static void Generator(Int3 pos, BlockData[] blocks, int daylightBrightnes } } } - - public void CoInitialize() - { - _grassId = Blocks.Register(new BlockType("Grass", true, false, true, 2)); - _rockId = Blocks.Register(new BlockType("Rock", true, false, true, 2)); - _dirtId = Blocks.Register(new BlockType("Dirt", true, false, true, 2)); - _sandId = Blocks.Register(new BlockType("Sand", true, false, true, 2)); - _waterId = Blocks.Register(new BlockType("Water", false, true, false, 2)); - Chunk.SetGenerator(WorldGen.Generator); - RendererInit(); - } - - public void CoFinalize() - { - } - - public void OnMemoryWarning() - { - } - - private static void RendererInit() - { - if (!Services.TryGet("BlockTextures", out var textures)) return; - var path = Path.Asset("Infinideas.Main") + "blocks/"; - uint[] id = - { - textures.Add(path + "grass_top.png"), - textures.Add(path + "grass_round.png"), - textures.Add(path + "dirt.png"), - textures.Add(path + "rock.png"), - textures.Add(path + "sand.png"), - textures.Add(path + "water.png") - }; - - var grass = new DefaultBlockRenderer(new[] {id[1], id[1], id[0], id[2], id[1], id[1]}); - var rock = new DefaultBlockRenderer(new[] {id[3], id[3], id[3], id[3], id[3], id[3]}); - var dirt = new DefaultBlockRenderer(new[] {id[2], id[2], id[2], id[2], id[2], id[2]}); - var sand = new DefaultBlockRenderer(new[] {id[4], id[4], id[4], id[4], id[4], id[4]}); - var water = new DefaultBlockRenderer(new[] {id[5], id[5], id[5], id[5], id[5], id[5]}); - - BlockRenderers.Add(_grassId, grass); - BlockRenderers.Add(_rockId, rock); - BlockRenderers.Add(_dirtId, dirt); - BlockRenderers.Add(_sandId, sand); - BlockRenderers.Add(_waterId, water); - } } } \ No newline at end of file diff --git a/Main/Main.csproj b/Main/Main.csproj index 521e4f8..807a473 100644 --- a/Main/Main.csproj +++ b/Main/Main.csproj @@ -4,19 +4,6 @@ netstandard2.0 - - - - - - - - - - - - - diff --git a/NEWorld.Linux/NEWorld.Linux.csproj b/NEWorld.Linux/NEWorld.Linux.csproj index 8df7186..47a7b5a 100644 --- a/NEWorld.Linux/NEWorld.Linux.csproj +++ b/NEWorld.Linux/NEWorld.Linux.csproj @@ -17,7 +17,7 @@ - + diff --git a/NEWorld.Linux/NEWorldApp.cs b/NEWorld.Linux/NEWorldApp.cs index 7bb5187..f362cb7 100644 --- a/NEWorld.Linux/NEWorldApp.cs +++ b/NEWorld.Linux/NEWorldApp.cs @@ -1,8 +1,8 @@ namespace NEWorld.Linux { - class NEWorldApp + internal class NEWorldApp { - static void Main(string[] args) + private static void Main(string[] args) { using (var game = new Xenko.Engine.Game()) { @@ -10,4 +10,4 @@ static void Main(string[] args) } } } -} +} \ No newline at end of file diff --git a/NEWorld.Windows/Assets/EffectCompileLog.xkeffectlog b/NEWorld.Windows/Assets/EffectCompileLog.xkeffectlog index 98e24bb..5ae6c49 100644 --- a/NEWorld.Windows/Assets/EffectCompileLog.xkeffectlog +++ b/NEWorld.Windows/Assets/EffectCompileLog.xkeffectlog @@ -216,3 +216,95 @@ UsedParameters: - !ToneMap Operator: !ToneMapHejl2Operator {} - !LuminanceToChannelTransform {} +--- +!EffectCompileRequest +EffectName: XenkoForwardShadingEffect.ShadowMapCaster +UsedParameters: + Material.PixelStageSurfaceShaders: !ShaderMixinSource + Mixins: + - ClassName: MaterialSurfaceArray + Compositions: + layers: !ShaderArraySource + Values: + - !ShaderMixinSource + Mixins: + - ClassName: MaterialSurfaceDiffuse + Compositions: + diffuseMap: !ShaderClassSource + ClassName: VertexTextureTerrain + - !ShaderMixinSource + Mixins: + - ClassName: MaterialSurfaceLightingAndShading + Compositions: + surfaces: !ShaderArraySource + Values: + - !ShaderClassSource + ClassName: MaterialSurfaceShadingDiffuseLambert + GenericArguments: [false] + Material.PixelStageStreamInitializer: !ShaderMixinSource + Mixins: + - ClassName: MaterialStream + - ClassName: MaterialPixelShadingStream + Lighting.DirectLightGroups: + - !ShaderMixinSource + Mixins: + - ClassName: LightDirectionalGroup + GenericArguments: [8] + - !ShaderClassSource + ClassName: LightClusteredPointGroup + - !ShaderClassSource + ClassName: LightClusteredSpotGroup + Lighting.EnvironmentLights: + - !ShaderClassSource + ClassName: LightSimpleAmbient + - !ShaderClassSource + ClassName: EnvironmentLight +--- +!EffectCompileRequest +EffectName: XenkoForwardShadingEffect +UsedParameters: + Material.PixelStageSurfaceShaders: !ShaderMixinSource + Mixins: + - ClassName: MaterialSurfaceArray + Compositions: + layers: !ShaderArraySource + Values: + - !ShaderMixinSource + Mixins: + - ClassName: MaterialSurfaceDiffuse + Compositions: + diffuseMap: !ShaderClassSource + ClassName: VertexTextureTerrain + - !ShaderMixinSource + Mixins: + - ClassName: MaterialSurfaceLightingAndShading + Compositions: + surfaces: !ShaderArraySource + Values: + - !ShaderClassSource + ClassName: MaterialSurfaceShadingDiffuseLambert + GenericArguments: [false] + Material.PixelStageStreamInitializer: !ShaderMixinSource + Mixins: + - ClassName: MaterialStream + - ClassName: MaterialPixelShadingStream + Lighting.DirectLightGroups: + - !ShaderMixinSource + Mixins: + - ClassName: LightDirectionalGroup + GenericArguments: [8] + - !ShaderClassSource + ClassName: LightClusteredPointGroup + - !ShaderClassSource + ClassName: LightClusteredSpotGroup + Lighting.EnvironmentLights: + - !ShaderClassSource + ClassName: LightSimpleAmbient + - !ShaderClassSource + ClassName: EnvironmentLight + XenkoEffectBase.RenderTargetExtensions: !ShaderMixinSource + Macros: + - Name: XENKO_RENDER_TARGET_COUNT + Definition: 1 + - Name: XENKO_MULTISAMPLE_COUNT + Definition: 1 diff --git a/NEWorld.Windows/NEWorldApp.cs b/NEWorld.Windows/NEWorldApp.cs index 9a381b6..fbaaf86 100644 --- a/NEWorld.Windows/NEWorldApp.cs +++ b/NEWorld.Windows/NEWorldApp.cs @@ -1,8 +1,8 @@ namespace NEWorld.Windows { - class NEWorldApp + internal class NEWorldApp { - static void Main(string[] args) + private static void Main(string[] args) { using (var game = new Xenko.Engine.Game()) { @@ -10,4 +10,4 @@ static void Main(string[] args) } } } -} +} \ No newline at end of file diff --git a/NEWorld.macOS/NEWorld.macOS.csproj b/NEWorld.macOS/NEWorld.macOS.csproj index a036827..0baa0dc 100644 --- a/NEWorld.macOS/NEWorld.macOS.csproj +++ b/NEWorld.macOS/NEWorld.macOS.csproj @@ -17,7 +17,7 @@ - + diff --git a/NEWorld.macOS/NEWorldApp.cs b/NEWorld.macOS/NEWorldApp.cs index b3c270c..a4e6357 100644 --- a/NEWorld.macOS/NEWorldApp.cs +++ b/NEWorld.macOS/NEWorldApp.cs @@ -1,8 +1,8 @@ namespace NEWorld.macOS { - class NEWorldApp + internal class NEWorldApp { - static void Main(string[] args) + private static void Main(string[] args) { using (var game = new Xenko.Engine.Game()) { @@ -10,4 +10,4 @@ static void Main(string[] args) } } } -} +} \ No newline at end of file diff --git a/NEWorld.sln.DotSettings.user b/NEWorld.sln.DotSettings.user index 620e1d7..3bce188 100644 --- a/NEWorld.sln.DotSettings.user +++ b/NEWorld.sln.DotSettings.user @@ -21,4 +21,7 @@ <Assembly Path="C:\Program Files\dotnet\shared\Microsoft.NETCore.App\2.1.7\System.Console.dll" /> <Assembly Path="C:\Program Files\dotnet\shared\Microsoft.NETCore.App\2.1.7\System.Threading.Thread.dll" /> <Assembly Path="C:\Users\yshli\.nuget\packages\xenko\3.1.0.1-beta01-0406\lib\netstandard2.0\Xenko.dll" /> + <Assembly Path="C:\Program Files\dotnet\sdk\NuGetFallbackFolder\messagepack\1.7.3.4\lib\netstandard2.0\MessagePack.dll" /> + <Assembly Path="C:\Users\yshli\.nuget\packages\xenko.core\3.1.0.1-beta01-0406\ref\netstandard2.0\Xenko.Core.dll" /> + <Assembly Path="C:\Users\yshli\.nuget\packages\xenko\3.1.0.1-beta01-0430\lib\netstandard2.0\Xenko.dll" /> </AssemblyExplorer> \ No newline at end of file diff --git a/NEWorld/Assets/GameSettings.xkgamesettings b/NEWorld/Assets/GameSettings.xkgamesettings index 299d48d..7ed69cb 100644 --- a/NEWorld/Assets/GameSettings.xkgamesettings +++ b/NEWorld/Assets/GameSettings.xkgamesettings @@ -9,31 +9,6 @@ Defaults: HrtfSupport: false - !Xenko.Assets.EditorSettings,Xenko.Assets RenderingMode: LDR - - !Xenko.Navigation.NavigationSettings,Xenko.Navigation - EnableDynamicNavigationMesh: false - IncludedCollisionGroups: AllFilter - BuildSettings: - CellHeight: 0.2 - CellSize: 0.3 - TileSize: 32 - MinRegionArea: 2 - RegionMergeArea: 20 - MaxEdgeLen: 12.0 - MaxEdgeError: 1.3 - DetailSamplingDistance: 6.0 - MaxDetailSamplingError: 1.0 - Groups: - - Id: 20b0f359-171f-4eae-9bc0-9f80749efb8f - Name: New group - AgentSettings: - Height: 1.0 - MaxClimb: 0.25 - MaxSlope: {Radians: 0.7853982} - Radius: 0.5 - - !Xenko.Physics.PhysicsSettings,Xenko.Physics - Flags: None - MaxSubSteps: 1 - FixedTimeStep: 0.0166666675 - !Xenko.Graphics.RenderingSettings,Xenko.Graphics DefaultBackBufferWidth: 1280 DefaultBackBufferHeight: 720 diff --git a/NEWorld/Assets/GraphicsCompositor.xkgfxcomp b/NEWorld/Assets/GraphicsCompositor.xkgfxcomp index 781abb2..4fa0eef 100644 --- a/NEWorld/Assets/GraphicsCompositor.xkgfxcomp +++ b/NEWorld/Assets/GraphicsCompositor.xkgfxcomp @@ -108,13 +108,7 @@ RenderFeatures: 14a071694411235038a102ac3794bb4d: !Xenko.Rendering.SimpleGroupToRenderStageSelector,Xenko.Engine RenderStage: ref!! 8676a7c8-a5c8-4aee-929c-33492ebd06e6 EffectName: Test - 9013eab3ea0ef6c98bf133b86c173d45: !Xenko.Particles.Rendering.ParticleEmitterRenderFeature,Xenko.Particles - RenderStageSelectors: - af1bd241305893ef8ff7952184e1cb0b: !Xenko.Particles.Rendering.ParticleEmitterTransparentRenderStageSelector,Xenko.Particles - OpaqueRenderStage: ref!! e3a6abbb-fcd8-4308-82a2-fad7de6a5bcc - TransparentRenderStage: ref!! 8676a7c8-a5c8-4aee-929c-33492ebd06e6 - EffectName: null - PipelineProcessors: {} + 9013eab3ea0ef6c98bf133b86c173d45: ~(Deleted) SharedRenderers: 60459475d3a3adaf2d1ba5d99913ca75: !Xenko.Rendering.Compositing.ForwardRenderer,Xenko.Engine Id: eae2968e-f8b9-4541-9f09-291e8b21e99b diff --git a/NEWorld/Assets/MainScene.xkscene b/NEWorld/Assets/MainScene.xkscene index 287d17a..c57f07a 100644 --- a/NEWorld/Assets/MainScene.xkscene +++ b/NEWorld/Assets/MainScene.xkscene @@ -9,8 +9,8 @@ Hierarchy: - ref!! 8d346802-8fe6-4d18-957c-c6dd8404ea84 - ref!! 1cc17873-cca9-48fe-a61f-a1d061e26959 - ref!! b1058700-11a7-4296-bb63-2fefdb5f28b7 - - ref!! ffafa74d-ee74-4f21-aeba-f6565b12674d - ref!! b1588acf-0fcd-4377-bf5d-6c81dcf34484 + - ref!! 64e3b5a5-d684-4f59-821f-a3ef18dae1d2 Parts: - Entity: Id: 1cc17873-cca9-48fe-a61f-a1d061e26959 @@ -18,8 +18,8 @@ Hierarchy: Components: 1fd11c5daf8481b885d448424af7cbe3: !TransformComponent Id: 2e13dd0e-ce0b-4194-87b1-355fb6805b1a - Position: {X: 0.0, Y: 2.0, Z: 0.0} - Rotation: {X: 1.131334E-08, Y: -0.9659258, Z: -0.258819044, W: -4.222196E-08} + Position: {X: 0.0, Y: 128.0, Z: 0.0} + Rotation: {X: -0.707106769, Y: 0.0, Z: 0.0, W: 0.707106769} Scale: {X: 1.0, Y: 1.0, Z: 1.0} Children: {} e709a18ae6b4e34290801582f4038c7d: !LightComponent @@ -32,10 +32,25 @@ Hierarchy: Filter: !LightShadowMapFilterTypePcf FilterSize: Filter5x5 Size: Large - DepthRange: {} + DepthRange: + ManualMaxDistance: 256.0 PartitionMode: !LightDirectionalShadowMap.PartitionLogarithmic {} ComputeTransmittance: false BiasParameters: {} + - Entity: + Id: 64e3b5a5-d684-4f59-821f-a3ef18dae1d2 + Name: Sphere + Components: + d72bc5de5518d27be82c2877ec8fe400: !TransformComponent + Id: fdbbe38c-4c4f-4d3b-b15b-85c332d2ba35 + Position: {X: 1.60677338, Y: -4.76837158E-07, Z: 0.8989582} + Rotation: {X: 0.0, Y: 0.0, Z: 0.0, W: 1.0} + Scale: {X: 1.0, Y: 1.0, Z: 1.0} + Children: {} + 9895f39d789515c18b0ad97b68a13e41: !ModelComponent + Id: 2cc6a1ac-dfd1-48f9-9f81-1f011530c92a + Model: e393c514-dbc9-4623-ad9e-3da0bb951a95:Sphere + Materials: {} - Entity: Id: 8d346802-8fe6-4d18-957c-c6dd8404ea84 Name: Camera @@ -85,19 +100,3 @@ Hierarchy: d80b4d8f8e4c7517fb33931ae02a1875: !NEWorld.MainScript,NEWorld Id: ad46feb5-e564-418b-8912-d79cfbc7a12d Material: a0b217f1-284e-4307-abd6-b42f56bedff1:Material - - Entity: - Id: ffafa74d-ee74-4f21-aeba-f6565b12674d - Name: Ambient light - Components: - 79053e6520eb8792554a10e8b4b67849: !TransformComponent - Id: 484f1a7f-af76-49ca-a6ba-1581287e98ce - Position: {X: -2.0, Y: 2.0, Z: 0.0} - Rotation: {X: 0.0, Y: 0.0, Z: 0.0, W: 1.0} - Scale: {X: 1.0, Y: 1.0, Z: 1.0} - Children: {} - da4d8f8e6c59aa339ef3d33b7c2a36a7: !LightComponent - Id: dc510114-c9ef-490a-bc7d-528bb0f3362f - Type: !LightAmbient - Color: !ColorRgbProvider - Value: {R: 0.647058845, G: 0.7882353, B: 0.9411765} - Intensity: 0.1 diff --git a/NEWorld/Assets/Material.xkmat b/NEWorld/Assets/Material.xkmat index 58655e3..7ea36b7 100644 --- a/NEWorld/Assets/Material.xkmat +++ b/NEWorld/Assets/Material.xkmat @@ -4,10 +4,8 @@ SerializedVersion: {Xenko: 2.0.0.0} Tags: [] Attributes: Diffuse: !MaterialDiffuseMapFeature - DiffuseMap: !ComputeShaderClassColor - MixinReference: VertexTextureTerrain - Generics: {} - CompositionNodes: {} + DiffuseMap: !ComputeColor + Value: {R: 1.0, G: 1.0, B: 1.0, A: 1.0} DiffuseModel: !MaterialDiffuseLambertModelFeature {} Overrides: UVScale: {X: 1.0, Y: 1.0} diff --git a/NEWorld/Assets/Sphere.xkpromodel b/NEWorld/Assets/Sphere.xkpromodel new file mode 100644 index 0000000..ea4bd81 --- /dev/null +++ b/NEWorld/Assets/Sphere.xkpromodel @@ -0,0 +1,12 @@ +!ProceduralModelAsset +Id: e393c514-dbc9-4623-ad9e-3da0bb951a95 +SerializedVersion: {Xenko: 2.0.0.0} +Tags: [] +Type: !SphereProceduralModel + Radius: 1.0 + Tessellation: 8 + Scale: {X: 1.0, Y: 1.0, Z: 1.0} + UvScale: {X: 1.0, Y: 1.0} + LocalOffset: {X: 0.0, Y: 0.0, Z: 0.0} + MaterialInstance: + Material: a0b217f1-284e-4307-abd6-b42f56bedff1:Material diff --git a/NEWorld/BasicCameraController.cs b/NEWorld/BasicCameraController.cs index 12e15e0..13bc036 100644 --- a/NEWorld/BasicCameraController.cs +++ b/NEWorld/BasicCameraController.cs @@ -7,20 +7,22 @@ namespace NEWorld { /// - /// A script that allows to move and rotate an entity through keyboard, mouse and touch input to provide basic camera navigation. + /// A script that allows to move and rotate an entity through keyboard, mouse and touch input to provide basic camera + /// navigation. /// /// - /// The entity can be moved using W, A, S, D, Q and E, arrow keys or dragging/scaling using multi-touch. - /// Rotation is achieved using the Numpad, the mouse while holding the right mouse button, or dragging using single-touch. + /// The entity can be moved using W, A, S, D, Q and E, arrow keys or dragging/scaling using multi-touch. + /// Rotation is achieved using the Numpad, the mouse while holding the right mouse button, or dragging using + /// single-touch. /// public class BasicCameraController : SyncScript { private const float MaximumPitch = MathUtil.PiOverTwo * 0.99f; + private float pitch; + private Vector3 translation; private Vector3 upVector; - private Vector3 translation; private float yaw; - private float pitch; public Vector3 KeyboardMovementSpeed { get; set; } = new Vector3(5.0f); @@ -63,56 +65,28 @@ private void ProcessInput() // Move with keyboard if (Input.IsKeyDown(Keys.W) || Input.IsKeyDown(Keys.Up)) - { translation.Z = -KeyboardMovementSpeed.Z; - } - else if (Input.IsKeyDown(Keys.S) || Input.IsKeyDown(Keys.Down)) - { - translation.Z = KeyboardMovementSpeed.Z; - } + else if (Input.IsKeyDown(Keys.S) || Input.IsKeyDown(Keys.Down)) translation.Z = KeyboardMovementSpeed.Z; if (Input.IsKeyDown(Keys.A) || Input.IsKeyDown(Keys.Left)) - { translation.X = -KeyboardMovementSpeed.X; - } - else if (Input.IsKeyDown(Keys.D) || Input.IsKeyDown(Keys.Right)) - { - translation.X = KeyboardMovementSpeed.X; - } + else if (Input.IsKeyDown(Keys.D) || Input.IsKeyDown(Keys.Right)) translation.X = KeyboardMovementSpeed.X; if (Input.IsKeyDown(Keys.Q)) - { translation.Y = -KeyboardMovementSpeed.Y; - } - else if (Input.IsKeyDown(Keys.E)) - { - translation.Y = KeyboardMovementSpeed.Y; - } + else if (Input.IsKeyDown(Keys.E)) translation.Y = KeyboardMovementSpeed.Y; // Alternative translation speed - if (Input.IsKeyDown(Keys.LeftShift) || Input.IsKeyDown(Keys.RightShift)) - { - translation *= SpeedFactor; - } + if (Input.IsKeyDown(Keys.LeftShift) || Input.IsKeyDown(Keys.RightShift)) translation *= SpeedFactor; // Rotate with keyboard if (Input.IsKeyDown(Keys.NumPad2)) - { pitch = KeyboardRotationSpeed.X; - } - else if (Input.IsKeyDown(Keys.NumPad8)) - { - pitch = -KeyboardRotationSpeed.X; - } + else if (Input.IsKeyDown(Keys.NumPad8)) pitch = -KeyboardRotationSpeed.X; if (Input.IsKeyDown(Keys.NumPad4)) - { yaw = KeyboardRotationSpeed.Y; - } - else if (Input.IsKeyDown(Keys.NumPad6)) - { - yaw = -KeyboardRotationSpeed.Y; - } + else if (Input.IsKeyDown(Keys.NumPad6)) yaw = -KeyboardRotationSpeed.Y; // Rotate with mouse if (Input.IsMouseButtonDown(MouseButton.Right)) @@ -128,15 +102,14 @@ private void ProcessInput() Input.UnlockMousePosition(); Game.IsMouseVisible = true; } - + // Handle gestures foreach (var gestureEvent in Input.GestureEvents) - { switch (gestureEvent.Type) { // Rotate by dragging case GestureType.Drag: - var drag = (GestureEventDrag)gestureEvent; + var drag = (GestureEventDrag) gestureEvent; var dragDistance = drag.DeltaTranslation; yaw = -dragDistance.X * TouchRotationSpeed.X; pitch = -dragDistance.Y * TouchRotationSpeed.Y; @@ -144,18 +117,17 @@ private void ProcessInput() // Move along z-axis by scaling and in xy-plane by multi-touch dragging case GestureType.Composite: - var composite = (GestureEventComposite)gestureEvent; + var composite = (GestureEventComposite) gestureEvent; translation.X = -composite.DeltaTranslation.X * TouchMovementSpeed.X; translation.Y = -composite.DeltaTranslation.Y * TouchMovementSpeed.Y; - translation.Z = -(float)Math.Log(composite.DeltaScale + 1) * TouchMovementSpeed.Z; + translation.Z = -(float) Math.Log(composite.DeltaScale + 1) * TouchMovementSpeed.Z; break; } - } } private void UpdateTransform() { - var elapsedTime = (float)Game.UpdateTime.Elapsed.TotalSeconds; + var elapsedTime = (float) Game.UpdateTime.Elapsed.TotalSeconds; translation *= elapsedTime; yaw *= elapsedTime; @@ -173,7 +145,7 @@ private void UpdateTransform() up.Normalize(); // Adjust pitch. Prevent it from exceeding up and down facing. Stabilize edge cases. - var currentPitch = MathUtil.PiOverTwo - (float)Math.Acos(Vector3.Dot(rotation.Forward, upVector)); + var currentPitch = MathUtil.PiOverTwo - (float) Math.Acos(Vector3.Dot(rotation.Forward, upVector)); pitch = MathUtil.Clamp(currentPitch + pitch, -MaximumPitch, MaximumPitch) - currentPitch; // Move in local coordinates @@ -183,4 +155,4 @@ private void UpdateTransform() Entity.Transform.Rotation *= Quaternion.RotationAxis(right, pitch) * Quaternion.RotationAxis(upVector, yaw); } } -} +} \ No newline at end of file diff --git a/NEWorld/Effects/VertexTextureTerrain.xksl b/NEWorld/Effects/VertexTextureTerrain.xksl index 2406cad..77aaa32 100644 --- a/NEWorld/Effects/VertexTextureTerrain.xksl +++ b/NEWorld/Effects/VertexTextureTerrain.xksl @@ -8,10 +8,9 @@ }; // stage Texture2D Almg; stage stream float2 TexCoord : TEXCOORD0; - stage stream float Color : COLOR0; override float4 Compute() { - return float4(streams.Color, streams.Color, streams.Color, streams.Color);//Almg.Sample(MeshTextureSampler, streams.TexCoord); + return float4(1.0, 1.0, 1.0, 1.0);//Almg.Sample(MeshTextureSampler, streams.TexCoord); } }; diff --git a/NEWorld/MainScript.cs b/NEWorld/MainScript.cs index 2b0e34f..7ee8b7b 100644 --- a/NEWorld/MainScript.cs +++ b/NEWorld/MainScript.cs @@ -17,14 +17,16 @@ // along with NEWorld. If not, see . // -using Core.Math; +using System; +using System.Threading; +using System.Threading.Tasks; +using Core; +using Core.Module; using Core.Utilities; using Game; using Game.Network; using Game.World; -using System; -using System.Threading; -using System.Threading.Tasks; +using NEWorld.Renderer; using Xenko.Core.Diagnostics; using Xenko.Core.Mathematics; using Xenko.Engine; @@ -39,6 +41,10 @@ public static class Context { private static IGame _game; + public static readonly VertexDeclaration VertexLayout = new VertexDeclaration( + VertexElement.TextureCoordinate(), VertexElement.Position() + ); + public static IGame Game { get => _game; @@ -59,15 +65,9 @@ public static IGame Game public static CommandList CommandList => Game.GraphicsContext.CommandList; - public static readonly VertexDeclaration VertexLayout = new VertexDeclaration( - VertexElement.TextureCoordinate(), - VertexElement.Color(), - VertexElement.Position() - ); - public static IndexBufferBinding IndexBuffer { get; private set; } } - + public static class IndexBufferBuilder { public static Buffer Build() @@ -91,11 +91,21 @@ public static Buffer Build() public class MainScript : SyncScript { + // Current world + private World currentWorld; public Material Material; + // Player + private Player player; + + private RdWorld rdWorld; + + // Local server + private Server server; + private void InitializeModules() { - Core.Module.Modules.Instance.Load("Main"); + Modules.Instance.Load("Main"); } private void InitializeContext() @@ -103,7 +113,7 @@ private void InitializeContext() Context.Game = Game; Context.Material = Material; Context.OperatingScene = Entity.Scene; - Core.LogPort.Logger = Log; + LogPort.Logger = Log; Log.ActivateLog(LogMessageType.Debug); } @@ -131,7 +141,7 @@ private void LoadPlayer() { player = new Player(0) { - Position = new Vec3(-16.0, 48.0, 32.0), Rotation = new Vec3(-45.0, -22.5, 0.0) + Position = new Double3(-16.0, 48.0, 32.0), Rotation = new Double3(-45.0, -22.5, 0.0) }; } @@ -145,7 +155,7 @@ private async Task EnterCurrentWorld() private void StartTerrainRenderService() { - rdWorld = new Renderer.RdWorld(currentWorld, player, 2); + rdWorld = new RdWorld(currentWorld, player, 1); } private async void Initialize() @@ -164,16 +174,18 @@ public override void Start() Initialize(); } + public override void Cancel() + { + Core.Services.Get("Game.TaskDispatcher").Reset(); + } + private static async Task RequestWorld() { // TODO: change this if (IsClient()) { var worldIds = await Client.GetAvailableWorldId.Call(); - if (worldIds.Length == 0) - { - throw new Exception("The server didn't response with any valid worlds."); - } + if (worldIds.Length == 0) throw new Exception("The server didn't response with any valid worlds."); var worldInfo = await Client.GetWorldInfo.Call(worldIds[0]); @@ -197,16 +209,5 @@ public override void Update() { Singleton.Instance.TaskDispatcher.ProcessRenderTasks(); } - - // Local server - private Server server; - - // Player - private Player player; - - // Current world - private World currentWorld; - - private Renderer.RdWorld rdWorld; } -} +} \ No newline at end of file diff --git a/NEWorld/NEWorld.csproj b/NEWorld/NEWorld.csproj index 7a290e9..9ccc28f 100644 --- a/NEWorld/NEWorld.csproj +++ b/NEWorld/NEWorld.csproj @@ -9,14 +9,10 @@ true - - - - - - - - + + + + diff --git a/NEWorld/Renderer/RdChunk.cs b/NEWorld/Renderer/RdChunk.cs index 09f5e71..29983f1 100644 --- a/NEWorld/Renderer/RdChunk.cs +++ b/NEWorld/Renderer/RdChunk.cs @@ -40,23 +40,24 @@ public class ChunkRenderData */ public void Generate(Chunk chunk) { - var vaOpacity = new VertexBuilder(262144 * (2 + 1 + 3)); - var vaTranslucent = new VertexBuilder(262144 * (2 + 1 + 3)); - var tmp = new Int3(); - for (tmp.X = 0; tmp.X < Chunk.Size; ++tmp.X) - for (tmp.Y = 0; tmp.Y < Chunk.Size; ++tmp.Y) - for (tmp.Z = 0; tmp.Z < Chunk.Size; ++tmp.Z) - { - var b = chunk[tmp]; - var target = Blocks.Index[b.Id].IsTranslucent ? vaTranslucent : vaOpacity; - BlockRenderers.Render(target, b.Id, chunk, tmp); - } + using (VertexBuilder vaOpacity = new VertexBuilder(262144 * (2 + 3)), vaTranslucent = new VertexBuilder(262144 * (2 + 3))) { + var tmp = new Int3(); + for (tmp.X = 0; tmp.X < Chunk.Size; ++tmp.X) + for (tmp.Y = 0; tmp.Y < Chunk.Size; ++tmp.Y) + for (tmp.Z = 0; tmp.Z < Chunk.Size; ++tmp.Z) + { + var b = chunk[tmp]; + var target = Blocks.Index[b.Id].IsTranslucent ? vaTranslucent : vaOpacity; + BlockRenderers.Render(target, b.Id, chunk, tmp); + } - var mesh0 = vaOpacity.Dump(); - var mesh1 = vaTranslucent.Dump(); - Model = (mesh0 != null && mesh1 != null) ? new Model {new MaterialInstance(Context.Material)} : null; - if (mesh0 != null) Model?.Add(mesh0); - if (mesh1 != null) Model?.Add(mesh1); + var mesh0 = vaOpacity.Dump(); + var mesh1 = vaTranslucent.Dump(); + Model = mesh0 != null && mesh1 != null ? new Model {new MaterialInstance(Context.Material)} : null; + if (mesh0 != null) Model?.Add(mesh0); + if (mesh1 != null) Model?.Add(mesh1); + //if (Model != null) Model.Materials[0].IsShadowCaster = true; + } } } @@ -91,4 +92,4 @@ public void Update(ChunkRenderData data) Entity.Remove(); } } -} +} \ No newline at end of file diff --git a/NEWorld/Renderer/RdWorld.cs b/NEWorld/Renderer/RdWorld.cs index 42b4786..13944fe 100644 --- a/NEWorld/Renderer/RdWorld.cs +++ b/NEWorld/Renderer/RdWorld.cs @@ -18,11 +18,11 @@ // using System; -using Game; -using Game.World; using System.Collections.Generic; using System.Linq; using Core.Utilities; +using Game; +using Game.World; using Xenko.Core.Mathematics; namespace NEWorld.Renderer @@ -31,8 +31,36 @@ public class RdWorld { public const int MaxChunkRenderCount = 4; + // Chunk Renderers + private readonly Dictionary chunkRenderers; + + // Ranges + public readonly int RenderDist; + + private readonly World world; + + public RdWorld(World world, Player player, int renderDistance) + { + this.world = world; + RenderDist = renderDistance; + chunkRenderers = new Dictionary(); + Singleton.Instance.TaskDispatcher.AddRegular(new RenderDetectorTask(this, world.Id, player)); + } + private class RenderDetectorTask : IReadOnlyTask { + private static readonly Int3[] Delta = + { + new Int3(1, 0, 0), new Int3(-1, 0, 0), + new Int3(0, 1, 0), new Int3(0, -1, 0), + new Int3(0, 0, 1), new Int3(0, 0, -1) + }; + + private readonly uint currentWorldId; + private readonly Player player; + + private readonly RdWorld rdWorldRenderer; + public RenderDetectorTask(RdWorld rdWorldRenderer, uint currentWorldId, Player player) { this.rdWorldRenderer = rdWorldRenderer; @@ -54,7 +82,6 @@ public void Task(ChunkService cs) var chunkPosition = chunk.Position; // In render range, pending to render if (chunk.IsUpdated && ChebyshevDistance(chunkpos, chunkPosition) <= rdWorldRenderer.RenderDist) - { if (NeighbourChunkLoadCheck(world, chunkPosition)) { // TODO: maybe build a VA pool can speed this up. @@ -64,30 +91,29 @@ public void Task(ChunkService cs) rdWorldRenderer.chunkRenderers)); if (++counter == MaxChunkRenderCount) break; } - } } } - - // TODO: Remove Type1 Clone - private static int ChebyshevDistance(Int3 l, Int3 r) => Math.Max(Math.Max(Math.Abs(l.X - r.X), Math.Abs(l.Y - r.Y)), Math.Abs(l.Z - r.Z)); - private static readonly Int3[] Delta = + // TODO: Remove Type1 Clone + private static int ChebyshevDistance(Int3 l, Int3 r) { - new Int3(1, 0, 0), new Int3(-1, 0, 0), - new Int3(0, 1, 0), new Int3(0, -1, 0), - new Int3(0, 0, 1), new Int3(0, 0, -1) - }; - - private static bool NeighbourChunkLoadCheck(World world, Int3 pos) => - Delta.All(p => world.IsChunkLoaded(pos + p)); + return Math.Max(Math.Max(Math.Abs(l.X - r.X), Math.Abs(l.Y - r.Y)), Math.Abs(l.Z - r.Z)); + } - private readonly RdWorld rdWorldRenderer; - private readonly uint currentWorldId; - private readonly Player player; + private static bool NeighbourChunkLoadCheck(World world, Int3 pos) + { + return Delta.All(p => world.IsChunkLoaded(pos + p)); + } } private class VboGenerateTask : IRenderTask { + private readonly ChunkRenderData chunkRenderData; + private readonly Dictionary chunkRenderers; + private readonly Int3 position; + + private readonly World world; + public VboGenerateTask(World world, Int3 position, ChunkRenderData crd, Dictionary chunkRenderers) { @@ -112,27 +138,6 @@ public void Task(ChunkService srv) chunkRenderers.Add(position, renderer); } } - - private readonly World world; - private readonly Int3 position; - private readonly ChunkRenderData chunkRenderData; - private readonly Dictionary chunkRenderers; - } - - public RdWorld(World world, Player player, int renderDistance) - { - this.world = world; - RenderDist = renderDistance; - chunkRenderers = new Dictionary(); - Singleton.Instance.TaskDispatcher.AddRegular(new RenderDetectorTask(this, world.Id, player)); } - - private readonly World world; - - // Ranges - public readonly int RenderDist; - - // Chunk Renderers - private readonly Dictionary chunkRenderers; } -} +} \ No newline at end of file diff --git a/NEWorld/Renderer/VertexBuilder.cs b/NEWorld/Renderer/VertexBuilder.cs index d857989..83f1f8c 100644 --- a/NEWorld/Renderer/VertexBuilder.cs +++ b/NEWorld/Renderer/VertexBuilder.cs @@ -1,17 +1,30 @@ using System; using System.Runtime.InteropServices; using Game.Terrain; +using Xenko.Core.Mathematics; using Xenko.Graphics; using Xenko.Rendering; using Buffer = Xenko.Graphics.Buffer; namespace NEWorld.Renderer { - public class VertexBuilder : IVertexBuilder + public class VertexBuilder : IVertexBuilder, IDisposable { - public VertexBuilder(int size) => data = Marshal.AllocHGlobal(size * sizeof(float)); + private readonly IntPtr data; + private int count; + + private int size; - ~VertexBuilder() => Marshal.FreeHGlobal(data); + public VertexBuilder(int size) + { + data = Marshal.AllocHGlobal(size * sizeof(float)); + } + + public void Dispose() + { + ReleaseUnmanagedResources(); + GC.SuppressFinalize(this); + } public void AddPrimitive(int verts, params float[] data) { @@ -20,12 +33,22 @@ public void AddPrimitive(int verts, params float[] data) size += data.Length; } + ~VertexBuilder() + { + ReleaseUnmanagedResources(); + } + + public void Rect(Int3 position, int face, Int2 tex, int rotation) + { + throw new NotImplementedException(); + } + public Mesh Dump() { return count > 0 ? new Mesh { - Draw = new MeshDraw() + Draw = new MeshDraw { DrawCount = count / 2 * 3, IndexBuffer = Context.IndexBuffer, @@ -33,7 +56,9 @@ public Mesh Dump() StartLocation = 0, VertexBuffers = new[] { - new VertexBufferBinding(Buffer.Vertex.New(Context.Game.GraphicsDevice, new DataPointer(data, size * sizeof(float))), + new VertexBufferBinding( + Buffer.Vertex.New(Context.Game.GraphicsDevice, + new DataPointer(data, size * sizeof(float))), Context.VertexLayout, count) } }, @@ -42,9 +67,9 @@ public Mesh Dump() : null; } - private int size; - private int count; - private readonly IntPtr data; + private void ReleaseUnmanagedResources() + { + Marshal.FreeHGlobal(data); + } } - } \ No newline at end of file diff --git a/NEWorldShell/Cli.cs b/NEWorldShell/Cli.cs index c725643..c41e767 100644 --- a/NEWorldShell/Cli.cs +++ b/NEWorldShell/Cli.cs @@ -28,6 +28,8 @@ namespace NEWorldShell { internal class ServerCommandLine { + private readonly CommandManager _commands; + public ServerCommandLine() { _commands = new CommandManager(); @@ -46,10 +48,8 @@ private void InitBuiltinCommands() { var helpString = "\nAvailable commands:\n"; foreach (var command in _commands.GetCommandMap()) - { helpString += command.Key + " - " + command.Value.Key.Author + " : " + command.Value.Key.Help + "\n"; - } return new CommandExecuteStat(true, helpString); }); @@ -104,7 +104,5 @@ private void InitBuiltinCommands() return new CommandExecuteStat(true, ""); }); } - - private readonly CommandManager _commands; } } \ No newline at end of file diff --git a/NEWorldShell/Command.cs b/NEWorldShell/Command.cs index f39395d..0d326d7 100644 --- a/NEWorldShell/Command.cs +++ b/NEWorldShell/Command.cs @@ -21,44 +21,47 @@ using System.Collections.Generic; using System.Linq; using System.Threading; +using Core; namespace NEWorldShell { public class CommandExecuteStat { + public readonly string Info; + + public bool Success; + public CommandExecuteStat(bool s, string i) { Success = s; Info = i; } - - public bool Success; - public readonly string Info; } public class Command { + public readonly List Args; + + public readonly string Name; + public Command(string rawString) { Args = rawString.Split(' ').ToList(); Name = Args.Count != 0 ? Args[0] : ""; if (Args.Count != 0) Args.RemoveAt(0); } - - public readonly string Name; - public readonly List Args; } public class CommandInfo { + public string Author; + public string Help; + public CommandInfo(string a, string h) { Author = a; Help = h; } - - public string Author; - public string Help; } @@ -66,6 +69,14 @@ public class CommandManager { public delegate CommandExecuteStat CommandHandleFunction(Command exec); + private readonly Dictionary> _commandMap; + + private readonly Thread _mainloop; + + private bool _threadRunning = true; + + private bool _waitingForInput; + public CommandManager() { _commandMap = new Dictionary>(); @@ -77,13 +88,9 @@ public CommandManager() { _threadRunning = false; if (!_waitingForInput) - { _mainloop.Join(); - } else - { _mainloop.Abort(); - } } public void InputLoop() @@ -95,11 +102,14 @@ public void InputLoop() _waitingForInput = false; var result = HandleCommand(new Command(input)); if (result.Info != "") - Core.LogPort.Debug(result.Info); + LogPort.Debug(result.Info); } } - public Dictionary> GetCommandMap() => _commandMap; + public Dictionary> GetCommandMap() + { + return _commandMap; + } public void SetRunningStatus(bool s) { @@ -118,13 +128,5 @@ private CommandExecuteStat HandleCommand(Command cmd) ? result.Value(cmd) : new CommandExecuteStat(false, "Command not exists, type help for available commands."); } - - private readonly Thread _mainloop; - - private bool _threadRunning = true; - - private bool _waitingForInput; - - private readonly Dictionary> _commandMap; } } \ No newline at end of file diff --git a/NEWorldShell/NEWorldShell.csproj b/NEWorldShell/NEWorldShell.csproj index 015cae8..66fb51a 100644 --- a/NEWorldShell/NEWorldShell.csproj +++ b/NEWorldShell/NEWorldShell.csproj @@ -9,8 +9,9 @@ Properties NEWorldShell NEWorldShell - v4.7.1 + v4.6.1 512 + AnyCPU diff --git a/NEWorldShell/Program.cs b/NEWorldShell/Program.cs index 98df9cb..cd80a81 100644 --- a/NEWorldShell/Program.cs +++ b/NEWorldShell/Program.cs @@ -35,4 +35,4 @@ public static void Main(string[] args) cli.Start(); } } -} +} \ No newline at end of file From 52407a500047d45317ddae46375fb039a839eff1 Mon Sep 17 00:00:00 2001 From: DWVoid Date: Fri, 1 Feb 2019 00:16:38 +0800 Subject: [PATCH 05/23] Shading VA Compress --- Game/Terrain/Block.cs | 95 +++---------------- NEWorld/Assets/MainScene.xkscene | 21 +--- NEWorld/Assets/Sphere.xkpromodel | 12 --- .../{Material.xkmat => VoxelMaterial.xkmat} | 8 +- NEWorld/Effects/VertexTextureTerrain.xksl | 24 ++++- NEWorld/MainScript.cs | 4 +- NEWorld/Renderer/RdChunk.cs | 3 +- NEWorld/Renderer/RdTextures.cs | 4 +- NEWorld/Renderer/VertexBuilder.cs | 79 +++++++++------ 9 files changed, 96 insertions(+), 154 deletions(-) delete mode 100644 NEWorld/Assets/Sphere.xkpromodel rename NEWorld/Assets/{Material.xkmat => VoxelMaterial.xkmat} (54%) diff --git a/Game/Terrain/Block.cs b/Game/Terrain/Block.cs index 881ad49..7076b86 100644 --- a/Game/Terrain/Block.cs +++ b/Game/Terrain/Block.cs @@ -19,15 +19,14 @@ using System.Collections.Generic; using Game.World; -using Xenko.Core.Annotations; using Xenko.Core.Mathematics; namespace Game.Terrain { - public unsafe struct BlockTexCoord + public struct BlockTexCoord { public uint Pos; - public fixed float D[4]; + public Int2 Tex; } public interface IBlockRenderer @@ -38,14 +37,13 @@ public interface IBlockRenderer public interface IVertexBuilder { - //void Rect(Int3 position, int face, Int2 tex, int rotation) - void AddPrimitive(int verts, params float[] data); + void Rect(Int3 position, Int2 tex, uint face, int rotation, uint shade); } public interface IBlockTextures { uint Add(string path); - unsafe void GetTexturePos(float* pos, uint id); + void GetTexturePos(ref BlockTexCoord pos); } public class DefaultBlockRenderer : IBlockRenderer @@ -59,16 +57,13 @@ public DefaultBlockRenderer(uint[] data) tex[i].Pos = data[i]; } - public unsafe void FlushTexture(IBlockTextures textures) + public void FlushTexture(IBlockTextures textures) { for (var i = 0; i < 6; ++i) - fixed (float* tex = this.tex[0].D) - { - textures.GetTexturePos(tex, this.tex[i].Pos); - } + textures.GetTexturePos(ref tex[i]); } - public unsafe void Render(IVertexBuilder target, Chunk chunk, Int3 pos) + public void Render(IVertexBuilder target, Chunk chunk, Int3 pos) { var worldpos = chunk.Position * Chunk.Size + pos; var curr = chunk[pos]; @@ -93,78 +88,10 @@ public unsafe void Render(IVertexBuilder target, Chunk chunk, Int3 pos) ? chunk.World.GetBlock(new Int3(worldpos.X, worldpos.Y, worldpos.Z - 1)) : chunk[new Int3(pos.X, pos.Y, pos.Z - 1)] }; - // Data: float2 tex; float lightcoeff; float3 normal, - // Right - if (AdjacentTest(curr, neighbors[0])) - fixed (float* tex = this.tex[0].D) - { - target.AddPrimitive(4, - tex[0], tex[1], pos.X + 1.0f, pos.Y + 1.0f, pos.Z + 1.0f, - tex[0], tex[3], pos.X + 1.0f, pos.Y + 0.0f, pos.Z + 1.0f, - tex[2], tex[3], pos.X + 1.0f, pos.Y + 0.0f, pos.Z + 0.0f, - tex[2], tex[1], pos.X + 1.0f, pos.Y + 1.0f, pos.Z + 0.0f - ); - } - - // Left - if (AdjacentTest(curr, neighbors[1])) - fixed (float* tex = this.tex[1].D) - { - target.AddPrimitive(4, - tex[0], tex[1], pos.X + 0.0f, pos.Y + 1.0f, pos.Z + 0.0f, - tex[0], tex[3], pos.X + 0.0f, pos.Y + 0.0f, pos.Z + 0.0f, - tex[2], tex[3], pos.X + 0.0f, pos.Y + 0.0f, pos.Z + 1.0f, - tex[2], tex[1], pos.X + 0.0f, pos.Y + 1.0f, pos.Z + 1.0f - ); - } - - // Top - if (AdjacentTest(curr, neighbors[2])) - fixed (float* tex = this.tex[2].D) - { - target.AddPrimitive(4, - tex[0], tex[1], pos.X + 0.0f, pos.Y + 1.0f, pos.Z + 0.0f, - tex[0], tex[3], pos.X + 0.0f, pos.Y + 1.0f, pos.Z + 1.0f, - tex[2], tex[3], pos.X + 1.0f, pos.Y + 1.0f, pos.Z + 1.0f, - tex[2], tex[1], pos.X + 1.0f, pos.Y + 1.0f, pos.Z + 0.0f - ); - } - - // Bottom - if (AdjacentTest(curr, neighbors[3])) - fixed (float* tex = this.tex[3].D) - { - target.AddPrimitive(4, - tex[0], tex[1],pos.X + 0.0f, pos.Y + 0.0f, pos.Z + 1.0f, - tex[0], tex[3], pos.X + 0.0f, pos.Y + 0.0f, pos.Z + 0.0f, - tex[2], tex[3], pos.X + 1.0f, pos.Y + 0.0f, pos.Z + 0.0f, - tex[2], tex[1], pos.X + 1.0f, pos.Y + 0.0f, pos.Z + 1.0f - ); - } - - // Front - if (AdjacentTest(curr, neighbors[4])) - fixed (float* tex = this.tex[4].D) - { - target.AddPrimitive(4, - tex[0], tex[1], pos.X + 0.0f, pos.Y + 1.0f, pos.Z + 1.0f, - tex[0], tex[3], pos.X + 0.0f, pos.Y + 0.0f, pos.Z + 1.0f, - tex[2], tex[3], pos.X + 1.0f, pos.Y + 0.0f, pos.Z + 1.0f, - tex[2], tex[1], pos.X + 1.0f, pos.Y + 1.0f, pos.Z + 1.0f - ); - } - - // Back - if (AdjacentTest(curr, neighbors[5])) - fixed (float* tex = this.tex[5].D) - { - target.AddPrimitive(4, - tex[0], tex[1], pos.X + 1.0f, pos.Y + 1.0f, pos.Z + 0.0f, - tex[0], tex[3], pos.X + 1.0f, pos.Y + 0.0f, pos.Z + 0.0f, - tex[2], tex[3], pos.X + 0.0f, pos.Y + 0.0f, pos.Z + 0.0f, - tex[2], tex[1], pos.X + 0.0f, pos.Y + 1.0f, pos.Z + 0.0f - ); - } + // Right Left Top Bottom Front Back + for (uint i = 0; i < 6; ++i) + if (AdjacentTest(curr, neighbors[i])) + target.Rect(pos, tex[i].Tex, i, 0, 15); } private static bool AdjacentTest(BlockData a, BlockData b) diff --git a/NEWorld/Assets/MainScene.xkscene b/NEWorld/Assets/MainScene.xkscene index c57f07a..ab2d506 100644 --- a/NEWorld/Assets/MainScene.xkscene +++ b/NEWorld/Assets/MainScene.xkscene @@ -10,7 +10,6 @@ Hierarchy: - ref!! 1cc17873-cca9-48fe-a61f-a1d061e26959 - ref!! b1058700-11a7-4296-bb63-2fefdb5f28b7 - ref!! b1588acf-0fcd-4377-bf5d-6c81dcf34484 - - ref!! 64e3b5a5-d684-4f59-821f-a3ef18dae1d2 Parts: - Entity: Id: 1cc17873-cca9-48fe-a61f-a1d061e26959 @@ -18,8 +17,8 @@ Hierarchy: Components: 1fd11c5daf8481b885d448424af7cbe3: !TransformComponent Id: 2e13dd0e-ce0b-4194-87b1-355fb6805b1a - Position: {X: 0.0, Y: 128.0, Z: 0.0} - Rotation: {X: -0.707106769, Y: 0.0, Z: 0.0, W: 0.707106769} + Position: {X: -60.0, Y: 100.0, Z: 0.0} + Rotation: {X: -0.5, Y: 0.0, Z: 0.0, W: 0.8660254} Scale: {X: 1.0, Y: 1.0, Z: 1.0} Children: {} e709a18ae6b4e34290801582f4038c7d: !LightComponent @@ -37,20 +36,6 @@ Hierarchy: PartitionMode: !LightDirectionalShadowMap.PartitionLogarithmic {} ComputeTransmittance: false BiasParameters: {} - - Entity: - Id: 64e3b5a5-d684-4f59-821f-a3ef18dae1d2 - Name: Sphere - Components: - d72bc5de5518d27be82c2877ec8fe400: !TransformComponent - Id: fdbbe38c-4c4f-4d3b-b15b-85c332d2ba35 - Position: {X: 1.60677338, Y: -4.76837158E-07, Z: 0.8989582} - Rotation: {X: 0.0, Y: 0.0, Z: 0.0, W: 1.0} - Scale: {X: 1.0, Y: 1.0, Z: 1.0} - Children: {} - 9895f39d789515c18b0ad97b68a13e41: !ModelComponent - Id: 2cc6a1ac-dfd1-48f9-9f81-1f011530c92a - Model: e393c514-dbc9-4623-ad9e-3da0bb951a95:Sphere - Materials: {} - Entity: Id: 8d346802-8fe6-4d18-957c-c6dd8404ea84 Name: Camera @@ -99,4 +84,4 @@ Hierarchy: Children: {} d80b4d8f8e4c7517fb33931ae02a1875: !NEWorld.MainScript,NEWorld Id: ad46feb5-e564-418b-8912-d79cfbc7a12d - Material: a0b217f1-284e-4307-abd6-b42f56bedff1:Material + Material: 9751d0fa-3d67-4760-ac85-9d209a2d12cb:VoxelMaterial diff --git a/NEWorld/Assets/Sphere.xkpromodel b/NEWorld/Assets/Sphere.xkpromodel deleted file mode 100644 index ea4bd81..0000000 --- a/NEWorld/Assets/Sphere.xkpromodel +++ /dev/null @@ -1,12 +0,0 @@ -!ProceduralModelAsset -Id: e393c514-dbc9-4623-ad9e-3da0bb951a95 -SerializedVersion: {Xenko: 2.0.0.0} -Tags: [] -Type: !SphereProceduralModel - Radius: 1.0 - Tessellation: 8 - Scale: {X: 1.0, Y: 1.0, Z: 1.0} - UvScale: {X: 1.0, Y: 1.0} - LocalOffset: {X: 0.0, Y: 0.0, Z: 0.0} - MaterialInstance: - Material: a0b217f1-284e-4307-abd6-b42f56bedff1:Material diff --git a/NEWorld/Assets/Material.xkmat b/NEWorld/Assets/VoxelMaterial.xkmat similarity index 54% rename from NEWorld/Assets/Material.xkmat rename to NEWorld/Assets/VoxelMaterial.xkmat index 7ea36b7..5f94f8d 100644 --- a/NEWorld/Assets/Material.xkmat +++ b/NEWorld/Assets/VoxelMaterial.xkmat @@ -1,11 +1,13 @@ !MaterialAsset -Id: a0b217f1-284e-4307-abd6-b42f56bedff1 +Id: 9751d0fa-3d67-4760-ac85-9d209a2d12cb SerializedVersion: {Xenko: 2.0.0.0} Tags: [] Attributes: Diffuse: !MaterialDiffuseMapFeature - DiffuseMap: !ComputeColor - Value: {R: 1.0, G: 1.0, B: 1.0, A: 1.0} + DiffuseMap: !ComputeShaderClassColor + MixinReference: VertexTextureTerrain + Generics: {} + CompositionNodes: {} DiffuseModel: !MaterialDiffuseLambertModelFeature {} Overrides: UVScale: {X: 1.0, Y: 1.0} diff --git a/NEWorld/Effects/VertexTextureTerrain.xksl b/NEWorld/Effects/VertexTextureTerrain.xksl index 77aaa32..685b40a 100644 --- a/NEWorld/Effects/VertexTextureTerrain.xksl +++ b/NEWorld/Effects/VertexTextureTerrain.xksl @@ -1,4 +1,4 @@ -shader VertexTextureTerrain : ComputeColor +shader VertexTextureTerrain : ComputeColor, TransformationBase { SamplerState MeshTextureSampler { @@ -7,10 +7,30 @@ AddressV = Wrap; }; // stage Texture2D Almg; + stage stream uint2 Pack: COLOR0; + + stage stream float4 Vertex : POSITION; stage stream float2 TexCoord : TEXCOORD0; + stage stream float3 Normal : NORMAL; + stage stream float Shade; + + override stage void VSMain() + { + uint high = streams.Pack.x, low = streams.Pack.y; + streams.Vertex = float4((high >> 16) & 0xFF, (high >> 8) & 0xFF, high & 0xFF, 1); + streams.TexCoord = float2((low >> 8) & 0xFF, low & 0xFF); + streams.Shade = ((float)(((high >> 24) & 0xFF) + 1)) * 0.0625f; + static const float3 Normals[6] = { + float3(1, 0, 0), float3(-1, 0, 0), + float3(0, 1, 0), float3(0, -1, 0), + float3(0, 0, 1), float3(0, 0, -1), + }; + streams.Normal = Normals[(low >> 16) & 0xFF]; + base.VSMain(); + } override float4 Compute() { - return float4(1.0, 1.0, 1.0, 1.0);//Almg.Sample(MeshTextureSampler, streams.TexCoord); + return float4(streams.Shade, streams.Shade, streams.Shade, 1.0f);//Almg.Sample(MeshTextureSampler, streams.TexCoord); } }; diff --git a/NEWorld/MainScript.cs b/NEWorld/MainScript.cs index 7ee8b7b..406c4da 100644 --- a/NEWorld/MainScript.cs +++ b/NEWorld/MainScript.cs @@ -42,7 +42,7 @@ public static class Context private static IGame _game; public static readonly VertexDeclaration VertexLayout = new VertexDeclaration( - VertexElement.TextureCoordinate(), VertexElement.Position() + VertexElement.Color(PixelFormat.R32G32_UInt) ); public static IGame Game @@ -155,7 +155,7 @@ private async Task EnterCurrentWorld() private void StartTerrainRenderService() { - rdWorld = new RdWorld(currentWorld, player, 1); + rdWorld = new RdWorld(currentWorld, player, 4); } private async void Initialize() diff --git a/NEWorld/Renderer/RdChunk.cs b/NEWorld/Renderer/RdChunk.cs index 29983f1..2240335 100644 --- a/NEWorld/Renderer/RdChunk.cs +++ b/NEWorld/Renderer/RdChunk.cs @@ -40,7 +40,7 @@ public class ChunkRenderData */ public void Generate(Chunk chunk) { - using (VertexBuilder vaOpacity = new VertexBuilder(262144 * (2 + 3)), vaTranslucent = new VertexBuilder(262144 * (2 + 3))) { + using (VertexBuilder vaOpacity = new VertexBuilder(262144), vaTranslucent = new VertexBuilder(262144)) { var tmp = new Int3(); for (tmp.X = 0; tmp.X < Chunk.Size; ++tmp.X) for (tmp.Y = 0; tmp.Y < Chunk.Size; ++tmp.Y) @@ -56,7 +56,6 @@ public void Generate(Chunk chunk) Model = mesh0 != null && mesh1 != null ? new Model {new MaterialInstance(Context.Material)} : null; if (mesh0 != null) Model?.Add(mesh0); if (mesh1 != null) Model?.Add(mesh1); - //if (Model != null) Model.Materials[0].IsShadowCaster = true; } } } diff --git a/NEWorld/Renderer/RdTextures.cs b/NEWorld/Renderer/RdTextures.cs index 15c39c7..fc80ff9 100644 --- a/NEWorld/Renderer/RdTextures.cs +++ b/NEWorld/Renderer/RdTextures.cs @@ -11,8 +11,8 @@ public uint Add(string path) return 0; } - public unsafe void GetTexturePos(float* pos, uint id) + public void GetTexturePos(ref BlockTexCoord pos) { - } + } } } \ No newline at end of file diff --git a/NEWorld/Renderer/VertexBuilder.cs b/NEWorld/Renderer/VertexBuilder.cs index 83f1f8c..a7fa7b4 100644 --- a/NEWorld/Renderer/VertexBuilder.cs +++ b/NEWorld/Renderer/VertexBuilder.cs @@ -10,14 +10,27 @@ namespace NEWorld.Renderer { public class VertexBuilder : IVertexBuilder, IDisposable { + private static readonly uint[,] Rotation = + { + {0x0000, 0x0001, 0x0101, 0x0100}, {0x0001, 0x0101, 0x0100, 0x0000}, + {0x0101, 0x0100, 0x0000, 0x0001}, {0x0100, 0x0000, 0x0001, 0x0101} + }; + + private static readonly uint[,] Faces = + { + {0x010101, 0x010001, 0x010000, 0x010100}, {0x000100, 0x000000, 0x000001, 0x000101}, + {0x000100, 0x000101, 0x010101, 0x010100}, {0x000001, 0x000000, 0x010000, 0x010001}, + {0x000101, 0x000001, 0x010001, 0x010101}, {0x010100, 0x010000, 0x000000, 0x000100} + }; + private readonly IntPtr data; private int count; + private unsafe uint* view; - private int size; - - public VertexBuilder(int size) + public unsafe VertexBuilder(int size) { - data = Marshal.AllocHGlobal(size * sizeof(float)); + data = Marshal.AllocHGlobal(size * 8); + view = (uint*) data.ToPointer(); } public void Dispose() @@ -26,11 +39,17 @@ public void Dispose() GC.SuppressFinalize(this); } - public void AddPrimitive(int verts, params float[] data) + public unsafe void Rect(Int3 position, Int2 tex, uint face, int rotation, uint shade) { - count += verts; - Marshal.Copy(data, 0, this.data + size * sizeof(float), data.Length); - size += data.Length; + var high = (shade << 24) | (uint) ((position.X << 16) | (position.Y << 8) | position.Z); + var low = (face << 16) | (uint) ((tex.X << 8) | tex.Y); + for (var i = 0; i < 4; ++i) + { + *view++ = high + Faces[face, i]; + *view++ = low + Rotation[rotation, i]; + } + + count += 4; } ~VertexBuilder() @@ -38,33 +57,35 @@ public void AddPrimitive(int verts, params float[] data) ReleaseUnmanagedResources(); } - public void Rect(Int3 position, int face, Int2 tex, int rotation) + public Mesh Dump() { - throw new NotImplementedException(); + return count > 0 ? CreateMesh() : null; } - public Mesh Dump() + private Mesh CreateMesh() { - return count > 0 - ? new Mesh + return new Mesh + { + Draw = new MeshDraw { - Draw = new MeshDraw + DrawCount = count / 2 * 3, + IndexBuffer = Context.IndexBuffer, + PrimitiveType = PrimitiveType.TriangleList, + StartLocation = 0, + VertexBuffers = new[] { - DrawCount = count / 2 * 3, - IndexBuffer = Context.IndexBuffer, - PrimitiveType = PrimitiveType.TriangleList, - StartLocation = 0, - VertexBuffers = new[] - { - new VertexBufferBinding( - Buffer.Vertex.New(Context.Game.GraphicsDevice, - new DataPointer(data, size * sizeof(float))), - Context.VertexLayout, count) - } - }, - MaterialIndex = 0 - } - : null; + CreateVertexBuffer() + } + }, + MaterialIndex = 0 + }; + } + + private VertexBufferBinding CreateVertexBuffer() + { + return new VertexBufferBinding( + Buffer.Vertex.New(Context.Game.GraphicsDevice, new DataPointer(data, count * 32)), Context.VertexLayout, + count); } private void ReleaseUnmanagedResources() From eead948ae30d8410848d834dc02f17064c631437 Mon Sep 17 00:00:00 2001 From: DWVoid Date: Fri, 1 Feb 2019 00:58:18 +0800 Subject: [PATCH 06/23] Publish Block Texture Assets --- Core/Path.cs | 5 ----- Game/Terrain/Block.cs | 2 +- Main/Assets/Textures/Blocks/Bedrock.png | Bin 0 -> 2493 bytes Main/Assets/Textures/Blocks/Bedrock.xktex | 7 +++++++ Main/Assets/Textures/Blocks/Cement.png | Bin 0 -> 1217 bytes Main/Assets/Textures/Blocks/Cement.xktex | 7 +++++++ Main/Assets/Textures/Blocks/Coal.png | Bin 0 -> 1489 bytes Main/Assets/Textures/Blocks/Coal.xktex | 7 +++++++ Main/Assets/Textures/Blocks/Dirt.png | Bin 0 -> 1722 bytes Main/Assets/Textures/Blocks/Dirt.xktex | 7 +++++++ Main/Assets/Textures/Blocks/Glowstone.png | Bin 0 -> 2688 bytes Main/Assets/Textures/Blocks/Glowstone.xktex | 7 +++++++ Main/Assets/Textures/Blocks/GrassRound.png | Bin 0 -> 2055 bytes Main/Assets/Textures/Blocks/GrassRound.xktex | 7 +++++++ Main/Assets/Textures/Blocks/GrassTop.png | Bin 0 -> 1876 bytes Main/Assets/Textures/Blocks/GrassTop.xktex | 7 +++++++ Main/Assets/Textures/Blocks/Iron.png | Bin 0 -> 1026 bytes Main/Assets/Textures/Blocks/Iron.xktex | 7 +++++++ Main/Assets/Textures/Blocks/Null.png | Bin 0 -> 1393 bytes Main/Assets/Textures/Blocks/Null.xktex | 7 +++++++ Main/Assets/Textures/Blocks/Plank.png | Bin 0 -> 1308 bytes Main/Assets/Textures/Blocks/Plank.xktex | 7 +++++++ Main/Assets/Textures/Blocks/Rock.png | Bin 0 -> 816 bytes Main/Assets/Textures/Blocks/Rock.xktex | 7 +++++++ Main/Assets/Textures/Blocks/Sand.png | Bin 0 -> 2272 bytes Main/Assets/Textures/Blocks/Sand.xktex | 7 +++++++ Main/Assets/Textures/Blocks/Stone.png | Bin 0 -> 1935 bytes Main/Assets/Textures/Blocks/Stone.xktex | 7 +++++++ Main/Assets/Textures/Blocks/TrunkRound.png | Bin 0 -> 1040 bytes Main/Assets/Textures/Blocks/TrunkRound.xktex | 7 +++++++ Main/Assets/Textures/Blocks/TrunkTop.png | Bin 0 -> 930 bytes Main/Assets/Textures/Blocks/TrunkTop.xktex | 7 +++++++ Main/Assets/Textures/Blocks/Water.png | Bin 0 -> 1588 bytes Main/Assets/Textures/Blocks/Water.xktex | 7 +++++++ Main/Main.cs | 13 ++++++------- NEWorld.Windows/NEWorld.Windows.xkpkg | 18 +++++++++++++++++- NEWorld/Assets/MainScene.xkscene | 2 +- NEWorld/MainScript.cs | 4 ++++ NEWorld/Renderer/RdTextures.cs | 14 ++++++++++---- 39 files changed, 151 insertions(+), 19 deletions(-) create mode 100644 Main/Assets/Textures/Blocks/Bedrock.png create mode 100644 Main/Assets/Textures/Blocks/Bedrock.xktex create mode 100644 Main/Assets/Textures/Blocks/Cement.png create mode 100644 Main/Assets/Textures/Blocks/Cement.xktex create mode 100644 Main/Assets/Textures/Blocks/Coal.png create mode 100644 Main/Assets/Textures/Blocks/Coal.xktex create mode 100644 Main/Assets/Textures/Blocks/Dirt.png create mode 100644 Main/Assets/Textures/Blocks/Dirt.xktex create mode 100644 Main/Assets/Textures/Blocks/Glowstone.png create mode 100644 Main/Assets/Textures/Blocks/Glowstone.xktex create mode 100644 Main/Assets/Textures/Blocks/GrassRound.png create mode 100644 Main/Assets/Textures/Blocks/GrassRound.xktex create mode 100644 Main/Assets/Textures/Blocks/GrassTop.png create mode 100644 Main/Assets/Textures/Blocks/GrassTop.xktex create mode 100644 Main/Assets/Textures/Blocks/Iron.png create mode 100644 Main/Assets/Textures/Blocks/Iron.xktex create mode 100644 Main/Assets/Textures/Blocks/Null.png create mode 100644 Main/Assets/Textures/Blocks/Null.xktex create mode 100644 Main/Assets/Textures/Blocks/Plank.png create mode 100644 Main/Assets/Textures/Blocks/Plank.xktex create mode 100644 Main/Assets/Textures/Blocks/Rock.png create mode 100644 Main/Assets/Textures/Blocks/Rock.xktex create mode 100644 Main/Assets/Textures/Blocks/Sand.png create mode 100644 Main/Assets/Textures/Blocks/Sand.xktex create mode 100644 Main/Assets/Textures/Blocks/Stone.png create mode 100644 Main/Assets/Textures/Blocks/Stone.xktex create mode 100644 Main/Assets/Textures/Blocks/TrunkRound.png create mode 100644 Main/Assets/Textures/Blocks/TrunkRound.xktex create mode 100644 Main/Assets/Textures/Blocks/TrunkTop.png create mode 100644 Main/Assets/Textures/Blocks/TrunkTop.xktex create mode 100644 Main/Assets/Textures/Blocks/Water.png create mode 100644 Main/Assets/Textures/Blocks/Water.xktex diff --git a/Core/Path.cs b/Core/Path.cs index f7f0abf..7f44971 100644 --- a/Core/Path.cs +++ b/Core/Path.cs @@ -23,11 +23,6 @@ namespace Core { public static class Path { - public static string Asset(string group) - { - return AppContext.BaseDirectory + "/Assets/" + group + "/"; - } - public static string Modules() { return AppContext.BaseDirectory; diff --git a/Game/Terrain/Block.cs b/Game/Terrain/Block.cs index 7076b86..731ceda 100644 --- a/Game/Terrain/Block.cs +++ b/Game/Terrain/Block.cs @@ -42,7 +42,7 @@ public interface IVertexBuilder public interface IBlockTextures { - uint Add(string path); + uint Add(string assetUri); void GetTexturePos(ref BlockTexCoord pos); } diff --git a/Main/Assets/Textures/Blocks/Bedrock.png b/Main/Assets/Textures/Blocks/Bedrock.png new file mode 100644 index 0000000000000000000000000000000000000000..c450d8a793776faf8bd0d29e154a5f9347bf50fa GIT binary patch literal 2493 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 literal 0 HcmV?d00001 diff --git a/Main/Assets/Textures/Blocks/Coal.xktex b/Main/Assets/Textures/Blocks/Coal.xktex new file mode 100644 index 0000000..a900546 --- /dev/null +++ b/Main/Assets/Textures/Blocks/Coal.xktex @@ -0,0 +1,7 @@ +!Texture +Id: def5376c-d58d-4872-8edc-bfd10cc66ba6 +SerializedVersion: {Xenko: 2.0.0.0} +Tags: [] +Source: !file Coal.png +Type: !ColorTextureType + ColorKeyColor: {R: 255, G: 0, B: 255, A: 255} diff --git a/Main/Assets/Textures/Blocks/Dirt.png b/Main/Assets/Textures/Blocks/Dirt.png new file mode 100644 index 0000000000000000000000000000000000000000..a5756613002008104684c55da57677b258f67a10 GIT binary patch literal 1722 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 literal 0 HcmV?d00001 diff --git a/Main/Assets/Textures/Blocks/GrassRound.xktex b/Main/Assets/Textures/Blocks/GrassRound.xktex new file mode 100644 index 0000000..0e05240 --- /dev/null +++ b/Main/Assets/Textures/Blocks/GrassRound.xktex @@ -0,0 +1,7 @@ +!Texture +Id: c896ecda-99c3-4815-b811-6dcf125c371c +SerializedVersion: {Xenko: 2.0.0.0} +Tags: [] +Source: !file GrassRound.png +Type: !ColorTextureType + ColorKeyColor: {R: 255, G: 0, B: 255, A: 255} diff --git a/Main/Assets/Textures/Blocks/GrassTop.png b/Main/Assets/Textures/Blocks/GrassTop.png new file mode 100644 index 0000000000000000000000000000000000000000..611b2252f7819f4629b29be1d6e23817d7147844 GIT binary patch literal 1876 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 literal 0 HcmV?d00001 diff --git a/Main/Assets/Textures/Blocks/Null.xktex b/Main/Assets/Textures/Blocks/Null.xktex new file mode 100644 index 0000000..a932bb1 --- /dev/null +++ b/Main/Assets/Textures/Blocks/Null.xktex @@ -0,0 +1,7 @@ +!Texture +Id: 758069be-3d4f-4f11-a44c-8b5bbea08cbb +SerializedVersion: {Xenko: 2.0.0.0} +Tags: [] +Source: !file Null.png +Type: !ColorTextureType + ColorKeyColor: {R: 255, G: 0, B: 255, A: 255} diff --git a/Main/Assets/Textures/Blocks/Plank.png b/Main/Assets/Textures/Blocks/Plank.png new file mode 100644 index 0000000000000000000000000000000000000000..5e3aab2f977e82d704d59b48ee0358b0c5b686fd GIT binary patch literal 1308 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 Ef{3QIssI20 literal 0 HcmV?d00001 diff --git a/Main/Assets/Textures/Blocks/TrunkTop.xktex b/Main/Assets/Textures/Blocks/TrunkTop.xktex new file mode 100644 index 0000000..176f240 --- /dev/null +++ b/Main/Assets/Textures/Blocks/TrunkTop.xktex @@ -0,0 +1,7 @@ +!Texture +Id: f6251b49-efcd-451d-aeb9-f422b2c5cd90 +SerializedVersion: {Xenko: 2.0.0.0} +Tags: [] +Source: !file TrunkTop.png +Type: !ColorTextureType + ColorKeyColor: {R: 255, G: 0, B: 255, A: 255} diff --git a/Main/Assets/Textures/Blocks/Water.png b/Main/Assets/Textures/Blocks/Water.png new file mode 100644 index 0000000000000000000000000000000000000000..b6c452d110701a3d412bd0f539ce05fed7781393 GIT binary patch literal 1588 zcmV-42Fv-0P)-j=$Ymi3E@b5R}#~Pt{iKj6@P>G`d0h z*T4V!pJx*=6W%rj0T3ntz@rJeIU)d$z-bQiz_SU=0~P@$LN|wdfQTSWAOapLz-_N? zj?)~&0ucd^008Lwt^fe{!0Q|kW&nVBE)dEM>agc7c-s`_ftc?_2wvTA4#zM@Q^wC@ zfC$)xAx!yx04jt&^6*Us+Jw=BVICj?mf0=CRi9%Gi`~ z3`azw)y>g_;htFwGi+uL06NU^)eXXgCJZnjGgI?~or$1Ks|5EzBYs2oW|-gaNGJ z>bq8bfGaDHK!wjl*fnA6!;_4|7rA^M^g0Ka3GM;mBqI}nZhP|NQ=tL?`p_g$W&h_7 z5h3P+f|Up$0MAXKd~I5sa1W?3h=4vE|9g#9I`hDz$?7((A76c-34^ebhk0OE!RMJ9 z=*+M(8Rj73s}v515JGbEZVu)wfih#bqrxq8ba-G_#&g@DvmDi8KxV2zS7wbd%&|AY zk6kAxVwJ)tib8}wvt*K4Z^5uw`alHeIavz_z=Saa@CaG-I9s;b^0&`^5$jgI zJLB)OF)>o|m#!F~C@Ntg6bWJJiCX7duCD$dzOoWePq>eBp0(VvONT~W$z?})7 z$Cw+HhG)0DZ;Hn>vS&+=@JLb)a~#8P49C$8M6?382t2n;S0eyNEMa&yS>z)CJOk)c zZspE&y}0u=&FE_wj&3-o2Ne7?OEqRYmoDfr>|!r2sjWzB697;qJU7LryjXk=|5k!h zydQhRk6o7y_u&}GAd=h;VTS(W(bnb>z^faM5vwHCrq{yD;plcC5pZKAHpwRZ*26wue1PUuXFslPnhs$(5CjbrJ&5aD!RPFNgyG=cK#DpQphm zx|$3HUN`k)qA9Zp`L4XI7(Ux+w&Cyq;qO0VY6k%9P2iE*@Hsn3i)=O_*C~;2yRK;_ zRWOhFJPmy$GS!kzcxB`sAo(5yGcBZwC*4wAwK9H8Kfe=_XwGsYRqxqkO+C25`sxD_ z`EE^0LbQ^~HR*s%1Vh})I-M*BiCj1=V4j3pzAf}^%JJJ6_S|;(t%Nl%6ncH6CO~B& zR^-&`C+p`JwCOMP}FNOQ+ z2Ja`hZP-;%T2k4+Z4JlpZ})-Fp5B}bm7t95qO<82Umi5 zBJ}gKSYx(u zmJ!R&FE_20{C3y4UravEu_oPf%uA("BlockTextures", out var textures)) return; - var path = Path.Asset("Infinideas.Main") + "blocks/"; uint[] id = { - textures.Add(path + "grass_top.png"), - textures.Add(path + "grass_round.png"), - textures.Add(path + "dirt.png"), - textures.Add(path + "rock.png"), - textures.Add(path + "sand.png"), - textures.Add(path + "water.png") + textures.Add("Textures/Blocks/GrassTop"), + textures.Add("Textures/Blocks/GrassRound"), + textures.Add("Textures/Blocks/Dirt"), + textures.Add("Textures/Blocks/Rock"), + textures.Add("Textures/Blocks/Sand"), + textures.Add("Textures/Blocks/Water") }; var grass = new DefaultBlockRenderer(new[] {id[1], id[1], id[0], id[2], id[1], id[1]}); diff --git a/NEWorld.Windows/NEWorld.Windows.xkpkg b/NEWorld.Windows/NEWorld.Windows.xkpkg index 70f35a9..80a8aa3 100644 --- a/NEWorld.Windows/NEWorld.Windows.xkpkg +++ b/NEWorld.Windows/NEWorld.Windows.xkpkg @@ -14,4 +14,20 @@ OutputGroupDirectories: {} ExplicitFolders: [] Bundles: [] TemplateFolders: [] -RootAssets: [] +RootAssets: + - 0c1e6502-9fe1-4fa6-b1c2-8e67f76f1f4f:Textures/Blocks/Iron + - 266e2127-34ad-4978-ae76-a37dbebf3547:Textures/Blocks/Rock + - 3091e23d-b2af-4698-b2c8-b16af90eaa2c:Textures/Blocks/Water + - 68b96536-d657-46f9-8319-173aa4f4af30:Textures/Blocks/Bedrock + - 6cde71ed-6d9a-4642-a312-d58c02dbb07d:Textures/Blocks/Plank + - 6cff36b6-77c2-47de-b5d1-18465cb77201:Textures/Blocks/Glowstone + - 729c1023-0d45-456b-b5af-9c2c9ef8b87a:Textures/Blocks/Cement + - 758069be-3d4f-4f11-a44c-8b5bbea08cbb:Textures/Blocks/Null + - 872b5b39-8830-41cd-99a3-b1f729f88966:Textures/Blocks/Dirt + - 8950f4b3-c0bf-4ba1-85a7-d008f78b8b99:Textures/Blocks/GrassTop + - bc3db60e-4472-4e7f-b1e8-626b4a0cc336:Textures/Blocks/TrunkRound + - c896ecda-99c3-4815-b811-6dcf125c371c:Textures/Blocks/GrassRound + - def5376c-d58d-4872-8edc-bfd10cc66ba6:Textures/Blocks/Coal + - f35d797b-97bd-4f89-823e-7ce0254f8fd5:Textures/Blocks/Stone + - f432c34d-8800-43a5-8493-ed3507c53aa9:Textures/Blocks/Sand + - f6251b49-efcd-451d-aeb9-f422b2c5cd90:Textures/Blocks/TrunkTop diff --git a/NEWorld/Assets/MainScene.xkscene b/NEWorld/Assets/MainScene.xkscene index ab2d506..a7ae45a 100644 --- a/NEWorld/Assets/MainScene.xkscene +++ b/NEWorld/Assets/MainScene.xkscene @@ -71,7 +71,7 @@ Hierarchy: Children: {} b770b44f804ee2b92e0cf2e21cb516aa: !BackgroundComponent Id: 9a17a387-5b6c-4dde-9185-771f3dd4b737 - Texture: null + Texture: d5bf7b66-7df1-475b-8721-f4755a9c6f96:Skybox texture - Entity: Id: b1588acf-0fcd-4377-bf5d-6c81dcf34484 Name: MainScript diff --git a/NEWorld/MainScript.cs b/NEWorld/MainScript.cs index 406c4da..7970bdf 100644 --- a/NEWorld/MainScript.cs +++ b/NEWorld/MainScript.cs @@ -29,6 +29,7 @@ using NEWorld.Renderer; using Xenko.Core.Diagnostics; using Xenko.Core.Mathematics; +using Xenko.Core.Serialization.Contents; using Xenko.Engine; using Xenko.Games; using Xenko.Graphics; @@ -66,6 +67,8 @@ public static IGame Game public static CommandList CommandList => Game.GraphicsContext.CommandList; public static IndexBufferBinding IndexBuffer { get; private set; } + + public static ContentManager Content { get; set; } } public static class IndexBufferBuilder @@ -111,6 +114,7 @@ private void InitializeModules() private void InitializeContext() { Context.Game = Game; + Context.Content = Content; Context.Material = Material; Context.OperatingScene = Entity.Scene; LogPort.Logger = Log; diff --git a/NEWorld/Renderer/RdTextures.cs b/NEWorld/Renderer/RdTextures.cs index fc80ff9..4f9aae8 100644 --- a/NEWorld/Renderer/RdTextures.cs +++ b/NEWorld/Renderer/RdTextures.cs @@ -1,18 +1,24 @@ -using Core; +using System.Collections.Generic; +using Core; using Game.Terrain; +using Xenko.Graphics; namespace NEWorld.Renderer { [DeclareService("BlockTextures")] public class RdTextures : IBlockTextures { - public uint Add(string path) + public uint Add(string assetUri) { - return 0; + var id = (uint) textures.Count; + textures.Add(Context.Content.Load(assetUri)); + return id; } public void GetTexturePos(ref BlockTexCoord pos) { - } + } + + private List textures = new List(); } } \ No newline at end of file From 6f623bbb6c521652cefbbbbb789b28a51d4da40e Mon Sep 17 00:00:00 2001 From: Cawell-Anki <1056847469@qq.com> Date: Fri, 1 Feb 2019 20:45:08 +0800 Subject: [PATCH 07/23] Fix ChunkRender & WorldGen --- Main/Main.cs | 4 ++-- NEWorld/Renderer/RdChunk.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Main/Main.cs b/Main/Main.cs index 5fbc968..9a15608 100644 --- a/Main/Main.cs +++ b/Main/Main.cs @@ -90,9 +90,9 @@ private static double Noise(int x, int y) private static double InterpolatedNoise(double x, double y) { - var intX = (int) x; + var intX = (int)System.Math.Floor(x); var fractionalX = x - intX; - var intY = (int) y; + var intY = (int)System.Math.Floor(y); var fractionalY = y - intY; var v1 = Noise(intX, intY); var v2 = Noise(intX + 1, intY); diff --git a/NEWorld/Renderer/RdChunk.cs b/NEWorld/Renderer/RdChunk.cs index 2240335..b33bf62 100644 --- a/NEWorld/Renderer/RdChunk.cs +++ b/NEWorld/Renderer/RdChunk.cs @@ -53,7 +53,7 @@ public void Generate(Chunk chunk) var mesh0 = vaOpacity.Dump(); var mesh1 = vaTranslucent.Dump(); - Model = mesh0 != null && mesh1 != null ? new Model {new MaterialInstance(Context.Material)} : null; + Model = mesh0 != null || mesh1 != null ? new Model {new MaterialInstance(Context.Material)} : null; if (mesh0 != null) Model?.Add(mesh0); if (mesh1 != null) Model?.Add(mesh1); } From 409db7efdf2dd47b1df3dcb4f383af50ec089711 Mon Sep 17 00:00:00 2001 From: DWVoid Date: Sun, 3 Feb 2019 01:36:52 +0800 Subject: [PATCH 08/23] Render Function Const Improve --- Game/Network/Protocols.cs | 2 +- Game/Terrain/Block.cs | 59 ++++++++++++++++++++-------------- Game/World/Chunk.cs | 29 +++++++++++++---- Game/World/World.cs | 2 +- Game/World/WorldTasks.cs | 6 ++-- Main/Main.cs | 16 ++++----- NEWorld/Renderer/RdChunk.cs | 25 ++++++++++---- NEWorld/Renderer/RdTextures.cs | 56 ++++++++++++++++++++++++++++++-- 8 files changed, 142 insertions(+), 53 deletions(-) diff --git a/Game/Network/Protocols.cs b/Game/Network/Protocols.cs index b32a553..884de69 100644 --- a/Game/Network/Protocols.cs +++ b/Game/Network/Protocols.cs @@ -63,7 +63,7 @@ private static byte[] Get(uint worldId, Int3 position) Chunk chunkPtr; try { - chunkPtr = world.GetChunk(ref position); + chunkPtr = world.GetChunk(position); } catch (KeyNotFoundException) { diff --git a/Game/Terrain/Block.cs b/Game/Terrain/Block.cs index 731ceda..3fbe336 100644 --- a/Game/Terrain/Block.cs +++ b/Game/Terrain/Block.cs @@ -25,14 +25,26 @@ namespace Game.Terrain { public struct BlockTexCoord { - public uint Pos; + public uint Id; public Int2 Tex; } + public class BlockRenderContext + { + public BlockRenderContext(Chunk current, Chunk[] neighbors) + { + Current = current; + Neighbors = neighbors; + } + + public readonly Chunk Current; + public readonly Chunk[] Neighbors; + } + public interface IBlockRenderer { void FlushTexture(IBlockTextures textures); - void Render(IVertexBuilder target, Chunk chunk, Int3 pos); + void Render(IVertexBuilder target, BlockRenderContext context, Int3 tmp); } public interface IVertexBuilder @@ -54,7 +66,7 @@ public DefaultBlockRenderer(uint[] data) { tex = new BlockTexCoord[6]; for (var i = 0; i < 6; ++i) - tex[i].Pos = data[i]; + tex[i].Id = data[i]; } public void FlushTexture(IBlockTextures textures) @@ -63,34 +75,33 @@ public void FlushTexture(IBlockTextures textures) textures.GetTexturePos(ref tex[i]); } - public void Render(IVertexBuilder target, Chunk chunk, Int3 pos) + public void Render(IVertexBuilder target, BlockRenderContext context, Int3 pos) { - var worldpos = chunk.Position * Chunk.Size + pos; - var curr = chunk[pos]; + var current = context.Current[pos]; BlockData[] neighbors = { - pos.X == Chunk.Size - 1 - ? chunk.World.GetBlock(new Int3(worldpos.X + 1, worldpos.Y, worldpos.Z)) - : chunk[new Int3(pos.X + 1, pos.Y, pos.Z)], + pos.X == Chunk.RowLast + ? context.Neighbors[0][0, pos.Y, pos.Z] + : context.Current[pos.X + 1, pos.Y, pos.Z], pos.X == 0 - ? chunk.World.GetBlock(new Int3(worldpos.X - 1, worldpos.Y, worldpos.Z)) - : chunk[new Int3(pos.X - 1, pos.Y, pos.Z)], - pos.Y == Chunk.Size - 1 - ? chunk.World.GetBlock(new Int3(worldpos.X, worldpos.Y + 1, worldpos.Z)) - : chunk[new Int3(pos.X, pos.Y + 1, pos.Z)], + ? context.Neighbors[1][Chunk.RowLast, pos.Y, pos.Z] + : context.Current[pos.X - 1, pos.Y, pos.Z], + pos.Y == Chunk.RowLast + ? context.Neighbors[2][pos.X, 0, pos.Z] + : context.Current[pos.X, pos.Y + 1, pos.Z], pos.Y == 0 - ? chunk.World.GetBlock(new Int3(worldpos.X, worldpos.Y - 1, worldpos.Z)) - : chunk[new Int3(pos.X, pos.Y - 1, pos.Z)], - pos.Z == Chunk.Size - 1 - ? chunk.World.GetBlock(new Int3(worldpos.X, worldpos.Y, worldpos.Z + 1)) - : chunk[new Int3(pos.X, pos.Y, pos.Z + 1)], + ? context.Neighbors[3][pos.X, Chunk.RowLast, pos.Z] + : context.Current[pos.X, pos.Y - 1, pos.Z], + pos.Z == Chunk.RowLast + ? context.Neighbors[4][pos.X, pos.Y, 0] + : context.Current[pos.X, pos.Y, pos.Z + 1], pos.Z == 0 - ? chunk.World.GetBlock(new Int3(worldpos.X, worldpos.Y, worldpos.Z - 1)) - : chunk[new Int3(pos.X, pos.Y, pos.Z - 1)] + ? context.Neighbors[5][pos.X, pos.Y, Chunk.RowLast] + : context.Current[pos.X, pos.Y, pos.Z - 1] }; // Right Left Top Bottom Front Back for (uint i = 0; i < 6; ++i) - if (AdjacentTest(curr, neighbors[i])) + if (AdjacentTest(current, neighbors[i])) target.Rect(pos, tex[i].Tex, i, 0, 15); } @@ -104,10 +115,10 @@ public static class BlockRenderers { private static readonly List Renderers = new List(); - public static void Render(IVertexBuilder target, int id, Chunk chunk, Int3 pos) + public static void Render(IVertexBuilder target, int id, BlockRenderContext context, Int3 pos) { if (Renderers.Count > 0 && Renderers[id] != null) - Renderers[id].Render(target, chunk, pos); + Renderers[id].Render(target, context, pos); } public static void Add(int pos, IBlockRenderer blockRenderer) diff --git a/Game/World/Chunk.cs b/Game/World/Chunk.cs index 0317463..d9b5916 100644 --- a/Game/World/Chunk.cs +++ b/Game/World/Chunk.cs @@ -28,9 +28,14 @@ public class Chunk { public delegate void Generator(Int3 chunkPos, BlockData[] chunkData, int daylightBrightness); - public const int BlocksSize = 0b1000000000000000; public const int SizeLog2 = 5; - public const int Size = 0b100000; + public const int RowSize = 32; + public const int RowLast = RowSize - 1; + public const int SliceSize = RowSize * RowSize; + public const int CubeSize = SliceSize * RowSize; + public const int BitShiftX = SizeLog2 * 2; + public const int BitShiftY = SizeLog2; + public const int AxisBits = 0b11111; // Chunk size private static bool _chunkGeneratorLoaded; @@ -43,7 +48,7 @@ public Chunk(Int3 position, World world, bool build = true) { Position = position; World = world; - Blocks = new BlockData[BlocksSize]; + Blocks = new BlockData[CubeSize]; if (build) Build(world.DaylightBrightness); } @@ -56,13 +61,23 @@ public Chunk(Int3 position, World world, bool build = true) public World World { get; } public BlockData[] Blocks { get; } + + public BlockData this[int x, int y, int z] + { + get => Blocks[x << BitShiftX | y << BitShiftY | z]; + set + { + Blocks[x << BitShiftX | y << BitShiftY | z] = value; + IsUpdated = true; + } + } public BlockData this[Int3 pos] { - get => Blocks[pos.X * Size * Size + pos.Y * Size + pos.Z]; + get => Blocks[pos.X << BitShiftX | pos.Y << BitShiftY | pos.Z]; set { - Blocks[pos.X * Size * Size + pos.Y * Size + pos.Z] = value; + Blocks[pos.X << BitShiftX | pos.Y << BitShiftY | pos.Z] = value; IsUpdated = true; } } @@ -76,7 +91,7 @@ public static void SetGenerator(Generator gen) } else { - throw new Exception("Chunk Generator Alreadly Loaded"); + throw new Exception("Chunk Generator Already Loaded"); } } @@ -121,7 +136,7 @@ public static Int3 GetPos(Int3 pos) // Convert world position to block coordinate in chunk (one axis) public static int GetBlockAxisPos(int pos) { - return pos & (Chunk.Size - 1); + return pos & Chunk.AxisBits; } // Convert world position to block coordinate in chunk (all axes) diff --git a/Game/World/World.cs b/Game/World/World.cs index 48ef3e2..400805b 100644 --- a/Game/World/World.cs +++ b/Game/World/World.cs @@ -70,7 +70,7 @@ public int GetChunkCount() return Chunks.Count; } - public Chunk GetChunk(ref Int3 chunkPos) + public Chunk GetChunk(Int3 chunkPos) { return Chunks[chunkPos]; } diff --git a/Game/World/WorldTasks.cs b/Game/World/WorldTasks.cs index 3b653ed..c33ff62 100644 --- a/Game/World/WorldTasks.cs +++ b/Game/World/WorldTasks.cs @@ -29,7 +29,7 @@ public partial class World private const int MaxChunkLoadCount = 64, MaxChunkUnloadCount = 64; private static readonly Int3 MiddleOffset = - new Int3(Chunk.Size / 2 - 1, Chunk.Size / 2 - 1, Chunk.Size / 2 - 1); + new Int3(Chunk.RowSize / 2 - 1, Chunk.RowSize / 2 - 1, Chunk.RowSize / 2 - 1); public void RegisterChunkTasks(ChunkService cs, Player player) { @@ -154,7 +154,7 @@ private static void GenerateLoadUnloadList(World world, Int3 centerPos, int load var curPos = chunk.Value.Position; // Out of load range, pending to unload if (ChebyshevDistance(centerCPos, curPos) > loadRange) - unloadList.Insert((curPos * Chunk.Size + MiddleOffset - centerPos).LengthSquared(), + unloadList.Insert((curPos * Chunk.RowSize + MiddleOffset - centerPos).LengthSquared(), chunk.Value); } @@ -165,7 +165,7 @@ private static void GenerateLoadUnloadList(World world, Int3 centerPos, int load var position = new Int3(x, y, z); // In load range, pending to load if (!world.IsChunkLoaded(position)) - loadList.Insert((position * Chunk.Size + MiddleOffset - centerPos).LengthSquared(), + loadList.Insert((position * Chunk.RowSize + MiddleOffset - centerPos).LengthSquared(), position); } } diff --git a/Main/Main.cs b/Main/Main.cs index 9a15608..a0e0cfb 100644 --- a/Main/Main.cs +++ b/Main/Main.cs @@ -118,16 +118,16 @@ private static double PerlinNoise2D(double x, double y) public static void Generator(Int3 pos, BlockData[] blocks, int daylightBrightness) { - for (var x = 0; x < Chunk.Size; x++) - for (var z = 0; z < Chunk.Size; z++) + for (var x = 0; x < Chunk.RowSize; x++) + for (var z = 0; z < Chunk.RowSize; z++) { - var absHeight = (int) PerlinNoise2D((pos.X * Chunk.Size + x) / NoiseScaleX, - (pos.Z * Chunk.Size + z) / NoiseScaleZ) / 2 - 64; - var height = absHeight - pos.Y * Chunk.Size; + var absHeight = (int) PerlinNoise2D((pos.X * Chunk.RowSize + x) / NoiseScaleX, + (pos.Z * Chunk.RowSize + z) / NoiseScaleZ) / 2 - 64; + var height = absHeight - pos.Y * Chunk.RowSize; var underWater = absHeight <= 0; - for (var y = 0; y < Chunk.Size; y++) + for (var y = 0; y < Chunk.RowSize; y++) { - ref var block = ref blocks[x * Chunk.Size * Chunk.Size + y * Chunk.Size + z]; + ref var block = ref blocks[x * Chunk.RowSize * Chunk.RowSize + y * Chunk.RowSize + z]; if (y <= height) { if (y == height) @@ -142,7 +142,7 @@ public static void Generator(Int3 pos, BlockData[] blocks, int daylightBrightnes } else { - block.Id = pos.Y * Chunk.Size + y <= 0 ? _waterId : (ushort) 0; + block.Id = pos.Y * Chunk.RowSize + y <= 0 ? _waterId : (ushort) 0; block.Brightness = (byte) daylightBrightness; block.Data = 0; } diff --git a/NEWorld/Renderer/RdChunk.cs b/NEWorld/Renderer/RdChunk.cs index b33bf62..ff54cc3 100644 --- a/NEWorld/Renderer/RdChunk.cs +++ b/NEWorld/Renderer/RdChunk.cs @@ -42,20 +42,31 @@ public void Generate(Chunk chunk) { using (VertexBuilder vaOpacity = new VertexBuilder(262144), vaTranslucent = new VertexBuilder(262144)) { var tmp = new Int3(); - for (tmp.X = 0; tmp.X < Chunk.Size; ++tmp.X) - for (tmp.Y = 0; tmp.Y < Chunk.Size; ++tmp.Y) - for (tmp.Z = 0; tmp.Z < Chunk.Size; ++tmp.Z) + var pos = chunk.Position; + var world = chunk.World; + var context = new BlockRenderContext(chunk, new [] + { + world.GetChunk(new Int3(pos.X + 1, pos.Y, pos.Z)), + world.GetChunk(new Int3(pos.X - 1, pos.Y, pos.Z)), + world.GetChunk(new Int3(pos.X, pos.Y + 1, pos.Z)), + world.GetChunk(new Int3(pos.X, pos.Y - 1, pos.Z)), + world.GetChunk(new Int3(pos.X, pos.Y, pos.Z + 1)), + world.GetChunk(new Int3(pos.X, pos.Y, pos.Z - 1)) + }); + for (tmp.X = 0; tmp.X < Chunk.RowSize; ++tmp.X) + for (tmp.Y = 0; tmp.Y < Chunk.RowSize; ++tmp.Y) + for (tmp.Z = 0; tmp.Z < Chunk.RowSize; ++tmp.Z) { var b = chunk[tmp]; var target = Blocks.Index[b.Id].IsTranslucent ? vaTranslucent : vaOpacity; - BlockRenderers.Render(target, b.Id, chunk, tmp); + BlockRenderers.Render(target, b.Id, context, tmp); } var mesh0 = vaOpacity.Dump(); var mesh1 = vaTranslucent.Dump(); Model = mesh0 != null || mesh1 != null ? new Model {new MaterialInstance(Context.Material)} : null; - if (mesh0 != null) Model?.Add(mesh0); - if (mesh1 != null) Model?.Add(mesh1); + if (mesh0 != null) Model.Add(mesh0); + if (mesh1 != null) Model.Add(mesh1); } } } @@ -71,7 +82,7 @@ public RdChunk(ChunkRenderData data, Vector3 chunkPosition) { Entity = new Entity(); Update(data); - Entity.GetOrCreate().Position = chunkPosition * Chunk.Size; + Entity.GetOrCreate().Position = chunkPosition * Chunk.RowSize; } public Entity Entity { get; } diff --git a/NEWorld/Renderer/RdTextures.cs b/NEWorld/Renderer/RdTextures.cs index 4f9aae8..b4a1196 100644 --- a/NEWorld/Renderer/RdTextures.cs +++ b/NEWorld/Renderer/RdTextures.cs @@ -2,6 +2,8 @@ using Core; using Game.Terrain; using Xenko.Graphics; +using System; +using Xenko.Core.Mathematics; namespace NEWorld.Renderer { @@ -11,14 +13,64 @@ public class RdTextures : IBlockTextures public uint Add(string assetUri) { var id = (uint) textures.Count; - textures.Add(Context.Content.Load(assetUri)); + var texture = Context.Content.Load(assetUri); + if (Context.Content.IsLoaded(assetUri)) + { + textures.Add(texture); + } + return id; } public void GetTexturePos(ref BlockTexCoord pos) { + pos.Tex = new Int2(0, 0); //new Int2((int) (pos.Id % texturePerLine), (int) (pos.Id / texturePerLine)); + } + + public static Texture FlushTextures() + { + var count = textures.Count; + texturePerLine = (1 << (int)(Math.Ceiling(Math.Log(Math.Ceiling(Math.Sqrt(count))) / Math.Log(2)))); + var wid = texturePerLine * pixelPerTexture; + var texture = Texture.New2D(Context.GraphicsDevice, wid, wid, + 1/*(int) (Math.Log(pixelPerTexture) / Math.Log(2))*/, PixelFormat.R8G8B8A8_UInt); + for (var i = 0; i < count; ++i) + { + var tile = textures[i]; + /*var raw = tile.GetData(Context.CommandList); + var unit = raw.Length / (tile.Width * tile.Height); + var down = new byte[unit * pixelPerTexture * pixelPerTexture]; + var xFact = (double)tile.Width / pixelPerTexture; + var yFact = (double)tile.Height / pixelPerTexture; + for (var xi = 0; xi < pixelPerTexture; ++xi) + { + for (var yi = 0; yi < pixelPerTexture; ++yi) + { + var from = (int)(unit * (Math.Round(xi * xFact) * pixelPerTexture + Math.Round(yi * yFact))); + var to = unit * (xi * pixelPerTexture + yi); + for (var n = 0; n < unit; ++n) + { + down[to + n] = raw[from + n]; + } + } + } + + var re = Texture.New2D(Context.GraphicsDevice, pixelPerTexture, pixelPerTexture, tile.Format, down);*/ + var re = tile; + var x = i % texturePerLine; + var y = i / texturePerLine; + var rx = x * pixelPerTexture; + var ry = y * pixelPerTexture; + Context.CommandList.CopyRegion( + re, re.GetSubResourceIndex(0, 0), null, + texture, texture.GetSubResourceIndex(0, 0), rx, ry); + } + return texture; } - private List textures = new List(); + public static int TexturesPreLine => 1; + + private static int texturePerLine, pixelPerTexture = 32; + private static Listtextures = new List(); } } \ No newline at end of file From 8f5a42b3aa4104b16d58933fa40aaec0ed6e70e3 Mon Sep 17 00:00:00 2001 From: DWVoid Date: Mon, 4 Feb 2019 00:26:56 +0800 Subject: [PATCH 09/23] Significantly Reduce Memory Footprint --- Core/Module/Module.cs | 14 +++ Game/ChunkService.cs | 18 ++- Game/Network/Client.cs | 2 + Game/Network/Protocols.cs | 130 ++++++++++++++++----- Game/Network/Server.cs | 3 +- Game/TaskDispatcher.cs | 22 ++-- Game/World/Chunk.cs | 224 +++++++++++++++++++++++++++++++----- Game/World/Player.cs | 6 +- Game/World/World.cs | 20 +--- Game/World/WorldTasks.cs | 36 +++--- Main/Main.cs | 98 ++++++++++++---- Main/Main.csproj | 1 + NEWorld/MainScript.cs | 23 ++-- NEWorld/Renderer/RdWorld.cs | 12 +- NEWorldShell/Cli.cs | 2 +- NEWorldShell/Program.cs | 1 + 16 files changed, 449 insertions(+), 163 deletions(-) diff --git a/Core/Module/Module.cs b/Core/Module/Module.cs index 24a2a9b..3f708a9 100644 --- a/Core/Module/Module.cs +++ b/Core/Module/Module.cs @@ -27,6 +27,8 @@ namespace Core.Module public interface IModule { void CoInitialize(); + void WorkspaceInitialize(); + void WorkspaceFinalize(); void CoFinalize(); void OnMemoryWarning(); } @@ -74,6 +76,18 @@ public void Load(string moduleFile) } } + public void WorkspaceInitialize() + { + foreach (var module in modules) + module.Value.Key.WorkspaceInitialize(); + } + + public void WorkspaceFinalize() + { + foreach (var module in modules) + module.Value.Key.WorkspaceFinalize(); + } + public void UnloadAll() { foreach (var module in modules) diff --git a/Game/ChunkService.cs b/Game/ChunkService.cs index 61d16f0..1f3fc01 100644 --- a/Game/ChunkService.cs +++ b/Game/ChunkService.cs @@ -38,26 +38,22 @@ public class ChunkService * and in the server-side of the multiplayer mode * are authoritative. */ - protected ChunkService(bool isAuthority) + static ChunkService() { - IsAuthority = isAuthority; + IsAuthority = true; Worlds = new WorldManager(); TaskDispatcher = Services.Get("Game.TaskDispatcher"); } - private ChunkService() : this(true) - { - } - - public TaskDispatcher TaskDispatcher { get; } + public static TaskDispatcher TaskDispatcher { get; private set; } - public WorldManager Worlds { get; } + public static WorldManager Worlds { get; private set; } - public bool IsAuthority { set; get; } + public static bool IsAuthority { set; get; } - public void EnableDispatcher() + public static void EnableDispatcher() { - TaskDispatcher.Start(this); + TaskDispatcher.Start(); } } } \ No newline at end of file diff --git a/Game/Network/Client.cs b/Game/Network/Client.cs index deb7a58..36b896f 100644 --- a/Game/Network/Client.cs +++ b/Game/Network/Client.cs @@ -30,6 +30,7 @@ public class Client : IDisposable public static GetChunk.Client GetChunk; public static GetAvailableWorldId.Client GetAvailableWorldId; public static GetWorldInfo.Client GetWorldInfo; + public static GetStaticChunkIds.Client GetStaticChunkIds; private static Core.Network.Client _client; @@ -44,6 +45,7 @@ public async Task Enable(string address, int port) _client.RegisterProtocol(GetChunk = new GetChunk.Client()); _client.RegisterProtocol(GetAvailableWorldId = new GetAvailableWorldId.Client()); _client.RegisterProtocol(GetWorldInfo = new GetWorldInfo.Client()); + _client.RegisterProtocol(GetStaticChunkIds = new GetStaticChunkIds.Client()); await _client.HandShake(); } diff --git a/Game/Network/Protocols.cs b/Game/Network/Protocols.cs index 884de69..5557c26 100644 --- a/Game/Network/Protocols.cs +++ b/Game/Network/Protocols.cs @@ -17,7 +17,9 @@ // along with NEWorld. If not, see . // +using System; using System.Collections.Generic; +using System.Data; using System.Threading; using System.Threading.Tasks; using Core.Network; @@ -28,14 +30,53 @@ namespace Game.Network { + public static class GetStaticChunkIds + { + public class Server : FixedLengthProtocol + { + public Server() : base(4){} + + public override string Name() + { + return "GetStaticChunkId"; + } + + public override void HandleRequest(Session.Receive request) + { + var session = request.ReadU32(); + var ids = StaticChunkPool.Id; + Reply.Send(request.Session, session, MessagePackSerializer.SerializeUnsafe(ids)); + } + } + + public class Client : StubProtocol + { + public override string Name() + { + return "GetStaticChunkId"; + } + + public async Task Call() + { + var session = Reply.AllocSession(); + using (var message = Network.Client.CreateMessage(Id)) + { + message.Write(session.Key); + } + + var result = await session.Value; + StaticChunkPool.Id = MessagePackSerializer.Deserialize>(result); + } + } + } + public static class GetChunk { + private static readonly ThreadLocal LocalMemCache = new ThreadLocal(); private static readonly int Size = MessagePackSerializer.SerializeUnsafe(new int[4]).Count; public class Server : FixedLengthProtocol { - private static readonly ThreadLocal LocalMemCache = new ThreadLocal(); - public Server() : base(Size) { } @@ -48,29 +89,22 @@ public override string Name() public override void HandleRequest(Session.Receive stream) { var request = MessagePackSerializer.Deserialize(stream.Raw); - var chunkData = Get((uint) request[0], new Int3(request[1], request[2], request[3])); + var chunk = GetChunk((uint) request[0], new Int3(request[1], request[2], request[3])); using (var message = stream.Session.CreateMessage(Id)) { message.Write(stream.Raw, 0, Size); - message.Write(chunkData, 0, chunkData.Length); + var cow = chunk.CopyOnWrite; + message.Write(cow); + if (cow == uint.MaxValue) + { + var chunkData = Get(chunk); + message.Write(chunkData, 0, chunkData.Length); + } } } - private static byte[] Get(uint worldId, Int3 position) + private static unsafe byte[] Get(Chunk chunkPtr) { - // TODO: empty chunk optimization - var world = Singleton.Instance.Worlds.Get(worldId); - Chunk chunkPtr; - try - { - chunkPtr = world.GetChunk(position); - } - catch (KeyNotFoundException) - { - var chunk = new Chunk(position, world); - chunkPtr = world.InsertChunkAndUpdate(position, chunk); - } - var chunkData = LocalMemCache.Value ?? (LocalMemCache.Value = new byte[32768 * 4]); for (var i = 0; i < 32768 * 4; ++i) { @@ -83,11 +117,29 @@ private static byte[] Get(uint worldId, Int3 position) return chunkData; } + + private static Chunk GetChunk(uint worldId, Int3 position) + { + var world = ChunkService.Worlds.Get(worldId); + Chunk chunkPtr; + try + { + chunkPtr = world.GetChunk(position); + } + catch (KeyNotFoundException) + { + var chunk = new Chunk(position, world); + // TODO: Implement a WorldTask Instead + chunkPtr = world.InsertChunkAndUpdate(position, chunk); + } + + return chunkPtr; + } } - public class Client : FixedLengthProtocol + public class Client : Protocol { - public Client() : base(32768 * 4 + Size) + public Client() { } @@ -95,22 +147,42 @@ public override string Name() { return "GetChunk"; } - + public override void HandleRequest(Session.Receive request) { - var data = request.Raw; - var req = MessagePackSerializer.Deserialize(data); - var srv = Singleton.Instance; - var chk = new Chunk(new Int3(req[1], req[2], req[3]), srv.Worlds.Get((uint) req[0])); - for (var i = Size; i < 32768 * 4 + Size; i += 4) + var buffer = new byte[Size]; + request.Read(buffer, 0, Size); + var req = MessagePackSerializer.Deserialize(buffer); + var cow = request.ReadU32(); + Chunk chk; + var chunkPos = new Int3(req[1], req[2], req[3]); + var world = ChunkService.Worlds.Get((uint) req[0]); + if (cow == uint.MaxValue) + { + var data = LocalMemCache.Value ?? (LocalMemCache.Value = new byte[32768 * 4]); + request.Read(data, 0, data.Length); + chk = DeserializeChunk(chunkPos, world, data); + } + else + { + chk = new Chunk(chunkPos, world, cow); + } + + ChunkService.TaskDispatcher.Add(new World.World.ResetChunkTask(chk)); + } + + private static unsafe Chunk DeserializeChunk(Int3 chunkPos, World.World world, byte[] data) + { + var chk = new Chunk(chunkPos, world, Chunk.InitOption.AllocateUnique); + for (var i = 0; i < 32768 * 4; i += 4) { - ref var block = ref chk.Blocks[(i - Size) >> 2]; + ref var block = ref chk.Blocks[i >> 2]; block.Id = (ushort) ((data[i] << 4) | (data[i + 1] >> 4)); block.Brightness = (byte) (data[i + 1] | 0xF); block.Data = (uint) ((data[i + 2] << 8) | data[i + 3]); } - srv.TaskDispatcher.Add(new World.World.ResetChunkTask(chk)); + return chk; } public void Call(uint worldId, Int3 position) @@ -176,7 +248,7 @@ public Server() : base(8) public override void HandleRequest(Session.Receive stream) { var request = stream.ReadU32(); - var world = Singleton.Instance.Worlds.Get(stream.ReadU32()); + var world = ChunkService.Worlds.Get(stream.ReadU32()); var ret = new Dictionary {{"name", world.Name}}; Reply.Send(stream.Session, request, MessagePackSerializer.SerializeUnsafe(ret)); } diff --git a/Game/Network/Server.cs b/Game/Network/Server.cs index e1c4533..299e79d 100644 --- a/Game/Network/Server.cs +++ b/Game/Network/Server.cs @@ -41,7 +41,8 @@ public void Enable(int port) server.RegisterProtocol(new GetChunk.Server()); server.RegisterProtocol(new GetAvailableWorldId.Server()); server.RegisterProtocol(new GetWorldInfo.Server()); - Singleton.Instance.Worlds.Add("test world"); + server.RegisterProtocol(new GetStaticChunkIds.Server()); + ChunkService.Worlds.Add("test world"); } public void Run() diff --git a/Game/TaskDispatcher.cs b/Game/TaskDispatcher.cs index c46c3c0..da061df 100644 --- a/Game/TaskDispatcher.cs +++ b/Game/TaskDispatcher.cs @@ -36,7 +36,7 @@ namespace Game */ public interface IReadOnlyTask { - void Task(ChunkService srv); + void Task(); } /** @@ -46,7 +46,7 @@ public interface IReadOnlyTask */ public interface IReadWriteTask { - void Task(ChunkService srv); + void Task(); } /** @@ -55,7 +55,7 @@ public interface IReadWriteTask */ public interface IRenderTask { - void Task(ChunkService srv); + void Task(); } [DeclareService("Game.TaskDispatcher")] @@ -68,8 +68,7 @@ public class TaskDispatcher : IDisposable private readonly List regularReadOnlyTasks; private readonly List regularReadWriteTasks; private readonly List threads; - - private ChunkService chunkService; + private RateController meter = new RateController(30); private List readOnlyTasks, nextReadOnlyTasks; private List readWriteTasks, nextReadWriteTasks; @@ -115,9 +114,8 @@ public void Dispose() ReleaseUnmanagedResources(); } - public void Start(ChunkService srv) + public void Start() { - chunkService = srv; shouldExit = false; for (var i = 0; i < TimeUsed.Length; ++i) { @@ -187,7 +185,7 @@ public void ProcessRenderTasks() lock (mutex) { foreach (var task in renderTasks) - task.Task(chunkService); + task.Task(); renderTasks.Clear(); Generic.Swap(ref renderTasks, ref nextRenderTasks); } @@ -224,15 +222,15 @@ private void Worker(int threadId) private void ProcessReadonlyTasks(int i) { - for (; i < regularReadOnlyTasks.Count; i += threads.Count) regularReadOnlyTasks[i].Task(chunkService); + for (; i < regularReadOnlyTasks.Count; i += threads.Count) regularReadOnlyTasks[i].Task(); for (i -= regularReadOnlyTasks.Count; i < readOnlyTasks.Count; i += threads.Count) - readOnlyTasks[i].Task(chunkService); + readOnlyTasks[i].Task(); } private void ProcessReadWriteTasks() { - foreach (var task in regularReadWriteTasks) task.Task(chunkService); - foreach (var task in readWriteTasks) task.Task(chunkService); + foreach (var task in regularReadWriteTasks) task.Task(); + foreach (var task in readWriteTasks) task.Task(); readWriteTasks.Clear(); } diff --git a/Game/World/Chunk.cs b/Game/World/Chunk.cs index d9b5916..7b34d82 100644 --- a/Game/World/Chunk.cs +++ b/Game/World/Chunk.cs @@ -19,14 +19,45 @@ using System; using System.Collections.Generic; +using System.Runtime.InteropServices; using System.Threading; using Xenko.Core.Mathematics; namespace Game.World { - public class Chunk + public class ChunkGeneratorContext { - public delegate void Generator(Int3 chunkPos, BlockData[] chunkData, int daylightBrightness); + public readonly Chunk Current; + + public readonly int DaylightBrightness; + + public ChunkGeneratorContext(Chunk current, int daylightBrightness) + { + Current = current; + DaylightBrightness = daylightBrightness; + } + + public void EnableCopyOnWrite(uint target) + { + Current.EnableCopyOnWrite(target); + } + + public void EnableFullArray() + { + Current.EnableFullArray(); + } + } + + public unsafe class Chunk : IDisposable + { + public delegate void Generator(ChunkGeneratorContext context); + + public enum InitOption + { + None, + Build, + AllocateUnique + } public const int SizeLog2 = 5; public const int RowSize = 32; @@ -37,22 +68,53 @@ public class Chunk public const int BitShiftY = SizeLog2; public const int AxisBits = 0b11111; + // Flags + private const uint CopyOnWriteBit = 0b1; + // Chunk size private static bool _chunkGeneratorLoaded; private static Generator _chunkGen; + private uint cowId; + private uint flags; + // For Garbage Collection private long mLastRequestTime; - public Chunk(Int3 position, World world, bool build = true) + public Chunk(BlockData fill) + { + EnableFullArray(); + for (var i = 0; i < CubeSize; ++i) + Blocks[i] = fill; + } + + public Chunk(Int3 position, World world, InitOption option = InitOption.Build) { Position = position; World = world; - Blocks = new BlockData[CubeSize]; - if (build) - Build(world.DaylightBrightness); + switch (option) + { + case InitOption.Build: + Build(world.DaylightBrightness); + break; + case InitOption.AllocateUnique: + EnableFullArray(); + break; + default: + EnableCopyOnWrite(StaticChunkPool.GetAirChunk()); + break; + } } + public Chunk(Int3 position, World world, uint other) + { + Position = position; + World = world; + EnableCopyOnWrite(other); + } + + public uint CopyOnWrite => (flags & CopyOnWriteBit) == CopyOnWriteBit ? cowId : uint.MaxValue; + // TODO: somehow avoid it! not safe. public bool IsUpdated { get; set; } @@ -60,28 +122,65 @@ public Chunk(Int3 position, World world, bool build = true) public World World { get; } - public BlockData[] Blocks { get; } - + public BlockData* Blocks { get; private set; } + public BlockData this[int x, int y, int z] { - get => Blocks[x << BitShiftX | y << BitShiftY | z]; + get => Blocks[(x << BitShiftX) | (y << BitShiftY) | z]; set { - Blocks[x << BitShiftX | y << BitShiftY | z] = value; + if ((flags & CopyOnWriteBit) == CopyOnWriteBit) + ExecuteFullCopy(); + Blocks[(x << BitShiftX) | (y << BitShiftY) | z] = value; IsUpdated = true; } } public BlockData this[Int3 pos] { - get => Blocks[pos.X << BitShiftX | pos.Y << BitShiftY | pos.Z]; + get => Blocks[(pos.X << BitShiftX) | (pos.Y << BitShiftY) | pos.Z]; set { - Blocks[pos.X << BitShiftX | pos.Y << BitShiftY | pos.Z] = value; + if ((flags & CopyOnWriteBit) == CopyOnWriteBit) + ExecuteFullCopy(); + Blocks[(pos.X << BitShiftX) | (pos.Y << BitShiftY) | pos.Z] = value; IsUpdated = true; } } + public void Dispose() + { + ReleaseResources(); + GC.SuppressFinalize(this); + } + + internal void EnableFullArray() + { + flags = 0; + Blocks = ChunkDataAllocator.Allocate(); + } + + internal void EnableCopyOnWrite(uint other) + { + Blocks = StaticChunkPool.GetChunk(other).Blocks; + flags = CopyOnWriteBit; + cowId = other; + } + + private void ExecuteFullCopy() + { + lock (this) + { + if ((flags & CopyOnWriteBit) == CopyOnWriteBit) + { + var old = Blocks; + EnableFullArray(); + for (var i = 0; i < CubeSize; ++i) + Blocks[i] = old[i]; + } + } + } + public static void SetGenerator(Generator gen) { if (!_chunkGeneratorLoaded) @@ -96,9 +195,9 @@ public static void SetGenerator(Generator gen) } // Build chunk - public void Build(int daylightBrightness) + private void Build(int daylightBrightness) { - _chunkGen(Position, Blocks, daylightBrightness); + _chunkGen(new ChunkGeneratorContext(this, daylightBrightness)); IsUpdated = true; } @@ -112,47 +211,110 @@ public bool CheckReleaseable() { return DateTime.Now - new DateTime(Interlocked.Read(ref mLastRequestTime)) > TimeSpan.FromSeconds(10); } + + private void ReleaseResources() + { + if ((flags & CopyOnWriteBit) != CopyOnWriteBit) + ChunkDataAllocator.Release(Blocks); + Blocks = null; + } + + ~Chunk() + { + ReleaseResources(); + } + + private static class ChunkDataAllocator + { + internal static BlockData* Allocate() + { + return (BlockData*) Marshal.AllocHGlobal(CubeSize * sizeof(BlockData)).ToPointer(); + } + + internal static void Release(BlockData* data) + { + Marshal.FreeHGlobal((IntPtr) data); + } + } } - public class ChunkManager : Dictionary + public static class StaticChunkPool { - public bool IsLoaded(Int3 chunkPos) + private static uint _airChunkId = uint.MaxValue; + private static List _staticList = new List(); + private static Dictionary _id; + + static StaticChunkPool() { - return ContainsKey(chunkPos); + _id = new Dictionary(); + Register("Default.AirChunk", new Chunk(new BlockData(0))); + } + + internal static Dictionary Id + { + get => _id; + set + { + var oldId = value; + var old = _staticList; + _id = value; + _staticList = new List(_id.Count); + for (var i = 0; i < _id.Count; ++i) + _staticList.Add(null); + foreach (var record in value) + _staticList[(int) record.Value] = old[(int) oldId[record.Key]]; + } } - // Convert world position to chunk coordinate (one axis) - public static int GetAxisPos(int pos) + public static void Register(string name, Chunk staticChunk) { - return pos >> Chunk.SizeLog2; + if (_id.TryGetValue(name, out var sid)) + if (_staticList[(int) sid] == null) + _staticList[(int) sid] = staticChunk; + + _id.Add(name, (uint) _staticList.Count); + _staticList.Add(staticChunk); } - // Convert world position to chunk coordinate (all axes) - public static Int3 GetPos(Int3 pos) + public static uint GetAirChunk() { - return new Int3(GetAxisPos(pos.X), GetAxisPos(pos.Y), GetAxisPos(pos.Z)); + if (_airChunkId == uint.MaxValue) + _airChunkId = _id["Default.AirChunk"]; + return _airChunkId; } - // Convert world position to block coordinate in chunk (one axis) - public static int GetBlockAxisPos(int pos) + public static Chunk GetChunk(uint id) { - return pos & Chunk.AxisBits; + return _staticList[(int) id]; } - // Convert world position to block coordinate in chunk (all axes) - public static Int3 GetBlockPos(Int3 pos) + public static uint GetId(string name) + { + return _id[name]; + } + } + + public class ChunkManager : Dictionary + { + public bool IsLoaded(Int3 chunkPos) + { + return ContainsKey(chunkPos); + } + + // Convert world position to chunk coordinate (all axes) + public static Int3 GetPos(Int3 pos) { - return new Int3(GetBlockAxisPos(pos.X), GetBlockAxisPos(pos.Y), GetBlockAxisPos(pos.Z)); + return new Int3(pos.X >> Chunk.SizeLog2, pos.Y >> Chunk.SizeLog2, pos.Z >> Chunk.SizeLog2); } public BlockData GetBlock(Int3 pos) { - return this[GetPos(pos)][GetBlockPos(pos)]; + return this[GetPos(pos)][pos.X & Chunk.AxisBits, pos.Y & Chunk.AxisBits, pos.Z & Chunk.AxisBits]; } public void SetBlock(Int3 pos, BlockData block) { - this[GetPos(pos)][GetBlockPos(pos)] = block; + this[GetPos(pos)][pos.X & Chunk.AxisBits, pos.Y & Chunk.AxisBits, pos.Z & Chunk.AxisBits] = block; } } } \ No newline at end of file diff --git a/Game/World/Player.cs b/Game/World/Player.cs index 2470a41..8720670 100644 --- a/Game/World/Player.cs +++ b/Game/World/Player.cs @@ -32,7 +32,7 @@ public class Player : PlayerObject public Player(uint worldId) : base(worldId) { - Singleton.Instance.TaskDispatcher.AddRegular(new PlayerUpdateTask(this, WorldId)); + ChunkService.TaskDispatcher.AddRegular(new PlayerUpdateTask(this, WorldId)); } public Double3 PositionDelta => positionDelta; @@ -114,9 +114,9 @@ public PlayerUpdateTask(Player player, uint worldId) this.worldId = worldId; } - public void Task(ChunkService srv) + public void Task() { - player.Update(srv.Worlds.Get(worldId)); + player.Update(ChunkService.Worlds.Get(worldId)); } } } diff --git a/Game/World/World.cs b/Game/World/World.cs index 400805b..668b8cd 100644 --- a/Game/World/World.cs +++ b/Game/World/World.cs @@ -80,31 +80,16 @@ public bool IsChunkLoaded(Int3 chunkPos) return Chunks.IsLoaded(chunkPos); } - public void DeleteChunk(Int3 chunkPos) + private void DeleteChunk(Int3 chunkPos) { Chunks.Remove(chunkPos); } - public static int GetChunkAxisPos(int pos) - { - return ChunkManager.GetAxisPos(pos); - } - public static Int3 GetChunkPos(Int3 pos) { return ChunkManager.GetPos(pos); } - public static int GetBlockAxisPos(int pos) - { - return ChunkManager.GetBlockAxisPos(pos); - } - - public static Int3 GetBlockPos(ref Int3 pos) - { - return ChunkManager.GetBlockPos(pos); - } - public BlockData GetBlock(Int3 pos) { return Chunks.GetBlock(pos); @@ -130,8 +115,9 @@ public Chunk InsertChunkAndUpdate(Int3 pos, Chunk chunk) return ret; } - public Chunk ResetChunk(ref Int3 pos, Chunk ptr) + private Chunk ResetChunk(ref Int3 pos, Chunk ptr) { + Chunks[pos].Dispose(); return Chunks[pos] = ptr; } diff --git a/Game/World/WorldTasks.cs b/Game/World/WorldTasks.cs index c33ff62..cfc02be 100644 --- a/Game/World/WorldTasks.cs +++ b/Game/World/WorldTasks.cs @@ -31,9 +31,9 @@ public partial class World private static readonly Int3 MiddleOffset = new Int3(Chunk.RowSize / 2 - 1, Chunk.RowSize / 2 - 1, Chunk.RowSize / 2 - 1); - public void RegisterChunkTasks(ChunkService cs, Player player) + public void RegisterChunkTasks(Player player) { - cs.TaskDispatcher.AddRegular(new LoadUnloadDetectorTask(this, player)); + ChunkService.TaskDispatcher.AddRegular(new LoadUnloadDetectorTask(this, player)); } public class ResetChunkTask : IReadWriteTask @@ -49,7 +49,7 @@ public ResetChunkTask(Chunk chunk) this.chunk = chunk; } - public void Task(ChunkService srv) + public void Task() { chunk.World.ResetChunkAndUpdate(chunk.Position, chunk); } @@ -57,25 +57,23 @@ public void Task(ChunkService srv) private class UnloadChunkTask : IReadWriteTask { - private readonly Int3 chunkPosition; - - private readonly World world; + private readonly Chunk chunk; /** * \brief Given a chunk, it will try to unload it (decrease a ref) * \param world the target world * \param chunkPosition the position of the chunk */ - public UnloadChunkTask(World world, Int3 chunkPosition) + public UnloadChunkTask(Chunk chk) { - this.world = world; - this.chunkPosition = chunkPosition; + chunk = chk; } - public void Task(ChunkService srv) + public void Task() { //TODO: for multiplayer situation, it should decrease ref counter instead of deleting - world.DeleteChunk(chunkPosition); + chunk.Dispose(); + chunk.World.DeleteChunk(chunk.Position); } } @@ -96,9 +94,9 @@ public BuildOrLoadChunkTask(World world, Int3 chunkPosition) this.chunkPosition = chunkPosition; } - public void Task(ChunkService srv) + public void Task() { - srv.TaskDispatcher.Add(new ResetChunkTask(new Chunk(chunkPosition, world))); + ChunkService.TaskDispatcher.Add(new ResetChunkTask(new Chunk(chunkPosition, world))); } } @@ -113,7 +111,7 @@ public LoadUnloadDetectorTask(World world, Player player) this.world = world; } - public void Task(ChunkService cs) + public void Task() { var loadList = new OrderedListIntLess(MaxChunkLoadCount); var unloadList = new OrderedListIntGreater(MaxChunkUnloadCount); @@ -124,14 +122,14 @@ public void Task(ChunkService cs) foreach (var loadPos in loadList) { // load a fake chunk - cs.TaskDispatcher.Add(new AddToWorldTask(new Chunk(loadPos.Value, world, false))); - cs.TaskDispatcher.Add(new BuildOrLoadChunkTask(world, loadPos.Value)); - if (!cs.IsAuthority) Client.GetChunk.Call(world.Id, loadPos.Value); + ChunkService.TaskDispatcher.Add(new AddToWorldTask(new Chunk(loadPos.Value, world, Chunk.InitOption.None))); + ChunkService.TaskDispatcher.Add(new BuildOrLoadChunkTask(world, loadPos.Value)); + if (!ChunkService.IsAuthority) Client.GetChunk.Call(world.Id, loadPos.Value); } foreach (var unloadChunk in unloadList) // add a unload task. - cs.TaskDispatcher.Add(new UnloadChunkTask(world, unloadChunk.Value.Position)); + ChunkService.TaskDispatcher.Add(new UnloadChunkTask(unloadChunk.Value)); } /** @@ -190,7 +188,7 @@ public AddToWorldTask(Chunk chunk) this.chunk = chunk; } - public void Task(ChunkService srv) + public void Task() { chunk.World.InsertChunkAndUpdate(chunk.Position, chunk); } diff --git a/Main/Main.cs b/Main/Main.cs index a0e0cfb..5ffaad6 100644 --- a/Main/Main.cs +++ b/Main/Main.cs @@ -21,7 +21,6 @@ using Core.Module; using Game.Terrain; using Game.World; -using Xenko.Core.Mathematics; namespace Main { @@ -30,6 +29,8 @@ public class Main : IModule { private static ushort _grassId, _rockId, _dirtId, _sandId, _waterId; + private static uint _rockChunkId, _waterChunkId; + public void CoInitialize() { _grassId = Blocks.Register(new BlockType("Grass", true, false, true, 2)); @@ -38,9 +39,21 @@ public void CoInitialize() _sandId = Blocks.Register(new BlockType("Sand", true, false, true, 2)); _waterId = Blocks.Register(new BlockType("Water", false, true, false, 2)); Chunk.SetGenerator(WorldGen.Generator); + StaticChunkPool.Register("Main.RockChunk", new Chunk(new BlockData(_rockId))); + StaticChunkPool.Register("Main.WaterChunk", new Chunk(new BlockData(_waterId))); RendererInit(); } + public void WorkspaceInitialize() + { + _rockChunkId = StaticChunkPool.GetId("Main.RockChunk"); + _waterChunkId = StaticChunkPool.GetId("Main.WaterChunk"); + } + + public void WorkspaceFinalize() + { + } + public void CoFinalize() { } @@ -116,35 +129,70 @@ private static double PerlinNoise2D(double x, double y) return total; } - public static void Generator(Int3 pos, BlockData[] blocks, int daylightBrightness) + public static unsafe void Generator(ChunkGeneratorContext context) { - for (var x = 0; x < Chunk.RowSize; x++) - for (var z = 0; z < Chunk.RowSize; z++) + var pos = context.Current.Position; + var heights = new int[Chunk.RowSize, Chunk.RowSize]; + var low = int.MaxValue; + var high = int.MinValue; { - var absHeight = (int) PerlinNoise2D((pos.X * Chunk.RowSize + x) / NoiseScaleX, - (pos.Z * Chunk.RowSize + z) / NoiseScaleZ) / 2 - 64; - var height = absHeight - pos.Y * Chunk.RowSize; - var underWater = absHeight <= 0; - for (var y = 0; y < Chunk.RowSize; y++) + for (var x = 0; x < Chunk.RowSize; x++) + for (var z = 0; z < Chunk.RowSize; z++) { - ref var block = ref blocks[x * Chunk.RowSize * Chunk.RowSize + y * Chunk.RowSize + z]; - if (y <= height) - { - if (y == height) - block.Id = underWater ? _sandId : _grassId; - else if (y >= height - 3) - block.Id = underWater ? _sandId : _dirtId; - else - block.Id = _rockId; + var val = heights[x, z] = (int) PerlinNoise2D((pos.X * Chunk.RowSize + x) / NoiseScaleX, + (pos.Z * Chunk.RowSize + z) / NoiseScaleZ) / 2 - 64; + if (val < low) low = val; + if (val > high) high = val; + } - block.Brightness = 0; - block.Data = 0; - } - else + if (pos.Y * Chunk.RowSize > high && high >= 0) + { + context.EnableCopyOnWrite(StaticChunkPool.GetAirChunk()); + return; + } + + if ((0-Chunk.RowSize) >= pos.Y * Chunk.RowSize && pos.Y * Chunk.RowSize > high) + { + context.EnableCopyOnWrite(StaticChunkPool.GetAirChunk()); + return; + } + + if (pos.Y * Chunk.RowSize < (low - Chunk.RowSize - 3)) + { + context.EnableCopyOnWrite(_rockChunkId); + return; + } + } + { + context.EnableFullArray(); + var blocks = context.Current.Blocks; + for (var x = 0; x < Chunk.RowSize; x++) + for (var z = 0; z < Chunk.RowSize; z++) + { + var absHeight = heights[x, z]; + var height = absHeight - pos.Y * Chunk.RowSize; + var underWater = absHeight <= 0; + for (var y = 0; y < Chunk.RowSize; y++) { - block.Id = pos.Y * Chunk.RowSize + y <= 0 ? _waterId : (ushort) 0; - block.Brightness = (byte) daylightBrightness; - block.Data = 0; + ref var block = ref blocks[x * Chunk.RowSize * Chunk.RowSize + y * Chunk.RowSize + z]; + if (y <= height) + { + if (y == height) + block.Id = underWater ? _sandId : _grassId; + else if (y >= height - 3) + block.Id = underWater ? _sandId : _dirtId; + else + block.Id = _rockId; + + block.Brightness = 0; + block.Data = 0; + } + else + { + block.Id = pos.Y * Chunk.RowSize + y <= 0 ? _waterId : (ushort) 0; + block.Brightness = (byte) context.DaylightBrightness; + block.Data = 0; + } } } } diff --git a/Main/Main.csproj b/Main/Main.csproj index 807a473..a8dcd41 100644 --- a/Main/Main.csproj +++ b/Main/Main.csproj @@ -2,6 +2,7 @@ netstandard2.0 + true diff --git a/NEWorld/MainScript.cs b/NEWorld/MainScript.cs index 7970bdf..bfc3113 100644 --- a/NEWorld/MainScript.cs +++ b/NEWorld/MainScript.cs @@ -125,7 +125,7 @@ private void EstablishChunkService() { if (IsClient()) { - Singleton.Instance.IsAuthority = false; + ChunkService.IsAuthority = false; } else { @@ -139,6 +139,8 @@ private void EstablishChunkService() private async Task EstablishGameConnection() { await Core.Services.Get("Game.Client").Enable("127.0.0.1", 31111); + await Client.GetStaticChunkIds.Call(); + Modules.Instance.WorkspaceInitialize(); } private void LoadPlayer() @@ -151,10 +153,9 @@ private void LoadPlayer() private async Task EnterCurrentWorld() { - var chunkService = Singleton.Instance; - currentWorld = Singleton.Instance.Worlds.Get(await RequestWorld()); - currentWorld.RegisterChunkTasks(chunkService, player); - chunkService.EnableDispatcher(); + currentWorld = ChunkService.Worlds.Get(await RequestWorld()); + currentWorld.RegisterChunkTasks(player); + ChunkService.EnableDispatcher(); } private void StartTerrainRenderService() @@ -179,8 +180,14 @@ public override void Start() } public override void Cancel() + { + TearDown(); + } + + private static void TearDown() { Core.Services.Get("Game.TaskDispatcher").Reset(); + Modules.Instance.WorkspaceFinalize(); } private static async Task RequestWorld() @@ -193,13 +200,13 @@ private static async Task RequestWorld() var worldInfo = await Client.GetWorldInfo.Call(worldIds[0]); - Singleton.Instance.Worlds.Add(worldInfo["name"]); + ChunkService.Worlds.Add(worldInfo["name"]); } // It's a simple wait-until-we-have-a-world procedure now. // But it should be changed into get player information // and get the world id from it. - while (Singleton.Instance.Worlds.Get(0) == null) + while (ChunkService.Worlds.Get(0) == null) Thread.Yield(); return 0; } @@ -211,7 +218,7 @@ private static bool IsClient() public override void Update() { - Singleton.Instance.TaskDispatcher.ProcessRenderTasks(); + ChunkService.TaskDispatcher.ProcessRenderTasks(); } } } \ No newline at end of file diff --git a/NEWorld/Renderer/RdWorld.cs b/NEWorld/Renderer/RdWorld.cs index 13944fe..69b8de6 100644 --- a/NEWorld/Renderer/RdWorld.cs +++ b/NEWorld/Renderer/RdWorld.cs @@ -29,7 +29,7 @@ namespace NEWorld.Renderer { public class RdWorld { - public const int MaxChunkRenderCount = 4; + public const int MaxChunkRenderCount = 16; // Chunk Renderers private readonly Dictionary chunkRenderers; @@ -44,7 +44,7 @@ public RdWorld(World world, Player player, int renderDistance) this.world = world; RenderDist = renderDistance; chunkRenderers = new Dictionary(); - Singleton.Instance.TaskDispatcher.AddRegular(new RenderDetectorTask(this, world.Id, player)); + ChunkService.TaskDispatcher.AddRegular(new RenderDetectorTask(this, world.Id, player)); } private class RenderDetectorTask : IReadOnlyTask @@ -68,14 +68,14 @@ public RenderDetectorTask(RdWorld rdWorldRenderer, uint currentWorldId, Player p this.player = player; } - public void Task(ChunkService cs) + public void Task() { var counter = 0; // TODO: improve performance by adding multiple instances of this and set a step when itering the chunks. var position = player.Position; var positionInt = new Int3((int) position.X, (int) position.Y, (int) position.Z); var chunkpos = World.GetChunkPos(positionInt); - var world = cs.Worlds.Get(currentWorldId); + var world = ChunkService.Worlds.Get(currentWorldId); foreach (var c in world.Chunks) { var chunk = c.Value; @@ -87,7 +87,7 @@ public void Task(ChunkService cs) // TODO: maybe build a VA pool can speed this up. var crd = new ChunkRenderData(); crd.Generate(chunk); - cs.TaskDispatcher.Add(new VboGenerateTask(world, chunkPosition, crd, + ChunkService.TaskDispatcher.Add(new VboGenerateTask(world, chunkPosition, crd, rdWorldRenderer.chunkRenderers)); if (++counter == MaxChunkRenderCount) break; } @@ -123,7 +123,7 @@ public VboGenerateTask(World world, Int3 position, ChunkRenderData crd, this.chunkRenderers = chunkRenderers; } - public void Task(ChunkService srv) + public void Task() { if (!world.Chunks.TryGetValue(position, out var chunk)) return; chunk.IsUpdated = false; diff --git a/NEWorldShell/Cli.cs b/NEWorldShell/Cli.cs index c41e767..93f0fc3 100644 --- a/NEWorldShell/Cli.cs +++ b/NEWorldShell/Cli.cs @@ -83,7 +83,7 @@ private void InitBuiltinCommands() { var ret = "Chunks loaded: "; long sum = 0; - var worlds = Singleton.Instance.Worlds; + var worlds = ChunkService.Worlds; foreach (var world in worlds) { ret += $"\n{world.Id} {world.Name} :\t{world.GetChunkCount()}"; diff --git a/NEWorldShell/Program.cs b/NEWorldShell/Program.cs index cd80a81..e63a795 100644 --- a/NEWorldShell/Program.cs +++ b/NEWorldShell/Program.cs @@ -31,6 +31,7 @@ public static void Main(string[] args) var cli = new ServerCommandLine(); var server = Services.Get("Game.Server"); server.Enable(31111); + Modules.Instance.WorkspaceInitialize(); server.Run(); cli.Start(); } From 4622681c8fadd58d814119ef69e989c91a321009 Mon Sep 17 00:00:00 2001 From: DWVoid Date: Wed, 6 Feb 2019 00:31:22 +0800 Subject: [PATCH 10/23] Update Task Dispatcher --- Core/Network/ConnectionHost.cs | 19 ++- Game/Network/Protocols.cs | 5 +- Game/Network/Server.cs | 1 - Game/TaskDispatcher.cs | 54 ++++----- Game/World/Blocks.cs | 1 + Game/World/Chunk.cs | 7 +- Game/World/Player.cs | 9 +- Game/World/World.cs | 26 ++-- Game/World/WorldTasks.cs | 210 ++++++++++++++++++--------------- NEWorld.sln.DotSettings.user | 2 + NEWorld/Renderer/RdWorld.cs | 49 ++++---- 11 files changed, 211 insertions(+), 172 deletions(-) diff --git a/Core/Network/ConnectionHost.cs b/Core/Network/ConnectionHost.cs index e71ab7b..84e318c 100644 --- a/Core/Network/ConnectionHost.cs +++ b/Core/Network/ConnectionHost.cs @@ -32,6 +32,7 @@ public sealed class Session : IDisposable private readonly TcpClient conn; private readonly NetworkStream ios; private readonly MemoryStream writeBuffer = new MemoryStream(new byte[8192], 0, 8192, true, true); + private readonly ReaderWriterLockSlim writeLock = new ReaderWriterLockSlim(); private MemoryStream buffer; private byte[] storage = new byte[8192]; @@ -194,6 +195,7 @@ internal Send(Session session, uint protocol) Session = session; ios = Session.ios; buffer = Session.writeBuffer; + session.writeLock.EnterWriteLock(); Write((byte) 'N'); Write((byte) 'W'); Write((byte) 'R'); @@ -205,9 +207,11 @@ internal Send(Session session, uint protocol) public void Dispose() { - FlushBuffer(); + ReleaseUnmanagedResources(); + GC.SuppressFinalize(this); } + public void Write(byte val) { buffer.WriteByte(val); @@ -264,6 +268,17 @@ public void Write(ArraySegment bytes) Debug.Assert(bytes.Array != null, "bytes.Array != null"); ios.Write(bytes.Array, bytes.Offset, bytes.Count); } + + private void ReleaseUnmanagedResources() + { + FlushBuffer(); + Session.writeLock.ExitWriteLock(); + } + + ~Send() + { + ReleaseUnmanagedResources(); + } } } @@ -357,7 +372,7 @@ private async Task Start() } catch (Exception e) { - if (Session.Live) {LogPort.Debug($"Encountering Exception {e}"); } + if (Session.Live) LogPort.Debug($"Encountering Exception {e}"); } CloseDown(); diff --git a/Game/Network/Protocols.cs b/Game/Network/Protocols.cs index 5557c26..f45ac04 100644 --- a/Game/Network/Protocols.cs +++ b/Game/Network/Protocols.cs @@ -17,13 +17,10 @@ // along with NEWorld. If not, see . // -using System; using System.Collections.Generic; -using System.Data; using System.Threading; using System.Threading.Tasks; using Core.Network; -using Core.Utilities; using Game.World; using MessagePack; using Xenko.Core.Mathematics; @@ -130,7 +127,7 @@ private static Chunk GetChunk(uint worldId, Int3 position) { var chunk = new Chunk(position, world); // TODO: Implement a WorldTask Instead - chunkPtr = world.InsertChunkAndUpdate(position, chunk); + chunkPtr = world.InsertChunkAndUpdate(chunk); } return chunkPtr; diff --git a/Game/Network/Server.cs b/Game/Network/Server.cs index 299e79d..273023c 100644 --- a/Game/Network/Server.cs +++ b/Game/Network/Server.cs @@ -20,7 +20,6 @@ using System; using System.Threading.Tasks; using Core; -using Core.Utilities; namespace Game.Network { diff --git a/Game/TaskDispatcher.cs b/Game/TaskDispatcher.cs index da061df..68c6b87 100644 --- a/Game/TaskDispatcher.cs +++ b/Game/TaskDispatcher.cs @@ -18,6 +18,7 @@ // using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading; using Core; @@ -25,8 +26,11 @@ namespace Game { - // TODO: we can add a `finished` flag in DEBUG mode - // to verify that all tasks are indeed processed. + public interface IInstancedTask + { + void Task(int instance, int instanceCount); + } + /** * \brief This type of tasks will be executed concurrently. * Note that "ReadOnly" here is with respect to chunks @@ -58,6 +62,10 @@ public interface IRenderTask void Task(); } + public interface IRegularReadOnlyTask : IInstancedTask + { + } + [DeclareService("Game.TaskDispatcher")] public class TaskDispatcher : IDisposable { @@ -65,12 +73,12 @@ public class TaskDispatcher : IDisposable // TODO: replace it with lock-free structure. private readonly object mutex; - private readonly List regularReadOnlyTasks; + private readonly ConcurrentBag readOnlyTasks; + private readonly List regularReadOnlyTasks; private readonly List regularReadWriteTasks; private readonly List threads; - + private RateController meter = new RateController(30); - private List readOnlyTasks, nextReadOnlyTasks; private List readWriteTasks, nextReadWriteTasks; private List renderTasks, nextRenderTasks; private bool shouldExit; @@ -91,9 +99,8 @@ private TaskDispatcher(int threadNumber) threads = new List(threadNumber); TimeUsed = new int[threadNumber]; mutex = new object(); - readOnlyTasks = new List(); - nextReadOnlyTasks = new List(); - regularReadOnlyTasks = new List(); + readOnlyTasks = new ConcurrentBag(); + regularReadOnlyTasks = new List(); readWriteTasks = new List(); nextReadWriteTasks = new List(); regularReadWriteTasks = new List(); @@ -128,10 +135,7 @@ public void Start() public void Add(IReadOnlyTask task) { - lock (mutex) - { - nextReadOnlyTasks.Add(task); - } + readOnlyTasks.Add(task); } public void Add(IReadWriteTask task) @@ -141,7 +145,7 @@ public void Add(IReadWriteTask task) nextReadWriteTasks.Add(task); } } - + public void Add(IRenderTask task) { lock (mutex) @@ -150,7 +154,7 @@ public void Add(IRenderTask task) } } - public void AddRegular(IReadOnlyTask task) + public void AddRegular(IRegularReadOnlyTask task) { lock (mutex) { @@ -166,16 +170,6 @@ public void AddRegular(IReadWriteTask task) } } - public int GetRegularReadOnlyTaskCount() - { - return regularReadOnlyTasks.Count; - } - - public int GetRegularReadWriteTaskCount() - { - return regularReadWriteTasks.Count; - } - /** * \brief Process render tasks. * This function should be called from the main thread. @@ -222,9 +216,13 @@ private void Worker(int threadId) private void ProcessReadonlyTasks(int i) { - for (; i < regularReadOnlyTasks.Count; i += threads.Count) regularReadOnlyTasks[i].Task(); - for (i -= regularReadOnlyTasks.Count; i < readOnlyTasks.Count; i += threads.Count) - readOnlyTasks[i].Task(); + for (var cnt = 0; cnt < regularReadOnlyTasks.Count; ++cnt) + regularReadOnlyTasks[cnt].Task(i, TimeUsed.Length); + + // TODO: Make sure the no further tasks will be added before exiting this function + // TODO: Add a timeout support for this to ensure the updation rate + while (readOnlyTasks.TryTake(out var task)) + task.Task(); } private void ProcessReadWriteTasks() @@ -236,8 +234,6 @@ private void ProcessReadWriteTasks() private void QueueSwap() { - readOnlyTasks.Clear(); - Generic.Swap(ref readOnlyTasks, ref nextReadOnlyTasks); Generic.Swap(ref readWriteTasks, ref nextReadWriteTasks); } diff --git a/Game/World/Blocks.cs b/Game/World/Blocks.cs index e35b64c..f872ca8 100644 --- a/Game/World/Blocks.cs +++ b/Game/World/Blocks.cs @@ -53,6 +53,7 @@ public BlockType(string name, bool solid, bool translucent, bool opaque, int har public bool IsOpaque { get; } } + // TODO: Implement ID Synchronization for each Game Instance public static class Blocks { private static readonly BlockType Air = new BlockType("Air", false, false, false, 0); diff --git a/Game/World/Chunk.cs b/Game/World/Chunk.cs index 7b34d82..998af4a 100644 --- a/Game/World/Chunk.cs +++ b/Game/World/Chunk.cs @@ -48,6 +48,9 @@ public void EnableFullArray() } } + // TODO: Implement Neighbor Chunk Handle Storage + // TODO: Implement Chunk Update Event Hook + // TODO: Implement The Unloaded Status Indicator public unsafe class Chunk : IDisposable { public delegate void Generator(ChunkGeneratorContext context); @@ -114,8 +117,8 @@ public Chunk(Int3 position, World world, uint other) } public uint CopyOnWrite => (flags & CopyOnWriteBit) == CopyOnWriteBit ? cowId : uint.MaxValue; - - // TODO: somehow avoid it! not safe. + + // TODO: Only Set On Real Content Updation public bool IsUpdated { get; set; } public Int3 Position { get; } diff --git a/Game/World/Player.cs b/Game/World/Player.cs index 8720670..aaf27cb 100644 --- a/Game/World/Player.cs +++ b/Game/World/Player.cs @@ -103,7 +103,7 @@ private void RotationMove() rotationSpeed *= 0; } - private class PlayerUpdateTask : IReadOnlyTask + private class PlayerUpdateTask : IRegularReadOnlyTask { private readonly Player player; private readonly uint worldId; @@ -114,9 +114,12 @@ public PlayerUpdateTask(Player player, uint worldId) this.worldId = worldId; } - public void Task() + public void Task(int instance, int count) { - player.Update(ChunkService.Worlds.Get(worldId)); + if (instance == count) + { + player.Update(ChunkService.Worlds.Get(worldId)); + } } } } diff --git a/Game/World/World.cs b/Game/World/World.cs index 668b8cd..c01606a 100644 --- a/Game/World/World.cs +++ b/Game/World/World.cs @@ -100,25 +100,25 @@ public void SetBlock(ref Int3 pos, BlockData block) Chunks.SetBlock(pos, block); } - private Chunk InsertChunk(ref Int3 pos, Chunk chunk) + private void InsertChunk(Chunk chunk) { - Chunks.Add(pos, chunk); - return chunk; + Chunks.Add(chunk.Position, chunk); + return; } - public Chunk InsertChunkAndUpdate(Int3 pos, Chunk chunk) + public Chunk InsertChunkAndUpdate(Chunk chunk) { - var ret = InsertChunk(ref pos, chunk); + InsertChunk(chunk); foreach (var dt in Delta) - if (Chunks.TryGetValue(pos + dt, out var target)) + if (Chunks.TryGetValue(chunk.Position + dt, out var target)) target.IsUpdated = true; - return ret; + return chunk; } - private Chunk ResetChunk(ref Int3 pos, Chunk ptr) + private void ResetChunk(Chunk ptr) { - Chunks[pos].Dispose(); - return Chunks[pos] = ptr; + Chunks[ptr.Position].Dispose(); + Chunks[ptr.Position] = ptr; } public List GetHitboxes(Aabb range) @@ -151,11 +151,11 @@ public void UpdateChunkLoadStatus() } } - private void ResetChunkAndUpdate(Int3 pos, Chunk chunk) + private void ResetChunkAndUpdate(Chunk chunk) { - ResetChunk(ref pos, chunk); + ResetChunk(chunk); foreach (var dt in Delta) - if (Chunks.TryGetValue(pos + dt, out var target)) + if (Chunks.TryGetValue(chunk.Position + dt, out var target)) target.IsUpdated = true; } } diff --git a/Game/World/WorldTasks.cs b/Game/World/WorldTasks.cs index cfc02be..97f0938 100644 --- a/Game/World/WorldTasks.cs +++ b/Game/World/WorldTasks.cs @@ -18,6 +18,7 @@ // using System; +using System.Threading; using Game.Network; using Game.Utilities; using Xenko.Core.Mathematics; @@ -51,7 +52,60 @@ public ResetChunkTask(Chunk chunk) public void Task() { - chunk.World.ResetChunkAndUpdate(chunk.Position, chunk); + chunk.World.ResetChunkAndUpdate(chunk); + } + } + + private class LoadTask : IReadWriteTask + { + private Chunk chunk; + private bool entryAdded; + + internal LoadTask(World world, Int3 chunkPosition) + { + // Adding Sentry + chunk = new Chunk(chunkPosition, world, Chunk.InitOption.None); + ChunkService.TaskDispatcher.Add(new LocalLoadTask(world, chunkPosition, this)); + if (!ChunkService.IsAuthority) Client.GetChunk.Call(world.Id, chunkPosition); + } + + public void Task() + { + var operated = Interlocked.Exchange(ref chunk, null); + if (entryAdded) + operated.World.ResetChunkAndUpdate(operated); + else + { + entryAdded = true; + operated.World.InsertChunkAndUpdate(operated); + } + } + + private void Reset(Chunk chk) + { + if (Interlocked.Exchange(ref chunk, chk) == null) + ChunkService.TaskDispatcher.Add(this); + } + + private class LocalLoadTask : IReadOnlyTask + { + private readonly Int3 chunkPosition; + + private readonly World world; + + private readonly LoadTask load; + + public LocalLoadTask(World wrd, Int3 position, LoadTask loadTask) + { + world = wrd; + chunkPosition = position; + load = loadTask; + } + + public void Task() + { + load.Reset(new Chunk(chunkPosition, world)); + } } } @@ -77,121 +131,87 @@ public void Task() } } - private class BuildOrLoadChunkTask : IReadOnlyTask - { - private readonly Int3 chunkPosition; - - private readonly World world; - - /** - * \brief Given a chunk, it will try to load it or build it - * \param world the target world - * \param chunkPosition the position of the chunk - */ - public BuildOrLoadChunkTask(World world, Int3 chunkPosition) - { - this.world = world; - this.chunkPosition = chunkPosition; - } - - public void Task() - { - ChunkService.TaskDispatcher.Add(new ResetChunkTask(new Chunk(chunkPosition, world))); - } - } - - private class LoadUnloadDetectorTask : IReadOnlyTask + private class LoadUnloadDetectorTask : IRegularReadOnlyTask { private readonly Player player; private readonly World world; - public LoadUnloadDetectorTask(World world, Player player) - { - this.player = player; - this.world = world; - } - - public void Task() + private class Instance { - var loadList = new OrderedListIntLess(MaxChunkLoadCount); - var unloadList = new OrderedListIntGreater(MaxChunkUnloadCount); - var playerPos = player.Position; - var position = new Int3((int) playerPos.X, (int) playerPos.Y, (int) playerPos.Z); - GenerateLoadUnloadList(world, position, 4, loadList, unloadList); + private readonly Int3 centerPos; + private readonly World world; + private readonly int instance, instances; + private readonly OrderedListIntLess loadList = new OrderedListIntLess(MaxChunkLoadCount); + private readonly OrderedListIntGreater unloadList = new OrderedListIntGreater(MaxChunkUnloadCount); - foreach (var loadPos in loadList) + internal Instance(World wrd, Player player, int ins, int inst) { - // load a fake chunk - ChunkService.TaskDispatcher.Add(new AddToWorldTask(new Chunk(loadPos.Value, world, Chunk.InitOption.None))); - ChunkService.TaskDispatcher.Add(new BuildOrLoadChunkTask(world, loadPos.Value)); - if (!ChunkService.IsAuthority) Client.GetChunk.Call(world.Id, loadPos.Value); + world = wrd; + instance = ins; + instances = inst; + var playerPos = player.Position; + centerPos = new Int3((int) playerPos.X, (int) playerPos.Y, (int) playerPos.Z); } - foreach (var unloadChunk in unloadList) - // add a unload task. - ChunkService.TaskDispatcher.Add(new UnloadChunkTask(unloadChunk.Value)); - } + internal void Run() + { + GenerateLoadUnloadList(4); - /** - * \brief Find the nearest chunks in load range to load, - * fartherest chunks out of load range to unload. - * \param world the world to load or unload chunks - * \param centerPos the center position - * \param loadRange chunk load range - * \param loadList (Output) Chunk load list [position, distance] - * \param unloadList (Output) Chunk unload list [pointer, distance] - */ - private static void GenerateLoadUnloadList(World world, Int3 centerPos, int loadRange, - OrderedListIntLess loadList, OrderedListIntGreater unloadList) - { - // centerPos to chunk coords - var centerCPos = GetChunkPos(centerPos); + foreach (var loadPos in loadList) + ChunkService.TaskDispatcher.Add(new LoadTask(world, loadPos.Value)); - foreach (var chunk in world.Chunks) - { - var curPos = chunk.Value.Position; - // Out of load range, pending to unload - if (ChebyshevDistance(centerCPos, curPos) > loadRange) - unloadList.Insert((curPos * Chunk.RowSize + MiddleOffset - centerPos).LengthSquared(), - chunk.Value); + foreach (var unloadChunk in unloadList) + // add a unload task. + ChunkService.TaskDispatcher.Add(new UnloadChunkTask(unloadChunk.Value)); } - for (var x = centerCPos.X - loadRange; x <= centerCPos.X + loadRange; x++) - for (var y = centerCPos.Y - loadRange; y <= centerCPos.Y + loadRange; y++) - for (var z = centerCPos.Z - loadRange; z <= centerCPos.Z + loadRange; z++) + private void GenerateLoadUnloadList(int loadRange) { - var position = new Int3(x, y, z); - // In load range, pending to load - if (!world.IsChunkLoaded(position)) - loadList.Insert((position * Chunk.RowSize + MiddleOffset - centerPos).LengthSquared(), - position); + var centerCPos = GetChunkPos(centerPos); + + // TODO: Instance this + if (instance == 0 ) + { + foreach (var chunk in world.Chunks) + { + var curPos = chunk.Value.Position; + // Out of load range, pending to unload + if (ChebyshevDistance(centerCPos, curPos) > loadRange) + unloadList.Insert((curPos * Chunk.RowSize + MiddleOffset - centerPos).LengthSquared(), + chunk.Value); + } + } + + var edge1 = loadRange * 2 + 1; + var edge2 = edge1 * edge1; + var edge3 = edge2 * edge1; + var corner = new Int3(centerCPos.X - loadRange, centerCPos.Y - loadRange, centerCPos.Z - loadRange); + for (var i = instance; i < edge3; i += instances) + { + var position = corner + new Int3(i / edge2, (i % edge2) / edge1, i % edge1); + // In load range, pending to load + if (!world.IsChunkLoaded(position)) + loadList.Insert((position * Chunk.RowSize + MiddleOffset - centerPos).LengthSquared(), + position); + } } } - // TODO: Remove Type1 Clone - private static int ChebyshevDistance(Int3 l, Int3 r) + public LoadUnloadDetectorTask(World world, Player player) { - return Math.Max(Math.Max(Math.Abs(l.X - r.X), Math.Abs(l.Y - r.Y)), Math.Abs(l.Z - r.Z)); + this.player = player; + this.world = world; } - private class AddToWorldTask : IReadWriteTask + public void Task(int instance, int instances) { - private readonly Chunk chunk; - - /** - * \brief Add a constructed chunk into world. - * \param worldID the target world's id - * \param chunk the target chunk - */ - public AddToWorldTask(Chunk chunk) - { - this.chunk = chunk; - } - - public void Task() - { - chunk.World.InsertChunkAndUpdate(chunk.Position, chunk); - } + new Instance(world, player, instance, instances).Run(); + } + + // TODO: Remove Type1 Clone + private static int ChebyshevDistance(Int3 l, Int3 r) + { + return Math.Max(Math.Max(Math.Abs(l.X - r.X), Math.Abs(l.Y - r.Y)), Math.Abs(l.Z - r.Z)); } } } diff --git a/NEWorld.sln.DotSettings.user b/NEWorld.sln.DotSettings.user index 3bce188..a637337 100644 --- a/NEWorld.sln.DotSettings.user +++ b/NEWorld.sln.DotSettings.user @@ -24,4 +24,6 @@ <Assembly Path="C:\Program Files\dotnet\sdk\NuGetFallbackFolder\messagepack\1.7.3.4\lib\netstandard2.0\MessagePack.dll" /> <Assembly Path="C:\Users\yshli\.nuget\packages\xenko.core\3.1.0.1-beta01-0406\ref\netstandard2.0\Xenko.Core.dll" /> <Assembly Path="C:\Users\yshli\.nuget\packages\xenko\3.1.0.1-beta01-0430\lib\netstandard2.0\Xenko.dll" /> + <Assembly Path="C:\Program Files\dotnet\shared\Microsoft.NETCore.App\2.1.7\System.Collections.Concurrent.dll" /> + <Assembly Path="C:\Users\yshli\.nuget\packages\xenko.core\3.1.0.1-beta01-0430\ref\netstandard2.0\Xenko.Core.dll" /> </AssemblyExplorer> \ No newline at end of file diff --git a/NEWorld/Renderer/RdWorld.cs b/NEWorld/Renderer/RdWorld.cs index 69b8de6..71c4ef4 100644 --- a/NEWorld/Renderer/RdWorld.cs +++ b/NEWorld/Renderer/RdWorld.cs @@ -20,7 +20,6 @@ using System; using System.Collections.Generic; using System.Linq; -using Core.Utilities; using Game; using Game.World; using Xenko.Core.Mathematics; @@ -47,7 +46,8 @@ public RdWorld(World world, Player player, int renderDistance) ChunkService.TaskDispatcher.AddRegular(new RenderDetectorTask(this, world.Id, player)); } - private class RenderDetectorTask : IReadOnlyTask + // TODO: Implement this with Chunk Updation Hook Instead + private class RenderDetectorTask : IRegularReadOnlyTask { private static readonly Int3[] Delta = { @@ -68,29 +68,32 @@ public RenderDetectorTask(RdWorld rdWorldRenderer, uint currentWorldId, Player p this.player = player; } - public void Task() + public void Task(int instance, int instances) { - var counter = 0; - // TODO: improve performance by adding multiple instances of this and set a step when itering the chunks. - var position = player.Position; - var positionInt = new Int3((int) position.X, (int) position.Y, (int) position.Z); - var chunkpos = World.GetChunkPos(positionInt); - var world = ChunkService.Worlds.Get(currentWorldId); - foreach (var c in world.Chunks) + if (instance == 0) { - var chunk = c.Value; - var chunkPosition = chunk.Position; - // In render range, pending to render - if (chunk.IsUpdated && ChebyshevDistance(chunkpos, chunkPosition) <= rdWorldRenderer.RenderDist) - if (NeighbourChunkLoadCheck(world, chunkPosition)) - { - // TODO: maybe build a VA pool can speed this up. - var crd = new ChunkRenderData(); - crd.Generate(chunk); - ChunkService.TaskDispatcher.Add(new VboGenerateTask(world, chunkPosition, crd, - rdWorldRenderer.chunkRenderers)); - if (++counter == MaxChunkRenderCount) break; - } + var counter = 0; + // TODO: improve performance by adding multiple instances of this and set a step when itering the chunks. + var position = player.Position; + var positionInt = new Int3((int) position.X, (int) position.Y, (int) position.Z); + var chunkpos = World.GetChunkPos(positionInt); + var world = ChunkService.Worlds.Get(currentWorldId); + foreach (var c in world.Chunks) + { + var chunk = c.Value; + var chunkPosition = chunk.Position; + // In render range, pending to render + if (chunk.IsUpdated && ChebyshevDistance(chunkpos, chunkPosition) <= rdWorldRenderer.RenderDist) + if (NeighbourChunkLoadCheck(world, chunkPosition)) + { + // TODO: maybe build a VA pool can speed this up. + var crd = new ChunkRenderData(); + crd.Generate(chunk); + ChunkService.TaskDispatcher.Add(new VboGenerateTask(world, chunkPosition, crd, + rdWorldRenderer.chunkRenderers)); + if (++counter == MaxChunkRenderCount) break; + } + } } } From f45adc69f11281769de4f90be744ff3987c60344 Mon Sep 17 00:00:00 2001 From: DWVoid Date: Wed, 6 Feb 2019 10:45:26 +0800 Subject: [PATCH 11/23] Split class Chunk into multiple files --- Game/World/Chunk.cs | 249 +----------------------------- Game/World/ChunkAccess.cs | 31 ++++ Game/World/ChunkConstants.cs | 14 ++ Game/World/ChunkDisposePattern.cs | 20 +++ Game/World/ChunkGenerator.cs | 54 +++++++ Game/World/ChunkManager.cs | 29 ++++ Game/World/ChunkReleaseTimer.cs | 20 +++ Game/World/ChunkStorage.cs | 80 ++++++++++ Game/World/StaticChunkPool.cs | 60 +++++++ Game/World/World.cs | 17 +- NEWorld.Windows/NEWorldApp.cs | 3 + 11 files changed, 316 insertions(+), 261 deletions(-) create mode 100644 Game/World/ChunkAccess.cs create mode 100644 Game/World/ChunkConstants.cs create mode 100644 Game/World/ChunkDisposePattern.cs create mode 100644 Game/World/ChunkGenerator.cs create mode 100644 Game/World/ChunkManager.cs create mode 100644 Game/World/ChunkReleaseTimer.cs create mode 100644 Game/World/ChunkStorage.cs create mode 100644 Game/World/StaticChunkPool.cs diff --git a/Game/World/Chunk.cs b/Game/World/Chunk.cs index 998af4a..f970183 100644 --- a/Game/World/Chunk.cs +++ b/Game/World/Chunk.cs @@ -17,44 +17,15 @@ // along with NEWorld. If not, see . // -using System; -using System.Collections.Generic; -using System.Runtime.InteropServices; -using System.Threading; using Xenko.Core.Mathematics; namespace Game.World { - public class ChunkGeneratorContext - { - public readonly Chunk Current; - - public readonly int DaylightBrightness; - - public ChunkGeneratorContext(Chunk current, int daylightBrightness) - { - Current = current; - DaylightBrightness = daylightBrightness; - } - - public void EnableCopyOnWrite(uint target) - { - Current.EnableCopyOnWrite(target); - } - - public void EnableFullArray() - { - Current.EnableFullArray(); - } - } - // TODO: Implement Neighbor Chunk Handle Storage // TODO: Implement Chunk Update Event Hook // TODO: Implement The Unloaded Status Indicator - public unsafe class Chunk : IDisposable + public unsafe partial class Chunk { - public delegate void Generator(ChunkGeneratorContext context); - public enum InitOption { None, @@ -62,28 +33,6 @@ public enum InitOption AllocateUnique } - public const int SizeLog2 = 5; - public const int RowSize = 32; - public const int RowLast = RowSize - 1; - public const int SliceSize = RowSize * RowSize; - public const int CubeSize = SliceSize * RowSize; - public const int BitShiftX = SizeLog2 * 2; - public const int BitShiftY = SizeLog2; - public const int AxisBits = 0b11111; - - // Flags - private const uint CopyOnWriteBit = 0b1; - - // Chunk size - private static bool _chunkGeneratorLoaded; - private static Generator _chunkGen; - - private uint cowId; - private uint flags; - - // For Garbage Collection - private long mLastRequestTime; - public Chunk(BlockData fill) { EnableFullArray(); @@ -116,8 +65,6 @@ public Chunk(Int3 position, World world, uint other) EnableCopyOnWrite(other); } - public uint CopyOnWrite => (flags & CopyOnWriteBit) == CopyOnWriteBit ? cowId : uint.MaxValue; - // TODO: Only Set On Real Content Updation public bool IsUpdated { get; set; } @@ -125,199 +72,11 @@ public Chunk(Int3 position, World world, uint other) public World World { get; } - public BlockData* Blocks { get; private set; } - - public BlockData this[int x, int y, int z] - { - get => Blocks[(x << BitShiftX) | (y << BitShiftY) | z]; - set - { - if ((flags & CopyOnWriteBit) == CopyOnWriteBit) - ExecuteFullCopy(); - Blocks[(x << BitShiftX) | (y << BitShiftY) | z] = value; - IsUpdated = true; - } - } - - public BlockData this[Int3 pos] - { - get => Blocks[(pos.X << BitShiftX) | (pos.Y << BitShiftY) | pos.Z]; - set - { - if ((flags & CopyOnWriteBit) == CopyOnWriteBit) - ExecuteFullCopy(); - Blocks[(pos.X << BitShiftX) | (pos.Y << BitShiftY) | pos.Z] = value; - IsUpdated = true; - } - } - - public void Dispose() - { - ReleaseResources(); - GC.SuppressFinalize(this); - } - - internal void EnableFullArray() - { - flags = 0; - Blocks = ChunkDataAllocator.Allocate(); - } - - internal void EnableCopyOnWrite(uint other) - { - Blocks = StaticChunkPool.GetChunk(other).Blocks; - flags = CopyOnWriteBit; - cowId = other; - } - - private void ExecuteFullCopy() - { - lock (this) - { - if ((flags & CopyOnWriteBit) == CopyOnWriteBit) - { - var old = Blocks; - EnableFullArray(); - for (var i = 0; i < CubeSize; ++i) - Blocks[i] = old[i]; - } - } - } - - public static void SetGenerator(Generator gen) - { - if (!_chunkGeneratorLoaded) - { - _chunkGen = gen; - _chunkGeneratorLoaded = true; - } - else - { - throw new Exception("Chunk Generator Already Loaded"); - } - } - - // Build chunk - private void Build(int daylightBrightness) - { - _chunkGen(new ChunkGeneratorContext(this, daylightBrightness)); - IsUpdated = true; - } - - // Reference Counting - public void MarkRequest() + public void MoveFrom(Chunk other) { - mLastRequestTime = DateTime.Now.Ticks; + MoveFromImpl(other); } - public bool CheckReleaseable() - { - return DateTime.Now - new DateTime(Interlocked.Read(ref mLastRequestTime)) > TimeSpan.FromSeconds(10); - } - - private void ReleaseResources() - { - if ((flags & CopyOnWriteBit) != CopyOnWriteBit) - ChunkDataAllocator.Release(Blocks); - Blocks = null; - } - - ~Chunk() - { - ReleaseResources(); - } - - private static class ChunkDataAllocator - { - internal static BlockData* Allocate() - { - return (BlockData*) Marshal.AllocHGlobal(CubeSize * sizeof(BlockData)).ToPointer(); - } - - internal static void Release(BlockData* data) - { - Marshal.FreeHGlobal((IntPtr) data); - } - } - } - - public static class StaticChunkPool - { - private static uint _airChunkId = uint.MaxValue; - private static List _staticList = new List(); - private static Dictionary _id; - - static StaticChunkPool() - { - _id = new Dictionary(); - Register("Default.AirChunk", new Chunk(new BlockData(0))); - } - - internal static Dictionary Id - { - get => _id; - set - { - var oldId = value; - var old = _staticList; - _id = value; - _staticList = new List(_id.Count); - for (var i = 0; i < _id.Count; ++i) - _staticList.Add(null); - foreach (var record in value) - _staticList[(int) record.Value] = old[(int) oldId[record.Key]]; - } - } - - public static void Register(string name, Chunk staticChunk) - { - if (_id.TryGetValue(name, out var sid)) - if (_staticList[(int) sid] == null) - _staticList[(int) sid] = staticChunk; - - _id.Add(name, (uint) _staticList.Count); - _staticList.Add(staticChunk); - } - - public static uint GetAirChunk() - { - if (_airChunkId == uint.MaxValue) - _airChunkId = _id["Default.AirChunk"]; - return _airChunkId; - } - - public static Chunk GetChunk(uint id) - { - return _staticList[(int) id]; - } - - public static uint GetId(string name) - { - return _id[name]; - } - } - - public class ChunkManager : Dictionary - { - public bool IsLoaded(Int3 chunkPos) - { - return ContainsKey(chunkPos); - } - - // Convert world position to chunk coordinate (all axes) - public static Int3 GetPos(Int3 pos) - { - return new Int3(pos.X >> Chunk.SizeLog2, pos.Y >> Chunk.SizeLog2, pos.Z >> Chunk.SizeLog2); - } - - public BlockData GetBlock(Int3 pos) - { - return this[GetPos(pos)][pos.X & Chunk.AxisBits, pos.Y & Chunk.AxisBits, pos.Z & Chunk.AxisBits]; - } - - public void SetBlock(Int3 pos, BlockData block) - { - this[GetPos(pos)][pos.X & Chunk.AxisBits, pos.Y & Chunk.AxisBits, pos.Z & Chunk.AxisBits] = block; - } + partial void MoveFromImpl(Chunk other); } } \ No newline at end of file diff --git a/Game/World/ChunkAccess.cs b/Game/World/ChunkAccess.cs new file mode 100644 index 0000000..15f2d4d --- /dev/null +++ b/Game/World/ChunkAccess.cs @@ -0,0 +1,31 @@ +using Xenko.Core.Mathematics; + +namespace Game.World +{ + public unsafe partial class Chunk + { + public BlockData this[int x, int y, int z] + { + get => Blocks[(x << BitShiftX) | (y << BitShiftY) | z]; + set + { + if (IsCopyOnWrite()) + ExecuteFullCopy(); + Blocks[(x << BitShiftX) | (y << BitShiftY) | z] = value; + IsUpdated = true; + } + } + + public BlockData this[Int3 pos] + { + get => Blocks[(pos.X << BitShiftX) | (pos.Y << BitShiftY) | pos.Z]; + set + { + if (IsCopyOnWrite()) + ExecuteFullCopy(); + Blocks[(pos.X << BitShiftX) | (pos.Y << BitShiftY) | pos.Z] = value; + IsUpdated = true; + } + } + } +} \ No newline at end of file diff --git a/Game/World/ChunkConstants.cs b/Game/World/ChunkConstants.cs new file mode 100644 index 0000000..3623c37 --- /dev/null +++ b/Game/World/ChunkConstants.cs @@ -0,0 +1,14 @@ +namespace Game.World +{ + public partial class Chunk + { + public const int SizeLog2 = 5; + public const int RowSize = 32; + public const int RowLast = RowSize - 1; + public const int SliceSize = RowSize * RowSize; + public const int CubeSize = SliceSize * RowSize; + public const int BitShiftX = SizeLog2 * 2; + public const int BitShiftY = SizeLog2; + public const int AxisBits = 0b11111; + } +} \ No newline at end of file diff --git a/Game/World/ChunkDisposePattern.cs b/Game/World/ChunkDisposePattern.cs new file mode 100644 index 0000000..57912af --- /dev/null +++ b/Game/World/ChunkDisposePattern.cs @@ -0,0 +1,20 @@ +using System; + +namespace Game.World +{ + public partial class Chunk: IDisposable + { + public void Dispose() + { + ReleaseCriticalResources(); + GC.SuppressFinalize(this); + } + + partial void ReleaseCriticalResources(); + + ~Chunk() + { + ReleaseCriticalResources(); + } + } +} \ No newline at end of file diff --git a/Game/World/ChunkGenerator.cs b/Game/World/ChunkGenerator.cs new file mode 100644 index 0000000..0b78a23 --- /dev/null +++ b/Game/World/ChunkGenerator.cs @@ -0,0 +1,54 @@ +using System; + +namespace Game.World +{ + public class ChunkGeneratorContext + { + public readonly Chunk Current; + + public readonly int DaylightBrightness; + + public ChunkGeneratorContext(Chunk current, int daylightBrightness) + { + Current = current; + DaylightBrightness = daylightBrightness; + } + + public void EnableCopyOnWrite(uint target) + { + Current.EnableCopyOnWrite(target); + } + + public void EnableFullArray() + { + Current.EnableFullArray(); + } + } + + public partial class Chunk + { + public delegate void Generator(ChunkGeneratorContext context); + + private static bool _chunkGeneratorLoaded; + private static Generator _chunkGen; + + public static void SetGenerator(Generator gen) + { + if (!_chunkGeneratorLoaded) + { + _chunkGen = gen; + _chunkGeneratorLoaded = true; + } + else + { + throw new Exception("Chunk Generator Already Loaded"); + } + } + + private void Build(int daylightBrightness) + { + _chunkGen(new ChunkGeneratorContext(this, daylightBrightness)); + IsUpdated = true; + } + } +} \ No newline at end of file diff --git a/Game/World/ChunkManager.cs b/Game/World/ChunkManager.cs new file mode 100644 index 0000000..462f9c7 --- /dev/null +++ b/Game/World/ChunkManager.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using Xenko.Core.Mathematics; + +namespace Game.World +{ + public class ChunkManager : Dictionary + { + public bool IsLoaded(Int3 chunkPos) + { + return ContainsKey(chunkPos); + } + + // Convert world position to chunk coordinate (all axes) + public static Int3 GetPos(Int3 pos) + { + return new Int3(pos.X >> Chunk.SizeLog2, pos.Y >> Chunk.SizeLog2, pos.Z >> Chunk.SizeLog2); + } + + public BlockData GetBlock(Int3 pos) + { + return this[GetPos(pos)][pos.X & Chunk.AxisBits, pos.Y & Chunk.AxisBits, pos.Z & Chunk.AxisBits]; + } + + public void SetBlock(Int3 pos, BlockData block) + { + this[GetPos(pos)][pos.X & Chunk.AxisBits, pos.Y & Chunk.AxisBits, pos.Z & Chunk.AxisBits] = block; + } + } +} \ No newline at end of file diff --git a/Game/World/ChunkReleaseTimer.cs b/Game/World/ChunkReleaseTimer.cs new file mode 100644 index 0000000..d856ca0 --- /dev/null +++ b/Game/World/ChunkReleaseTimer.cs @@ -0,0 +1,20 @@ +using System; +using System.Threading; + +namespace Game.World +{ + public partial class Chunk + { + private long mLastRequestTime; + + public bool CheckReleaseable() + { + return DateTime.Now - new DateTime(Interlocked.Read(ref mLastRequestTime)) > TimeSpan.FromSeconds(10); + } + + public void MarkRequest() + { + mLastRequestTime = DateTime.Now.Ticks; + } + } +} \ No newline at end of file diff --git a/Game/World/ChunkStorage.cs b/Game/World/ChunkStorage.cs new file mode 100644 index 0000000..dedabf0 --- /dev/null +++ b/Game/World/ChunkStorage.cs @@ -0,0 +1,80 @@ +using System; +using System.Runtime.InteropServices; + +namespace Game.World +{ + public unsafe partial class Chunk + { + private static class Allocator + { + internal static BlockData* Allocate() + { + return (BlockData*) Marshal.AllocHGlobal(CubeSize * sizeof(BlockData)).ToPointer(); + } + + internal static void Release(BlockData* data) + { + Marshal.FreeHGlobal((IntPtr) data); + } + } + + public BlockData* Blocks { get; private set; } + public uint CopyOnWrite { get; private set; } = uint.MaxValue; + + internal void EnableFullArray() + { + CopyOnWrite = uint.MaxValue; + Blocks = Allocator.Allocate(); + } + + internal void EnableCopyOnWrite(uint other) + { + Blocks = StaticChunkPool.GetChunk(other).Blocks; + CopyOnWrite = other; + } + + private bool IsCopyOnWrite() + { + return CopyOnWrite != uint.MaxValue; + } + + private void ExecuteFullCopy() + { + lock (this) + { + if (IsCopyOnWrite()) + { + var old = Blocks; + EnableFullArray(); + for (var i = 0; i < CubeSize; ++i) + Blocks[i] = old[i]; + } + } + } + + private void ReleaseBlockData() + { + if (Blocks != null) + { + if (!IsCopyOnWrite()) + Allocator.Release(Blocks); + Blocks = null; + } + } + + partial void MoveFromImpl(Chunk other) + { + ReleaseBlockData(); + CopyOnWrite = other.CopyOnWrite; + Blocks = other.Blocks; + other.Blocks = null; + other.CopyOnWrite = uint.MaxValue; + IsUpdated = true; + } + + partial void ReleaseCriticalResources() + { + ReleaseBlockData(); + } + } +} diff --git a/Game/World/StaticChunkPool.cs b/Game/World/StaticChunkPool.cs new file mode 100644 index 0000000..5ea50cd --- /dev/null +++ b/Game/World/StaticChunkPool.cs @@ -0,0 +1,60 @@ +using System.Collections.Generic; + +namespace Game.World +{ + public static class StaticChunkPool + { + private static uint _airChunkId = uint.MaxValue; + private static List _staticList = new List(); + private static Dictionary _id; + + static StaticChunkPool() + { + _id = new Dictionary(); + Register("Default.AirChunk", new Chunk(new BlockData(0))); + } + + internal static Dictionary Id + { + get => _id; + set + { + var oldId = value; + var old = _staticList; + _id = value; + _staticList = new List(_id.Count); + for (var i = 0; i < _id.Count; ++i) + _staticList.Add(null); + foreach (var record in value) + _staticList[(int) record.Value] = old[(int) oldId[record.Key]]; + } + } + + public static void Register(string name, Chunk staticChunk) + { + if (_id.TryGetValue(name, out var sid)) + if (_staticList[(int) sid] == null) + _staticList[(int) sid] = staticChunk; + + _id.Add(name, (uint) _staticList.Count); + _staticList.Add(staticChunk); + } + + public static uint GetAirChunk() + { + if (_airChunkId == uint.MaxValue) + _airChunkId = _id["Default.AirChunk"]; + return _airChunkId; + } + + public static Chunk GetChunk(uint id) + { + return _staticList[(int) id]; + } + + public static uint GetId(string name) + { + return _id[name]; + } + } +} \ No newline at end of file diff --git a/Game/World/World.cs b/Game/World/World.cs index c01606a..4fc328a 100644 --- a/Game/World/World.cs +++ b/Game/World/World.cs @@ -34,12 +34,9 @@ public partial class World new Int3(0, 0, 1), new Int3(0, 0, -1) }; - protected static uint IdCount; - private readonly Double3 hitboxOffset = new Double3(1.0, 1.0, 1.0); // All Chunks (Chunk array) - private readonly object mutex = new object(); public World(string name) { @@ -103,7 +100,6 @@ public void SetBlock(ref Int3 pos, BlockData block) private void InsertChunk(Chunk chunk) { Chunks.Add(chunk.Position, chunk); - return; } public Chunk InsertChunkAndUpdate(Chunk chunk) @@ -117,8 +113,7 @@ public Chunk InsertChunkAndUpdate(Chunk chunk) private void ResetChunk(Chunk ptr) { - Chunks[ptr.Position].Dispose(); - Chunks[ptr.Position] = ptr; + Chunks[ptr.Position].MoveFrom(ptr); } public List GetHitboxes(Aabb range) @@ -141,16 +136,6 @@ public List GetHitboxes(Aabb range) return res; } - public void UpdateChunkLoadStatus() - { - lock (mutex) - { - foreach (var kvPair in Chunks.ToList()) - if (kvPair.Value.CheckReleaseable()) - Chunks.Remove(kvPair.Key); - } - } - private void ResetChunkAndUpdate(Chunk chunk) { ResetChunk(chunk); diff --git a/NEWorld.Windows/NEWorldApp.cs b/NEWorld.Windows/NEWorldApp.cs index fbaaf86..c0bcd3b 100644 --- a/NEWorld.Windows/NEWorldApp.cs +++ b/NEWorld.Windows/NEWorldApp.cs @@ -4,10 +4,13 @@ internal class NEWorldApp { private static void Main(string[] args) { + // TODO: Remove Later when Launching Server with Client is Possible + var server = System.Diagnostics.Process.Start("NEWorldShell.exe"); using (var game = new Xenko.Engine.Game()) { game.Run(); } + server?.Kill(); } } } \ No newline at end of file From 117b7643117b77be335a31d0bfb89984c8ef06b6 Mon Sep 17 00:00:00 2001 From: DWVoid Date: Fri, 8 Feb 2019 00:03:49 +0800 Subject: [PATCH 12/23] Refactor out chunk serialize methods --- Game/Client/PlayerChunkView.cs | 36 ++++++++++++++++++++++ Game/Network/Protocols.cs | 27 +++-------------- Game/World/ChunkAccess.cs | 55 ++++++++++++++++++++++++++++++++++ Game/World/WorldTasks.cs | 3 +- NEWorld.sln.DotSettings | 20 +++++++++++++ 5 files changed, 116 insertions(+), 25 deletions(-) create mode 100644 Game/Client/PlayerChunkView.cs create mode 100644 NEWorld.sln.DotSettings diff --git a/Game/Client/PlayerChunkView.cs b/Game/Client/PlayerChunkView.cs new file mode 100644 index 0000000..99544d1 --- /dev/null +++ b/Game/Client/PlayerChunkView.cs @@ -0,0 +1,36 @@ +using Game.World; +using Xenko.Core.Mathematics; + +namespace Game.Client +{ + public class PlayerChunkView + { + private const int SectionMask = 0b1111; + + private const int SectionBits = 4; + + private Chunk[,,][,,] Section; + + private Int3 BasePosition; + + private Chunk[,,] GetSectionRelative(Int3 offset) + { + return Section[offset.X >> SectionBits, offset.Y >> SectionBits, offset.Y >> SectionBits]; + } + + private Chunk GetChunkRelative(Int3 offset) + { + return GetSectionRelative(offset)[offset.X & SectionMask, offset.Y & SectionMask, offset.Z & SectionMask]; + } + + private Int3 ComputeRelative(Int3 absolute) + { + return absolute - BasePosition; + } + + public Chunk GetChunk(Int3 chunk) + { + return GetChunkRelative(ComputeRelative(chunk)); + } + } +} \ No newline at end of file diff --git a/Game/Network/Protocols.cs b/Game/Network/Protocols.cs index f45ac04..1e0dc76 100644 --- a/Game/Network/Protocols.cs +++ b/Game/Network/Protocols.cs @@ -100,18 +100,10 @@ public override void HandleRequest(Session.Receive stream) } } - private static unsafe byte[] Get(Chunk chunkPtr) + private static byte[] Get(Chunk chunk) { var chunkData = LocalMemCache.Value ?? (LocalMemCache.Value = new byte[32768 * 4]); - for (var i = 0; i < 32768 * 4; ++i) - { - var block = chunkPtr.Blocks[i >> 2]; - chunkData[i++] = (byte) (block.Id >> 4); - chunkData[i++] = (byte) ((block.Id << 4) | block.Brightness); - chunkData[i++] = (byte) (block.Data >> 8); - chunkData[i] = (byte) block.Data; - } - + chunk.SerializeTo(chunkData); return chunkData; } @@ -136,10 +128,6 @@ private static Chunk GetChunk(uint worldId, Int3 position) public class Client : Protocol { - public Client() - { - } - public override string Name() { return "GetChunk"; @@ -168,17 +156,10 @@ public override void HandleRequest(Session.Receive request) ChunkService.TaskDispatcher.Add(new World.World.ResetChunkTask(chk)); } - private static unsafe Chunk DeserializeChunk(Int3 chunkPos, World.World world, byte[] data) + private static Chunk DeserializeChunk(Int3 chunkPos, World.World world, byte[] data) { var chk = new Chunk(chunkPos, world, Chunk.InitOption.AllocateUnique); - for (var i = 0; i < 32768 * 4; i += 4) - { - ref var block = ref chk.Blocks[i >> 2]; - block.Id = (ushort) ((data[i] << 4) | (data[i + 1] >> 4)); - block.Brightness = (byte) (data[i + 1] | 0xF); - block.Data = (uint) ((data[i + 2] << 8) | data[i + 3]); - } - + chk.DeserializeFrom(data); return chk; } diff --git a/Game/World/ChunkAccess.cs b/Game/World/ChunkAccess.cs index 15f2d4d..f05b82f 100644 --- a/Game/World/ChunkAccess.cs +++ b/Game/World/ChunkAccess.cs @@ -27,5 +27,60 @@ public BlockData this[Int3 pos] IsUpdated = true; } } + + public void SerializeTo(byte[] data) + { + fixed (byte* buffer = data) + { + SerializeTo(buffer); + } + } + + public void SerializeTo(byte[] data, int offset) + { + fixed (byte* buffer = data) + { + SerializeTo(buffer + offset); + } + } + + public void SerializeTo(byte* buffer) + { + for (var i = 0; i < 32768 * 4; ++i) + { + var block = Blocks[i >> 2]; + buffer[i++] = (byte) (block.Id >> 4); + buffer[i++] = (byte) ((block.Id << 4) | block.Brightness); + buffer[i++] = (byte) (block.Data >> 8); + buffer[i] = (byte) block.Data; + } + } + + public void DeserializeFrom(byte[] data) + { + fixed (byte* buffer = data) + { + DeserializeFrom(buffer); + } + } + + public void DeserializeFrom(byte[] data, int offset) + { + fixed (byte* buffer = data) + { + DeserializeFrom(buffer + offset); + } + } + + public void DeserializeFrom(byte* buffer) + { + for (var i = 0; i < 32768 * 4; i += 4) + { + ref var block = ref Blocks[i >> 2]; + block.Id = (ushort) ((buffer[i] << 4) | (buffer[i + 1] >> 4)); + block.Brightness = (byte) (buffer[i + 1] | 0xF); + block.Data = (uint) ((buffer[i + 2] << 8) | buffer[i + 3]); + } + } } } \ No newline at end of file diff --git a/Game/World/WorldTasks.cs b/Game/World/WorldTasks.cs index 97f0938..8a4d168 100644 --- a/Game/World/WorldTasks.cs +++ b/Game/World/WorldTasks.cs @@ -19,7 +19,6 @@ using System; using System.Threading; -using Game.Network; using Game.Utilities; using Xenko.Core.Mathematics; @@ -66,7 +65,7 @@ internal LoadTask(World world, Int3 chunkPosition) // Adding Sentry chunk = new Chunk(chunkPosition, world, Chunk.InitOption.None); ChunkService.TaskDispatcher.Add(new LocalLoadTask(world, chunkPosition, this)); - if (!ChunkService.IsAuthority) Client.GetChunk.Call(world.Id, chunkPosition); + if (!ChunkService.IsAuthority) Network.Client.GetChunk.Call(world.Id, chunkPosition); } public void Task() diff --git a/NEWorld.sln.DotSettings b/NEWorld.sln.DotSettings new file mode 100644 index 0000000..64e81af --- /dev/null +++ b/NEWorld.sln.DotSettings @@ -0,0 +1,20 @@ + + // +// $SOLUTION$:$PROJECT$: $FILENAME$ +// NEWorld: A Free Game with Similar Rules to Minecraft. +// Copyright (C) 2015-$CURRENT_YEAR$ NEWorld Team +// +// NEWorld is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// NEWorld is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General +// Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with NEWorld. If not, see <http://www.gnu.org/licenses/>. +// + \ No newline at end of file From bf1b8f077c28a1b6765cb867c00d4fba419a4d23 Mon Sep 17 00:00:00 2001 From: DWVoid Date: Sat, 9 Feb 2019 18:23:58 +0800 Subject: [PATCH 13/23] Simplify Task Pattern and Update Headers --- Core/Generic.cs | 5 +- Core/LogPort.cs | 20 ++++- Core/Math/Mat4.cs | 5 +- Core/Module/Module.cs | 5 +- Core/Network/Client.cs | 5 +- Core/Network/ConnectionHost.cs | 5 +- Core/Network/Protocol.cs | 5 +- Core/Network/Protocols.cs | 5 +- Core/Network/Server.cs | 5 +- Core/Path.cs | 5 +- Core/Services.cs | 5 +- Core/Utilities/RateController.cs | 5 +- Core/Utilities/Singleton.cs | 5 +- Game/ChunkService.cs | 5 +- Game/Client/PlayerChunkView.cs | 20 ++++- Game/Network/Client.cs | 5 +- Game/Network/Protocols.cs | 5 +- Game/Network/Server.cs | 5 +- Game/TaskDispatcher.cs | 101 ++++++++++++++++-------- Game/Terrain/Block.cs | 5 +- Game/Utilities/Aabb.cs | 5 +- Game/Utilities/OrderedList.cs | 5 +- Game/World/Blocks.cs | 5 +- Game/World/Chunk.cs | 5 +- Game/World/ChunkAccess.cs | 18 +++++ Game/World/ChunkConstants.cs | 18 +++++ Game/World/ChunkDisposePattern.cs | 18 +++++ Game/World/ChunkGenerator.cs | 18 +++++ Game/World/ChunkManager.cs | 18 +++++ Game/World/ChunkReleaseTimer.cs | 18 +++++ Game/World/ChunkStorage.cs | 18 +++++ Game/World/ChunkUpdate.cs | 76 ++++++++++++++++++ Game/World/Object.cs | 5 +- Game/World/Player.cs | 5 +- Game/World/PlayerObject.cs | 5 +- Game/World/StaticChunkPool.cs | 18 +++++ Game/World/World.cs | 5 +- Game/World/WorldTasks.cs | 36 +++------ Main/Main.cs | 5 +- NEWorld.Linux/NEWorldApp.cs | 18 +++++ NEWorld.Windows/NEWorldApp.cs | 18 +++++ NEWorld.macOS/NEWorldApp.cs | 18 +++++ NEWorld.sln.DotSettings | 39 ++++----- NEWorld/BasicCameraController.cs | 20 ++++- NEWorld/MainScript.cs | 5 +- NEWorld/Renderer/RdChunk.cs | 34 +++----- NEWorld/Renderer/RdTextures.cs | 20 ++++- NEWorld/Renderer/RdWorld.cs | 76 +++++++----------- NEWorld/Renderer/VertexBuilder.cs | 20 ++++- NEWorldShell/Cli.cs | 7 +- NEWorldShell/Command.cs | 5 +- NEWorldShell/Program.cs | 5 +- NEWorldShell/Properties/AssemblyInfo.cs | 5 +- 53 files changed, 571 insertions(+), 246 deletions(-) create mode 100644 Game/World/ChunkUpdate.cs diff --git a/Core/Generic.cs b/Core/Generic.cs index f5389cd..54dfe9a 100644 --- a/Core/Generic.cs +++ b/Core/Generic.cs @@ -1,7 +1,7 @@ // -// Core: Generic.cs +// NEWorld/Core: Generic.cs // NEWorld: A Free Game with Similar Rules to Minecraft. -// Copyright (C) 2015-2018 NEWorld Team +// Copyright (C) 2015-2019 NEWorld Team // // NEWorld is free software: you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published @@ -16,7 +16,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with NEWorld. If not, see . // - namespace Core { public static class Generic diff --git a/Core/LogPort.cs b/Core/LogPort.cs index a7be568..508e928 100644 --- a/Core/LogPort.cs +++ b/Core/LogPort.cs @@ -1,4 +1,22 @@ -using System; +// +// NEWorld/Core: LogPort.cs +// NEWorld: A Free Game with Similar Rules to Minecraft. +// Copyright (C) 2015-2019 NEWorld Team +// +// NEWorld is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// NEWorld is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General +// Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with NEWorld. If not, see . +// +using System; using Xenko.Core.Diagnostics; namespace Core diff --git a/Core/Math/Mat4.cs b/Core/Math/Mat4.cs index 7b61041..9943c66 100644 --- a/Core/Math/Mat4.cs +++ b/Core/Math/Mat4.cs @@ -1,7 +1,7 @@ // -// Core: Mat4.cs +// NEWorld/Core: Mat4.cs // NEWorld: A Free Game with Similar Rules to Minecraft. -// Copyright (C) 2015-2018 NEWorld Team +// Copyright (C) 2015-2019 NEWorld Team // // NEWorld is free software: you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published @@ -16,7 +16,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with NEWorld. If not, see . // - using System.Collections.Generic; using Xenko.Core.Mathematics; diff --git a/Core/Module/Module.cs b/Core/Module/Module.cs index 3f708a9..2608a55 100644 --- a/Core/Module/Module.cs +++ b/Core/Module/Module.cs @@ -1,7 +1,7 @@ // -// Core: Module.cs +// NEWorld/Core: Module.cs // NEWorld: A Free Game with Similar Rules to Minecraft. -// Copyright (C) 2015-2018 NEWorld Team +// Copyright (C) 2015-2019 NEWorld Team // // NEWorld is free software: you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published @@ -16,7 +16,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with NEWorld. If not, see . // - using System; using System.Collections.Generic; using System.Reflection; diff --git a/Core/Network/Client.cs b/Core/Network/Client.cs index 63ec21a..193fd65 100644 --- a/Core/Network/Client.cs +++ b/Core/Network/Client.cs @@ -1,7 +1,7 @@ // -// Core: Client.cs +// NEWorld/Core: Client.cs // NEWorld: A Free Game with Similar Rules to Minecraft. -// Copyright (C) 2015-2018 NEWorld Team +// Copyright (C) 2015-2019 NEWorld Team // // NEWorld is free software: you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published @@ -16,7 +16,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with NEWorld. If not, see . // - using System; using System.Collections.Generic; using System.Net.Sockets; diff --git a/Core/Network/ConnectionHost.cs b/Core/Network/ConnectionHost.cs index 84e318c..d761938 100644 --- a/Core/Network/ConnectionHost.cs +++ b/Core/Network/ConnectionHost.cs @@ -1,7 +1,7 @@ // -// Core: ConnectionHost.cs +// NEWorld/Core: ConnectionHost.cs // NEWorld: A Free Game with Similar Rules to Minecraft. -// Copyright (C) 2015-2018 NEWorld Team +// Copyright (C) 2015-2019 NEWorld Team // // NEWorld is free software: you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published @@ -16,7 +16,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with NEWorld. If not, see . // - using System; using System.Collections.Generic; using System.Diagnostics; diff --git a/Core/Network/Protocol.cs b/Core/Network/Protocol.cs index 5f29b07..ebd9491 100644 --- a/Core/Network/Protocol.cs +++ b/Core/Network/Protocol.cs @@ -1,7 +1,7 @@ // -// Core: Protocol.cs +// NEWorld/Core: Protocol.cs // NEWorld: A Free Game with Similar Rules to Minecraft. -// Copyright (C) 2015-2018 NEWorld Team +// Copyright (C) 2015-2019 NEWorld Team // // NEWorld is free software: you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published @@ -16,7 +16,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with NEWorld. If not, see . // - namespace Core.Network { public abstract class Protocol diff --git a/Core/Network/Protocols.cs b/Core/Network/Protocols.cs index e22fd81..d2b4855 100644 --- a/Core/Network/Protocols.cs +++ b/Core/Network/Protocols.cs @@ -1,7 +1,7 @@ // -// Core: Protocols.cs +// NEWorld/Core: Protocols.cs // NEWorld: A Free Game with Similar Rules to Minecraft. -// Copyright (C) 2015-2018 NEWorld Team +// Copyright (C) 2015-2019 NEWorld Team // // NEWorld is free software: you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published @@ -16,7 +16,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with NEWorld. If not, see . // - using System; using System.Collections.Concurrent; using System.Collections.Generic; diff --git a/Core/Network/Server.cs b/Core/Network/Server.cs index c10704c..51354d4 100644 --- a/Core/Network/Server.cs +++ b/Core/Network/Server.cs @@ -1,7 +1,7 @@ // -// Core: Server.cs +// NEWorld/Core: Server.cs // NEWorld: A Free Game with Similar Rules to Minecraft. -// Copyright (C) 2015-2018 NEWorld Team +// Copyright (C) 2015-2019 NEWorld Team // // NEWorld is free software: you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published @@ -16,7 +16,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with NEWorld. If not, see . // - using System.Collections.Generic; using System.Net; using System.Net.Sockets; diff --git a/Core/Path.cs b/Core/Path.cs index 7f44971..9f671e5 100644 --- a/Core/Path.cs +++ b/Core/Path.cs @@ -1,7 +1,7 @@ // -// Core: Path.cs +// NEWorld/Core: Path.cs // NEWorld: A Free Game with Similar Rules to Minecraft. -// Copyright (C) 2015-2018 NEWorld Team +// Copyright (C) 2015-2019 NEWorld Team // // NEWorld is free software: you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published @@ -16,7 +16,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with NEWorld. If not, see . // - using System; namespace Core diff --git a/Core/Services.cs b/Core/Services.cs index 22af4d7..a8ecb83 100644 --- a/Core/Services.cs +++ b/Core/Services.cs @@ -1,7 +1,7 @@ // -// Core: Services.cs +// NEWorld/Core: Services.cs // NEWorld: A Free Game with Similar Rules to Minecraft. -// Copyright (C) 2015-2018 NEWorld Team +// Copyright (C) 2015-2019 NEWorld Team // // NEWorld is free software: you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published @@ -16,7 +16,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with NEWorld. If not, see . // - using System; using System.Collections.Generic; using System.Reflection; diff --git a/Core/Utilities/RateController.cs b/Core/Utilities/RateController.cs index a525cb4..70d07fb 100644 --- a/Core/Utilities/RateController.cs +++ b/Core/Utilities/RateController.cs @@ -1,7 +1,7 @@ // -// Core: RateController.cs +// NEWorld/Core: RateController.cs // NEWorld: A Free Game with Similar Rules to Minecraft. -// Copyright (C) 2015-2018 NEWorld Team +// Copyright (C) 2015-2019 NEWorld Team // // NEWorld is free software: you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published @@ -16,7 +16,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with NEWorld. If not, see . // - using System; using System.Threading; diff --git a/Core/Utilities/Singleton.cs b/Core/Utilities/Singleton.cs index 5053fd3..da9c8bc 100644 --- a/Core/Utilities/Singleton.cs +++ b/Core/Utilities/Singleton.cs @@ -1,7 +1,7 @@ // -// Core: Singleton.cs +// NEWorld/Core: Singleton.cs // NEWorld: A Free Game with Similar Rules to Minecraft. -// Copyright (C) 2015-2018 NEWorld Team +// Copyright (C) 2015-2019 NEWorld Team // // NEWorld is free software: you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published @@ -16,7 +16,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with NEWorld. If not, see . // - using System; using System.Reflection; diff --git a/Game/ChunkService.cs b/Game/ChunkService.cs index 1f3fc01..b4aaae1 100644 --- a/Game/ChunkService.cs +++ b/Game/ChunkService.cs @@ -1,7 +1,7 @@ // -// Game: ChunkService.cs +// NEWorld/Game: ChunkService.cs // NEWorld: A Free Game with Similar Rules to Minecraft. -// Copyright (C) 2015-2018 NEWorld Team +// Copyright (C) 2015-2019 NEWorld Team // // NEWorld is free software: you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published @@ -16,7 +16,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with NEWorld. If not, see . // - using Core; using Game.World; diff --git a/Game/Client/PlayerChunkView.cs b/Game/Client/PlayerChunkView.cs index 99544d1..36349d6 100644 --- a/Game/Client/PlayerChunkView.cs +++ b/Game/Client/PlayerChunkView.cs @@ -1,4 +1,22 @@ -using Game.World; +// +// NEWorld/Game: PlayerChunkView.cs +// NEWorld: A Free Game with Similar Rules to Minecraft. +// Copyright (C) 2015-2019 NEWorld Team +// +// NEWorld is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// NEWorld is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General +// Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with NEWorld. If not, see . +// +using Game.World; using Xenko.Core.Mathematics; namespace Game.Client diff --git a/Game/Network/Client.cs b/Game/Network/Client.cs index 36b896f..0972f8b 100644 --- a/Game/Network/Client.cs +++ b/Game/Network/Client.cs @@ -1,7 +1,7 @@ // -// Game: Client.cs +// NEWorld/Game: Client.cs // NEWorld: A Free Game with Similar Rules to Minecraft. -// Copyright (C) 2015-2018 NEWorld Team +// Copyright (C) 2015-2019 NEWorld Team // // NEWorld is free software: you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published @@ -16,7 +16,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with NEWorld. If not, see . // - using System; using System.Threading.Tasks; using Core; diff --git a/Game/Network/Protocols.cs b/Game/Network/Protocols.cs index 1e0dc76..62ef3af 100644 --- a/Game/Network/Protocols.cs +++ b/Game/Network/Protocols.cs @@ -1,7 +1,7 @@ // -// Game: Protocols.cs +// NEWorld/Game: Protocols.cs // NEWorld: A Free Game with Similar Rules to Minecraft. -// Copyright (C) 2015-2018 NEWorld Team +// Copyright (C) 2015-2019 NEWorld Team // // NEWorld is free software: you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published @@ -16,7 +16,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with NEWorld. If not, see . // - using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; diff --git a/Game/Network/Server.cs b/Game/Network/Server.cs index 273023c..a89a913 100644 --- a/Game/Network/Server.cs +++ b/Game/Network/Server.cs @@ -1,7 +1,7 @@ // -// Game: Server.cs +// NEWorld/Game: Server.cs // NEWorld: A Free Game with Similar Rules to Minecraft. -// Copyright (C) 2015-2018 NEWorld Team +// Copyright (C) 2015-2019 NEWorld Team // // NEWorld is free software: you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published @@ -16,7 +16,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with NEWorld. If not, see . // - using System; using System.Threading.Tasks; using Core; diff --git a/Game/TaskDispatcher.cs b/Game/TaskDispatcher.cs index 68c6b87..db3cf87 100644 --- a/Game/TaskDispatcher.cs +++ b/Game/TaskDispatcher.cs @@ -1,7 +1,7 @@ // -// Game: TaskDispatcher.cs +// NEWorld/Game: TaskDispatcher.cs // NEWorld: A Free Game with Similar Rules to Minecraft. -// Copyright (C) 2015-2018 NEWorld Team +// Copyright (C) 2015-2019 NEWorld Team // // NEWorld is free software: you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published @@ -16,10 +16,10 @@ // You should have received a copy of the GNU Lesser General Public License // along with NEWorld. If not, see . // - using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Runtime.CompilerServices; using System.Threading; using Core; using Core.Utilities; @@ -31,18 +31,6 @@ public interface IInstancedTask void Task(int instance, int instanceCount); } - /** - * \brief This type of tasks will be executed concurrently. - * Note that "ReadOnly" here is with respect to chunks - * data specifically. However please be aware of - * thread safety when you write something other than - * chunks. - */ - public interface IReadOnlyTask - { - void Task(); - } - /** * \brief This type of tasks will be executed in one thread. * Thus, it is safe to do write opeartions inside @@ -53,14 +41,6 @@ public interface IReadWriteTask void Task(); } - /** - * \brief This type of tasks will be executed in main thread. - * Thus, it is safe to call OpenGL function inside. - */ - public interface IRenderTask - { - void Task(); - } public interface IRegularReadOnlyTask : IInstancedTask { @@ -73,16 +53,69 @@ public class TaskDispatcher : IDisposable // TODO: replace it with lock-free structure. private readonly object mutex; - private readonly ConcurrentBag readOnlyTasks; + private readonly ConcurrentBag readOnlyTasks; private readonly List regularReadOnlyTasks; private readonly List regularReadWriteTasks; private readonly List threads; private RateController meter = new RateController(30); private List readWriteTasks, nextReadWriteTasks; - private List renderTasks, nextRenderTasks; + private List renderTasks, nextRenderTasks; private bool shouldExit; + public struct NextReadOnlyChanceS + { + public struct Awaiter : INotifyCompletion + { + public bool IsCompleted => false; + + public void GetResult() + { + } + + public void OnCompleted(Action continuation) + { + ChunkService.TaskDispatcher.AddReadOnlyTask(continuation); + } + } + + public Awaiter GetAwaiter() { return new Awaiter();} + } + + /** + * \brief This type of tasks will be executed concurrently. + * Note that "ReadOnly" here is with respect to chunks + * data specifically. However please be aware of + * thread safety when you write something other than + * chunks. + */ + public NextReadOnlyChanceS NextReadOnlyChance() { return new NextReadOnlyChanceS(); } + + public struct NextRenderChanceS + { + public struct Awaiter : INotifyCompletion + { + public bool IsCompleted => false; + + public void GetResult() + { + } + + public void OnCompleted(Action continuation) + { + ChunkService.TaskDispatcher.AddRenderTask(continuation); + } + } + + public Awaiter GetAwaiter() { return new Awaiter();} + } + + /** + * \brief This type of tasks will be executed in main thread. + * Thus, it is safe to call OpenGL function inside. + */ + public NextRenderChanceS NextRenderChance() { return new NextRenderChanceS(); } + // Automatic Creation. We reserve one virtual processor for main thread public TaskDispatcher() : this(Environment.ProcessorCount - 1) { @@ -99,13 +132,13 @@ private TaskDispatcher(int threadNumber) threads = new List(threadNumber); TimeUsed = new int[threadNumber]; mutex = new object(); - readOnlyTasks = new ConcurrentBag(); + readOnlyTasks = new ConcurrentBag(); regularReadOnlyTasks = new List(); readWriteTasks = new List(); nextReadWriteTasks = new List(); regularReadWriteTasks = new List(); - renderTasks = new List(); - nextRenderTasks = new List(); + renderTasks = new List(); + nextRenderTasks = new List(); } public int[] TimeUsed { get; } @@ -133,7 +166,7 @@ public void Start() } } - public void Add(IReadOnlyTask task) + private void AddReadOnlyTask(Action task) { readOnlyTasks.Add(task); } @@ -145,8 +178,8 @@ public void Add(IReadWriteTask task) nextReadWriteTasks.Add(task); } } - - public void Add(IRenderTask task) + + private void AddRenderTask(Action task) { lock (mutex) { @@ -179,7 +212,7 @@ public void ProcessRenderTasks() lock (mutex) { foreach (var task in renderTasks) - task.Task(); + task(); renderTasks.Clear(); Generic.Swap(ref renderTasks, ref nextRenderTasks); } @@ -220,9 +253,9 @@ private void ProcessReadonlyTasks(int i) regularReadOnlyTasks[cnt].Task(i, TimeUsed.Length); // TODO: Make sure the no further tasks will be added before exiting this function - // TODO: Add a timeout support for this to ensure the updation rate + // TODO: AddReadOnlyTask a timeout support for this to ensure the updation rate while (readOnlyTasks.TryTake(out var task)) - task.Task(); + task(); } private void ProcessReadWriteTasks() diff --git a/Game/Terrain/Block.cs b/Game/Terrain/Block.cs index 3fbe336..92645ca 100644 --- a/Game/Terrain/Block.cs +++ b/Game/Terrain/Block.cs @@ -1,7 +1,7 @@ // -// Game: Block.cs +// NEWorld/Game: Block.cs // NEWorld: A Free Game with Similar Rules to Minecraft. -// Copyright (C) 2015-2018 NEWorld Team +// Copyright (C) 2015-2019 NEWorld Team // // NEWorld is free software: you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published @@ -16,7 +16,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with NEWorld. If not, see . // - using System.Collections.Generic; using Game.World; using Xenko.Core.Mathematics; diff --git a/Game/Utilities/Aabb.cs b/Game/Utilities/Aabb.cs index e7f40d9..c204613 100644 --- a/Game/Utilities/Aabb.cs +++ b/Game/Utilities/Aabb.cs @@ -1,7 +1,7 @@ // -// Game: Aabb.cs +// NEWorld/Game: Aabb.cs // NEWorld: A Free Game with Similar Rules to Minecraft. -// Copyright (C) 2015-2018 NEWorld Team +// Copyright (C) 2015-2019 NEWorld Team // // NEWorld is free software: you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published @@ -16,7 +16,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with NEWorld. If not, see . // - using System; using Xenko.Core.Mathematics; diff --git a/Game/Utilities/OrderedList.cs b/Game/Utilities/OrderedList.cs index cde9d3c..35895eb 100644 --- a/Game/Utilities/OrderedList.cs +++ b/Game/Utilities/OrderedList.cs @@ -1,7 +1,7 @@ // -// Game: OrderedList.cs +// NEWorld/Game: OrderedList.cs // NEWorld: A Free Game with Similar Rules to Minecraft. -// Copyright (C) 2015-2018 NEWorld Team +// Copyright (C) 2015-2019 NEWorld Team // // NEWorld is free software: you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published @@ -16,7 +16,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with NEWorld. If not, see . // - using System; using System.Collections; using System.Collections.Generic; diff --git a/Game/World/Blocks.cs b/Game/World/Blocks.cs index f872ca8..e25da66 100644 --- a/Game/World/Blocks.cs +++ b/Game/World/Blocks.cs @@ -1,7 +1,7 @@ // -// Game: Blocks.cs +// NEWorld/Game: Blocks.cs // NEWorld: A Free Game with Similar Rules to Minecraft. -// Copyright (C) 2015-2018 NEWorld Team +// Copyright (C) 2015-2019 NEWorld Team // // NEWorld is free software: you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published @@ -16,7 +16,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with NEWorld. If not, see . // - namespace Game.World { public struct BlockData diff --git a/Game/World/Chunk.cs b/Game/World/Chunk.cs index f970183..ad9cfd5 100644 --- a/Game/World/Chunk.cs +++ b/Game/World/Chunk.cs @@ -1,7 +1,7 @@ // -// Game: Chunk.cs +// NEWorld/Game: Chunk.cs // NEWorld: A Free Game with Similar Rules to Minecraft. -// Copyright (C) 2015-2018 NEWorld Team +// Copyright (C) 2015-2019 NEWorld Team // // NEWorld is free software: you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published @@ -16,7 +16,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with NEWorld. If not, see . // - using Xenko.Core.Mathematics; namespace Game.World diff --git a/Game/World/ChunkAccess.cs b/Game/World/ChunkAccess.cs index f05b82f..1ce2c9a 100644 --- a/Game/World/ChunkAccess.cs +++ b/Game/World/ChunkAccess.cs @@ -1,3 +1,21 @@ +// +// NEWorld/Game: ChunkAccess.cs +// NEWorld: A Free Game with Similar Rules to Minecraft. +// Copyright (C) 2015-2019 NEWorld Team +// +// NEWorld is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// NEWorld is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General +// Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with NEWorld. If not, see . +// using Xenko.Core.Mathematics; namespace Game.World diff --git a/Game/World/ChunkConstants.cs b/Game/World/ChunkConstants.cs index 3623c37..eeff063 100644 --- a/Game/World/ChunkConstants.cs +++ b/Game/World/ChunkConstants.cs @@ -1,3 +1,21 @@ +// +// NEWorld/Game: ChunkConstants.cs +// NEWorld: A Free Game with Similar Rules to Minecraft. +// Copyright (C) 2015-2019 NEWorld Team +// +// NEWorld is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// NEWorld is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General +// Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with NEWorld. If not, see . +// namespace Game.World { public partial class Chunk diff --git a/Game/World/ChunkDisposePattern.cs b/Game/World/ChunkDisposePattern.cs index 57912af..ddf627d 100644 --- a/Game/World/ChunkDisposePattern.cs +++ b/Game/World/ChunkDisposePattern.cs @@ -1,3 +1,21 @@ +// +// NEWorld/Game: ChunkDisposePattern.cs +// NEWorld: A Free Game with Similar Rules to Minecraft. +// Copyright (C) 2015-2019 NEWorld Team +// +// NEWorld is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// NEWorld is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General +// Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with NEWorld. If not, see . +// using System; namespace Game.World diff --git a/Game/World/ChunkGenerator.cs b/Game/World/ChunkGenerator.cs index 0b78a23..76edacf 100644 --- a/Game/World/ChunkGenerator.cs +++ b/Game/World/ChunkGenerator.cs @@ -1,3 +1,21 @@ +// +// NEWorld/Game: ChunkGenerator.cs +// NEWorld: A Free Game with Similar Rules to Minecraft. +// Copyright (C) 2015-2019 NEWorld Team +// +// NEWorld is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// NEWorld is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General +// Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with NEWorld. If not, see . +// using System; namespace Game.World diff --git a/Game/World/ChunkManager.cs b/Game/World/ChunkManager.cs index 462f9c7..1c3d000 100644 --- a/Game/World/ChunkManager.cs +++ b/Game/World/ChunkManager.cs @@ -1,3 +1,21 @@ +// +// NEWorld/Game: ChunkManager.cs +// NEWorld: A Free Game with Similar Rules to Minecraft. +// Copyright (C) 2015-2019 NEWorld Team +// +// NEWorld is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// NEWorld is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General +// Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with NEWorld. If not, see . +// using System.Collections.Generic; using Xenko.Core.Mathematics; diff --git a/Game/World/ChunkReleaseTimer.cs b/Game/World/ChunkReleaseTimer.cs index d856ca0..f0481f8 100644 --- a/Game/World/ChunkReleaseTimer.cs +++ b/Game/World/ChunkReleaseTimer.cs @@ -1,3 +1,21 @@ +// +// NEWorld/Game: ChunkReleaseTimer.cs +// NEWorld: A Free Game with Similar Rules to Minecraft. +// Copyright (C) 2015-2019 NEWorld Team +// +// NEWorld is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// NEWorld is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General +// Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with NEWorld. If not, see . +// using System; using System.Threading; diff --git a/Game/World/ChunkStorage.cs b/Game/World/ChunkStorage.cs index dedabf0..7f873d5 100644 --- a/Game/World/ChunkStorage.cs +++ b/Game/World/ChunkStorage.cs @@ -1,3 +1,21 @@ +// +// NEWorld/Game: ChunkStorage.cs +// NEWorld: A Free Game with Similar Rules to Minecraft. +// Copyright (C) 2015-2019 NEWorld Team +// +// NEWorld is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// NEWorld is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General +// Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with NEWorld. If not, see . +// using System; using System.Runtime.InteropServices; diff --git a/Game/World/ChunkUpdate.cs b/Game/World/ChunkUpdate.cs new file mode 100644 index 0000000..3595a80 --- /dev/null +++ b/Game/World/ChunkUpdate.cs @@ -0,0 +1,76 @@ +// +// NEWorld/Game: ChunkUpdate.cs +// NEWorld: A Free Game with Similar Rules to Minecraft. +// Copyright (C) 2015-2019 NEWorld Team +// +// NEWorld is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// NEWorld is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General +// Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with NEWorld. If not, see . +// + +using Xenko.Core.Mathematics; + +namespace Game.World +{ + public partial class Chunk + { + public delegate void ChunkUpdateHandler(Chunk chunk); + + public static event ChunkUpdateHandler OnChunkUpdate; + + public static event ChunkUpdateHandler OnNeighborUpdate; + + private bool pendingLocalUpdate, pendingNeighborUpdate; + + private void TriggerUpdate() + { + EnqueueLocalUpdateTask(); + TriggerNeighborUpdate(); + } + + private async void EnqueueLocalUpdateTask() + { + pendingLocalUpdate = true; + await ChunkService.TaskDispatcher.NextReadOnlyChance(); + OnChunkUpdate?.Invoke(this); + } + + private void TriggerNeighborUpdate() + { + foreach (var neighbor in GetNeighbors()) + { + neighbor.EnqueueNeighborUpdateTask(); + } + } + + private async void EnqueueNeighborUpdateTask() + { + pendingNeighborUpdate = true; + await ChunkService.TaskDispatcher.NextReadOnlyChance(); + OnNeighborUpdate?.Invoke(this); + } + + public Chunk[] GetNeighbors() + { + // TODO: Cache The Value + return new [] + { + World.GetChunk(new Int3(Position.X + 1, Position.Y, Position.Z)), + World.GetChunk(new Int3(Position.X - 1, Position.Y, Position.Z)), + World.GetChunk(new Int3(Position.X, Position.Y + 1, Position.Z)), + World.GetChunk(new Int3(Position.X, Position.Y - 1, Position.Z)), + World.GetChunk(new Int3(Position.X, Position.Y, Position.Z + 1)), + World.GetChunk(new Int3(Position.X, Position.Y, Position.Z - 1)) + }; + } + } +} diff --git a/Game/World/Object.cs b/Game/World/Object.cs index 7c16eca..4fded48 100644 --- a/Game/World/Object.cs +++ b/Game/World/Object.cs @@ -1,7 +1,7 @@ // -// Game: Object.cs +// NEWorld/Game: Object.cs // NEWorld: A Free Game with Similar Rules to Minecraft. -// Copyright (C) 2015-2018 NEWorld Team +// Copyright (C) 2015-2019 NEWorld Team // // NEWorld is free software: you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published @@ -16,7 +16,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with NEWorld. If not, see . // - using Game.Utilities; using Xenko.Core.Mathematics; diff --git a/Game/World/Player.cs b/Game/World/Player.cs index aaf27cb..28a1805 100644 --- a/Game/World/Player.cs +++ b/Game/World/Player.cs @@ -1,7 +1,7 @@ // -// Game: Player.cs +// NEWorld/Game: Player.cs // NEWorld: A Free Game with Similar Rules to Minecraft. -// Copyright (C) 2015-2018 NEWorld Team +// Copyright (C) 2015-2019 NEWorld Team // // NEWorld is free software: you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published @@ -16,7 +16,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with NEWorld. If not, see . // - using Core.Math; using Core.Utilities; using Xenko.Core.Mathematics; diff --git a/Game/World/PlayerObject.cs b/Game/World/PlayerObject.cs index 913bd11..2713248 100644 --- a/Game/World/PlayerObject.cs +++ b/Game/World/PlayerObject.cs @@ -1,7 +1,7 @@ // -// Game: PlayerObject.cs +// NEWorld/Game: PlayerObject.cs // NEWorld: A Free Game with Similar Rules to Minecraft. -// Copyright (C) 2015-2018 NEWorld Team +// Copyright (C) 2015-2019 NEWorld Team // // NEWorld is free software: you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published @@ -16,7 +16,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with NEWorld. If not, see . // - using System; using Game.Utilities; using Xenko.Core.Mathematics; diff --git a/Game/World/StaticChunkPool.cs b/Game/World/StaticChunkPool.cs index 5ea50cd..86bd254 100644 --- a/Game/World/StaticChunkPool.cs +++ b/Game/World/StaticChunkPool.cs @@ -1,3 +1,21 @@ +// +// NEWorld/Game: StaticChunkPool.cs +// NEWorld: A Free Game with Similar Rules to Minecraft. +// Copyright (C) 2015-2019 NEWorld Team +// +// NEWorld is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// NEWorld is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General +// Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with NEWorld. If not, see . +// using System.Collections.Generic; namespace Game.World diff --git a/Game/World/World.cs b/Game/World/World.cs index 4fc328a..9bae4a9 100644 --- a/Game/World/World.cs +++ b/Game/World/World.cs @@ -1,7 +1,7 @@ // -// Game: World.cs +// NEWorld/Game: World.cs // NEWorld: A Free Game with Similar Rules to Minecraft. -// Copyright (C) 2015-2018 NEWorld Team +// Copyright (C) 2015-2019 NEWorld Team // // NEWorld is free software: you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published @@ -16,7 +16,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with NEWorld. If not, see . // - using System; using System.Collections.Generic; using System.Linq; diff --git a/Game/World/WorldTasks.cs b/Game/World/WorldTasks.cs index 8a4d168..ca77e10 100644 --- a/Game/World/WorldTasks.cs +++ b/Game/World/WorldTasks.cs @@ -1,7 +1,7 @@ // -// Game: WorldTasks.cs +// NEWorld/Game: WorldTasks.cs // NEWorld: A Free Game with Similar Rules to Minecraft. -// Copyright (C) 2015-2018 NEWorld Team +// Copyright (C) 2015-2019 NEWorld Team // // NEWorld is free software: you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published @@ -16,7 +16,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with NEWorld. If not, see . // - using System; using System.Threading; using Game.Utilities; @@ -41,7 +40,7 @@ public class ResetChunkTask : IReadWriteTask private readonly Chunk chunk; /** - * \brief Add a constructed chunk into world. + * \brief AddReadOnlyTask a constructed chunk into world. * \param chunk the target chunk */ public ResetChunkTask(Chunk chunk) @@ -54,7 +53,7 @@ public void Task() chunk.World.ResetChunkAndUpdate(chunk); } } - + private class LoadTask : IReadWriteTask { private Chunk chunk; @@ -64,10 +63,10 @@ internal LoadTask(World world, Int3 chunkPosition) { // Adding Sentry chunk = new Chunk(chunkPosition, world, Chunk.InitOption.None); - ChunkService.TaskDispatcher.Add(new LocalLoadTask(world, chunkPosition, this)); if (!ChunkService.IsAuthority) Network.Client.GetChunk.Call(world.Id, chunkPosition); + LocalLoad(world, chunkPosition); } - + public void Task() { var operated = Interlocked.Exchange(ref chunk, null); @@ -82,29 +81,14 @@ public void Task() private void Reset(Chunk chk) { - if (Interlocked.Exchange(ref chunk, chk) == null) + if (Interlocked.Exchange(ref chunk, chk) == null) ChunkService.TaskDispatcher.Add(this); } - private class LocalLoadTask : IReadOnlyTask + private async void LocalLoad(World world, Int3 position) { - private readonly Int3 chunkPosition; - - private readonly World world; - - private readonly LoadTask load; - - public LocalLoadTask(World wrd, Int3 position, LoadTask loadTask) - { - world = wrd; - chunkPosition = position; - load = loadTask; - } - - public void Task() - { - load.Reset(new Chunk(chunkPosition, world)); - } + await ChunkService.TaskDispatcher.NextReadOnlyChance(); + Reset(new Chunk(position, world)); } } diff --git a/Main/Main.cs b/Main/Main.cs index 5ffaad6..5b62426 100644 --- a/Main/Main.cs +++ b/Main/Main.cs @@ -1,7 +1,7 @@ // -// Main: Main.cs +// NEWorld/Main: Main.cs // NEWorld: A Free Game with Similar Rules to Minecraft. -// Copyright (C) 2015-2018 NEWorld Team +// Copyright (C) 2015-2019 NEWorld Team // // NEWorld is free software: you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published @@ -16,7 +16,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with NEWorld. If not, see . // - using Core; using Core.Module; using Game.Terrain; diff --git a/NEWorld.Linux/NEWorldApp.cs b/NEWorld.Linux/NEWorldApp.cs index f362cb7..33f74e4 100644 --- a/NEWorld.Linux/NEWorldApp.cs +++ b/NEWorld.Linux/NEWorldApp.cs @@ -1,3 +1,21 @@ +// +// NEWorld/NEWorld.Linux: NEWorldApp.cs +// NEWorld: A Free Game with Similar Rules to Minecraft. +// Copyright (C) 2015-2019 NEWorld Team +// +// NEWorld is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// NEWorld is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General +// Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with NEWorld. If not, see . +// namespace NEWorld.Linux { internal class NEWorldApp diff --git a/NEWorld.Windows/NEWorldApp.cs b/NEWorld.Windows/NEWorldApp.cs index c0bcd3b..7568a11 100644 --- a/NEWorld.Windows/NEWorldApp.cs +++ b/NEWorld.Windows/NEWorldApp.cs @@ -1,3 +1,21 @@ +// +// NEWorld/NEWorld.Windows: NEWorldApp.cs +// NEWorld: A Free Game with Similar Rules to Minecraft. +// Copyright (C) 2015-2019 NEWorld Team +// +// NEWorld is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// NEWorld is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General +// Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with NEWorld. If not, see . +// namespace NEWorld.Windows { internal class NEWorldApp diff --git a/NEWorld.macOS/NEWorldApp.cs b/NEWorld.macOS/NEWorldApp.cs index a4e6357..01b5dac 100644 --- a/NEWorld.macOS/NEWorldApp.cs +++ b/NEWorld.macOS/NEWorldApp.cs @@ -1,3 +1,21 @@ +// +// NEWorld/NEWorld.macOS: NEWorldApp.cs +// NEWorld: A Free Game with Similar Rules to Minecraft. +// Copyright (C) 2015-2019 NEWorld Team +// +// NEWorld is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// NEWorld is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General +// Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with NEWorld. If not, see . +// namespace NEWorld.macOS { internal class NEWorldApp diff --git a/NEWorld.sln.DotSettings b/NEWorld.sln.DotSettings index 64e81af..95d9601 100644 --- a/NEWorld.sln.DotSettings +++ b/NEWorld.sln.DotSettings @@ -1,20 +1,23 @@  - // -// $SOLUTION$:$PROJECT$: $FILENAME$ -// NEWorld: A Free Game with Similar Rules to Minecraft. -// Copyright (C) 2015-$CURRENT_YEAR$ NEWorld Team -// -// NEWorld is free software: you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published -// by the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// NEWorld is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY -// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General -// Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with NEWorld. If not, see <http://www.gnu.org/licenses/>. -// + + True + <?xml version="1.0" encoding="utf-16"?><Profile name="Update Header"><CppClangTidyCleanupDescriptor /><CSCodeStyleAttributes ArrangeTypeAccessModifier="False" ArrangeTypeMemberAccessModifier="False" SortModifiers="False" RemoveRedundantParentheses="False" AddMissingParentheses="False" ArrangeBraces="False" ArrangeAttributes="False" ArrangeArgumentsStyle="False" ArrangeCodeBodyStyle="False" ArrangeVarStyle="False" /><CSOptimizeUsings><OptimizeUsings>False</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSUpdateFileHeader>True</CSUpdateFileHeader><XAMLCollapseEmptyTags>False</XAMLCollapseEmptyTags><CppUpdateFileHeader>True</CppUpdateFileHeader></Profile> + +$SOLUTION$/$PROJECT$: $FILENAME$ +NEWorld: A Free Game with Similar Rules to Minecraft. +Copyright (C) 2015-$CURRENT_YEAR$ NEWorld Team + +NEWorld is free software: you can redistribute it and/or modify it +under the terms of the GNU Lesser General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +NEWorld is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General +Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with NEWorld. If not, see <http://www.gnu.org/licenses/>. + \ No newline at end of file diff --git a/NEWorld/BasicCameraController.cs b/NEWorld/BasicCameraController.cs index 13bc036..d23834a 100644 --- a/NEWorld/BasicCameraController.cs +++ b/NEWorld/BasicCameraController.cs @@ -1,4 +1,22 @@ -using System; +// +// NEWorld/NEWorld: BasicCameraController.cs +// NEWorld: A Free Game with Similar Rules to Minecraft. +// Copyright (C) 2015-2019 NEWorld Team +// +// NEWorld is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// NEWorld is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General +// Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with NEWorld. If not, see . +// +using System; using Xenko.Core; using Xenko.Core.Mathematics; using Xenko.Engine; diff --git a/NEWorld/MainScript.cs b/NEWorld/MainScript.cs index bfc3113..71d86d9 100644 --- a/NEWorld/MainScript.cs +++ b/NEWorld/MainScript.cs @@ -1,7 +1,7 @@ // -// NEWorld: GameScene.cs +// NEWorld/NEWorld: MainScript.cs // NEWorld: A Free Game with Similar Rules to Minecraft. -// Copyright (C) 2015-2018 NEWorld Team +// Copyright (C) 2015-2019 NEWorld Team // // NEWorld is free software: you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published @@ -16,7 +16,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with NEWorld. If not, see . // - using System; using System.Threading; using System.Threading.Tasks; diff --git a/NEWorld/Renderer/RdChunk.cs b/NEWorld/Renderer/RdChunk.cs index ff54cc3..5479fee 100644 --- a/NEWorld/Renderer/RdChunk.cs +++ b/NEWorld/Renderer/RdChunk.cs @@ -1,6 +1,7 @@ -// NEWorld: GameScene.cs +// +// NEWorld/NEWorld: RdChunk.cs // NEWorld: A Free Game with Similar Rules to Minecraft. -// Copyright (C) 2015-2018 NEWorld Team +// Copyright (C) 2015-2019 NEWorld Team // // NEWorld is free software: you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published @@ -15,7 +16,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with NEWorld. If not, see . // - using Game.Terrain; using Game.World; using Xenko.Core.Mathematics; @@ -29,7 +29,7 @@ namespace NEWorld.Renderer * But it does not involve OpenGL operations so it can be * safely called from all threads. */ - public class ChunkRenderData + public class ChunkVboGen { public Model Model { get; private set; } @@ -38,21 +38,11 @@ public class ChunkRenderData * Does not involve OpenGL functions. * \param chunk the chunk to be rendered. */ - public void Generate(Chunk chunk) + public ChunkVboGen Generate(Chunk chunk) { using (VertexBuilder vaOpacity = new VertexBuilder(262144), vaTranslucent = new VertexBuilder(262144)) { var tmp = new Int3(); - var pos = chunk.Position; - var world = chunk.World; - var context = new BlockRenderContext(chunk, new [] - { - world.GetChunk(new Int3(pos.X + 1, pos.Y, pos.Z)), - world.GetChunk(new Int3(pos.X - 1, pos.Y, pos.Z)), - world.GetChunk(new Int3(pos.X, pos.Y + 1, pos.Z)), - world.GetChunk(new Int3(pos.X, pos.Y - 1, pos.Z)), - world.GetChunk(new Int3(pos.X, pos.Y, pos.Z + 1)), - world.GetChunk(new Int3(pos.X, pos.Y, pos.Z - 1)) - }); + var context = new BlockRenderContext(chunk, chunk.GetNeighbors()); for (tmp.X = 0; tmp.X < Chunk.RowSize; ++tmp.X) for (tmp.Y = 0; tmp.Y < Chunk.RowSize; ++tmp.Y) for (tmp.Z = 0; tmp.Z < Chunk.RowSize; ++tmp.Z) @@ -68,20 +58,21 @@ public void Generate(Chunk chunk) if (mesh0 != null) Model.Add(mesh0); if (mesh1 != null) Model.Add(mesh1); } + + return this; } } /** * \brief The renderer that can be used to render directly. It includes * VBO that we need to render. It can be generated from a - * ChunkRenderData + * ChunkVboGen */ public class RdChunk { - public RdChunk(ChunkRenderData data, Vector3 chunkPosition) + public RdChunk(Vector3 chunkPosition) { Entity = new Entity(); - Update(data); Entity.GetOrCreate().Position = chunkPosition * Chunk.RowSize; } @@ -93,11 +84,10 @@ public RdChunk(ChunkRenderData data, Vector3 chunkPosition) * main thread. * \param data The render data that will be used to generate VBO */ - public void Update(ChunkRenderData data) + public void Update(Model model) { - var model = data.Model; if (model != null) - Entity.GetOrCreate().Model = data.Model; + Entity.GetOrCreate().Model = model; else Entity.Remove(); } diff --git a/NEWorld/Renderer/RdTextures.cs b/NEWorld/Renderer/RdTextures.cs index b4a1196..93e9266 100644 --- a/NEWorld/Renderer/RdTextures.cs +++ b/NEWorld/Renderer/RdTextures.cs @@ -1,4 +1,22 @@ -using System.Collections.Generic; +// +// NEWorld/NEWorld: RdTextures.cs +// NEWorld: A Free Game with Similar Rules to Minecraft. +// Copyright (C) 2015-2019 NEWorld Team +// +// NEWorld is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// NEWorld is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General +// Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with NEWorld. If not, see . +// +using System.Collections.Generic; using Core; using Game.Terrain; using Xenko.Graphics; diff --git a/NEWorld/Renderer/RdWorld.cs b/NEWorld/Renderer/RdWorld.cs index 71c4ef4..bab9767 100644 --- a/NEWorld/Renderer/RdWorld.cs +++ b/NEWorld/Renderer/RdWorld.cs @@ -1,7 +1,7 @@ // -// NEWorld: GameScene.cs +// NEWorld/NEWorld: RdWorld.cs // NEWorld: A Free Game with Similar Rules to Minecraft. -// Copyright (C) 2015-2018 NEWorld Team +// Copyright (C) 2015-2019 NEWorld Team // // NEWorld is free software: you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published @@ -16,7 +16,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with NEWorld. If not, see . // - using System; using System.Collections.Generic; using System.Linq; @@ -28,7 +27,7 @@ namespace NEWorld.Renderer { public class RdWorld { - public const int MaxChunkRenderCount = 16; + public const int MaxChunkRenderCount = 64; // Chunk Renderers private readonly Dictionary chunkRenderers; @@ -75,72 +74,55 @@ public void Task(int instance, int instances) var counter = 0; // TODO: improve performance by adding multiple instances of this and set a step when itering the chunks. var position = player.Position; - var positionInt = new Int3((int) position.X, (int) position.Y, (int) position.Z); - var chunkpos = World.GetChunkPos(positionInt); + var center = World.GetChunkPos(new Int3((int) position.X, (int) position.Y, (int) position.Z)); var world = ChunkService.Worlds.Get(currentWorldId); foreach (var c in world.Chunks) { var chunk = c.Value; - var chunkPosition = chunk.Position; // In render range, pending to render - if (chunk.IsUpdated && ChebyshevDistance(chunkpos, chunkPosition) <= rdWorldRenderer.RenderDist) - if (NeighbourChunkLoadCheck(world, chunkPosition)) + if (chunk.IsUpdated && ChebyshevDistance(center, chunk.Position) <= rdWorldRenderer.RenderDist) + if (NeighbourChunkLoadCheck(world, chunk.Position)) { - // TODO: maybe build a VA pool can speed this up. - var crd = new ChunkRenderData(); - crd.Generate(chunk); - ChunkService.TaskDispatcher.Add(new VboGenerateTask(world, chunkPosition, crd, - rdWorldRenderer.chunkRenderers)); + GenerateVbo(chunk, rdWorldRenderer.chunkRenderers); if (++counter == MaxChunkRenderCount) break; } } } } - // TODO: Remove Type1 Clone - private static int ChebyshevDistance(Int3 l, Int3 r) + private static async void GenerateVbo(Chunk target, Dictionary pool) { - return Math.Max(Math.Max(Math.Abs(l.X - r.X), Math.Abs(l.Y - r.Y)), Math.Abs(l.Z - r.Z)); + await ChunkService.TaskDispatcher.NextReadOnlyChance(); + var model = new ChunkVboGen().Generate(target).Model; + await ChunkService.TaskDispatcher.NextRenderChance(); + var position = target.Position; + // TODO: Check the Chunk Directly + if (!target.World.Chunks.ContainsKey(position)) return; + target.IsUpdated = false; // TODO: Remove when chunk update driver is complete + GetOrAddRdChunk(pool, position).Update(model); } - private static bool NeighbourChunkLoadCheck(World world, Int3 pos) + private static RdChunk GetOrAddRdChunk(Dictionary pool, Int3 position) { - return Delta.All(p => world.IsChunkLoaded(pos + p)); + if (pool.TryGetValue(position, out var it)) return it; + var renderer = new RdChunk(new Vector3(position.X, position.Y, position.Z)); + Context.OperatingScene.Entities.Add(renderer.Entity); + pool.Add(position, renderer); + return renderer; } - } - - private class VboGenerateTask : IRenderTask - { - private readonly ChunkRenderData chunkRenderData; - private readonly Dictionary chunkRenderers; - private readonly Int3 position; - - private readonly World world; - public VboGenerateTask(World world, Int3 position, ChunkRenderData crd, - Dictionary chunkRenderers) + // TODO: Remove Type1 Clone + private static int ChebyshevDistance(Int3 l, Int3 r) { - this.world = world; - this.position = position; - chunkRenderData = crd; - this.chunkRenderers = chunkRenderers; + return Math.Max(Math.Max(Math.Abs(l.X - r.X), Math.Abs(l.Y - r.Y)), Math.Abs(l.Z - r.Z)); } - public void Task() + private static bool NeighbourChunkLoadCheck(World world, Int3 pos) { - if (!world.Chunks.TryGetValue(position, out var chunk)) return; - chunk.IsUpdated = false; - if (chunkRenderers.TryGetValue(position, out var it)) - { - it.Update(chunkRenderData); - } - else - { - var renderer = new RdChunk(chunkRenderData, new Vector3(position.X, position.Y, position.Z)); - Context.OperatingScene.Entities.Add(renderer.Entity); - chunkRenderers.Add(position, renderer); - } + return Delta.All(p => world.IsChunkLoaded(pos + p)); } } + + } } \ No newline at end of file diff --git a/NEWorld/Renderer/VertexBuilder.cs b/NEWorld/Renderer/VertexBuilder.cs index a7fa7b4..a22c640 100644 --- a/NEWorld/Renderer/VertexBuilder.cs +++ b/NEWorld/Renderer/VertexBuilder.cs @@ -1,4 +1,22 @@ -using System; +// +// NEWorld/NEWorld: VertexBuilder.cs +// NEWorld: A Free Game with Similar Rules to Minecraft. +// Copyright (C) 2015-2019 NEWorld Team +// +// NEWorld is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// NEWorld is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General +// Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with NEWorld. If not, see . +// +using System; using System.Runtime.InteropServices; using Game.Terrain; using Xenko.Core.Mathematics; diff --git a/NEWorldShell/Cli.cs b/NEWorldShell/Cli.cs index 93f0fc3..757f6a9 100644 --- a/NEWorldShell/Cli.cs +++ b/NEWorldShell/Cli.cs @@ -1,7 +1,7 @@ // -// NEWorldShell: Cli.cs +// NEWorld/NEWorldShell: Cli.cs // NEWorld: A Free Game with Similar Rules to Minecraft. -// Copyright (C) 2015-2018 NEWorld Team +// Copyright (C) 2015-2019 NEWorld Team // // NEWorld is free software: you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published @@ -16,7 +16,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with NEWorld. If not, see . // - using System; using System.Runtime; using Core; @@ -66,7 +65,7 @@ private void InitBuiltinCommands() _commands.RegisterCommand("server.ups", new CommandInfo("internal", "Show the ups."), cmd => { - // TODO: Add UPS counter for server + // TODO: AddReadOnlyTask UPS counter for server return new CommandExecuteStat(true, "[Server UPS counter not finished yet!]"); }); diff --git a/NEWorldShell/Command.cs b/NEWorldShell/Command.cs index 0d326d7..750acbe 100644 --- a/NEWorldShell/Command.cs +++ b/NEWorldShell/Command.cs @@ -1,7 +1,7 @@ // -// NEWorldShell: Command.cs +// NEWorld/NEWorldShell: Command.cs // NEWorld: A Free Game with Similar Rules to Minecraft. -// Copyright (C) 2015-2018 NEWorld Team +// Copyright (C) 2015-2019 NEWorld Team // // NEWorld is free software: you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published @@ -16,7 +16,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with NEWorld. If not, see . // - using System; using System.Collections.Generic; using System.Linq; diff --git a/NEWorldShell/Program.cs b/NEWorldShell/Program.cs index e63a795..4289a30 100644 --- a/NEWorldShell/Program.cs +++ b/NEWorldShell/Program.cs @@ -1,7 +1,7 @@ // -// NEWorldShell: Program.cs +// NEWorld/NEWorldShell: Program.cs // NEWorld: A Free Game with Similar Rules to Minecraft. -// Copyright (C) 2015-2018 NEWorld Team +// Copyright (C) 2015-2019 NEWorld Team // // NEWorld is free software: you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published @@ -16,7 +16,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with NEWorld. If not, see . // - using Core; using Core.Module; using Game.Network; diff --git a/NEWorldShell/Properties/AssemblyInfo.cs b/NEWorldShell/Properties/AssemblyInfo.cs index a1812d0..597a823 100644 --- a/NEWorldShell/Properties/AssemblyInfo.cs +++ b/NEWorldShell/Properties/AssemblyInfo.cs @@ -1,7 +1,7 @@ // -// NEWorldShell: AssemblyInfo.cs +// NEWorld/NEWorldShell: AssemblyInfo.cs // NEWorld: A Free Game with Similar Rules to Minecraft. -// Copyright (C) 2015-2018 NEWorld Team +// Copyright (C) 2015-2019 NEWorld Team // // NEWorld is free software: you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published @@ -16,7 +16,6 @@ // You should have received a copy of the GNU Lesser General Public License // along with NEWorld. If not, see . // - using System.Reflection; using System.Runtime.InteropServices; From e4bc2df54bbed8ea8ffc633465779df63d610767 Mon Sep 17 00:00:00 2001 From: DWVoid Date: Sun, 10 Feb 2019 00:37:13 +0800 Subject: [PATCH 14/23] Implment Global EventBus --- Core/EventBus.cs | 180 +++++++++++++++++++++++++++++++++++++++++ Game/TaskDispatcher.cs | 119 +++++++++++++++------------ 2 files changed, 246 insertions(+), 53 deletions(-) create mode 100644 Core/EventBus.cs diff --git a/Core/EventBus.cs b/Core/EventBus.cs new file mode 100644 index 0000000..447f2d8 --- /dev/null +++ b/Core/EventBus.cs @@ -0,0 +1,180 @@ +// +// NEWorld/Core: EventBus.cs +// NEWorld: A Free Game with Similar Rules to Minecraft. +// Copyright (C) 2015-2019 NEWorld Team +// +// NEWorld is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// NEWorld is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General +// Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with NEWorld. If not, see . +// + +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Threading; + +namespace Core +{ + public class DeclareBusEventHandlerAttribute : Attribute + { + public readonly Type Type; + + public DeclareBusEventHandlerAttribute(Type type) + { + Type = type; + } + } + + public static class EventBus + { + public delegate void EventHandler(object sender, T payload); + + private static readonly Dictionary EventHandlers = new Dictionary(); + + public static void Add(EventHandler handler) + { + var slot = GetOrCreateSlot(); + slot.Rwl.EnterWriteLock(); + slot.Handlers += handler; + slot.Rwl.ExitWriteLock(); + } + + private static Slot GetOrCreateSlot() + { + Slot slot; + lock (EventHandlers) + { + if (EventHandlers.TryGetValue(typeof(T), out var value)) + { + slot = (Slot) value; + } + else + { + slot = new Slot(); + EventHandlers.Add(typeof(T), slot); + } + } + + return slot; + } + + private static ISlot GetOrCreateSlot(Type type) + { + ISlot slot; + lock (EventHandlers) + { + if (EventHandlers.TryGetValue(type, out var value)) + { + slot = (ISlot) value; + } + else + { + slot = (ISlot) Activator.CreateInstance(typeof(Slot<>).MakeGenericType(type)); + EventHandlers.Add(type, slot); + } + } + + return slot; + } + + public static void Remove(EventHandler handler) + { + Slot slot; + lock (EventHandlers) + { + if (EventHandlers.TryGetValue(typeof(T), out var value)) + slot = (Slot) value; + else + return; + } + + slot.Rwl.EnterWriteLock(); + slot.Handlers -= handler; + slot.Rwl.ExitWriteLock(); + } + + public static void AddCollection(object obj) + { + ProcessCollection(obj, true); + } + + public static void RemoveCollection(object obj) + { + ProcessCollection(obj, false); + } + + private static void ProcessCollection(object obj, bool add) + { + foreach (var method in obj.GetType().GetMethods()) + if (method.IsDefined(typeof(DeclareBusEventHandlerAttribute), true)) + { + var payload = + (DeclareBusEventHandlerAttribute) method.GetCustomAttribute( + typeof(DeclareBusEventHandlerAttribute)); + var handlerType = typeof(EventHandler<>).MakeGenericType(payload.Type); + var del = method.IsStatic + ? Delegate.CreateDelegate(handlerType, method) + : Delegate.CreateDelegate(handlerType, obj, method); + if (add) + GetOrCreateSlot(payload.Type).Add(del); + else + GetOrCreateSlot(payload.Type).Remove(del); + } + } + + public static void Broadcast(object sender, T payload) + { + Slot slot = null; + lock (EventHandlers) + { + if (EventHandlers.TryGetValue(typeof(T), out var value)) + slot = (Slot) value; + } + + slot?.Invoke(sender, payload); + } + + private interface ISlot + { + void Add(Delegate handler); + void Remove(Delegate handler); + } + + private class Slot : ISlot + { + public readonly ReaderWriterLockSlim Rwl = new ReaderWriterLockSlim(); + + public void Add(Delegate handler) + { + Rwl.EnterWriteLock(); + typeof(Slot).GetEvents()[0].AddMethod.Invoke(this, new object[] {handler}); + Rwl.ExitWriteLock(); + } + + public void Remove(Delegate handler) + { + Rwl.EnterWriteLock(); + typeof(Slot).GetEvents()[0].RemoveMethod.Invoke(this, new object[] {handler}); + Rwl.ExitWriteLock(); + } + + public event EventHandler Handlers; + + public void Invoke(object sender, T payload) + { + Rwl.EnterReadLock(); + Handlers?.Invoke(sender, payload); + Rwl.ExitReadLock(); + } + } + } +} diff --git a/Game/TaskDispatcher.cs b/Game/TaskDispatcher.cs index db3cf87..72edc2b 100644 --- a/Game/TaskDispatcher.cs +++ b/Game/TaskDispatcher.cs @@ -16,6 +16,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with NEWorld. If not, see . // + using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -63,59 +64,6 @@ public class TaskDispatcher : IDisposable private List renderTasks, nextRenderTasks; private bool shouldExit; - public struct NextReadOnlyChanceS - { - public struct Awaiter : INotifyCompletion - { - public bool IsCompleted => false; - - public void GetResult() - { - } - - public void OnCompleted(Action continuation) - { - ChunkService.TaskDispatcher.AddReadOnlyTask(continuation); - } - } - - public Awaiter GetAwaiter() { return new Awaiter();} - } - - /** - * \brief This type of tasks will be executed concurrently. - * Note that "ReadOnly" here is with respect to chunks - * data specifically. However please be aware of - * thread safety when you write something other than - * chunks. - */ - public NextReadOnlyChanceS NextReadOnlyChance() { return new NextReadOnlyChanceS(); } - - public struct NextRenderChanceS - { - public struct Awaiter : INotifyCompletion - { - public bool IsCompleted => false; - - public void GetResult() - { - } - - public void OnCompleted(Action continuation) - { - ChunkService.TaskDispatcher.AddRenderTask(continuation); - } - } - - public Awaiter GetAwaiter() { return new Awaiter();} - } - - /** - * \brief This type of tasks will be executed in main thread. - * Thus, it is safe to call OpenGL function inside. - */ - public NextRenderChanceS NextRenderChance() { return new NextRenderChanceS(); } - // Automatic Creation. We reserve one virtual processor for main thread public TaskDispatcher() : this(Environment.ProcessorCount - 1) { @@ -149,6 +97,27 @@ public void Dispose() GC.SuppressFinalize(this); } + /** + * \brief This type of tasks will be executed concurrently. + * Note that "ReadOnly" here is with respect to chunks + * data specifically. However please be aware of + * thread safety when you write something other than + * chunks. + */ + public NextReadOnlyChanceS NextReadOnlyChance() + { + return new NextReadOnlyChanceS(); + } + + /** + * \brief This type of tasks will be executed in main thread. + * Thus, it is safe to call OpenGL function inside. + */ + public NextRenderChanceS NextRenderChance() + { + return new NextRenderChanceS(); + } + ~TaskDispatcher() { ReleaseUnmanagedResources(); @@ -275,5 +244,49 @@ private void ReleaseUnmanagedResources() Reset(); barrier?.Dispose(); } + + public struct NextReadOnlyChanceS + { + public struct Awaiter : INotifyCompletion + { + public bool IsCompleted => false; + + public void GetResult() + { + } + + public void OnCompleted(Action continuation) + { + ChunkService.TaskDispatcher.AddReadOnlyTask(continuation); + } + } + + public Awaiter GetAwaiter() + { + return new Awaiter(); + } + } + + public struct NextRenderChanceS + { + public struct Awaiter : INotifyCompletion + { + public bool IsCompleted => false; + + public void GetResult() + { + } + + public void OnCompleted(Action continuation) + { + ChunkService.TaskDispatcher.AddRenderTask(continuation); + } + } + + public Awaiter GetAwaiter() + { + return new Awaiter(); + } + } } } \ No newline at end of file From e6f14a6a03f4093cfd0f06767cdf2b5bf488b566 Mon Sep 17 00:00:00 2001 From: DWVoid Date: Sun, 10 Feb 2019 01:04:39 +0800 Subject: [PATCH 15/23] Adopt EventBus in Modules[1] --- Core/EventBus.cs | 37 +++++++++++++++++++------------------ Core/Module/Module.cs | 16 +--------------- Game/Events.cs | 41 +++++++++++++++++++++++++++++++++++++++++ Main/Main.cs | 10 +++++++--- NEWorld/MainScript.cs | 7 ++++--- NEWorldShell/Program.cs | 3 ++- 6 files changed, 74 insertions(+), 40 deletions(-) create mode 100644 Game/Events.cs diff --git a/Core/EventBus.cs b/Core/EventBus.cs index 447f2d8..5b6ad22 100644 --- a/Core/EventBus.cs +++ b/Core/EventBus.cs @@ -19,19 +19,12 @@ using System; using System.Collections.Generic; -using System.Reflection; using System.Threading; namespace Core { public class DeclareBusEventHandlerAttribute : Attribute { - public readonly Type Type; - - public DeclareBusEventHandlerAttribute(Type type) - { - Type = type; - } } public static class EventBus @@ -117,17 +110,25 @@ private static void ProcessCollection(object obj, bool add) foreach (var method in obj.GetType().GetMethods()) if (method.IsDefined(typeof(DeclareBusEventHandlerAttribute), true)) { - var payload = - (DeclareBusEventHandlerAttribute) method.GetCustomAttribute( - typeof(DeclareBusEventHandlerAttribute)); - var handlerType = typeof(EventHandler<>).MakeGenericType(payload.Type); - var del = method.IsStatic - ? Delegate.CreateDelegate(handlerType, method) - : Delegate.CreateDelegate(handlerType, obj, method); - if (add) - GetOrCreateSlot(payload.Type).Add(del); + var payload = method.GetParameters(); + if (payload.Length == 2) + { + var payloadType = payload[payload.Length - 1].ParameterType; + var handlerType = typeof(EventHandler<>).MakeGenericType(payloadType); + var del = method.IsStatic + ? Delegate.CreateDelegate(handlerType, method) + : Delegate.CreateDelegate(handlerType, obj, method); + if (add) + GetOrCreateSlot(payloadType).Add(del); + else + GetOrCreateSlot(payloadType).Remove(del); + } else - GetOrCreateSlot(payload.Type).Remove(del); + { + throw new ArgumentException( + $"Excepting Arguments (System.Object, T) But Got {payload.Length} at Handler {method}" + + ", Stopping. Note that Previously Added Handlers will NOT be Removed"); + } } } @@ -177,4 +178,4 @@ public void Invoke(object sender, T payload) } } } -} +} \ No newline at end of file diff --git a/Core/Module/Module.cs b/Core/Module/Module.cs index 2608a55..1b288a6 100644 --- a/Core/Module/Module.cs +++ b/Core/Module/Module.cs @@ -26,8 +26,6 @@ namespace Core.Module public interface IModule { void CoInitialize(); - void WorkspaceInitialize(); - void WorkspaceFinalize(); void CoFinalize(); void OnMemoryWarning(); } @@ -74,19 +72,7 @@ public void Load(string moduleFile) LogPort.Debug($"Module {type} Load Failure : {e}"); } } - - public void WorkspaceInitialize() - { - foreach (var module in modules) - module.Value.Key.WorkspaceInitialize(); - } - - public void WorkspaceFinalize() - { - foreach (var module in modules) - module.Value.Key.WorkspaceFinalize(); - } - + public void UnloadAll() { foreach (var module in modules) diff --git a/Game/Events.cs b/Game/Events.cs new file mode 100644 index 0000000..711a1d5 --- /dev/null +++ b/Game/Events.cs @@ -0,0 +1,41 @@ +// +// NEWorld/NEWorld: Events.cs +// NEWorld: A Free Game with Similar Rules to Minecraft. +// Copyright (C) 2015-2019 NEWorld Team +// +// NEWorld is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// NEWorld is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General +// Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with NEWorld. If not, see . +// + +namespace Game +{ + public class GameLoadEvent + { + + } + + public class GameStartEvent + { + + } + + public class GameEndEvent + { + + } + + public class GameUnloadEvent + { + + } +} diff --git a/Main/Main.cs b/Main/Main.cs index 5b62426..4117a59 100644 --- a/Main/Main.cs +++ b/Main/Main.cs @@ -18,6 +18,7 @@ // using Core; using Core.Module; +using Game; using Game.Terrain; using Game.World; @@ -40,16 +41,19 @@ public void CoInitialize() Chunk.SetGenerator(WorldGen.Generator); StaticChunkPool.Register("Main.RockChunk", new Chunk(new BlockData(_rockId))); StaticChunkPool.Register("Main.WaterChunk", new Chunk(new BlockData(_waterId))); + EventBus.AddCollection(this); RendererInit(); } - public void WorkspaceInitialize() + [DeclareBusEventHandler] + public void GameLoads(object sender, GameLoadEvent load) { _rockChunkId = StaticChunkPool.GetId("Main.RockChunk"); _waterChunkId = StaticChunkPool.GetId("Main.WaterChunk"); } - - public void WorkspaceFinalize() + + [DeclareBusEventHandler] + public void GameUnloads(object sender, GameUnloadEvent load) { } diff --git a/NEWorld/MainScript.cs b/NEWorld/MainScript.cs index 71d86d9..b3b0e3d 100644 --- a/NEWorld/MainScript.cs +++ b/NEWorld/MainScript.cs @@ -17,6 +17,7 @@ // along with NEWorld. If not, see . // using System; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Core; @@ -139,7 +140,7 @@ private async Task EstablishGameConnection() { await Core.Services.Get("Game.Client").Enable("127.0.0.1", 31111); await Client.GetStaticChunkIds.Call(); - Modules.Instance.WorkspaceInitialize(); + EventBus.Broadcast(this, new GameLoadEvent()); } private void LoadPlayer() @@ -183,10 +184,10 @@ public override void Cancel() TearDown(); } - private static void TearDown() + private void TearDown() { Core.Services.Get("Game.TaskDispatcher").Reset(); - Modules.Instance.WorkspaceFinalize(); + EventBus.Broadcast(this, new GameUnloadEvent()); } private static async Task RequestWorld() diff --git a/NEWorldShell/Program.cs b/NEWorldShell/Program.cs index 4289a30..ed256d8 100644 --- a/NEWorldShell/Program.cs +++ b/NEWorldShell/Program.cs @@ -30,9 +30,10 @@ public static void Main(string[] args) var cli = new ServerCommandLine(); var server = Services.Get("Game.Server"); server.Enable(31111); - Modules.Instance.WorkspaceInitialize(); + EventBus.Broadcast(null, new Game.GameLoadEvent()); server.Run(); cli.Start(); + EventBus.Broadcast(null, new Game.GameUnloadEvent()); } } } \ No newline at end of file From bdb3f9585feac9d19735db092d710a4932c57721 Mon Sep 17 00:00:00 2001 From: DWVoid Date: Sun, 10 Feb 2019 14:57:03 +0800 Subject: [PATCH 16/23] Add Automatic Reflective Control --- Core/ApplicationControl.cs | 43 ++++++++ Core/AssemblyReflectiveScanner.cs | 136 +++++++++++++++++++++++++ Core/EventBus.cs | 12 +++ Core/Module/Module.cs | 71 ++++++------- Core/Services.cs | 51 ++-------- Core/Utilities/Singleton.cs | 162 ------------------------------ Game/Events.cs | 10 +- Main/Main.cs | 10 ++ NEWorld.Linux/NEWorldApp.cs | 4 +- NEWorld.Windows/NEWorldApp.cs | 4 +- NEWorld.macOS/NEWorldApp.cs | 4 +- NEWorld/MainScript.cs | 6 +- NEWorldShell/Program.cs | 4 +- 13 files changed, 265 insertions(+), 252 deletions(-) create mode 100644 Core/ApplicationControl.cs create mode 100644 Core/AssemblyReflectiveScanner.cs delete mode 100644 Core/Utilities/Singleton.cs diff --git a/Core/ApplicationControl.cs b/Core/ApplicationControl.cs new file mode 100644 index 0000000..4c16dff --- /dev/null +++ b/Core/ApplicationControl.cs @@ -0,0 +1,43 @@ +// +// NEWorld/Core: ApplicationControl.cs +// NEWorld: A Free Game with Similar Rules to Minecraft. +// Copyright (C) 2015-2019 NEWorld Team +// +// NEWorld is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// NEWorld is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General +// Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with NEWorld. If not, see . +// + +namespace Core +{ + public static class ApplicationControl + { + public class Launch + { + } + + public class Shutdown + { + } + + public static void DoLaunch() + { + AssemblyReflectiveScanner.UpdateDomainAssemblies(); + EventBus.Broadcast(null, new Launch()); + } + + public static void DoShutdown() + { + EventBus.Broadcast(null, new Shutdown()); + } + } +} diff --git a/Core/AssemblyReflectiveScanner.cs b/Core/AssemblyReflectiveScanner.cs new file mode 100644 index 0000000..98ec255 --- /dev/null +++ b/Core/AssemblyReflectiveScanner.cs @@ -0,0 +1,136 @@ +// +// NEWorld/Core: AssemblyReflectiveScanner.cs +// NEWorld: A Free Game with Similar Rules to Minecraft. +// Copyright (C) 2015-2019 NEWorld Team +// +// NEWorld is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// NEWorld is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General +// Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with NEWorld. If not, see . +// + +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace Core +{ + public class DeclareAssemblyReflectiveScannerAttribute : Attribute + { + } + + public interface IAssemblyReflectiveScanner + { + void ProcessType(Type type); + } + + internal static class AssemblyReflectiveScanner + { + // Only for conflict resolve for multi-thread load + private static HashSet _processed = new HashSet(); + private static readonly object ProcessLock = new object(); + private static readonly List Scanners = new List(); + private static readonly List Scanned = new List(); + + internal static void UpdateDomainAssemblies() + { + AppDomain.CurrentDomain.AssemblyLoad += OnAssemblyLoadServiceRegisterAgent; + var snapshot = AppDomain.CurrentDomain.GetAssemblies(); + foreach (var assembly in snapshot) + if (!CheckIfAssemblyProcessed(assembly)) + ScanAssembly(assembly); + + lock (ProcessLock) + { + _processed = null; + lock (Scanned) + { + foreach (var assembly in Scanned) ProcessAssembly(assembly); + } + } + } + + private static bool CheckIfAssemblyProcessed(Assembly assembly) + { + lock (ProcessLock) + { + return (bool) _processed?.Contains(assembly.GetName()); + } + } + + private static void OnAssemblyLoadServiceRegisterAgent(object sender, AssemblyLoadEventArgs args) + { + ScanAssembly(args.LoadedAssembly); + } + + private static void ScanForAssemblyScanners(Assembly assembly) + { + foreach (var type in assembly.GetExportedTypes()) + if (CheckScannerType(type)) + InitializeScanner(type); + } + + private static bool CheckScannerType(Type type) + { + return type.IsDefined(typeof(DeclareAssemblyReflectiveScannerAttribute), false) && + typeof(IAssemblyReflectiveScanner).IsAssignableFrom(type); + } + + private static void InitializeScanner(Type type) + { + var currentScanner = (IAssemblyReflectiveScanner) Activator.CreateInstance(type); + lock (Scanners) + { + Scanners.Add(currentScanner); + } + + lock (ProcessLock) + { + if (_processed != null) return; + lock (Scanned) + { + foreach (var assembly in Scanned) + foreach (var target in assembly.GetExportedTypes()) + currentScanner.ProcessType(target); + } + } + } + + private static void ScanAssembly(Assembly assembly) + { + lock (ProcessLock) + { + _processed?.Add(assembly.GetName(true)); + } + + ScanForAssemblyScanners(assembly); + + lock (Scanned) + { + Scanned.Add(assembly); + } + + lock (ProcessLock) + { + if (_processed == null) ProcessAssembly(assembly); + } + } + + private static void ProcessAssembly(Assembly assembly) + { + foreach (var target in assembly.GetExportedTypes()) + lock (Scanners) + { + foreach (var currentScanner in Scanners) currentScanner.ProcessType(target); + } + } + } +} \ No newline at end of file diff --git a/Core/EventBus.cs b/Core/EventBus.cs index 5b6ad22..89c53a1 100644 --- a/Core/EventBus.cs +++ b/Core/EventBus.cs @@ -178,4 +178,16 @@ public void Invoke(object sender, T payload) } } } + + public sealed class DeclareGlobalBusEventHandlerClassAttribute : Attribute {} + + [DeclareAssemblyReflectiveScanner] + public sealed class GlobalBusEventHandlerClassDetector : IAssemblyReflectiveScanner + { + public void ProcessType(Type type) + { + if (type.IsDefined(typeof(DeclareGlobalBusEventHandlerClassAttribute), false)) + EventBus.AddCollection(Activator.CreateInstance(type)); + } + } } \ No newline at end of file diff --git a/Core/Module/Module.cs b/Core/Module/Module.cs index 1b288a6..f744345 100644 --- a/Core/Module/Module.cs +++ b/Core/Module/Module.cs @@ -19,7 +19,6 @@ using System; using System.Collections.Generic; using System.Reflection; -using Core.Utilities; namespace Core.Module { @@ -30,54 +29,56 @@ public interface IModule void OnMemoryWarning(); } - public class DeclareModuleAttribute : Attribute + public sealed class DeclareModuleAttribute : Attribute { } - public class Modules + [DeclareAssemblyReflectiveScanner] + [DeclareGlobalBusEventHandlerClass] + public sealed class Modules : IAssemblyReflectiveScanner { - private readonly Dictionary> modules; + private static readonly Dictionary Loaded = new Dictionary(); - private string basePath = Path.Modules(); - - private Modules() + private static string _basePath = Path.Modules(); + + public static void SetBasePath(string path) { - modules = new Dictionary>(); + _basePath = path; } - public IModule this[string name] => modules[name].Key; - - public static Modules Instance => Singleton.Instance; - - public void SetBasePath(string path) + public static void Load(string moduleFile) + { + Assembly.Load(moduleFile); + } + + [DeclareBusEventHandler] + public static void UnloadAll(object sender, ApplicationControl.Shutdown type) { - basePath = path; + lock (Loaded) + { + foreach (var module in Loaded) + module.Value.CoFinalize(); + Loaded.Clear(); + } } - public void Load(string moduleFile) + public void ProcessType(Type type) { - var assembly = Assembly.Load(moduleFile); - var possibleTypes = assembly.GetExportedTypes(); - foreach (var type in possibleTypes) - if (type.IsDefined(typeof(DeclareModuleAttribute), false) && typeof(IModule).IsAssignableFrom(type)) - try - { - var module = (IModule) Activator.CreateInstance(type); - module.CoInitialize(); - modules.Add(type.FullName ?? "", new KeyValuePair(module, assembly)); - LogPort.Debug($"Loaded Module : {type}"); - } - catch (Exception e) + if (type.IsDefined(typeof(DeclareModuleAttribute), false) && typeof(IModule).IsAssignableFrom(type)) + try + { + var module = (IModule) Activator.CreateInstance(type); + module.CoInitialize(); + lock (Loaded) { - LogPort.Debug($"Module {type} Load Failure : {e}"); + Loaded.Add(type.FullName ?? "", module); } - } - - public void UnloadAll() - { - foreach (var module in modules) - module.Value.Key.CoFinalize(); - modules.Clear(); + LogPort.Debug($"Loaded Module : {type}"); + } + catch (Exception e) + { + LogPort.Debug($"Module {type} Load Failure : {e}"); + } } } } \ No newline at end of file diff --git a/Core/Services.cs b/Core/Services.cs index a8ecb83..5706df3 100644 --- a/Core/Services.cs +++ b/Core/Services.cs @@ -16,6 +16,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with NEWorld. If not, see . // + using System; using System.Collections.Generic; using System.Reflection; @@ -50,19 +51,18 @@ public ServiceManagerException(string message, Exception innerException) : base( } } - public static class Services + [DeclareAssemblyReflectiveScanner] + public sealed class Services : IAssemblyReflectiveScanner { - // Only for conflict resolve for multi-thread load - private static HashSet _processed = new HashSet(); - private static readonly DisposeList Dispose = new DisposeList(); private static readonly Dictionary Ready = new Dictionary(); private static readonly Dictionary Providers = new Dictionary(); private static readonly Dictionary Dependencies = new Dictionary(); - static Services() + public void ProcessType(Type type) { - UpdateDomainAssemblies(); + if (type.IsDefined(typeof(DeclareServiceAttribute), false)) + Inject(type); } public static TI Get(string name) @@ -81,33 +81,6 @@ public static TI Get(string name) } } - private static void UpdateDomainAssemblies() - { - AppDomain.CurrentDomain.AssemblyLoad += OnAssemblyLoadServiceRegisterAgent; - var snapshot = AppDomain.CurrentDomain.GetAssemblies(); - foreach (var assembly in snapshot) - if (!CheckIfAssemblyProcessed(assembly)) - ScanAssembly(assembly); - - lock (_processed) - { - _processed = null; - } - } - - private static bool CheckIfAssemblyProcessed(Assembly assembly) - { - lock (_processed) - { - return (bool) _processed?.Contains(assembly.GetName()); - } - } - - private static void OnAssemblyLoadServiceRegisterAgent(object sender, AssemblyLoadEventArgs args) - { - ScanAssembly(args.LoadedAssembly); - } - public static bool TryGet(string name, out TI ins) { try @@ -122,18 +95,6 @@ public static bool TryGet(string name, out TI ins) } } - private static void ScanAssembly(Assembly assembly) - { - lock (assembly) - { - _processed?.Add(assembly.GetName(true)); - } - - foreach (var type in assembly.GetExportedTypes()) - if (type.IsDefined(typeof(DeclareServiceAttribute), false)) - Inject(type); - } - private static void Inject(Type tp) { var name = tp.GetCustomAttribute().Name; diff --git a/Core/Utilities/Singleton.cs b/Core/Utilities/Singleton.cs deleted file mode 100644 index da9c8bc..0000000 --- a/Core/Utilities/Singleton.cs +++ /dev/null @@ -1,162 +0,0 @@ -// -// NEWorld/Core: Singleton.cs -// NEWorld: A Free Game with Similar Rules to Minecraft. -// Copyright (C) 2015-2019 NEWorld Team -// -// NEWorld is free software: you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published -// by the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// NEWorld is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY -// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General -// Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with NEWorld. If not, see . -// -using System; -using System.Reflection; - -// ReSharper disable InconsistentlySynchronizedField - -namespace Core.Utilities -{ - /// - /// Represents errors that occur while creating a singleton. - /// - /// - /// http://msdn.microsoft.com/en-us/library/ms229064(VS.80).aspx - /// - [Serializable] - public class SingletonException : Exception - { - /// - /// Initializes a new instance with a specified error message. - /// - /// The message that describes the error. - public SingletonException(string message) - : base(message) - { - } - - /// - /// Initializes a new instance with a reference to the inner - /// exception that is the cause of this exception. - /// - /// - /// The exception that is the cause of the current exception, - /// or a null reference if no inner exception is specified. - /// - public SingletonException(Exception innerException) - : base(null, innerException) - { - } - - /// - /// Initializes a new instance with a specified error message and a - /// reference to the inner exception that is the cause of this exception. - /// - /// The message that describes the error. - /// - /// The exception that is the cause of the current exception, - /// or a null reference if no inner exception is specified. - /// - public SingletonException(string message, Exception innerException) - : base(message, innerException) - { - } - } - - /// - /// Manages the single instance of a class. - /// - /// - /// Generic variant of the strategy presented here : http://geekswithblogs.net/akraus1/articles/90803.aspx. - /// Prefered to http://www.yoda.arachsys.com/csharp/singleton.html, where static initialization doesn't allow - /// proper handling of exceptions, and doesn't allow retrying type initializers initialization later - /// (once a type initializer fails to initialize in .NET, it can't be re-initialized again). - /// - /// Type of the singleton class. - public static class Singleton - where T : class - { - #region Constructors - - /// - /// Type-initializer to prevent type to be marked with beforefieldinit. - /// - /// - /// This simply makes sure that static fields initialization occurs - /// when Instance is called the first time and not before. - /// - static Singleton() - { - } - - #endregion Constructors - - #region Fields - - /// - /// The single instance of the target class. - /// - /// - /// The volatile keyword makes sure to remove any compiler optimization that could make concurrent - /// threads reach a race condition with the double-checked lock pattern used in the Instance property. - /// See http://www.bluebytesoftware.com/blog/PermaLink,guid,543d89ad-8d57-4a51-b7c9-a821e3992bf6.aspx - /// - private static volatile T _instance; - - /// - /// The dummy object used for locking. - /// - // ReSharper disable once StaticMemberInGenericType - private static readonly object Lock = new object(); - - #endregion Fields - - - #region Properties - - /// - /// Gets the single instance of the class. - /// - public static T Instance - { - get - { - if (_instance != null) return _instance; - lock (Lock) - { - if (_instance == null) _instance = ConstructInstance(); - } - - return _instance; - } - } - - private static T ConstructInstance() - { - ConstructorInfo constructor; - try - { - // Binding flags exclude public constructors. - constructor = typeof(T).GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, - new Type[0], null); - } - catch (Exception exception) - { - throw new SingletonException(exception); - } - - if (constructor == null || constructor.IsAssembly) // Also exclude internal constructors. - throw new SingletonException($"A private or protected constructor is missing for '{typeof(T).Name}'."); - - return (T) constructor.Invoke(null); - } - - #endregion Properties - } -} \ No newline at end of file diff --git a/Game/Events.cs b/Game/Events.cs index 711a1d5..2409a14 100644 --- a/Game/Events.cs +++ b/Game/Events.cs @@ -19,23 +19,27 @@ namespace Game { - public class GameLoadEvent + public class GameRenderPrepareEvent { + } + public class GameLoadEvent + { } public class GameStartEvent { - } public class GameEndEvent { - } public class GameUnloadEvent { + } + public class GameRenderFinalizeEvent + { } } diff --git a/Main/Main.cs b/Main/Main.cs index 4117a59..33cdf72 100644 --- a/Main/Main.cs +++ b/Main/Main.cs @@ -42,6 +42,11 @@ public void CoInitialize() StaticChunkPool.Register("Main.RockChunk", new Chunk(new BlockData(_rockId))); StaticChunkPool.Register("Main.WaterChunk", new Chunk(new BlockData(_waterId))); EventBus.AddCollection(this); + } + + [DeclareBusEventHandler] + public void GameRenderInit(object sender, GameRenderPrepareEvent load) + { RendererInit(); } @@ -57,6 +62,11 @@ public void GameUnloads(object sender, GameUnloadEvent load) { } + [DeclareBusEventHandler] + public void GameRenderFinalize(object sender, GameRenderFinalizeEvent load) + { + } + public void CoFinalize() { } diff --git a/NEWorld.Linux/NEWorldApp.cs b/NEWorld.Linux/NEWorldApp.cs index 33f74e4..342e060 100644 --- a/NEWorld.Linux/NEWorldApp.cs +++ b/NEWorld.Linux/NEWorldApp.cs @@ -18,14 +18,16 @@ // namespace NEWorld.Linux { - internal class NEWorldApp + internal static class NEWorldApp { private static void Main(string[] args) { + Core.ApplicationControl.DoLaunch(); using (var game = new Xenko.Engine.Game()) { game.Run(); } + Core.ApplicationControl.DoShutdown(); } } } \ No newline at end of file diff --git a/NEWorld.Windows/NEWorldApp.cs b/NEWorld.Windows/NEWorldApp.cs index 7568a11..3e740fd 100644 --- a/NEWorld.Windows/NEWorldApp.cs +++ b/NEWorld.Windows/NEWorldApp.cs @@ -18,16 +18,18 @@ // namespace NEWorld.Windows { - internal class NEWorldApp + internal static class NEWorldApp { private static void Main(string[] args) { // TODO: Remove Later when Launching Server with Client is Possible var server = System.Diagnostics.Process.Start("NEWorldShell.exe"); + Core.ApplicationControl.DoLaunch(); using (var game = new Xenko.Engine.Game()) { game.Run(); } + Core.ApplicationControl.DoShutdown(); server?.Kill(); } } diff --git a/NEWorld.macOS/NEWorldApp.cs b/NEWorld.macOS/NEWorldApp.cs index 01b5dac..b9cc328 100644 --- a/NEWorld.macOS/NEWorldApp.cs +++ b/NEWorld.macOS/NEWorldApp.cs @@ -18,14 +18,16 @@ // namespace NEWorld.macOS { - internal class NEWorldApp + internal static class NEWorldApp { private static void Main(string[] args) { + Core.ApplicationControl.DoLaunch(); using (var game = new Xenko.Engine.Game()) { game.Run(); } + Core.ApplicationControl.DoShutdown(); } } } \ No newline at end of file diff --git a/NEWorld/MainScript.cs b/NEWorld/MainScript.cs index b3b0e3d..f2657cf 100644 --- a/NEWorld/MainScript.cs +++ b/NEWorld/MainScript.cs @@ -17,12 +17,10 @@ // along with NEWorld. If not, see . // using System; -using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Core; using Core.Module; -using Core.Utilities; using Game; using Game.Network; using Game.World; @@ -108,7 +106,7 @@ public class MainScript : SyncScript private void InitializeModules() { - Modules.Instance.Load("Main"); + Modules.Load("Main"); } private void InitializeContext() @@ -119,6 +117,7 @@ private void InitializeContext() Context.OperatingScene = Entity.Scene; LogPort.Logger = Log; Log.ActivateLog(LogMessageType.Debug); + EventBus.Broadcast(this, new GameRenderPrepareEvent()); } private void EstablishChunkService() @@ -188,6 +187,7 @@ private void TearDown() { Core.Services.Get("Game.TaskDispatcher").Reset(); EventBus.Broadcast(this, new GameUnloadEvent()); + EventBus.Broadcast(this, new GameRenderFinalizeEvent()); } private static async Task RequestWorld() diff --git a/NEWorldShell/Program.cs b/NEWorldShell/Program.cs index ed256d8..b26d1ff 100644 --- a/NEWorldShell/Program.cs +++ b/NEWorldShell/Program.cs @@ -26,7 +26,8 @@ internal static class Program { public static void Main(string[] args) { - Modules.Instance.Load("Main"); + ApplicationControl.DoLaunch(); + Modules.Load("Main"); var cli = new ServerCommandLine(); var server = Services.Get("Game.Server"); server.Enable(31111); @@ -34,6 +35,7 @@ public static void Main(string[] args) server.Run(); cli.Start(); EventBus.Broadcast(null, new Game.GameUnloadEvent()); + ApplicationControl.DoShutdown(); } } } \ No newline at end of file From 81bbc7b7ba72e2e8ca4cf45ff475686e1e577854 Mon Sep 17 00:00:00 2001 From: DWVoid Date: Sun, 10 Feb 2019 16:57:31 +0800 Subject: [PATCH 17/23] Add Reflection Filter Attribute to Filter Out Assemblies not Designed for the System --- Core/AssemblyReflectiveScanner.cs | 8 +++++++- Core/{Module => }/Module.cs | 3 ++- Core/Umbrella.cs | 20 ++++++++++++++++++++ Game/Umbrella.cs | 20 ++++++++++++++++++++ Main/Main.cs | 1 - Main/Umbrella.cs | 20 ++++++++++++++++++++ NEWorld/MainScript.cs | 1 - NEWorld/Umbrella.cs | 20 ++++++++++++++++++++ NEWorldShell/Program.cs | 1 - 9 files changed, 89 insertions(+), 5 deletions(-) rename Core/{Module => }/Module.cs (99%) create mode 100644 Core/Umbrella.cs create mode 100644 Game/Umbrella.cs create mode 100644 Main/Umbrella.cs create mode 100644 NEWorld/Umbrella.cs diff --git a/Core/AssemblyReflectiveScanner.cs b/Core/AssemblyReflectiveScanner.cs index 98ec255..b082191 100644 --- a/Core/AssemblyReflectiveScanner.cs +++ b/Core/AssemblyReflectiveScanner.cs @@ -23,7 +23,11 @@ namespace Core { - public class DeclareAssemblyReflectiveScannerAttribute : Attribute + public sealed class DeclareNeWorldAssemblyAttribute : Attribute + { + } + + public sealed class DeclareAssemblyReflectiveScannerAttribute : Attribute { } @@ -111,6 +115,8 @@ private static void ScanAssembly(Assembly assembly) _processed?.Add(assembly.GetName(true)); } + if (!assembly.IsDefined(typeof(DeclareNeWorldAssemblyAttribute), false)) return; + ScanForAssemblyScanners(assembly); lock (Scanned) diff --git a/Core/Module/Module.cs b/Core/Module.cs similarity index 99% rename from Core/Module/Module.cs rename to Core/Module.cs index f744345..a7ae666 100644 --- a/Core/Module/Module.cs +++ b/Core/Module.cs @@ -16,11 +16,12 @@ // You should have received a copy of the GNU Lesser General Public License // along with NEWorld. If not, see . // + using System; using System.Collections.Generic; using System.Reflection; -namespace Core.Module +namespace Core { public interface IModule { diff --git a/Core/Umbrella.cs b/Core/Umbrella.cs new file mode 100644 index 0000000..1bac328 --- /dev/null +++ b/Core/Umbrella.cs @@ -0,0 +1,20 @@ +// +// NEWorld/Core: Umbrella.cs +// NEWorld: A Free Game with Similar Rules to Minecraft. +// Copyright (C) 2015-2019 NEWorld Team +// +// NEWorld is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// NEWorld is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General +// Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with NEWorld. If not, see . +// + +[assembly:Core.DeclareNeWorldAssembly] diff --git a/Game/Umbrella.cs b/Game/Umbrella.cs new file mode 100644 index 0000000..2b9989f --- /dev/null +++ b/Game/Umbrella.cs @@ -0,0 +1,20 @@ +// +// NEWorld/Game: Umbrella.cs +// NEWorld: A Free Game with Similar Rules to Minecraft. +// Copyright (C) 2015-2019 NEWorld Team +// +// NEWorld is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// NEWorld is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General +// Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with NEWorld. If not, see . +// + +[assembly:Core.DeclareNeWorldAssembly] diff --git a/Main/Main.cs b/Main/Main.cs index 33cdf72..7912cbc 100644 --- a/Main/Main.cs +++ b/Main/Main.cs @@ -17,7 +17,6 @@ // along with NEWorld. If not, see . // using Core; -using Core.Module; using Game; using Game.Terrain; using Game.World; diff --git a/Main/Umbrella.cs b/Main/Umbrella.cs new file mode 100644 index 0000000..5cb3804 --- /dev/null +++ b/Main/Umbrella.cs @@ -0,0 +1,20 @@ +// +// NEWorld/Main: Umbrella.cs +// NEWorld: A Free Game with Similar Rules to Minecraft. +// Copyright (C) 2015-2019 NEWorld Team +// +// NEWorld is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// NEWorld is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General +// Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with NEWorld. If not, see . +// + +[assembly:Core.DeclareNeWorldAssembly] diff --git a/NEWorld/MainScript.cs b/NEWorld/MainScript.cs index f2657cf..596cb4b 100644 --- a/NEWorld/MainScript.cs +++ b/NEWorld/MainScript.cs @@ -20,7 +20,6 @@ using System.Threading; using System.Threading.Tasks; using Core; -using Core.Module; using Game; using Game.Network; using Game.World; diff --git a/NEWorld/Umbrella.cs b/NEWorld/Umbrella.cs new file mode 100644 index 0000000..f3bcda3 --- /dev/null +++ b/NEWorld/Umbrella.cs @@ -0,0 +1,20 @@ +// +// NEWorld/NEWorld: Umbrella.cs +// NEWorld: A Free Game with Similar Rules to Minecraft. +// Copyright (C) 2015-2019 NEWorld Team +// +// NEWorld is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// NEWorld is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General +// Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with NEWorld. If not, see . +// + +[assembly:Core.DeclareNeWorldAssembly] diff --git a/NEWorldShell/Program.cs b/NEWorldShell/Program.cs index b26d1ff..21a662a 100644 --- a/NEWorldShell/Program.cs +++ b/NEWorldShell/Program.cs @@ -17,7 +17,6 @@ // along with NEWorld. If not, see . // using Core; -using Core.Module; using Game.Network; namespace NEWorldShell From b26d89d19af2f2c29eff5004fd4844b245f15b49 Mon Sep 17 00:00:00 2001 From: DWVoid Date: Mon, 11 Feb 2019 02:07:45 +0800 Subject: [PATCH 18/23] Textures! --- Core/AssemblyReflectiveScanner.cs | 2 +- Core/Module.cs | 2 +- Core/Path.cs | 30 ----------- Game/Events.cs | 2 + Main/Assets/Textures/Blocks/Bedrock.xktex | 3 ++ Main/Assets/Textures/Blocks/Cement.xktex | 3 ++ Main/Assets/Textures/Blocks/Coal.xktex | 3 ++ Main/Assets/Textures/Blocks/Dirt.xktex | 3 ++ Main/Assets/Textures/Blocks/Glowstone.xktex | 3 ++ Main/Assets/Textures/Blocks/GrassRound.xktex | 3 ++ Main/Assets/Textures/Blocks/GrassTop.xktex | 3 ++ Main/Assets/Textures/Blocks/Iron.xktex | 3 ++ Main/Assets/Textures/Blocks/Null.xktex | 3 ++ Main/Assets/Textures/Blocks/Plank.xktex | 3 ++ Main/Assets/Textures/Blocks/Rock.xktex | 3 ++ Main/Assets/Textures/Blocks/Sand.xktex | 3 ++ Main/Assets/Textures/Blocks/Stone.xktex | 3 ++ Main/Assets/Textures/Blocks/TrunkRound.xktex | 3 ++ Main/Assets/Textures/Blocks/TrunkTop.xktex | 3 ++ Main/Assets/Textures/Blocks/Water.xktex | 3 ++ NEWorld/Effects/VertexTextureTerrain.cs | 2 + NEWorld/Effects/VertexTextureTerrain.xksl | 27 ++++++---- NEWorld/MainScript.cs | 15 +++++- NEWorld/Renderer/RdTextures.cs | 56 ++++++++------------ 24 files changed, 104 insertions(+), 80 deletions(-) delete mode 100644 Core/Path.cs diff --git a/Core/AssemblyReflectiveScanner.cs b/Core/AssemblyReflectiveScanner.cs index b082191..4b93882 100644 --- a/Core/AssemblyReflectiveScanner.cs +++ b/Core/AssemblyReflectiveScanner.cs @@ -66,7 +66,7 @@ private static bool CheckIfAssemblyProcessed(Assembly assembly) { lock (ProcessLock) { - return (bool) _processed?.Contains(assembly.GetName()); + return _processed != null && (bool) (_processed?.Contains(assembly.GetName())); } } diff --git a/Core/Module.cs b/Core/Module.cs index a7ae666..5620d60 100644 --- a/Core/Module.cs +++ b/Core/Module.cs @@ -40,7 +40,7 @@ public sealed class Modules : IAssemblyReflectiveScanner { private static readonly Dictionary Loaded = new Dictionary(); - private static string _basePath = Path.Modules(); + private static string _basePath = AppContext.BaseDirectory; public static void SetBasePath(string path) { diff --git a/Core/Path.cs b/Core/Path.cs deleted file mode 100644 index 9f671e5..0000000 --- a/Core/Path.cs +++ /dev/null @@ -1,30 +0,0 @@ -// -// NEWorld/Core: Path.cs -// NEWorld: A Free Game with Similar Rules to Minecraft. -// Copyright (C) 2015-2019 NEWorld Team -// -// NEWorld is free software: you can redistribute it and/or modify it -// under the terms of the GNU Lesser General Public License as published -// by the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// NEWorld is distributed in the hope that it will be useful, but WITHOUT -// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY -// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General -// Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with NEWorld. If not, see . -// -using System; - -namespace Core -{ - public static class Path - { - public static string Modules() - { - return AppContext.BaseDirectory; - } - } -} \ No newline at end of file diff --git a/Game/Events.cs b/Game/Events.cs index 2409a14..2e818ff 100644 --- a/Game/Events.cs +++ b/Game/Events.cs @@ -17,6 +17,8 @@ // along with NEWorld. If not, see . // +using Core; + namespace Game { public class GameRenderPrepareEvent diff --git a/Main/Assets/Textures/Blocks/Bedrock.xktex b/Main/Assets/Textures/Blocks/Bedrock.xktex index 116e3a6..974abd0 100644 --- a/Main/Assets/Textures/Blocks/Bedrock.xktex +++ b/Main/Assets/Textures/Blocks/Bedrock.xktex @@ -3,5 +3,8 @@ Id: 68b96536-d657-46f9-8319-173aa4f4af30 SerializedVersion: {Xenko: 2.0.0.0} Tags: [] Source: !file Bedrock.png +IsCompressed: false Type: !ColorTextureType + UseSRgbSampling: false ColorKeyColor: {R: 255, G: 0, B: 255, A: 255} +IsStreamable: false diff --git a/Main/Assets/Textures/Blocks/Cement.xktex b/Main/Assets/Textures/Blocks/Cement.xktex index f550681..dd8bbc1 100644 --- a/Main/Assets/Textures/Blocks/Cement.xktex +++ b/Main/Assets/Textures/Blocks/Cement.xktex @@ -3,5 +3,8 @@ Id: 729c1023-0d45-456b-b5af-9c2c9ef8b87a SerializedVersion: {Xenko: 2.0.0.0} Tags: [] Source: !file Cement.png +IsCompressed: false Type: !ColorTextureType + UseSRgbSampling: false ColorKeyColor: {R: 255, G: 0, B: 255, A: 255} +IsStreamable: false diff --git a/Main/Assets/Textures/Blocks/Coal.xktex b/Main/Assets/Textures/Blocks/Coal.xktex index a900546..a160a82 100644 --- a/Main/Assets/Textures/Blocks/Coal.xktex +++ b/Main/Assets/Textures/Blocks/Coal.xktex @@ -3,5 +3,8 @@ Id: def5376c-d58d-4872-8edc-bfd10cc66ba6 SerializedVersion: {Xenko: 2.0.0.0} Tags: [] Source: !file Coal.png +IsCompressed: false Type: !ColorTextureType + UseSRgbSampling: false ColorKeyColor: {R: 255, G: 0, B: 255, A: 255} +IsStreamable: false diff --git a/Main/Assets/Textures/Blocks/Dirt.xktex b/Main/Assets/Textures/Blocks/Dirt.xktex index 91759ef..194d4d4 100644 --- a/Main/Assets/Textures/Blocks/Dirt.xktex +++ b/Main/Assets/Textures/Blocks/Dirt.xktex @@ -3,5 +3,8 @@ Id: 872b5b39-8830-41cd-99a3-b1f729f88966 SerializedVersion: {Xenko: 2.0.0.0} Tags: [] Source: !file Dirt.png +IsCompressed: false Type: !ColorTextureType + UseSRgbSampling: false ColorKeyColor: {R: 255, G: 0, B: 255, A: 255} +IsStreamable: false diff --git a/Main/Assets/Textures/Blocks/Glowstone.xktex b/Main/Assets/Textures/Blocks/Glowstone.xktex index 1ad1f0e..c6f9d9f 100644 --- a/Main/Assets/Textures/Blocks/Glowstone.xktex +++ b/Main/Assets/Textures/Blocks/Glowstone.xktex @@ -3,5 +3,8 @@ Id: 6cff36b6-77c2-47de-b5d1-18465cb77201 SerializedVersion: {Xenko: 2.0.0.0} Tags: [] Source: !file Glowstone.png +IsCompressed: false Type: !ColorTextureType + UseSRgbSampling: false ColorKeyColor: {R: 255, G: 0, B: 255, A: 255} +IsStreamable: false diff --git a/Main/Assets/Textures/Blocks/GrassRound.xktex b/Main/Assets/Textures/Blocks/GrassRound.xktex index 0e05240..791ceca 100644 --- a/Main/Assets/Textures/Blocks/GrassRound.xktex +++ b/Main/Assets/Textures/Blocks/GrassRound.xktex @@ -3,5 +3,8 @@ Id: c896ecda-99c3-4815-b811-6dcf125c371c SerializedVersion: {Xenko: 2.0.0.0} Tags: [] Source: !file GrassRound.png +IsCompressed: false Type: !ColorTextureType + UseSRgbSampling: false ColorKeyColor: {R: 255, G: 0, B: 255, A: 255} +IsStreamable: false diff --git a/Main/Assets/Textures/Blocks/GrassTop.xktex b/Main/Assets/Textures/Blocks/GrassTop.xktex index c381e52..726a131 100644 --- a/Main/Assets/Textures/Blocks/GrassTop.xktex +++ b/Main/Assets/Textures/Blocks/GrassTop.xktex @@ -3,5 +3,8 @@ Id: 8950f4b3-c0bf-4ba1-85a7-d008f78b8b99 SerializedVersion: {Xenko: 2.0.0.0} Tags: [] Source: !file GrassTop.png +IsCompressed: false Type: !ColorTextureType + UseSRgbSampling: false ColorKeyColor: {R: 255, G: 0, B: 255, A: 255} +IsStreamable: false diff --git a/Main/Assets/Textures/Blocks/Iron.xktex b/Main/Assets/Textures/Blocks/Iron.xktex index bb13b1a..cdc066e 100644 --- a/Main/Assets/Textures/Blocks/Iron.xktex +++ b/Main/Assets/Textures/Blocks/Iron.xktex @@ -3,5 +3,8 @@ Id: 0c1e6502-9fe1-4fa6-b1c2-8e67f76f1f4f SerializedVersion: {Xenko: 2.0.0.0} Tags: [] Source: !file Iron.png +IsCompressed: false Type: !ColorTextureType + UseSRgbSampling: false ColorKeyColor: {R: 255, G: 0, B: 255, A: 255} +IsStreamable: false diff --git a/Main/Assets/Textures/Blocks/Null.xktex b/Main/Assets/Textures/Blocks/Null.xktex index a932bb1..59720d7 100644 --- a/Main/Assets/Textures/Blocks/Null.xktex +++ b/Main/Assets/Textures/Blocks/Null.xktex @@ -3,5 +3,8 @@ Id: 758069be-3d4f-4f11-a44c-8b5bbea08cbb SerializedVersion: {Xenko: 2.0.0.0} Tags: [] Source: !file Null.png +IsCompressed: false Type: !ColorTextureType + UseSRgbSampling: false ColorKeyColor: {R: 255, G: 0, B: 255, A: 255} +IsStreamable: false diff --git a/Main/Assets/Textures/Blocks/Plank.xktex b/Main/Assets/Textures/Blocks/Plank.xktex index a82087a..7ff1f80 100644 --- a/Main/Assets/Textures/Blocks/Plank.xktex +++ b/Main/Assets/Textures/Blocks/Plank.xktex @@ -3,5 +3,8 @@ Id: 6cde71ed-6d9a-4642-a312-d58c02dbb07d SerializedVersion: {Xenko: 2.0.0.0} Tags: [] Source: !file Plank.png +IsCompressed: false Type: !ColorTextureType + UseSRgbSampling: false ColorKeyColor: {R: 255, G: 0, B: 255, A: 255} +IsStreamable: false diff --git a/Main/Assets/Textures/Blocks/Rock.xktex b/Main/Assets/Textures/Blocks/Rock.xktex index 253ee7f..f6e7d48 100644 --- a/Main/Assets/Textures/Blocks/Rock.xktex +++ b/Main/Assets/Textures/Blocks/Rock.xktex @@ -3,5 +3,8 @@ Id: 266e2127-34ad-4978-ae76-a37dbebf3547 SerializedVersion: {Xenko: 2.0.0.0} Tags: [] Source: !file Rock.png +IsCompressed: false Type: !ColorTextureType + UseSRgbSampling: false ColorKeyColor: {R: 255, G: 0, B: 255, A: 255} +IsStreamable: false diff --git a/Main/Assets/Textures/Blocks/Sand.xktex b/Main/Assets/Textures/Blocks/Sand.xktex index 25114e5..642a2bb 100644 --- a/Main/Assets/Textures/Blocks/Sand.xktex +++ b/Main/Assets/Textures/Blocks/Sand.xktex @@ -3,5 +3,8 @@ Id: f432c34d-8800-43a5-8493-ed3507c53aa9 SerializedVersion: {Xenko: 2.0.0.0} Tags: [] Source: !file Sand.png +IsCompressed: false Type: !ColorTextureType + UseSRgbSampling: false ColorKeyColor: {R: 255, G: 0, B: 255, A: 255} +IsStreamable: false diff --git a/Main/Assets/Textures/Blocks/Stone.xktex b/Main/Assets/Textures/Blocks/Stone.xktex index e8fa5e6..869091c 100644 --- a/Main/Assets/Textures/Blocks/Stone.xktex +++ b/Main/Assets/Textures/Blocks/Stone.xktex @@ -3,5 +3,8 @@ Id: f35d797b-97bd-4f89-823e-7ce0254f8fd5 SerializedVersion: {Xenko: 2.0.0.0} Tags: [] Source: !file Stone.png +IsCompressed: false Type: !ColorTextureType + UseSRgbSampling: false ColorKeyColor: {R: 255, G: 0, B: 255, A: 255} +IsStreamable: false diff --git a/Main/Assets/Textures/Blocks/TrunkRound.xktex b/Main/Assets/Textures/Blocks/TrunkRound.xktex index a6e09e2..cbb544e 100644 --- a/Main/Assets/Textures/Blocks/TrunkRound.xktex +++ b/Main/Assets/Textures/Blocks/TrunkRound.xktex @@ -3,5 +3,8 @@ Id: bc3db60e-4472-4e7f-b1e8-626b4a0cc336 SerializedVersion: {Xenko: 2.0.0.0} Tags: [] Source: !file TrunkRound.png +IsCompressed: false Type: !ColorTextureType + UseSRgbSampling: false ColorKeyColor: {R: 255, G: 0, B: 255, A: 255} +IsStreamable: false diff --git a/Main/Assets/Textures/Blocks/TrunkTop.xktex b/Main/Assets/Textures/Blocks/TrunkTop.xktex index 176f240..8c18787 100644 --- a/Main/Assets/Textures/Blocks/TrunkTop.xktex +++ b/Main/Assets/Textures/Blocks/TrunkTop.xktex @@ -3,5 +3,8 @@ Id: f6251b49-efcd-451d-aeb9-f422b2c5cd90 SerializedVersion: {Xenko: 2.0.0.0} Tags: [] Source: !file TrunkTop.png +IsCompressed: false Type: !ColorTextureType + UseSRgbSampling: false ColorKeyColor: {R: 255, G: 0, B: 255, A: 255} +IsStreamable: false diff --git a/Main/Assets/Textures/Blocks/Water.xktex b/Main/Assets/Textures/Blocks/Water.xktex index ba3a364..28f35ac 100644 --- a/Main/Assets/Textures/Blocks/Water.xktex +++ b/Main/Assets/Textures/Blocks/Water.xktex @@ -3,5 +3,8 @@ Id: 3091e23d-b2af-4698-b2c8-b16af90eaa2c SerializedVersion: {Xenko: 2.0.0.0} Tags: [] Source: !file Water.png +IsCompressed: false Type: !ColorTextureType + UseSRgbSampling: false ColorKeyColor: {R: 255, G: 0, B: 255, A: 255} +IsStreamable: false diff --git a/NEWorld/Effects/VertexTextureTerrain.cs b/NEWorld/Effects/VertexTextureTerrain.cs index 02bf025..ef0070d 100644 --- a/NEWorld/Effects/VertexTextureTerrain.cs +++ b/NEWorld/Effects/VertexTextureTerrain.cs @@ -18,6 +18,8 @@ namespace Xenko.Rendering { public static partial class VertexTextureTerrainKeys { + public static readonly ValueParameterKey TexturePerLine = ParameterKeys.NewValue(); public static readonly ObjectParameterKey MeshTextureSampler = ParameterKeys.NewObject(); + public static readonly ObjectParameterKey Almg = ParameterKeys.NewObject(); } } diff --git a/NEWorld/Effects/VertexTextureTerrain.xksl b/NEWorld/Effects/VertexTextureTerrain.xksl index 685b40a..1934bd5 100644 --- a/NEWorld/Effects/VertexTextureTerrain.xksl +++ b/NEWorld/Effects/VertexTextureTerrain.xksl @@ -1,14 +1,19 @@ -shader VertexTextureTerrain : ComputeColor, TransformationBase +shader VertexTextureTerrain: ComputeColor, TransformationBase { - SamplerState MeshTextureSampler - { - Filter = MIN_MAG_MIP_LINEAR; - AddressU = Wrap; - AddressV = Wrap; - }; - // stage Texture2D Almg; + //(cbuffer for values, and rgroup for textures) + cbuffer PerMaterial { + stage float TexturePerLine; + } + rgroup PerMaterial { + SamplerState MeshTextureSampler + { + Filter = MIN_MAG_MIP_LINEAR; + AddressU = Wrap; + AddressV = Wrap; + }; + stage Texture2D Almg; + } stage stream uint2 Pack: COLOR0; - stage stream float4 Vertex : POSITION; stage stream float2 TexCoord : TEXCOORD0; stage stream float3 Normal : NORMAL; @@ -18,7 +23,7 @@ { uint high = streams.Pack.x, low = streams.Pack.y; streams.Vertex = float4((high >> 16) & 0xFF, (high >> 8) & 0xFF, high & 0xFF, 1); - streams.TexCoord = float2((low >> 8) & 0xFF, low & 0xFF); + streams.TexCoord = float2((low >> 8) & 0xFF, low & 0xFF) / TexturePerLine; streams.Shade = ((float)(((high >> 24) & 0xFF) + 1)) * 0.0625f; static const float3 Normals[6] = { float3(1, 0, 0), float3(-1, 0, 0), @@ -31,6 +36,6 @@ override float4 Compute() { - return float4(streams.Shade, streams.Shade, streams.Shade, 1.0f);//Almg.Sample(MeshTextureSampler, streams.TexCoord); + return float4(streams.Shade, streams.Shade, streams.Shade, 1.0f) * Almg.Sample(MeshTextureSampler, streams.TexCoord); } }; diff --git a/NEWorld/MainScript.cs b/NEWorld/MainScript.cs index 596cb4b..f8ba038 100644 --- a/NEWorld/MainScript.cs +++ b/NEWorld/MainScript.cs @@ -22,6 +22,7 @@ using Core; using Game; using Game.Network; +using Game.Terrain; using Game.World; using NEWorld.Renderer; using Xenko.Core.Diagnostics; @@ -61,11 +62,11 @@ public static IGame Game public static GraphicsContext GraphicsContext => Game.GraphicsContext; - public static CommandList CommandList => Game.GraphicsContext.CommandList; - public static IndexBufferBinding IndexBuffer { get; private set; } public static ContentManager Content { get; set; } + + public static RenderDrawContext RdwContext { get; set; } } public static class IndexBufferBuilder @@ -115,6 +116,7 @@ private void InitializeContext() Context.Material = Material; Context.OperatingScene = Entity.Scene; LogPort.Logger = Log; + Context.RdwContext = new RenderDrawContext(Services, RenderContext.GetShared(Services), Game.GraphicsContext); Log.ActivateLog(LogMessageType.Debug); EventBus.Broadcast(this, new GameRenderPrepareEvent()); } @@ -165,6 +167,7 @@ private async void Initialize() { InitializeContext(); InitializeModules(); + LoadTextures(); EstablishChunkService(); await EstablishGameConnection(); LoadPlayer(); @@ -172,6 +175,14 @@ private async void Initialize() StartTerrainRenderService(); } + private void LoadTextures() + { + var texture = RdTextures.FlushTextures(); + BlockRenderers.FlushTextures(Core.Services.Get("BlockTextures")); + Material.Passes[0].Parameters.Set(VertexTextureTerrainKeys.Almg, texture); + Material.Passes[0].Parameters.Set(VertexTextureTerrainKeys.TexturePerLine, RdTextures.TexturesPerLine); + } + public override void Start() { Initialize(); diff --git a/NEWorld/Renderer/RdTextures.cs b/NEWorld/Renderer/RdTextures.cs index 93e9266..bc730e3 100644 --- a/NEWorld/Renderer/RdTextures.cs +++ b/NEWorld/Renderer/RdTextures.cs @@ -22,6 +22,7 @@ using Xenko.Graphics; using System; using Xenko.Core.Mathematics; +using Xenko.Rendering.Images; namespace NEWorld.Renderer { @@ -30,11 +31,11 @@ public class RdTextures : IBlockTextures { public uint Add(string assetUri) { - var id = (uint) textures.Count; + var id = (uint) Textures.Count; var texture = Context.Content.Load(assetUri); if (Context.Content.IsLoaded(assetUri)) { - textures.Add(texture); + Textures.Add(texture); } return id; @@ -42,53 +43,38 @@ public uint Add(string assetUri) public void GetTexturePos(ref BlockTexCoord pos) { - pos.Tex = new Int2(0, 0); //new Int2((int) (pos.Id % texturePerLine), (int) (pos.Id / texturePerLine)); + pos.Tex = new Int2((int) (pos.Id % TexturesPerLine), (int) (pos.Id / TexturesPerLine)); } public static Texture FlushTextures() { - var count = textures.Count; - texturePerLine = (1 << (int)(Math.Ceiling(Math.Log(Math.Ceiling(Math.Sqrt(count))) / Math.Log(2)))); - var wid = texturePerLine * pixelPerTexture; + var count = Textures.Count; + TexturesPerLine = (1 << (int)(Math.Ceiling(Math.Log(Math.Ceiling(Math.Sqrt(count))) / Math.Log(2)))); + var wid = TexturesPerLine * pixelPerTexture; var texture = Texture.New2D(Context.GraphicsDevice, wid, wid, - 1/*(int) (Math.Log(pixelPerTexture) / Math.Log(2))*/, PixelFormat.R8G8B8A8_UInt); + 1/*(int) (Math.Log(pixelPerTexture) / Math.Log(2))*/, PixelFormat.R8G8B8A8_UNorm); + var result = Texture.New2D(Context.GraphicsDevice, pixelPerTexture, pixelPerTexture, 1, PixelFormat.R8G8B8A8_UNorm, TextureFlags.RenderTarget | TextureFlags.ShaderResource); + var scaler = new ImageScaler(SamplingPattern.Expanded); + scaler.SetOutput(result); for (var i = 0; i < count; ++i) { - var tile = textures[i]; - /*var raw = tile.GetData(Context.CommandList); - var unit = raw.Length / (tile.Width * tile.Height); - var down = new byte[unit * pixelPerTexture * pixelPerTexture]; - var xFact = (double)tile.Width / pixelPerTexture; - var yFact = (double)tile.Height / pixelPerTexture; - for (var xi = 0; xi < pixelPerTexture; ++xi) - { - for (var yi = 0; yi < pixelPerTexture; ++yi) - { - var from = (int)(unit * (Math.Round(xi * xFact) * pixelPerTexture + Math.Round(yi * yFact))); - var to = unit * (xi * pixelPerTexture + yi); - for (var n = 0; n < unit; ++n) - { - down[to + n] = raw[from + n]; - } - } - } - - var re = Texture.New2D(Context.GraphicsDevice, pixelPerTexture, pixelPerTexture, tile.Format, down);*/ - var re = tile; - var x = i % texturePerLine; - var y = i / texturePerLine; + var tile = Textures[i]; + scaler.SetInput(tile); + scaler.Draw(Context.RdwContext); + var x = i % TexturesPerLine; + var y = i / TexturesPerLine; var rx = x * pixelPerTexture; var ry = y * pixelPerTexture; - Context.CommandList.CopyRegion( - re, re.GetSubResourceIndex(0, 0), null, + Context.RdwContext.CommandList.CopyRegion( + result, result.GetSubResourceIndex(0, 0), null, texture, texture.GetSubResourceIndex(0, 0), rx, ry); } return texture; } - public static int TexturesPreLine => 1; + public static int TexturesPerLine { get; private set; } - private static int texturePerLine, pixelPerTexture = 32; - private static Listtextures = new List(); + private static int pixelPerTexture = 32; + private static readonly List Textures = new List(); } } \ No newline at end of file From 66972194f8e58d1dd58d9975caeca8561c45fc66 Mon Sep 17 00:00:00 2001 From: DWVoid Date: Mon, 11 Feb 2019 03:05:37 +0800 Subject: [PATCH 19/23] Mipmap --- NEWorld/Assets/MainScene.xkscene | 18 +++++++ NEWorld/Effects/VertexTextureTerrain.xksl | 2 +- NEWorld/Renderer/RdTextures.cs | 61 ++++++++++++++++------- 3 files changed, 63 insertions(+), 18 deletions(-) diff --git a/NEWorld/Assets/MainScene.xkscene b/NEWorld/Assets/MainScene.xkscene index a7ae45a..c6f77a7 100644 --- a/NEWorld/Assets/MainScene.xkscene +++ b/NEWorld/Assets/MainScene.xkscene @@ -10,6 +10,7 @@ Hierarchy: - ref!! 1cc17873-cca9-48fe-a61f-a1d061e26959 - ref!! b1058700-11a7-4296-bb63-2fefdb5f28b7 - ref!! b1588acf-0fcd-4377-bf5d-6c81dcf34484 + - ref!! 3cb14cc7-8d18-4297-833c-05b0988ea34e Parts: - Entity: Id: 1cc17873-cca9-48fe-a61f-a1d061e26959 @@ -36,6 +37,22 @@ Hierarchy: PartitionMode: !LightDirectionalShadowMap.PartitionLogarithmic {} ComputeTransmittance: false BiasParameters: {} + - Entity: + Id: 3cb14cc7-8d18-4297-833c-05b0988ea34e + Name: Ambient light + Components: + 055a0e18643d9b0b976c9cc0b1fff92a: !TransformComponent + Id: 56e85624-3f61-4c79-a480-0e9c245c2b29 + Position: {X: 0.0, Y: 0.0, Z: 0.0} + Rotation: {X: 0.0, Y: 0.0, Z: 0.0, W: 1.0} + Scale: {X: 1.0, Y: 1.0, Z: 1.0} + Children: {} + 6836afe6382702a77272903fb605a7fc: !LightComponent + Id: 2a92935c-26e7-44dd-89e6-f5f94e76ddea + Type: !LightAmbient + Color: !ColorRgbProvider + Value: {R: 1.0, G: 1.0, B: 1.0} + Intensity: 0.25 - Entity: Id: 8d346802-8fe6-4d18-957c-c6dd8404ea84 Name: Camera @@ -50,6 +67,7 @@ Hierarchy: Id: ad927bbe-0362-46f3-bf19-c11527de8a86 Name: null Projection: Perspective + FarClipPlane: 2048.0 Slot: dce133b3-fc2c-4978-8f3c-8a7bc66947f0 84e3e1914c52c7d890fe96ba860bf21f: !NEWorld.BasicCameraController,NEWorld Id: 0ae3e97b-49cb-4d05-983b-e3c306612767 diff --git a/NEWorld/Effects/VertexTextureTerrain.xksl b/NEWorld/Effects/VertexTextureTerrain.xksl index 1934bd5..550fd7f 100644 --- a/NEWorld/Effects/VertexTextureTerrain.xksl +++ b/NEWorld/Effects/VertexTextureTerrain.xksl @@ -7,7 +7,7 @@ rgroup PerMaterial { SamplerState MeshTextureSampler { - Filter = MIN_MAG_MIP_LINEAR; + Filter = MIN_MAG_MIP_POINT; AddressU = Wrap; AddressV = Wrap; }; diff --git a/NEWorld/Renderer/RdTextures.cs b/NEWorld/Renderer/RdTextures.cs index bc730e3..43a6695 100644 --- a/NEWorld/Renderer/RdTextures.cs +++ b/NEWorld/Renderer/RdTextures.cs @@ -51,25 +51,52 @@ public static Texture FlushTextures() var count = Textures.Count; TexturesPerLine = (1 << (int)(Math.Ceiling(Math.Log(Math.Ceiling(Math.Sqrt(count))) / Math.Log(2)))); var wid = TexturesPerLine * pixelPerTexture; - var texture = Texture.New2D(Context.GraphicsDevice, wid, wid, - 1/*(int) (Math.Log(pixelPerTexture) / Math.Log(2))*/, PixelFormat.R8G8B8A8_UNorm); - var result = Texture.New2D(Context.GraphicsDevice, pixelPerTexture, pixelPerTexture, 1, PixelFormat.R8G8B8A8_UNorm, TextureFlags.RenderTarget | TextureFlags.ShaderResource); - var scaler = new ImageScaler(SamplingPattern.Expanded); - scaler.SetOutput(result); - for (var i = 0; i < count; ++i) + using (Texture texture = Texture.New2D(Context.GraphicsDevice, wid, wid, PixelFormat.R8G8B8A8_UNorm), + result = Texture.New2D(Context.GraphicsDevice, pixelPerTexture, pixelPerTexture, + PixelFormat.R8G8B8A8_UNorm, TextureFlags.RenderTarget | TextureFlags.ShaderResource)) { - var tile = Textures[i]; - scaler.SetInput(tile); - scaler.Draw(Context.RdwContext); - var x = i % TexturesPerLine; - var y = i / TexturesPerLine; - var rx = x * pixelPerTexture; - var ry = y * pixelPerTexture; - Context.RdwContext.CommandList.CopyRegion( - result, result.GetSubResourceIndex(0, 0), null, - texture, texture.GetSubResourceIndex(0, 0), rx, ry); + using (var scaler = new ImageScaler(SamplingPattern.Linear)) + { + scaler.SetOutput(result); + for (var i = 0; i < count; ++i) + { + var tile = Textures[i]; + scaler.SetInput(tile); + scaler.Draw(Context.RdwContext); + var x = i % TexturesPerLine; + var y = i / TexturesPerLine; + var rx = x * pixelPerTexture; + var ry = y * pixelPerTexture; + Context.RdwContext.CommandList.CopyRegion( + result, result.GetSubResourceIndex(0, 0), null, + texture, texture.GetSubResourceIndex(0, 0), rx, ry); + } + return MakeMipmap(texture, (int) Math.Floor(Math.Log(pixelPerTexture) / Math.Log(2)), scaler); + } } - return texture; + } + + private static Texture MakeMipmap(Texture input, int levels, ImageScaler scaler) + { + var ret = Texture.New2D(Context.GraphicsDevice, input.Width, input.Height, levels + 1, input.Format); + var fact = 1; + scaler.SetInput(input); + for (var i = 0; i <= levels; ++i) + { + using (var target = Texture.New2D(Context.GraphicsDevice, input.Width / fact, input.Height / fact, + input.Format, TextureFlags.RenderTarget | TextureFlags.ShaderResource)) + { + scaler.SetOutput(target); + scaler.Draw(Context.RdwContext); + Context.RdwContext.CommandList.CopyRegion( + target, target.GetSubResourceIndex(0, 0), null, + ret, ret.GetSubResourceIndex(0, i), 0, 0); + } + + fact *= 2; + } + + return ret; } public static int TexturesPerLine { get; private set; } From a4490f6f23a62ef181371cdcd21480fc361b27ad Mon Sep 17 00:00:00 2001 From: DWVoid Date: Mon, 11 Feb 2019 10:32:49 +0800 Subject: [PATCH 20/23] Transparent Render --- NEWorld/Assets/MainScene.xkscene | 1 + NEWorld/Assets/VoxelMaterialTransparent.xkmat | 19 +++++++++++++++++++ NEWorld/MainScript.cs | 7 ++++++- NEWorld/Renderer/RdChunk.cs | 8 ++++++-- 4 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 NEWorld/Assets/VoxelMaterialTransparent.xkmat diff --git a/NEWorld/Assets/MainScene.xkscene b/NEWorld/Assets/MainScene.xkscene index c6f77a7..03a6ca0 100644 --- a/NEWorld/Assets/MainScene.xkscene +++ b/NEWorld/Assets/MainScene.xkscene @@ -103,3 +103,4 @@ Hierarchy: d80b4d8f8e4c7517fb33931ae02a1875: !NEWorld.MainScript,NEWorld Id: ad46feb5-e564-418b-8912-d79cfbc7a12d Material: 9751d0fa-3d67-4760-ac85-9d209a2d12cb:VoxelMaterial + MaterialTransparent: 9b99a21d-5934-44d8-8267-6ae0a64d4ff1:VoxelMaterialTransparent diff --git a/NEWorld/Assets/VoxelMaterialTransparent.xkmat b/NEWorld/Assets/VoxelMaterialTransparent.xkmat new file mode 100644 index 0000000..5076642 --- /dev/null +++ b/NEWorld/Assets/VoxelMaterialTransparent.xkmat @@ -0,0 +1,19 @@ +!MaterialAsset +Id: 9b99a21d-5934-44d8-8267-6ae0a64d4ff1 +SerializedVersion: {Xenko: 2.0.0.0} +Tags: [] +Attributes: + Diffuse: !MaterialDiffuseMapFeature + DiffuseMap: !ComputeShaderClassColor + MixinReference: VertexTextureTerrain + Generics: {} + CompositionNodes: {} + DiffuseModel: !MaterialDiffuseLambertModelFeature {} + Transparency: !MaterialTransparencyBlendFeature + Alpha: !ComputeFloat + Value: 1.0 + Tint: !ComputeColor + Value: {R: 1.0, G: 1.0, B: 1.0, A: 1.0} + Overrides: + UVScale: {X: 1.0, Y: 1.0} +Layers: {} diff --git a/NEWorld/MainScript.cs b/NEWorld/MainScript.cs index f8ba038..db0f802 100644 --- a/NEWorld/MainScript.cs +++ b/NEWorld/MainScript.cs @@ -56,6 +56,8 @@ public static IGame Game public static Material Material { get; set; } + public static Material MaterialTransparent { get; set; } + public static Scene OperatingScene { get; set; } public static GraphicsDevice GraphicsDevice => Game.GraphicsDevice; @@ -95,7 +97,7 @@ public class MainScript : SyncScript // Current world private World currentWorld; public Material Material; - + public Material MaterialTransparent; // Player private Player player; @@ -114,6 +116,7 @@ private void InitializeContext() Context.Game = Game; Context.Content = Content; Context.Material = Material; + Context.MaterialTransparent = MaterialTransparent; Context.OperatingScene = Entity.Scene; LogPort.Logger = Log; Context.RdwContext = new RenderDrawContext(Services, RenderContext.GetShared(Services), Game.GraphicsContext); @@ -181,6 +184,8 @@ private void LoadTextures() BlockRenderers.FlushTextures(Core.Services.Get("BlockTextures")); Material.Passes[0].Parameters.Set(VertexTextureTerrainKeys.Almg, texture); Material.Passes[0].Parameters.Set(VertexTextureTerrainKeys.TexturePerLine, RdTextures.TexturesPerLine); + MaterialTransparent.Passes[0].Parameters.Set(VertexTextureTerrainKeys.Almg, texture); + MaterialTransparent.Passes[0].Parameters.Set(VertexTextureTerrainKeys.TexturePerLine, RdTextures.TexturesPerLine); } public override void Start() diff --git a/NEWorld/Renderer/RdChunk.cs b/NEWorld/Renderer/RdChunk.cs index 5479fee..9b00315 100644 --- a/NEWorld/Renderer/RdChunk.cs +++ b/NEWorld/Renderer/RdChunk.cs @@ -54,9 +54,13 @@ public ChunkVboGen Generate(Chunk chunk) var mesh0 = vaOpacity.Dump(); var mesh1 = vaTranslucent.Dump(); - Model = mesh0 != null || mesh1 != null ? new Model {new MaterialInstance(Context.Material)} : null; + Model = mesh0 != null || mesh1 != null ? new Model {new MaterialInstance(Context.Material), new MaterialInstance(Context.MaterialTransparent)} : null; if (mesh0 != null) Model.Add(mesh0); - if (mesh1 != null) Model.Add(mesh1); + if (mesh1 != null) + { + Model.Add(mesh1); + mesh1.MaterialIndex = 1; + } } return this; From 67252724c0f7bf2d1fd4562c3bf058b4758eb370 Mon Sep 17 00:00:00 2001 From: Harry Null Date: Mon, 11 Feb 2019 14:54:35 +0800 Subject: [PATCH 21/23] Implement chunk save/load --- Game/World/WorldTasks.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/Game/World/WorldTasks.cs b/Game/World/WorldTasks.cs index ca77e10..f38bb5a 100644 --- a/Game/World/WorldTasks.cs +++ b/Game/World/WorldTasks.cs @@ -88,7 +88,17 @@ private void Reset(Chunk chk) private async void LocalLoad(World world, Int3 position) { await ChunkService.TaskDispatcher.NextReadOnlyChance(); - Reset(new Chunk(position, world)); + Chunk chk; + if (world.ChunkExistsInDisk(chunk)) + { + chk = new Chunk(position, world, Chunk.InitOption.None); + world.LoadChunkFromDisk(ref chk); + } + else + { + chk = new Chunk(position, world); + } + Reset(chk); } } @@ -111,6 +121,7 @@ public void Task() //TODO: for multiplayer situation, it should decrease ref counter instead of deleting chunk.Dispose(); chunk.World.DeleteChunk(chunk.Position); + chunk.World.SaveChunkToDisk(chunk); } } From 0782aab822f568a2aa5ebb127471fa283666b693 Mon Sep 17 00:00:00 2001 From: Harry Null Date: Mon, 11 Feb 2019 14:56:43 +0800 Subject: [PATCH 22/23] Implement chunk save/load --- Game/World/WorldFileStorage.cs | 86 ++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 Game/World/WorldFileStorage.cs diff --git a/Game/World/WorldFileStorage.cs b/Game/World/WorldFileStorage.cs new file mode 100644 index 0000000..e27e947 --- /dev/null +++ b/Game/World/WorldFileStorage.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Runtime.Serialization.Formatters.Binary; +using System.Text; +using System.Threading; +using MessagePack.Internal; +using Xenko.Core.Mathematics; + +namespace Game.World { + public partial class World + { + private FileStream _indexFileStream; + private FileStream _dataFileStream; + private Dictionary _index; + private Mutex _indexLock; + private Mutex _dataLock; + + private void EnsureFileStorageInitialized() + { + if (_indexFileStream == null) + { + _indexFileStream = new FileStream(Name + ".index", + FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read); + if (_indexFileStream.Length != 0) + _index = new BinaryFormatter().Deserialize(_indexFileStream) as Dictionary; + else + _index = new Dictionary(); + } + if (_dataFileStream == null) + _dataFileStream = new FileStream(Name + ".data", + FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read); + } + + private Int64 FindChunkOffsetInFile(Int3 position) + { + if(_index.TryGetValue(position, out var ret)) + { + return ret; + } + + lock (_indexLock) + { + var offset = _index.Count; + _index[position] = offset; + new BinaryFormatter().Serialize(_indexFileStream, _index); + + return offset; + } + } + + public void SaveChunkToDisk(Chunk chunk) + { + EnsureFileStorageInitialized(); + + var buffer = new byte[32768 * 4]; + chunk.SerializeTo(buffer); + lock (_dataLock) { + _dataFileStream.Seek(FindChunkOffsetInFile(chunk.Position), SeekOrigin.Begin); + _dataFileStream.Write(buffer, 0, buffer.Length); + } + } + + public bool ChunkExistsInDisk(Chunk chunk) + { + lock (_indexLock) { + return _index.ContainsKey(chunk.Position); + } + } + + public bool LoadChunkFromDisk(ref Chunk chunk) + { + EnsureFileStorageInitialized(); + + var buffer = new byte[32768 * 4]; + lock (_dataLock) + { + _dataFileStream.Seek(FindChunkOffsetInFile(chunk.Position), SeekOrigin.Begin); + _dataFileStream.Read(buffer, 0, buffer.Length); + } + chunk.DeserializeFrom(buffer); + return true; + } + } +} From 25364ebfd326db6d71fbfd0b08fb1bfebf4f7e2d Mon Sep 17 00:00:00 2001 From: Harry Null Date: Tue, 12 Feb 2019 09:13:05 +0800 Subject: [PATCH 23/23] Fix chunk dispose --- Game/World/WorldTasks.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Game/World/WorldTasks.cs b/Game/World/WorldTasks.cs index f38bb5a..70104d6 100644 --- a/Game/World/WorldTasks.cs +++ b/Game/World/WorldTasks.cs @@ -119,9 +119,9 @@ public UnloadChunkTask(Chunk chk) public void Task() { //TODO: for multiplayer situation, it should decrease ref counter instead of deleting - chunk.Dispose(); - chunk.World.DeleteChunk(chunk.Position); chunk.World.SaveChunkToDisk(chunk); + chunk.World.DeleteChunk(chunk.Position); + chunk.Dispose(); } }