Page 2 of 2 FirstFirst 12
Results 11 to 16 of 16
  1. #11
    Join Date
    Aug 2010
    Posts
    129
    Here is a little more quick playground work. Again just IDE refactor with a compilation. No actual testing. I will see if Photon blows up when I have a chance. Currently I am just waiting for actual work stuff to compile so I figured I would take a second to see what I could put together. But like I said, this is where I would start. Yes there needs to be more refactoring and I would like to take any common logic and move it to an internal helper class for these peer base classes so that there is no code reuse.

    ALso to note, I am not using autofac anymore so this wont just compile for any of you but that is a simple thing to revert as it is only a few lines of code.
    MMO.Server

    MMO.Server.MMOClientPeerBase (replacement for MMOPeerBase)
    Code:
    using MMO.Base;
    using Photon.SocketServer;
    using PhotonHostRuntimeInterfaces;
    using System;
    using System.Collections.Generic;
    
    namespace MMO.Server
    {
        public abstract class MMOClientPeerBase<TContext> : ClientPeer, IServerTransport
            where TContext : ClientContext
        {
            private readonly CallbackByteMap<Action<OperationCode, Dictionary<byte, object>>> _callbacks;
    
            protected TContext ClientContext { get; private set; }
    
            protected MMOClientPeerBase(InitRequest initRequest) : base(initRequest)
            {
                _callbacks = new CallbackByteMap<Action<OperationCode, Dictionary<byte, object>>>();
            }
    
            protected override void OnOperationRequest(OperationRequest operationRequest, SendParameters sendParameters)
            {
                var operationCode = (OperationCode)operationRequest.OperationCode;
    
                if (ClientContext == null)
                {
                    if (operationCode != OperationCode.InitContext)
                        throw new ArgumentException($"Operation code {operationCode} is not supported");
    
                    var contextType = (ContextType)operationRequest.Parameters[(byte)OperationParameter.ContextType];
                    ClientContext = CreateContext(contextType, operationRequest);
    
                    if (ClientContext == null)
                        throw new ArgumentException($"Context Type {contextType} is not valid");
                }
                else if (operationCode == OperationCode.SendSystemResponse)
                {
                    var methodInvokeId = (byte)operationRequest[(byte)OperationParameter.SystemInvokeId];
                    var callback = _callbacks.GetCallback(methodInvokeId);
                    callback(operationCode, operationRequest.Parameters);
                }
                else
                {
                    ClientContext.OnOperationRequest(operationCode, operationRequest.Parameters);
                }
            }
    
            protected abstract TContext CreateContext(ContextType type, OperationRequest request);
    
            protected override void OnDisconnect(DisconnectReason reasonCode, string reasonDetail)
            {
                ClientContext.OnDisconnect();
            }
    
            #region IServerTransport Members
            public void SendEventWithResponse(EventCode code, Dictionary<byte, object> parameters, Action<OperationCode, Dictionary<byte, object>> onRespoinse)
            {
                parameters[(byte)EventCodeParameter.SystemInvokeId] = _callbacks.RegisterCallback(onRespoinse);
                SendEvent(new EventData((byte)code, parameters), new SendParameters());
            }
    
            public virtual void SendData(Event @event)
            {
                SendEvent(@event.EventData, @event.SendParameters);
            }
    
            public virtual void SendOperationResponse(OperationCode code, Dictionary<byte, object> parameters)
            {
                SendOperationResponse(new OperationResponse((byte)code, parameters), Event.Reliable);
            }
            #endregion
        }
    }

    MMO.Server.MMOInboundS2SPeerBase - New Inbound Server to Server Peer
    Code:
    using MMO.Base;
    using MMO.Client.Infrastructure;
    using Photon.SocketServer;
    using Photon.SocketServer.ServerToServer;
    using PhotonHostRuntimeInterfaces;
    using System;
    using System.Collections.Generic;
    
    namespace MMO.Server
    {
        public abstract class MMOInboundS2SPeerBase<TContext> : InboundS2SPeer, IServerTransport
            where TContext : ClientContext
        {
            private readonly CallbackByteMap<Action<OperationCode, Dictionary<byte, object>>> _callbacks;
            protected Action<EventCode, Dictionary<byte, object>>[] _eventHandlers { get; }
    
            protected TContext ClientContext { get; private set; }
    
            protected MMOInboundS2SPeerBase(InitRequest initRequest) : base(initRequest)
            {
                _callbacks = new CallbackByteMap<Action<OperationCode, Dictionary<byte, object>>>();
            }
    
            #region Methods
            protected abstract TContext CreateContext(ContextType type, OperationRequest request);
            protected void HandleSystemCallback(OperationCode code, Dictionary<byte, object> parameters)
            {
                if (code != OperationCode.SendSystemResponse)
                    throw new ArgumentException($"Code {code} is not valid for handling responses", nameof(code));
    
                var methodInvokeId = (byte)parameters[(byte)OperationParameter.SystemInvokeId];
                var callback = _callbacks.GetCallback(methodInvokeId);
                callback(code, parameters);
            }
    
            protected void HandleEvent(EventCode code, Dictionary<byte, object> parameters)
            {
                var handler = _eventHandlers[(byte)code];
                if (handler == null)
                    throw new InvalidOperationException($"Handler for event code {code} was not registered");
    
                handler(code, parameters);
            }
    
            public void AddEventReader(IEventReaderModule eventReader)
            {
                foreach (var registration in eventReader.GetRegistration())
                {
                    if (_eventHandlers[(int)registration.Code] != null)
                    {
                        var oldHandler = _eventHandlers[(int)registration.Code];
                        var action = registration.Action;
    
                        _eventHandlers[(int)registration.Code] = (code, parameter) =>
                        {
                            oldHandler(code, parameter);
                            action(code, parameter);
                        };
                    }
                    else
                    {
                        _eventHandlers[(int)registration.Code] = registration.Action;
                    }
                }
            }
            #endregion
    
            #region Overrides
            protected override void OnOperationRequest(OperationRequest operationRequest, SendParameters sendParameters)
            {
                var operationCode = (OperationCode)operationRequest.OperationCode;
    
                if (ClientContext == null)
                {
                    if (operationCode != OperationCode.InitContext)
                        throw new ArgumentException($"Operation code {operationCode} is not supported");
    
                    var contextType = (ContextType)operationRequest.Parameters[(byte)OperationParameter.ContextType];
                    ClientContext = CreateContext(contextType, operationRequest);
    
                    if (ClientContext == null)
                        throw new ArgumentException($"Context Type {contextType} is not valid");
                }
                else if (operationCode == OperationCode.SendSystemResponse)
                {
                    var methodInvokeId = (byte)operationRequest[(byte)OperationParameter.SystemInvokeId];
                    var callback = _callbacks.GetCallback(methodInvokeId);
                    callback(operationCode, operationRequest.Parameters);
                }
                else
                {
                    ClientContext.OnOperationRequest(operationCode, operationRequest.Parameters);
                }
            }
    
            protected override void OnDisconnect(DisconnectReason reasonCode, string reasonDetail)
            {
                ClientContext.OnDisconnect();
            }
    
            protected override void OnEvent(IEventData eventData, SendParameters sendParameters)
            {
                HandleEvent((EventCode)eventData.Code, eventData.Parameters);
            }
    
            protected override void OnOperationResponse(OperationResponse operationResponse, SendParameters sendParameters)
            {
                HandleSystemCallback((OperationCode)operationResponse.OperationCode, operationResponse.Parameters);
            }
            #endregion
    
            #region IServerTransport Members
            public void SendEventWithResponse(EventCode code, Dictionary<byte, object> parameters, Action<OperationCode, Dictionary<byte, object>> onRespoinse)
            {
                parameters[(byte)EventCodeParameter.SystemInvokeId] = _callbacks.RegisterCallback(onRespoinse);
                SendEvent(new EventData((byte)code, parameters), new SendParameters());
            }
    
            public virtual void SendData(Event @event)
            {
                SendEvent(@event.EventData, @event.SendParameters);
            }
    
            public virtual void SendOperationResponse(OperationCode code, Dictionary<byte, object> parameters)
            {
                SendOperationResponse(new OperationResponse((byte)code, parameters), Event.Reliable);
            }
            #endregion
        }
    }

    MMO.Server.MMOOutboundS2SPeerBase - New Outbound Server to Server Peer
    Code:
    using MMO.Base;
    using MMO.Client.Infrastructure;
    using MMO.Client.Systems;
    using Photon.SocketServer;
    using Photon.SocketServer.ServerToServer;
    using PhotonHostRuntimeInterfaces;
    using System;
    using System.Collections.Generic;
    using MMO.Base.Async;
    using Microsoft.Extensions.DependencyInjection;
    
    namespace MMO.Server
    {
        public abstract class MMOOutboundS2SPeerBase<TContext> : OutboundS2SPeer, ISystemFactory, IClientTransport
            where TContext : ServerContext
        {
            public event Action OnDisconnected;
    
            protected TContext _serverContext { get; }
            protected Client.Systems.ClientSystems _clientSystems { get; }
            protected ComponentMap _componentMap { get; }
            protected IServiceScope _scope { get; }
            protected CallbackByteMap<Action<OperationCode, Dictionary<byte, object>>> _callbacks { get; }
            protected Action<EventCode, Dictionary<byte, object>>[] _eventHandlers { get; }
    
            protected HashSet<IClientTransportListener> Listeners { get; }
    
            protected MMOOutboundS2SPeerBase(ApplicationBase app, TContext application) : base(app)
            {
                _callbacks = new CallbackByteMap<Action<OperationCode, Dictionary<byte, object>>>();
                _eventHandlers = new Action<EventCode, Dictionary<byte, object>>[byte.MaxValue + 1];
                Listeners = new HashSet<IClientTransportListener>();
    
                _serverContext = application;
                _scope = application.Container.CreateScope();
                _componentMap = new ComponentMap();
    
                var operationWriter = new SystemsOperationWriter(_serverContext.Serializer, this);
                _clientSystems = new Client.Systems.ClientSystems(_componentMap, this, operationWriter);
                AddEventReader(new SystemsEventReader(_serverContext.Serializer, _clientSystems, this));
    
                application.OnDiposed += OnApplicationDisposed;
            }
    
            #region Methods
            protected void HandleSystemCallback(OperationCode code, Dictionary<byte, object> parameters)
            {
                if (code != OperationCode.SendSystemResponse)
                    throw new ArgumentException($"Code {code} is not valid for handling responses", nameof(code));
    
                var methodInvokeId = (byte)parameters[(byte)OperationParameter.SystemInvokeId];
                var callback = _callbacks.GetCallback(methodInvokeId);
                callback(code, parameters);
            }
    
            protected void HandleEvent(EventCode code, Dictionary<byte, object> parameters)
            {
                var handler = _eventHandlers[(byte)code];
                if (handler == null)
                    throw new InvalidOperationException($"Handler for event code {code} was not registered");
    
                handler(code, parameters);
            }
    
            protected void SendOperationInternal(OperationCode code, Dictionary<byte, object> parameters)
            {
                SendOperationRequest(new OperationRequest((byte)code, parameters), new SendParameters { Unreliable = false });
            }
    
            private void OnApplicationDisposed()
            {
                Disconnect();
            }
            #endregion
    
            #region Overrides
            protected override void OnConnectionEstablished(object responseObject)
            {
                SendOperation(OperationCode.InitContext, new Dictionary<byte, object> { [(byte)OperationParameter.ContextType] = ContextType.Region });
            }
    
            protected override void OnConnectionFailed(int errorCode, string errorMessage)
            {
            }
    
            protected override void OnDisconnect(DisconnectReason reasonCode, string reasonDetail)
            {
                OnDisconnected?.Invoke();
                _scope.Dispose();
                _serverContext.OnDiposed -= OnApplicationDisposed;
            }
    
            protected override void OnOperationRequest(OperationRequest operationRequest, SendParameters sendParameters)
            {
            }
    
            protected override void OnEvent(IEventData eventData, SendParameters sendParameters)
            {
                HandleEvent((EventCode)eventData.Code, eventData.Parameters);
            }
    
            protected override void OnOperationResponse(OperationResponse operationResponse, SendParameters sendParameters)
            {
                HandleSystemCallback((OperationCode)operationResponse.OperationCode, operationResponse.Parameters);
            }
            #endregion
    
            #region ISystemFactory Members
            public abstract ISystemBase CreateSystem(Type interfaceType, Func<Type, object> proxyFactory, out Type concreteType);        
            #endregion
    
            #region IClientTransport Members
            public void SendOperation(OperationCode code, Dictionary<byte, object> parameters)
            {
                SendOperationInternal(code, parameters);
            }
    
            public void SendOperation(OperationCode code, Dictionary<byte, object> parameters, Action<OperationCode, Dictionary<byte, object>> onResponse)
            {
                parameters[(byte)OperationParameter.SystemInvokeId] = _callbacks.RegisterCallback(onResponse);
                SendOperationInternal(code, parameters);
            }
    
            public void AddEventReader(IEventReaderModule eventReader)
            {
                foreach (var registration in eventReader.GetRegistration())
                {
                    if (_eventHandlers[(int)registration.Code] != null)
                    {
                        var oldHandler = _eventHandlers[(int)registration.Code];
                        var action = registration.Action;
    
                        _eventHandlers[(int)registration.Code] = (code, parameter) =>
                        {
                            oldHandler(code, parameter);
                            action(code, parameter);
                        };
                    }
                    else
                    {
                        _eventHandlers[(int)registration.Code] = registration.Action;
                    }
                }
            }
    
            public void AddListener(IClientTransportListener listener)
            {
                Listeners.Add(listener);
            }
    
            public Deferred Connect()
            {
                return Deferred.Success();
            }
    
            public void Service()
            {
            }
    
            Deferred IClientTransport.Disconnect()
            {
                return Deferred.Success();
            }
            #endregion
        }
    }

    MMO.Server.Master

    MMO.Server.Master.Application
    Code:
    using log4net;
    using log4net.Config;
    using MMO.Base;
    using Photon.SocketServer;
    using System.IO;
    
    namespace MMO.Server.Master
    {
        public class Application : ApplicationBase
        {
            private static readonly ILog Log = LogManager.GetLogger(typeof(Application));
    
            private readonly MasterServerContext _application;
    
            public Application()
            {
                System.Diagnostics.Debugger.Launch();
                System.Diagnostics.Debugger.Break();
    
                _application = new MasterServerContext(new SimpleSerializer());
            }
    
            protected override PeerBase CreatePeer(InitRequest initRequest)
            {
                // Check which peer is connecting here based on the incoming port.
                switch (initRequest.LocalPort)
                {
                    case 5055:
                        return new MMOClientPeer(_application, initRequest);
                    case 5065: // some other port that is only accessible to region servers calling into the master server
                        return new MMOServerPeer(_application, initRequest);
                    default:
                        // Should log an expection for this connection and / or get the native photon peer create a peer 
                        //  and quick disconnect them (initRequest.PhotonPeer.DisconnectClient())
                        return null;
                }
            }
    
            protected override void Setup()
            {
                GlobalContext.Properties["ServerName"] = "Master";
                XmlConfigurator.ConfigureAndWatch(new FileInfo(Path.Combine(BinaryPath, "log4net.config")));
                ExitGames.Logging.LogManager.SetLoggerFactory(ExitGames.Logging.Log4Net.Log4NetLoggerFactory.Instance);
    
                Log.Info("Master Server Started...");
            }
    
            protected override void TearDown()
            {
                _application.Dispose();
            }
        }
    }
    MMO.Server.Master.MMOClientPeer - Replacement for MMO.Server.Master.Peer for Clients
    Code:
    using MMO.Base;
    using Photon.SocketServer;
    using MMO.Server.Master.Systems;
    
    namespace MMO.Server.Master
    {
        public class MMOClientPeer : MMOClientPeerBase<ClientContext>
        {
            private readonly MasterServerContext _application;
    
            public MMOClientPeer(MasterServerContext application, InitRequest initRequest) : base(initRequest)
            {
                _application = application;
            }
    
            protected override ClientContext CreateContext(ContextType type, OperationRequest request)
            {
                if (type == ContextType.Player)
                {
                    var context = new PlayerContextMaster(_application, this);
                    context.Systems.Create<LoginSystem>();
                    return context;
                }
    
                return null;
            }
        }
    }
    MMO.Server.Master.MMOServerPeer - Replacement for MMO.Server.Master.Peer for Servers
    Code:
    using MMO.Base;
    using Photon.SocketServer;
    using MMO.Server.Master.Systems.Region;
    
    namespace MMO.Server.Master
    {
        public class MMOServerPeer : MMOInboundS2SPeerBase<ClientContext>
        {
            private readonly MasterServerContext _application;
    
            public MMOServerPeer(MasterServerContext application, InitRequest initRequest) : base(initRequest)
            {
                _application = application;
            }
    
            protected override ClientContext CreateContext(ContextType type, OperationRequest request)
            {
                if (type == ContextType.Region)
                {
                    var context = new RegionServerContext(_application, this);
                    context.Systems.Create<GameInstanceSystem>();
                    return context;
                }
    
                return null;
            }
        }
    }

    MMO.Server.Region

    MMO.Server.Master.Region.Peer - I should probably rename this one too. Just for clients
    Code:
    using log4net;
    using Microsoft.Extensions.DependencyInjection;
    using MMO.Base;
    using MMO.Data.Services;
    using Photon.SocketServer;
    
    namespace MMO.Server.Region
    {
        public class Peer : MMOClientPeerBase<ClientContext>
        {
            private static readonly ILog Log = LogManager.GetLogger(typeof(Peer));
            private readonly GameServerContext _application;
    
            public Peer(GameServerContext application, InitRequest initRequest) : base(initRequest)
            {
                _application = application;
            }
    
            protected override ClientContext CreateContext(ContextType type, OperationRequest request)
            {
                if (type == ContextType.Player)
                {
                    var gameId = (int)request[(byte)OperationParameter.GameId];
                    var token = (string)request[(byte)OperationParameter.Token];
    
                    var gameEntityService = _application.Container.GetService<GameEntityService>();//.Resolve<GameEntityService>();
                    var playerInGame = gameEntityService.GetPlayerInGame(_application.Config.GameServer.UniqueId, gameId, token);
    
                    if (playerInGame == null || playerInGame.HasAbandoned)
                    {
                        Disconnect();
                        return null;
                    }
    
                    var context = new PlayerContextGame(_application, this);
                    context.Login(playerInGame.Player);
                    Log.Info($"Player from {RemoteIP} connected as {context.UserDetails.Username}");
    
                    _application.AddPlayerToGame(gameId, context)
                        .OnFail(Disconnect);
    
                    return context;
                }
    
                return null;
            }
        }
    }

    MMO.Server.Master.Region.MasterServerPeer
    Code:
    using log4net;
    using MMO.Base;
    using PhotonHostRuntimeInterfaces;
    using System;
    
    namespace MMO.Server.Region
    {
        public class MasterServerPeer : MMOOutboundS2SPeerBase<GameServerContext>
        {
            private static readonly ILog Log = LogManager.GetLogger(typeof(MasterServerPeer));
            
            public MasterServerPeer(Application app, GameServerContext serverContext) : base(app, serverContext)
            {
            }
    
            public override ISystemBase CreateSystem(Type interfaceType, Func<Type, object> proxyFactory, out Type concreteType)
            {
                var registeredSystem = _serverContext.SystemTypeRegistry.GetSystemFromClientInterfaceType(interfaceType);
                var instance = _scope.ServiceProvider.GetService(registeredSystem.ConcreteType);
                var proxy = proxyFactory(registeredSystem.ServerInterfaceType);
                ((IMasterSystemBase)instance).SetContext(_serverContext, proxy);
    
                concreteType = registeredSystem.ConcreteType;
                return (ISystemBase)instance;
            }
    
            protected override void OnDisconnect(DisconnectReason reasonCode, string reasonDetail)
            {
                Log.Info($"MasterServerPeer Disconnect - {reasonCode}: {reasonDetail}");
                base.OnDisconnect(reasonCode, reasonDetail);
            }
        }
    }
    Last edited by am385; 06-09-2017 at 11:01 PM.

  2. #12
    Join Date
    Feb 2014
    Posts
    273
    No need to make me feel bad by emphasising how you do all this in a few moments..... some of us have to struggle.

    You've done a few things outside Nelson's code here for which I need more information.
    * MMOOutboundS2SPeerBase.cs uses Microsoft.Extensions.DependencyInjection. I assume that's the NuGet packages: "Microsoft.Extensions.DependencyInjection" and
    "Microsoft.Extensions.DependencyInjection.Abstract ions".

    I guess this is part of your scheme to replace autofac. Perhaps we need to see what you did to make sense of your code.
    I've never used it but would switch to it just to get your code working at my end.
    As you guessed I have compiler errors - have you by any chance made any changes to ServerContext.cs ??

    * Yep I have renamed Region|Master/Peer.cs to ClientPeer.cs in both Master and Region projects as they do the same thing essentially.
    * Did you have any trouble getting server master to listen on more than just 5055? I had to introduce TCP for region servers to connect to the master server just so I could get a different port to choose in application.cs
    Last edited by oldngrey; 06-10-2017 at 07:32 AM.

  3. #13
    Join Date
    Aug 2010
    Posts
    129
    AutoFac is a dependency injection system. Microsoft.Extensions.DependencyInjection is just replacing the Autofac code. I can put out more information, but this is a more basic system than AutoFac. AutoFac is much more advanced and full featured at the moment. This changed every place that autofac was used in all projects. It is not needed. I would just change the lines that are having issues back to autofac. It should be a simple copy and paste from your original code.

    I can put out more code on this switch, but I think this would be best to finally throw up a a shared repository that I can code my work in so that anyone can actually see a real diff.

    Photon can listen on what ever ports you specify in the XML config. You would have to say that the master can listen on 5055 and 5056 and what ever other ports you want per server. This means that the region servers (if they are running on the same box) would need different ports too.
    Last edited by am385; 06-10-2017 at 11:29 AM.

  4. #14
    Join Date
    Feb 2014
    Posts
    273
    In the long run, I think access to your repository would save a lot of questions and provide a nice insight as to how you do things.
    Personally, I'd prefer something SourceTree can access - so probably BitBucket. But if you are on Azure primarily, then I'd have to ask what's needed to get access to that.

    Sorry to bother you with all these questions.
    I seem to be rich in free time, but poor in ability. You are the opposite!

    I've yet to see an architecture that I understand for the incoming ports that takes into account a client, a master server and several different region servers. My TCP solution worked in the production build for region server connections to the master server so I just left it alone rather than fiddle with it. I may just have to make another branch of my project to go back to Nelson's reliable udp.

  5. #15
    Join Date
    Aug 2010
    Posts
    129
    So i had a little time tonight and I confirmed that as long as you change the ports in all of the configs to match that this does work. I assumed it would as logically nothing changed. It just refactored out some of the logic to a common base classes.

    Let me see what I can do about my own repository. It is hosted on Visual Studio Team Services so I would have to add you as a contributor to this project. I think I can have up to 5 free contributors. Luckily since I work for Microsoft, my own account does not count since I have a Visual Studio Subscription from work. My only issue with that is that I would need to setup a bunch of security as I keep all of my personal projects in VSTS. Let me see if this would be possible. Otherwise I will just set up a GitHub or Bitbucket account like we talked about and manually port over my changes. If we are going to do that I would also like for users to user git as it is meant to be used with branches and pull requests.

  6. #16
    Join Date
    Feb 2014
    Posts
    273
    OK. Looking forward to seeing your repository.
    (My wife thought I was talking about suppositories -- medical professional! She isn't normally interested in coding, but sometimes I can't help it)

    Sorry I was so thick before with Autofac. I didn't associate those errors with your injection code. I thought you had modded the ServerContext.

    Your PeerBase classes refactoring is now working at my end on my production server. Any further refactoring isn't vital to the cause. I am just happy the Photon4 migration is essentially complete. This went much further than I think Nelson realised at the time of his last video.

    I wish I had named my project with my domain/game name instead of the Buzz prefix.

    I've just created my VSTS account and messed around with a test project etc. Kinda neat! When you want my account e-mail address, pm me. Seems like a ton of permissions in there for sharing. We both might learn something
    From what I can see you would just put me in the existing "Readers" group for that project.
    Last edited by oldngrey; 06-19-2017 at 09:43 AM.

Page 2 of 2 FirstFirst 12

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •