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/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..4b93882
--- /dev/null
+++ b/Core/AssemblyReflectiveScanner.cs
@@ -0,0 +1,142 @@
+//
+// 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 sealed class DeclareNeWorldAssemblyAttribute : Attribute
+ {
+ }
+
+ public sealed 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 _processed != null && (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));
+ }
+
+ if (!assembly.IsDefined(typeof(DeclareNeWorldAssemblyAttribute), false)) return;
+
+ 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/Core.csproj b/Core/Core.csproj
index 8814d3f..7396d2f 100644
--- a/Core/Core.csproj
+++ b/Core/Core.csproj
@@ -1,75 +1,12 @@
-
-
-
+
+
- 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/EventBus.cs b/Core/EventBus.cs
new file mode 100644
index 0000000..89c53a1
--- /dev/null
+++ b/Core/EventBus.cs
@@ -0,0 +1,193 @@
+//
+// 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.Threading;
+
+namespace Core
+{
+ public class DeclareBusEventHandlerAttribute : Attribute
+ {
+ }
+
+ 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 = 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
+ {
+ 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");
+ }
+ }
+ }
+
+ 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();
+ }
+ }
+ }
+
+ 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/Generic.cs b/Core/Generic.cs
index 277dacb..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,54 +16,10 @@
// 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
{
- 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 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
new file mode 100644
index 0000000..508e928
--- /dev/null
+++ b/Core/LogPort.cs
@@ -0,0 +1,36 @@
+//
+// 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
+{
+ public static class LogPort
+ {
+ public static Logger Logger { private get; set; }
+
+ public static void Debug(string str)
+ {
+ if (Logger != null)
+ Logger.Debug(str);
+ else
+ Console.WriteLine(str);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Core/Math/Mat4.cs b/Core/Math/Mat4.cs
index f7ad55a..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,257 +16,11 @@
// 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;
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) => new Mat4F(1.0f)
- {
- Data =
- {
- [3] = delta.X,
- [7] = delta.Y,
- [11] = delta.Z
- }
- };
-
- // Construct a identity matrix
- public static Mat4F Identity() => 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;
@@ -287,10 +41,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;
}
@@ -333,34 +84,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()
{
@@ -388,58 +111,28 @@ public Mat4D Transposed()
};
}
- // Inverse matrix
- public Mat4D Inverse(double[] data)
+ // Construct a translation matrix
+ public static Mat4D Translation(Double3 delta)
{
- Data = data;
- var res = Identity();
- for (uint i = 0; i < 4; i++)
+ return new Mat4D(1.0f)
{
- 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++)
+ Data =
{
- res.MultAndAdd((uint) i, j, -Data[j * 4 + i]);
- MultAndAdd((uint) i, j, -Data[j * 4 + i]);
+ [3] = delta.X,
+ [7] = delta.Y,
+ [11] = delta.Z
}
- }
-
- return this;
+ };
}
- // Construct a translation matrix
- public static Mat4D Translation(Vec3 delta) => new Mat4D(1.0f)
- {
- 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)
+ public static Mat4D Rotation(double degrees, Double3 vec)
{
vec.Normalize();
var alpha = degrees * Pi / 180.0f;
@@ -464,52 +157,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 7f7b417..0000000
--- a/Core/Math/Vector.cs
+++ /dev/null
@@ -1,137 +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() => Square(X) + Square(Y);
-
- public double Length() => 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) =>
- 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 bool Equals(Vec2 other) =>
- 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() => Square(X) + Square(Y) + Square(Z);
-
- public double Length() => 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) =>
- 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) =>
- 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) =>
- 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);
-
- 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/Module.cs b/Core/Module.cs
new file mode 100644
index 0000000..5620d60
--- /dev/null
+++ b/Core/Module.cs
@@ -0,0 +1,85 @@
+//
+// NEWorld/Core: Module.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 interface IModule
+ {
+ void CoInitialize();
+ void CoFinalize();
+ void OnMemoryWarning();
+ }
+
+ public sealed class DeclareModuleAttribute : Attribute
+ {
+ }
+
+ [DeclareAssemblyReflectiveScanner]
+ [DeclareGlobalBusEventHandlerClass]
+ public sealed class Modules : IAssemblyReflectiveScanner
+ {
+ private static readonly Dictionary Loaded = new Dictionary();
+
+ private static string _basePath = AppContext.BaseDirectory;
+
+ public static void SetBasePath(string path)
+ {
+ _basePath = path;
+ }
+
+ public static void Load(string moduleFile)
+ {
+ Assembly.Load(moduleFile);
+ }
+
+ [DeclareBusEventHandler]
+ public static void UnloadAll(object sender, ApplicationControl.Shutdown type)
+ {
+ lock (Loaded)
+ {
+ foreach (var module in Loaded)
+ module.Value.CoFinalize();
+ Loaded.Clear();
+ }
+ }
+
+ public void ProcessType(Type type)
+ {
+ if (type.IsDefined(typeof(DeclareModuleAttribute), false) && typeof(IModule).IsAssignableFrom(type))
+ try
+ {
+ var module = (IModule) Activator.CreateInstance(type);
+ module.CoInitialize();
+ lock (Loaded)
+ {
+ Loaded.Add(type.FullName ?? "", module);
+ }
+ 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/Module/Module.cs b/Core/Module/Module.cs
deleted file mode 100644
index 3651516..0000000
--- a/Core/Module/Module.cs
+++ /dev/null
@@ -1,87 +0,0 @@
-//
-// Core: Module.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.Reflection;
-using Core.Utilities;
-
-namespace Core.Module
-{
- public interface IModule
- {
- void CoInitialize();
- void CoFinalize();
- void OnMemoryWarning();
- }
-
- public class DeclareModuleAttribute : Attribute
- {
- }
-
- public class Modules
- {
- private Modules()
- {
- _modules = new Dictionary>();
- }
-
- 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);
- module.CoInitialize();
- _modules.Add(type.FullName ?? "", new KeyValuePair(module, assembly));
- Console.WriteLine($"Loaded Module : {type}");
- }
- catch (Exception e)
- {
- Console.WriteLine($"Module {type} Load Failure : {e}");
- }
- }
- }
-
- Services.ScanAssembly(assembly);
- }
-
- 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 99d6e97..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,40 +16,77 @@
// 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;
-using Core.Utilities;
+using System.Threading.Tasks;
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()
+ {
+ ReleaseUnmanagedResources();
+ GC.SuppressFinalize(this);
+ }
- public void NegotiateProtocols()
+ ~Client()
+ {
+ ReleaseUnmanagedResources();
+ }
+
+ 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);
- foreach (var entry in 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();
+ }
+
+ public ConnectionHost.Connection GetConnection()
+ {
+ return connection;
+ }
- private static int ProtocolSorter(Protocol x, Protocol y) => Comparer.Default.Compare(x.Id, y.Id);
+ private static int ProtocolSorter(Protocol x, Protocol y)
+ {
+ return Comparer.Default.Compare(x.Id, y.Id);
+ }
- private readonly ConnectionHost _connHost = new ConnectionHost();
+ private void ReleaseUnmanagedResources()
+ {
+ Close();
+ }
}
}
\ No newline at end of file
diff --git a/Core/Network/ConnectionHost.cs b/Core/Network/ConnectionHost.cs
index af63e76..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,147 +16,381 @@
// 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 System.Diagnostics;
+using System.IO;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
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 readonly ReaderWriterLockSlim writeLock = new ReaderWriterLockSlim();
+ 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()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ ~Session()
+ {
+ Dispose(false);
+ }
+
+ internal Receive WaitMessage()
+ {
+ return new Receive(this);
+ }
+
+ public Send CreateMessage(uint protocol)
+ {
+ return new Send(this, protocol);
+ }
+
+ private void ReleaseUnmanagedResources()
+ {
+ ios.Close();
+ }
+
+ private void Dispose(bool disposing)
{
- public Connection(ulong cid, TcpClient client, ConnectionHost server)
+ ReleaseUnmanagedResources();
+ if (disposing)
{
- _cid = cid;
- _client = client;
- _server = server;
- Stream = client.GetStream();
- _finalize = Start();
+ conn?.Dispose();
+ ios?.Dispose();
+ writeBuffer?.Dispose();
+ buffer?.Dispose();
}
+ }
- public bool Valid { get; private set; }
+ public sealed class Receive : IDisposable
+ {
+ private Stream ios;
- public void Close()
+ internal Receive(Session s)
{
- CloseDown();
- _finalize.Wait();
+ Session = s;
+ ios = Session.ios;
+ Raw = Session.storage;
}
- private async Task Start()
+ public byte[] Raw { get; private set; }
+
+ public Session Session { get; }
+
+ public void Dispose()
{
- Valid = true;
- var headerCache = new byte[8]; // ["NWRC"] + Int32BE(Protocol Id)
- while (Valid)
+ }
+
+ internal async Task LoadExpected(int length)
+ {
+ if (length == 0) return;
+
+ if (length > Raw.Length)
{
- try
- {
- var bytesRead = await Stream.ReadAsync(headerCache, 0, 8);
- if (VerifyPackageValidity(headerCache, bytesRead))
- try
- {
- _server.Protocols[GetProtocolId(headerCache)].HandleRequest(Stream);
- }
- catch (Exception e)
- {
- Console.WriteLine(e.ToString());
- }
- else
- throw new Exception("Bad Package Recieved");
- }
- catch (Exception e)
- {
- if (_client.Connected == false)
- break;
- Console.WriteLine($"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);
+ }
+ else
+ {
+ Session.buffer.Seek(0, SeekOrigin.Begin);
}
- CloseDown();
+ 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);
+ }
+
+ private static int GetProtocolId(byte[] head)
+ {
+ return (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';
+ }
+
+ public byte ReadU8()
+ {
+ var ret = ios.ReadByte();
+ if (ret >= 0)
+ return (byte) ret;
+ throw new EndOfStreamException();
+ }
+
+ public char ReadChar()
+ {
+ return (char) ReadU16();
+ }
+
+ public ushort ReadU16()
+ {
+ return (ushort) ((ReadU8() << 8) | ReadU8());
}
- public NetworkStream Stream { get; }
+ public uint ReadU32()
+ {
+ return (uint) ((ReadU16() << 16) | ReadU16());
+ }
- private static int GetProtocolId(byte[] head) => head[4] << 24 | head[5] << 16 | head[6] << 8 | head[7];
+ public ulong ReadU64()
+ {
+ return (ReadU32() << 32) | ReadU32();
+ }
- private static bool VerifyPackageValidity(byte[] head, int read) =>
- head[0] == 'N' && head[1] == 'W' && head[2] == 'R' && head[3] == 'C' && read == 8;
+ 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();
+ }
+ }
- private ulong _cid;
- private readonly TcpClient _client;
- private readonly ConnectionHost _server;
- private readonly Task _finalize;
+ 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;
+ session.writeLock.EnterWriteLock();
+ 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()
+ {
+ ReleaseUnmanagedResources();
+ GC.SuppressFinalize(this);
+ }
+
+
+ public void Write(byte val)
+ {
+ buffer.WriteByte(val);
+ }
+
+ public void Write(char val)
+ {
+ Write((byte) (val >> 8));
+ Write((byte) (val & 0xFF));
+ }
+
+ public void Write(ushort val)
{
- Protocols.Add(newProtocol);
+ 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);
+ }
+
+ private void ReleaseUnmanagedResources()
+ {
+ FlushBuffer();
+ Session.writeLock.ExitWriteLock();
+ }
+
+ ~Send()
+ {
+ ReleaseUnmanagedResources();
+ }
+ }
+ }
+
+ [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()
+ {
+ _connections = new List();
}
- public void SweepInvalidConnectionsIfNecessary()
+ public void Dispose()
{
- var utilization = 1.0 - (double) _invalidConnections / _clients.Count;
+ ReleaseUnmanagedResources();
+ GC.SuppressFinalize(this);
+ }
+
+ ~ConnectionHost()
+ {
+ ReleaseUnmanagedResources();
+ }
+
+ private static void SweepInvalidConnectionsIfNecessary()
+ {
+ var utilization = (double) _connectionCounter / _connections.Count;
if (utilization < UtilizationThreshold)
SweepInvalidConnections();
}
- public Connection GetConnection(ulong id) => _clients[id];
-
- private void SweepInvalidConnections()
+ private static void SweepInvalidConnections()
{
- foreach (var hd in _clients.ToList())
- if (!hd.Value.Valid)
- {
- _clients.Remove(hd.Key);
- Interlocked.Decrement(ref _invalidConnections);
- }
+ var swap = new List();
+ foreach (var hd in _connections)
+ if (hd.Valid)
+ swap.Add(hd);
+ _connections = swap;
}
- public void AddConnection(TcpClient conn)
+ public static Connection Add(TcpClient conn, List protocols)
{
- _clients.Add(_sessionIdTop, new Connection(_sessionIdTop, conn, this));
- ++_sessionIdTop;
+ var connect = new Connection(conn, protocols);
+ _connections.Add(connect);
+ return connect;
}
- private ulong _sessionIdTop;
- private int _invalidConnections;
- public readonly List Protocols;
- private readonly Dictionary _clients;
- private readonly object _protocolLock = new object();
+ public static int CountConnections()
+ {
+ return _connectionCounter;
+ }
- public void CloseAll()
+ private void ReleaseUnmanagedResources()
{
- foreach (var hd in _clients)
- hd.Value.Close();
+ foreach (var hd in _connections)
+ hd.Close();
}
- 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 && Session.Live)
+ 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 c8eeee2..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,113 +16,30 @@
// You should have received a copy of the GNU Lesser General Public License
// 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)
- };
+ public uint Id { get; set; }
- 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);
+ public int Expecting { get; protected set; }
- 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 abstract class StandardProtocol : Protocol
- {
- protected abstract void HandleRequestData(byte[] data, NetworkStream stream);
-
- 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 1da960c..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,104 +16,121 @@
// 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.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
-using Core.Utilities;
-using MsgPack.Serialization;
+using MessagePack;
namespace Core.Network
{
- public static class ProtocolFetchProtocol
+ public static class Handshake
{
+ 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 MessagePackSerializer.Deserialize[]>(result);
+ }
+
public class Server : FixedLengthProtocol
{
- public Server(List protocols) : base(Size) => _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, MessagePackSerializer.SerializeUnsafe(reply));
+ }
- private readonly List _protocols;
+ public override string Name()
+ {
+ return "FetchProtocols";
+ }
}
public class Client : StubProtocol
{
- public override string Name() => "FetchProtocols";
-
- public static KeyValuePair[] Get(ConnectionHost.Connection conn)
+ public override string Name()
{
- var session = ProtocolReply.AllocSession();
- Send(conn.Stream, Request(1, SerialSend.PackSingleObjectAsBytes(session.Key)));
- return SerialReply.UnpackSingleObject(session.Value.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 daa4ef6..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,30 +16,22 @@
// 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;
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 +41,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/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/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 d2098af..5706df3 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
@@ -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]
@@ -45,23 +51,20 @@ public ServiceManagerException(string message, Exception innerException) : base(
}
}
- public static class Services
+ [DeclareAssemblyReflectiveScanner]
+ public sealed class Services : IAssemblyReflectiveScanner
{
- private class DisposeList
- {
- ~DisposeList()
- {
- foreach (var disposable in List)
- disposable.Dispose();
- }
+ 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();
- public readonly List List = new List();
+ public void ProcessType(Type type)
+ {
+ if (type.IsDefined(typeof(DeclareServiceAttribute), false))
+ Inject(type);
}
- static Services() => ScanAssembly(Assembly.GetCallingAssembly());
-
- public static void Inject() where TP : IDisposable => Inject(typeof(TP));
-
public static TI Get(string name)
{
try
@@ -87,20 +90,11 @@ public static bool TryGet(string name, out TI ins)
}
catch (ServiceManagerException)
{
- ins = default;
+ ins = default(TI);
return false;
}
}
- public static void ScanAssembly(Assembly assembly)
- {
- 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;
@@ -117,14 +111,20 @@ 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;
}
- 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/Core/Path.cs b/Core/Umbrella.cs
similarity index 70%
rename from Core/Path.cs
rename to Core/Umbrella.cs
index f009587..1bac328 100644
--- a/Core/Path.cs
+++ b/Core/Umbrella.cs
@@ -1,7 +1,7 @@
-//
-// Core: Path.cs
+//
+// NEWorld/Core: Umbrella.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
@@ -17,14 +17,4 @@
// along with NEWorld. If not, see .
//
-using System;
-
-namespace Core
-{
- public static class Path
- {
- public static string Asset(string group) => AppContext.BaseDirectory + "/Assets/" + group + "/";
-
- public static string Modules() => AppContext.BaseDirectory;
- }
-}
\ No newline at end of file
+[assembly:Core.DeclareNeWorldAssembly]
diff --git a/Core/Utilities/RateController.cs b/Core/Utilities/RateController.cs
index 0ede70f..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;
@@ -33,35 +32,44 @@ 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()
+ {
+ 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
*/
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 +78,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/Singleton.cs b/Core/Utilities/Singleton.cs
deleted file mode 100644
index 87ab555..0000000
--- a/Core/Utilities/Singleton.cs
+++ /dev/null
@@ -1,174 +0,0 @@
-//
-// Core: Singleton.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.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 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 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.
- ///
- public static T Instance
- {
- get
- {
- if (Test()) return _instance;
- lock (Lock)
- {
- if (!Test()) _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) => obj.Valid();
-
- 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/Core/Utilities/StrictDispose.cs b/Core/Utilities/StrictDispose.cs
deleted file mode 100644
index 5d26503..0000000
--- a/Core/Utilities/StrictDispose.cs
+++ /dev/null
@@ -1,78 +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
- {
- ~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 void Dispose()
- {
- if (_released)
- return;
- TravelRelease();
- Release();
- _released = true;
- }
-
- public bool Valid() => !_released;
-
- private StrictDispose _first, _sibling;
-
- private bool _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 f6cbc66..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,7 @@
// You should have received a copy of the GNU Lesser General Public License
// along with NEWorld. If not, see .
//
-
+using Core;
using Game.World;
namespace Game
@@ -37,21 +37,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 = new TaskDispatcher(4, this);
+ TaskDispatcher = Services.Get("Game.TaskDispatcher");
}
- private ChunkService() : this(true)
- {
- }
+ public static TaskDispatcher TaskDispatcher { get; private set; }
- public TaskDispatcher TaskDispatcher { get; }
+ public static WorldManager Worlds { get; private set; }
- public WorldManager Worlds { get; }
+ public static bool IsAuthority { set; get; }
- public bool IsAuthority { set; get; }
+ public static void EnableDispatcher()
+ {
+ TaskDispatcher.Start();
+ }
}
}
\ No newline at end of file
diff --git a/Game/Client/PlayerChunkView.cs b/Game/Client/PlayerChunkView.cs
new file mode 100644
index 0000000..36349d6
--- /dev/null
+++ b/Game/Client/PlayerChunkView.cs
@@ -0,0 +1,54 @@
+//
+// 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
+{
+ 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/Events.cs b/Game/Events.cs
new file mode 100644
index 0000000..2e818ff
--- /dev/null
+++ b/Game/Events.cs
@@ -0,0 +1,47 @@
+//
+// 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 .
+//
+
+using Core;
+
+namespace Game
+{
+ public class GameRenderPrepareEvent
+ {
+ }
+
+ public class GameLoadEvent
+ {
+ }
+
+ public class GameStartEvent
+ {
+ }
+
+ public class GameEndEvent
+ {
+ }
+
+ public class GameUnloadEvent
+ {
+ }
+
+ public class GameRenderFinalizeEvent
+ {
+ }
+}
diff --git a/Game/Game.csproj b/Game/Game.csproj
index 0c12459..7955c02 100644
--- a/Game/Game.csproj
+++ b/Game/Game.csproj
@@ -1,81 +1,18 @@
-
-
-
+
+
- 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..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,39 +16,46 @@
// 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;
+using Core.Network;
namespace Game.Network
{
[DeclareService("Game.Client")]
public class Client : IDisposable
{
- public void 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();
- }
-
public static GetChunk.Client GetChunk;
public static GetAvailableWorldId.Client GetAvailableWorldId;
public static GetWorldInfo.Client GetWorldInfo;
+ public static GetStaticChunkIds.Client GetStaticChunkIds;
- public void Stop()
- {
- _client.Close();
- }
+ private static Core.Network.Client _client;
public void Dispose()
{
- _client?.Close();
_client?.Dispose();
}
- private Core.Network.Client _client;
+ 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());
+ _client.RegisterProtocol(GetStaticChunkIds = new GetStaticChunkIds.Client());
+ await _client.HandShake();
+ }
+
+ public static Session.Send CreateMessage(uint protocol)
+ {
+ return _client.CreateMessage(protocol);
+ }
+
+ public void Stop()
+ {
+ _client.Close();
+ }
}
}
\ No newline at end of file
diff --git a/Game/Network/Protocols.cs b/Game/Network/Protocols.cs
index 75d579f..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,184 +16,245 @@
// 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.Sockets;
using System.Threading;
-using Core.Math;
+using System.Threading.Tasks;
using Core.Network;
-using Core.Utilities;
using Game.World;
-using MsgPack.Serialization;
+using MessagePack;
+using Xenko.Core.Mathematics;
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
{
public Server() : base(Size)
{
}
- public override string Name() => "GetChunk";
+ public override string Name()
+ {
+ return "GetChunk";
+ }
- protected override void HandleRequestData(byte[] data, NetworkStream stream)
+ public override void HandleRequest(Session.Receive stream)
{
- var request = From.UnpackSingleObject(data);
- var chunkData = Get((uint) request[0], new Vec3(request[1], request[2], request[3]));
- Send(stream, Request(Id));
- Send(stream, data);
- Send(stream, chunkData);
+ var request = MessagePackSerializer.Deserialize(stream.Raw);
+ 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);
+ var cow = chunk.CopyOnWrite;
+ message.Write(cow);
+ if (cow == uint.MaxValue)
+ {
+ var chunkData = Get(chunk);
+ message.Write(chunkData, 0, chunkData.Length);
+ }
+ }
}
- private static readonly ThreadLocal LocalMemCache = new ThreadLocal();
+ private static byte[] Get(Chunk chunk)
+ {
+ var chunkData = LocalMemCache.Value ?? (LocalMemCache.Value = new byte[32768 * 4]);
+ chunk.SerializeTo(chunkData);
+ return chunkData;
+ }
- private static byte[] Get(uint worldId, Vec3 position)
+ private static Chunk GetChunk(uint worldId, Int3 position)
{
- // TODO: empty chunk optimization
- var world = Singleton.Instance.Worlds.Get(worldId);
+ var world = ChunkService.Worlds.Get(worldId);
Chunk chunkPtr;
try
{
- chunkPtr = world.GetChunk(ref position);
+ chunkPtr = world.GetChunk(position);
}
catch (KeyNotFoundException)
{
var chunk = new Chunk(position, world);
- chunkPtr = world.InsertChunkAndUpdate(position, chunk);
+ // TODO: Implement a WorldTask Instead
+ chunkPtr = world.InsertChunkAndUpdate(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;
- }
-
- return chunkData;
+ return chunkPtr;
}
}
- public class Client : FixedLengthProtocol
+ public class Client : Protocol
{
- public override string Name() => "GetChunk";
-
- public Client(ConnectionHost.Connection conn) : base(32768 * 4 + Size) => _stream = conn.Stream;
-
- protected override void HandleRequestData(byte[] data, NetworkStream stream)
+ public override string Name()
{
- 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]));
- for (var i = Size; i < 32768 * 4 + Size; i += 4)
+ return "GetChunk";
+ }
+
+ public override void HandleRequest(Session.Receive request)
+ {
+ 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
{
- ref var block = ref chk.Blocks[(i - Size) >> 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 = new Chunk(chunkPos, world, cow);
}
- srv.TaskDispatcher.Add(new World.World.ResetChunkTask((uint) req[0], chk));
+ ChunkService.TaskDispatcher.Add(new World.World.ResetChunkTask(chk));
}
- public void Call(uint worldId, Vec3 position)
+ private static Chunk DeserializeChunk(Int3 chunkPos, World.World world, byte[] data)
{
- var data = new[] {(int) worldId, position.X, position.Y, position.Z};
- Send(_stream, Request(Id, From.PackSingleObjectAsBytes(data)));
+ var chk = new Chunk(chunkPos, world, Chunk.InitOption.AllocateUnique);
+ chk.DeserializeFrom(data);
+ return chk;
}
- private readonly NetworkStream _stream;
+ 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(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
{
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, MessagePackSerializer.SerializeUnsafe(new uint[] {0}));
}
- public override string Name() => "GetAvailableWorldId";
+ public override string Name()
+ {
+ return "GetAvailableWorldId";
+ }
}
public class Client : StubProtocol
{
- public Client(ConnectionHost.Connection conn) => _stream = conn.Stream;
-
- public override string Name() => "GetAvailableWorldId";
-
- public uint[] Call()
+ public override string Name()
{
- var session = ProtocolReply.AllocSession();
- Send(_stream, Request(Id, SerialSend.PackSingleObjectAsBytes(session.Key)));
- return SerialReply.UnpackSingleObject(session.Value.Result);
+ return "GetAvailableWorldId";
}
- private readonly NetworkStream _stream;
- }
-
- private static readonly MessagePackSerializer SerialSend = MessagePackSerializer.Get();
-
- private static readonly MessagePackSerializer SerialReply = MessagePackSerializer.Get();
+ public async Task Call()
+ {
+ var session = Reply.AllocSession();
+ using (var message = Network.Client.CreateMessage(Id))
+ {
+ message.Write(session.Key);
+ }
- private static readonly int Size = SerialSend.PackSingleObject(0).Length;
+ var result = await session.Value;
+ return MessagePackSerializer.Deserialize(result);
+ }
+ }
}
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 = ChunkService.Worlds.Get(stream.ReadU32());
+ var ret = new Dictionary {{"name", world.Name}};
+ Reply.Send(stream.Session, request, MessagePackSerializer.SerializeUnsafe(ret));
}
- public override string Name() => "GetWorldInfo";
+ public override string Name()
+ {
+ return "GetWorldInfo";
+ }
}
public class Client : StubProtocol
{
- public Client(ConnectionHost.Connection conn) => _stream = conn.Stream;
-
- public override string Name() => "GetWorldInfo";
-
- public Dictionary Call(uint wid)
+ public override string Name()
{
- var session = ProtocolReply.AllocSession();
- Send(_stream, Request(Id, SerialSend.PackSingleObjectAsBytes(new[] {session.Key, (int) wid})));
- return SerialReply.UnpackSingleObject(session.Value.Result);
+ return "GetWorldInfo";
}
- private readonly NetworkStream _stream;
- }
-
- private static readonly MessagePackSerializer SerialSend = MessagePackSerializer.Get();
-
- private static readonly MessagePackSerializer> SerialReply =
- MessagePackSerializer.Get>();
+ public async Task> Call(uint wid)
+ {
+ var session = Reply.AllocSession();
+ using (var message = Network.Client.CreateMessage(Id))
+ {
+ message.Write(session.Key);
+ message.Write(wid);
+ }
- private static readonly int Size = SerialSend.PackSingleObject(new int[2]).Length;
+ var result = await session.Value;
+ return MessagePackSerializer.Deserialize>(result);
+ }
+ }
}
}
\ No newline at end of file
diff --git a/Game/Network/Server.cs b/Game/Network/Server.cs
index a8b533a..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,46 +16,67 @@
// 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;
-using Core.Utilities;
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);
- _server.RegisterProtocol(new GetChunk.Server());
- _server.RegisterProtocol(new GetAvailableWorldId.Server());
- _server.RegisterProtocol(new GetWorldInfo.Server());
- Singleton.Instance.Worlds.Add("test world");
+ server = new Core.Network.Server(port);
+ server.RegisterProtocol(new GetChunk.Server());
+ server.RegisterProtocol(new GetAvailableWorldId.Server());
+ server.RegisterProtocol(new GetWorldInfo.Server());
+ server.RegisterProtocol(new GetStaticChunkIds.Server());
+ ChunkService.Worlds.Add("test world");
}
public void Run()
{
- _wait = _server.RunAsync();
+ wait = server.RunAsync();
}
- public int CountConnections() => _server.CountConnections();
+ public int CountConnections()
+ {
+ return server.CountConnections();
+ }
public void Stop()
{
- _server.StopServer();
- _wait.Wait();
+ server.ShutDown();
+ 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/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..72edc2b 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
@@ -17,26 +17,19 @@
// 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;
namespace Game
{
- // TODO: we can add a `finished` flag in DEBUG mode
- // to verify that all tasks are indeed processed.
- /**
- * \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
+ public interface IInstancedTask
{
- void Task(ChunkService srv);
- IReadOnlyTask Clone();
+ void Task(int instance, int instanceCount);
}
/**
@@ -46,187 +39,254 @@ public interface IReadOnlyTask
*/
public interface IReadWriteTask
{
- void Task(ChunkService srv);
- IReadWriteTask Clone();
+ 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
+
+ public interface IRegularReadOnlyTask : IInstancedTask
{
- void Task(ChunkService srv);
- IRenderTask Clone();
}
- public class TaskDispatcher
+ [DeclareService("Game.TaskDispatcher")]
+ public class TaskDispatcher : IDisposable
{
+ private readonly Barrier barrier;
+
+ // TODO: replace it with lock-free structure.
+ private readonly object mutex;
+ 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 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)
{
- _threadNumber = threadNumber;
- _chunkService = chunkService;
+ barrier = new Barrier(threadNumber);
+ threads = new List(threadNumber);
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 ConcurrentBag();
+ regularReadOnlyTasks = new List();
+ readWriteTasks = new List();
+ nextReadWriteTasks = new List();
+ regularReadWriteTasks = new List();
+ renderTasks = new List();
+ nextRenderTasks = new List();
+ }
+
+ public int[] TimeUsed { get; }
+
+ public void Dispose()
+ {
+ ReleaseUnmanagedResources();
+ 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()
{
- if (!_shouldExit) Stop();
+ ReleaseUnmanagedResources();
}
public void Start()
{
- _shouldExit = false;
- _roundNumber = 0;
- _numberOfUnfinishedThreads = _threadNumber;
- _threads = new List(_threadNumber);
- for (var i = 0; i < _threadNumber; ++i)
+ shouldExit = false;
+ for (var i = 0; i < TimeUsed.Length; ++i)
{
var i1 = i;
var trd = new Thread(() => { Worker(i1); });
trd.Start();
- _threads.Add(trd);
+ threads.Add(trd);
}
}
- public void Add(IReadOnlyTask task)
+ private void AddReadOnlyTask(Action task)
{
- lock (_mutex)
- _nextReadOnlyTasks.Add(task);
+ readOnlyTasks.Add(task);
}
public void Add(IReadWriteTask task)
{
- lock (_mutex)
- _nextReadWriteTasks.Add(task);
+ lock (mutex)
+ {
+ nextReadWriteTasks.Add(task);
+ }
}
- public void Add(IRenderTask task)
+ private void AddRenderTask(Action task)
{
- lock (_mutex)
- _nextRenderTasks.Add(task);
+ lock (mutex)
+ {
+ nextRenderTasks.Add(task);
+ }
}
- public void AddRegular(IReadOnlyTask task)
+ public void AddRegular(IRegularReadOnlyTask 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 GetRegularReadWriteTaskCount() => _regularReadWriteTasks.Count;
-
/**
* \brief Process render tasks.
* This function should be called from the main thread.
*/
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();
+ renderTasks.Clear();
+ Generic.Swap(ref renderTasks, ref nextRenderTasks);
}
}
- 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)
+ while (!shouldExit)
{
- // A tick starts
- var currentRoundNumber = _roundNumber;
- // Process read-only work.
- for (var i = threadId; i < _readOnlyTasks.Count; i += _threadNumber)
+ ProcessReadonlyTasks(threadId);
+ // The last finished thread is responsible to do writing jobs
+ if (barrier.ParticipantsRemaining == 1)
{
- _readOnlyTasks[i].Task(_chunkService);
+ QueueSwap();
+ ProcessReadWriteTasks();
+ meter.Yield(); // Rate Control
}
- // Finish the tick
TimeUsed[threadId] = meter.GetDeltaTimeMs();
+ barrier.SignalAndWait();
+ }
+ }
- // The last finished thread is responsible to do writing jobs
- if (Interlocked.Decrement(ref _numberOfUnfinishedThreads) == 0)
+ private void ProcessReadonlyTasks(int i)
+ {
+ 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: AddReadOnlyTask a timeout support for this to ensure the updation rate
+ while (readOnlyTasks.TryTake(out var task))
+ task();
+ }
+
+ private void ProcessReadWriteTasks()
+ {
+ foreach (var task in regularReadWriteTasks) task.Task();
+ foreach (var task in readWriteTasks) task.Task();
+ readWriteTasks.Clear();
+ }
+
+ private void QueueSwap()
+ {
+ Generic.Swap(ref readWriteTasks, ref nextReadWriteTasks);
+ }
+
+ private void ReleaseUnmanagedResources()
+ {
+ Reset();
+ barrier?.Dispose();
+ }
+
+ public struct NextReadOnlyChanceS
+ {
+ public struct Awaiter : INotifyCompletion
+ {
+ public bool IsCompleted => false;
+
+ public void GetResult()
{
- // 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
+
+ public void OnCompleted(Action continuation)
{
- meter.Yield();
- // Wait for other threads...
- while (_roundNumber == currentRoundNumber)
- Thread.Yield();
+ ChunkService.TaskDispatcher.AddReadOnlyTask(continuation);
}
}
+
+ public Awaiter GetAwaiter()
+ {
+ return new Awaiter();
+ }
}
- // 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;
+ 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
diff --git a/Game/Terrain/Block.cs b/Game/Terrain/Block.cs
index 47cf893..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,150 +16,108 @@
// You should have received a copy of the GNU Lesser General Public License
// along with NEWorld. If not, see