DeadlyStr1ke Posted July 8, 2024 Share Posted July 8, 2024 (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): Spoiler https://imgur.com/1CBeNCj DSWTGenerateDynamic.psc Generate.psc Edited July 8, 2024 by DeadlyStr1ke Issue was solved, solution included Link to comment Share on other sites More sharing options...
DlinnyLag Posted July 8, 2024 Share Posted July 8, 2024 (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 July 8, 2024 by DlinnyLag Link to comment Share on other sites More sharing options...
DeadlyStr1ke Posted July 8, 2024 Author Share Posted July 8, 2024 (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 July 8, 2024 by DeadlyStr1ke Link to comment Share on other sites More sharing options...
DlinnyLag Posted July 9, 2024 Share Posted July 9, 2024 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 1 Link to comment Share on other sites More sharing options...
DeadlyStr1ke Posted July 9, 2024 Author Share Posted July 9, 2024 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 More sharing options...
Recommended Posts