Функции для многопоточного скриптинга

Anything and all.

Moderators: Murderator+, Murderator

Post Reply
Beyonder
Expert!
Posts: 388
Joined: 2005-04-23 10:19:43
Contact:

Функции для многопоточного скриптинга

Post by Beyonder »

Встречаются шарды на которых стоит минимальная задержка между всеми действиями. На таких шардах очень трудно сделать нормальный многопоточный скрипт, особенно если каждая часть скрипта должна иногда что-то выполнять.
К примеру приведу шард на котором я сейчас играю - UOSecondAge. Одна из "особенностей" шарда - можно одновременно использовать сразу несколько навыков, главное между их использованием должна быть задержка минимум 500мсек.
Именно для этой цели я написал несколько функций реазизующих подобие мьютексов.

Стоит заметить, функции захламляют реестр и никак не очищают. Для каждого actionType используются 2 ключа в реестре для каждого клиента.
Для любопытных, задающихся вопросом "почему не использовать GetGlobal/SetGlobal" - отвечаю: они не синхронизированы. Тоесть если два потока попытаются считать одновременно один и тот-же GetGlobal - клиент зависнет. Поэтому пришлось через задницу использовать SetEasyUO/GetEasyUO.

Опишу на примере, зачем они нужны:

Code: Select all

sub lumberjack()
   while true
      getNextTree()
      chopTree()
   wend
endsub

sub bowcraft()
   while true
      getPlanks(7)
      createBow()
   wend
endsub


Если запустить обе функции одновременно - они будут только друг другу мешать. Т.к. часто будет запускаться chopTree() и createBow() с интервалом менее чем 500мсек и один из них будет сбиваться.

Теперь вариант с моими функциями:

Code: Select all

var timerAction = 0

sub lumberjack()
   while true
      getNextTree()
      registerAction(timerAction,500)
      chopTree()
   wend
endsub

sub bowcraft()
   while true
      getPlanks(7)
      registerAction(timerAction,500)
      createBow()
   wend
endsub


Теперь, каждая функция будет ждать свободного мьютекса. Как только он освободится - она его займёт на указанное количество милисекунд. Только через указанное количество секунд - вторая функция сможет занять мьютекс и выполнить свои действия. Теперь функции не будут друг другу мешать, а будут выполнять свои действия соблюдая дистанцию в 500мсек.

Не думаю что есть много шардов где это можно использовать, и скриптеров которые это сумеют использовать, но всётаки выложу.

Проверочный скрипт (для проверки на работоспособность):

Code: Select all

sub startThreads()
     var i
     for i=0 to 5
          UO.Exec('exec testRegisterAction')
     next
endsub

Sub testRegisterAction()
   var id = UO.Random(10000)
   while true
      registerAction(0,500)
      UO.Print("Hello from thread "+str(id))
   wend
endsub


Ну и сами мьютексы:

Code: Select all

### REGISTER ACTION FUNCTION
Sub registerAction(id,time)
   var lockID = str(id) + str(UO.Random(1024 * 1024))
   var lockVarId = id*2+0
   var lockEndsId = id*2+1
   var oldLockID
   var waitTime = 0

   while true
      oldLockID = getInstanceVar( lockVarId )
      #If this action is already locked, wait till it gets unlocked
      repeat
         wait(50)
      until (val( getInstanceVar( lockEndsId ) ) <= uo.Timer())
      #If time went up, but action is owned by same lockid, we can unlock it
      if ( getInstanceVar( lockVarId ) == oldLockID ) then
         #And relock to ourselves
         setInstanceVar( lockVarId , lockID)
         #Setting timer is required in case some new thread comes to the beginning of this whole function and bypass all the checks
         setInstanceVar( lockEndsId , str(UO.Timer() + 1) )
         
         #Now we wait for some time, and check if we are still the owners of this lock. If several processes come to this spot - only one will be alive at the end
         wait(50) #!!!WARNING IF YOU HAVE A SLOW COMPUTER OR MANY UO INSTANCES - SET THIS TO HIGHER VALUE         
         if ( getInstanceVar( lockVarId ) == lockID ) then
            #Now set our normal timer
            setInstanceVar( lockEndsId , str(UO.Timer() + (time/100)) )
            return lockID
         endif
      endif
      #It could take some time for other thread to initialize it's mutex, so lets wait a little
      wait(100)
   wend
endsub

Sub unregisterAction(id)   
   var lockEndsId = id*2+1
   setInstanceVar( lockEndsId, str(0))
endsub

Sub reregisterAction(id, time)   
   var lockEndsId = id*2+1
   setInstanceVar( lockEndsId, str(UO.Timer() + (time/100)))
endsub

Sub isRegisteredAction(id)   
   var lockEndsId = id*2+1
   
   if (val( getInstanceVar( lockEndsId ) ) > uo.Timer() ) then
      return true
   else
      return false
   endif
endsub

### INSTANCE HANDLING
Sub getInstanceKey()
   return UO.Hex2Int(UO.GetSerial())
endsub

Sub setInstanceVar(id,value)
   UO.SetEasyUO(getInstanceKey()*1000+id,value)
endsub

Sub getInstanceVar(id)
   return UO.GetEasyUO(getInstanceKey()*1000+id)
endsub


Первый параметр у функции - ID мьютекса. Это должно быть обязательно число. Поэтому сверху файла можете обьявить пару констант (см. пример выше).

П.С. Теперь мой чар ходит по лесу как комбайн. Одновременно рубит лес, перемалывает его в луки и табуретки :D

П.П.С. Если здесь есть кто понял что это и зачем оно нужно - отпишитесь :D
Last edited by Beyonder on 2010-02-01 22:16:43, edited 3 times in total.
Mirage
Posts: 2802
Joined: 2009-05-28 09:58:28
Location: Иваново
Contact:

Re: Функции для многопоточного скриптинга

Post by Mirage »

Как я понял ты вводишь InstanceVar только для записи в реест? Почему него не очистить по завершению удалив и пересоздав соответствующую ветку?
Ведь потом сам же сократишь раза в 2 :)
Beyonder
Expert!
Posts: 388
Joined: 2005-04-23 10:19:43
Contact:

Re: Функции для многопоточного скриптинга

Post by Beyonder »

Обновил скрипт, по глупости использовал GetGlobal и SetGlobal (которые не синхронизированы) в синхронизированной функции setInstanceVar и getInstanceVar - из-за чего их смысл потерялся и скрипт безбожно глючил. Теперь привязал всё к серийнику персонажа, так даже лучше.

Как я понял ты вводишь InstanceVar только для записи в реест?


InstanceVar - Основное назначение функции это запись и чтение переменных в пределах текущего УО в синхронизированном виде. Тоесть чтобы быть уверенным что одну и ту-же переменную в один момент времени могут без проблем прочитать два потока. Реализована она через реестр, т.к. других синхронизированных переменных я не нашёл.
Был вариант сделать через UO.AddObject (там тоже синхронизировано), но тогда появляются тонны спама в клиенте.

Почему него не очистить по завершению удалив и пересоздав соответствующую ветку?

Покажи мне функцию удаления из реестра, я вижу только setEasyUO и getEasyUO :)
Mirage
Posts: 2802
Joined: 2009-05-28 09:58:28
Location: Иваново
Contact:

Re: Функции для многопоточного скриптинга

Post by Mirage »

Beyonder wrote:Покажи мне функцию удаления из реестра, я вижу только setEasyUO и getEasyUO :)

задействовать внешний файл с командами reg delete/reg add :roll: Топором зато работает ;)
Beyonder
Expert!
Posts: 388
Joined: 2005-04-23 10:19:43
Contact:

Re: Функции для многопоточного скриптинга

Post by Beyonder »

Ну это уже совсем изврат... Текущая версия впринципе почти не замусоривает реестр. Она использует по 2 ключа на каждого персонажа (на каждый уникальный серийник), так что даже если у нас 10 персонажей, то будет каких-то 20 ключей в реестре...
Post Reply