Jump to content

CK Papyrus Read/Write Locking


nosis

Recommended Posts

Posted

I'm not 100% sure this is the correct place to post this, but I'm not sure where else, so if it's not then move please (and I appologize).

 

I'm very new to CK and papyrus and--as a programmer--naturally did things a programmer would do only to find out that there's really nasty side-effects due to simultaneous thread access (read: race conditions creating inf loops and what-not). Anyone who knows the issues of concurrant programming knows the solutions given at creationkit.com are sub-standard for true semaphore protection (and will eventually bite you) so I've been experiementing with read/write locking with papyrus and have come up with the following formula...

 

A script can have one of three states:

  1. Default ("")
  2. ReadLocked
  3. WriteLocked

Many readers can access the protected code in the script at once, but writers are mutually exclusive.

 

So, the basic idea is (we'll just say it's a quest object, but it shouldn't matter)...

 

The mutex around the number of readers:

Scriptname MainLibReadLocker extends Quest

Int readers = 0

Function Lock()
    GotoState("Locked")
EndFunction

Function Inc()
EndFunction

Bool Function Dec()
EndFunction

Function Unlock()
EndFunction

State Locked
    Function Lock()
        While (GetState() != "")
            Utility.WaitMenuMode(0.0000001)
        EndWhile
        ;Try again hoping there is no contention
        Lock()
    EndFunction

    Function Inc()
        readers += 1
    EndFunction

    Bool Function Dec()
        readers -= 1
        Return (readers == 0)
    EndFunction

    Function Unlock()
        GotoState("")
    EndFunction
EndState

The main script:

Scriptname MainLib extends Quest

MainLibReadLocker Property ReadLocker Auto

Actor[] actorArrToBeProtected

Function AquireReadLock()
    ReadLocker.Lock()
    GotoState("ReadLocked")
    ReadLocker.Inc()
    ReadLocker.Unlock()
EndFunction

Function AquireWriteLock()
    ReadLocker.Lock()
    GotoState("WriteLocked")
EndFunction

Function UpgradeLock()
EndFunction

Function ReleaseLock()
EndFunction

State ReadLocked
    Function AquireReadLock()
        ReadLocker.Lock()
        GotoState("ReadLocked")
        ReadLocker.IncReaders()
        ReadLocker.Unlock()
    EndFunction

    Function AquireWriteLock()
        While (GetState() != "")
            Utility.WaitMenuMode(0.0000001)
        EndWhile
        ;Try again hoping there is no contention
        AquireWriteLock()
    EndFunction

    Function UpgradeLock()
        ReleaseLock()
        AquireWriteLock()
    EndFunction

    Function ReleaseLock()
        ReadLocker.Lock()
        If (ReadLocker.Dec())
            GotoState("")
        EndIf
        ReadLocker.Unlock()
    EndFunction
EndState

State WriteLocked
    Function AquireReadLock()
        While (GetState() == "WriteLocked")
            Utility.WaitMenuMode(0.0000001)
        EndWhile
        ;Try again hoping there is no contention
        AquireReadLock()
    EndFunction

    Function AquireWriteLock()
        While (GetState() != "")
            Utility.WaitMenuMode(0.0000001)
        EndWhile
        ;Try again hoping there is no contention
        AquireWriteLock()
    EndFunction

    Function UpgradeLock()
        ;Do nothing assuming we are the actual writer... dangerous when not true
    EndFunction

    Function ReleaseLock()
        ReadLocker.Unlock()
        GotoState("")
    EndFunction
EndState

Int Function GetActorIndex(Actor who, Bool privateCall=False)
    If (!privateCall)
        AquireReadLock()
    EndIf
    Int i = 0
    While (i != actorArrToBeProtected.Length) && (actorArrToBeProtected[i] != who)
        i += 1
    EndWhile
    If (i == actorArrToBeProtected.Length)
        i = -1
    EndIf
    If (!privateCall)
        ReleaseLock()
    EndIf
    Return i
EndFunction

Bool Function ActorExists(Actor who)
    Return (GetActorIndex(who) != -1)
EndFunction

Bool Function AddActor(Actor who)
    AquireReadLock()
    Int i = GetActorIndex(who, True)
    If (i != -1)
        ReleaseLock()
        Return False
    EndIf
    UpgradeLock()
    i = GetActorIndex(who, True)
    If (i != -1)
        ReleaseLock()
        Return False
    EndIf
    ;Push who to the back (excluded in example because of super-of-a-joke arrays in papyrus)
    ReleaseLock()
    Return True
EndFunction

Bool Function RemoveActor(Actor who)
    AquireReadLock()
    Int i = GetActorIndex(who, True)
    If (i == -1)
        ReleaseLock()
        Return False
    EndIf
    UpgradeLock()
    i = GetActorIndex(who, True)
    If (i == -1)
        ReleaseLock()
        Return False
    EndIf
    ;Remove who from array (excluded in example because of super-of-a-joke arrays in papyrus)
    ReleaseLock()
    Return True
EndFunction

So several questions here...

 

Most importantly is GotoState() an atomic opperation (at least within the realm of script threads) to actually make something like this 100% work (does it mutex itself?). I've played with this idea with several hours of gameplay now hand haven't locked up given events firing nearly constantly from multiple threads, but that doesn't mean it's 100%. The whole idea relies on GotoState() being an atomic operation.

 

Second, if the answer to the first question is "yes", then is there any way to elegently introduce recusive locking into this so that a thread can "lock" itself multiple times, unlock themselves the same number of times and then--and only then--the lock is released? I'm missing a thread ID so that doesn't help. Ideas?

 

EDIT: Oh yeah, third question. What's the recursion limit on the stack? If there's starvation anyone know when things can crash?

 

Thanks in advance for your guidence.

 

 

Posted

Okay, thinking about it more, I at least I think I might have answered my second question about recursive locking.

 

IIF GetState() is atomic in nature, then an array of serials can be created (incremented and decremented around a lock or simply incremented to infinity allowing underflow into eventual overflow). The stack management would have to be locked, but could have a second array (of mapped-equal index to the serial array) of recurisve lock counts. That would totally work. I believe. It's an idea anyhow.

Posted

I'm pretty sure GotoState() is a protected with some kind of semaphore as I've now run a bunch of tests trying to break code protected by it and all of it has been 100% successful. It doesn't mean I'm right, but I probably am at this point.

 

That's great news if I'm right. :)

 

I've also made a lean-mean read-write locking library for arbitrary use that I'll release once it's fully tested (I haven't tested the recursive locking capabilities yet).

Archived

This topic is now archived and is closed to further replies.

  • Recently Browsing   0 members

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