(No Title)
I should be resting up for the drive tomorrow, but oh well…
Computers are (now) for productivity, digital media, communication, and light entertainment. Game consoles are for gaming. Let’s take a look here: $150 buys you a cute, miniature PS2 that produces video on par with that produced by a processor of that price (not to mention the added mobo, video card, memory, peripherals useless to gaming like CD writers, etc., etc.).
Why are we demanding DirectX/Avalon/whatever technology out of our productivity boxes when an X Box is the perfect gamers’ compromise between a PC and a console? (For the record, I hate the X Box for its poor selection of games and huge form factor, but the idea is sound.) It’s time to subdivide and specialize; hardware is too cheap to buy multi-purpose boxes and make them specialized at everything.
In MUD news, connection management (login/logoff socket polling, user counting, and a cool doubly-linked list for connected clients) is finished, and I’m dumbly recv()-ing input off the socket descriptors and bouncing it to the console. No send()-ing yet, and the next step is getting the client input buffers going and smacking in a basic command interpreter. That shouldn’t be hard, but I’m currently in OOP hell. Dig:
Server
—- Client List (::Players, type List)
———Client (Player)
————- Client Input Buffers (one per client) (::char[MAX_BUF_SIZE])
—- World Data (Rooms List)
—- Command Interpreter
—- Socket Polling (::Monitor)
The server “owns” the clients. Since the server owns the listen socket (port 4000, or whatever specified by the command line [already working by the way, yay!]), I don’t see many ways around this. Another no-way street is the input buffer situation. Each client has to own his buffer.
However, the command interpreter (CI) needs to be at the level of the Server, or it will be cumbersome (read: craploads of pointers/references) to access the world, clients, etc. But, since the CI needs to pop commands off the client buffers, there needs to be some graceful way to write the interaction, and I’m afraid I’m going to make a bad decision that will really be a pain in the butt later on.
Current plan:
The main program loop already calls Server::Monitor() each cycle, which adds players to the list and updates stuff. It also checks for read-ready buffers (buffers that have data waiting for a recv() call). It’s nice to keep this task within the main Server class (since Server owns the listen socket), and it’s dang near impossible to have this happen on a per-client basis (Google on “blocking sockets” for the gory details).
When ::Monitor() runs into a read-ready socket, it needs to enqueue that data on the client buffer for the particular user. This is no sweat (although you do need an O(n) linear search through the clients linked list every time data is readable to match the socket descriptor with the proper Player object).
Aside: I thought about using a dictionary class to facilitate a binary search by socket descriptor (which is actually just an integer anyway). However, this seems to suck, and I shy away from it. Is it really worth it to get O(ln(n)) or whatever? I can’t remember what a binary search is in O-notation, but I don’t think it’s valuable enough to make the data structure complex. There is enough C++ abstraction going on to make this work later without changing function calls, anyway.
The current plan (finally I got there) is to use the now-hypothetical Player::BufferEnqueue(char*) function to monitor when a newline is sent to the socket. For each instance of a newline character, the enqueue function builds a Command object containing the terminated line of text and adds it to a templated linked list List. When the recv()-ed input processing is completed, the BufferEnqueue() function returns this List object to Server, which in turn bumps it over to the CI for processing (or at least further queueing).
Sound good?