Photon Server + Plugins + PUN

Hi there,

I'm currently using PUN for my game, which is limiting to my case since I want to be able to have dozens/hunderds of players in the same world (MMoish)..

Atm I limit the number of players per room to 10. Also cheating is relatively simple since the authority is at best a master client.

My question is, can I use Photon Server + Plugins + PUN to run an a server that hosts hundreds of players? Or do the plugins stuff with the callbacks on the server work on for room based stuff?

Thanks ahead,
Michael.

Comments

  • Hi, @StarKist

    well, yes, plugin stuff was developed to work with rooms, but that does not mean that you can not use it. The Room can be base for your Worlds. you may use the plugin to do interests management and all the logic.

    best,
    Ilya
  • hi @chvetsov,

    I see, so basically I can have just a few rooms or even just one and each room can support a lot of players in it, is that what you are saying?
  • @StarKist yes, that is what I'm saying, but do not forget about interest management otherwise your clients will be overwhelmed by the number of messages
    And one more note, there is no support for this in default implementation, you have to intercept messages and rebroadcast them your self

    best,
    ilya
  • StarKist
    StarKist
    edited September 2018
    @chvetsov I see, nice, yes I'm aware of interest management...
    I saw something about intercepting RPCs from PUN using Photon Server by implementing custom decoding of RaiseEvent on the server side..

    Any idea how hard it would be to convert from PUN where the master client is the authority to having the master server intercept RPCs and Call RPCs on the clients instead?

    Instead of implementing a completely custom protocol from scratch?

    Thanks for the info,
    Michael.
  • StarKist
    StarKist
    edited September 2018
    @chvetsov Bumping this with a new question:

    If I want to control bots on the server, is it a good idea to use a Timer:
    timer = PluginHost.CreateTimer(
    ScheduledEvent,
    1000,
    2000);

    To implement the bots behavior? for example let the Timer run 30 times a second or so?
    looping through the bots and have them broadcast updates to the recipients according to the interest management?

    Another option is to run a headless unity client along the server that will control the bots, that will make a lot of things easier, however, I wonder how much worse would the performance be in relation to running is purely on photon server..

    Thanks,
    Michael.
  • In general, you may go in either way. Timers and Unity Host will work because you want just one world per GS. The current version of self-hosted unity takes a lot of resources, but they are going to make it better.

    With timers, I would probably use a slightly different way. I would create the timer at the end of logic. Timers are calling your method slightly irregular plus your logic also may take different time. You may take all this into account and schedule timer to a shorter time, for instance. So that calculation will be more smooth.
    Check what does better suit your needs

    best,
    ilya
  • Hmm so creating a timer 30 times a second is ok?
    From what you are saying I understand that the timer calls my function and then waits for it to end before timing the next call (in the case of a periodical timer), rather than an interrupt like timer, is that right?

  • Well, to be honest, 30 times per second on the server is too much. I mean from the server logic point of view. usually, it is ok if it ticks 10 times per second
    >From what you are saying I understand that the timer calls my function and then waits for it to end before timing the next call (in the case of a periodical timer), rather than an interrupt like timer, is that right?
    yes, that is right. every new timer callback will be executed only when previous finishes

    best,
    ilya
  • When I say 30 ticks per second I mean the server-side only calculations, rather than broadcast to clients...
    I need to think if it makes sense to run the server-side calculation faster than the sending rate at all, since updates like position can be as slow as 5-10 updates per second, while other events, like "attack" can be handled in an "event driven" manner - meaning immediately on arrival without waiting for the next tick.
  • that all makes sense

    best,
    ilya
  • StarKist
    StarKist
    edited October 2018
    @chvetsov

    New question:

    What would be a good way to store the player list on the server in the plugin?

    I can see the pluginHost.GameActors, now I want to extend that for much more information related to the game, like positions, rotations, hp, and all that.

    Is creating a List/Array of players with my information and a pointer to the IActor for each of them ok? or is there a better way?

    Thanks,
    Michael.
  • >Is creating a List/Array of players with my information and a pointer to the IActor for each of them ok? or is there a better way?

    no, there is nothing built-in. Except for properties, but they are not quick enough to access them.

    best,
    ilya
  • Next Question:
    Is there a master server thing where it wont let players create new rooms and return
    "Operation failed: OperationResponse 227: ReturnCode: 32762" if the CPU usage on the server machine is high?

    I notice clients can't create new rooms when I see the next log:
    "2018-10-23 22:31:45,510 [44] INFO Photon.Common.LoadBalancer.LoadShedding.FeedbackController - Transit CpuUsage from High to Highest with input 90
    2018-10-23 22:31:45,513 [44] INFO Photon.Common.LoadBalancer.LoadShedding.WorkloadController - FeedbackLevel changed: old=High, new=Highest"

    Or is this a coincidence and I get that error for another reason?

    Thanks,
    Michael.
  • Hi, @StarKist

    In this case, server treated as overloaded in LoadBalancer does not allow to create games on it. This error is a clear sign that you need more game servers

    best,
    ilya
  • @chvetsov , I did not deploy the solution yet, just running this on my local machine, was wondering if I got it right and I can carry on..

    Thanks!
  • @chvetsov, Implementing server side ticks now using PluginHost.CreateOneTimeTimer(Update, nextUpdateIn);

    I set the next code up:

    private int goalUpdateInterval = 50; // tick every 50ms (20 fps)
    void Update()
    {
    // start stopwatch to measure how much time the update call takes to run
    stopwatch.Restart();

    // *************************************************************************
    // Update body
    // *************************************************************************

    // run update tick on all players
    UpdatePlayers();

    Debug.WriteLine("diff: " + (previousMs - (DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond)));
    previousMs = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;


    // *************************************************************************
    // Update body - End
    // *************************************************************************

    // calculate in how much time we should invoke the next update call according to how much
    // time passed since the beggining of this update call
    stopwatch.Stop();
    elapsedTime = (int)stopwatch.ElapsedMilliseconds;
    Debug.WriteLine("elapsedTime: "+elapsedTime);
    if(elapsedTime > goalUpdateInterval) {
    nextUpdateIn = 0;
    deltaTime = elapsedTime/1000f;
    } else {
    nextUpdateIn = goalUpdateInterval - elapsedTime;
    deltaTime = goalUpdateInterval/1000f;
    }
    frameNum++;
    // set the time for the next Update call
    sst_UpdateTimer = myPlugin.PluginHost.CreateOneTimeTimer(Update, nextUpdateIn);
    }


    The output is:
    elapsedTime: 1
    diff: -61
    elapsedTime: 0
    diff: -64
    elapsedTime: 0
    diff: -63
    elapsedTime: 0
    diff: -60
    diff: 0
    elapsedTime: 3
    diff: -48
    elapsedTime: 0
    diff: -58
    elapsedTime: 0
    diff: -63
    elapsedTime: 0
    diff: -63
    diff: 0
    elapsedTime: 1
    diff: -61
    elapsedTime: 0
    diff: -62
    elapsedTime: 0
    diff: -63
    elapsedTime: 0
    diff: -63
    diff: 0
    elapsedTime: 3
    diff: -47
    elapsedTime: 0
    diff: -60
    elapsedTime: 0
    diff: -60
    elapsedTime: 1
    diff: -62
    diff: 0
    elapsedTime: 1
    diff: -61
    elapsedTime: 0
    diff: -62
    elapsedTime: 0
    diff: -65
    elapsedTime: 0
    diff: -59
    diff: 0
    elapsedTime: 3
    diff: -60
    elapsedTime: 1

    What I would expect to see is diff: -50 on average, since thats what I configured for goalUpdateInterval;
    but I get about 10-12 extra ms, which I feel is rather unacceptable.

    Could you give me a hint on why that happens and how can I make it accurate? down to the ms?

    Thanks,
    Michael.
  • @chvetsov, Hi,

    I'm implementing the server side ticks now using a Timer.

    The code I have atm is:

    private int goalUpdateInterval = 50; // tick every 50ms (20 fps)
    void Update()
    {
    // start stopwatch to measure how much time the update call takes to run
    stopwatch.Restart();

    // *************************************************************************
    // Update body
    // *************************************************************************

    // run update tick on all players
    UpdatePlayers();

    Debug.WriteLine("diff: " + (previousMs - (DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond)));
    previousMs = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;


    // *************************************************************************
    // Update body - End
    // *************************************************************************

    // calculate in how much time we should invoke the next update call according to how much
    // time passed since the beggining of this update call
    stopwatch.Stop();
    elapsedTime = (int)stopwatch.ElapsedMilliseconds;
    Debug.WriteLine("elapsedTime: "+elapsedTime);
    if(elapsedTime > goalUpdateInterval) {
    nextUpdateIn = 0;
    deltaTime = elapsedTime/1000f;
    } else {
    nextUpdateIn = goalUpdateInterval - elapsedTime;
    deltaTime = goalUpdateInterval/1000f;
    }
    frameNum++;
    // set the time for the next Update call
    sst_UpdateTimer = myPlugin.PluginHost.CreateOneTimeTimer(Update, nextUpdateIn);
    }


    The output for this code is:
    diff: -56
    diff: 0
    elapsedTime: 1
    diff: -61
    elapsedTime: 0
    diff: -64
    elapsedTime: 0
    diff: -63
    elapsedTime: 0
    diff: -60
    diff: 0
    elapsedTime: 3
    diff: -48
    elapsedTime: 0
    diff: -58
    elapsedTime: 0
    diff: -63
    elapsedTime: 0
    diff: -63
    diff: 0
    elapsedTime: 1
    diff: -61
    elapsedTime: 0
    diff: -62
    elapsedTime: 0
    diff: -63
    elapsedTime: 0
    diff: -63
    diff: 0
    elapsedTime: 3
    diff: -47
    elapsedTime: 0
    diff: -60
    elapsedTime: 0
    diff: -60
    elapsedTime: 1
    diff: -62
    diff: 0
    elapsedTime: 1
    diff: -61
    elapsedTime: 0
    diff: -62
    elapsedTime: 0
    diff: -65


    The output I'd expect to see however is diff: -50 on avg.
    I cant figure out where I get the extra 10-15 ms from since I do try to compensate for the runtime of the update loop itself.

    Could you give me a hint on whats happening and how to make it accurate? preferably down to the ms?

    Thanks,
    Michael.
  • The thing is that when a timer is fired task is enqueued into a fiber. The fiber may have some tasks, that is why you may see this delays

    best,
    Ilya
  • StarKist
    StarKist
    edited November 2018
    I see, can I then run a thread where the update function happens with lock{} on shared memory? or is it going to break things?
  • @StarKist sure, you may do this, but this solution is totally unscalable because for every room you will need this thread. Or in your factory, you may create a thread and this thread will tick all your rooms. But not sure how well this will work

    best,
    ilya
  • @chvetsov, why would it be unscalable, I'm sure it can handle 30-50 threads, am I right?
  • well, yes, 30-50 it will be able. if this is all you need then it is fine

    best
    ilya