def init(config: 'Configuration') -> None: ''' Do some importing and set-up. ''' log_dotted = label.normalize(_DOTTED, 'init') log.start_up(log_dotted, "Initializing Veredi...") registration(config) log.start_up(log_dotted, "Initialization done.")
def add(system: System) -> SystemId: ''' Helper to take an already created system, and add it into the SystemManager's systems. Returns the SystemId assigned by SystemManager. ''' log_dotted = label.normalize(_DOTTED, 'add') log.start_up(log_dotted, f"Adding system '{str(system.klass)}'...") sid = background.manager.system.add(system) log.start_up(log_dotted, f"Added system '{str(system.klass)}' with " f"SystemId: {str(sid)}", log_success=log.SuccessType.SUCCESS) return sid
def configuration(rules: label.LabelInput, game_id: Any, path: pathlib.Path = None) -> Configuration: ''' Find config file and parse into the Configuration object for this game. `rules` is the Label for the rules type, e.g. 'veredi.rules.d20.pf2' `game_id` is the repository identity key for the specific game. `path` can be either a directory or the config file path. If `path` is None, this looks in the current directory for the first file that matches the _CONFIG_FILE_NAME_GLOBS patterns. ''' log_dotted = label.normalize(_DOTTED, 'configuration') log.start_up(log_dotted, "Creating Veredi Configuration...") # ------------------------------ # Sanity Checks? # ------------------------------ log.start_up(log_dotted, "Checking Inputs...", path, _CONFIG_FILE_NAME_GLOBS) # Normalize rules to a dotted string. rules = label.normalize(rules) # game_id... dunno much about it until we get a repository for it. # ------------------------------ # Figure out actual config file path from input path. # ------------------------------ log.start_up(log_dotted, "Finding Config File...", path, _CONFIG_FILE_NAME_GLOBS) path = _config_path(log_dotted, path) # ------------------------------ # Create Configuration. # ------------------------------ log.start_up(log_dotted, "Creating Configuration...", path, _CONFIG_FILE_NAME_GLOBS) config = Configuration(rules, game_id, config_path=path) # ------------------------------ # Done. # ------------------------------ log.start_up(log_dotted, "Done creating Veredi Configuration.", log_success=log.SuccessType.SUCCESS) return config
def create(config: Optional[Configuration], context: VerediContext, system_type: Type[System], debug_flags: Optional[DebugFlag] = None) -> SystemId: ''' Helper to create a system. Returns the created system's SystemId. ''' log_dotted = label.normalize(_DOTTED, 'create') log.start_up(log_dotted, f"Creating system '{str(system_type.klass)}'...") sid = background.manager.system.create(system_type, context) log.start_up(log_dotted, f"Created system '{str(system_type.klass)}' with " f"SystemId: {str(sid)}", log_success=log.SuccessType.SUCCESS) return sid
def server( path: pathlib.Path = None, debug_flags: Optional[DebugFlag] = None ) -> Tuple[Configuration, Engine]: ''' Start a Veredi Game Engine, ECS Managers, etc. Everything required to run a game server. Returns the configuration and the engine in a tuple. ''' log_dotted = label.normalize(_DOTTED, 'server') log.start_up(log_dotted, "Creating Veredi Server...") # --- # Find & parse config file. # --- log.start_up(log_dotted, "Creating Configuration...") config = configuration(path) # --- # Set up ECS. # --- log.start_up(log_dotted, "Creating Managers...") meeting = managers(config, debug_flags=debug_flags) # --- # Set game. # --- log.start_up(log_dotted, "Creating Game...") game_engine = engine(config, meeting, debug_flags) # --- # Set up logging. # --- # TODO: log? # TODO: log_server? # TODO: log_client? # --- # Done. # --- log.start_up(log_dotted, "Done Creating Veredi Server.", log_success=log.SuccessType.SUCCESS) return config, game_engine
def _create_or_add(log_dotted: str, config: Optional[Configuration], context: VerediContext, system_or_type: Union[System, Type[System]], debug_flags: DebugFlag, special_context: bool) -> SystemId: ''' Helper for using SysCreateType to `create`/`add` systems. ''' ctx_str = ('special context' if special_context else 'context') sid = None # System type to create? if issubclass(system_or_type, System): log.start_up( log_dotted, f"Creating system '{str(system_or_type.klass)}' " f"with {ctx_str} {str(context)}...") sid = create(config, context, system_or_type, debug_flags) log.start_up(log_dotted, f"Created system '{str(system_or_type.klass)}'.", log_success=log.SuccessType.SUCCESS) # System already created? elif isinstance(system_or_type, System): log.start_up( log_dotted, f"Adding system '{str(system_or_type.klass)}' " f"with {ctx_str} {str(context)}...") sid = create(config, context, system_or_type, debug_flags) log.start_up(log_dotted, f"Added system '{str(system_or_type.klass)}'.", log_success=log.SuccessType.SUCCESS) # Error! else: msg = "`system_or_type` must be a System instance or System type." error = ConfigError(msg, data={ 'config': config, 'context': context, 'system_or_type': system_or_type, 'debug_flags': debug_flags, 'special_context': special_context, }) raise log.exception(error, msg) return sid
def engine( configuration: Configuration, meeting: Meeting, # Optional Debug Stuff: debug_flags: Optional[DebugFlag] = None, ) -> Engine: ''' Create and configure a game engine using the other supplied parameters. ''' log_dotted = label.normalize(_DOTTED, 'engine') log.start_up(log_dotted, "Building the Engine...") # --- # Sanity. # --- if not configuration: msg = "Configuration must be provided." error = ConfigError(msg, data={ 'configuration': str(configuration), 'meeting': str(meeting), }) raise log.exception(error, msg) if not meeting: # This shouldn't happen - should raise an error before here, right? msg = "Managers' Meeting must be provided." error = ConfigError(msg, data={ 'configuration': str(configuration), 'meeting': str(meeting), }) raise log.exception(error, msg) # --- # Create engine. # --- log.start_up(log_dotted, "Initializing the Engine...") owner = None campaign_id = None engine = Engine(owner, campaign_id, configuration, meeting, debug_flags) log.start_up(log_dotted, "Done building the Engine.", log_success=log.SuccessType.SUCCESS) return engine
def many(config: Optional[Configuration], context: VerediContext, *args: SysCreateType, debug_flags: Optional[DebugFlag] = None) -> List[SystemId]: ''' Helper to `create`/`add` many systems. See SysCreateType docstr for what it accepts. Returns a list of SystemIds. If tuple, will use tuple's context, else uses the `context` arg. e.g.: systems(config, context, SomeSystem) # could just use `create` or add` systems(config, context, (SomeSystem, different_context)) # uses `different_context` systems(config, context, (SomeSystem, context_for_SomeSystem), # ignores `context` TwoSystem) # uses `context` ''' log_dotted = label.normalize(_DOTTED, 'many') log.start_up(log_dotted, f"Creating {len(args)} systems...") sids = [] for each in args: sid = None system_or_type = None system_context = None context_unique = False if isinstance(each, tuple): # Have a special context in tuple - ignore the "default" `context` # arg. system_or_type = each[0] system_context = each[1] context_unique = True else: # Use the normal `context` arg. system_or_type = each system_context = context context_unique = False log.start_up( log_dotted, f"Processing system '{str(system_or_type.klass)}' for " f"adding/creating...") # Alrighty - do the thing with the system. sid = _create_or_add(log_dotted, config, system_context, system_or_type, debug_flags, context_unique) sids.append(sid) log.start_up(log_dotted, f"System '{str(system_or_type.klass)}' processed.", log_success=log.SuccessType.SUCCESS) log.start_up(log_dotted, f"Created {len(sids)} systems with SystemIds: {str(sids)}", log_success=log.SuccessType.SUCCESS) return sids
def _set_up(self) -> VerediHealth: ''' Sanity checks before loading, and any additional set-up required after initialization. Raises ConfigError ''' # TODO: Move creation of our repo/serdes here? log.start_up(self.dotted, "Configuration set-up...") if not self._path: log.start_up(self.dotted, "Configuration set-up: No path! Erroring out...", log_success=False) raise log.exception(ConfigError, "No path for config data after loading!") if not self._codec: log.start_up(self.dotted, "Configuration set-up: No Codec! Erroring out...", log_success=False) raise log.exception(ConfigError, "No codec for config data after loading!") if not self._serdes: log.start_up(self.dotted, "Configuration set-up: No Serdes! Erroring out...", log_success=False) raise log.exception(ConfigError, "No serdes for config data after loading!") if not self._repo: log.start_up(self.dotted, "Configuration set-up: No Repository! " "Erroring out...", log_success=False) raise log.exception( ConfigError, "No repository for config data after loading!") log.start_up(self.dotted, "Done setting up Configuration.", log_success=True) return VerediHealth.HEALTHY
def __init__(self, rules: label.LabelInput, game_id: Any, config_path: Optional[pathlib.Path] = None, config_repo: Optional['BaseRepository'] = None, config_serdes: Optional['BaseSerdes'] = None, config_codec: Optional[Codec] = None) -> None: ''' Create a Configuration object for the game's set-up. `rules_dotted` is the veredi dotted label of the game's rules type. example: 'veredi.rules.d20.pf2.game' `game_id` is the Repository id/key/record-name for the game's Saved records. `config_repo`, `config_serdes`, and `config_codec` are the repository, serdes, and codec to use for data about the game's set-up (Definitions and Saved records). The game's data (players, items, etc) is delt with through DataManager with a different Repository/Serdes/Codec. `Config_path` is an optional override of the default_path() used to find the general Veredi configuration data. Raises LoadError and ConfigError ''' log.start_up(self.dotted, "Creating Configuration...") self._define_vars() # --- # Sanity Check... # --- preexisting = background.config.link(background.Link.CONFIG) if preexisting: # Log start-up error... msg = ("A Configuration already exists in the background context! " "Two configs cannot be used to initialize Veredi.") log.start_up(self.dotted, msg + ' Pre-existing: {}', preexisting, success=False) # ...And error out. raise background.config.exception(None, msg) # --- # Params # --- self._rules = label.normalize(rules) self._id = game_id log.start_up( self.dotted, "Creating Configuration for: " f"rules: '{self._rules}', game-id: '{self._id}'...") self._path = config_path or default_path() log.start_up(self.dotted, "Configuration path (using {}): {}", ('provided' if config_path else 'default'), self._path) try: # --- # NOTE: # Config's Repo & Serdes are not the game's repo/serdes. Ours are # used for loading the config data itself, and so: # 1) Can't rely on config data to configure themselves. # 2) Are the type for loading "this file (or data) exactly". # --- context = ConfigContext(self._path, self.dotted) # --- # Config Repository # --- # This will usually be a FileBareRepository created by us, but # allow it to be passed in. self._repo = config_repo if not self._repo: self._repo = FileBareRepository(context) log.start_up(self.dotted, " Created config's repo: '{}'", self._repo.dotted) else: log.start_up(self.dotted, " Using passed in repo: '{}'", self._repo.dotted) # --- # Config Serdes # --- # This will usually be a YamlSerdes created by us, but allow it to # be passed in. self._serdes = config_serdes if not self._serdes: self._serdes = YamlSerdes() log.start_up(self.dotted, " Created config's serdes: '{}'", self._serdes.dotted) else: log.start_up(self.dotted, " Using passed in serdes: '{}'", self._serdes.dotted) # --- # Config Codec # --- # This will usually be a Codec created by us, but allow it to # be passed in. self._codec = config_codec if not self._codec: self._codec = Codec() log.start_up(self.dotted, " Created config's codec: '{}'", self._codec.dotted) else: log.start_up(self.dotted, " Using passed in codec: '{}'", self._codec.dotted) # --- # Background # --- # Do background stuff after repo/serdes so that they cannot try to # use us to make stuff from the config we haven't loaded yet. log.start_up(self.dotted, "Setting Configuration into background data...") # Set ourself into the background. background.config.init(self._path, self) self._set_background() log.start_up(self.dotted, "Configuration set into background data.") except Exception as err: log.start_up(self.dotted, "Configuration failed to initialize... Erroring out.", log_success=False) raise log.exception( ConfigError, "Found an exception when creating config.") from err # --- # Finalize: Load & Set-Up Configuration # --- log.start_up(self.dotted, "Configuration final load, set-up...") self._load() self._set_up() log.start_up(self.dotted, "Done initializing Configuration.", log_success=True)
def managers(configuration: Configuration, time_manager: Optional[TimeManager] = None, event_manager: Optional[EventManager] = None, component_manager: Optional[ComponentManager] = None, entity_manager: Optional[EntityManager] = None, system_manager: Optional[SystemManager] = None, data_manager: Optional[DataManager] = None, identity_manager: Optional[IdentityManager] = None, debug_flags: Optional[DebugFlag] = None) -> Meeting: ''' Creates a Meeting of EcsManagers. Any/all managers may be provided as arguments. If a manager is not provided, one will be created using the `configuration`, which must be provided. ''' log_dotted = label.normalize(_DOTTED, 'managers') log.start_up(log_dotted, "Creating Meeting of EcsManagers...") # --- # Sanity. # --- if not configuration: msg = "Configuration must be provided." error = ConfigError(msg, data={ 'configuration': str(configuration), }) raise log.exception(error, msg) # --- # Create Managers. # --- # Create any managers that weren't passed in. # Time time = time_manager or TimeManager(debug_flags=debug_flags) log.start_up(log_dotted, "Created TimeManager.") # Event event = event_manager or EventManager(configuration, debug_flags) log.start_up(log_dotted, "Created EventManager.") # Component component = component_manager or ComponentManager(configuration, event, debug_flags) log.start_up(log_dotted, "Created ComponentManager.") # Entity entity = entity_manager or EntityManager(configuration, event, component, debug_flags) log.start_up(log_dotted, "Created EntityManager.") # System system = system_manager or SystemManager(event, debug_flags) log.start_up(log_dotted, "Created SystemManager.") # Data data = data_manager or DataManager(configuration, time, event, component, debug_flags) log.start_up(log_dotted, "Created DataManager.") # Identity identity = identity_manager or IdentityManager(configuration, time, event, entity, debug_flags) log.start_up(log_dotted, "Created IdentityManager.") # --- # Finish up. # --- # And now we can create the Manager's Meeting itself. meeting = Meeting(time, event, component, entity, system, data, identity, debug_flags) log.start_up(log_dotted, "Created Meeting of Managers.") # Save to background and return. mtg_bg_data, mtg_bg_owner = meeting.get_background() background.manager.set(meeting.dotted, meeting, mtg_bg_data, mtg_bg_owner) log.start_up(log_dotted, "Set managers into background context.") log.start_up( log_dotted, "Finalize TimeManager's initialization after " "Meeting creation...") # TimeManager has to delay some initialization until after other managers # are created. time.finalize_init(data) log.start_up(log_dotted, "TimeManager fully initialized.") log.start_up(log_dotted, "Done Creating Meeting of EcsManagers.", log_success=log.SuccessType.SUCCESS) return meeting
def _config_path(log_dotted: label.DotStr, path: pathlib.Path = None) -> pathlib.Path: ''' Find config file from `path` and parse into the Configuration object for the game. `path` can be either a directory or the config file path. If `path` is None, this looks in the current directory for the first file that matches the _CONFIG_FILE_NAME_GLOBS patterns. ''' # --- # Default path? # --- if path: log.start_up(log_dotted, "Looking for config in provided path: {}", path) else: path = pathlib.Path(os.getcwd()) log.start_up(log_dotted, "Set config path to current working directory: {}", path) # --- # Sanity checks for path. # --- if not path.exists(): msg = "Configuration path doesn't exist." error = ConfigError(msg, data={ 'path': str(path), 'exists': path.exists(), 'is_file': path.is_file(), 'is_dir': path.is_dir(), }) raise log.exception(error, msg + f" path: {path}") if not path.is_dir() and not path.is_file(): msg = "Configuration path must be a file or directory." error = ConfigError(msg, data={ 'path': str(path), 'exists': path.exists(), 'is_file': path.is_file(), 'is_dir': path.is_dir(), }) raise log.exception(error, msg + f" path: {path}") # --- # Get config. # --- # Already checked that it's either file or dir, so: if path.is_dir(): log.start_up( log_dotted, "Path is a directory; look for config file " "by glob: {} {}", path, _CONFIG_FILE_NAME_GLOBS) # Look for files matching the glob. Claim the first one and ignore any # others. claim = None for glob in _CONFIG_FILE_NAME_GLOBS: for match in path.glob(glob): if not match.is_file(): continue claim = match log.start_up( log_dotted, "Found config file '{}' by glob '{}' in directory '{}'.", claim, glob, path, _CONFIG_FILE_NAME_GLOBS, log_success=log.SuccessType.SUCCESS) break if claim: break # Nothing found? Something wrong? if not claim or not claim.exists() or not claim.is_file(): msg = "No config file found in supplied directory." error = ConfigError(msg, data={ 'dir': str(path), 'globs': _CONFIG_FILE_NAME_GLOBS, 'claim': claim, }) raise log.exception( error, msg + f" dir: {str(path)}, claim: {str(claim)}") # Got a file to claim; set it as our path now. path = claim.resolve() log.start_up(log_dotted, "Config file path resolved to: {}", path, _CONFIG_FILE_NAME_GLOBS, log_success=log.SuccessType.NEUTRAL) # Path is a file. Not much needs done. else: path = path.resolve() log.start_up(log_dotted, "Path is a file; resolved to: {}", path, log_success=log.SuccessType.NEUTRAL) log.start_up(log_dotted, "Final path to config file: {}", path, log_success=log.SuccessType.SUCCESS) return path