Author Topic: How do roguebasin categories work?  (Read 21700 times)

Bear

  • Rogueliker
  • ***
  • Posts: 308
  • Karma: +0/-0
    • View Profile
How do roguebasin categories work?
« on: October 03, 2011, 06:16:05 PM »
A few days ago I put up a roguebasin page for my game-in-progress, Neohack. 

As I went back to it today, I noticed that it had acquired a category, "hacklike game." 

This is accurate, if a bit premature; I wouldn't have called it a game yet, it's more a tech demo at the moment.  Anyway, "hacklike" is an accurate description of where I'm going, and I'm glad it happened.  But I'm trying to understand how and why it happened.

The page for "hacklike games" doesn't show any recent edits, and the categorization isn't part of the page source, and the Neohack page doesn't show any edits besides mine (and one guy correcting the capitalization of the name of another game I mentioned).  And yet, when I go into the diff histories, the categorization shows up with the second edit - which is probably before anyone else noticed the page, and I think, is before I even had put up the project home page that it links to on my server. 

How'd that happen?


rsaarelm

  • Newcomer
  • Posts: 33
  • Karma: +0/-0
    • View Profile
    • Email
Re: How do roguebasin categories work?
« Reply #1 on: October 08, 2011, 01:23:34 PM »
No input on the wiki mystery, I just want to gush about how excited I am at finally getting a look at the game. I've been wanting to see the source since your "deck of cards" component system posts in 2004. The architecture is looking very neat.

Slash

  • Creator of Roguetemple
  • Administrator
  • Rogueliker
  • *****
  • Posts: 1203
  • Karma: +4/-1
    • View Profile
    • Slashie.net
    • Email
Re: How do roguebasin categories work?
« Reply #2 on: October 08, 2011, 02:43:59 PM »
I haven't replied because, to say the truth, I don't know! that's the kind of stuff that happens with a community site, you can get a lot of enhancements but they are anonymous.

I'll have to check a bit about this

Bear

  • Rogueliker
  • ***
  • Posts: 308
  • Karma: +0/-0
    • View Profile
Re: How do roguebasin categories work?
« Reply #3 on: October 15, 2011, 05:12:59 PM »
No input on the wiki mystery, I just want to gush about how excited I am at finally getting a look at the game. I've been wanting to see the source since your "deck of cards" component system posts in 2004. The architecture is looking very neat.

Have you actually downloaded it and walked the guy around the screen  (which, sigh, is all it visibly does now)?

I'm pleased with the "card deck" or "attribute" architecture, although there's not much content that uses it now.   The (very straightforward and well-documented) code for that is in the file "htable.c" and the interface in "htable.h" if you're looking for it, and suitable for reuse by MANY projects, both game and non-game, with minimal or no modification.

One of the things that does use it is the "triggerable responses to events" code, which uses "cards" to keep track of trigger attributes.  These attributes result in some scriptable event being called (before or after the event) whenever something has a particular role in a particular event type.   The game has "ai" and "reschedule-ai" events, and the triggerable response to the "ai" event for the player is where the game paints the screen and gets the player's input.

I'm pleased with the schedule-and-events (and triggerable responses) architecture, but at the moment less pleased with its implementation. It has all the virtues I designed it for (never needs to "feed time" to an actor whose turn it isn't, never needs to iterate through all pending events or all surviving actors looking for a specific one, etc),  but I don't think that, in its current form, more than a fraction of it could be reasonably reused by another project.  It's nice and general, but I'm very self-conscious that the way I did it (tables of function pointers, autogenerated code, indirect calls that result in other indirect calls, etc) will cause a fair number of "structured code" purists who aren't familiar with Data-Directed techniques (or, indeed, anything except Object-Oriented and simple procedural, which is all they teach these days) to scream that I have sinned mightily against their pious ideals and declare that the code is "an unreadable, unstructured, insane mess."  In truth, it's just DD rather than OO.  But for folks who don't know anything except OO, it probably looks unstructured and awful.

One particular real problem with the way I implemented the schedule code is that there is crucial meta-code (that is, code which does code generation) in the makefile in the form of sed scripts - which means builds on machines that don't have sed installed may be problematic, that it won't transfer well to IDE's that don't want to play nice with makefiles, and that yet more people will suppose that I must be insane.  Legions of programmers out there regard meta-code, of any kind, as prima facie evidence of insanity.  But, I'm an old LISP kid and comfortable with meta, and I didn't spend the effort to come up with a better way to do this within the limits of C.  Heh, now that I think of it, legions of coders out there, including just about all the other LISP kids who haven't written compilers, probably regard the use of C as strong evidence of insanity, too.  But I think it's kinda fun in a retro way.  Anyway, while I really like what it does and how it works, it oughta be encapsulated better before I try to advertise it as a reusable library for other projects. 

I tried to document it well, but mostly I've poured a lot of effort into testing and debugging it, and for now I'm counting on it to be bug-free code that people who don't understand it will *NEVER* want to mess with. 

A bunch of things that appear to be "small conveniences," such as the screen-resize event getting handled intelligently, and the scrolling help-line, were kinda subtle to code and had lots of corner cases, but I came up with general, content-neutral solutions for them, and I'm happy with getting those working nicely too. 

Anyway, as the perennial perfectionist who has now abandoned several games-in-progress as having flaws that other people proceeded with but which I found unforgivable, I'm pretty confident that this is the final architecture of the engine.  This is the first engine that I'm truly happy with in terms of how it works.  I may restructure or rewrite components in the future, but this engine has a peculiar set of virtues and optimizations and generality that I am actually satisfied with, so the way the whole thing works won't change anymore. 

So now, finally, I'm starting on actual game content, and fleshing out the rest of the engine functionality as I need it.

Bear

rsaarelm

  • Newcomer
  • Posts: 33
  • Karma: +0/-0
    • View Profile
    • Email
Re: How do roguebasin categories work?
« Reply #4 on: October 20, 2011, 12:32:15 PM »
I downloaded it and went straight to source-diving, since there wasn't much game content yet. The attribute system looks like it's basically an entity component system, which I made a stub page on Roguebasin. I've got something similar, but it hasn't yet been tested with seriously complicated game logic.

The event and schedule thing is more interesting. I haven't figured out a good architecture for doing that for my own projects, but I did notice a need for it when doing status effects in Teratogen. When trying to do a confusion effect, I noticed that there wasn't really an uniform control flow from a (player or AI) entity's intent to act to the actual action happening, where the confusion check would get entered and would randomly scramble the action for entities with confusion. I'm still not sure how event systems with the confusing control flow indirection and a good static control flow with monolithic functions enumerating all the exceptions to the rule they're handling will balance out in practice.

I'm kinda worried how many fixed roles the NeoHack event system seems to have for entities relating to an event, reminds me of the "if you have a procedure with ten parameters, you probably missed some" thing. Also reminds me of the Inform 7 interactive fiction system, which lets you write rule-based games in style of "If X holds when trying Y, do Z instead.", without necessarily specifying any fixed control flow where these will go in. It seems to manage to do its stuff with some kind of logic programming language style core without baking in lots of assumptions, and then lets the world model details be specified on top of the system. I haven't dived into it too deep, but it does deal with sort of the same complex object interactions in discretized time thing as roguelikes do.

I also noticed the generated code thing and had a bit of a problem following that, but then figure out that it's just a way to get a list of function pointers and function names that you can't get otherwise since there's no reflection in C.

Couldn't get the game to actually build on my Arch box, gcc 4.6.1 says

Code: [Select]
$ make
...
gcc -std=c99 -Wall -Werror -c -o character.o character.c
character.c: In function ‘Char_DrawCombined’:
character.c:92:7: error: variable ‘classlength’ set but not used [-Werror=unused-but-set-variable]
character.c:90:7: error: variable ‘namelength’ set but not used [-Werror=unused-but-set-variable]
cc1: all warnings being treated as errors

make: *** [character.o] Error 1
$ gcc --version
gcc (GCC) 4.6.1 20110819 (prerelease)

Also, I figured the event stuff should have a starting page at Roguebasin as well, so I went and stubbed this.

rsaarelm

  • Newcomer
  • Posts: 33
  • Karma: +0/-0
    • View Profile
    • Email
Re: How do roguebasin categories work?
« Reply #5 on: October 20, 2011, 07:59:15 PM »
But, I'm an old LISP kid and comfortable with meta, and I didn't spend the effort to come up with a better way to do this within the limits of C.

Wanna see a magic trick?

Code: [Select]
/* gcc -ldl -rdynamic */

#include <stdio.h>
#include <dlfcn.h>
#include <link.h>
#include <assert.h>
#include <string.h>

void action_foo() {
  printf("Foo\n");
}

void action_bar() {
  printf("Bar\n");
}

typedef struct symbol_table_t {
  ElfW(Sym)* symtab;
  char* strtab;
  int nsyms;
} symbol_table;

symbol_table get_symbol_table(void* dlhandle) {
  struct link_map* lmap = (struct link_map*)dlhandle;
  symbol_table result = { NULL, NULL, -1 };
  ElfW(Dyn) *dyn;
  dyn = lmap->l_ld;
  while (dyn->d_tag) {
    switch (dyn->d_tag) {
    case DT_SYMTAB:
      result.symtab = (Elf32_Sym*)dyn->d_un.d_ptr;
      break;
    case DT_STRTAB:
      result.strtab = (void*)dyn->d_un.d_ptr;
      break;
    case DT_HASH:
      result.nsyms = *((int*)(dyn->d_un.d_ptr + lmap->l_addr + 4));
    }
    dyn++;
  }
  return result;
}

char* get_symbol(symbol_table* symbols, size_t idx) {
  assert(idx >= 0 && idx < symbols->nsyms);
  return symbols->strtab + symbols->symtab[idx].st_name;
}

int has_prefix(const char* str, const char* prefix) {
  return strncmp(str, prefix, strlen(prefix)) == 0;
}

int main(int argc, char* argv[]) {
  void* handle = dlopen(0, RTLD_NOW);
  symbol_table symbols = get_symbol_table(handle);
  void (*action_fn)(void);
  char* name;
  int i;

  for (i = 0; i < symbols.nsyms; i++) {
    name = get_symbol(&symbols, i);
    if (has_prefix(name, "action_")) {
      printf("Dynamically calling function '%s': ", name);

      action_fn = dlsym(handle, name);
      action_fn();
    }
  }

  dlclose(handle);
  return 0;
}

Code: [Select]
$ make && ./demo
gcc -ldl -rdynamic demo.c -o demo
Dynamically calling function 'action_bar': Bar
Dynamically calling function 'action_foo': Foo
$
« Last Edit: October 20, 2011, 08:06:29 PM by rsaarelm »

Bear

  • Rogueliker
  • ***
  • Posts: 308
  • Karma: +0/-0
    • View Profile
Re: How do roguebasin categories work?
« Reply #6 on: October 21, 2011, 04:01:39 PM »

Couldn't get the game to actually build on my Arch box, gcc 4.6.1 says

Code: [Select]
$ make
...
gcc -std=c99 -Wall -Werror -c -o character.o character.c
character.c: In function ‘Char_DrawCombined’:
character.c:92:7: error: variable ‘classlength’ set but not used [-Werror=unused-but-set-variable]
character.c:90:7: error: variable ‘namelength’ set but not used [-Werror=unused-but-set-variable]
cc1: all warnings being treated as errors

make: *** [character.o] Error 1
$ gcc --version
gcc (GCC) 4.6.1 20110819 (prerelease)

Interesting.  I'm on GCC 4.4.5 with my Debian Stable distro.  It looks like v4.6.1 is warning on something that 4.4.5 does not.  If you want to build the current snapshot code, delete either -Wall or -Werror from the command in the makefile.  Meanwhile, I'll  investigate those warnings and see if they're from incomplete code or just an oversight.

Bear

Bear

  • Rogueliker
  • ***
  • Posts: 308
  • Karma: +0/-0
    • View Profile
Re: How do roguebasin categories work?
« Reply #7 on: October 21, 2011, 04:37:13 PM »
I downloaded it and went straight to source-diving, since there wasn't much game content yet. The attribute system looks like it's basically an entity component system, which I made a stub page on Roguebasin. I've got something similar, but it hasn't yet been tested with seriously complicated game logic.

You mean, wasn't any game content yet.  But yes, that is a component-based architecture, and thank you for continuing to pay attention in your CS classes after they got done
explaining OO.  So many don't.

I'm still not sure how event systems with the confusing control flow indirection and a good static control flow with monolithic functions enumerating all the exceptions to the rule they're handling will balance out in practice.

I think it will just depend on how much content there eventually is.  If your game is as simple as, say, Rogue, then monolithic functions win.  If your game is as complex as, say, Moria, then OO methodology probably wins over monolithic functions.  If your game is as complex as, say, Dwarf Fortress, then event-and-response and component architectures win over OO.  Each approach has a level of cognitive overhead, and the more advanced ones aren't really justified unless there's a certain level of content complexity.  The deciding factor for me was modularity.  With the component architecture and the event-and-response systems, I can guarantee the code-level compatibility of all patches that conform to a very non-limiting set of guidelines.  So if a thousand different patches accumulate, I should be able to just dump them in and build without worrying about what order they're applied in or whether any of them step on code lines that others depend on.

For what it's worth, the control flow indirections are done in a nearly-standard way according to Data-Directed coding practices.   C doesn't have any reflective capabilities, which is why the meta-code was needed to get lists of function pointers and function and index names, but the indirections themselves are very non-confusing if you've studied Data Directed code.

Bear
« Last Edit: October 21, 2011, 04:39:11 PM by Bear »

Ancient

  • Rogueliker
  • ***
  • Posts: 453
  • Karma: +0/-0
    • View Profile
Re: How do roguebasin categories work?
« Reply #8 on: October 21, 2011, 09:26:54 PM »
A few days ago I put up a roguebasin page for my game-in-progress, Neohack. 

As I went back to it today, I noticed that it had acquired a category, "hacklike game." 

[...]But I'm trying to understand how and why it happened.
You attached it. Yes!

When you were creating game information box you borrowed one from NetHack. It is the only article that has category tag so evilly misplaced. You bet that "[[Category:Hacklike]]" lurking just behind template definition has a nice hiding place. I am off to fix that both for NeoHack and NetHack articles.
Michał Bieliński, reviewer for Temple of the Roguelike

rsaarelm

  • Newcomer
  • Posts: 33
  • Karma: +0/-0
    • View Profile
    • Email
Re: How do roguebasin categories work?
« Reply #9 on: October 21, 2011, 10:25:32 PM »
C doesn't have any reflective capabilities, which is why the meta-code was needed to get lists of function pointers and function and index names

No portable reflective capabilities. The trick code I posted above parses the header data of an ELF binary and looks up the symbol table, so that function names in the binary can then be enumerated and function pointers dynamically fetched by name using dlsym. Windows can do something similar with LoadLibrary.

It's less portable and at least as hacky as the codegen approach, but it's still neat that you can do that.

Bear

  • Rogueliker
  • ***
  • Posts: 308
  • Karma: +0/-0
    • View Profile
Re: How do roguebasin categories work?
« Reply #10 on: October 22, 2011, 01:54:15 AM »
[...]But I'm trying to understand how and why it happened.
You attached it. Yes!

When you were creating game information box you borrowed one from NetHack. It is the only article that has category tag so evilly misplaced. You bet that "[[Category:Hacklike]]" lurking just behind template definition has a nice hiding place.

Ding!  We have a winner!  You're absolutely right. Okay, I did it without noticing.  Thanks.


Bear

  • Rogueliker
  • ***
  • Posts: 308
  • Karma: +0/-0
    • View Profile
Re: How do roguebasin categories work?
« Reply #11 on: October 22, 2011, 01:58:53 AM »
C doesn't have any reflective capabilities, which is why the meta-code was needed to get lists of function pointers and function and index names

No portable reflective capabilities. The trick code I posted above ....
It's less portable and at least as hacky as the codegen approach, but it's still neat that you can do that.

True, it is.  But I prefer not to use such tricks, especially when trying to produce something portable.  Introducing a dependency on sed and make is not good for portability; but I think it's probably better (and easier for people to comprehend) than introducing a dependency on a particular version of the ELF layout.

rsaarelm

  • Newcomer
  • Posts: 33
  • Karma: +0/-0
    • View Profile
    • Email
Re: How do roguebasin categories work?
« Reply #12 on: October 22, 2011, 02:10:37 PM »
But I prefer not to use such tricks, especially when trying to produce something portable.  Introducing a dependency on sed and make is not good for portability; but I think it's probably better (and easier for people to comprehend) than introducing a dependency on a particular version of the ELF layout.

I thought there was maybe a way to do something similar with the preprocessor only, but it just doesn't seem to be able to maintain enough state so that I could make a threading chain of registered functions using just some modest preprocessor decoration. However, the somewhat obscure, but reasonably well supported "plus plus" extension to C enables dynamic global initializers, which do allow doing something neat:

Code: [Select]
#include <stdio.h>
#include <string.h>

struct symtable {
  const char* name;
  void* data;
  symtable* next;

  static symtable* root;

  symtable(const char* name, void* data) :
  name(name), data(data), next(root) {
    root = this;
  }
};

symtable* symtable::root = NULL;


// Different function signatures need different macros.
// This one is for functions of type void (void).
#define ACT(name) name(void); \
symtable __symtable_##name(#name, (void*)name); \
void name


// Function definitions using the registration macro.
void ACT(action_foo)() {
  printf("Foo\n");
}

void ACT(action_bar)() {
  printf("Bar\n");
}


int main(int argc, char* argv[]) {
  for (symtable* sym = symtable::root; sym; sym = sym->next) {
    void (*action_fn)(void);
    // XXX: Workaround for C++ unwillingness to cast void* to function ptr.
    memcpy(&action_fn, &sym->data, sizeof(action_fn));

    printf("Executing '%s': ", sym->name);
    action_fn();
  }
  return 0;
}

Code: [Select]
$ gcc -x c++ symdef.c -o symdef
$ ./symdef
Executing 'action_bar': Bar
Executing 'action_foo': Foo
« Last Edit: October 22, 2011, 02:20:23 PM by rsaarelm »

rsaarelm

  • Newcomer
  • Posts: 33
  • Karma: +0/-0
    • View Profile
    • Email
Re: How do roguebasin categories work?
« Reply #13 on: October 22, 2011, 06:49:52 PM »
Oh, hey, turns out it was really simple after all.

All you need to do is open a portal into Hell,

Code: [Select]
$ svn co http://svn.boost.org/svn/boost/trunk/boost/preprocessor boost/preprocessor

then make areg.h

Code: [Select]

#include <boost/preprocessor/slot/counter.hpp>
#include <boost/preprocessor/arithmetic.hpp>
#include <boost/preprocessor/stringize.hpp>

#define SIGN(n) void n()

SIGN(N);

#define PARENT_VAR BOOST_PP_CAT(_x, BOOST_PP_COUNTER)
#define VAR BOOST_PP_CAT(_x, BOOST_PP_ADD(BOOST_PP_COUNTER, 1))

#define NODE_ROOT PARENT_VAR

#define GEN_VAR(var, parent_var, n) node var = { BOOST_PP_STRINGIZE(n), n, &parent_var };

GEN_VAR(VAR, PARENT_VAR, N)

#include BOOST_PP_UPDATE_COUNTER()

SIGN(N)
#undef N

and preproc.c

Code: [Select]

#include <stdio.h>

typedef struct t_node {
  char* name;
  void* data;
  struct t_node* next;
} node;

node _x0 = { NULL, NULL, NULL };

#define N action_foo
#include "areg.h"
{
  printf("Foo\n");
}

#define N action_bar
#include "areg.h"
{
  printf("Bar\n");
}


int main(int argc, char* argv[]) {
  node* node;
  void (*fptr)();
  for (node = &NODE_ROOT; node->name; node = node->next) {
    printf("Running %s: ", node->name);
    fptr = node->data;
    fptr();
  }
  return 0;
}

and you get

Code: [Select]
$ gcc -I. preproc.c -o preproc
$ ./preproc
Running action_bar: Bar
Running action_foo: Foo

Bear

  • Rogueliker
  • ***
  • Posts: 308
  • Karma: +0/-0
    • View Profile
Re: How do roguebasin categories work?
« Reply #14 on: October 26, 2011, 01:21:07 AM »
Nope, there will be no portals to hell (or boost),  Thank you.