13 Mar, 2009, David Haley wrote in the 101st comment:
Votes: 0
I don't think limiting the arguments to 16 (well, 15) is a good idea. That means you can only send 7 key/val pairs at once. Yes yes I can send the command multiple times with different key/vals, or use different commands etc., but that makes it a fair bit more annoying to deal with on the client end. Imagine you want to send just a prompt, in key/val format:

package.prompt | hp | 100 | maxhp | 200 | mana | 100 | maxmana | 100 | fatigue | 100 | maxfatigue | 150 | mentalstate | 100 | maxmentalstate <OOPS>

Or imagine you're sending a list of inventory items…

package.inventory | item1 | item2 | item3 | … | item15 | …item16<OOPS>

To refresh my memory: why are we restricting things like this?
13 Mar, 2009, elanthis wrote in the 102nd comment:
Votes: 0
David Haley said:
To refresh my memory: why are we restricting things like this?


Because implementations are going to have a limitation. It's best if we know what the minimum values are, so e.g. somebody doesn't write a ZMP implementation that uses a fixed array of 8 elements and wonder why some ZMP commands fail. Or a TELNET implementation that only allows a maximum of 512 bytes in a subrequest buffer and wonder why some ZMP commands fail.

I usually put a limit of 16KB on the buffer size and 32 args on a ZMP command in a pure-C implementation, but some MUDs might want to keep their memory usage down (many MUD hosts have ridiculously small memory limits). Dynamic buffers and arrays are a nightmare in C, and I don't think most MUD coders could really deal with them, so "arbitrary size" just isn't an option.

Every codebase I've seen has some kind of limit. Input lines can't be more than 128 characters in more than a few MUDs I've looked at over the last few days. The input buffer is often limited to only 1024 bytes, which is fine for very simple ZMP commands but certainly won't work if you want to start making very data-rich commands, e.g. complex GUI packages.

I honestly think any command that's using more than 4-6 arguments and more than 1024 bytes of data is probably a mistake, but leaving room in case I'm wrong seems like a safe bet. :)
13 Mar, 2009, Scandum wrote in the 103rd comment:
Votes: 0
elanthis said:
You weren't much for critical thinking problems in grade school, were you?

I have a high field dependence, nothing to be intimidated about. I'm not saying that ZMP doesn't allow anything, just that it's likely to crumble under its own weight.
13 Mar, 2009, David Haley wrote in the 104th comment:
Votes: 0
elanthis said:
I usually put a limit of 16KB on the buffer size and 32 args on a ZMP command in a pure-C implementation, but some MUDs might want to keep their memory usage down (many MUD hosts have ridiculously small memory limits). Dynamic buffers and arrays are a nightmare in C, and I don't think most MUD coders could really deal with them, so "arbitrary size" just isn't an option.

Err, I guess so. This is in the client–>server direction, though; do you think this is really a problem in the server–>client direction? In fact, they don't really have to buffer anything at all to send the ZMP commands.

As for the clients, I think we can trust them to do the right thing here?

elanthis said:
I honestly think any command that's using more than 4-6 arguments (…) is probably a mistake

You don't think the key/val prompt and inventory list are plausible examples?

Yes, I could embed my own format into the ZMP command, like:

package.inventory | item1,item2,item3,item4,…

but then I think we've thrown away a perfectly good delimiter and introduced complications (now I have to escape my new delimiter, etc.)

If I can't send inventory lists using ZMP commands in some sane way (without having to send commands many times, or using several commands, etc.), then a whole lot of its appeal has gone away for me. :sad:
13 Mar, 2009, elanthis wrote in the 105th comment:
Votes: 0
David Haley said:
Err, I guess so. This is in the client–>server direction, though; do you think this is really a problem in the server–>client direction? In fact, they don't really have to buffer anything at all to send the ZMP commands.


Receiving, not sending, is the problem. Implementations generally work by buffering up data until the end delimiter. In the case of normal input, that's until the \r\n. For ZMP, MSSP, ENVIRON, or other TELNET things, it's until IAC SE. Most implementations have a fixed buffer size, and good implementations at least have a maximum buffer size (to protect against a buggy remote end sending 2GB of data with no end delimiter).

The idea is to specify what those fixed/maximum sizes must be to be fully ZMP compliant. Looking at tbaMUD/Circle for example it is limited currently to 512 bytes (and it'll also break if there's a newline embedded in the ZMP/MSSP/ENVIRON/etc. data), which is pretty small. I wouldn't be surprised if most clients have similar assumptions. The comment in tbaMUD reads "do you really need more than 256 characters on a line?" which sounds like an assumption a client is just as likely to make. Especially given that almost every client expects the server to send wrapped lines instead of doing word-wrapping on their own.

David Haley said:
You don't think the key/val prompt and inventory list are plausible examples?


<snip big explanation, updated thoughts on argument limits below>

I guess it's not a bad thing, I just personally wouldn't design it that way.

For any kind of package that does need a really big list I would go with multiple commands. That really does have advantages, though. With just a list of arguments you can't easily express any auxillary data for each argument. Say you want your inventory to display item weight and item count in the inventory list… does that mean your inventory command now requires three arguments for each item? What happens when another MUD also wants to show basic item stats in the inventory list? How do they express that in a setup that has no room for expansion?

IAC SB "inv.begin" NUL IAC SE
IAC SB "inv.item" NUL "longsword" NUL "3kg" NUL "x2" NUL IAC SE
IAC SB "inv.item" NUL "breastplate" NUL "18kg" NUL IAC SE
IAC SB "inv.item" NUL "oak wand" NUL "5kg" NUL "x4" NUL IAC SE
IAC SB "inv.end" NUL IAC SE

Yes, it complicates processing a little, I know. It would require the inventory package handler to either keep a list of pending updates, or make it update the list as the commands are received and cause "flicker." (I suppose what I just described is actually more of a generic gridview command…)

Alternatively, you could design the inventory package to assign a unique ID to items, so all your package has to do after sending the initial list is just send occasional add and remove commands instead of having to send the whole list over and over. The server would just need to send the add/remove commands from inside the player's add_item()/remove_item() methods and you'd have a constantly up-to-date inventory list.

Argument limits are around because of my goal of allowing simplistic C implementations of ZMP. My script-language implementations of ZMP have no argument count limitations because they just split the string and they're done. My C implementations are limited because they require absolutely no dynamic memory allocations, and rely on a fixed-size argv array. I don't want to require people to have to do really complicated things because I expect that 90% of implementations are going to be in C, dynamic memory in C is a pain, and most MUD coders are fairly low-skill. At best a C implementation with no argument list limitations would have to look something like:

/* count arguments */
for (i = 0; i != length; ++i) {
if (buffer[i] == '\0')
++argc;
}

/* allocate argument array */
argv = (char**)malloc(argc * sizeof(char*));
if (argv == 0)
deal_with_error();

/* parse out arguments */
c = buffer;
for (i = 0; i != argc; ++i) {
argv[i] = c;
c += strlen© + 1;
}

/* exec command */
process_zmp(argc, argv);

/* don't leak memory */
free(argv);


I guess it's not _that_ bad. Certainly far better than if we used 0x01 for delimiters (then you'd have to dynamically allocate strings for each argument, too, to get a NUL terminator on the strings, and actually parsing strings would require an extra loop). If people think the limit should be set really high, even 128 arguments, I'm okay with that. If everyone thinks the spec should mandate no limit (other than a buffer size), I guess I'm okay with that – I'll need to update the reference implementation and ping a few client authors either way.
13 Mar, 2009, David Haley wrote in the 106th comment:
Votes: 0
Re: inventory; I was thinking something like what Nick described, where you send a basic list of identifiers, and then you allow more commands to get details on those identifiers. Inventory item description is actually something I'm very happy to say that MUDs will have to just deal with w.r.t. the client; I don't think it's easy at all to devise some scheme that will work across MUDs even of the same general codebase family.

For the rest, see this new thread.
13 Mar, 2009, elanthis wrote in the 107th comment:
Votes: 0
Yeah, I have no real direction on the best way to do inventory stuff yet. I honestly haven't put a lot of thought into that. Still worrying about the basics.

Unrelated note: use http://zmp.sourcemud.org instead of the other address. I just got notified that awemud.net is expiring in less than a week, and I have no intentions of renewing the domain. The old address will redirect you with a 301 to the new address until the domain goes away.
14 Mar, 2009, Nick Gammon wrote in the 108th comment:
Votes: 0
elanthis said:
I wouldn't be surprised if most clients have similar assumptions. The comment in tbaMUD reads "do you really need more than 256 characters on a line?" which sounds like an assumption a client is just as likely to make. Especially given that almost every client expects the server to send wrapped lines instead of doing word-wrapping on their own.


I personally don't make assumptions like that, which is one of the reasons MUSHclient rarely crashes. I tried out a test case:

barberpole = ""

for i = 33, 127 do
barberpole = barberpole .. string.char (i)
end – for

line = ""

for i = 1, 10000 do
line = line .. string.format ("(%i) %s", i, barberpole)
end – for

line = line .. "\n"

Simulate (line)


This is Lua code you can execute in the Immediate execution window. It produces a single line of 1018895 bytes, and sends that to the input processor, effectively simulating receiving a packet that size from the MUD. It works fine, admittedly it takes over a minute to process, but we are talking about almost a megabyte of input here.

The line wraps (as it is designed to do), but each line but the last in the output buffer is marked as a partial line, and the final one is marked as "end of paragraph".

It could have contained MXP (or Pueblo) commands, because the whole thing is done by a state machine, not by making assumptions like "oh I expect a line won't exceed 256 characters". Indeed MUSHclient started off as a MUSH client, and MUSHes tend to not wrap lines at the server end, but expect the client to do it.
100.0/108