Page 1 of 1

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

Posted: 2010-02-01 18:36:10
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

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

Posted: 2010-02-01 20:00:01
by Mirage
Как я понял ты вводишь InstanceVar только для записи в реест? Почему него не очистить по завершению удалив и пересоздав соответствующую ветку?
Ведь потом сам же сократишь раза в 2 :)

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

Posted: 2010-02-01 20:33:24
by Beyonder
Обновил скрипт, по глупости использовал GetGlobal и SetGlobal (которые не синхронизированы) в синхронизированной функции setInstanceVar и getInstanceVar - из-за чего их смысл потерялся и скрипт безбожно глючил. Теперь привязал всё к серийнику персонажа, так даже лучше.
Как я понял ты вводишь InstanceVar только для записи в реест?
InstanceVar - Основное назначение функции это запись и чтение переменных в пределах текущего УО в синхронизированном виде. Тоесть чтобы быть уверенным что одну и ту-же переменную в один момент времени могут без проблем прочитать два потока. Реализована она через реестр, т.к. других синхронизированных переменных я не нашёл.
Был вариант сделать через UO.AddObject (там тоже синхронизировано), но тогда появляются тонны спама в клиенте.
Почему него не очистить по завершению удалив и пересоздав соответствующую ветку?
Покажи мне функцию удаления из реестра, я вижу только setEasyUO и getEasyUO :)

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

Posted: 2010-02-01 21:51:58
by Mirage
Beyonder wrote:Покажи мне функцию удаления из реестра, я вижу только setEasyUO и getEasyUO :)
задействовать внешний файл с командами reg delete/reg add :roll: Топором зато работает ;)

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

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