Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parse packet in lua feature #2978

Closed
wants to merge 15 commits into from
Closed

Conversation

nekiro
Copy link
Member

@nekiro nekiro commented Apr 28, 2020

This pr allows you to parse packet in lua using creaturescripts interface.
I have included template creaturescript how to parse packet.
Registering a packet in lua means it will not be parsed in source code.

@yamaken93
Copy link
Member

There is a "major" mistake in this pr in the protocolgame code. The player methods and specially the creatureevents code should be called by the dispatcher thread, you are calling them from the main/asio thread = crash.

@infister
Copy link

There is a "major" mistake in this pr in the protocolgame code. The player methods and specially the creatureevents code should be called by the dispatcher thread, you are calling them from the main/asio thread = crash.

I guess this is not very hard to correct. However, I prefer separating this feature into modules, like in OTX.

@nekiro
Copy link
Member Author

nekiro commented Apr 28, 2020

@cristofermartins interesting, can you tell me in which cases or why would it crash?
cause I was testing and I didnt see any crashes, so I'm curious

@nekiro
Copy link
Member Author

nekiro commented Apr 28, 2020

@infister to get module "feeling" you can use revscriptsys.

@infister
Copy link

infister commented Apr 28, 2020

@cristofermartins interesting, can you tell me in which cases or why would it crash?
cause I was testing and I didnt see any crashes, so I'm curious

try to reload this with syntax error and then receive such packet

@infister to get module "feeling" you can use revscriptsys.

I am not talking about feeling, this just, in my opinion, should be separated from creaturescripts.

@SaiyansKing
Copy link
Contributor

@cristofermartins interesting, can you tell me in which cases or why would it crash?
cause I was testing and I didnt see any crashes, so I'm curious

You have a racing condition inside "getParsePacketEvent" if somehow new event will be registered from dispatcher thread and asio thread will be looping through eventsList it'll result in crash.

Also I'm seeing that many people don't understand what is passing object by reference because your function "executeParsePacket" might get different data when it actually get called by dispatcher thread(data can be changed by asio thread).

@MillhioreBT
Copy link
Contributor

I prefer to use the OTX method, and it is also compatible with Revscripts ;)

@nekiro
Copy link
Member Author

nekiro commented Apr 28, 2020

@MillhioreBT "otx" you meant @slavidodo method right? cause their code is stolen from slavi

@nekiro
Copy link
Member Author

nekiro commented Apr 28, 2020

@SaiyansKing can you enligthen me, how and why would be the data changed by asio thread? I will agree thats something I dont know much so I want to learn, will enjoy a good explanation

@MillhioreBT
Copy link
Contributor

MillhioreBT commented Apr 28, 2020

@MillhioreBT "otx" you meant @slavidodo method right? cause their code is stolen from slavi

It's pretty good anyway, why switch to yours? is better? I would gladly change if necessary.

@nekiro
Copy link
Member Author

nekiro commented Apr 28, 2020

@MillhioreBT ofc, you are free to use whatever you like to use. I'm not the one to judge which one is better and if you like their implementation better then use the one that suit you the best.
This is my implementation and that's all.

@SaiyansKing
Copy link
Contributor

@SaiyansKing can you enligthen me, how and why would be the data changed by asio thread? I will agree thats something I dont know much so I want to learn, will enjoy a good explanation

Its just that when you had reference to "Connection::msg" asio thread would read new data to that buffer without waiting for your dispatcher task to do its work. It is rather rare racing condition but a possibility is a possibility so it was worth mentioning it.

@nekiro
Copy link
Member Author

nekiro commented Apr 28, 2020

@SaiyansKing thanks! Can I request your review on that pr? I changed the code a bit I guess it should be fine now?

@yamaken93
Copy link
Member

I also have a question(which is related to this pull request): why the packet parsing is not done by the dispatcher thread?

@nekiro
Copy link
Member Author

nekiro commented Apr 28, 2020

@cristofermartins can you review the pull request, because I'm not sure what you mean now?

@SaiyansKing
Copy link
Contributor

SaiyansKing commented Apr 28, 2020

@SaiyansKing thanks! Can I request your review on that pr? I changed the code a bit I guess it should be fine now?

I'm sorry, I can't do reviews on pr's here, it looks ok, however someone might question you why you passed player directly instead of player uid but looking at connection.cpp it shouldn't be possible to queue work before "Protocol::release" will be called so I don't know reason why they used player uid in first place.

I also have a question(which is related to this pull request): why the packet parsing is not done by the dispatcher thread?

If you're asking me then I don't know performance-wise it doesn't matter because at most you read only few bytes and main job still do dispatcher, maybe they didn't want to make unnecessary copies of networkmessage buffer because as I posted before passing it as reference would have rare racing condition.

SaiyansKing
SaiyansKing previously approved these changes Apr 28, 2020
Copy link
Contributor

@SaiyansKing SaiyansKing left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks ok to me.

@yamaken93
Copy link
Member

Memory leak in the Game::playerExecuteParsePacketEvent, message(pointer to NetworkMessage) is not deleted.

@nekiro
Copy link
Member Author

nekiro commented Apr 28, 2020

@cristofermartins message is deleted in lua, its the same thing like when you use following code in lua and network message class in lua got garbage collector which calls delete

local msg = NetworkMessage()
msg:delete()

@MillhioreBT
Copy link
Contributor

@nekiro Will a situation be possible where it is necessary to keep the pointer alive for an extra time at the end of the event? otherwise, we can clean pointer from fonts to avoid writing an additional line, what do you think?

MillhioreBT
MillhioreBT previously approved these changes Apr 29, 2020
Copy link
Contributor

@MillhioreBT MillhioreBT left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It convinced me, I tested it and it seems to work very well.

@nekiro
Copy link
Member Author

nekiro commented Apr 29, 2020

@MillhioreBT you shouldnt have to delete the message manually, garbage collector should delete it at the next run, when reference count hits 0 so last msg:delete() isnt necessary, but better safe than sorry.

@kygov
Copy link
Contributor

kygov commented Apr 29, 2020

In my opinion we should implement code from otservbr as its much better.
https://github.com/opentibiabr/otservbr-global/blob/develop/src/modules.cpp

@nekiro
Copy link
Member Author

nekiro commented Apr 29, 2020

@kygov if you want to openly say something is better than something else. You should give us arguments on why.

MillhioreBT
MillhioreBT previously approved these changes May 4, 2020
Copy link
Contributor

@MillhioreBT MillhioreBT left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with this, and might even insist on creating multiple instances of this event:
I did it this way and it worked wonderfully:

game.cpp
void Game::playerExecuteParsePacketEvent(uint32_t playerId, uint8_t recvbyte, NetworkMessage* message)
{
	Player* player = getPlayerByID(playerId);
	if (!player) {
		return;
	}

	if (!player->hasEventRegistered(CREATURE_EVENT_PARSE_PACKET)) {
		return;
	}

	for (CreatureEvent* creatureEvent : player->eventsList) {
		if (!creatureEvent->isLoaded()) {
			continue;
		}

		if (creatureEvent->getEventType() == CREATURE_EVENT_PARSE_PACKET && creatureEvent->getRecvbyte() == recvbyte) {
			std::unique_ptr<NetworkMessage> msgPtr(new NetworkMessage(*message));
			creatureEvent->executeParsePacket(player, recvbyte, std::move(msgPtr));
		}
	}

	delete message;
	message = nullptr;
}

Use this Revscript to verify that what I say is true:

data/scripts/parsepacket.lua
local parsePacketEvent = CreatureEvent("ParsePacket")
parsePacketEvent.onParsePacket = function (player, recvbyte, msg)
	-- structure for parseUseItem packet, recbyte: 130 (0x82)
	local pos = msg:getPosition()
	local clientId = msg:getU16()
	local stackpos = msg:getByte()
	local index = msg:getByte()
	print(("Player: %s wants to use item at position (%d, %d, %d) with sprite id %d"):format(player:getName(), pos.x, pos.y, pos.z, clientId))
	msg:delete()
end
parsePacketEvent:recvbyte(130)
parsePacketEvent:register()

local cliportEvent = CreatureEvent("CliportLua")
cliportEvent.onParsePacket = function (player, recvbyte, msg)
	local cliportStatus = player:getStorageValue(131070) == 1
	if not cliportStatus then
		return 
	end
	local pos = msg:getPosition()
	local path = player:getPathTo(pos, 0, 1, false, false, 0)
	if path then
		local fromPos = player:getPosition()
		for i, dir in pairs(path) do
			fromPos:getNextPosition(dir)
		end
		player:teleportTo(fromPos, true)
	end
	msg:delete()
end
cliportEvent:recvbyte(130)
cliportEvent:register()

local loginEvent = CreatureEvent("LoginEventCliport")
loginEvent.onLogin = function (player)
	player:registerEvent("ParsePacket")
	player:registerEvent("CliportLua")
	return true
end
loginEvent:register()

local talkEvent = TalkAction("/cliport")
talkEvent.onSay = function (player, words, param)
	if not player:getGroup():getAccess() then
		return true
	end
	local cliportStatus = player:getStorageValue(131070) == -1
	player:setStorageValue(131070, cliportStatus and 1 or -1)
	if cliportStatus then
		player:sendTextMessage(MESSAGE_EVENT_DEFAULT, "Cliport condition enabled.")
	else
		player:sendTextMessage(MESSAGE_EVENT_DEFAULT, "Cliport condition disabled.")
	end
	return false
end
talkEvent:register()

It seems to work too well without my small changes and also with them.

@nekiro
Copy link
Member Author

nekiro commented May 14, 2020

I was going through the code again and I just got an idea, what about events interface. This pr is currently using creaturescripts, but events looks like a better place for that? What do you think?

@infister
Copy link

If you could add multiple events for parsing different recvbytes, then it might be okay.

@MillhioreBT
Copy link
Contributor

I was going through the code again and I just got an idea, what about events interface. This pr is currently using creaturescripts, but events looks like a better place for that? What do you think?

I like the idea, and now that the PR #2867 is working perfectly, this has a very nice potential.

src/networkmessage.h Outdated Show resolved Hide resolved
mattyx14
mattyx14 previously approved these changes Oct 1, 2020
@nekiro nekiro dismissed stale reviews from mattyx14 and MillhioreBT via 3a76f7a October 1, 2020 11:11
@ghost ghost mentioned this pull request Nov 26, 2020
@MillhioreBT
Copy link
Contributor

Hello, I want to revive this PR and suggest that it move to Events, there would be no need to make smart pointers, nor would we need to duplicate the pointer, since with EventCallbacks we can create instances likewise filtering a single event by several lua_calls automatically and this would be cool

@nekiro
Copy link
Member Author

nekiro commented Apr 20, 2021

Hello, I want to revive this PR and suggest that it move to Events, there would be no need to make smart pointers, nor would we need to duplicate the pointer, since with EventCallbacks we can create instances likewise filtering a single event by several lua_calls automatically and this would be cool

Smart pointers are made to be used.
I don't see any advantage with moving it to events though, besides one disadvantage which is you cannot unregister the execution.

@MillhioreBT MillhioreBT mentioned this pull request Dec 5, 2021
3 tasks
@nekiro
Copy link
Member Author

nekiro commented Dec 5, 2021

@EvilHero90 remove your stale pending review please

@ghost ghost requested review from yamaken93, djarek and EvilHero90 December 6, 2021 18:36
@Zbizu
Copy link
Contributor

Zbizu commented Dec 6, 2021

Wouldn't it be better in events?
What's the point of registering the creatureevent on every single creature when what you really want is just expanding the protocol for everyone on the server?

@nekiro
Copy link
Member Author

nekiro commented Dec 7, 2021

Wouldn't it be better in events? What's the point of registering the creatureevent on every single creature when what you really want is just expanding the protocol for everyone on the server?

Well probably yes, but also no. It's nice to have control over what you register to players on the other hand events is not a bad idea, though we would have to implement a class to parse these opcodes and store them to know which opcodes are registered in lua, or just store them in container in game class.

@Zbizu
Copy link
Contributor

Zbizu commented Dec 7, 2021

You can just if + return on players you don't want to serve.

@MillhioreBT
Copy link
Contributor

Just allow multiple bytes per event and it would be ready to merge, in my opinion.
example:
image

@@ -44,6 +44,10 @@ class NetworkMessage
enum { MAX_PROTOCOL_BODY_LENGTH = MAX_BODY_LENGTH - 10 };

NetworkMessage() = default;
NetworkMessage(const NetworkMessage& other) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't make sense if you don't really plan to make a copy, I see that you modified PR to not make copies, so why this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

future proof

Copy link
Contributor

@MillhioreBT MillhioreBT Dec 9, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although if you ask me, I think that you should allow us to have the freedom of being able to write several events with the same byte, and make a copy of the message for each event that is called, each one decides whether to do more than one event, why put limitations?

then we can do the following:
image

@@ -526,6 +526,11 @@ void ProtocolGame::parsePacket(NetworkMessage& msg)
}
}

if (player->hasEventRegistered(CREATURE_EVENT_PARSE_PACKET)) {
addGameTask(&Game::playerExecuteParsePacketEvent, player->getID(), recvbyte, msg);
return;
Copy link
Contributor

@MillhioreBT MillhioreBT Dec 10, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is serious? or a joke? XD
the return; <<<

player:registerEvent("parsepacket") >>> the player will stop doing things, the (all)-packet's are simply never parsed in the sources

@nekiro nekiro closed this Dec 10, 2021
@ranisalt ranisalt mentioned this pull request Mar 29, 2022
3 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement Increase or improvement in quality, value, or extent feature New feature or functionality
Projects
None yet
Development

Successfully merging this pull request may close these issues.