Я люблю Lua. I love Lua.

Итераторы

Posted in Uncategorized by ilovelua on 29 августа, 2013

После того, как написал несколько раз такой код:

function(command, ...)
	...

	local combos = command.combos

	if combos then
		local deviceCombos = combos[deviceName]

		if deviceCombos then
			for i, combo in ipairs(deviceCombos) do
				-- что-то сделать с combo
				...
			end
		end
	end

	...
end

мне стало понятно, что нужно придумать что-нибудь поизящнее, чем эти двойные проверки. И тут я вспомнил про итераторы! Programming in Lua говорит нам, что итератором может быть функция, которая при каждом вызове возвращает следующий элемент коллекции. Поскольку deviceCombos это массив, а мне при переборе индекс элемента не нужен, то задача упрощается:

function commandCombos(command, deviceName)
	local pos = 0
	local combos
	local deviceCombos

	if command then
		combos = command.combos

		if combos then
			deviceCombos = combos[deviceName]
		end
	end

	return function()
		if deviceCombos then
			pos = pos + 1
			return deviceCombos[pos]
		end
	end
end

Обратите внимание, что переменная pos — это upvalue.
Теперь проверим получившуюся функцию:

local commandNil = nil
local commandNoCombos = {}
local commandEmptyCombos = {
	combos = {},
}
local command = {
	combos = {
		mouse = {
			{key = 'MOUSE1', action = 1},
			{key = 'MOUSE2', action = 2},
		},
		keyboard  = {
			{key = 'A', action = 11},
			{key = 'B', action = 22},
		},
	}
}

for combo in commandCombos(commandNil) do
	assert(false)
end

for combo in commandCombos(commandNoCombos) do
	assert(false)
end

for combo in commandCombos(commandNoCombos) do
	assert(false)
end

for combo in commandCombos(commandEmptyCombos) do
	assert(false)
end

for combo in commandCombos(command, 'mouse') do
	print('~~~mouse', combo.key, combo.action)
end

for combo in commandCombos(command, 'keyboard') do
	print('~~~keyboard', combo.key, combo.action)
end

На выходе ожидаемо получаем:

~~~mouse MOUSE1 1
~~~mouse MOUSE2 2
~~~keyboard A 11
~~~keyboard B 22

Upvalues и вопросы производительности

Posted in Uncategorized by ilovelua on 23 июля, 2013

Дано: список событий(массив таблиц).
Задача: с делать фильтр по полям событий.

Решение 1(прямое, как палка):

function getEventVisible(event, filter)
	if filter.initiatorId then
		if filter.initiatorId ~= event.initiatorMissionID then
			return false
		end
	end

	if filter.targetId then
		if filter.targetId ~= event.targetMissionID then
			return false
		end
	end

	if filter.objectId then
		if event.initiatorMissionID ~= filter.objectId and event.targetMissionID ~= filter.objectId then
			return false
		end
	end

	if filter.weaponId then
		if filter.weaponId ~= event.weapon then
			return false
		end
	end

	if filter.eventType then
		if filter.eventType ~= eventEventType then
			return false
		end 
	end

	if filter.initiatorCoalition then
		if filter.initiatorCoalition ~= DebriefingMissionData.getUnitCoalitionName(event.initiatorMissionID) then
			return false
		end
	end

	if filter.targetCoalition then
		if filter.targetCoalition ~= DebriefingMissionData.getUnitCoalitionName(event.targetMissionID) then
			return false
		end
	end

	return true
end

Тут мы видим, что некоторые поля используются несколько раз. Это срочно нужно оптимизировать:

function getEventVisible2(event, filter)
	local initiatorId = filter.initiatorId
	local targetId = filter.targetId
	local objectId = filter.objectId
	local weaponId = filter.weaponId
	local eventType = filter.eventType
	local initiatorCoalition = filter.initiatorCoalition
	local targetCoalition = filter.targetCoalition
	local getUnitCoalitionNameFunc = DebriefingMissionData.getUnitCoalitionName
	local eventInitiatorId = event.initiatorMissionID
	local eventTargetId = event.targetMissionID
	local eventWeaponId = event.weapon
	local eventEventType = event.type
	local eventInitiatorCoalition = getUnitCoalitionNameFunc(eventInitiatorId)
	local eventTargetCoalition = getUnitCoalitionNameFunc(eventTargetId)

	if initiatorId then
		if initiatorId ~= eventInitiatorId then
			return false
		end
	end

	if targetId then
		if targetId ~= eventTargetId then
			return false
		end
	end

	if objectId then
		if eventInitiatorId ~= objectId and eventTargetId ~= objectId then
			return false
		end
	end

	if weaponId then
		if weaponId ~= eventWeaponId then
			return false
		end
	end

	if eventType then
		if eventType ~= eventEventType then
			return false
		end 
	end

	if initiatorCoalition then
		if initiatorCoalition ~= eventInitiatorCoalition then
			return false
		end
	end

	if targetCoalition then
		if targetCoalition ~= eventTargetCoalition then
			return false
		end
	end

	return true
end

Тут можно было бы откинуться на кресле и сделать глоток крепкого виски… Но чу! Это что же такое получается? Для фильтра, который один для всех событий, значения полей каждый раз извлекаются из таблицы filter. Непорядок. А ну-ка, где тут у нас upvalues?

function getFilterFunc(filter)
	local initiatorId = filter.initiatorId
	local targetId = filter.targetId
	local objectId = filter.objectId
	local weaponId = filter.weaponId
	local eventType = filter.eventType
	local initiatorCoalition = filter.initiatorCoalition
	local targetCoalition = filter.targetCoalition
	local getUnitCoalitionNameFunc = DebriefingMissionData.getUnitCoalitionName

	return function(event)
		local eventInitiatorId = event.initiatorMissionID
		local eventTargetId = event.targetMissionID
		local eventWeaponId = event.weapon
		local eventEventType = event.type
		local eventInitiatorCoalition = getUnitCoalitionNameFunc(eventInitiatorId)
		local eventTargetCoalition = getUnitCoalitionNameFunc(eventTargetId)

		if initiatorId then
			if initiatorId ~= eventInitiatorId then
				return false
			end
		end

		if targetId then
			if targetId ~= eventTargetId then
				return false
			end
		end

		if objectId then
			if eventInitiatorId ~= objectId and eventTargetId ~= objectId then
				return false
			end
		end

		if weaponId then
			if weaponId ~= eventWeaponId then
				return false
			end
		end

		if eventType then
			if eventType ~= eventEventType then
				return false
			end 
		end

		if initiatorCoalition then
			if initiatorCoalition ~= eventInitiatorCoalition then
				return false
			end
		end

		if targetCoalition then
			if targetCoalition ~= eventTargetCoalition then
				return false
			end
		end

		return true
	end
end

Ну а теперь…(ТЕСТЫ!!! ТЕСТЫ!!!)
Я не вижу ваши руки!

local events = DebriefingEventsData.getEvents()
local filterFunc = getFilterFunc(filter_)

print('~~~event count:', #events)

local t = os.clock()

for i = 1, 1000 do
	local result = {}
	for i, event in ipairs(events) do	
		if getEventVisible(event, filter_) then
			table.insert(result, event)
		end
	end
end

print('~~~getEventVisible', os.clock() - t)

t = os.clock()

for i = 1, 1000 do
	local result = {}
	for i, event in ipairs(events) do	
		if getEventVisible2(event, filter_) then
			table.insert(result, event)
		end
	end
end

print('~~~getEventVisible2', os.clock() - t)

t = os.clock()

for i = 1, 1000 do
	local result = {}
	for i, event in ipairs(events) do	
		if filterFunc(event) then
			table.insert(result, event)
		end
	end
end

print('~~~filterFunc', os.clock() - t)

Результаты:
~~~event count: 1467
~~~getEventVisible 1.32
~~~getEventVisible2 2.583
~~~filterFunc 2.286
~~~event count: 1467
~~~getEventVisible 0.698
~~~getEventVisible2 2
~~~filterFunc 1.706
~~~event count: 1467
~~~getEventVisible 1.326
~~~getEventVisible2 2.581
~~~filterFunc 2.284
~~~event count: 1467
~~~getEventVisible 1.642
~~~getEventVisible2 2.203
~~~filterFunc 1.984
~~~event count: 1467
~~~getEventVisible 1.506
~~~getEventVisible2 2.061
~~~filterFunc 1.822
~~~event count: 1467
~~~getEventVisible 1.338
~~~getEventVisible2 2.577
~~~filterFunc 2.275
~~~event count: 1467
~~~getEventVisible 0.75999999999999
~~~getEventVisible2 2.013
~~~filterFunc 1.712
~~~event count: 1467
~~~getEventVisible 1.322
~~~getEventVisible2 2.576
~~~filterFunc 2.281
~~~event count: 1467
~~~getEventVisible 1.323
~~~getEventVisible2 2.574
~~~filterFunc 2.288
~~~event count: 1467
~~~getEventVisible 1.687
~~~getEventVisible2 2.234
~~~filterFunc 1.999
~~~event count: 1467
~~~getEventVisible 1.717
~~~getEventVisible2 2.087
~~~filterFunc 1.808
~~~event count: 1467
~~~getEventVisible 1.743
~~~getEventVisible2 2.109
~~~filterFunc 1.83

Ой, что это? Наш прекрасный оптимизированный код работает хуже неоптимизированного? Похоже, что так. Причем местами «неоптимизированная» версия быстрее «оптимизированной» почти в 2 раза!
Думаю, что в getEventVisible2() создается слишком много локальных переменных, причем некоторые из них могут не понадобиться. А что не так с filterFunc()? Там вроде локальных переменных совсем немного? Похоже, что доступ к «замороженным» переменным не так уж и быстр, либо Lua при вызове этой функции должна откуда-то загрузить все upvalues.
В общем, как обычно с оптимизацией — все нужно проверять.

I love Lua upvalues!

Posted in Uncategorized by ilovelua on 22 июня, 2011

Сегодня обнаружил повод для радости. Итак, встречайте — это upvalues!

Это связано с вчерашней темой про калбеки. Калбеком в окно передается Lua функция без параметров. А в нашем существующем редакторе, требуется вызывать функцию с параметром. Что нам поможет? Upvalues!

Дано:

Функция установки калбека для окна:

function setCallback(self, callback, key1, key2, key3, key4)
  gui.WindowSetCallback(self.widget, callback, key1, key2, key3, key4)
end

и функция для установки клавишной комбинации:

-- ... - список кнопок
function addKeyCombination(self, callback, object, ...)
  ???
end

для которой должно быть вызвано callback(object).

Что делать? А все очень просто:

-- ... - список кнопок
function addKeyCombination(self, callback, object, ...)
  self:setCallback(function () callback(object) end, ...)
end

Мы создаем безымянную функцию без параметров, внутри которой вызываем калбек с параметром. Многоточие пробрасывает остальные параметры функции addKeyCombination в функцию setCallback.

Лаконично. Красиво. Изящно.

Браво, Lua!

Tagged with: