Error when leaving the master's room from the room Photon - c#

I set the timer of the existence of the room as the properties of the room. As soon as the master player leaves the room, an exception is triggered: Operation setProperties (252) not called because client is not connected or not ready yet, client state: Leaving. This error does not affect the operation, there is no such error when another player exits. How can I handle this exception? It just bothers the eyes.
private void Update()
{
_currentPlayersCountText.text = PhotonNetwork.PlayerList.Length.ToString();
if (PhotonNetwork.IsMasterClient)
{
if (PhotonNetwork.PlayerList.Length >= 5)
{
_startGameButton.GetComponent<Button>().enabled = true;
}
else
{
_startGameButton.GetComponent<Button>().enabled = false;
}
Hashtable ht = PhotonNetwork.CurrentRoom.CustomProperties;
ht.Remove("timer");
ht.Add("timer", _roomTimer.timeRemaining);
PhotonNetwork.CurrentRoom.SetCustomProperties(ht);
}
if (_roomTimer.timeRemaining <= 110)
{
LeaveRoom();
}
}
Here is the code that most likely causes this error.

You could try
public void LeaveRoom()
{
PhotonNetwork.LeaveRoom();
}
public override void OnLeftRoom()
{
StartCoroutine(WaitToLeave());
}
IEnumerator WaitToLeave()
{
while(PhotonNetwork.InRoom)
yield return null;
SceneManager.LoadScene(0);
}

Related

Start, Pause, Resume and Stop Unity Coroutines?

I have a class Program which is a list of Nodes that contain a method IEnumerator Invoke() and the Program class iterates through each Node invoking it. I want to be able to provide methods to Start, Pause, Resume, and Stop execution. Starting would cause the invocation to start at the top of the list, Pausing would effectively 'Stop' the execution and allow Resume to be able to pick up wherever execution was when Pause was called, and Stop would cease all function and would require Start to be called to begin again. With Unity's built-in Coroutines is this even possible, and if it is how do I Pause/Resume a coroutine?
EDIT
what I'm looking for is how to essentially pause an instance of Program and be able to resume it at the same step.
If I understand one of the comments correctly the suggestion it makes would be something similar to this?
public abstract class Node {
public abstract IEnumerator Invoke(ProgramCaller caller);
}
public class Program : Node {
private List<Node> nodes;
public override IEnumerator Invoke(ProgramCaller caller) {
int index = 0;
while(index < nodes.Count) {
if(caller.Paused) {
yield return null;
}
else {
yield return nodes[index].Invoke(caller);
index++;
}
}
}
}
So from what I read is you have e.g.
public class Node
{
public IEnumerator Invoke()
{
yield return null;
}
}
Then a Unity Coroutine is basically using the IEnumerator and invoking MoveNext on certain intervals (Update by default except using the special ones like e.g. WaitForFixedUpdate etc).
So you could simply make Program implement that like e.g.
public class Program : IEnumerator
{
public Node[] nodes;
private int index = -1;
private IEnumerator currentNode;
public bool MoveNext()
{
if (nodes == null || nodes.Length == 0)
{
return false;
}
while (currentNode == null)
{
index++;
if (index >= nodes.Length)
{
return false;
}
currentNode = nodes[index]?.Invoke();
}
if (currentNode.MoveNext())
{
return true;
}
currentNode = null;
return true;
}
public void Reset()
{
index = -1;
currentNode = null;
}
public object Current => null;
}
and then you can link this up to a Coroutine from a MonoBehaviour like e.g.
public class Example : MonoBehaviour
{
public Program program;
private Coroutine currentRoutine;
// just a name alias
public void StartProgram() => RestartProgram();
public void RestartProgram()
{
StopProgram();
ResumeProgram();
}
public void ResumeProgram()
{
currentRoutine = StartCoroutine(program);
}
public void PauseProgram()
{
if (currentRoutine != null)
{
StopCoroutine(currentRoutine);
}
}
public void StopProgram()
{
PauseProgram();
program.Reset();
}
}
as you see the only difference between Start/Stop and Pause/Resume is resetting or not resetting the Program.
Alternatively and maybe even more simple: A Coroutine is paused automatically when disabling according MonoBehaviour and resumed when enabling it again.
=> If it is an option for you to have a dedicated runner component for each program then all you need really is the resetting part and you could simply do
public class Program
{
public Node[] nodes;
public IEnumerator Run()
{
foreach (var node in nodes)
{
yield return node.Invoke();
}
}
}
This way you can run them all as a single IEnumerator and then
public class Example : MonoBehaviour
{
public Program program;
private Coroutine currentRoutine;
// just a name alias
public void StartProgram() => RestartProgram();
public void RestartProgram()
{
StopProgram();
currentRoutine = StartCoroutine(program.Run());
}
public void ResumeProgram()
{
enabled = true;
}
public void PauseProgram()
{
enabled = false;
}
public void StopProgram()
{
if (currentRoutine != null)
{
StopCoroutine(currentRoutine);
}
}
}

How to get public int 'currentSlot' from grandchildren

im beginner and still learning please don't hate.
So my script is located in Canvas, (Canvas -> inv -> invslot) but I need to get currentSlot int from a script in invslot gameobj., how would I do that the right way? I've tried this so far but doesn't seem to work at all.
void UpdateUI () {
for (int i = 0; i < slots.Length; i++)
{
if (i < inventory.items.Count) {
currentSlot = GetComponentsInChildren<InventoryScroll>();
slots[i] = currentSlot;
currentSlot.AddItem(inventory.items[i]);
Debug.Log ("Updating UI");
} else
{
slots[i].ClearSlot();
}
}
}
EDIT!
Here is the top of my InventoryUI, where I want to bring the int currentSlot
public Transform itemsParent;
Inventory inventory;
InventorySlot[] slots;
// Start is called before the first frame update
void Start()
{
inventory = Inventory.instance;
inventory.onItemChangedCallback += UpdateUI;
slots = itemsParent.GetComponentsInChildren<InventorySlot>();
}
But the Inventoryscroll goes this way
List<GameObject> slots = new List<GameObject>();
public int currentSlot=0;
int slotsToScroll=3;
void Start() {
foreach(Transform child in this.transform) {
slots.Add(child.gameObject);
}
}
void Update () {
if (Input.GetKeyDown(KeyCode.Alpha1)) {
currentSlot=0;
UpdateDisplay();
}
if (Input.GetAxis("Mouse ScrollWheel") >0){
if (currentSlot<slotsToScroll) {
currentSlot++;
} else {
currentSlot=0;
}
UpdateDisplay();
}
}
void UpdateDisplay() {
for (int i = 0; i < slots.Count; i++)
{
if (i==currentSlot) {
slots[i].transform.GetChild(0).gameObject.SetActive(true);
} else {
slots[i].transform.GetChild(0).gameObject.SetActive(false);
}
}
}
Inventory script
#region Singleton
public static Inventory instance;
void Awake () {
if (instance != null) {
Debug.LogWarning("More than one instance of inventory found!");
return;
}
instance = this;
}
#endregion
public delegate void OnItemChanged();
public OnItemChanged onItemChangedCallback;
public int space = 6;
public List items = new List();
public bool Add (Item item) {
if (!item.isDefaultItem) {
if(items.Count >= space) {
Debug.Log("Not enough inventory space.");
return false;
}
items.Add(item);
if (onItemChangedCallback != null)
onItemChangedCallback.Invoke();
}
return true;
public void Remove (Item item) {
items.Remove(item);
if (onItemChangedCallback != null)
onItemChangedCallback.Invoke();
Careful, Unity has two methods:
GetComponentsInChildren
Returns all components of Type type in the GameObject or any of its children.
this returns a InventoryScroll []!
and GetComponentInChildren
Returns the component of Type type in the GameObject or any of its children using depth first search.
This returns one single InventoryScroll reference, the first one found!
Note the s!
From your description and how you use it in your code it seems like you wanted to use the latter but have an s too much.
The second mistake: From the variable name it sounds like you rather wanted to get an InventorySlot not an InventoryScroll!
So you should use
currentSlot = GetComponentInChildren<InventorySlot>();
Though it is hard to tell what your actual goal is with this code without seeing the types of slots and currentSlot.
It also appears really strange to me that you already get all slots in Start but here you overwrite
slots[i] = currentSlot;
for each i that is i < inventory.items.Coun. Also you iterate through slots.Length but you pass in inventory.izems[i] .. I don't understand what is supposed to happen here exactly.

Unity "SceneManager.activeSceneChanged" getting called when "LoadSceneAsync" and load other scene

first, there is a singleton object simply is subscribed to activeSceneChanged
SomeSingleton.cs
void Awake ()
{
Debug.LogError ("AWAKE");
if (instance == null) {
Debug.LogError ("instance == null");
instance = this;
MusicController.instance = this;
SceneManager.activeSceneChanged += OnSceneChanged;
DontDestroyOnLoad (this.gameObject);
} else {
Debug.LogError ("destroy");
Destroy (this.gameObject);
}
}
void OnSceneChanged (Scene previousScene, Scene changedScene)
{
Debug.LogError ("OnSceneChanged changedScene = " + changedScene.name);
}
from Lobby Scene player moves to Room Scene.
in room scene.
the "Arena" Scene gets preloaded for better game experience.
LoadSceneAsync("Arena")
with allowSceneActivation = false
when player clicks play button, sets allowSceneActivation = true
but when user decides to go back and clicks Leave Button, LoadScene("Lobby") gets called.
At this time in console, it will Log
LOGS
OnSceneChanged changedScene = Lobby // time : 0.793
OnSceneChanged changedScene = Room // time : 3.982
[when player clicks Leave Button]
OnSceneChanged changedScene = Arena // time : 7.583
OnSceneChanged changedScene = Lobby // time : 7.583
like this...
it just gets activated but wont actually load the scene. why is Arena getting activatd? how to solve this problem???
should i do some unload thing to the AsyncLoading?
==== EDITED =====
when leave button clicked
public void LeaveRoom ()
{
print ("Leave Room And Move To Lobby");
SetCanStartIfMasterClient (false);
PhotonNetwork.LeaveRoom ();
AdmobController.DestroyBanner ();
Waiting.instance.StartWaiting (waitingForLeaveRoomData);
SceneFader.instance.LoadSceneWhenFadingComplete (SceneType.Lobby);
}
in SceneFader.cs
public enum SceneFadeType
{
FadeIn = 1,
FadeOut = -1
}
public class SceneFader : MonoBehaviour
{
public static SceneFader instance;
void Awake ()
{
instance = this;
// StartFadeOut
StartFading (SceneFadeType.FadeOut, null);
}
public void LoadSceneWhenFadingComplete (SceneType sceneType)
{
// 1. start Fading
// 2. on fading complete, LoadScene
StartFading
(
fadeType: SceneFadeType.FadeIn,
onFadingComplete: () => {
SceneManager.LoadScene (sceneType.ToString ());
}
);
}
public void LoadSceneWhenFadingCompleteAsync (ScenePreloader scenePreloader)
{
// 1 start preloading scene
// 2 on preloading complete, start fading
// 3 on fading complete LoadScene
scenePreloader.AddOnPreloadCompleteAndTriggerIfLoaded (
onPreloadComplete: () => {
StartFading (
fadeType: SceneFadeType.FadeIn,
onFadingComplete: () => {
scenePreloader.ActivateSceneWhenReady ();
}
);
}
);
}
private void StartFading (SceneFadeType fadeType, Action onFadingComplete)
{
Debug.LogWarning ("StartFAding ");
ScreenFader.instance.BeginFade (fadeType, onFadingComplete);
AudioMasterController.instance.StartFading (fadeType, null);
}
}
in ScenePreloader.cs
public class ScenePreloader : MonoBehaviour
{
Action<float> onPreloadRateChanged;
Action onPreloadComplete;
public AsyncOperation sceneLoader;
public bool isPreloadCompleted;
public void StartPreloadingScene (SceneType sceneType, LoadSceneMode loadSceneMode, Action<float> onPreloadRateChanged = null, Action onPreloadComplete = null)
{
this.onPreloadComplete = onPreloadComplete;
this.onPreloadRateChanged = onPreloadRateChanged;
StartCoroutine (PreloadSceneOperation (sceneType, loadSceneMode));
}
public IEnumerator PreloadSceneOperation (SceneType sceneType, LoadSceneMode loadSceneMode = LoadSceneMode.Single)
{
print ("PreloadSceneOperation sceneType = " + sceneType.ToString ());
sceneLoader = SceneManager.LoadSceneAsync (sceneType.ToString (), loadSceneMode);
sceneLoader.allowSceneActivation = false;
while (!sceneLoader.isDone) {
yield return null;
if (sceneLoader.progress >= 0.9f) {
print ("onPreloadComplete");
isPreloadCompleted = true;
if (onPreloadComplete != null) {
onPreloadComplete ();
}
break;
} else {
if (onPreloadRateChanged != null) {
onPreloadRateChanged (sceneLoader.progress);
}
}
}
}
public void ActivateSceneWhenReady ()
{
sceneLoader.allowSceneActivation = true;
}
public void AddOnPreloadCompleteAndTriggerIfLoaded (Action onPreloadComplete)
{
AddOnPreloadComplete (onPreloadComplete);
if (isPreloadCompleted) {
print ("isAlreadyLoadedTrigeringCallback");
onPreloadComplete ();
}
}
public void AddOnPreloadComplete (Action onPreloadComplete)
{
this.onPreloadComplete += onPreloadComplete;
}
public void SetOnLoadProgressValueChanged (Action<float> onLoadProgressValueChanged)
{
this.onPreloadRateChanged = onLoadProgressValueChanged;
}
}

Unity3D Coroutine Error

I'm stuck at Unity scripting. I got 3 files : Scene.cs , Player.cs and NetworkUtil.cs . I can't compile my code, as I don't know how to pass Coroutine response back to Scene.
In Scene.cs ( MonoBehavior class ) :
void OnGUI() {
if(GUI.Button(new Rect(0, 0, 100, 100), "Register")) {
StartCoroutine(registerPlayer());
}
}
IEnumerator registerPlayer() {
return Player.NewPlayer("Raptor"); // since Player is not IEnumerator, this line causes error.
}
In Player.cs ( Object class ) :
public static Player NewPlayer(string name) {
Player p = new Player();
result = p.tryRegister();
if(result) {
return p;
} else {
return null;
}
}
private bool tryRegister() {
Dictionary<string, string> data = new Dictionary<string, string>();
data.Add("some_key", "some_value");
NetworkUtil.ExecuteAPI(data);
}
In NetworkUtil.cs ( Object class ) :
public static IEnumerator ExecuteAPI(Dictionary<string, string> data) {
WWWForm form = new WWWForm();
form.AddField("mode", request);
foreach(KeyValuePair<string, string> entry in data) {
form.AddField(entry.Key, entry.Value);
}
WWW handler = new WWW(API_URL, form);
yield return handler;
if(handler.error != null) {
Debug.Log("Error occurred: " + handler.error); // Server Error
} else {
Debug.Log("Response: " + handler.text);
}
}
How can I change the codes to make the flow complete?
Note: In Object class, StartCoroutine() does not exist.
EDIT
I have changed my codes to use Delegates & Events:
// DelegatesAndEvents.cs
public class DelegatesAndEvents : MonoBehaviour {
public delegate void RegisterEventHandler(Player player);
public static event RegisterEventHandler onPlayerRegister;
public static void NewPlayerRegistered(Player player) {
if(onPlayerRegister != null) {
onPlayerRegister(player);
}
}
}
Then in Scene.cs:
void Start() {
DelegatesAndEvents.onPlayerRegister += this.userRegistered;
}
public void userRegistered(Player player) {
Debug.Log("User registered.");
}
How should I put the trigger in NetworkUtil.cs & Player.cs ?
There are several ways of doing that, because you not only need to return an IEnumerator, but it's Unity itself that will iterate on it, so you don't have the possibility to catch the return value.
Technically is possible to do the following:
IEnumerator registerPlayer() {
yield return Player.NewPlayer("Raptor"); // this is legal, player will be the Current value of the IEnumerator
}
If you want to do do something like the code above, you need to wrap the coroutine inside another, and iterate on it by yourself (I did something similar implementing behavior trees):
IEnumerator Wrap(IEnumerator playerRegister)
{
while(playerRegister.MoveNext())
{
Player p = playerRegister.Current as Player; //this can be done if you know what you are doing
yield return null;
}
}
Another way is not return anything and pass a delegate to the coroutine, that will be called passing back to the caller the required parameter.
Something like:
IEnumerator DoSomething(Action<Response> whenDone)
{
while (doingSomething)
yield return null;
whenDone(response);
}
EDIT
The other problem with your code is that you are calling another coroutine (ExecuteAPI)from inside Player's constructor.
So, making registerPlayer a coroutine is pointless since you are not yielding anything.
In your case the simplest way to go is to pass a delegate to ExecuteAPI that will be called when you receive the response from the server.

Is this a safe way to execute threads alternatively?

I would like to run code alternatively, so I could stop execution at any moment. Is this code safe?
static class Program
{
static void Main()
{
var foo = new Foo();
//wait for interaction (this will be GUI app, so eg. btnNext_click)
foo.Continue();
//wait again etc.
foo.Continue();
foo.Continue();
foo.Continue();
foo.Continue();
foo.Continue();
}
}
class Foo
{
public Foo()
{
new Thread(Run).Start();
}
private void Run()
{
Break();
OnRun();
}
protected virtual void OnRun()
{
for (var i = 0; i < 5; i++)
{
Console.WriteLine(i);
Break();
}
//do something else and break;
}
private void Break()
{
lock (this)
{
Monitor.Pulse(this);
Monitor.Wait(this);
}
}
public void Continue()
{
lock (this)
{
Monitor.Pulse(this);
Monitor.Wait(this);
}
}
}
Of course I know, that now the application will never ends, but that's not the point.
I need this, because I would like to present steps in some kind of an algorithm and describe what is going on in particular moment, and making everything in one thread would lead to many complications even when using small amount of loops in the code. For example those lines:
for (var i = 0; i < 5; i++)
{
Console.WriteLine(i);
Break();
}
should be then replaced with:
if (this.i < 5)
{
Console.WriteLine(i++);
}
And that is just a small example of what I want to present. The code will be more complicated than a dummy for loop.
I recommend you check out this blog post about implementing fibers.
Code (In case the site goes down.)
public class Fiber
{
private readonly Stack<IEnumerator> stackFrame = new Stack<IEnumerator>();
private IEnumerator currentRoutine;
public Fiber(IEnumerator entryPoint)
{
this.currentRoutine = entryPoint;
}
public bool Step()
{
if (currentRoutine.MoveNext())
{
var subRoutine = currentRoutine.Current
as IEnumerator;
if (subRoutine != null)
{
stackFrame.Push(currentRoutine);
currentRoutine = subRoutine;
}
}
else if (stackFrame.Count > 0)
{
currentRoutine = stackFrame.Pop();
}
else
{
OnFiberTerminated(
new FiberTerminatedEventArgs(
currentRoutine.Current
)
);
return false;
}
return true;
}
public event EventHandler<FiberTerminatedEventArgs> FiberTerminated;
private void OnFiberTerminated(FiberTerminatedEventArgs e)
{
var handler = FiberTerminated;
if (handler != null)
{
handler(this, e);
}
}
}
public class FiberTerminatedEventArgs : EventArgs
{
private readonly object result;
public FiberTerminatedEventArgs(object result)
{
this.result = result;
}
public object Result
{
get { return this.result; }
}
}
class FiberTest
{
private static IEnumerator Recurse(int n)
{
Console.WriteLine(n);
yield return n;
if (n > 0)
{
yield return Recurse(n - 1);
}
}
static void Main(string[] args)
{
var fiber = new Fiber(Recurse(5));
while (fiber.Step()) ;
}
}
"...this will be GUI app..."
Then you probably do not want and will not have sequential code like above in Main().
I.e. the main GUI thread will not execute a serial code like above, but generally be idle, repainting, etc. or handling the Continue button click.
In that event handler you may better use an Auto|ManualResetEvent to signal the worker to proceed.
In the worker, just wait for the event.
I would suggest that any time one considers using Monitor.Wait(), one should write code so that it would work correctly if the Wait sometimes spontaneously acted as though it received a pulse. Typically, this means one should use the pattern:
lock(monitorObj)
{
while(notYetReady)
Monitor.Wait(monitorObj);
}
For your scenario, I'd suggest doing something like:
lock(monitorObj)
{
turn = [[identifier for this "thread"]];
Monitor.PulseAll(monitorObj);
while(turn != [[identifier for this "thread"]])
Monitor.Wait(monitorObj);
}
It is not possible for turn to change between its being checked whether it's the current thread's turn to proceed and the Monitor.Wait. Thus, if the Wait isn't skipped, the PulseAll is guaranteed to awaken it. Note that the code would work just fine if Wait spontaneously acted as though it received a pulse--it would simply spin around, observe turn wasn't set for the current thread, and go back to waiting.

Categories

Resources