Yoko
http://forum.yoko.com.ua/

Функции для многопоточного скриптинга
http://forum.yoko.com.ua/viewtopic.php?f=20&t=15538
Page 1 of 1

Author:  Beyonder [ 2010-02-01 18:36:10 ]
Post subject:  Функции для многопоточного скриптинга

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

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

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

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

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


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

Теперь вариант с моими функциями:
Code:
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:
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:
### 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

Author:  Mirage [ 2010-02-01 20:00:01 ]
Post subject:  Re: Функции для многопоточного скриптинга

Как я понял ты вводишь InstanceVar только для записи в реест? Почему него не очистить по завершению удалив и пересоздав соответствующую ветку?
Ведь потом сам же сократишь раза в 2 :)

Author:  Beyonder [ 2010-02-01 20:33:24 ]
Post subject:  Re: Функции для многопоточного скриптинга

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

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


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

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

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

Author:  Mirage [ 2010-02-01 21:51:58 ]
Post subject:  Re: Функции для многопоточного скриптинга

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

задействовать внешний файл с командами reg delete/reg add :roll: Топором зато работает ;)

Author:  Beyonder [ 2010-02-01 22:08:39 ]
Post subject:  Re: Функции для многопоточного скриптинга

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

Page 1 of 1 All times are UTC+02:00
Powered by phpBB® Forum Software © phpBB Limited
https://www.phpbb.com/