Jump to content

[SOLVED] Need help with Papyrus Stages, multithreading and preventing multiples of events slipping into eachother


Recommended Posts

Posted (edited)

The issue has been solved, if you encountered the same problem, please refer to my second post's "Edit" section for the solution! Cheers!

Hello, everyone, I've been working on procedural generation for my project, which works great up until one point, and that is having multiples of objects with a script attached firing the OnLoad event, slipping into each other, changing variables in the middle of one of them running, resulting in terrifying stuff (vehicle generations slipping into furniture generation events, resulting in vehicles spawned where furniture should have and vice versa). 

I read that all of this has to do with Papyrus' threading, stages and other ways to "lock" a script, and while I've been reading up on it a bunch, I'm still complete and utter 0 in this field.

Below I will attach all the scripts I have, with a description of what I use them for, but my main issue/goal is to set them up in a way that each of the objects runs and finished the event before the next one starts to.

Here's the main "Generator" script I have (which utilizes Data Structures framework):
 

Spoiler
Scriptname DSWTGenerateDynamic Extends ObjectReference
{Script for procedurally generating a single Dynamic.}
Group GlobalData
	{Main keywords/global arrays and variables to store information in}
	Keyword Property DSWT_Keyword_StorageGenerators Auto Mandatory
	Keyword Property DSWT_Keyword_StorageDynamics Auto Mandatory
	GlobalVariable Property DSWT_GlobalVar_DynamicsGenerated Auto Mandatory
EndGroup

Group Generator
	{Main Generator Settings}
	Form[] Property poolDynamics Auto
	Bool Property isLightGenerator = True Auto
	Bool Property isRandomRotation = False Auto
	Int Property chanceNone = 0 Auto
EndGroup

Event OnLoad()
	; Premature Generator Exit
	If Self.IsDisabled()	
		return	
	EndIf	

	; Initialize Dynamics Cleaner for later by starting the generation process
	If DSWT_GlobalVar_DynamicsGenerated.GetValueInt() != 1
		DSWT_GlobalVar_DynamicsGenerated.SetValue(1)
	EndIf	
	
	; ----------------- Register activated Generator
	If isLightGenerator
		DS:FormArray.Add(DSWT_Keyword_StorageDynamics, Self as ObjectReference)
	Else
		DS:FormArray.Add(DSWT_Keyword_StorageGenerators, Self as ObjectReference)
	EndIf	

	; Roll for a chance of Generator not creating anything	
	If Utility.RandomInt(1,100) <= chanceNone
		Self.Disable()
		return
	EndIf	

	; Start the main Generation process
	DSWT:Libraries:Generation:Generate.Dynamic(Self as ObjectReference, DSWT_Keyword_StorageDynamics, poolDynamics, isRandomRotation)
EndEvent

 

And here is the SWT:Libraries:Generation:Generate script that is called in the script above:

 

Spoiler
Scriptname DSWT:Libraries:Generation:Generate

Function Dynamic(ObjectReference sourceGenerator, Keyword storageTarget, Form[] poolStaticDynamics, Bool isStaticRandomRotation) global
	Debug.Trace(sourceGenerator + ": Generator activated for generation...")

	; Get the original Generators position and rotation
	Float sourceGeneratorPosX = sourceGenerator.GetPositionX ()
	Float sourceGeneratorPosY = sourceGenerator.GetPositionY()
	Float sourceGeneratorPosZ = sourceGenerator.GetPositionZ()
	Float sourceGeneratorRotX = sourceGenerator.GetAngleX()
	Float sourceGeneratorRotY = sourceGenerator.GetAngleY()
	Float sourceGeneratorRotZ = sourceGenerator.GetAngleZ()
	Float sourceGeneratorScale = sourceGenerator.GetScale()	
	; Set random rotation if the Dynamic can be freely randomly rotated
	If isStaticRandomRotation
		sourceGeneratorRotZ = Utility.RandomFloat(0.0,359.0)
	EndIf	
	
	; Begin random Dynamic generation and set it in place of Generator
	Int poolStaticDynamicsSize = poolStaticDynamics.length - 1
	ObjectReference generatedStaticDynamic = sourceGenerator.PlaceAtMe(poolStaticDynamics[Utility.RandomInt(0,poolStaticDynamicsSize)])
	generatedStaticDynamic.SetPosition(sourceGeneratorPosX,sourceGeneratorPosY,sourceGeneratorPosZ)
	generatedStaticDynamic.SetAngle(sourceGeneratorRotX,sourceGeneratorRotY,sourceGeneratorRotZ)
	generatedStaticDynamic.SetScale(sourceGeneratorScale)
	
	ConsoleUtil.SetSelectedReference(generatedStaticDynamic)
	ConsoleUtil.ExecuteCommand("ForcePersistent")
	
	; Disable and Enable for quick spawning
	generatedStaticDynamic.Disable()
	generatedStaticDynamic.Enable()
	Debug.Trace(sourceGenerator + ": Dynamic " + generatedStaticDynamic + " generated...")
	; Register the generated dynamic for future cleaning
	DS:FormArray.Add(storageTarget, generatedStaticDynamic)
	
	;Hide the Generator by Disabling it after the Dynamic is spawned
	sourceGenerator.Disable()
	Debug.Trace(sourceGenerator + ": Generator registered for cleaning and disabled.")
EndFunction

 

I'm also attaching both of these as .psc files for anyone wanting quick access to them. Once I figure out a way to overcome this obstacle with some way to "lock" the script for each seperate object, everything else is set and done to begin work on much bigger things. This is the only thing holding my project back at the moment, so I'm thankfull for any help in advance!

 

As a little preview that will hopefully peak more interest to this post, here's a GIF of the much advanced generation in progress (including smart surface population wip):

 

 

DSWTGenerateDynamic.psc Generate.psc

Edited by DeadlyStr1ke
Issue was solved, solution included
Link to comment
Share on other sites

Posted (edited)

hm... Not sure that I understand the problem correctly, but you can try to make your Dynamic function non-global.

The reason why scripts need to be "locked" - sync access to script's variables. Global functions do not use script's variables, so it is possible that calls of global function does not "lock" script.

 

I know that global native functions doesn't lock the script where they are declared, but I'm not sure about just global functions.

Edited by DlinnyLag
Link to comment
Share on other sites

Posted (edited)

Hey @DlinnyLag, thank you for the reply! I'll try out your suggestion, maybe that'll do the trick.

Gonna try to explain what is happening with an example:

So, I have a room, in which I have 3 activator objects, using the same script that is attached to their forms (DSWTGenerateDynamic), one is a table, that spawns a random version of table, taken from the array in the script property, second is a chair, that does the same, third is a couch, same process. Sometimes, when they run simultaneously, the second or third activator can change the variables before the first one finishes generating, for example

ObjectReference generatedStaticDynamic = sourceGenerator.PlaceAtMe(poolStaticDynamics[Utility.RandomInt(0,poolStaticDynamicsSize)])

changing this variable for the table "generator" to be either a chair or a couch, thus leaking into it's thread before it can finish.

What I was looking for is for a way to completely "lock" the thread, so that no matter what my table objects calls inside the OnLoad event, the chair and couch wont run until the table is finished with the thread. So, force them to que and not slip in during calls. I read on States, checked out bool locks, but haven't had any success in implementing them, might be that I am understanding them process wrong. I'll try one more way I think it can work after I try your suggestion.

Edit: I got it working with the use of a GlobalVariable, since neither states, nor bools appear to be thread-safe for such things.

This is the code I used to lock up the script until each individual reference is done with it:

 

    ; Wait until the global lock is free
    While DSWT_GlobalVar_isDynamicLocked.GetValueInt() == 1
        Utility.Wait(1.0)
    EndWhile
    ; Lock the global variable
    DSWT_GlobalVar_isDynamicLocked.SetValue(1)

 

Edited by DeadlyStr1ke
Link to comment
Share on other sites

8 hours ago, DeadlyStr1ke said:

I got it working with the use of a GlobalVariable, since neither states, nor bools appear to be thread-safe for such things.
This is the code I used to lock up the script until each individual reference is done with it:

Your solution still allow race condition.

You can use the fact that calls of DS:*Set.Add/Remove and DS:*Dict*.Add/Remove are atomic and returns result of operation and build your own mutex.

An example, how it can be done:

Keyword someKey

int someNumber = 10 const

bool function AcquireLock()
   return DS:IntSet.Add(someKey, someNumber) ; returns true if value was added to the set, otherwise - false
endfunction

function ReleaseLock()
  DS:IntSet.Remove(someKey, someNumber)
endfunction

function CriticalSection()
   while !AcquireLock()
      Utility.Wait(1) ; 
   endwhile

   ; Do the stuff 
   ....

   ReleaseLock()
endfunction

 

 

 

  • Like 1
Link to comment
Share on other sites

3 hours ago, DlinnyLag said:

Your solution still allow race condition.

You can use the fact that calls of DS:*Set.Add/Remove and DS:*Dict*.Add/Remove are atomic and returns result of operation and build your own mutex.


Ah, I see, this locking tactic looks like it should do the trick. Thank you!

Link to comment
Share on other sites

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...