Search Unity

  1. Get all the Unite Berlin 2018 news on the blog.
    Dismiss Notice
  2. Unity 2018.2 has arrived! Read about it here.
    Dismiss Notice
  3. We're looking for your feedback on the platforms you use and how you use them. Let us know!
    Dismiss Notice
  4. The Unity Pro & Visual Studio Professional Bundle gives you the tools you need to develop faster & collaborate more efficiently. Learn more.
    Dismiss Notice
  5. Improve your Unity skills with a certified instructor in a private, interactive classroom. Learn more.
    Dismiss Notice
  6. ARCore is out of developer preview! Read about it here.
    Dismiss Notice
  7. Magic Leap’s Lumin SDK Technical Preview for Unity lets you get started creating content for Magic Leap One™. Find more information on our blog!
    Dismiss Notice
  8. Want to see the most recent patch releases? Take a peek at the patch release page.
    Dismiss Notice

Requiring User Input when very Deep inside the Game Process

Discussion in 'General Discussion' started by manutoo, May 18, 2018.

  1. manutoo

    manutoo

    Joined:
    Jul 13, 2010
    Posts:
    256
    Hello,

    I'm creating sequels of my old games and by doing so I'm going to port their engine from C++ to C# in Unity.

    1 thing that bothers me is that I've been requesting user input from very deep in my game process. (likely up to 10 nested function calls)
    To do so, I just opened a menu, and then manually ran the system process which in turn called the renderer, GUI system, etc.

    In Unity, this is impossible to do like this.

    To solve this issue, I see only 2 solutions : 1st one is to allow to exit & re-enter the process when user input is required ; I already did it once in the past, but it can get cumbersome, especially when it gets very deeply nested.

    So now I'm thinking about multi-threading : game Update() would start a separate thread and wait for it to either end, or for it to request system process (ie: exit Update() and let Unity does its stuff).

    So I was wondering if other devs faced this kind of issue ? And how they handled it ? If they used multithreading, were they satisfied with the result ? (ie: maintenance wise, was it ok ?)

    And would the new job system be my correct tool in this case ?

    Note : coroutines can't help me here.

    Here how it'd look like :

    Code (CSharp):
    1. public class CGame : MonoBehaviour
    2. {
    3.     enum EState
    4.     {
    5.         e_Done,
    6.         e_Processing,
    7.         e_RequireUserInput,
    8.     }
    9.  
    10.     EState m_State = EState.e_Done;   // This is the only variable that can be accessed by both threads at the same time
    11.  
    12.     void Update()
    13.     {
    14.         if (m_State == EState.e_Done)
    15.         {
    16.             StartThread(Process());
    17.         }
    18.  
    19.         m_State = EState.e_Processing;
    20.  
    21.         while (m_State == EState.e_Processing)
    22.         {
    23.             // Do nothing, just waiting...
    24.         }
    25.     }
    26.  
    27.     void Process()
    28.     {
    29.         // ... do some stuff ...
    30.  
    31.         // Need User Input
    32.         RequestUserInput();
    33.  
    34.         // ... do some more stuff using User Input ...
    35.  
    36.         // Done
    37.         m_State = EState.e_Done;
    38.     }
    39.  
    40.     void RequestUserInput()
    41.     {
    42.         CGui.OpenMenu("HeyUserTellMeWhatYouWantToDo");
    43.  
    44.        while (!GotUserInput())
    45.        {
    46.            m_State = EState.e_RequireUserInput;
    47.  
    48.            while (m_State != EState.e_Processing)
    49.            {
    50.                // Do nothing, just waiting...
    51.            }
    52.        }
    53.     }
    54. }
    Use Case (for people wanting to be sure I can't do it another way) :
    my game is a tennis game where I process tournaments & players. According to the situation, the player controlled by the user may or may not do something (ie: enter the tournament he has selected) and then he has to take a new decision depending of this new context, and this is not known before processing the other players. And the consequences of his decision will influence the next processed players.

    EDIT Extra explanation :
    In term of coding graph, I guess you could see it as nested functions like this : A() -> B() -> C() -> D() -> E() -> F() with the user input needed in F() (dependent of F() processing) while E() requires F() result to go on, D() requires E() result, and so on.

    So if you had to process a lot of non-Unity data using a lot of non-Unity nested functions, and required process-dependent user input in the middle of that giant mess, how did you do it ?
     
    Last edited: May 20, 2018
  2. frosted

    frosted

    Joined:
    Jan 17, 2014
    Posts:
    3,039
    I think coroutine is actually exactly what you're looking for.

    Code (csharp):
    1. private IEnumerator Start(){
    2.     // do stuff before the input is needed
    3.    
    4.     while( IsWaitingForUserInput() ) yield return null;
    5.  
    6.     // you have user input now do whatever else
    7. }
    Why don't you think a coroutine is appropriate?
     
  3. manutoo

    manutoo

    Joined:
    Jul 13, 2010
    Posts:
    256
    @frosted,
    my example is about implementing the solution, not about the issue... ;)

    The key point is "likely up to 10 nested function calls" ; each function requiring the result of the called function right away ; if the function requiring user input is a coroutine, its caller won't get the result right away and thus the processing can't continue.
     
  4. frosted

    frosted

    Joined:
    Jan 17, 2014
    Posts:
    3,039
    Understanding the issue is key to providing a solution.

    Using IEnumerator without coroutine can still potentially provide solution, since you can use this to essentially block until the process is complete without needing unity callbacks.

    Depending on how exactly you're reading input or what you're operating on, you may have problems processing from background thread as unity tends to reject access to unity functions from non main thread.

    Also, in your provided example spawning the thread achieves nothing. You could simply switch on state and achieve the same logical functionality without the complexity of additional thread.

    The problem appears to revolve around needing to block a call until user input has completed. If the originating call is on unity's main thread anyway, this is not something you want to do as it will block internal process. If the call is occurring outside of main thread then you can simply sleep until your state is switched.
     
  5. manutoo

    manutoo

    Joined:
    Jul 13, 2010
    Posts:
    256
    @frosted,
    in my exposed solution, there's no actual input processing in the sub-thread, only data processing and opening menus, so the main thread can show this menu and gather user input.

    I exposed the exact problem in "user case" above.
    In term of coding graph, I guess you could see it as nested functions like this : A() -> B() -> C() -> D() -> E() -> F() with the user input needed in F() (dependent of F() processing) while E() requires F() result to go on, D() requires E() result, and so on.

    And again, the example is the solution, not the issue. I'm trying to demonstrate the functionality achieved through multi-threading to make it more clear how my solution to the problem would work (ie: how the sub-thread & the main thread would wait for each other, which is the key point in this situation)
     
  6. frosted

    frosted

    Joined:
    Jan 17, 2014
    Posts:
    3,039
    your example blocks update until the other process completes and is trying to open a gui menu on non main thread. Like, in the 5 lines of code you wrote there 2 are problematic.

    I won't try to convince you though, please try your example then when you understand the problems, come back to my response and re-read it.
     
    N1warhead likes this.
  7. N1warhead

    N1warhead

    Joined:
    Mar 12, 2014
    Posts:
    3,227
    You should really just simplify the process. I'll never understand why people just have to make things more complicated than they need to be. Be creative and find another way to do it as simple as possible. It seems the way you're doing it now is just going to be a mess to manage and even create.

    Like why do you even need to do Multi-Threading for user input? Seems like overkill. But perhaps if whatever it is you're trying to do requires Threading, then perhaps the new Unity Jobs system can work for you? Sense it's essentially Multi-Threading.

    Oh and by the way, you might be wasting your time with pure Multi-Threading as Unity is (not) thread safe. You can run your own Methods in Threads, but not Update, etc.


    EDIT: and what I mean by the very first sentence is.
    For example: back when I first started Unity a few years ago. the FPS Controller, hundreds of lines of code, to do the same exact thing that I can now do in essentially 60 lines of code... Keep it simple, why make it harder than it needs to be.
     
    Chrisasan, ippdev and Kiwasi like this.
  8. manutoo

    manutoo

    Joined:
    Jul 13, 2010
    Posts:
    256
    @frosted,
    blocking the main thread is how it works currently, so this behavior won't change.
    Opening the menu doesn't invoke Unity function so there's no threading issue. If an Unity function was called, it'd be possible to create an custom command to avoid that by letting the main thread open the menu.

    PS: I have 20 years of coding experience, it's not a noob question here. I'd like people with actual experience with this issue to answer me... :rolleyes:
     
  9. frosted

    frosted

    Joined:
    Jan 17, 2014
    Posts:
    3,039
    @manutoo,

    If your underlying call stack is on unity main thread, blocking it while relying on unity functionality to present menu will simply not work. Application freeze on your code causing essentially deadlock while main thread waits for update from main thread.

    if the calling code is running on a background thread then you can simply while( state ) sleep; the returning call and block there with no impact on unity and you already have a separate thread running to notify unity main thread.

    If the calling code is on unity main thread but your gui is on separate thread/process, then you don't need to spawn an intermediate thread to make the opening call.

    To explain a little simpler:

    If
    Code (csharp):
    1. CGui.OpenMenu("HeyUserTellMeWhatYouWantToDo");
    blocks while a non unity menu is being shown then your secondary thread has no meaning - you can simply stick that in your update method and have same result.

    If
    Code (csharp):
    1. CGui.OpenMenu("HeyUserTellMeWhatYouWantToDo");
    doesn't block because it has it's own thread/process, then opening a new thread to just call this and wait while also blocking update also has no utility. You again, can simply call this from update on main thread while waiting for notification.

    According to the code and context you presented, there is no argument for a thread to act while fully blocking the other one. Either your example is flawed, your context is flawed or your understanding of the process is flawed.
     
  10. manutoo

    manutoo

    Joined:
    Jul 13, 2010
    Posts:
    256
    @frosted,
    it's neither of both.
    CGui.OpenMenu() sets the current shown menu, that will be then rendered by the classic GUI() call (it'd be same if it was handled by the new GUI system), thus the need to give back control to the main thread through the change of m_State.

    After further reading, in C# it'd be done by WaitHandle.WaitOne() , which would replace the "while (m_State ...)" in the sample.

    If you had actually understood my initial problem, you'd see that none of your answers so far are pertinent, and I'm afraid I don't know how to explain more clearly... o_O

    I'll try to rephrase it one last time : if you had to process a lot of non-Unity data using a lot of non-Unity nested functions, and required process-dependent user input in the middle of that giant mess, how did you do it ?

    As long as you don't understand the actual issue, please don't answer anymore ; you'll just lose your time and mine... :(
     
    Last edited: May 19, 2018
  11. gilley033

    gilley033

    Joined:
    Jul 10, 2012
    Posts:
    599
    I don't get it. It seems like a state machine might server you better?

    State 1 - > Doing Stuff
    State 2 - > Waiting for local users input
    State 3 - > (Local user has provided input) Waiting for remote users input
    Once all users have provided input, go back to state 1 (but doing different stuff obviously).

    Am I not understanding right? Maybe I'm just tired, but it's not easily understand exactly what you're issue is, and looking at others responses, it doesn't appear that anyone else is comprehending this issue any better. This leads me to believe you might benefit from going over your original post and taking another crack at explaining everything. Try to look at the issue from all sides from the perspective of someone who has not dealt with it at all.

    That's just my humble opinion on what you can do to try and get better feedback. Good luck!!
     
  12. Doug_B

    Doug_B

    Joined:
    Jun 4, 2017
    Posts:
    1,084
    The problem here is that you need to find a way to switch from this procedural paradigm (such as an old DOS single threaded program would have used) to an event driven one (e.g., a windowing, GUI system or Unity).

    In the case of the former the program can, of course, be held indefinitely in F() whilst the user enters details. But, as you suggest, that won't work in a framework like Unity where functions must yield results per frame.

    To do this, you really need to arrange the code to fit the architecture of Unity rather than trying to hammer the thread-blocking pattern in.

    For example, there will be ways to split the game into a sequence of scenes. Maybe you could have a player setup scene (e.g. enter player name, avatar, racket preference etc.). Then have a tournament setup scene (e.g. total number of players, number of sets per game etc.). Then a game prep scene (e.g. allow player to select a tournament, an opponent etc.). Then the game scene itself. Now you can control the navigation between those scenes based on required user input at the appropriate times.

    Apologies if that's a bit vague but without knowing the specifics of your game flow logic, it's difficult to be more precise. :)
     
  13. ippdev

    ippdev

    Joined:
    Feb 7, 2010
    Posts:
    2,596
    You had the Unity answer. Unity is not C# nor is it C++ at the tools level. it s a component based frame dependent architecture built for rapid application development with all sorts of beautiful Monobehaviours to make your life easy once you get a grip on how it actually works.. Trying to force your pure C++ or C# paradigm on it will cause you cascading woes.
     
    VergilUa, Kiwasi and Ryiah like this.
  14. frosted

    frosted

    Joined:
    Jan 17, 2014
    Posts:
    3,039
    good lord guys, look at his code example and think through the problem. This is not remotely complex or difficult.

    This is just an ignorant programmer who is too proud to realize he needs to read some real introduction level stuff and is convinced he's a rocket scientist doing cutting edge research.
     
  15. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    11,812
    I believe this is more a case of him not properly explaining the problem and trying to directly port an existing project rather than rebuild it around Unity.

    For starters, let's touch on his explanation. He's constantly referring to user input but what he's clearly talking about is the main game loop. As you know we can't treat Update as a main method, start executing code, and only return once the program is done and ready to stop, but that's exactly how a non-engine based game would be designed.

    By directly porting (converting from C# to C++) rather than rebuilding it around the design principles of Unity he's having to jump through hoops to allow Unity to handle the main game loop as it is designed to while still letting his game function the way it was originally designed to.

    His game runs in its own thread while Unity runs in the main thread, Unity passes inputs through to the game, the game passes outputs through to Unity, and there is a very minor state system keeping them synced.
     
    Last edited: May 19, 2018
  16. ippdev

    ippdev

    Joined:
    Feb 7, 2010
    Posts:
    2,596
    It would take me 10 to 20 minutes to set up and test given the requirements for each screen or menu or what have you to move on to the next step. The simplest method would be and Update loop in a Controller component turned on only for this use. It would have a bool for each function set to true when completed. Polling in the Update would immediately disable the previous component and set active the next or if it was purely function i would just fire off the next function in sequence after the current function's bool switch flipped to true. I am dealing with somewhat similar mindsets at my day job in that i am the lone pure Unity developer versus seven C#-ers with two having dabbled in Unity. It really ticks them off they cannot just use a new constructor to create instances of my components and that they have to use Instantiate and AddComponent. It ticks me off they tried to write a layer over top of the Unity toolset that I have to go through to use my tools and deprive me of the juicy bits of kit that hundreds of Unity technicians have put in place to make my job easier with Monobehaviours.

    Once the fellow working directly with me on the lower level data structures that Unity parses and displays understood why i wanted things a certain way i got it and development went from moss growing rate to a rapid clip. The big light came on when I one day wanted the data structure to add one string to a List<> of properties to be able to extract a critical value at a matched index and could have finished that toolset in five minutes. He fussed with me that that was not the way to do it. I told him I could finish my work in five minutes if he would accommodate./ We went back and forth for 3 hours whilst he tried to convince me about the use of a windows business form paradigm but would cause the use of another parsing loop with cascading conditionals. It was one line of code on his end versus 20 on mine and a hit to the framerate. I am stubborn. He gave in. Five minutes later the toolset was on the repo completed. I reminded him of the time spent fussing about versus the reality of my claim. He said he liked the way the new data structure was working, I liked single line access to an unknown property's index with no conditionals at parsing time, so we were good to go all around. Since that day I have had no issues in getting what i need for the Unity toolkit to be used to it's greatest efficiency and most powerful and simplest elegance.
     
  17. ippdev

    ippdev

    Joined:
    Feb 7, 2010
    Posts:
    2,596
    I am dealing with this misunderstanding of jargon and the way the Unity engine works and how Unity devs refer to things. I can say something that developers here on the forum would have no issue understanding what my question or statement was about. I use the same jargon with the C#-ers and often get an argument that has nothing to do with what i am speaking to. Or..I am writing a toolkit that has maybe 80 lines of code, no Update loops and has maybe five objects, all in an inactive gameobject. If I did not mention it nobody would know it was there unless they unfolded the Hierarchy and wondered what those objects did. I did mention it as I was giving them a visualization toolkit for relationships they have been fussing about how it should work for a bit.. I saw the solution immediately as far as the mechanic on the Unity level.. however they parse the data to get to me matters not. The toolkit will be able to handle it once I understand the parsing method. It was reacted to like i had put an active function in a C# class that would affect all functionality all the time unless it was put into a #define. I argued there was no need for that as Unity would not use any of the toolkit unless i activated the gameobject and called a function from a button. This actually led to an argument where I was hung up on and the fellow had to calm down before proceeding. I suppose it is human's perennial problem of two different movies playing in two different heads.Yanny vs Laurel. I see the application in this case as a Unity app and they see it as a C# app using Unity to display it.
     
    Jingle-Fett and frosted like this.
  18. ippdev

    ippdev

    Joined:
    Feb 7, 2010
    Posts:
    2,596
    I had the data formatted to properly use the Unity toolkit and Monobehaviours. The only functions at the data structuring level were ones that massaged the data for either use by the Unity tools or for storage on the cloud. All top level mechanics and functionality remained the domain of the Unity toolkit. Being process dependent I used what i monikered a switchboard pattern in a tools controller. I would understand the order of function firing and variable setting, constructing bools to switch to the next process in the sequence. So, one function would finish and set the bool to true and cause the firing of the next process in the same frame with no delays. It would finish the process, set it's bool to true which would then trigger the next process.
     
  19. manutoo

    manutoo

    Joined:
    Jul 13, 2010
    Posts:
    256
    @Ryiah got it mostly right, with the exception that I'd be running into that issue even if I had started from scratch, because the exit & re-enter code needed to follow Unity implementation is actually awful and maintenance wise it's a mess.

    I think the sub-thread solution is very elegant and easy to implement, but I'd have been happy to get some head up from someone who already did that, especially as I have at least 3 months of work to port the engine, so I won't be able to test anything for a while and thus knowing I picked a sound solution would ease the urge to see it in action... :p

    Anyway, writing down my concept above helped me to decide it was the best course of action.

    As it raised a lot of incomprehension in the answers, I guess it means it's not as much a common issue as I thought it was. And actually thinking about it now, for me, it only happened in my tennis Tour engine ; I never got a similar issue in all other games & stuff I have been coding...

    I'm in a case where I cannot get user input early (because I don't know what input I'll need before I'm inside the process), I cannot delay the decision (because the rest of process depends on the user input) and I can't easily get out of the current nested calls : I can't just make a little state to handle the exit & re-entry of the different processing sequences, nor make everything more flat (ie: less nested calls).

    @Doug_B,
    what you describe is a classic menu flow ; fortunately, even if I'm an ignorant programmer, it's still something that I don't have issue to handle... :D

    @gilley033,
    yes, a state machine could do, but the issue here is the extreme nested function calls which makes the exit & re-entry paths especially dirty to implement & maintain.

    @ippdev,
    it's really not about C# or C++ paradigm. I have a Tour handling that takes 80'000+ of packed, complex and inter-dependent lines of code, and they have and will have nothing to do about Unity. :)
     
  20. ippdev

    ippdev

    Joined:
    Feb 7, 2010
    Posts:
    2,596
    Except you are using Unity to interact with and display the results of manipulation of those 80k+ lines of code. Does this irony escape you? You sound just like some of the folks at my work..Simply refusing to recognize the environment they are sandboxing in and according the tools the respect they deserve. Frankly..you shouldn't be using Unity. It will only get in your way with your not gonna compromise headspace.
     
    Kiwasi, angrypenguin and N1warhead like this.
  21. snacktime

    snacktime

    Joined:
    Apr 15, 2013
    Posts:
    1,592
    I've been down this road multiple times with experienced developers coming into a new environment. My advice is relax and go with the flow. Give the new paradigm a chance and put aside what you think you know for the time being. Do that and a light will go off before too long. Fight it, and it won't end well.
     
    Kiwasi, ippdev, N1warhead and 2 others like this.
  22. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    6,417
    Your nested function call should happen within the coroutine.

    The point of coroutine is that it can stop execution till the next frame and continue from where it left off without programmer going nuts. Meaning it won't block the caller.

    So you could do this from within coroutine:
    Code (csharp):
    1.  
    2. while (!gotUserInput)
    3.     yield return null; //advance to next frame.
    4.  
    What's more you can recursively dive into another coroutine, one good example being "WaitForSeconds" (https://docs.unity3d.com/ScriptReference/WaitForSeconds.html )

    This stuff can be used to script something like UI in nearly procedural fashion.

    The main drawback is that they don't serialize meaning you can't automatically hot-reload scene you're working on.

    Aside from that gotcha, when you have to request input mid-call, likely coroutines are the way to go.
     
    Last edited: May 19, 2018
  23. manutoo

    manutoo

    Joined:
    Jul 13, 2010
    Posts:
    256
    @ippdev,
    => https://en.wikipedia.org/wiki/Separation_of_concerns

    @snacktime,
    I moved to Unity paradigm years ago and I'm very happy with it. However, the current issue doesn't fit in a simple way in Unity paradigm, at least not without a ton of dirty ugly code in the middle. Or it can, but with the elegant & simple multi-threading solution exposed above.

    @neginfinity,
    coroutines don't work when their callers need their result right away ; ie: with nested functions processing data.

    I already happily used them for GUI & Input stuff, though, where the process can be easily linearized.
     
  24. Jingle-Fett

    Jingle-Fett

    Joined:
    Oct 18, 2009
    Posts:
    545
    Replying to your reply to neginfinity, then in that case wouldn't you just hold those results as variables and then any other functions can grab the results at any time? Meaning you have a bunch of variables, and the coroutines manipulate those variables (as opposed to returning the data directly). For example, if you wanted a UI that can only be interacted with when it's done fading in, have a bool called isTransitioning. At the beginning of the coroutine it's set to true, and at the end it's set to false. Other code can then just check isTransitioning to determine whether or not it can do something. User input function OpenMenu then might do something like if(!isTransitioning)//do stuff. Same thing could apply to any other kind of game mechanic, like waiting for the player to press a button to hit a tennis ball.
    Sorry if I'm misunderstanding anything!
     
  25. ippdev

    ippdev

    Joined:
    Feb 7, 2010
    Posts:
    2,596
    The little jargon fest means nothing in context. It does not alter the fact that you are dealing with a hybrid in that Unity is a frame dependent component based architecture. You seem to think you have some kind of unique code situation nobody has ever had to deal with prior...i.e. cascading functions. You have a notion that plopping this cascading function set in another thread is gonna whiz bang yer stuff and yer good to go. Funny it doesn't look that way to the experienced in many languages developers who have chimed in here and backed up what they were saying with technical explanations and expertise. You are looking like you do not want the answer..you want the answer you want..which ain't happening.
     
    Lu4e, snacktime and N1warhead like this.
  26. JohnnyA

    JohnnyA

    Joined:
    Apr 9, 2010
    Posts:
    4,502
    At some point years of experience ceases to be a relevant metric: it seems everyone here has lots of years of experience, and making appeals to authority is probably not going to get us anywhere.

    @manutoo you did a poor job of explaining your problem and its constraints and maybe you are just a little too set on thinking about the problem in a certain way. But if you are happy with your elegant solution then continuing to argue seems somewhat moot.
     
  27. manutoo

    manutoo

    Joined:
    Jul 13, 2010
    Posts:
    256
    @JohnnyA,
    I didn't come here to argue, but to ask for people to share their experience with such kind of issue. But no one who answered had that kind of issue. Everybody (except Ryiah) misinterpreted what I wrote, so I guess I didn't explain well enough. Unfortunately, I don't see how to explain it in another way, especially to people who seem to be there just to talk me down to show off their ego instead of actually trying to understand my issue. No one asked me to explain more in detail a point ; everybody assumed they totally understand my issue, thought it was super simple, despite me keep telling everybody they didn't get it and it wasn't that simple... :cool:

    And I direly hope any coder with 20 years of experience is able to use a coroutine when he needs to, and to organize a menu flow when required, and wouldn't come to ask for help about this on the Unity forum... :rolleyes:

    @Jingle-Fett,
    issue is not on the menu side, and has nothing to do with a transitioning variable...


    @ippdev,
    >> "You are looking like you do not want the answer"

    Yes, it's exactly what I have written in my 1st post. I don't want someone finding a solution to my issue. I want to know what other people did when facing that issue. Maybe next time try to read & understand before trying to "help"... ;)


    @Everybody,

    and if someone here is genuinely curious to understand what is the issue, here a last attempt to explain it with some pseudo code :

    Code (CSharp):
    1. class CTour
    2. {
    3.     void Process()
    4.     {
    5.         //... other stuff comes here...
    6.  
    7.         HandleDay();
    8.  
    9.         //... other stuff comes here...
    10.     }
    11.  
    12.     void HandleDay()
    13.     {
    14.         //... other stuff comes here...
    15.  
    16.        if (NewWeek())
    17.        {
    18.            FillTournaments();
    19.            CreateTournamentDraws();
    20.        }
    21.  
    22.         //... other stuff comes here...
    23.     }
    24.  
    25.     void FillTournaments()
    26.     {
    27.         for (int i = 0; i < m_NbPlayer; ++i)
    28.         {
    29.             ChooseTournament(m_PlayerSortedByRank[i]);
    30.         }
    31.     }
    32.  
    33.     void PickTournament(CPlayer p)
    34.     {
    35.         if (p.IsControllerByUser)
    36.         {
    37.             if (p.SelectedTournament.IsFull)       // this depends of previous player choices
    38.             {
    39.                 CGui.WaitForMenu("AskUserIfHeWantsToGoToALowerTournament");       // <= this is blocking in my current engine, showing the menu & waiting for the user to answer the question
    40.  
    41.                 if (CGui.Result == EAnswer.e_Yes)
    42.                     EnterTournament(p, p.NewlyChosenTournament);
    43.             }
    44.             else
    45.                 EnterTournament(p, p.SelectedTournament);
    46.         }
    47.         else
    48.         {
    49.             // CPU Player chooses a tournament
    50.         }
    51.     }
    52. }
    Reminder : you can't put the player selection out of the loop, as his choice is influenced by and influences the CPU player choices.
    Real case has much, much more nested functions.
    Real case has many, many places like this where it's required to show a menu.
    Real case will get new menus to show in the future when adding functionalities.

    PS: at this point, it's not about me asking for sharing your experience, it's about me to see if I can make understand at least one person what the problem actually is... :p
     
    Last edited: May 20, 2018
  28. Doug_B

    Doug_B

    Joined:
    Jun 4, 2017
    Posts:
    1,084
    Hi manutoo,

    From your more detailed pseudo code there, it turns out I did understand exactly what you are asking. I refer you again to my earlier response above which exactly answers your question and is in keeping with your new pseudo code.

    What I do not understand is how you do not understand the answer I gave you (and has subsequently been followed up by a number of others).

    As a summary of what has been discussed above, you have 2 ways to progress:
    1. A developer of your skill will be able to shoe-horn your 80+k lines of single-threaded code into Unity. But it will be difficult and require jumping through a number of hoops to do so.
    2. You could create a game architecture that fits the Unity framework. Then reuse subroutines and assets from your original game where appropriate / possible.
    If you are looking for a 3rd option, then when you find it maybe you could come back and enlighten us about it. I, for one, wish you all the best in your endeavours. :)
     
  29. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    6,417
    You can't have a result right away, when user input is involved. It is an async call, because a user can have their sweet time responding to it.

    To return data from coroutine see:
    https://answers.unity.com/questions/24640/how-do-i-return-a-value-from-a-coroutine.html


    Getting back to the question:
    This is not exactly a good design even outside of unity.In case of unity it screams for coroutine.

    Make coroutine a class, and return result of calculation through it.

    I'm not sure why you keep rejecting this approach. If you need to "immediate" result within coroutine, just do normal function call without yield.

    Within the context of original question, you want a state machine, and C# enumerator provides it.

    So you'd have a monobehavior that launches coroutine upon starting, and the coroutine would handle your whole game logic.
    Code (csharp):
    1.  
    2. IEnumerator coroutineHandler(){
    3.      yield return setupMenus();
    4.      yield return handleUserInput();
    5.      yield return cleanup();
    6. }
    7.  
    Where, for example,. setupMenus() could spawn gameObjects, handle user input would wait for the user to react to it, and cleanup() would clean it up. Very short, very simple, very clean approach.
     
    VergilUa and Kiwasi like this.
  30. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    6,417
    Here you go. Coroutine approach, pseudocode:
    Code (csharp):
    1.  
    2. class Gameplay{
    3.     IEnumerator requestUserInput(){
    4.         GameObejct menuDialog = spawnMenu("Question", "Yes", "No");
    5.         while (!menuDialog.gotResponse){
    6.             yield return null;
    7.         }
    8.         doStuffWithResponse();
    9.         cleanup(menuDialog);//Destroy
    10.     }
    11.     IEnumerator gameLoop(){
    12.         spawnGuiObjects();
    13.         while(!gameTerminated){
    14.             if (newWeek()){
    15.                 while(!tournament.full){
    16.                     yield return requestUserInput();
    17.                 }                          
    18.             }
    19.             yield return runTournament();          
    20.         }
    21.     }
    22.     void Start(){
    23.         StartCoroutine(gameLoop())
    24.     }
    25. }
    26.  
     
    Jingle-Fett likes this.
  31. Jingle-Fett

    Jingle-Fett

    Joined:
    Oct 18, 2009
    Posts:
    545
    The example I gave in my post with the transition variable is about how you can make a coroutine process a variable, and then use that variable as a condition to decide whether to proceed or not. This solves the problem you mentioned of:
    Here's a coroutine based pseudo code example of what you could do with your PickTournament function:
    Code (CSharp):
    1.  
    2.     public class CPlayer
    3.     {
    4.         public bool isControlledByUser;
    5.         public bool selectedTournamentFull;
    6.     }
    7.  
    8.     IEnumerator PickTournament(CPlayer p)
    9.     {      
    10.         StartCoroutine("CheckControlledByUser", p);
    11.         StartCoroutine("IsTournamentFull", p);
    12.         while (true)
    13.         {
    14.             if (p.isControlledByUser)
    15.             {
    16.                 if (p.selectedTournamentFull)
    17.                 {
    18.                     //do stuff
    19.                 }
    20.             }
    21.         }
    22.     }
    23.  
    24.     //this coroutine checks if p.isControlledByUser should be true or false
    25.     IEnumerator CheckControlledByUser(CPlayer p)
    26.     {
    27.         while (true)//use whatever conditions you want to end the loop
    28.         {
    29.             if (Input.GetButtonDown("Fire1"))//use whatever you want to determine if true or false
    30.             {
    31.                 p.isControlledByUser = true;                
    32.             }
    33.             yield return null;
    34.         }
    35.     }
    36.  
    37.     //this coroutine checks if tournament is full. It is dependent on whether isControlledByUser is true or false
    38.     IEnumerator IsTournamentFull(CPlayer p)
    39.     {
    40.         while (true)
    41.         {
    42.             if (Input.GetButtonDown("Fire1") && p.isControlledByUser)
    43.             {
    44.                 p.selectedTournamentFull = true;              
    45.             }
    46.             yield return null;
    47.         }
    48.     }
    49.  
    50.  
    51.  
     
  32. manutoo

    manutoo

    Joined:
    Jul 13, 2010
    Posts:
    256
    @Doug_B,
    actually, the 1) is easy, it's the ~10 lines of code exposed in my solution in the 1st post. And done.
    So now why should I break into thousand pieces a code that works decently well & that provides a logical flow easy to follow ?
    And how future code would be easier to develop & maintain if broke into thousand of pieces instead of just adding my 10 lines of code that will work once and for all for existing & future code ?
    It's especially true as I may not know in advance what parts will need to be broken into pieces, as I may think of new functionalities later on (eg: adding stuff from user feedback).

    For me, it's now ultra obvious the sub-thread solution is a far superior solution. I wasn't totally convinced when I thought about it at 1st, so I can understand that a seasoned developer might need a little moment to wrap his head around it, but the total negation I met here is rather surprising... :confused:

    @neginfinity,
    your example totally lost the functionality needed & exposed in my little example.

    @Jingle-Fett,
    same than neginfinity but you even didn't understand how the pseudo code works at 1st... :p (actually, I'm not sure neginfinity got it, but I let him the benefice of the doubt :D )
    And for the global picture, see my answer above to Doug_B .
     
  33. AndersMalmgren

    AndersMalmgren

    Joined:
    Aug 31, 2014
    Posts:
    2,441
    So you want to force a Square (your 80k lines of unmaintainable mess) through a circle (Unity)? Good luck with that
     
  34. Doug_B

    Doug_B

    Joined:
    Jun 4, 2017
    Posts:
    1,084
    Glad you have a solution you are happy with. :)
     
  35. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    6,417
    I give you a general idea. To adapt it fully, you'll have to do some work yourself.

    The general idea is that boundaries of Update() method should be replaced by yield return. In situation where you call a menu, the functionality of menu is handled within sub-corutine.

    The example was meant to demonstrate that.

    Yes, you can move player selection anywhere.
    Having more nested functions is not a problem. It is standard refactoring work.
    Many, many, many more places where it is necessary to show a menu will be automatically transform into "ShowMenu()" coroutine/sub-coroutine. It will be actually quite elegant with coroutine approach. In any part of coroutine where you need to show a menu you'll simply make a call.
    And I don't see how more menus will be a problem.
    Code (csharp):
    1.  
    2. var playerMenu = new PlayerMenu("title", "Choice1", "Choice2");
    3. yield return playerMenu.run();
    4. if (playerMenu.choice == ....)
    5.  
    At this point I suspect you'll be happier hiring a freelancer to work on your exact specific scenario. IIRC, spawning separate thread can actually lead to trouble if you start messing with gameobjects with it, because many portions of api are not expected to be thread safe.
     
  36. AndersMalmgren

    AndersMalmgren

    Joined:
    Aug 31, 2014
    Posts:
    2,441
    I would use a custom yield instruction instead. I have done just that in our UI, it looks like this

    Code (CSharp):
    1.         private IEnumerator ShowSwitchTeam()
    2.         {
    3.             var confirm = ShowConfirm(AvatarController.Instance.IsDead() ? "Switch team?" : "You are not dead, you will lose a life. Continue?");
    4.             yield return confirm;
    5.  
    6.             if (confirm.Result == ConfirmResult.OK)
    7.             {
    8.                 BaseGameMode.Instance.RequestSwitchTeam(Networking.PrimarySocket.Me.NetworkId, false);
    9.             }
    10.         }
     
  37. spiderpk

    spiderpk

    Joined:
    Jul 26, 2017
    Posts:
    1
    Unless I'm missing something the issue at-hand is there is some code running in a background thread that needs to wait for user input, and there is a desire to limit change to the already-written code.

    I'm still relatively new to Unity, but in a non-Unity application I would typically use dependency injection to solve this problem. I look at the case of "I need user interaction" as a cross-cutting concern, and I typically use service location for cross-cutting concerns - right or wrong, this approach lets me easily separate concerns while compromising a little bit on visibility of dependencies:


    Code (CSharp):
    1. public interface IUserInteraction
    2. {
    3.     string GetResponse(string question);
    4. }
    In my UI-dependent background thread I'd just get a handle to the "user interaction service" and use it - the calls would block (assuming this method is running on a background thread):


    Code (CSharp):
    1. private object MyNestedCoreProcess()
    2. {
    3.     object ret = null;
    4.     IUserInteraction userSvc = ServiceLocator.LocateService<IUserInteraction>();
    5.  
    6.     string userResponse = userSvc.GetResponse("Should we continue?");
    7.  
    8.     //do stuff based on userResponse and assign ret
    9.  
    10.     return ret;
    11. }
    The "user interaction service" would be responsible for sleeping the background thread while waiting for a response on the main thread. This is where my lack-of-knowledge in Unity shows, but based on a little googling one suggested approach is to have some sort of state/flag that indicates when a co-routine should be started:


    Code (CSharp):
    1. public string GetResponse(string question)
    2. {
    3.     if (this.MainThread == Thread.CurrentThread)
    4.     {
    5.         throw new InvalidOperationException("Blocking methods must be called from a background thread!");
    6.     }
    7.  
    8.     this.StartTime = DateTime.Now.Ticks;
    9.     this.Question = question;
    10.  
    11.     //Background thread is blocked until response is received or it times out
    12.     while (this.Response == null)
    13.     {
    14.         long now = DateTime.Now.Ticks;
    15.  
    16.         if (now - this.StartTime < 50000000)
    17.         {
    18.             Thread.Sleep(10);
    19.         }
    20.         else
    21.         {
    22.             this.Response = "no response";
    23.             break;
    24.         }
    25.     }
    26.  
    27.     return this.Response;
    28. }
    29.  
    30. private IEnumerator GetResponseCoRoutine(string question)
    31. {
    32.     btnRespond.gameObject.SetActive(true);
    33.     btnRespond.GetComponentInChildren<Text>().text = question;
    34.  
    35.     while (true)
    36.     {
    37.         if (this.Response == null)
    38.             yield return null;
    39.         else
    40.         {
    41.             btnRespond.GetComponentInChildren<Text>().text = "Respond";
    42.             btnRespond.gameObject.SetActive(false);
    43.             this.Question = null;
    44.             yield break;
    45.         }
    46.     }
    47. }
    48.  
    49. public void Update()
    50. {
    51.     if (this.Question != null)
    52.     {
    53.         StartCoroutine(GetResponseCoRoutine(this.Question));
    54.     }
    55. }
    I would clean up the above code for "real" use (not the least of which would be correctly addressing thread safety), but hopefully it gets the point across.

    One other approach in Unity 2017 would be to use TAP - it would help simplify the code in the service:


    Code (CSharp):
    1. public async Task<string> GetResponseAsync(string question)
    2. {
    3.     btnRespond.gameObject.SetActive(true);
    4.     btnRespond.GetComponentInChildren<Text>().text = question;
    5.  
    6.     string ret = await Task.Run(() =>
    7.     {
    8.         this.StartTime = DateTime.Now.Ticks;
    9.  
    10.         //Block until response is received or it times out
    11.         while (this.Response == null)
    12.         {
    13.             long now = DateTime.Now.Ticks;
    14.  
    15.             if (now - this.StartTime < 50000000)
    16.             {
    17.                 Thread.Sleep(10);
    18.             }
    19.             else
    20.             {
    21.                 this.Response = "no response";
    22.                 break;
    23.             }
    24.         }
    25.  
    26.         return this.Response;
    27.     });
    28.  
    29.     btnRespond.GetComponentInChildren<Text>().text = "Respond";
    30.     btnRespond.gameObject.SetActive(false);
    31.  
    32.     return ret;
    33. }
     
  38. frosted

    frosted

    Joined:
    Jan 17, 2014
    Posts:
    3,039
    I answered the problem here "if the call is occurring outside of the main thread then you can simply sleep until your state is switched".

    I answered again here:
    The problem was that @manutoo didn't understand "while( state ) sleep;" or other methods of thread notification.
    This is why when he discovered WaitHandle.WaitOne() the question was more or less solved.

    Code (csharp):
    1. if (p.SelectedTournament.IsFull)       // this depends of previous player choices
    2. {
    3.     CGui.OpenMenuNonBlocking("AskUserIfHeWantsToGoToALowerTournament");    
    4.     //----------------------
    5.     // SOLUTION IS THIS LINE
    6.     //----------------------
    7.     WaitForUserResponse.WaitOne();
    8.     //----------------------
    9.     // ALTERNATE SOLUTION IMPLEMENTATION
    10.     //----------------------
    11.     while( CGui.Result == EAnswer.e_Unspecified ) Thread.sleep(1);
    12.     // END OF SOLUTIONS
    13.  
    14.     if (CGui.Result == EAnswer.e_Yes)
    15.         EnterTournament(p, p.NewlyChosenTournament);
    16. }
    17.  
    The problem in this thread is that I assumed that @manutoo was asking a more complex question than "how do I sleep a thread" and he apparently didn't understand my answers since they were overly brief (since I assumed his question had to be more complex than this).

    In sum, when I said, "If the call is occurring outside of main thread then you can simply sleep until your state is switched." he should have said, "Oh this is what I want to do, how do I implement that?"

    Instead he rambled about how skilled he was and assumed I didn't understand multithreading. It was an example of dunning krueger in action.

    Finally, it's worth noting that if @manutoo is still creating an additional thread (when his calling code here is already running on it's own thread (which it must be in order to block while waiting for user input)) then he's still doing it wrong.
     
    Last edited: May 20, 2018
  39. Jingle-Fett

    Jingle-Fett

    Joined:
    Oct 18, 2009
    Posts:
    545
    No, I understood your pseudocode. But this isn't the scripting section of the forum. The example I gave was to show the general idea of the solution, its your job to figure out how you can adapt the solution your specific code. Your original question was how others deal with nested functions that depend on the result of the next one. Coroutines are a way to do that and I showed it in the example I gave.
     
  40. ShilohGames

    ShilohGames

    Joined:
    Mar 24, 2014
    Posts:
    1,966
    I have over 30 years coding experience. The first thing I do when building a solution is try to make it simple. Both simple to build and simple to maintain. Anytime code feels like the "middle of that giant mess", it is time to refactor the code in a more simple way.

    For input systems in Unity, I don't have a bunch of input code scattered throughout many different classes, because that tends to cause trouble once a project grows. I always set up an input class and then attach that class to an object in the scene. For example, I will often have a class called InputLayer.cs and that is the only class that accepts input. All of the other classes that react to input will have a reference to that class. My InputLayer class will have public variables exposed for all of the actions the input will trigger.

    If I need a user customizable input scheme in a Unity project, I will use Rewired and then my InputLayer class will get input data from Rewired and then expose the actions through public variables. All of my other classes will still get input from my InputLayer class. None of my other classes will get input data directly.

    Also, I do not attach my InputLayer class to my player controller object, because the player controller will get disabled during menus and transitions.
     
  41. manutoo

    manutoo

    Joined:
    Jul 13, 2010
    Posts:
    256
    @AndersMalmgren,
    it's a mess but it's maintainable, and I intend to keep it that way... :rolleyes:

    @Doug_B,
    thanks, and I'll report the result in this topic once it's done in a few months, especially if I was wrong and something went horribly bad... :p

    @neginfinity & @Jingle-Fett,
    I don't need the general idea about how to use coroutines ; I already used them in the past and will do again every time they are the right tool for the situation.
    But here the point would to show that using coroutine doesn't create horrible code hard to maintain. Which you didn't do because even with so short code, it's already inconvenient to use, so with a bit of imagination, it's not hard to conclude that with 80k+ lines of intricate code, it'd be way more nightmarish.

    @spiderpk,
    as you have StartCoroutine() in the Update(), it means you have mixed up something and not achieve the needed functionality, which is to block the sub-thread (a Coroutine isn't blocking, it's its point :) )...

    @frosted,
    my question was not about the details of the implementation (I know how to find & read a tutorial ;) ), but about the possible unforeseen consequences of this choice, especially maintenance wise. (3rd or 4th time I write this in this topic o_O )

    And I didn't assume you didn't understand multi-threading ; I just known you didn't understand what I was asking, which you just shown 1 more time...

    @ShilohGames,
    I do similar things as well as it's 101 of coding, so it's really not the issue in this topic.

    About level of complexity, it's inevitable and I don't need a lecture from someone who has no idea what is in my code.

    @Everybody,
    little game : there's something very important (and very short) that is missing maintenance-wise in the pseudo-code solution in the 1st topic. Will you be able to find it..? :D
     
    Last edited: May 21, 2018
  42. AndersMalmgren

    AndersMalmgren

    Joined:
    Aug 31, 2014
    Posts:
    2,441
    Just a side note, the service locator pattern is a anti pattern. Constructor injection is the way togo, for that we need to be able to control how objects are instanced in Unity, sadly I dont think its possible with Unity since they control when the objects are created.
     
  43. jasonxtate66

    jasonxtate66

    Joined:
    Nov 21, 2017
    Posts:
    119
    It seems like it would be easier to just re-do the project in Unity if you really want to port the project to Unity, using a lot less line of codes and using it's built-in abilities and functions... along with porting over your existing assets. If it is originally 100's of lines of C++ coding, a lot of it can be simplified using Unity (not just the C# language). I don't think I fully understand either.
     
  44. manutoo

    manutoo

    Joined:
    Jul 13, 2010
    Posts:
    256
    @jasonxtate66,
    the 80k+ lines of code are only the Tour engine and aren't system dependent so they don't care on what they are running (as long as I can request the user input in the middle of them).

    I also have thousands & thousands lines of code for my system & game libraries, and I'll be happy to toss a good part of them to be replaced by Unity engine API calls. :)
     
  45. jasonxtate66

    jasonxtate66

    Joined:
    Nov 21, 2017
    Posts:
    119
    If you have 80k+ lines of C++ code, why would you try to adapt that to Unity without it being a nightmare? Just curious.
     
    VergilUa and angrypenguin like this.
  46. AndersMalmgren

    AndersMalmgren

    Joined:
    Aug 31, 2014
    Posts:
    2,441
    Sounds like you have a design problem. Our entire games input ends up at the same place, a abstract interface (Strategy pattern). Anywhere in our domain we can access a instance of this abstraction.

    But it sounds like this touring engine contains the rules and then it also is bad separation of concerns to mix it up with user input
     
  47. manutoo

    manutoo

    Joined:
    Jul 13, 2010
    Posts:
    256
    @jasonxtate66,
    it's just much faster to port than to rewrite everything from scratch. I ported many things in my career and it's something I'm comfortable to do.

    The nightmarish part would be to force the use of CoRoutines where they don't belong.
    The headache part would be to force exit & reentry to grab my user input.

    But the sub-thread solution avoids all that.

    @AndersMalmgren,
    so how would you change the pseudo-code exposed above to avoid requesting user input in the middle of the Tour processing : https://forum.unity.com/threads/req...-inside-the-game-process.532010/#post-3503138 ?
     
  48. AndersMalmgren

    AndersMalmgren

    Joined:
    Aug 31, 2014
    Posts:
    2,441
    In your orginal code example from first post you had a Monobehaviour that requested a menu in the RequestUserInput() method. Now it seems its not a monobehaviour but the actual touring code?

    If I understand this correctly CGui.WaitForMenu will land in CLR land and display a menu inside Unity, so if this is correct I would just make sure your Cpp Touring engine runs on a seperate thread. The CLR code that triggers when CGui.WaitForMenu triggers should look something like this (pseudo-code)

    Code (CSharp):
    1. public class CppToCLRInterop
    2. {
    3.  
    4.    private AutoResetEvent sync = new AutoResetEvent();
    5.  
    6.    public void OpenMenu()
    7.    {
    8.        DisplayUnityMenu(); //This need to trigger menu display on Unity thread, it can be done by putting the menu request on a Queue and in a Monobehaviour Update display the menu
    9.       sync.WaitOne();
    10.    }
    11.  
    12.    //This methods is called from Unity thread when Menu input is done
    13.    public void Continue()
    14.    {
    15.       sync.Set();
    16.    }
    17.  
    18. }
    Above code is super simplified

    edit: I want to point out that blocking code like CGui.WaitForMenu("AskUserIfHeWantsToGoToALowerTournament"); is a thing of the past. The modern way is async programming, but since OP does not want to change his code this is the only solution
     
    Last edited: May 21, 2018
  49. manutoo

    manutoo

    Joined:
    Jul 13, 2010
    Posts:
    256
    @AndersMalmgren,
    ok, so you actually agree with my structural organization, so no need to call for judgment too fast... ;)
     
  50. AndersMalmgren

    AndersMalmgren

    Joined:
    Aug 31, 2014
    Posts:
    2,441
    See my edit