317 lines
12 KiB
Python
317 lines
12 KiB
Python
"""
|
|
|
|
RenderPipeline
|
|
|
|
Copyright (c) 2014-2016 tobspr <tobias.springer1@gmail.com>
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
in the Software without restriction, including without limitation the rights
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in
|
|
all copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
THE SOFTWARE.
|
|
|
|
"""
|
|
|
|
import os
|
|
import atexit
|
|
|
|
from panda3d.core import Filename, VirtualFileSystem, get_model_path
|
|
from panda3d.core import VirtualFileMountRamdisk
|
|
from direct.stdpy.file import join, isdir, isfile
|
|
|
|
from rpcore.rpobject import RPObject
|
|
|
|
|
|
class MountManager(RPObject):
|
|
|
|
""" This classes mounts the required directories for the pipeline to run.
|
|
This is important if the pipeline is in a subdirectory for example. The
|
|
mount manager also handles the lock, storing the current PID into a file
|
|
named instance.pid and ensuring that there is only 1 instance of the
|
|
pipeline running at one time. """
|
|
|
|
def __init__(self, pipeline):
|
|
""" Creates a new mount manager """
|
|
RPObject.__init__(self)
|
|
self._pipeline = pipeline
|
|
self._base_path = self._find_basepath()
|
|
self._lock_file = "instance.pid"
|
|
self._model_paths = []
|
|
self._write_path = None
|
|
self._mounted = False
|
|
self._do_cleanup = True
|
|
self._config_dir = None
|
|
|
|
self.debug("Auto-Detected base path to", self._base_path)
|
|
atexit.register(self._on_exit_cleanup)
|
|
|
|
@property
|
|
def write_path(self):
|
|
""" Returns the write path previously set with set_write_path, or None
|
|
if no write path has been set yet. """
|
|
return self._write_path
|
|
|
|
@write_path.setter
|
|
def write_path(self, pth):
|
|
""" Set a writable directory for generated files. This can be a string
|
|
path name or a multifile with openReadWrite(). If no pathname is set
|
|
then the root directory is used.
|
|
|
|
This feature is usually only used for debugging, the pipeline will dump
|
|
all generated shaders and other temporary files to that directory.
|
|
If you don't need this, you can use set_virtual_write_path(), which
|
|
will create the temporary path in the VirtualFileSystem, thus not
|
|
writing any files to disk. """
|
|
if pth is None:
|
|
self._write_path = None
|
|
self._lock_file = "instance.pid"
|
|
else:
|
|
self._write_path = Filename.from_os_specific(pth).get_fullpath()
|
|
self._lock_file = join(self._write_path, "instance.pid")
|
|
|
|
@property
|
|
def base_path(self):
|
|
""" Returns the base path of the pipeline. This returns the path previously
|
|
set with set_base_path, or the auto detected base path if no path was
|
|
set yet """
|
|
return self._base_path
|
|
|
|
@base_path.setter
|
|
def base_path(self, pth):
|
|
""" Sets the path where the base shaders and models on are contained. This
|
|
is usually the root of the rendering pipeline folder """
|
|
self.debug("Set base path to '" + pth + "'")
|
|
self._base_path = Filename.from_os_specific(pth).get_fullpath()
|
|
|
|
@property
|
|
def config_dir(self):
|
|
""" Returns the config directory previously set with set_config_dir, or
|
|
None if no directory was set yet """
|
|
|
|
@config_dir.setter
|
|
def config_dir(self, pth):
|
|
""" Sets the path to the config directory. Usually this is the config/
|
|
directory located in the pipeline root directory. However, if you want
|
|
to load your own configuration files, you can specify a custom config
|
|
directory here. Your configuration directory should contain the
|
|
pipeline.yaml, plugins.yaml, daytime.yaml and configuration.prc.
|
|
|
|
It is highly recommended you use the pipeline provided config files, modify
|
|
them to your needs, and as soon as you think they are in a final version,
|
|
copy them over. Please also notice that you should keep your config files
|
|
up-to-date, e.g. when new configuration variables are added.
|
|
|
|
Also, specifying a custom configuration_dir disables the functionality
|
|
of the PluginConfigurator and DayTime editor, since they operate on the
|
|
pipelines default config files.
|
|
|
|
Set the directory to None to use the default directory. """
|
|
self._config_dir = Filename.from_os_specific(pth).get_fullpath()
|
|
|
|
@property
|
|
def do_cleanup(self):
|
|
""" Returns whether the mount manager will attempt to cleanup the
|
|
generated files after the application stopped running """
|
|
return self._do_cleanup
|
|
|
|
@do_cleanup.setter
|
|
def do_cleanup(self, cleanup):
|
|
""" Sets whether to cleanup the tempfolder after the application stopped.
|
|
This is mostly useful for debugging, to analyze the generated tempfiles
|
|
even after the pipeline stopped running """
|
|
self._do_cleanup = cleanup
|
|
|
|
def get_lock(self):
|
|
""" Checks if we are the only instance running. If there is no instance
|
|
running, write the current PID to the instance.pid file. If the
|
|
instance file exists, checks if the specified process still runs. This
|
|
way only 1 instance of the pipeline can be run at one time. """
|
|
|
|
# Check if there is a lockfile at all
|
|
if isfile(self._lock_file):
|
|
# Read process id from lockfile
|
|
try:
|
|
with open(self._lock_file, "r") as handle:
|
|
pid = int(handle.readline())
|
|
except IOError as msg:
|
|
self.error("Failed to read lockfile:", msg)
|
|
return False
|
|
|
|
# Check if the process is still running
|
|
if self._is_pid_running(pid):
|
|
self.error("Found running instance")
|
|
return False
|
|
|
|
# Process is not running anymore, we can write the lockfile
|
|
self._write_lock()
|
|
return True
|
|
|
|
else:
|
|
# When there is no lockfile, just create it and continue
|
|
self._write_lock()
|
|
return True
|
|
|
|
def _find_basepath(self):
|
|
""" Attempts to find the pipeline base path by looking at the location
|
|
of this file """
|
|
pth = os.path.abspath(join(os.path.dirname(os.path.realpath(__file__)), ".."))
|
|
return Filename.from_os_specific(pth).get_fullpath()
|
|
|
|
def _is_pid_running(self, pid):
|
|
""" Checks if a pid is still running """
|
|
|
|
# Code snippet from:
|
|
# http://stackoverflow.com/questions/568271/how-to-check-if-there-exists-a-process-with-a-given-pid
|
|
|
|
if os.name == 'posix':
|
|
import errno
|
|
if pid < 0:
|
|
return False
|
|
try:
|
|
os.kill(pid, 0)
|
|
except OSError as err:
|
|
return err.errno == errno.EPERM
|
|
else:
|
|
return True
|
|
else:
|
|
import ctypes
|
|
kernel32 = ctypes.windll.kernel32
|
|
process = kernel32.OpenProcess(0x100000, 0, pid)
|
|
if process != 0:
|
|
kernel32.CloseHandle(process)
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
def _write_lock(self):
|
|
""" Internal method to write the current process id to the instance.pid
|
|
lockfile. This is used to ensure no second instance of the pipeline is
|
|
running. """
|
|
|
|
with open(self._lock_file, "w") as handle:
|
|
handle.write(str(os.getpid()))
|
|
|
|
def _try_remove(self, fname):
|
|
""" Tries to remove the specified filename, returns either True or False
|
|
depending if we had success or not """
|
|
try:
|
|
os.remove(fname)
|
|
return True
|
|
except (IOError, OSError):
|
|
pass
|
|
return False
|
|
|
|
def _on_exit_cleanup(self):
|
|
""" Gets called when the application exists """
|
|
|
|
if self._do_cleanup:
|
|
self.debug("Cleaning up ..")
|
|
|
|
if self._write_path is not None:
|
|
|
|
# Try removing the lockfile
|
|
self._try_remove(self._lock_file)
|
|
|
|
# Check for further tempfiles in the write path
|
|
# We explicitely use os.listdir here instead of pandas listdir,
|
|
# to work with actual paths
|
|
for fname in os.listdir(self._write_path):
|
|
pth = join(self._write_path, fname)
|
|
|
|
# Tempfiles from the pipeline start with "$$" to distinguish
|
|
# them from user created files
|
|
if isfile(pth) and fname.startswith("$$"):
|
|
self._try_remove(pth)
|
|
|
|
# Delete the write path if no files are left
|
|
if len(os.listdir(self._write_path)) < 1:
|
|
try:
|
|
os.removedirs(self._write_path)
|
|
except IOError:
|
|
pass
|
|
|
|
@property
|
|
def is_mounted(self):
|
|
""" Returns whether the MountManager was already mounted by calling
|
|
mount() """
|
|
return self._mounted
|
|
|
|
def mount(self):
|
|
""" Inits the VFS Mounts. This creates the following virtual directory
|
|
structure, from which all files can be located:
|
|
|
|
/$$rp/ (Mounted from the render pipeline base directory)
|
|
+ rpcore/
|
|
+ shader/
|
|
+ ...
|
|
|
|
/$rpconfig/ (Mounted from config/, may be set by user)
|
|
+ pipeline.yaml
|
|
+ ...
|
|
|
|
/$$rptemp/ (Either ramdisk or user specified)
|
|
+ day_time_config
|
|
+ shader_auto_config
|
|
+ ...
|
|
|
|
/$$rpshader/ (Link to /$$rp/rpcore/shader)
|
|
|
|
"""
|
|
self.debug("Setting up virtual filesystem")
|
|
self._mounted = True
|
|
|
|
def convert_path(pth):
|
|
return Filename.from_os_specific(pth).get_fullpath()
|
|
vfs = VirtualFileSystem.get_global_ptr()
|
|
|
|
# Mount config dir as $$rpconf
|
|
if self._config_dir is None:
|
|
config_dir = convert_path(join(self._base_path, "config/"))
|
|
self.debug("Mounting auto-detected config dir:", config_dir)
|
|
vfs.mount(config_dir, "/$$rpconfig", 0)
|
|
else:
|
|
self.debug("Mounting custom config dir:", self._config_dir)
|
|
vfs.mount(convert_path(self._config_dir), "/$$rpconfig", 0)
|
|
|
|
# Mount directory structure
|
|
vfs.mount(convert_path(self._base_path), "/$$rp", 0)
|
|
vfs.mount(convert_path(join(self._base_path, "rpcore/shader")), "/$$rp/shader", 0)
|
|
vfs.mount(convert_path(join(self._base_path, "effects")), "effects", 0)
|
|
|
|
# Mount the pipeline temp path:
|
|
# If no write path is specified, use a virtual ramdisk
|
|
if self._write_path is None:
|
|
self.debug("Mounting ramdisk as /$$rptemp")
|
|
vfs.mount(VirtualFileMountRamdisk(), "/$$rptemp", 0)
|
|
else:
|
|
# In case an actual write path is specified:
|
|
# Ensure the pipeline write path exists, and if not, create it
|
|
if not isdir(self._write_path):
|
|
self.debug("Creating temporary path, since it does not exist yet")
|
|
try:
|
|
os.makedirs(self._write_path)
|
|
except IOError as msg:
|
|
self.fatal("Failed to create temporary path:", msg)
|
|
self.debug("Mounting", self._write_path, "as /$$rptemp")
|
|
vfs.mount(convert_path(self._write_path), '/$$rptemp', 0)
|
|
|
|
get_model_path().prepend_directory("/$$rp")
|
|
get_model_path().prepend_directory("/$$rp/shader")
|
|
get_model_path().prepend_directory("/$$rptemp")
|
|
|
|
def unmount(self):
|
|
""" Unmounts the VFS """
|
|
raise NotImplementedError("TODO")
|