WebSocketSharp .onMessage on main thread? / handling in Unity? - unity3d

I find it very difficult to find a simple reliable library to use, in Unity, for a simple websocket client. WebSocketSharp (ie, https://github.com/sta/websocket-sharp ) seems fine but many items are unexplained.
When incoming messages arrive,
wssharp_connection.OnMessage += (sender, e) =>
{
if (!e.IsText) { return; }
Debug.Log(">> OnMessage, " + e.Data);
Handle(e.Data);
// but wait, not the main Unity thread??
};
void Handle(string msg)
{
.. your conventional Unity call, in a monobehavior
}
Testing shows it seems to arrive on (always? sometimes?) not the main Unity thread.
Does anyone know
In fact is it always not the main thread, or can it vary?
If always another thread, in fact is it always the same one thread, or does it vary/many?
Since every single Unity app that uses a websocket client has to do this, I'm surprised that there are not 100s of discussion about how to handle incoming messages, including on the WebSocketSharp page,
but here's what I would do, just in the usual way you handle "threading" in a frame-based engine like Unity:
wssharp_connection.OnMessage += (sender, e) =>
{
if (!e.IsText) { return; }
incoming_messages.Enqueue(e.Data);
};
// and ...
ConcurrentQueue<string> incoming_messages = new ConcurrentQueue<string>();
void Update()
{
if (incoming_messages.TryDequeue(out var message))
{
Handle(message);
}
}
So then
Is that indeed the usual way to handle the threading problem in WebSocketSharp, when using inside Unity?
Given how many billion Unity apps there are with websockets clients, this must be a pretty well-trodden path.

Related

Unity game stops while coroutine running

Unity(character movement and everything in current game scene) stops while downloading textures from web url.
I'm using Socket.IO for online multiplayer.
request 'map info' with socket.emit in void Start()
get 'map info' and create map entities with Instantiate()
get profile images(textures) from url and change texture of map entities
IEnumerator DownloadImage(string MediaUrl, SpriteRenderer spr) {
// check if url is malformed
if (MediaUrl == null || MediaUrl.Contains("Null")) {
yield break;
}
UnityWebRequest request = UnityWebRequestTexture.GetTexture(MediaUrl);
yield return request.SendWebRequest();
if (request.result == UnityWebRequest.Result.ConnectionError || request.result == UnityWebRequest.Result.DataProcessingError || request.result == UnityWebRequest.Result.ProtocolError)
Debug.Log(request.error);
else {
Texture2D tex = ((DownloadHandlerTexture)request.downloadHandler).texture;
spr.sprite = Sprite.Create(tex, new Rect(0.0f, 0.0f, tex.width, tex.height), new Vector2(0.5f, 0.5f), tex.width / 2f);
}
}
Is there a way to keep game running with user interactions(character movement or button actions) while downloading textures?
Unity co-routines (CR) are a entry-level and sometimes hazardous form of performing work over a series of frames on the Main Unity thread. They are useful for what is essentially a logical lerp across a series of frames such as fading, explode-my-spaceship-after-5-seconds, or deferring cockpit display updates.
CRs are sliced up and multiplexed over the Main Thread so the slowest part of your game is going to be the CR with the largest demand for time in a single frame. Performing anything lengthy during a single yield per frame is going to slow down your game, particularly I/O operations.
Alternative - Unity Jobs
Instead, consider using Unity 'IJob' which allows for operations to be executed on a worker thread thus freeing up the Unity thread to do what it does best and not lengthy I/O operations.
Unity:
Use IJob to schedule a single job that runs in parallel to other jobs and the main thread. When a job is scheduled, the job's Execute method is invoked on a worker thread. More...
Now wasting a whole thread that may spend it's life waiting for I/O to complete is arguably a waste of a perfectly good thread but it is certainly better than blocking the Main Thread.
Coroutines...thar be dragons
So earlier I mentioned "hazardous" with regards to CRs and the reason is three-fold:
When firing off a CR in a method that is called many times per second such as Update() make sure you adequately guard the StartCoroutine() else you could end up with zillions of them running only to run out of memory. This one is quite common on Stack Overflow
Don't make the mistake of thinking they are somehow a worker thread or other piece of magic that won't slow down the Main Thread
In many ways CRs are like DoEvents, Application.DoEvents in Visual Basic and .NET respectively and both leads to a nasty case of re-entrancy (where state is clobbered and unpredictable just like in a multi-threaded app but without the threads)
There's also the whole is Unity promoting a strange use of IEnumerator and yield? debate.
My other tip is, if you need something lerped or delayed and isn't doing I/O, consider just making a class and use Time.
e.g. in my flight sim, I only want to update cockpit displays every 100ms or so. For that I define a Delay class:
public class Delay
{
private float _lastInterval;
/// <summary>
/// The timeout in seconds
/// </summary>
/// <param name="timeout"></param>
private Delay(float timeout)
{
Timeout = timeout;
_lastInterval = Time.time;
}
public float Timeout { get; }
public bool IsTimedOut => Time.time> _lastInterval + Timeout;
public void Reset()
{
_lastInterval = Time.time;
}
public static Delay StartNew(float delayInSeconds)
{
return new Delay(delayInSeconds);
}
}
.
.
.
private void Update()
{
if (!_delay.IsTimedOut)
{
return;
}
// Do something time consuming
_delay.Reset();
}

How to generate a random number and make sure it is the same for everyone using Photon in Unity?

Hi Guys I am converting a single player Sudoko game into a multiplayer game (right now 2 players) using Photon in Unity.
The basic logic of the Sudoku game is that there are 300 puzzle data already loaded in it. A random number between 1 to 300 is picked up and the corresponding puzzle is loaded.
But the problem I am facing is even though I have made sure that the same number is getting picked up for both the client and the master server, different puzzles are getting loaded.
So basically I script called MultiManager attached to the MultiManager GameObject in the Sudoku screen.The script looks something like this.
void Start()
{
PV = GetComponent<PhotonView>();
if (PhotonNetwork.IsMasterClient)
{
puzzleIndex = Random.Range(0, 300);
PV.RPC("RPC_PuzzleIndex", RpcTarget.Others, puzzleIndex);
}
gameManager = FindObjectOfType(typeof(GameManager)) as GameManager;
gameManager.PlayNewGame("easy");
}
[PunRPC]
void RPC_PuzzleIndex(int puzzleIndexNUmber)
{
puzzleIndex = puzzleIndexNUmber;
}
So in the GameManager script you have these functions:
public void PlayNewGame(string groupId)
{
// Get the PuzzleGroupData for the given groupId
for (int i = 0; i < puzzleGroups.Count; i++)
{
PuzzleGroupData puzzleGroupData = puzzleGroups[i];
if (groupId == puzzleGroupData.groupId)
{
PlayNewGame(puzzleGroupData);
return;
}
}
}
private void PlayNewGame(PuzzleGroupData puzzleGroupData)
{
// Get a puzzle that has not yet been played by the user
PuzzleData puzzleData = puzzleGroupData.GetPuzzle();
// Play the game using the new puzzle data
PlayGame(puzzleData);
}
And in the PuzzleGroupData class you have this function :
public PuzzleData GetPuzzle()
{
return new PuzzleData(puzzleFiles[MultiManager.puzzleIndex], shiftAmount, groupId);
}
I don't quite get as to whats wrong which is happening. I tried to use other variations like keeping that random number outside of the condition inside of PhotonNetwork.isMasterClient and all, but doesn't work.
If anyone can help it would be great. By the way this Sudoku game was purchased and I am trying to convert it to a mutliPlayer game
Since the master is usually the first one in a room so also the first one getting Start called I think what happens is that your other clients are simply not connected yet when the RPC is called.
Further it might also happen (actually pretty likely) that Start is called before the RPC has the chance to be received.
I would rather actually wait until you have the value and do
void Start()
{
PV = GetComponent<PhotonView>();
if (PhotonNetwork.IsMasterClient)
{
PV.RPC(name of(RPC_PuzzleIndex), RpcTarget.AllBuffered, Random.Range(0, 300));
}
}
[PunRPC]
void RPC_PuzzleIndex(int puzzleIndexNUmber)
{
puzzleIndex = puzzleIndexNUmber;
gameManager = FindObjectOfType(typeof(GameManager)) as GameManager;
gameManager.PlayNewGame("easy");
}
This way
the RpcTarget.AllBuffered makes sure that also clients joining later will receive the call
there is no way you start a game without receiving the random value first

OnClientExitRoom function is called twice

I'm using NetworkRoomManager and NetworkDiscovery and when a player exits a room scene I call the NetworkManager.singleton.StopClient() in networkdiscoveryhud then you will found out that its calling the OnClientExitRoom function in networkroomplayerext script twice.
Or I should not use NetworkManager.singleton.StopClient() when client exit a room scene? Below is my script for client or server exiting a room.
public void StopHost()
{
if (NetworkServer.active && NetworkClient.isConnected)
{
NetworkManager.singleton.StopHost();
}
else if (NetworkClient.isConnected)
{
NetworkManager.singleton.StopClient();
}
else if (NetworkServer.active)
{
NetworkManager.singleton.StopServer();
}
networkDiscovery.StopDiscovery();
}
I recommend Lobby and Worlds. It's much more flexible than NetworkRoomManager and has a ton of built in features.
More info on it here. https://trello.com/c/0jT4kZ6O

Unet Syncing animator layer weight Unity 3D

I'm currently working on a multiplayer fps to learn UNET and Unity. in this project I'm using Network Animator Component to sync my animations.
I have one layer that runs the normal states like walking and jumping and another that just controls the upper body for scoping. This layer gets activated when scoping.
The parameters a syncing fine but not the layer weight.
The script that controls the layer weight gets disabled on the remote player as it is part of the same script that takes care of shooting and we don't want to shoot from the remote player.
So I placed that code on a different script and made Layerweight a Syncvar, still no results.
this is the snippet that sets the weight and fades it back out.
if (isScoped)
{
animator.SetLayerWeight(1, 1);
layerWeight = 1;
}
else
{
layerWeight = Mathf.Lerp(layerWeight, 0, 0.1f);
animator.SetLayerWeight(1, layerWeight);
}
animator.SetBool("Scoped", isScoped);
how can I sync a variable that is updated on a disabled script?
And what are the different custom attributes like [Command] and [ClientRpc] I should use to get this working?
I have read the documentation and manuals of UNET, but struggle at this relatively easy task as I'm having a hard time understanding the difference of these calls.
In general the main problem with [SyncVar] is that they are
synchronized from the server to clients
so not the other way round!
Then
[Command] is a method that is called by any client but only executed on the server
[ClientRpc] is kind of the opposite, it is called by the server and then executed on all clients
so what I would do is have a setter method for the layerweight like
private void SetLayerWeight(float value)
{
// only player with authority may do so
if(!isLocalPlayer) return;
// Tell the server to update the value
CmdSetLayerWeight(value);
}
[Command]
private void CmdSetLayerWeight(float value)
{
// Server may now tell it to all clients
RpcSetLayerWeight(value);
}
[ClientRpc]
private void RpcSetLayerWeight(float value)
{
// called on all clients including server
layerweight = value;
animator.SetLayerWeight(1, layerweight);
}
and call it like
if (isScoped)
{
SetLayerWeight(1);
}
else
{
SetLayerWeight(Mathf.Lerp(layerWeight, 0, 0.1f));
}
Note though that passing this forth and back between server and client will cause some uncanny lag!
You would probably be better syncing the isScoped bool instead and assign the value directly on the calling client:
private void SetScoped(bool value)
{
// only player with authority may do so
if(!isLocalPlayer) return;
// for yourself already apply the value directly so you don't have
// to wait until it comes back from the server
scoped = value;
// Tell the server to update the value and who you are
CmdSetLayerWeight(value);
}
// Executed only on the server
[Command]
private void CmdSetLayerWeight(bool value)
{
// Server may now tell it to all clients
RpcSetLayerWeight(value);
}
// Executed on all clients
[ClientRpc]
private void RpcSetLayerWeight(bool value)
{
// called on all clients including server
// since setting a bool is not expensive it doesn't matter
// if this is done again also on the client who originally initialized this
scoped = value;
}

Can't get RunTransaction with Firebase in Unity to work

I'm working on a game in Unity, using Firebase for user and game data.
Everything is fine saving the game to Unity, but in Multiplayer I have the challenge to pair an open game to a unique player 2.
The flow is:
1. You look for a game with a state of "open"
2. If no game is found within X seconds, a new one is created with the state of "open"
I have a query that returns open games (those with state "open")
This query is added a Firebase eventhandler
public void OnButtonLookForOpenMPGames()
{
this.OpenFirebaseGamesAsHost = this.LiveGamereference.OrderByChild("State").EqualTo(WYMSettings.MP_GAME_STATE_OPEN_TO_PLAYER2)
.LimitToFirst(3);
this.OpenFirebaseGamesAsHost.ChildAdded += this.OnOpenMPGameFound;
}
This fires fine in the handler:
private void OnOpenMPGameFound(object sender, ChildChangedEventArgs args)
{
Debug.Log("Looking for open games");
if (args.DatabaseError != null)
{
// handle errors
}
else
{
Debug.Log("Open game found (it may be my own)");
// remove event listener because we got a hit
// it will fire as many times as it's true!
this.OpenFirebaseGamesAsHost.ChildAdded -= this.OnOpenMPGameFound;
if (args.Snapshot.Child("HostId").Value.ToString() != FirebaseAuth.DefaultInstance.CurrentUser.UserId)
{
Debug.Log("Other game than mine found open");
// this.AddDelayedUpdateAction(() =>
// {
// this.MPGameLockInTransaction(args.Snapshot.Reference);
// });
this.MPGameLockInTransaction(args.Snapshot.Reference);
}
}
}
The problem: The handler passes this on to the transaction function, but it fails with an inner exception (Internal Task Faulted) - I've tried many different variations, but can't figure this one out from the official Firebase example.
Problem code:
What I want to achieve is to lock only that game to those two players, the host and the players 2 in this example querying for open games. The State is an int, but here a string for clarity.
private void MPGameLockInTransaction(DatabaseReference mpreference)
{
mpreference.RunTransaction(mutableData =>
{
MultiPlayerGame transactionMPG = mutableData.Value as MultiPlayerGame;
if (transactionMPG == null)
{
return TransactionResult.Abort();
}
if (transactionMPG.State != "open")
{
// game is taken, abort
Debug.Log("transaction aborted");
return TransactionResult.Abort();
}
transactionMPG.State = "game started";
mutableData.Value = transactionMPG;
return TransactionResult.Success(mutableData);
}).ContinueWith(task =>
{
if (task.Exception != null)
{
Debug.Log("Transactionlock to game failed" + task.Exception.ToString());
// Look over again
// not implemented yet
}
else
{
Debug.Log("starting game immediately");
this.AddDelayedUpdateAction(() => this.StartMPGameImmediatelyFromSearch());
}
});
}
This is just a guess but it might be related to threading.
In newer .Net version ContinueWith might end on a thread where most of the Unity API may not be called (including Debug.Log).
Instead you could try and use ContinueWithOnMainThread from the extensions instead which was created exactly for this reason.
With the .NET 4.x runtime, continuations may often execute in the background, whilst this extension method pushes them onto the main thread.
The .NET 3.x runtime didn't have this issue as continuations were contained within the Parse library, which did this implicitly.