How to detect and interact with large numbers of references?

Post » Thu Oct 22, 2015 6:47 am

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):

Spoiler
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:

Spoiler
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.

User avatar
Schel[Anne]FTL
 
Posts: 3384
Joined: Thu Nov 16, 2006 6:53 pm

Return to V - Skyrim