Tuesday, January 03, 2017

Notes on the Unity3D UNET HLAPI

The NetworkManager instantiates a Player prefab. This is your Player Object.

In a two player game, your GameObjects exist conceptually in 3 places, and logically in 2 places. An object exists on each game client, and on the game server. If the game server is also a client, it is called a host, and it shares the GameObject with the client.

By default:
- only methods on components on the Player Object can have the [Command] attribute.
- you can only call [Command] methods from other methods on the Player Object.

It is hard to see which code is server code, and which is client code. I recommend using the [Server] and [Client] attributes liberally to add this information to the source code. When this attribute cannot be applied to a method (only NetworkBehaviours support this attribute) then add the attribute inside a comment.

The NetworkTransform does not provide interpolation when in Transform mode, only when using RigidBody mode. You will need to write this code yourself using [SyncVar].

For fast iterative development, keep the NetworkManager in your development scene, and add the NetworkManagerHUD component. Set this scene as your startup scene, then Build and Run, choose Host or Client from the GUI, then press Play in the editor, and choose Host or Client.

NetworkServer.Spawn only create an instance on the client, and sets it's transform component to match the server. You will have to do any other configuration on this object via a secondary [ClientRpc] call.

Less Code is Less Bugs in Less Time

Some favourite quotes from Ron Jeffries, one of the three founders of the Extreme Programming software development methodology...

Often you will be building some class and you’ll hear yourself saying “We’re going to need…”.

Resist that impulse, every time. Always implement things when you actually need them, never when you just foresee that you need them. 

And this too...

The best way to implement code quickly is to implement less of it. The best way to have fewer bugs is to implement less code.



Is the Unity3D GameObject.GetComponent method slow?

TLDR; No, not really.

Using the below test script, using GetComponent is about 3x slower than using a direct reference. Caching the return calue in a Dictionary, is slightly faster.

A typical result for me is 232 GetComponent(), 74 for private field and 201 for a dictionary lookup. If you use a builtin Unity type, like Transform, GetComponent is twice as fast.

I made sure the compiler was not optimising out my tests by putting in some fake 'xyzzy' work which is not included in the timing.

Why this simple test? I'm proving to myself it is not terribly bad to use a GetComponent call in an update loop, or keep a Dictionary cache. The call itself is so fast (0.000000232 seconds) that there is really no issue using it a few times inside an update loop.


using System.Collections;
using System.Collections.Generic;
using UnityEngine;


public class GetComponentTest : MonoBehaviour {
    public int iterations = 1000000;

    GetComponentTest testComponentB, testComponentA;

    Dictionary<System.Type, Component> cache = new Dictionary<System.Type, Component>();

    void Start () {
        var sw = new System.Diagnostics.Stopwatch();
        for(var i=0; i<iterations; i++) {
            sw.Start();
            var r = GetComponent<GetComponentTest>();
            sw.Stop ();
            if(r.name == "xyzzy") {
                Debug.Log("xyzzy");
            }
        }
        Debug.Log(sw.ElapsedMilliseconds);

        testComponentB = GetComponent<GetComponentTest>();
        sw.Reset();
        for(var i=0; i<iterations; i++) {
            sw.Start();
            testComponentA = testComponentB;
            sw.Stop ();
            if(testComponentA.name == "xyzzy") {
                Debug.Log("xyzzy");
            }
        }
        Debug.Log(sw.ElapsedMilliseconds);
        sw.Reset();

        cache[typeof(GetComponentTest)] = GetComponent<GetComponentTest>();
        sw.Reset();
        for(var i=0; i<iterations; i++) {
            sw.Start();
            testComponentA = cache[typeof(GetComponentTest)] as GetComponentTest;
            sw.Stop ();
            if(testComponentA.name == "xyzzy") {
                Debug.Log("xyzzy");
            }
        }
        Debug.Log(sw.ElapsedMilliseconds);
        sw.Reset();
    }
}

Thursday, December 29, 2016

I made a Server Program in Go.

MiddleMan: A Pub/Sub and Request/Response server in Go.

This is my first Go project. It is a rewrite of an existing Python server, based on some previous work: https://entitycrisis.blogspot.de/2016/09/python3-asyncio-pubsub-plaything.html

All code available here: https://github.com/simonwittber/middleman

I can see Go becoming my preferred language for game servers.

Friday, September 23, 2016

Python3 Asyncio PubSub Plaything


#!/usr/bin/env python
import io
import asyncio
import websockets
import logging
import collections

logger = logging.getLogger('websockets.server')
logger.setLevel(logging.ERROR)
logger.addHandler(logging.StreamHandler())
events = collections.defaultdict(lambda: set())
#-----------------------------------------------------------------------
async def handle_outgoing_queue(websocket):
    while websocket.open:
        msg = await websocket.outbox.get()
        await websocket.send(msg)
#-----------------------------------------------------------------------
async def pubsub(websocket, path):
    websocket.prefix = path.encode()
    websocket.outbox = asyncio.Queue()
    websocket.subscriptions = set()
    sender_task = asyncio.ensure_future(handle_outgoing_queue(websocket))
    while True:
        msg = await websocket.recv()
        if msg is None: break
        if isinstance(msg, str): msg = msg.encode()
        stream = io.BytesIO(msg)
        await handle_message(websocket, stream)
    sender_task.cancel()
    for name in websocket.subscriptions:
        try:
            events[name].remove(websocket)
        except KeyError:
            pass
#-----------------------------------------------------------------------
async def handle_message(websocket, stream):
    cmd = stream.readline().strip()
    name = websocket.prefix + stream.readline().strip()
    print(cmd, name);
    if cmd == b"SUB":
        events[name].add(websocket)
        websocket.subscriptions.add(name)
    elif cmd == b"UNS":
        subscribers = events[name]
        try:
            websocket.subscriptions.remove(name)
        except KeyError:
            pass
        try:
            subscribers.remove(websocket)
        except KeyError:
            pass
    elif cmd == b"PUB":
        stream.seek(0)
        msg = stream.read()
        for subscriber in events[name]:
            await subscriber.outbox.put(msg)
#-----------------------------------------------------------------------
start_server = websockets.serve(pubsub, '0.0.0.0', 8765)
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()

Popular Posts