I'm trying to make Conway's Game of Life in Skyrim. I had it working, but I decided to break it and try again because it was (predictably) running very slowly. My reworked version seems to run WAY faster (the initialization process went from taking about 5 minutes to about 15 seconds). The problem is that I'm using an integer to track when the entire array of objects is ready before moving on.
So, the game...
The game is an array of lights (I've got 25x25) which are either on or off. They simultaneously turn on and off based on how many of their neighbors are currently on. So, within a single iteration, changes should to their neighbors should not be considered.
The setup...
At the moment, I've got the array of lights. Each light is a LinkedRef of a button directly below it. Each button has an identical script. Controlling the whole thing is another button off to the side. This button has a different script with variables that the various light-linked buttons increment.
The problem...
The main script has integer variables meant to track how many of the lights' scripts are ready for the next step. When a light is ready, it increments the relevant counter on the main script. When that number is equal to the number of lights, the script should move on. However, instead, the number freezes. Some of the lights have tried to increment the main script's integer at the same time, and it only registered one of them (this is my guess, at least).
Is there a way to do this incrementing in a safe way so each light can tell the main script it's ready? I don't want the main script to iterate through the lights, and I can't use a standard array because there are 625 lights. I could potentially use an expanded array thanks to SKSE, but I'd rather avoid it if possible (1: I've never used those functions, and while I imagine they'd be very simple, I don't know that for sure; 2: Using an array will require iterating through that array, which, while probably faster than a FormList, is still much slower than the current method).
So, all I really need (if possible) is a way to make 100% certain that when ScriptA tries to increment ScriptB.Var it's registered and not lost due to high increment rates.
Scripts (top script is the main one, bottom one goes on each light's linked button):
Scriptname CGLRunGameScript extends ObjectReference Hidden ObjectReference Property CGL_ActivateButton AutoObjectReference Property CGL_TrackingLight AutoFormList Property CGLLightFormList AutoFloat Property Delay = 1.0 AutoInt Property Ready = 0 Auto HiddenBool Property Running = False Auto HiddenBool Property Priming = False Auto HiddenInt Property Primed = 0 Auto HiddenInt Property Finished = 0 Auto HiddenInt LightCountEvent OnInit() LightCount = CGLLightFormList.GetSize() While Ready < LightCount Debug.Notification(Ready + "/" + LightCount) Utility.Wait(3.0) EndWhile Debug.Notification("Game is ready.")EndEventEvent OnActivate(ObjectReference akActivator) Running = !Running If Running Debug.Notification("Starting game...") CGL_TrackingLight.Enable() IterateGame() Else UnregisterForUpdate() Debug.Notification("Stopping game...") EndIfEndEventEvent OnUpdate() If Running IterateGame() EndIf If Running RegisterForSingleUpdate(Delay) Else CGL_TrackingLight.MoveTo(Self, afYOffset = 64, afZOffset = 128.0) CGL_TrackingLight.Disable() ;Debug.Notification("Game stopped.") EndIfEndEventFunction IterateGame() Priming = True Primed = 0 Finished = 0 CGL_ActivateButton.Activate(Self) While Running && Primed < LightCount Utility.Wait(0.5) EndWhile Priming = False While Running && Finished < LightCount Utility.Wait(0.5) EndWhileEndFunction
Scriptname CGLRunGameButtonScript extends ObjectReference Hidden Bool Property LightState = False Auto HiddenObjectReference Property myLinkedRef Auto HiddenInt Property ActiveNeighbors = 0 Auto HiddenInt Property NextNeighbors = 0 Auto HiddenCGLRunGameScript GameRefObjectReference LightRefCGLRunGameButtonScript LeftCGLRunGameButtonScript TopLeftCGLRunGameButtonScript TopCGLRunGameButtonScript TopRightCGLRunGameButtonScript RightCGLRunGameButtonScript BottomRightCGLRunGameButtonScript BottomCGLRunGameButtonScript BottomLeftEvent OnInit() LightRef = GetLinkedRef() as ObjectReference LightRef.Enable() GameRef = Game.GetFormFromFile(0x00000DCA, "CGL_Conway's Game of Life.esp") as CGLRunGameScript FormList LightList = GameRef.CGLLightFormList Int SideLength = Math.Sqrt(LightList.GetSize()) as Int Int Index = LightList.Find(Self) Int XPos = (Index % SideLength) + 1 Int YPos = Math.Floor(Index/SideLength) + 1 Int LeftPos = XPos - 1 Int TopPos = YPos + 1 Int RightPos = XPos + 1 Int BottomPos = YPos - 1 If LeftPos == 0 LeftPos = SideLength EndIf If RightPos > SideLength RightPos = 1 EndIf If TopPos > SideLength TopPos = 1 EndIf If BottomPos == 0 BottomPos = SideLength EndIf Left = LightList.GetAt(CoordToIndex(LeftPos, YPos, SideLength)) as CGLRunGameButtonScript TopLeft = LightList.GetAt(CoordToIndex(LeftPos, TopPos, SideLength)) as CGLRunGameButtonScript Top = LightList.GetAt(CoordToIndex(XPos, TopPos, SideLength)) as CGLRunGameButtonScript TopRight = LightList.GetAt(CoordToIndex(RightPos, TopPos, SideLength)) as CGLRunGameButtonScript Right = LightList.GetAt(CoordToIndex(RightPos, YPos, SideLength)) as CGLRunGameButtonScript BottomRight = LightList.GetAt(CoordToIndex(RightPos, BottomPos, SideLength)) as CGLRunGameButtonScript Bottom = LightList.GetAt(CoordToIndex(XPos, BottomPos, SideLength)) as CGLRunGameButtonScript BottomLeft = LightList.GetAt(CoordToIndex(LeftPos, BottomPos, SideLength)) as CGLRunGameButtonScript LightRef.Disable() Utility.Wait(Utility.RandomInt()/100) GameRef.Ready += 1EndEventInt Function CoordToIndex(Int XPos, Int YPos, Int SideLength) Return ((XPos - 1) + SideLength * (YPos - 1))EndFunctionEvent OnActivate(ObjectReference akActivator) If GameRef.Running && akActivator != Game.GetPlayer() ActiveNeighbors = NextNeighbors GameRef.Primed += 1 While GameRef.Priming Utility.Wait(0.5) EndWhile If ActiveNeighbors == 3 && !LightState EnableLight() ElseIf (ActiveNeighbors < 2 || ActiveNeighbors > 3) && LightState EnableLight(False) EndIf Else EnableLight(!LightState) EndIf GameRef.Finished += 1EndEventFunction EnableLight(Bool Enabling = True) LightState = Enabling Int Delta = 1 If Enabling LightRef.Enable() Else LightRef.Disable() Delta = -1 EndIf Left.NextNeighbors += Delta TopLeft.NextNeighbors += Delta Top.NextNeighbors += Delta TopRight.NextNeighbors += Delta Right.NextNeighbors += Delta BottomRight.NextNeighbors += Delta Bottom.NextNeighbors += Delta BottomLeft.NextNeighbors += DeltaEndFunction
The problem can be seen in just the OnInit events (they will be a problem later, too, though). For the second script, the entire event can be ignored except the last two lines. So, these simplified scripts still cause the problem to occur:
Scriptname CGLRunGameScript extends ObjectReference Hidden FormList Property CGLLightFormList AutoInt Property Ready = 0 Auto HiddenInt LightCountEvent OnInit() LightCount = CGLLightFormList.GetSize() While Ready < LightCount Debug.Notification(Ready + "/" + LightCount) Utility.Wait(3.0) EndWhile Debug.Notification("Game is ready.")EndEvent
Scriptname CGLRunGameButtonScript extends ObjectReference Hidden CGLRunGameScript GameRefEvent OnInit() GameRef = Game.GetFormFromFile(0x00000DCA, "CGL_Conway's Game of Life.esp") as CGLRunGameScript Utility.Wait(Utility.RandomInt()/100) GameRef.Ready += 1EndEvent
The repeated notification tends to get to about "525/625". I made each light give me a black soul gem (because I'm testing with a new character via coc'ing from the main menu, and black soul gems are an easy item I am 100% sure I won't have), and I successfully get 625, so all 625 lights are definitely running. Unfortunately, the only thing I can think of is to actually use THAT to my advantage. Replace the integers with some item which I add to and remove from some container.
Are GlobalVariables guaranteed to be safe? Also, is this thread safety? I know generally what threat safety is, but I don't know for sure that this is specifically a thread safety issue (which is why I didn't mention the term before, even though I'm pretty sure it's correct here)? Any ideas would be appreciated.