Evennia - a Python-Based Mu* Server
-
@Griatch It's basically what Tyche said. The plugin architecture does most of the work for you so as a developer all you have to remember to do is reload the plugins you changed. Happy to discuss in more detail if you like but I'm not sure this topic is the best place for it. Feel free to contact me if you want to know more.
@WTFE - There are plenty of examples of plugin infrastructures. It's not like we're doing something revolutionary here.
-
Ah. This might well be an underlying difference between Python and Ruby. Interesting. Over time we (we, as in Evennia devs) found hot-reloading tended to lead to hard-to-trace bugs and errors as people's custom heavily interconnected modules got out of internal sync with different versions. Hence our solution with two processes to allow for a full in-memory cleanup without any player losing connection.
.
Griatch -
@Griatch Interesting. Yes, I think Ruby helps quite a lot here. Also the architecture of Ares helps too, as heavily-interconnected modules are discouraged in favor of modular plugins with well-defined APIs. And even if you mess up the code by screwing up one of the connections, the underlying engine is as bulletproof as I could make it to this sort of error. Just tweak and reload and everything's good.
-
@faraday said:
@Griatch Interesting. Yes, I think Ruby helps quite a lot here. Also the architecture of Ares helps too, as heavily-interconnected modules are discouraged in favor of modular plugins with well-defined APIs. And even if you mess up the code by screwing up one of the connections, the underlying engine is as bulletproof as I could make it to this sort of error. Just tweak and reload and everything's good.
We have approached this quite differently it seems. Evennia is in many ways a code library; offering convenient programming tools for a game developer; as such many times interconnectivity is necessary to avoid code duplication and making things easily maintainable. Ares has a different scope but at any rate, the flat plugin code layout is very appealing - well done!
.
Griatch -
Oh hey, an Evennia thread. Oh hey, there's Griatch. Oh look, the usual gaggle jumping on it just because it's 'not archaic,' whatever that means.
Evennia, as a point, is useless to, well, everyone. Is it great that Griatch bothered to do something new? Sure. It had promise too some time ago. Then something changed. I haven't been able to determine if the change was complexity that couldn't be overcome or laziness but change occurred. What was an online text game server in name and function became a general purpose python socket library with what are being called 'mu functions.' It may as well be a chat room. You literally have to code everything by hand. From the command parser to the interaction handlers. These are things that current servers already do for us. That's an increased time investment for not a lot of benefit.
Case in point: I won't ever say something new isn't needed. However, this isn't it.
-
Evennia is a game development framework for building text-based online multiplayer games. It's aimed at small teams of game developers interested in making such a game using primarily Python. To that end it supplies programming resources and tools highly customized for this genre of game. That's it. Those preferring other solutions are more than welcome to pursue them.
I'm surprised and saddened to hear such a bitter tone. Did you maybe try Evennia and were disappointed it was not what you thought it would be?
.
GriatchPS:
Commands are parsed just fine out of the box, but you are given the option to change the syntax to one you prefer better. And to clarify, Evennia always worked like this; there has in fact never been more game-specific code in Evennia's distribution than there is now, due to our contrib folder slowly growing. The features out-of-box are covered here. -
I've not looked at Evannia's code but I don't see anything wrong with writing something that's meant to do something else.
If they wanted to create a game server they would have, but they shouldn't be judged because they had a different goal than expected. It's like criticizing a company for making an RPG because it's not an FPS.
-
@WTFE said:
Ah, the eternal null check. Tony Hoare's "billion dollar mistake" made manifest.
A properly-architected system does those null checks for you behind the scenes so you don't have to do it yourself constantly with the attendant risk of failure when (not if) you forget one.
What is Null?
You can't get away from null.
nil is the Ruby singleton object that represents nothingness. -
You can't get away from null ... and yet entire language environments exist in which there's not a nil/null/whatever check in sight.
-
@WTFE said:
You can't get away from null ... and yet entire language environments exist in which there's not a nil/null/whatever check in sight.
I think you might be thinking of a different type of problem? For example In C/C++, with explicit pointer management, failing to handle null-pointers is a serious issue.. High-level, garbage-collected languages like Python and Ruby manage such things for you, making that particular aspect a non-issue. Null/None/Nil remains a very useful property in any language though, to represent nothingness as @Tyche said. Which language environments are you referring to?
.
Griatch -
If you can have a value that is "nothing" that needs to be tested for before you do things, you have a null check. It may not be a physical null pointer (although I'd bet large amounts of money that most ARE such behind the scenes), but it's effectively the same thing: a bottom test. An unnecessary complication that boils down to "get X, test if X is actually there, use X". Such a manual cycle is incredibly error-prone (and is, indeed, at the core of a lot of security breaches and other related bugs).
Languages which don't have this basically do proper abstraction behind the scenes. Prolog, for example, has no "nil" value (unless you deliberately put one in for the rare use cases in which it is necessary). Any language with proper higher-order functions (or equivalent) can also dodge the bullet in most cases. The same applies to languages like Erlang or the ML family (although sadly in the libraries people will define an equivalent instead of thinking out their abstraction; imperative thinking still infects even declarative languages a lot of the time when people get started).
As an example of how Erlang (the language, not necessarily the library) avoids the need for nil checks, the core of a proper Erlang program is a process. A process has a mailbox. A typical process waits for a message and reacts according to it. If there is no message it waits until there is one. There's no cycle of "get message; check if message exists; do something with that message" -- unless you explicitly TELL it to work that way (with timeouts, etc.). By default there's no need for nil checks.
Similarly pattern matching in the MLs, combined with the nice array of higher-order functions, eliminates the need for nil checks in the overwhelming majority of cases you'd find them in imperative code (to the point that I don't actually recall when I last did an explicit nil check in SML). Sure, under the covers, there's presence checks galore, but the end-user is shielded from them in 99.44% of the cases unless they're doing some very specific things.
As for Prolog, the most common use case for nil checks in imperative languages coincides with backtracking situations in Prolog. The runtime finds the "nil" (no solution) case for you and just goes back and tries again if there's another path.
The advantage to all this is, of course, that your code isn't littered with nil checks that fall outside of the actual problem domain you're coding to. The code reflects your problem domain more accurately and what code there is can be more easily followed without the line noise of constant access/existence/solution/whatever checks. (Unless, of course, said checks are part of the very problem domain, like say file system handling where checking if a file exists can be viewed as a nil check if you squint right.) And of course, it means there's no way to forget to do the nil check. The system's underlying mechanisms do them for you and direct as appropriate.
Ever since Tony Hoare made his "billion dollar mistake" (a mistake he made, incidentally, not because of and actual need but because it was so easy to implement in the Algol W compiler!) the software industry has internalized the nil check to the point that we can't even imagine what programming without one would look like. Like I said, it even infects otherwise declarative languages and environments in a pernicious way as people reinvent it.
-
I'm not familiar with the languages you refer to and will thus not attempt to comment on them. At least in the case of Python, you generally don't do null-checks. In fact, null (or None in Python lingo) is not anything special - it's just an object like any other, used as the default return from functions if you don't give an explicit return (and don't want any). Storing None, for example at an index in a list can in itself be a useful piece of information in some cases.
The Python philosophy is otherwise "duck typing" or "leap before you look", meaning that you should generally not validate data before trying to use it. Rather you catch errors only if they actually happen. This cuts down dramatically on validation checks. This is not alway practical and you can do stupid things here - it's true that it's not something explicitly enforced by language design.
In the case of Evennia, we use asynchronous callback mechanisms which will indeed only be called when there is actually something there - repeatedly checking for a result to be around is a silly way to do things any day.
.
Griatch -
@WTFE I'm not sure I see that as an "advantage". As you say, checking for the presence of something is a core part of almost any application, whether it's checking for a message or checking to make sure the user didn't forget to provide some critical piece of data. Why would you want to re-invent that concept every single time you do an app when the ubiquitous nil/null check is something everyone's already familiar with?
-
@faraday said:
@WTFE I'm not sure I see that as an "advantage". As you say, checking for the presence of something is a core part of almost any application, whether it's checking for a message or checking to make sure the user didn't forget to provide some critical piece of data. Why would you want to re-invent that concept every single time you do an app when the ubiquitous nil/null check is something everyone's already familiar with?
Because people shouldn't be familiar-to-the-point-of-contempt with a manual, rote, error-prone process.
People were once familiar with writing out the underlying machine code directly, hand-assembling from a textual description into final object code. No less a person than Von Neumann himself scoffed at the notion of an automated assembler because it was wasting valuable machine time on something a person could do. Nowadays 99.44% of programmers would have their eyes glaze over if you handed them an assembler source file because in the interim we've recognized that human beings suck at rote work.
If you have a rote pattern whose absence leads to (often undiagnosable) failure, you don't expose it and make it the norm (if you're sane). You find a way to automate it or abstract it away unless it's absolutely needed. This is true whether you're dealing with hardware abstractions (trust me: you really don't want to deal with the vagaries of all the pieces of hardware behind your code; be very happy the people who designed your OS, as bad as it may be, didn't do that to you!), memory management (even the "manual" memory management of C and C++ and their ilk is automated to the hilt; it's just not as automated as it could be), or, in this case, presence/absence checks (a.k.a. nil checks).
Now it sounds like Griatch's plugin system does it right and doesn't expose a requirement for a presence check. That's good. It would have been easier to provide that in another language, but it's decent that he's thought of that. Because I guarantee you: if you make plugin developers have to look for this kind of stuff, you'll have a fragile set of plugins that will at the very least comically fail and at the worst case may subtly corrupt your server.
-
@Griatch Actually, I've been following Evennia since you put up the website. At some point, your mission statement changed. It used to be you called Evennia a Mu Server in python. At some point, that changed to Game Development Framework with a slant towards text based online gaming.
I don't particularly have a bitter tone towards Evennia. I just don't think that Evennia is useful for anyone. You made it in Python and that is an easy enough language to learn, but you're parading it around as something anyone can pick up. Except they can't. 'Hey guys, any developer can pick up some python knowledge and use this' is misleading at best and an outright lie at worst. Your easy to use toolkit is a web of libraries and namespaces. Adding a command to the game requires knowledge of four different files and namespaces. It's harder than the 'smile' example thrown out, though to be fair to you i'm not holding you to that since the tutorials on your website don't use that example.
My issue with Evennia is not that you did something new or that you did it in the way you did. Hey, you did something new. You did it following modern design philosophies, all the power to you. I choose instead to do my new by integrating the new into the existing technologies. Neither one is wrong. I just don't feel that your way is useful to most developers in this hobby. Your way is most likely to attract new developers, but it's most likely to be useless to current developers.
TL;DR: No you can't just go learn python and use evennia. Evennia is a giant web of intricacies and complications. Your way may attract new developers but it's a useless addition for current developers. However, you are doing good, no disrespect to the work.
-
Thanks for the elaboration!
From our perspective the "framework" term is actually the more honest one. Evennia is a MU* server too, which is confusing the issue, but the problem is that traditional MU* people expect something different from a "server/codebase": to many traditional devs it suggests something like Diku or a mudlib containing more game code and aiming to a particular game style. "Framework" better fits what Evennia actually is. Regardless of what we call it, the actual concept of Evennia actually hasn't changed much along the way though: The default commands you get from a vanilla install are still very much remniscent of MUX for example. We've just gradually expanded Evennia's capabilities and made it easier for people to rip out things and replace it with things of their own (if they feel so inclined).
There is no denying that to make the most of Evennia it requires you to read up on what it offers. There is a reason we spend so much time on our documentation. If you are completely new to python there will be a definite learning curve. The amount of complete Python newbies using Evennia does suggest it's not impossible however.
Simple examples are always potentially misleading, but if you are completely new to Evennia you might want to try something simple. So here are the lines for adding a trivial echo command: it requires the modification of two files already prepared for you when you initialize your game directory:
# mygame/commands/command.py class MyEcho (Command): key = "echo" def func(self): self.caller.msg("You hear your own echo: '%s'." % self.args.strip())
To add it to your game you need to put your command into a command set. This could be all in the same file if you wanted to, but by default we separate them for cleanliness. Doing so requires two additional lines in another pre-created file:
# mygame/commands/default_cmdsets.py from commands.command import MyEcho #... # (inside the pre-created default cmdset class) self.add(MyEcho())
After a server
@reload
, you can useecho Hellooooo
in game to see your argument returned to you. My impression is that this doesn't represent unsurmountable complexity even for beginner Python users (and we have a lot of those). Admittedly this is a trivial example, but you can get very far from this point by "just" knowing Python along with some of the properties Evennia makes available in the command body.But at the end of the day: Can you "just go learn Python and then use Evennia"? No of course not, in the same way you can't "just go learn C++/Lua and then use the Cryengine"; The language and the libraries on top of it represent different ecosystem levels.
I admit it can be very hard for me/us as developers to actually determine what is hard for a newbie however. We are constantly trying to improve in this respect, so if you have any particular ideas for how you think Evennia could/should improve I'm happy to milk you for them.
.
Griatch -
(tooltip text: If that doesn't fix it, git.txt contains the phone number of a friend of mine who understands git. Just wait through a few minutes of 'It's really pretty simple, just think of branches as...' and eventually you'll learn the commands that will fix everything.)
-
For people reading this thread who are more softcoders, a situation that came up in the Evennia IRC channel:
In order to embed functions, you currently need to type
{function( args )
. Since my text editor wants to close all brackets, braces, parens, and so forth I suggested another character. They were already thinking about|function( args )
, which while I think is better I don't think is very legible. For example:This is a test|testfunc( me )...
I suggested fully enclosing for functions. The above might be:
This is a test|testfunc( me )|...
Someone mentioned brackets [], and as a Mush coder, this hit a happy spot. The above would end up:
This is test[testfunc( me )]...
However!
Evennia's lead designer wants a single token to mean all inline substitutions, both ANSI and functions. ANSI color codes would use the same inline token. For example:
{r
for red,{B
for blue background, and so forth.Why is this a problem? Because ANSI color codes would become harder to type if brackets [] were used. I mean, imagine typing this:
[r][h][B]This is really ugly text.[n]
Eugh, no. This would be a lot easier for everyone:
|r|h|BThis is really ugly text.|n
What I've pitched is a mix of both. @Griatch already knows this, so I'm pitching this to the rest of you, whether or not you're a real-world-coder or a Mush/Mux coder. What do you think of:
|hThis is hilited text!|n |[500This is xterm256 red.|n [rhB]This is really ugly text.|n But [get_random_word( this|that|it, | )] is easier-to-read code.
While Griatch thinks about this, I'm wondering what future softcoders might think, and how current realcoders might react.
advanced note
In Mushcode, '%' is the prefix to 'substitution code', such as
%n
(enactor's name) or%xr
(ansi red) or%va
(the @va attribute). It provides flexibility and shorthanding. IMO, Evennia's{<ansi color code>
is also shorthanding. What I don't know is if my understanding of Mush's % is biasing me toward a better solution.I know I imagine that Evennia might allow me to code my own substitution code, such as:
||n
or||va
or||o
instead of[name( you )]
or[v( a )]
or[obj( you )]
respectively.(note, obj() is for the objective form pronoun: him, her, it, them)
Thanks!
-
The problem with prefix codes (or paired identical tokens) is one of scoping. I'll pull an example of this (specifically paired identical tokens) out of the realm of Unix-alike shell scripts.
The traditional way of getting the output of a command in a shell into another command is the use of the "backtick" (`). You wrap the command in backticks to get the string that the command would otherwise print to your screen. So, for example, `date -I` would put the current date in the "international" format into your command. So if I wanted to scan a file for any instances of today's date in it, I'd use a command line like this: grep -i `date -I` my_file.txt
There are several problems with this (for an example of these, check the actual text I had to type above to get it looking the way it doesβ¦), but chief among them is that you can't nest. I can't take the output of a command and put it into another command whose output I want to use. foo `bar`baz`` doesn't work, for example. This unnecessarily limits your abilities to compose higher-order constructs in your text.
The solution to this in the Unix-alike shell world was to deprecate the use of backticks and instead use the $(...) construct. Because there's a clear opening token and a clear closing token, it's easy to build nested structures. The original scanning one would now be grep -i $(date -I) my_file.txt, for example. And the one that doesn't work would become foo $(bar$(baz)). It's clearer to the eye where the replacement is going to be in the first example, and the second example is actually possible. (Incidentally, this also makes it easier to write the parser, so a developer who opposes balanced tokens is of dubious competence in my books.)
Of course this still leaves us with the problem of how to identify replacements. The MUSHcode solution is, as is typical for that family of half-baked software, utterly idiotic. Using % as the introductory token doesn't work because % is a character people use a lot. As in saying "75% off!", a problem I face after well over a decade of playing MUSHes still. (It doesn't help that various bits of softcode deal with this inconsistently so sometimes I have to use %% and sometimes I don't in an incoherent mess.) Too, using square brackets is also pretty idiotic. We may not use square brackets as often in English as we'd use, say, the percentage sign, but we still do use them.
What a good system really needs is something that's consistent and different from English.
So here's the outline of what I propose:
First, use a compound, balanced-pair approach for EVERYTHING. Period. Yes it means you type a bit more when you're accessing, say, a variable name. Suck it up. For substitution codes, open with, say "$(" and close with ")". So what in MUSH would be "%n" becomes "$(n)". (Let's not quibble over whether it should be $(...) or %{...} here. This is illustrating a concept, not detailed specifics.) For function invocation and substitution I'd suggest a different pairing be used, just to make it visually clear to the reader that different behaviour is intended. So "[random_select(this|that|the other)]" in a hypothetical MUSHing substitution could be instead "${random_select(this|that|the other)}".
Now my reasoning for these:
- First, $( and ${ (or whatever opening tokens are chosen) are not exactly commonly used tokens in English expressions. This means you reduce ambiguity and the resulting disambiguation (like MUSHcode's irritating, and inconsistent, requirement for %% when you want to talk about sales promotions). About the only time you're going to have to escape these is if you're directly talking about the code itself.
- To my eyes $(n) stands out a lot more from the text than %n does. It calls out that a substitution is being made and this makes it perfectly clear precisely what is being substituted and where it fits.
- The parser is easier to write when you have a clear and consistent single character that begins a substitution.
- It's easy to mix and match at need, as well as nesting at need. Consider a piece of code that can randomly affect the invoker or the target: ${name(${select_from([$(n), $(1)]})} is bonked on the head!
- It is also, by virtue of being properly tokenized, parsed, and executed code instead of simple-minded string substitutions all the way down, code that's able to be formatted. In situ, not passed through external formatting/deformatting tools.
-
I think @thenomain should probably have referenced this feature request which actually provides the context for the discussion.
You make some good points here - for a much more elaborate system. Evennia's inlinefunc system (which Thenomain refers to in the function-call bits) was never intended to be a full nestable language construct, it was meant as a way to allow coders to offer customizable replacements by safely parsing and calling functions supplied by the developer - this was meant to always be a single call, the return of which replaces the function call within the string. Any code trickery was meant to be done in that function, not in the input - the user's agency is only to choose which function to call, potentially with arguments. Without any intent to support nesting, having an ending bracket-type thing was superfluous except as a visual cue.
So nesting/scoping is/was not in this proposal at this time. If people are seriously wanting to build off this for something more softcode-capable though, maybe we should expand its purpose to be more general though. Then proper scoping is indeed paramount, as you point out.
Not so convinced about the need for such scoping for normal color markers (and although I agree % is a bad token name, so is $ is you are looking for something never ever used ... that's just a detail though). While computer-science sound, outside of mushing, this is "coding" intended to be used by "normal" users too, and by people not used to the mush way of things. I would mainly consider something like $(r) only if it was done with the goal of homogenizing the syntax of colors and function calls across the board - which is by all means a possibly worthy goal.
.
Griatch