- Home /
coroutines : trouble editing and accessing the same public variable
Hi,
Before I go into detail, let me pose what I THINK my question is:
Is it correct to access and edit public generic lists with multiple running instances of a single coRoutine? My script seems to update said variables, but when reading from them they act like they aren't updated all the way, or at all.
I'm trying to use a generic list as an active dynamic list of objects that should be EXCLUDED from a functions operation.
So in rough psuedo code:
coRoutine (){
for (gameObject in bigList){
if(gameObject is not in excludeList){
DoStuff();
}
}
Since I'm not using UPDATE at ALL, I start a few of these coroutines in the START function, and then they call themselves at the end to continue the simulation.
I'm wondering if they aren't updating because these functions aren't being run from Update?
Can anyone shed any light on this list or why its not working correctly? Or a better way to go about this?
I thought maybe assigning a simple script with a public variable to each gameObject that can move that has a boolean, that signifies if it is "excluded" or not. Maybe that would update with coRoutines?
Any help much appreciated!
This is my first question, so let me know if I can be more clear in any way :)
}
EDIT:
Apologies for the lack of initial info! The code is long, hopefully the below snippet is enough to give you an idea of what I'm trying to do.
Several cubes, or gameObjects are sliding around in a 2d plane (similar to those 2d sliding puzzle games). Part of what I want is to have multiple empty spaces and multiple cubes moving around to empty spots independently of each other, but when I have more than 1 empty spot, and there for more than 1 cube moving at a time they tend to conflict and move to the same spot and overlap.
The below code in conjunction with some if statements in other functions were meant to check if an empty spot was already chosen( a member of "empties_CheckedOut") as an available empty spot and to exclude it from the list of possible empty spots to choose from.
Declaring the initial empty list of empty spots:
private var empties_CheckedOut = new List.<Vector3>();
Regular Function used for "Checking out" an empty spot:
//--------------Function used for "checking OUT" an empty spot. means it cant be used again until it is checked back in.-----------------//
function CheckOutEmpty(item : Vector3) : ArrayList {
var trueFalsePass : boolean;
var genericReturnList = new ArrayList();
trueFalsePass = false;
if(empties_CheckedOut.IndexOf(item) == -1){
empties_CheckedOut.Add(item);
trueFalsePass = true;
//print("Checked OUT empty: " + item);
}
genericReturnList.Add(trueFalsePass);
genericReturnList.Add(item);
return genericReturnList;
}
Regular Function used for "Checking in" an empty spot:
//--------------Function used for "checking IN" an empty spot. means it can be checked out and used at any time after this.-----------------//
function CheckInEmpty(item : Vector3) : ArrayList {
var trueFalsePass : boolean;
var genericReturnList = new ArrayList();
trueFalsePass = false;
if(empties_CheckedOut.IndexOf(item) != -1){
empties_CheckedOut.Remove(item);
trueFalsePass = true;
//print("Checked IN empty: " + item);
}
genericReturnList.Add(trueFalsePass);
return genericReturnList;
}
Once a start and end position is computed, this coRoutine does the actual moving of the game object:
function SmoothMove (objToMove : Transform, startpos : Vector3, endpos : Vector3, seconds : float) { // smooth move, pass obj, new pos, and seconds for move.
var t = 0.0; // set time counter to 0
var randToAdd : float;
randToAdd = Random.Range(cubeSpeedRandomAmt*-1,cubeSpeedRandomAmt);
seconds += randToAdd;
var tmpTransform : Transform = objToMove; // grab the transform argument to variable.
while (t <= 1.0) { // loop for Lerping from old, to new location. yields to update viewport.
t += Time.deltaTime/seconds; // use deltatime to regulate speed across devices.
tmpTransform.localPosition = Vector3.Lerp(startpos, endpos, Mathf.SmoothStep(0.0, 1.0, t)); // uses smoothStep to easeIn/Out
yield;
}
var checkinMoverResult = CheckInMover(objToMove);
var checkinEmptyResult = CheckInEmpty(endpos);
var checkoutEmptyResult = CheckOutEmpty(startpos);
yield WaitForSeconds(cubePause);
DoIt();
}
This is kind of a wrapper function, this gets called from smoothMove at the end to keep the sliding process going once started in "Start":
function DoIt(){
CodeProfiler.Begin("doit:Update");
var pickEmptyResult = PickEmptySpot(cubeChildrenInitPositionsFull);
//print(pickEmptyResult.Count);
//print("Chosen empty spot: " + pickEmptyResult[0]);
var pickMoverResult = PickMoverCube(pickEmptyResult[0]);
//print("Chosen mover cube: " + pickMoverResult);
if(pickMoverResult == GameObject.Find("breakObject").transform){
//print("doing nothin");
}
else{
SmoothMove(pickMoverResult, pickMoverResult.localPosition, pickEmptyResult[0], cubeSpeed);
}
CodeProfiler.End("doit:Update");
}
I use the start function here to kick off however many empty spot / cube movers as desired. The idea is that once started, the coroutines feed themselves the necessary info to keep going.
function Start () {
while(droppedCubesSpawnCounter > 0){
DoIt();
droppedCubesSpawnCounter -= 1;
yield WaitForSeconds(cubePause);
}
}
I could do with seeing your actual code. $$anonymous$$odifying the same collection in more than one instance of a coroutine could cause all sorts of weirdness with the foreach iterator I'd guess... But do you mean that one object can have the same coroutine running more than once? Or do you mean that multiple objects have a single instance of the coroutine (there's absolutely no problem with that at all)?
I pretty much always do what you are doing with my code - I rarely write an update function as the state switching ability of a coroutine is far more powerful. There are no fundamental issues with this.
Re-read your question - if they are writing to the same collection, and it really is public then you should only have a problem if you yield and the iterator is subsequently passing over a different updated version of the collection.
e.g.
foreach(var t in someList)
{
Debug.Log(t.name);
yield return null;
}
Will fail if the someList is modified by anything during the yield function - you would get an exception about the collection being modified in the console window.
The normal approach for this is to copy the list to some local array and iterate this (because this doesn't allocate memory) or if you don't care about the GC then do this having included System.Linq or the like:
foreach(var t in someList.ToList())
I have implemented a Bit$$anonymous$$ask to deter$$anonymous$$e if blocks of each type (up to 32 types) are to be included in the shuffle bag.
Its quick and relatively simple and the best thing about it is that the total on/off config is defined by one single integer value.
Bitwise ops are pretty quick :)
Updated with more code!
the actual coroutine doesn't directly edit the public list, but it calls a function that does. $$anonymous$$aybe unneccesary / part of the problem?
Answer by enviralDesigns · Nov 25, 2013 at 09:05 AM
Problem fixed!
Thanks for all the suggestions and help. Through a different route I came up with several days break from this I managed to troubleshoot the problem down to my own logic.
There were no problems updating variables, I just was checking in / checking out things incorrectly causing overlap.
Answer by $$anonymous$$ · Nov 21, 2013 at 01:21 AM
For simulation, you probably want to look into putting your logic into Update() or FixedUpdate() functions and using Time.deltaTime or Time.fixedDeltaTime for computing incremental updates to your objects.
Co-routines could probably work, but what's the reason why you're trying to use coroutines?
Some code would also help identify the problem you're having with coroutines, pseudo code would not help if you're having a specific issue and not seeing your coroutine fire.
The reason to use coroutine is that state based logic reads much more logically in a coroutine than in an Update - simply because you can localise where the code is executing rather than having to switch or if your way through a block of code to get to where the actual instructions for the current state will execute.
A coroutine will read (and perform) like a normal program for sequences etc, it's much less error prone normally. A coroutine can be made to act like a fixed update by doing yield return new WaitForFixedUpdate().
I chose coRoutine because I felt like it would make a $$anonymous$$imal impact on performance as possible.
some of the heavier lifting happens when the code checks for available empty spots in the grid, and available mover cubes in the grid.
So perfor$$anonymous$$g this at the end of a move seemed a logical way to optimize that ins$$anonymous$$d of at fixed intervals.
Answer by aldonaletto · Nov 21, 2013 at 02:01 AM
Lists are .Net/Mono stuff, and have nothing to do with Update. A possible cause is the coroutine being started before its previous instance has finished: this lets two or more coroutines running in parallel, and changes made by a coroutine instance may be undone by another instance. Replacing Update with a coroutine usually is done this way:
function Start(): IEnumerator {
while (true){
// do stuff here
yield; // wait for next frame
}
}
Notice that only one coroutine instance is created (the Start function itself), and runs forever in an endless loop. This eliminates any possibility of multiple coroutines running at the same time, and also allocates memory only once. If you start a coroutine every frame, however, you'll get lots of coroutines running in parallel (and lots of blocks allocated in the heap). Anyway, post your Start function to show what exactly you're doing.
I believe I wasn't totally clear in my original post, the co routine itself does not edit the public list, but it calls a function that makes the necessary edit.
there are multiple co routines happening in parallel, when one instance ends ,it fires off a function that starts itself again.
The idea was for the sim to feed itself.
Answer by Tarlius · Nov 21, 2013 at 02:52 AM
This may be a simple oversight in the pseudo code provided, but you do have a loop in your coroutine... right? Because otherwise you'll just run through the list once and then the coroutine will "finish" and not be called again...
coRoutine (){
while(true) { // <- this
for (gameObject in bigList){
if(gameObject is not in excludeList){
DoStuff();
}
}
yield return whatever; // and this
}
}
I updated the code and such to be more clear, but the co routine calls a wrapper function that calls the co routine after some initial data is calculated.
I thought that that might be the case, but better safe than sorry! I've seen quite a few questions with such simple answers ;)
I think your your problem is that you are nesting coroutines without using yielding a new coroutine
ie: when you call Smooth$$anonymous$$ove, which is a coroutine, you are calling it as a function, not a coroutine. I'm not sure about the syntax in UnityScript, but I know that can cause lots of fun problems in C#.
Perhaps you need to yield DoIt()
and yield Smooth$$anonymous$$ove()
or something? Again, not up on the UnityScript syntax, but in C# if you don't do yield StartCoroutine(DoIt());
it won't end well...
Don't think that this is true in JS, I believe that is automatic.