Author Topic: neohack -- code introspection in C  (Read 7538 times)

Bear

  • Rogueliker
  • ***
  • Posts: 308
  • Karma: +0/-0
    • View Profile
neohack -- code introspection in C
« on: October 27, 2011, 12:50:38 AM »

On reflection, I probably shouldn't have used the word "Hack" in the name of this game, considering what I am doing.  It's just a little bit too appropriate. 

So, I'm writing in C because I like the old-fashioned hardcore-ness of C.  But C has no introspective capabilities at all and, after due consideration, I used a data-directed style with reified events and a  component architecture.  So, naturally, I had to fake some code introspection, which I've done by using sed scripts in the makefile. 

There are (so far) two classes of modular data-functions in the game, which are "action" functions and "trigger" functions.  I have a closure-type based on action functions, which is the reified event type I was talking about.  The events go on the schedule, and the game proceeds by just getting the next event, executing it, and disposing of it, repeat until done.  Executing any event may cause more events to be allocated and placed on the schedule.  The "trigger" functions are also data-functions, and reified trigger calls become attributes which can be attached to an actor.  When a particular thing happens to an actor (ie, it has a particular part in some event that gets executed) any matching triggers get called.  So, for example, when a mummy takes fire damage, a trigger can cause it to burst into flame, or when the player character processes an AI event, a trigger can cause the game to display the map and prompt for input. 

In order to facilitate this, I decided I needed function tables for events and triggers; that is, arrays that have function pointers in them.  In order to refer to the function, I refer to its location in the function table.  I do this using a named constant whose name is derived from the name of the function.  And the names of the constants are also available to the program as strings, so that it can read or write the names (which will stay the same no matter how other functions are added or removed) rather than the index values or worse yet the bare function pointers, in savefiles when serializing the schedule or the actors' attributes. 

Today, I made a subclass of events even more special than they were previously.  Now at a crucial point at the beginning of a game, the program directly calls all events whose names end in 'INIT'.  At the same time, I added some more makefile hackiness, so that all the c files in a subdirectory named 'content' get built and linked to the project. 

The idea is that game content can be added by adding a file of code to the 'content' subdirectory.  You include one "action" function named 'action_something_init' in the file, the game calls it during startup, and you get your opportunity to add content to the game using the interfaces in the main code, that let you do things like add a creature or an item to the random-generation tables, allocate prototypes and add attributes to them, add map generation methods, etc. 

If correctly done (and the rules for doing it correctly are simple) you can add arbitrary amounts of content, just by dropping the files into the content directory.  No need to change the makefile, no need to set flags, no need to add a call to the new code; just drop the file in, rebuild, and it's there.

doing this in C is a huge hack.  But I like the design.
Bear


rsaarelm

  • Newcomer
  • Posts: 33
  • Karma: +0/-0
    • View Profile
    • Email
Re: neohack -- code introspection in C
« Reply #1 on: October 30, 2011, 10:44:43 PM »
Out of the weird hack solutions I gave in the other thread, I do kinda like the C++ one, which uses a macro and the fact that C++ lets you have dynamic global initializers to build a table of named function pointers before the program enters main.

The problem of course is that then the project would be C++ and not C, although outside the dynamic initialization part you could keep developing things entirely in C style.

jaydg

  • Rogueliker
  • ***
  • Posts: 54
  • Karma: +0/-0
    • View Profile
    • NLarn Homepage
Re: neohack -- code introspection in C
« Reply #2 on: November 09, 2011, 11:40:15 AM »
You might want to have a look at GObject. This library is part of GLib and supports writing object oriented C. There is another library which allows introspection: GObjectIntrospection.
GLib can load modules at runtime.

I guess all the things you want to do can be achieved with these libraries.

ido

  • Rogueliker
  • ***
  • Posts: 618
  • Karma: +0/-0
    • View Profile
    • Tame Tick
Re: neohack -- code introspection in C
« Reply #3 on: November 09, 2011, 03:20:12 PM »
You might want to have a look at GObject. This library is part of GLib and supports writing object oriented C. There is another library which allows introspection: GObjectIntrospection.
GLib can load modules at runtime.

I guess all the things you want to do can be achieved with these libraries.

If you do that you might as well do something sane, like use a higher level language.

Which is to say, I don't think Bear will go for it.

Bear

  • Rogueliker
  • ***
  • Posts: 308
  • Karma: +0/-0
    • View Profile
Re: neohack -- code introspection in C
« Reply #4 on: November 11, 2011, 04:18:04 AM »
No, I'm not likely to use any libraries I don't write for this one, aside from old standards like assert and malloc and wchar and ncursesw. 

The thing about it is, all code-introspection libraries in C are hacks just as ugly as the makefile hacks I'm using.  So, aside from not getting to write it myself, what would be the point?

Progress report:  A week or so ago, I added an alternate main menu with vi-key movement  (the default is numpad movement).  You can start the game with vi-keys turned on using a command line argument, or you can switch back and forth between main menus in the game using a keystroke command.  And, yes, the "ticker line" shows the new command bindings instantly when the switch is made, and yes the game remembers the user preference and switches to the "right" main menu when it comes back from a submenu.

Then I thought about the generation of monsters and items.  I started coding a couple times, and each time I got a few functions written and then went, "no, this isn't the right way to do this."  So I stared at it a lot.  And played a lot of solitaire and minesweeper waiting for clarity about specific aspects of the design to arrive in my brain.  And, today it finally did.  So I have a design I'm finally happy with for this aspect of the game, and I'm moving forward with it.

So today I added code to keep track of actor generation tables.  The way I'm doing things, an 'actor' can be a monster or an item, so the same code will do monster generation and item generation (just using different tables, natch).  So now the game keeps a list of named tables of actors with generation oddments, and there are calls to add a table to the list or add an actor prototype to a table, which can be called from init functions in the content files.   There's also a call to generate an actor from a particular table, which will be used by dungeon map generators (also 'content', but of a different kind). 

The actors stored in the tables are prototypes.  They get copied, and the copies get "scanslated" and put into the dungeon.  Scanslation is an action which the prototype can have a custom triggered response on, so you can have arbitrary things happen as a result of scanslation. By default it replaces generated actors that have a "pack" attribute with a definite number of actual monsters,  makes monsters up to ten percent bigger or smaller or faster or slower than the norm for their type, picks genders for creatures that have randomized gender, picks weapons for creatures that have randomized weapons, etc,  or gives creatures very occasional special abilities or odd intrinsics. But different monster types or items can have their own scanslation functions, so in theory scanslation for different creatures can be different, and sensitive to, eg, dungeon depth or the terrain/generator type.  And using the scanslation trigger, creatures will be able to modify the dungeon map itself to suit themselves when they're generated - so shopkeepers, for example, can pick out spots to use and then and build walls and doors, etc, as needed to set up their shops, or boxes full of cash can install secret doors with heavy locks all around and make themselves a 'secret vault.'   And this meets, or at least makes possible, basically all of the things I wanted out of monster and item generation. 

Once I've debugged the actor generation tables a bit, and implemented the default actor scanslation, I'll be posting another dev snapshot on the project page.