Esempio n. 1
0
 /// <summary>
 /// Sets the current tick
 /// </summary>
 /// <param name="Tick">The new tick</param>
 public void SetCurrentTick(uint Tick)
 {
     MDLog.Debug(LOG_CAT, $"Tick changed by code from {CurrentTick} to {Tick}");
     CurrentTick         = Tick;
     TickSynchAdjustment = 0;
     OnGameTickChanged(CurrentTick);
 }
Esempio n. 2
0
        private void UpdateLabel()
        {
            DisplayLabel.Clear();
            DisplayLabel.PushTable(2);
            AddText("Name", Colors.White);
            AddText("Value", Colors.White);

            foreach (string key in DebugInfoList.Keys)
            {
                string text = "";
                try
                {
                    text = DebugInfoList[key].InfoFunction.Invoke();
                    AddText(key, DebugInfoList[key].Color);
                    AddText(text, DebugInfoList[key].Color);
                }
                catch (Exception ex)
                {
                    // Something went wrong
                    MDLog.Debug(LOG_CAT, ex.ToString());
                }
            }

            DisplayLabel.Pop();
        }
Esempio n. 3
0
        /// <summary>
        /// Called when an input event happens
        /// </summary>
        /// <param name="Event">The event</param>
        public void OnInputEvent(InputEvent Event)
        {
            MDInputType OldInputType = LastInputType;

            switch (Event)
            {
            case InputEventKey _:
            case InputEventMouse _:
                LastInputType = MDInputType.MouseAndKeyboard;
                break;

            case InputEventJoypadButton _:
            case InputEventJoypadMotion _:
                LastInputType = MDInputType.JoyPad;
                break;

            case InputEventScreenTouch _:
            case InputEventGesture _:
                LastInputType = MDInputType.Touch;
                break;

            default:
                MDLog.Warn(LOG_CAT, $"Unknown Input Event Type: {Event.AsText()}");
                break;
            }

            if (OldInputType != LastInputType)
            {
                OnInputTypeChanged(OldInputType, LastInputType);
            }
        }
Esempio n. 4
0
        public void Disconnect()
        {
            MDLog.Info(LOG_CAT, "Disconnected from server");
            IsSessionStarted = false;
            foreach (int PeerId in Players.Keys)
            {
                MDPlayerInfo Player = Players[PeerId];
                OnPlayerLeftEvent(PeerId);
                Player.RemoveAndFree();
            }

            NetworkedMultiplayerENet peer = GetPeer();

            if (peer != null)
            {
                peer.CloseConnection();
                SetNetworkPeer(null);
            }

            StopUPNP();
            Players.Clear();
            ClearNetworkedNodes();
            OnSessionEndedEvent();
            SceneBuffer.Clear();
        }
Esempio n. 5
0
        public override void _Ready()
        {
            MDStatics.GI = this;

            // Configuration first
            CreateConfiguration();

            // Init static classes first
            MDLog.Initialize(GetLogDirectory());
            MDArguments.PopulateArgs();
            MDProfiler.Initialize();
            MDOnScreenDebug.Initialize();

            // Hook up events
            GetTree().Connect("node_added", this, nameof(OnNodeAdded_Internal));
            GetTree().Connect("node_removed", this, nameof(OnNodeRemoved_Internal));

            // Init instances
            CreateGameSession();
            CreateGameSynchronizer();
            CreateReplicator();
            CreateInterfaceManager();

            RegisterNodeAndChildren(GetTree().Root);
        }
Esempio n. 6
0
        /// <summary>
        /// Register a command, with custom help text that can be displayed in the console
        /// </summary>
        /// <param name="Instance">The instance to register for</param>
        /// <param name="Method">The method to register</param>
        /// <param name="HelpText">Help text to show in console</param>
        /// <param name="DefaultParams">Default parameters</param>
        public static void RegisterCommand(object Instance, MethodInfo Method, string HelpText,
                                           object[] DefaultParams = null)
        {
            if (Method == null)
            {
                return;
            }

            if (_commandMap == null)
            {
                _commandMap = new Dictionary <string, CommandInfo>();
            }

            string MethodName = Method.Name.ToLower();

            if (_commandMap.ContainsKey(MethodName))
            {
                MDLog.Warn(LOG_CAT, $"Command with name [{Method.Name}] is already registered, it will be replaced");
            }

            CommandInfo NewCommand;

            NewCommand.HelpText    = HelpText;
            NewCommand.Instance    = Instance;
            NewCommand.Method      = Method;
            NewCommand.DefaultArgs = DefaultParams;

            _commandMap[MethodName] = NewCommand;
        }
Esempio n. 7
0
        public bool ShouldObjectBeReplicated(object LastValue, object CurrentValue)
        {
            ExtractMembers();
            if (LastValues.Count == 0 && CurrentValue != null)
            {
                return(true);
            }
            else if (CurrentValue != LastValue)
            {
                return(true);
            }
            else if (LastValue == null && CurrentValue == null)
            {
                return(false);
            }

            for (int i = 0; i < Members.Count; i++)
            {
                object value = Members[i].GetValue(CurrentValue);
                if (DataConverters[i].ShouldObjectBeReplicated(LastValues[i], value))
                {
                    MDLog.Trace(LOG_CAT, "We should be replicated");
                    return(true);
                }
            }

            return(false);
        }
Esempio n. 8
0
        public bool StartServer(int Port, int MaxPlayers = DEFAULT_MAX_PLAYERS)
        {
            NetworkedMultiplayerENet peer = new NetworkedMultiplayerENet();

            peer.Connect("peer_connected", this, nameof(ServerOnPeerConnected));
            peer.Connect("peer_disconnected", this, nameof(ServerOnPeerDisconnected));
            ConfigurePeer(peer);

            Error error   = peer.CreateServer(Port, MaxPlayers);
            bool  Success = error == Error.Ok;

            MDLog.CLog(Success, LOG_CAT, MDLogLevel.Info,
                       $"Starting server on port {Port} with {MaxPlayers} max players.");
            MDLog.CLog(!Success, LOG_CAT, MDLogLevel.Error, $"Failed to start server on port {Port}");

            if (Success)
            {
                UPNPPort = Port;
                SetNetworkPeer(peer);
                ServerOnStarted();
            }
            else
            {
                MDLog.Error(LOG_CAT, "Failed to start server");
                OnSessionFailedEvent();
            }

            return(Success);
        }
Esempio n. 9
0
        // Create and initialize the player object
        private MDPlayerInfo GetOrCreatePlayerObject(int PeerId)
        {
            if (Players.ContainsKey(PeerId))
            {
                return(Players[PeerId]);
            }

            Type PlayerType = GameInstance.GetPlayerInfoType();

            if (!MDStatics.IsSameOrSubclass(PlayerType, typeof(MDPlayerInfo)))
            {
                MDLog.Error(LOG_CAT, $"Provided player type [{PlayerType.Name}] is not a subclass of MDPlayerInfo");
                return(null);
            }

            MDPlayerInfo Player = Activator.CreateInstance(PlayerType) as MDPlayerInfo;

            Player.SetPeerId(PeerId);
            Player.PauseMode = PauseModeEnum.Process;
            AddChild(Player);
            Players.Add(PeerId, Player);

            OnPlayerInfoCreated(Player);

            return(Player);
        }
Esempio n. 10
0
 private void SendClockedCall(int PeerId, ClockedRemoteCall.TypeOfCall Type, MDReliability Reliability,
                              string NodePath, string Method, MDRemoteMode Mode, params object[] Parameters)
 {
     MDLog.Trace(LOG_CAT, $"Sending clocked call {Method} on {NodePath} with parameters ({MDStatics.GetParametersAsString(Parameters)})");
     if (Reliability == MDReliability.Reliable)
     {
         if (PeerId != -1)
         {
             RpcId(PeerId, nameof(ClockedCall), GameClock.GetTick(), Type, NodePath, Method, Mode, Parameters);
         }
         else
         {
             Rpc(nameof(ClockedCall), GameClock.GetTick(), Type, NodePath, Method, Mode, Parameters);
         }
     }
     else
     {
         if (PeerId != -1)
         {
             RpcUnreliableId(PeerId, nameof(ClockedCall), GameClock.GetTick(), Type, NodePath, Method, Mode,
                             Parameters);
         }
         else
         {
             RpcUnreliable(nameof(ClockedCall), GameClock.GetTick(), Type, NodePath, Method, Mode, Parameters);
         }
     }
 }
Esempio n. 11
0
        /// <summary>
        /// Parses the settings we know about
        /// </summary>
        /// <param name="SettingsValues">A setting array that has been run through MDReplicator.ParseParameters</param>
        protected void ParseSettings(MDReplicatedSetting[] SettingsValues)
        {
            foreach (MDReplicatedSetting setting in SettingsValues)
            {
                switch ((Settings)setting.Key)
                {
                case Settings.OnValueChangedEvent:
                    Node Node = NodeRef.GetRef() as Node;
                    OnValueChangedMethodCallback = Node.GetType().GetMethodRecursive(setting.Value.ToString());
                    if (OnValueChangedMethodCallback == null)
                    {
                        OnValueChangedEventCallback = Node.GetType().GetEvent(setting.Value.ToString());
                    }
                    MDLog.CError(OnValueChangedMethodCallback == null && OnValueChangedEventCallback == null, LOG_CAT, $"Failed to find method or event with name {setting.Value.ToString()} on Node {Node.GetPath()}");
                    break;

                case Settings.Converter:
                    Type DataConverterType = Type.GetType(setting.Value.ToString());
                    DataConverter = MDStatics.CreateConverterOfType(DataConverterType);
                    break;

                case Settings.CallOnValueChangedEventLocally:
                    ShouldCallOnValueChangedCallbackLocally = setting.Value as bool? ?? false;
                    break;
                }
            }

            // We got no data converter, get default one
            if (DataConverter == null)
            {
                DataConverter = MDStatics.GetConverterForType(Member.GetUnderlyingType());
            }
        }
Esempio n. 12
0
 // Starts a server or client based on the command args
 private void CheckArgsForConnectionInfo()
 {
     if (MDArguments.HasArg(ARG_STANDALONE))
     {
         StartStandalone();
     }
     // Expects -server=[port]
     else if (MDArguments.HasArg(ARG_SERVER))
     {
         int Port = MDArguments.GetArgInt(ARG_SERVER);
         StartServer(Port);
     }
     // Expects -client=[IPAddress:Port]
     else if (MDArguments.HasArg(ARG_CLIENT))
     {
         string   ClientArg = MDArguments.GetArg(ARG_CLIENT);
         string[] HostPort  = ClientArg.Split(":");
         if (HostPort.Length == 2)
         {
             StartClient(HostPort[0], HostPort[1].ToInt());
         }
         else
         {
             MDLog.Error(LOG_CAT,
                         $"Failed to parse client arg {ClientArg}, expecting -{ARG_CLIENT}=[IPAddress:Port]");
         }
     }
 }
Esempio n. 13
0
        /// <summary>
        /// Spawn a network node
        /// </summary>
        /// <param name="ScenePath">The path to the scene to spawn</param>
        /// <param name="Parent">The parent that the new instance will be a child of</param>
        /// <param name="NodeName">The name of the new node</param>
        /// <param name="UseRandomName">If set to true a random number will be added at the end of the node name</param>
        /// <param name="NetworkMaster">The peer that should own this, default is server</param>
        /// <param name="SpawnPos">Where the spawn this node</param>
        /// <returns>The new node</returns>
        public Node SpawnNetworkedNode(string ScenePath, Node Parent, string NodeName, bool UseRandomName = true,
                                       int NetworkMaster = -1, Vector3?SpawnPos = null)
        {
            if (this.IsMaster() == false)
            {
                MDLog.Error(LOG_CAT, "Only server can spawn networked nodes");
                return(null);
            }

            if (!Parent.IsInsideTree())
            {
                MDLog.Error(LOG_CAT, $"Parent [{Parent.Name}] is not inside the tree");
                return(null);
            }

            NodeName = BuildNodeName(NodeName, UseRandomName);

            int    NodeMaster = NetworkMaster != -1 ? NetworkMaster : MDStatics.GetPeerId();
            string ParentPath = Parent.GetPath();

            Vector3 SpawnPosVal = SpawnPos.GetValueOrDefault();

            if (MDStatics.IsNetworkActive())
            {
                Rpc(nameof(SpawnNodeScene), ScenePath, ParentPath, NodeName, NodeMaster, SpawnPosVal);
            }

            return(SpawnNodeScene(ScenePath, ParentPath, NodeName, NodeMaster, SpawnPosVal));
        }
Esempio n. 14
0
        private void GenerateConfigFile(string Source, string Target)
        {
            string existingTarget = FindFile(Target);

            if (existingTarget == "")
            {
                string SourceFilePath = FindFile(Source);
                File   SourceFile     = new File();
                SourceFile.Open(SourceFilePath, File.ModeFlags.Read);
                string SourceText = SourceFile.GetAsText();
                SourceFile.Close();

                if (SourceText == "")
                {
                    MDLog.Error(LOG_CAT, $"Failed to read config from {SourceFilePath}");
                    return;
                }

                string TargetFilePath = $"res://{Target}";
                File   TargetFile     = new File();
                TargetFile.Open(TargetFilePath, File.ModeFlags.Write);

                TargetFile.StoreString(SourceText);
                TargetFile.Close();

                MDLog.Info(LOG_CAT, $"Copied config file from {SourceFilePath} to {TargetFilePath}");
            }
            else
            {
                MDLog.Info(LOG_CAT, $"Config file {existingTarget} already exists");
            }
        }
        private void CheckAllClientsSynched(Timer timer)
        {
            if (PeerSynchInfo.Values.Where(peerInfo => peerInfo.CompletedSynch == false).ToList().Count > 0)
            {
                MDLog.Debug(LOG_CAT, "All clients are not synched yet");
                return;
            }

            // Check if we still need to wait for a better confidence on the TickMsec value
            if (PeerSynchInfo.Values.Where(peerInfo => peerInfo.IsClientMSecConfident() == false).ToList().Count > 0)
            {
                MDLog.Debug(LOG_CAT, "Still waiting for a more secure msec value");
                return;
            }

            MDLog.Debug(LOG_CAT, "All clients synched, sending unpause signal");
            // Alright tell all clients to unpause in a bit
            foreach (int peerid in GameSession.GetAllPeerIds())
            {
                // Get our current game tick
                uint tickToUnpause = GameClock?.GetTick() != null?GameClock.GetTick() : 0;

                if (peerid != MDStatics.GetServerId())
                {
                    RpcId(peerid, nameof(UnpauseAtTickMsec),
                          GetPlayerTicksMsec(peerid) + GetUnpauseCountdownDurationMSec(), tickToUnpause);
                }
            }

            UnpauseAtTickMsec(OS.GetTicksMsec() + GetUnpauseCountdownDurationMSec(), 0);
            PeerSynchInfo.Values.ToList().ForEach(value => value.CompletedSynch = false);
            timer.RemoveAndFree();
        }
Esempio n. 16
0
        /// <summary>
        /// Clients receive this from the server as a way to attempt to keep them in synch in case they freeze for any reason
        /// </summary>
        /// <param name="EstimateTime">The estimated time for the tick</param>
        /// <param name="EstimatedTick">The estimated tick for the time</param>
        public void CheckSynch(long EstimateTime, long EstimatedTick)
        {
            long currentTime = OS.GetTicksMsec();

            if (EstimateTime < currentTime)
            {
                // This packet was delayed and is already in the past, ignore
                MDLog.Trace(LOG_CAT,
                            $"[{currentTime}] Ignoring tick packet as it was in the past {EstimatedTick} at {EstimateTime}");
                return;
            }

            // Figure out what tick we would be at when the estimated time is hit
            long localTickAtTime = GetTickAtTimeOffset(EstimateTime - currentTime);
            long tickOffset      = EstimatedTick - localTickAtTime;

            if (Math.Abs(tickOffset) > MaxTickDesynch)
            {
                MDLog.Trace(LOG_CAT,
                            $"[{currentTime}] We are out of synch, we should be at tick {EstimatedTick} at {EstimateTime}");
                MDLog.Trace(LOG_CAT,
                            $"[{currentTime}] We will be at tick {localTickAtTime} which is off by {tickOffset}");
                // We are too far out of synch
                TickSynchAdjustment = (int)tickOffset;
            }
        }
Esempio n. 17
0
        /// <summary>
        /// Gets the number of a method
        /// </summary>
        /// <param name="Node">The node to look this up for</param>
        /// <param name="MethodNumber">The name of the method</param>
        /// <param name="Parameters">The parameters you intend to send to the method</param>
        /// <returns>The MethodInfo or -1 if it is not found</returns>
        public static int GetMethodNumber(Node Node, string Method, params object[] Parameters)
        {
            Type nodeType = Node.GetType();

            if (!NumberedMethodInfoCache.ContainsKey(nodeType))
            {
                NumberedMethodInfoCache.Add(nodeType, new List <MethodInfo>(nodeType.GetAllMethods()));
            }

            MethodInfo info = GetMethodInfo(Node, Method, Parameters);

            if (info != null)
            {
                List <MethodInfo> methodInfos = NumberedMethodInfoCache[nodeType];
                for (int i = 0; i < methodInfos.Count; i++)
                {
                    if (methodInfos[i].Equals(info))
                    {
                        return(i);
                    }
                }
            }

            MDLog.Warn(LOG_CAT, $"Method number could not be found for {nodeType.ToString()}#{Method}({GetParametersAsString(Parameters)})");
            return(-1);
        }
Esempio n. 18
0
        private void DoCall(Node Target)
        {
            switch (Type)
            {
            case TypeOfCall.RPC:
                MethodInfo methodInfo = MDStatics.GetMethodInfo(Target, Convert.ToInt32(Name));
                if (methodInfo == null)
                {
                    MDLog.Fatal(LOG_CAT, $"Could not find method {Target.GetType().ToString()}#{Name}");
                    return;
                }
                object[] convertedParams = MDStatics.ConvertParametersBackToObject(methodInfo, Parameters);
                MDStatics.GetReplicator().RpcSenderId = SenderPeerId;
                methodInfo.Invoke(Target, convertedParams);
                MDStatics.GetReplicator().RpcSenderId = -1;
                break;

            case TypeOfCall.RSET:
                MemberInfo       memberInfo = MDStatics.GetMemberInfo(Target, Name);
                IMDDataConverter Converter  = MDStatics.GetConverterForType(memberInfo.GetUnderlyingType());
                object           value      = Converter.ConvertBackToObject(memberInfo.GetValue(Target), Parameters);
                MDLog.Trace(LOG_CAT, $"Setting {Name} on {Target.Name} to value {value}");
                memberInfo.SetValue(Target, value);
                break;
            }
        }
Esempio n. 19
0
        /// <summary>
        /// Unregisters the given instance's fields marked with [MDReplicated()]
        /// </summary>
        /// <param name="Instance">The node to unregister</param>
        public void UnregisterReplication(Node Instance)
        {
            if (!IsInstanceValid(Instance))
            {
                return;
            }

            foreach (ReplicatedNode repNode in NodeList)
            {
                if (repNode.Instance.GetRef() != Instance)
                {
                    continue;
                }

                MDLog.Debug(LOG_CAT, $"Unregistering node {Instance.Name}");
                NetworkIdKeyMap.RemoveMembers(repNode.Members);
                foreach (MDReplicatedMember member in repNode.Members)
                {
                    GroupManager.RemoveReplicatedMember(member);
                    KeyToMemberMap.Remove(member.GetUniqueKey());
                }
            }

            NodeList.RemoveAll(RepNode => RepNode.Instance.GetRef() == Instance);
        }
Esempio n. 20
0
        public object ConvertBackToObject(object CurrentObject, object[] Parameters)
        {
            MDLog.Trace(LOG_CAT, $"MDCommandReplicator converting back ({MDStatics.GetParametersAsString(Parameters)})");

            if (Parameters.Length == 1 && Parameters[0] == null)
            {
                return(null);
            }

            T obj;

            if (CurrentObject != null)
            {
                // Replace values in existing object
                obj = (T)CurrentObject;
            }
            else
            {
                // Return a new object
                obj = (T)Activator.CreateInstance(typeof(T));
            }

            for (int i = 0; i < Parameters.Length; i++)
            {
                // Get the length of the data
                int length = Convert.ToInt32(Parameters[i].ToString());

                // Extract parameters and apply to the command replicator
                object[] converterParams = Parameters.SubArray(i + 1, i + length);
                obj.MDProcessCommand(converterParams);
                i += length;
            }

            return((T)obj);
        }
Esempio n. 21
0
        public override void _Ready()
        {
            MDLog.AddLogCategoryProperties(LOG_CAT, new MDLogProperties(MDLogLevel.Trace));
            this.RegisterCommandAttributes();

            CheckArgsForConnectionInfo();
        }
Esempio n. 22
0
        ///<summary>Adjust the remote tick offset if necessary</summary>
        private void AdjustRemoteTickOffset()
        {
            LastTickDuplicated = false;
            if (CurrentRemoteTickOffsetTarget != CurrentRemoteTickOffset)
            {
                // Ensure that we don't modify the offset two consecutive frames in a row
                if (LastRemoteTickOffset != CurrentRemoteTickOffset)
                {
                    LastRemoteTickOffset = CurrentRemoteTickOffset;
                    return;
                }

                if (CurrentRemoteTickOffsetTarget > CurrentRemoteTickOffset)
                {
                    MDLog.Debug(LOG_CAT,
                                $"Increasing remote tick offset from {CurrentRemoteTickOffset} to {CurrentRemoteTickOffset + 1}");
                    // Notify that we are skipping this tick
                    OnRemoteSkippedTick(GetRemoteTick());
                    CurrentRemoteTickOffset++;
                }
                else
                {
                    MDLog.Debug(LOG_CAT,
                                $"Decreasing remote tick offset from {CurrentRemoteTickOffset} to {CurrentRemoteTickOffset - 1}");
                    CurrentRemoteTickOffset--;
                    // We need to mark that last tick was a duplicate
                    LastTickDuplicated = true;
                }
            }
        }
Esempio n. 23
0
 /// <summary>
 /// Disposes the profiler
 /// </summary>
 public void Dispose()
 {
     Timer.Stop();
     if (_enabledProfiles.Contains(LowerProfileName) || MDArguments.HasArg(LOG_ARG))
     {
         MDLog.Info(LOG_CAT, $"Profiling [{ProfileName}] took {GetMicroSeconds()} us");
     }
 }
 private void RequestTicksMsec(int RequestNumber, uint ServerTimeOfRequest)
 {
     // Respond
     RpcId(GameSession.GetNetworkMaster(), nameof(ResponseTicksMsec), OS.GetTicksMsec(), ServerTimeOfRequest,
           RequestNumber);
     MDLog.Trace(LOG_CAT,
                 $"Responded to server request number {RequestNumber} for OS.GetTicksMsec with [{OS.GetTicksMsec()}]");
 }
Esempio n. 25
0
 protected virtual void OnClientSentPlayerName(string ClientName)
 {
     MDLog.Debug(LOG_CAT, $"Server received initialization for PeerId [{PeerId}] from owner");
     if (HasInitialized == false)
     {
         PlayerName = ClientName;
         MarkPlayerInitializationCompleted();
     }
 }
Esempio n. 26
0
        private void ClientOnConnected()
        {
            MDLog.Info(LOG_CAT, "Client connected to server");
            int PeerId = MDStatics.GetPeerId();

            OnPlayerJoined_Internal(PeerId);
            OnSessionStartedEvent();
            IsSessionStarted = true;
        }
 private void NotifyAllNodesSynched()
 {
     // Called from client the first time they complete their node synch
     MDLog.Trace(LOG_CAT, $"Peer [{Multiplayer.GetRpcSenderId()}] has completed node synchronization");
     if (!PeerSynchInfo.ContainsKey(Multiplayer.GetRpcSenderId()))
     {
         PeerSynchInfo[Multiplayer.GetRpcSenderId()].CompletedNodeSynch = true;
     }
 }
Esempio n. 28
0
 /// <summary>
 /// Remove the uint id from our map
 /// </summary>
 /// <param name="id">The id</param>
 public void RemoveValue(uint id)
 {
     if (NetworkIDToKeyMap.ContainsKey(id))
     {
         MDLog.Trace(LOG_CAT, $"Removing id [{id}]");
         KeyToNetworkIdMap.Remove(NetworkIDToKeyMap[id]);
         RemoveBufferId(id);
         NetworkIDToKeyMap.Remove(id);
     }
 }
Esempio n. 29
0
        private static PackedScene LoadPackedScene(String path)
        {
            String full_path = path;

            if (!ResourceLoader.Exists(full_path))
            {
                MDLog.Warn(LOG_CAT, $"Can't find: {full_path}");
            }
            return((PackedScene)ResourceLoader.Load(full_path));
        }
Esempio n. 30
0
        /// <summary>
        /// Creates an instance of the type based on the base class T
        /// </summary>
        /// <param name="Type">The type to instantiate</param>
        /// <returns>The instance created or null if it fails</returns>
        public static T CreateTypeInstance <T>(Type Type) where T : class
        {
            if (!IsSameOrSubclass(Type, typeof(T)))
            {
                MDLog.Error(LOG_CAT, $"Type [{Type.Name}] is not a subclass of [{typeof(T).Name}]");
                return(null);
            }

            return(Activator.CreateInstance(Type) as T);
        }