Angband.oook.cz
Angband.oook.cz
AboutVariantsLadderForumCompetitionComicScreenshotsFunniesLinks

Go Back   Angband Forums > Angband > Development

Reply
 
Thread Tools Display Modes
Old November 13, 2016, 20:33   #1
Nick
Vanilla maintainer
 
Nick's Avatar
 
Join Date: Apr 2007
Location: Canberra, Australia
Age: 52
Posts: 6,518
Donated: $60
Nick is on a distinguished road
Functions from data files

I'm currently making an effort to get as much data as possible into text files in lib/gamedata (here's an example I've just done).

Some of this involves getting information at run time to put into functions that have to be defined at compile time. We have two ways of dealing with this:
  1. Preprocessor shenanigans, like:
    Code:
    PROJ_ENV(LIGHT_WEAK,	COLOUR_ORANGE,	"light")
    ,
    Code:
    static const project_feature_handler_f feature_handlers[] = {
    	#define PROJ_ENV(a, col, desc) project_feature_handler_##a,
    	#include "list-project-environs.h"
    	#undef PROJ_ENV
    	NULL
    };
    and
    Code:
    /* Light up the grid */
    static void project_feature_handler_LIGHT_WEAK(project_feature_handler_context_t *context)
    {
    	const int x = context->x;
    	const int y = context->y;
    
    	/* Turn on the light */
    	sqinfo_on(cave->squares[y][x].info, SQUARE_GLOW);
    
    	/* Grid is in line of sight */
    	if (square_isview(cave, y, x)) {
    		if (!player->timed[TMD_BLIND]) {
    			/* Observe */
    			context->obvious = true;
    		}
    
    		/* Fully update the visuals */
    		player->upkeep->update |= (PU_UPDATE_VIEW | PU_MONSTERS);
    	}
    }
  2. Explicit coupling, like:
    Code:
    name:BA_ACID
    hit:100
    effect:BALL:ACID:2
    dice:15+1d$S
    expr:S:MONSTER_LEVEL:* 3
    ,
    Code:
    expression_base_value_f spell_value_base_by_name(const char *name)
    {
    	static const struct value_base_s {
    		const char *name;
    		expression_base_value_f function;
    	} value_bases[] = {
    		{ "MONSTER_LEVEL", spell_value_base_monster_level },
    		{ "PLAYER_LEVEL", spell_value_base_player_level },
    		{ "DUNGEON_LEVEL", spell_value_base_dungeon_level },
    		{ "MAX_SIGHT", spell_value_base_max_sight },
    		{ "FOOD_FAINT", spell_value_base_food_faint },
    		{ "FOOD_STARVE", spell_value_base_food_starve },
    		{ "WEAPON_DAMAGE", spell_value_base_weapon_damage },
    		{ NULL, NULL },
    	};
    	const struct value_base_s *current = value_bases;
    
    	while (current->name != NULL && current->function != NULL) {
    		if (my_stricmp(name, current->name) == 0)
    			return current->function;
    
    		current++;
    	}
    
    	return NULL;
    }
    and
    Code:
    static int spell_value_base_monster_level(void)
    {
    	int level = 0;
    
    	/* Check the reference race first */
    	if (ref_race)
    		level = ref_race->level;
    	/* Otherwise the current monster if there is one */
    	else if (cave->mon_current > 0)
    		level = cave_monster(cave, cave->mon_current)->race->level;
    
    	return level;
    }

Note that the first one the data is actually not in a data file but in a .h template file, but there are cases where it is and the .h file for the preprocessor is just a link to get the name of the function compiled in.

I was going to ask which of these is better, but on looking at them I think the second one clearly wins - which is a shame, since there's only one of those and lots of the other.

Am I right? Have I missed something? Is there another method (not involving lua, python or whatever)?
__________________
One Ring to rule them all, One Ring to find them,
One Ring to bring them all and in the darkness bind them.
Nick is offline   Reply With Quote
Old November 13, 2016, 21:01   #2
Derakon
Prophet
 
Derakon's Avatar
 
Join Date: Dec 2009
Posts: 7,878
Derakon is on a distinguished road
If I understand the question correctly, you're trying to figure out how to identify a) what function needs to be invoked, and b) what parameters to pass to the function, at runtime? If so, then yes, absolutely, the explicitly coupled version is superior to using preprocessor hacks, if only for readability -- the problem I have with the preprocessor directives is that functions get defined/invoked "magically" -- there's not an obvious path in the code to get from point A to point B because the code as written isn't complete.

The value_bases array in the second example is much more like what I'd aim for. It's effectively a manually-created dispatch table, where you use a string to look up the function you want to invoke. Then someone reading the code can say "ah, this function is going to invoke one of these other functions depending on the value of this string"; nice and straightforward to comprehend.
Derakon is offline   Reply With Quote
Old November 13, 2016, 21:08   #3
Pete Mack
Prophet
 
Join Date: Apr 2007
Location: Seattle, WA
Posts: 4,043
Donated: $40
Pete Mack is on a distinguished road
Neither is better. When you want an enum, you must use a .h file, at which point it's better to put all associated info in the same place. Same holds for generated code.
When you want pure data, of course a data file is better
Pete Mack is offline   Reply With Quote
Old November 13, 2016, 21:23   #4
fizzix
Prophet
 
Join Date: Aug 2009
Location: Madison, Wisconsin, US
Posts: 2,925
fizzix is on a distinguished road
Quote:
Originally Posted by Pete Mack View Post
Neither is better. When you want an enum, you must use a .h file, at which point it's better to put all associated info in the same place. Same holds for generated code.
When you want pure data, of course a data file is better
Why can't you get an enum out of the data file? It would be something like this:

Read through file once and catalog the number of functions of different types
Make the enumerated list
Read through again and get the data
fizzix is offline   Reply With Quote
Old November 13, 2016, 21:37   #5
Pete Mack
Prophet
 
Join Date: Apr 2007
Location: Seattle, WA
Posts: 4,043
Donated: $40
Pete Mack is on a distinguished road
Enums are language constants. There is no way to initialize them at run time
Pete Mack is offline   Reply With Quote
Old November 14, 2016, 06:55   #6
Nick
Vanilla maintainer
 
Nick's Avatar
 
Join Date: Apr 2007
Location: Canberra, Australia
Age: 52
Posts: 6,518
Donated: $60
Nick is on a distinguished road
Thanks for the answers to my badly expressed question. I think I had actually answered it for myself by writing it anyway, but this has given me more to think about.
__________________
One Ring to rule them all, One Ring to find them,
One Ring to bring them all and in the darkness bind them.
Nick is offline   Reply With Quote
Old November 14, 2016, 07:21   #7
AnonymousHero
Veteran
 
AnonymousHero's Avatar
 
Join Date: Jun 2007
Posts: 1,285
AnonymousHero is on a distinguished road
I think a variation of what fizzix suggested would work:

Write a little code generator which pre-processes the data files into something usable for the C compiler, be it struct definitions or enums or whatever. An added bonus is that the generated definitions/code are easily greppable contrary to the "X macro" pattern.

(Unless I'm misunderstanding the goals here. As long as the values in the data files actually don't change at run-time, what I'm suggesting should work pretty nicely.)
AnonymousHero is offline   Reply With Quote
Old November 14, 2016, 09:47   #8
Nick
Vanilla maintainer
 
Nick's Avatar
 
Join Date: Apr 2007
Location: Canberra, Australia
Age: 52
Posts: 6,518
Donated: $60
Nick is on a distinguished road
The goal is to make the game as player modifiable - without compiling - as possible. Obviously there are limits, but I want to get close to them. And there is quite a variety of situations to deal with, so one solution won't fit all of them.
__________________
One Ring to rule them all, One Ring to find them,
One Ring to bring them all and in the darkness bind them.
Nick is offline   Reply With Quote
Old November 14, 2016, 11:37   #9
AnonymousHero
Veteran
 
AnonymousHero's Avatar
 
Join Date: Jun 2007
Posts: 1,285
AnonymousHero is on a distinguished road
Quote:
Originally Posted by Nick View Post
The goal is to make the game as player modifiable - without compiling - as possible. Obviously there are limits, but I want to get close to them. And there is quite a variety of situations to deal with, so one solution won't fit all of them.
Oh, right. Forgot the "without compiling" bit...

In that case you're going to eventually run into Greenspun's tenth rule. The only real way "out" of that is to bite the bullet and embed an interpreter/compiler rather than ending up implementing one's own (inevitably awful) variant of a LISP. However, personally, I don't think doing so is a good idea unless it's a language with LISP-level support for DSLs and meta-programs. Not mention that it's already been tried to limited success in both Angband and ToME2 -- though pretty half-heartedly AFAIUI in both cases[1]. If it were up to me, I'd rather focus on just making it easier to make changes to the C code and making it as easy as possible to compile.

[1] In the ToME2 case the interface between the C bits and the Lua bits were an awful mix of high-level and low-level -- all the C structs were basically just exported completely as-is to the Lua bits. It was incredibly error prone and the dynamic type checking of Lua exacerbated that whenever one had to interface with the C data/code; so we got basically none of the benefits of dynamic type checking, but all of the drawbacks. Not a happy place.
AnonymousHero is offline   Reply With Quote
Old November 14, 2016, 14:57   #10
Derakon
Prophet
 
Derakon's Avatar
 
Join Date: Dec 2009
Posts: 7,878
Derakon is on a distinguished road
Note that the solution I came up with for Pyrel for this problem did not involve embedding code into the data files, even though I could have trivially done that as Python has an exec() function that allows it to execute strings as programs*. Instead, if I wanted e.g. a wand that fired three elemental balls in succession, then the wand's object file entry would look something like:
Code:
"name": "Wand~ of Examples",
"alloc_depth": 50,
"alloc_rarity": 10,
...
"use_effect": [
  {
    "proc_type": "ball",
    "radius": 2,
    "target": "manual",
    "element": "darkness",
    "damage": "$plvl * (5 + 2d5)"
  },
  {
    "proc_type": "ball",
    "radius": "$plvl / 10",
    "target": "repeat",
    "element": "nether",
    "damage": "100"
  },
  {
    "proc_type": "ball",
    "radius": "100",
    "target": "repeat",
    "element": "hard light",
    "damage": "10000"
  }
]
The code that parsed these files would hand off the "use_effect" section to a separate parser, which would examine the "proc_type" entry to determine which function to call, and then hand that function the rest of the entry (with the radius, target info, element, and damage) which it would use as parameters to decide what to actually do. So somewhere in the code was a mapping of strings to functions that looked like this:
Code:
PROC_TYPES = {
  "ball": proc_ball_func,
  "beam": proc_beam_func,
  "all_los": proc_hit_everything_func,
  ...
}
All of those functions had the same signature: they took a "record" parameter, which is one of the mappings loaded from the data file. They were then responsible for extracting data from the record, providing default values for anything that wasn't present, etc.

Really the only fancy part of this is the equation evaluator.

* You should never, ever call this function as it is a security nightmare, but it does exist.
Derakon is offline   Reply With Quote
Reply


Currently Active Users Viewing This Thread: 1 (0 members and 1 guests)
 
Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump

Similar Threads
Thread Thread Starter Forum Replies Last Post
Ladder data in excel or calc eugensk00 Oook! 0 January 2, 2015 12:32
RST help files fph Development 13 September 6, 2011 20:08
delete.me files konijn_ Development 2 December 8, 2009 22:43
edit files? tummychow Vanilla 2 September 13, 2009 20:38
How about ... replacing /edit/*.txt and /data/*.raw with SQLite ? PaulBlay Development 22 June 8, 2009 03:31


All times are GMT +1. The time now is 21:10.


Powered by vBulletin® Version 3.8.7
Copyright ©2000 - 2017, vBulletin Solutions, Inc.