Перейти к публикации
Форум - ComputerCraft
Zer0Galaxy

Love2d. Способы реализации удаленного выполнения Lua-скриптов.

Рекомендованные сообщения

Две недели назад я начал изучать платформу для создания двумерных игр Love2d. Не скажу, что знаю ее уже досконально, но некоторое представление имеется. В первую очередь меня интересовала возможность создания многопользовательских игр, в которых игроку предстояло бы создавать программы. Под управлением этих программ должны работать некие игровые объекты. Не имеет значения, будут ли это космические корабли, бороздящие просторы вселенной, или роботы, роющие алмазики в шахтах, суть игры остается неизменной – определить, чья программа эффективнее.

В данной теме я хотел бы поделиться своими наработками в этом направлении и выслушать мнение людей, которые возможно интересовались этой же проблемой.

Для начала сформулирую основные задачи:

1)      Сетевой обмен. Поскольку игра многопользовательская, она должна быть построена по принципу «клиент-сервер», т.е. должна быть реализована клиентская часть, предоставляющая игроку игровой интерфейс, и серверная, на которой будет реализована игровая механика. Поэтому первой задачей я назвал бы реализацию сетевого обмена между клиентом и сервером.

2)      Возможность исполнения на сервере lua-скрипров, написанных игроками. Может и не стоило бы выделять это в отдельную задачу (все мы слышали про функцию load) если бы не одно «но». Скрипт, что бы там ни было написано, не должен блокировать работу сервера. Как, к примеру, парировать работу такого скрипта?

while true do end

Он напрочь завесит сервер, если не принять мер.

3)      Параллельное выполнение скриптов. Один скрипт может выполняться несколько часов или даже дней. Это не означает, что остальные скрипты должны ждать своей очереди. Тут нам на помощь придут потоки (threads). Поток – довольно дорогое удовольствие и при большом количестве потоков на сервере могут возникнуть проблемы, но другого способа обеспечить распараллеливание я не знаю. Об особенностях реализации потоков и способах обмена между потоком и основной программой я бы хотел поговорить.

 

А можно мне модерку на эту тему, что б тут флудосрач не разводили?

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

 

 

Сетевой обмен

 

Нужно в первую очередь определиться с целью обмена. А некоторых случаях нужно использовать UDP, в некоторых TCP.

 

Насчет луа-скриптов от игроков: исполнять их в любом случае надо на сервере. Пока неизвестно на чем будет написан сервер нельзя точно сказать как прервать блокирование такого потока, ограничение по ресурсам и прочие хитрости. Конечно можно написать сервер и на самом love2d, но скорость будет хромать. love2d не предназначен для высоконагруженных серверов, это фреймворк для 2д игр.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Насчет потоков - в ОС же есть какой-то козырь, типа, поток на несколько компов.

Вайлтруду тоже нужно побеждать методом ОС - если комп будет забивать свою часть потока, он крашится с TLWY.

Только вот... Я и понятия не имею, как это все реализовано. Нужно прямо ковырять сырцы, боюсь я.

И вот вопросик - как будет работать всякая чушь типа файловой системы, ограниченного размера озу у каждого компа, etc?

Надо уж понять, как будет работать главная фича(тм) игрульки.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Я писал специальный пакетный обработчик, регистрируешь пакет, определяешь в нём функции toBytes, fromBytes, в теле пакета реализуешь код который превращает байты в данные и наоборот, также функция handle которая принимает готовый пакет, структура пакета, 2 байта = длина пакета, 1 байт id пакета(количество байт зависит от количества id, 256 разных пакетов думаю хватит) , данные которые пойдут в fromBytes функцию. Тут желательно принцип ООП применять, в toBytes и fromBytes нужно передавать готовый буфер имеющий методы для конвертации из байт в данные и наоборот.


while true do end

debug.sethook, ей можно регулировать такие вещи.

Изменено пользователем NEO

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Сетевой обмен.

Love2d предоставляет несколько способов обмена по сети. Это и библиотека enet и библиотека LUBE. Тем не менее, я решил остановиться на стандартном TCP-сокете.

Начнем с сервера. Предположим мы хотим создать сервер, который будет принимать строку символов, как то ее преобразовывать (например, инвертировать символы) и возвращать обратно клиенту. Сделать это можно так:

 

-- подключаем либу
local socket = require("socket")
-- создаем TCP-сокет сервера и открываем на нем порт
local server = socket.try(socket.bind("*", 12345))
-- чтобы сокет не блокировал поток, устанавливаем нулевой таймаут
server:settimeout(0)
-- тут будем хранить сокеты клиентов
clients={}
-- для тех, кто не в курсе, функция love.update вызывается love-средой автоматически с минимальным периодом
function love.update(dt)
  -- проверяем, не хочет ли кто законектиться
  local client = server:accept()
  if client then  -- если хочет
  -- устанавливаем минимальное время ожидания, что бы сокет не блокировал поток
    client:settimeout(0)
  -- посылаем клиенту приветствие
	client:send("Hello\n")
  -- и сохраняем сокет клиента
    clients[#clients+1]=client
  end
  local i,n=1,#clients
    -- проверяем, что говорят клиенты
  while i<=n do
    local line, err = clients[i]:receive()
    if not err then -- если клиент что-то сказал,
	  if line=="quit" then
clients[i]:close() – если клиент сказал «quit», разрываем соединение с клиентом
	  else
		clients[i]:send(line:reverse() .. "\n") -- или отвечаем клиенту
	  end
	elseif err=="closed" then -- если клиент закрылся,
		table.remove(clients,i) -- удаляем его из списка активных клиентов
		i=i-1
		n=n-1
	end
	i=i+1
  end
end

 

 

 

И клиент:

 

 

local utf8 = require("utf8")
local socket = require("socket")
local text, ansver = "","" -- вводимый текст и ответ от сервера

function love.load()
    -- enable key repeat so backspace can be held down to trigger love.keypressed multiple times.
    love.keyboard.setKeyRepeat(true)
	-- создаем клиент-сокет
	client = assert(socket.connect("localhost", 12345))
	client:settimeout(0)
end
 
function love.textinput(t)
    text = text .. t – добавляем символ к строке ввода
end
 
function love.keypressed(key)
    if key == "backspace" then -- если нажата клавиша backspace
        -- get the byte offset to the last UTF-8 character in the string.
        local byteoffset = utf8.offset(text, -1)
 
        if byteoffset then
			-- удаляем последний введенный символ
            text = string.sub(text, 1, byteoffset - 1)
        end
    end
	if key == "return" then -- если нажата клавиша Enter,
		client:send(text.."\n") -- отправляем введеный текст на сервер
		text="" -- и сбрасываем строку ввода
	end
end

function love.update(dt)
	local line, err = client:receive() -- проверяем что ответил сервер
	if line then ansver = line end 
	if err=="closed" then ansver="closed" end
end

function love.draw()
    love.graphics.print(text, 0, 0) -- строка ввода
    love.graphics.print(ansver, 0, 12) -- ответ сервера
end

 

 

 

Следует заметить, что обмен реализуется исключительно путем передачи строк. Причем если верить документации, функция может принимать первым параметром одно из трех значение:

- “*a” – читает из сокета, пока соединение не будет закрыто. Не совсем понял как это. А если я не собираюсь закрывать сокет в ближайшее время?

- “*l” – читает из сокета строку, которая должна завершаться символом «перевод строки». Сам символ «перевод строки» не передается. В результате возникает проблема, если мы попытаемся передать текст из нескольких строк (а при передаче скрипта так и будет). На приемной стороне текст будет разбит на строки и придется его снова собирать по каким то правилам. Есть конечно вариант на передающей стороне заменить символ перевода на какой ни будь другой, а на приемной стороне выполнить обратную замену.

- число – определяет количество байт, которые будут считаны из сокета. Этот вариант нам не очень подходит, поскольку мы не знаем количества принятых байт. Можно, конечно вычитывать по одному символу, но…

Короче, ни один из трех способов не устраивает меня в полной мере. Кто использовал сокеты, что думаете?

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

 

 

Нужно в первую очередь определиться с целью обмена. А некоторых случаях нужно использовать UDP, в некоторых TCP.
Обмен будет осуществляться как то так: Клиент отправляет команду с параметрами, сервер ее исполняет и возвращает результат.

Например: 

Клиент:  "залогиниться <username> <password>"
Сервер: "Ok"
Клиент: "получи_новый_скрипт <skript_name> while true do end"
Сервер: "Ok"
Клиент: "выполни_скрипт <skript_name>"
Сервер: "Error: Too long without yielding"

Ориентируюсь на TCP, т.к. более надежен. Об одновременном использовании обоих протоколов в пределах одной программы не думал.

 

 

 

Насчет луа-скриптов от игроков: исполнять их в любом случае надо на сервере. Пока неизвестно на чем будет написан сервер нельзя точно сказать как прервать блокирование такого потока, ограничение по ресурсам и прочие хитрости. Конечно можно написать сервер и на самом love2d, но скорость будет хромать. love2d не предназначен для высоконагруженных серверов, это фреймворк для 2д игр.
Предполагаю, что сервер будет на love исключительно из-за того, что там уже реализована физика взаимодействия твердых тел. А что скорость будет хромать, так вряд ли эту игрушку будут юзать более пяти игроков одновременно. Должно потянуть.

 

 

И вот вопросик - как будет работать всякая чушь типа файловой системы, ограниченного размера озу у каждого компа, etc? Надо уж понять, как будет работать главная фича(тм) игрульки.
Файловая система пока за пределами моих мечтаний. Это все таки не OpenComputers, а что-то на много проще. А вот ограничение ОЗУ это действительно проблема. Как ее решать, надеюсь придумаем со временем.

 

 

debug.sethook, ей можно регулировать такие вещи.
Пример приведи, пожалуйста. 
  • Like 1

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

 

 

- “*a” – читает из сокета, пока соединение не будет закрыто. Не совсем понял как это. А если я не собираюсь закрывать сокет в ближайшее время?

 

Заблокирует навечно, пока не встретит EOF.

 

 

 

- число – определяет количество байт, которые будут считаны из сокета. Этот вариант нам не очень подходит, поскольку мы не знаем количества принятых байт. Можно, конечно вычитывать по одному символу, но…

 

Во взрослых протоколах пишется в первых байтах размер сообщения. Например так:

<4 байта - размер сообщения как N: 15> <текст сообщения, N байт: something 12345>

Но в таком случае достаточно чтения одной строки, да.


 

 

Следует заметить, что обмен реализуется исключительно путем передачи строк

 

Строки, да, но внутри них мы можем писать бинарные данные, например длину сообщения ту же.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Обмен будет осуществляться как то так: Клиент отправляет команду с параметрами, сервер ее исполняет и возвращает результат.

Например: 

Клиент:  "залогиниться <username> <password>"
Сервер: "Ok"
Клиент: "получи_новый_скрипт <skript_name> while true do end"
Сервер: "Ok"
Клиент: "выполни_скрипт <skript_name>"
Сервер: "Error: Too long without yielding"

Пример приведи, пожалуйста. 

 

function hook()

     coroutine.yield()

end

 

debug.sethook(hook, 100)

 

Каждые 100 инструкции будет вызываться coroutine.yield. в OC таким образом реализована защита от while true do и большой нагрузки цп.

  • Like 2

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Предполагаю, что сервер будет на love исключительно из-за того, что там уже реализована физика взаимодействия твердых тел. А что скорость будет хромать, так вряд ли эту игрушку будут юзать более пяти игроков одновременно. Должно потянуть.

 

Там Box2D юзается для физики. Либа написана на C++, но можно воткнуть куда угодно практически (потому что врапперов к ней море).

Так что если знаком с каким-нибудь языком более подходящим для бекэнда, стоит его рассмотреть как вариант

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

function hook()

     coroutine.yield()

end

 

debug.sethook(hook, 100)

 

Каждые 100 инструкции будет вызываться coroutine.yield. в OC таким образом реализована защита от while true do и большой нагрузки цп.

Не хватает только ограничения кол-ва инструкций на игрока.Думаю,стоит сделать.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Не хватает только ограничения кол-ва инструкций на игрока.Думаю,стоит сделать.

local coroutines = {}

coroutines[coroutine.create(function ()
  -- код игрока
end)] = 0  -- количество операций

debug.sethook(function ()
  local running = coroutine.running()
  if running then
    coroutines[running] = coroutines[running] + 100
    coroutine.yield(--[[ ... ]])
  end
end, 100)

что тут сложного?

Изменено пользователем LeshaInc

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

 

 

Конечно можно написать сервер и на самом love2d, но скорость будет хромать. love2d не предназначен для высоконагруженных серверов, это фреймворк для 2д игр.

 

Там Box2D юзается для физики. Либа написана на C++, но можно воткнуть куда угодно практически (потому что врапперов к ней море). Так что если знаком с каким-нибудь языком более подходящим для бекэнда, стоит его рассмотреть как вариант
Может ли кто подробно расписать реализацию  луа-скриптов из под С++ или какого то другого языка? Я одно время пытался этим заняться, но потом забросил.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Может ли кто подробно расписать реализацию  луа-скриптов из под С++ или какого то другого языка? Я одно время пытался этим заняться, но потом забросил.

 

На это дело дока есть, подробная, где-то на офф. сайте. Сам не могу расписать, потому что тоже до конца не разбирался.

Из C++ она цепляется напрямую, и это самый естественный способ её использования.

Из под Java / Scala можно заюзать либу типа JLua или прицепить Lua (например тот же Eris из OC) через JNI.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Может ли кто подробно расписать реализацию  луа-скриптов из под С++ или какого то другого языка? Я одно время пытался этим заняться, но потом забросил.

Там стэк машина, читай lua С api, всё просто.

https://www.lua.org/manual/5.1/manual.html

методы начинаются на lua_

 

LuaState* L;

 

lua_pushinteger(L, 999);

lua_setglobal(L, "var");

 

Таким образом теперь есть глобальная переменная по имени var со значением 999.

Всё работает по принципу стэка, ты пушаешь туда что - то, а уже функциями работаешь с верхушки.

Изменено пользователем NEO

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

function hook()

     coroutine.yield()

end

 

debug.sethook(hook, 100)

 

Каждые 100 инструкции будет вызываться coroutine.yield. в OC таким образом реализована защита от while true do и большой нагрузки цп.

Попытался выполнить такой код:

function hook(ev,linenum)
  linenum=linenum or ""
  io.write(ev.." "..linenum.." ")
end
 
debug.sethook(hook,"l",100)

for i=1,100 do
  io.write(i.." ")
end

Думал, хук будет хукать на каждые сто строк, т.е. пару раз за сто итераций цикла for. А не тут то было, на каждую строку хукает. Что не так делаю?

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Попытался выполнить такой код:

function hook(ev,linenum)
  linenum=linenum or ""
  io.write(ev.." "..linenum.." ")
end
 
debug.sethook(hook,"l",100)

for i=1,100 do
  io.write(i.." ")
end

Думал, хук будет хукать на каждые сто строк, т.е. пару раз за сто итераций цикла for. А не тут то было, на каждую строку хукает. Что не так делаю?

debug.sethook(hook, '', 100), будет каждые 100 инструкции, можно написать миллион инструкции в строке.

Изменено пользователем NEO
  • Like 2

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Создайте аккаунт или войдите в него для комментирования

Вы должны быть пользователем, чтобы оставить комментарий

Создать аккаунт

Зарегистрируйтесь для получения аккаунта. Это просто!

Зарегистрировать аккаунт

Войти

Уже зарегистрированы? Войдите здесь.

Войти сейчас

×