Several param changes

Started by
5 comments, last by Bob Janova 15 years, 3 months ago
I will describe my system, maybe something is wrong. Now I'm talking about MMO. 1. Every person has a list of things (wood, coins etc). It's being sent to client at game start and then send every change. For example I get at start "Wood=20,Coins=40" then I get the command "Wood=15" and show to the user "You gave 5 wood" or if Wood=25 "You earned 5 wood". Because there must be an information about changes. Why to send all the items? I need to have a fast access to inventory (no need to wait if I want to select an apple to eat). 2. Some objects affect my things. For example I get to the tree, and get an apple. So there's a function changeParam(APPLE, 1). And when I eat, I call changeParam(APPLE, -1). It's on server: changeParam(param, delta) { this.itemList[param] += delta; if(this.itemList[param] < 0) { this.itemList[param] -= delta; send(socket, CODE_INFO, "Not enough " + param); return false; } send(socket, CODE_SETPARAM, param, this.itemList[param]); return true; } It's because I can't eat apples if I don't have one. But there's a problem! There's not always so easy. For example I need to use 2 items and get another. For example COINS+FISHES=FISHER_MEDAL. So I need something like: if(changeParam(COINS, -1)) { if(changeParam(FISH, -1)) { changeParam(FISHER_MEDAL, 1) } else { changeParam(COINS, 1) } } And if I have coins, but have no fish, I'll get: Coins lost. You have not enough fish. Coins earned. How to solve this? Yes, I can check like if(getParam(COINS) >= 1 && getParam(FISH) >= 1) then action, but it must be synchronized and has no standard output like "Not enough ..."
VATCHENKO.COM
Advertisement
Why not just have the server check if there's enough of something, and if not send the "not enough X" message?

E.g.
if(getParam(COINS) < 1){   changeParam(COINS, -1); // Will cause "not enough coins" to be sent}else if(getParam(FISH) < 1){   changeParam(FISH, -1); // Will cause "not enough fish" to be sent}else{   changeParam(COINS, -1);       // Will cause "you gave 1 coin" to be sent   changeParam(FISH, -1);        // Will cause "you gave 1 fish" to be sent   changeParam(FISHER_MEDAL, 1); // Will cause "you earned 1 fisher medal" to be sent}
If you're using some sort of multithreading which would mean the inventory could change at any point in that function, then you'll have to mutex lock it.
The example you posted needs to be synchronised anyway, and for a complex condition I don't see any way to avoid requiring a lock on the inventory while you do the checks.

Here's how I do it in Abaria when you build an improvement (which can have several requirements):
lock(ni){	if(!ni.HasGoodsAndCash(Costs)) return null;	ni.UseGoodsAndCash(Costs);	target.ImprovementType = ID;	Improvement imp = CreateImprovement(game, world, location);	((ServerTile)target.Data).Improvement = imp;	return imp;}

HasGoodsAndCash is like a multi-parameter version of your getParam (it takes an array of resources), and UseGoodsAndCash is like changeParam (it takes an array of things to change). And you'll see that I couldn't think of a way to solve that without locking the inventory :) (well I just lock the nation as a whole, but I could be a little cleverer and only lock the stockpile).
In general, you want to work on the order of transactions, not just individual adjustments. Think of your character stats as a database, where you make updates that are "all or nothing."

You can describe the transaction as:

NEED = Coins=5,Fishes=5
ACTION = Medal=1

You would then run an "execute transaction" function that would first check all the needs, and if present, it would deduct the needs and add the medal. As long as you're not multi-threading action execution, this will work fine, because the executing thread works as the synchronization point. If you want to multi-thread execution, then the "run a transaction" function also needs to lock the character in question.

You can then extend this system to involve multiple parties, to use for trade etc.
enum Bool { True, False, FileNotFound };
Yes, but my example doesn't need synchronization (only getParam and changeParam must be synchronized, but not a whole action). Because we subtract every param, and then add something. If somebody else will take my apples in this time, two changeParam functions will process one after another, but it won't affect the result because one or another "difficult process" will not be executed normally (man won't be able to get my apples, or the thing that gives me something for apples won't be able).

Another note, that objects are lists, not static arrays. Because usually there are 1000 types of objects, but every person takes about 20 objects, there's no need to make array[1000] for every person. So getParam uses an search algorithm (the best if we have some Hash Map or Tree Map - it's fast). But changeParam equals getParam + some checks + save the result. So it's not efficient to use getParam for check and then changeParam to affect.

But I have idea: what if I use getParam and setParam? And maybe some notEnough function to inform. I'll synchronize some difficult changes and first check all the values (with getParam) and then maybe inform with notEnough, and if it's all ok, change params.

Another idea: inform client after big change. So I aggregate something like actionList.add(changeParam(APPLE, -1)) and then call user.inform(actionList).

About the case of function (somebody mentioned) "What we need, then what we get" - no warranty that there are 1 object that being affected. Maybe there's a group action.
VATCHENKO.COM
Quote:Original post by Anton Vatchenko
Yes, but my example doesn't need synchronization (only getParam and changeParam must be synchronized, but not a whole action). Because we subtract every param, and then add something. If somebody else will take my apples in this time, two changeParam functions will process one after another, but it won't affect the result because one or another "difficult process" will not be executed normally (man won't be able to get my apples, or the thing that gives me something for apples won't be able).


This might seem like a good idea to you, but consider this. I'll put it in an exaggerated example so you can try and realize what all the previous posters are telling you.

Me -> You: I will trade you $1,000,000 for all your source code.
You -> Me: Sure, here is all my source code. Where is my $1,000,000?
Me -> You: *No response* (Assume I've ran away and you will never be getting a penny)

Now back to an example using your game, what if, when a player tries to trade 1 coin and one fish to get their medal, the server crashes? At this point, the player would have lost their coin let's say, since that was executed once, and the crash came before the fish was checked. Now, the player complains to you that they lost their coin and you now have a real problem to solve. However, that is not the least of your worries because a big money spending player for your game just spent a month gaining all the necessary items to trade for some "godly item", and they just got robbed of it all by the server.

Anyways, can you see the problem now when you try to do things individually? You really need to take it as an all or nothing transaction, as hplus0603 has said. You need to lock in your player's data so no code anywhere else can access or modify it and do not allow actions to "stack up", because then you get into hard to debug synchronization issues and race condition exploits.
Quote:Yes, but my example doesn't need synchronization (only getParam and changeParam must be synchronized, but not a whole action)

If you're synchronising those functions already (and you should be), why such resistance to synchronising a little further out? The reason for synchronising a whole transaction (check a bunch of stuff and then alter a bunch of values) is exactly the same as for synchronising the individual check, and it shouldn't slow things down at all (in fact, possibly less, if you simply put a lock around the whole thing instead of two locks in two successive calls to changeParam).

This topic is closed to new replies.

Advertisement