This document describes the use of the Panda's Config.prc configuration files and the runtime subsystem that extracts values from these files, defined in dtool/src/prc. USING THE PRC FILES In its default mode, when Panda starts up it will search in the install/etc directory (or in the directory named by the environment variable PRC_DIR if it is set) for all files named *.prc (that is, any files with an extension of "prc") and read each of them for runtime configuration. (It is possible to change this default behavior; see COMPILE-TIME OPTIONS FOR FINDING PRC FILES, below.) All of the prc files are loaded in alphabetical order, so that the files that have alphabetically later names are loaded last. Since variables defined in an later file may shadow variables defined in an earlier file, this means that filenames towards the end of the alphabet have the most precedence. Panda by default installs a handful of system prc files into the install/etc directory. These files have names beginning with digits, like 20_panda.prc and 40_direct.prc, so that they will be loaded in a particular order. If you create your own prc file in this directory, we recommend that you begin its filename with letters, so that it will sort to the bottom of the list and will therefore override any of the default variables defined in the system prc files. Within a particular prc file, you may define any number of configuration variables and their associated value. Each definition must appear one per line, with at least one space separating the variable and its definition, e.g.: load-display pandagl This specifies that the variable "load-display" should have the value "pandagl". Comments may also appear in the file; they are introduced by a leading hash mark (#). A comment may be on a line by itself, or it may be on the same line following a variable definition; if it is on the same line as a variable definition, the hash mark must be preceded by at least one space to separate it from the definition. The legal values that you may specify for any particular variable depends on the variable. The complete list of available variables and the valid values for each is not documented here (a list of the most commonly modified variables appears in another document, but also see cvMgr.listVariables(), below). Many variables accept any string value (such as load-display, above); many others, such as aspect-ratio, expect a numeric value. A large number of variables expect a simple boolean true/false value. You may observe the Python convention of using 0 vs. 1 to represent false vs. true; or you may literally type "false" or "true", or just "f" and "t". For historical reasons, Panda also recognizes the Scheme convention of "#f" and "#t". Most variables only accept one value at a time. If there are two different definitions for a given variable in the same file, the topmost definition applies. If there are two different definitions in two different files, the definition given in the file loaded later applies. However, some variables accept multiple values. This is particularly common for variables that name search directories, like model-path. In the case of this kind of variable, all definitions given for the variable are taken together; it is possible to extend the definition by adding another prc file, but you cannot remove any value defined in a previously-loaded prc file. DEFINING CONFIG VARIABLES New config variables may be defined on-the-fly in either C++ or Python code. To do this, create an instance of one of the following classes: ConfigVariableString ConfigVariableBool ConfigVariableInt ConfigVariableDouble ConfigVariableFilename ConfigVariableEnum (C++ only) ConfigVariableList ConfigVariableSearchPath These each define a config variable of the corresponding type. For instance, a ConfigVariableInt defines a variable whose value must always be an integer value. The most common variable types are the top four, which are self-explanatory; the remaining four are special types: ConfigVariableFilename - This is a convenience class which behaves very much like a ConfigVariableString, except that it automatically converts from OS-specific filenames that may be given in the prc file to Panda-specific filenames, and it also automatically expands environment variable references, so that the user may name a file based on the value of an environment variable (e.g. $PANDAMODELS/file.egg). ConfigVariableEnum - This is a special template class available in C++ only. It provides a convenient way to define a variable that may accept any of a handful of different values, each of which is defined by a keyword. For instance, the text-encoding variable may be set to any of "iso8859", "utf8", or "unicode", which correspond to TextEncoder::E_iso8859, E_utf8, and E_unicode, respectively. The ConfigVariableEnum class relies on a having sensible pair of functions defined for operator << (ostream) and operator >> (istream) for the enumerated type. These two functions should reverse each other, so that the output operator generates a keyword for each value of the enumerated type, and the input operator recognizes each of the keywords generated by the output operator. This is a template class. It is templated on its enumerated type, e.g. ConfigVariableEnum. ConfigVariableList - This class defines a special config variable that records all of its definitions appearing in all prc files and retrieves them as a list, instead of a standard config variable that returns only the topmost definition. (See "some variables accept multiple values", above.) Unlike the other kinds of config variables, a ConfigVariableList is read-only; it can be modified only by loading additional prc files, rather than directly setting its value. Also, its constructor lacks a default_value parameter, since there is no default value (if the variable is not defined in any prc file, it simply returns an empty list). ConfigVariableSearchPath - This class is very similar to a ConfigVariableList, above, except that it is intended specifically to represent the multiple directories of a search path. In general, a ConfigVariableSearchPath variable can be used in place of a DSearchPath variable. Unlike ConfigVariableList, instances of this variable can be locally modified by appending or prepending additional directory names. In general, each of the constructors to the above classes accepts the following parameters: (name, default_value, description = "", flags = 0) The default_value parameter should be of the same type as the variable itself; for instance, the default_value for a ConfigVariableBool must be either true or false. The ConfigVariableList and ConfigVariableSearchPath constructors do not have a default_value parameter. The description should be a sentence or two describing the purpose of the variable and the effects of setting it. It will be reported with variable.getDescription() or ConfigVariableManager.listVariables(); see QUERYING CONFIG VARIABLES, below. The flags variable is usually set to 0, but it may be an integer trust level and/or the union of any of the values in the enumerated type ConfigFlags::VariableFlags. For the most part, this is used to restrict the variable from being set by unsigned prc files. See SIGNED PRC FILES, below. Once you have created a config variable of the appropriate type, you may generally treat it directly as a simple variable of that type. This works in both C++ and in Python. For instance, you may write code such as this: ConfigVariableInt foo_level("foo-level", -1, "The level of foo"); if (foo_level < 0) { cerr << "You didn't specify a valid foo_level!\n"; } else { // Four snarfs for every foo. int snarf_level = 4 * foo_level; } In rare cases, you may find that the implicit typecast operators aren't resolved properly by the compiler; if this happens, you can use variable.get_value() to retrieve the variable's value explicitly. DIRECTLY ASSIGNING CONFIG VARIABLES In general, config variables can be directly assigned values appropriate to their type, as if they were ordinary variables. In C++, the assignment operator is overloaded to perform this function, e.g.: foo_level = 5; In Python, this syntax is not possible--the assignment operator in Python completely replaces the value of the assigned symbol and cannot be overloaded. So the above statement in Python would replace foo_level with an actual integer of the value 5. In many cases, this is close enough to what you intended anyway, but if you want to keep the original functionality of the config variable (e.g. so you can restore it to its original value later), you need to use the set_value() method instead, like this: fooLevel.setValue(5) When you assign a variable locally, the new definition shadows all prc files that have been read or will ever be read, until you clear your definition. To restore a variable to its original value as defined by the topmost prc file, use clear_local_value(): fooLevel.clearLocalValue() This interface for assigning config variables is primarily intended for the convenience of developing an application interactively; it is sometimes useful to change the value of a variable on the fly. QUERYING CONFIG VARIABLES There are several mechanisms for finding out the values of individual config variables, as well as for finding the complete list of available config variables. In particular, one easy way to query an existing config variable's value is simply to create a new instance of that variable, e.g.: print ConfigVariableInt("foo-level") The default value and comment are optional if another instance of the same config variable has previously been created, supplying these parameters. However, it is an error if no instance of a particular config variable specifies a default value. It is also an error (but it is treated as a warning) if two different instances of a variable specify different default values. (Note that, although it is convenient to create a new instance of the variable in order to query or modify its value interactively, we recommend that all the references to a particular variable in code should use the same instance wherever possible. This minimizes the potential confusion about which instance should define the variable's default value and/or description, and reduces chance of conflicts should two such instances differ.) If you don't know the type of the variable, you can also simply create an instance of the generic ConfigVariable class, for the purpose of querying an existing variable only (you should not define a new variable using the generic class). To find out more detail about a variable and its value, use the ls() method in Python (or the write() method in C++), e.g.: ConfigVariable("foo-level").ls() In addition to the variable's current and default values, this also prints a list of all of the prc files that contributed to the value of the variable, as well as the description provided for the variable. To get a list of all known config variables, use the methods on ConfigVariableManager. In C++, you can get a pointer this object via ConfigVariableManager::get_global_ptr(); in Python, use the cvMgr builtin, created by ShowBase.py. print cvMgr Lists all of the variables in active use: all of the variables whose value has been set by one or more prc files, along with the name of the prc file that defines that value. cvMgr.listVariables() Lists all of the variables currently known to the config system; that is, all variables for which a ConfigVariable instance has been created at runtime, whether or not its value has been changed from the default. This may omit variables defined in some unused subsystem (like pandaegg, for instance), and it will omit variables defined by Python code which hasn't yet been executed (e.g. variables within defined with a function that hasn't yet been called). This will also omit variables deemed to be "dynamic" variables, for instance all of the notify-level-* variables, and variables such as pstats-active-*. These are omitted simply to keep the list of variable names manageable, since the list of dynamic variable names tends to be very large. Use cvMgr.listDynamicVariables() if you want to see these variable names. cvMgr.listUnusedVariables() Lists all of the variables that have been defined by some prc file, but which are not known to the config system (no ConfigVariable instance has yet been created for this variable). These variables may represent misspellings or typos in your prc file, or they may be old variables which are no longer used in the system. However, they may also be legitimate variables for some subsystem or application which simply has not been loaded; there is no way for Panda to make this distinction. RE-READING PRC FILES If you modify a prc file at some point after Panda has started, Panda will not automatically know that it needs to reload its config files and will not therefore automatically recognize your change. However, you can force this to happen by making the following call: ConfigPageManager::get_global_ptr()->reload_implicit_pages() Or, in Python: cpMgr.reloadImplicitPages() This will tell Panda to re-read all of the prc files it found automatically at startup and update the variables' values accordingly. RUNTIME PRC FILE MANAGEMENT In addition to the prc files that are found and loaded automatically by Panda at startup, you can load files up at runtime as needed. The functions to manage this are defined in load_prc_file.h: ConfigPage *page = load_prc_file("myPage.prc") ... unload_prc_file(page); (The above shows the C++ syntax; the corresponding Python code is similar, but of course the functions are named loadPrcFile() and unloadPrcFile().) That is to say, you can call load_prc_file() to load up a new prc file at any time. Each file you load is added to a LIFO stack of prc files. If a variable is defined in more than one prc file, the topmost file on the stack (i.e. the one most recently loaded) is the one that defines the variable's value. You can call unload_prc_file() at any time to unload a file that you have previously loaded. This removes the file from the stack and allows any variables it modified to return to their previous value. The single parameter to unload_prc_file() should be the pointer that was returned from the corresponding call to load_prc_file(). Once you have called unload_prc_file(), the pointer is invalid and should no longer be used. It is an error to call unload_prc_file() twice on the same pointer. The filename passed to load_prc_file() may refer to any file that is on the standard prc file search path (e.g. $PRC_DIR), as well as on the model-path. It may be a physical file on disk, or a subfile of a multifile (and mounted via Panda's virtual file system). If your prc file is stored as an in-memory string instead of as a disk file (for instance, maybe you just built it up), you can use the load_prc_file_data() method to load the prc file from the string data. The first parameter is an arbitrary name to assign to your in-memory prc file; supply a filename if you have one, or use some other name that is meaningful to you. You can see the complete list of prc files that have been loaded into the config system at any given time, including files loaded explicitly via load_prc_file(), as well as files found in the standard prc file search path and loaded implicitly at startup. Simply use ConfigPageManager::write(), e.g. in Python: print cpMgr COMPILE-TIME OPTIONS FOR FINDING PRC FILES As described above in USING THE PRC FILES, Panda's default startup behavior is to load all files named *.prc in the directory named by the environment variable PRC_DIR. This is actually a bit of an oversimplification. The complete default behavior is as follows: (1) If PRC_PATH is set, separate it into a list of directories and make a search path out of it. (2) If PRC_DIR is set, prepend it onto the search path defined by PRC_PATH, above. (3) If neither was set, put the compiled-in value for DEFAULT_PRC_DIR, which is usually the install/etc directory, alone on the search path. Steps (1), (2), and (3) define what is referred to in this document as "the standard prc search path". You can query this search path via cpMgr.getSearchPath(). (4) Look for all files named *.prc on each directory of the resulting search path, and load them up in reverse search path order, and within each directory, in forward alphabetical order. This means that directories listed first on the search path override directories listed later, and within a directory, files alphabetically later override files alphabetically earlier. This describes the default behavior, without any modifications to Config.pp. If you wish, you can further fine-tune each of the above steps by defining various Config.pp variables at compile time. The following Config.pp variables may be defined: #define PRC_PATH_ENVVARS PRC_PATH #define PRC_DIR_ENVVARS PRC_DIR These name the environment variable(s) to use instead of PRC_PATH and PRC_DIR. In either case, you may name multiple environment variables separated by a space; each variable is consulted one at a time, in the order named, and the results are concatenated. For instance, if you put the following line in your Config.pp file: #define PRC_PATH_ENVVARS CFG_PATH ETC_PATH Then instead of checking $PRC_PATH in step (1), above, Panda will first check $CFG_PATH, and then $ETC_PATH, and the final search path will be the concatenation of both. You can also define either or both of PRC_PATH_ENVVARS or PRC_DIR_ENVVARS to the empty string; this will disable runtime checking of environment variables, and force all prc files to be loaded from the directory named by DEFAULT_PRC_DIR. #define PRC_PATTERNS *.prc This describes the filename patterns that are used to identify prc files in each directory in step(4), above. The default is *.prc, but you can change this if you have any reason to. You can specify multiple filename patterns separated by a space. For instance, if you still have some config files named "Configrc", following an older Panda convention, you can define the following in your Config.pp file: #define PRC_PATTERNS *.prc Configrc This will cause Panda to recognize files named "Configrc", as well as any file ending in the extension prc, as a legitimate prc file. #define DEFAULT_PRC_DIR $[INSTALL_DIR]/etc This is the directory from which to load prc files if all of the variables named by PRC_PATH_ENVVARS and PRC_DIR_ENVVARS are undefined or empty. #define DEFAULT_PATHSEP This doesn't strictly apply to the config system, since it globally affects search paths throughout Panda. This specifies the character or characters used to separate the different directory names of a search path, for instance $PRC_PATH. The default character is ':' on Unix, and ';' on Windows. If you specify multiple characters, any of them may be used as a separator. EXECUTABLE PRC FILES One esoteric feature of Panda's config system is the ability to automatically execute a standalone program which generates a prc file as output. This feature is not enabled by default. To enable it, you must define the Config.pp variable PRC_EXECUTABLE_PATTERNS before you build Panda. This variable is similar to PRC_PATTERNS, described above, except it names file names which, when found along the standard prc search path, should be taken to be the name of an executable program. Panda will execute each of these programs, in the appropriate order according to alphabetical sorting with the regular prc files, and whatever the program writes to standard output is taken to be the contents of a prc file. By default the contents of the environment variable $PRC_EXECUTABLE_ARGS are passed as arguments to the executable program. You can change this to a different environment variable by redefining PRC_EXECUTABLE_ARGS_ENVVAR in your Config.pp (or prevent the passing of arguments by defining this to the empty string). SIGNED PRC FILES Another esoteric feature of Panda's config system is the ability to restrict certain config variables to modification only by a prc file that has been provided by an authorized source. This is primarily useful when Panda is to be used for deployment of applications (games, etc.) to a client; it has little utility in a fully trusted environment. When this feature is enabled, you can specify an optional trust level to each ConfigVariable constructor. The trust level is an integer value, greater than 0 (and <= ConfigFlags::F_trust_level_mask), which should be or'ed in with the flags parameter. A number of random keys must be generated ahead of time and compiled into Panda; there must be a different key for each different trust level. Each prc file can then optionally be signed by exactly one of the available keys. When a prc file has been signed by a recognized key, Panda assigns the corresponding trust level to that prc file. An unsigned prc file has an implicit trust level of 0. If a signed prc file is modified in any way after it has been signed, its signature will no longer match the contents of the file and its trust level drops to 0. The newly-modified file must be signed again to restore its trust level. When a ConfigVariable is constructed with a nonzero trust level, that variable's value may then not be set by any prc file with a trust level lower that the variable's trust level. If a prc file with an insufficient trust level attempts to modify the variable, the new value is ignored, and the value from the previous trusted prc file (or the variable's default value) is retained. The default trust level for a ConfigVariable is 0, which means the variable can be set by any prc file, signed or unsigned. To set any nonzero trust level, pass the integer trust level value as the flags parameter to the ConfigVariable constructor. To explicitly specify a trust level of 0, pass ConfigFlags::F_open. To specify a ConfigVariable that cannot be set by any prc files at all, regardless of trust level, use ConfigFlags::F_closed. This feature is not enabled by default. It is somewhat complicated to enable this feature, because doing so requres generating one or more private/public key pairs, and compiling the public keys into the low-level Panda system so that it can recognize signed prc files when they are provided, and compiling the private keys into standalone executables, one for each private key, that can be used to officially sign approved prc files. This initial setup therefore requires a bit of back-and-forth building and rebuilding in the dtool directory. To enable this feature, follow the following procedure. (1) Decide how many different trust levels you require. You can have as many as you want, but most applications will require only one trust level, or possibly two. The rare application will require three or more. If you decide to use multiple trust levels, you can make a distinction between config variables that are somewhat sensitive and those that are highly sensitive. (2) Obtain and install the OpenSSL library, if it is not already installed (http://www.openssl.org). Adjust your Config.pp file as necessary to point to the installed OpenSSL headers and libraries (in particular, define SSL_IPATH and SSL_LIBS), and then ppremake and make install your dtool tree. It is not necessary to build the panda tree or any subsequent trees yet. (3) Set up a directory to hold the generated public keys. The contents of this directory must be accessible to anyone building Panda for your application; it also must have a lifetime at least as long as the lifetime of your application. It probably makes sense to make this directory part of your application's source tree. The contents of this directory will not be particularly sensitive and need not be kept any more secret than the rest of your application's source code. (4) Set up a directory in a secure place to hold the generated private keys. The contents of this directory should be regarded as somewhat sensitive, and should not be available to more than a manageable number of developers. It need not be accessible to people building Panda. However, this directory should have a lifetime as long as the lifetime of your application. Depending on your environment, it may or may not make sense to make this directory a part of your application's source tree; it can be the same directory as that chosen for (3), above. (5) Run the program make-prc-key. This program generates the public and private key pairs for each of your trust levels. The following is an example: make-prc-key -a /keys.cxx -b /sign#.cxx 1 2 The output of make-prc-key will be compilable C++ source code. The first parameter, -a, specifies the name of the public key output file. This file will contain all of the public keys for the different trust levels, and will become part of the libdtool library. It is not particularly sensitive, and must be accessible to anyone who will be compiling dtool. The second parameter, -b, specifies a collection of output files, one for each trust level. Each file can be compiled as a standalone program (that links with libdtool); the resulting program can then be used to sign any prc files with the corresponding trust level. The hash character '#' appearing in the filename will be filled in with the numeric trust level. The remaining arguments to make-prc-key are the list of trust levels to generate key pairs for. In the example above, we are generating two key pairs, for trust level 1 and for trust level 2. The program will prompt you to enter a pass phrase for each private key. This pass phrase is used to encrypt the private key as written into the output file, to reduce the sensitivity of the prc signing program (and its source code). The user of the signing program must re-enter this pass phrase in order to sign a prc file. You may specify a different pass phrase for each trust level, or you may use the -p "pass phrase" command-line option to provide the same pass phrase for all trust levels. If you do not want to use the pass phrase feature at all, use -p "", and keep the generated programs in a safe place. (6) Modify your Config.pp file (for yourself, and for anyone else who will be building dtool for your application) to add the following line: #define PRC_PUBLIC_KEYS_FILENAME /keys.cxx Where /keys.cxx is the file named by -a, above. Consider whether you want to enforce the trust level in the development environment. The default is to respect the trust level only when Panda is compiled for a release build, i.e. when OPTIMIZE is set to 4. You can redefine PRC_RESPECT_TRUST_LEVEL if you want to change this default behavior. Re-run ppremake and then make install in dtool. (7) Set up a Sources.pp file in your private key directory to compile the files named by -b against dtool. It should contain an entry something like these for each trust level: #begin bin_target #define OTHER_LIBS dtool #define USE_PACKAGES ssl #define TARGET sign1 #define SOURCES sign1.cxx #end bin_target #begin bin_target #define OTHER_LIBS dtool #define USE_PACKAGES ssl #define TARGET sign2 #define SOURCES sign2.cxx #end bin_target (8) If your private key directory is not a part of your application source hierarchy (or your application does not use ppremake), create a Package.pp in the same directory to mark the root of a ppremake source tree. You can simply copy the Package.pp file from panda/Package.pp. You do not need to do this if your private key directory is already part of a ppremake-controlled source hierarchy. (9) Run ppremake and then make install in the private key directory. This will generate the programs sign1 and sign2 (or whatever you have named them). Distribute these programs to the appropriate people who have need to sign prc files, and tell them the pass phrases that you used to generate them. (10) Build the rest of the Panda trees normally. Advanced tip: if you follow the directions above, your sign1 and sign2 programs will require libdtool.dll at runtime, and may need to be recompiled from time to time if you get a new version of dtool. To avoid this, you can link these programs statically, so that they are completely standalone. This requires one more back-and-forth rebuilding of dtool: (a) Put the following line in your Config.pp file: #define LINK_ALL_STATIC 1 (b) Run ppremake and make clean install in dtool. Note that you must make clean. This will generate a static version of libdtool.lib. (c) Run ppremake and make clean install in your private key directory, to recompile the sign programs against the new static libdtool.lib. (d) Remove (or comment out) the LINK_ALL_STATIC line in your Config.pp file. (e) Run ppremake and make clean install in dtool to restore the normal dynamic library, so that future builds of panda and the rest of your application will use the dynamic libdtool.dll properly.