Files
2025-09-29 00:52:08 +02:00

387 lines
14 KiB
Ruby
Executable File

#
# File:: %RS_TOOLSLIB%/pipeline/resourcing/convert.rb
# Description:: Singleton interface to our convert system.
#
# Author:: Greg Smith <greg@rockstarnorth.com>
# Author:: David Muir <david.muir@rockstarnorth.com>
# Date:: 13 February 2008
#
# This file exposes the public interface to our conversion system. It allows
# scripts to start asset pipeline conversions and processing based on the
# dynamically loaded set of converters.
#
# Each converter specifies a content node type that can be converted; using
# the inputs to generate that type.
#
#
#-----------------------------------------------------------------------------
# Uses
#-----------------------------------------------------------------------------
require 'pipeline/os/path'
require 'pipeline/config/projects'
require 'pipeline/util/rage'
require 'pipeline/resourcing/converter'
require 'pipeline/resourcing/converters/converter_pack'
require 'pipeline/resourcing/converters/converter_zip'
require 'pipeline/resourcing/converters/converter_map'
require 'pipeline/resourcing/converters/converter_map_scenexml'
require 'pipeline/resourcing/converters/converter_animation'
require 'pipeline/resourcing/converters/converter_character'
require 'pipeline/resourcing/converters/converter_rage'
include Pipeline
#-----------------------------------------------------------------------------
# Implementation
#-----------------------------------------------------------------------------
module Pipeline
module Resourcing
#
# == Description
# ConvertSystem class provides the user-interface to the conversion stage
# (Stage 2) of our pipeline; converting game-targeted independent data to
# platform specific data.
#
# The system is initialised with a set of converters that know how to
# transform content nodes.
#
# Before use ensure that you call the +setup+ method.
#
# == Example Usage
#
# proj = Pipeline::Config::instance().projects['jimmy']
# convert = Pipeline::ConvertSystem::instance()
# convert.setup( proj )
# convert.build( ... ) do |content, success|
# ... user-code ...
# end
#
class ConvertSystem
include Singleton
#---------------------------------------------------------------------
# Attributes
#---------------------------------------------------------------------
attr_reader :project
attr_reader :branch
attr_reader :cache_root, :rebuild, :temporary, :preview
attr_reader :converters
#---------------------------------------------------------------------
# Methods
#---------------------------------------------------------------------
#
# Setup the convert system for a specific project and branch.
#
# If no branch is specified then the project's default branch is used.
#
def setup( project, branch_name = nil, network = false, rebuild = false, temporary = false, preview = false, load_content = true )
config = Pipeline::Config::instance()
project.load_config( )
project.load_content( ) if ( load_content )
@project = project
@branch = @project.branches[branch_name] if ( @project.branches.has_key?( branch_name ) )
@branch = @project.branches[@project.default_branch] if ( @branch.nil? )
# Verify we have one or more platforms enabled; if not display an
# error telling the user nothing will convert.
at_least_one_target = false
@branch.targets.each_pair do |platform, target|
next unless ( target.enabled )
at_least_one_target = true
end
if ( ( not at_least_one_target ) and ( not config.user.username.downcase.include?( User::ASSETBUILDER_USER ) ) )
error_msg = "There are no platforms enabled for the current branch.\n"
error_msg += "Project: #{@project.uiname}\n"
error_msg += "Branch: #{@branch.name}\n\n"
error_msg += "Re-run the installer and enabled one or more platforms."
GUI::MessageBox::error("Platform Conversion Error Notification", error_msg)
return false
end
@rebuild = rebuild
@temporary = temporary
@preview = preview
@cache_root = ""
if @temporary then
@cache_root = OS::Path.combine( Config::instance().temp, 'convert', @branch.name )
else
@cache_root = OS::Path.combine( project.local, 'convert', @branch.name )
end
@converters = []
add_default_converters( )
end
#
# Builds a passed in content tree, along with any dependencies. The
# converters used will yield for each content node that is built specifying
# the content node and a bool success status.
#
# === Example Usage
# node = project.content.find_by_group( 'platform' )
# convert.build( node ) do |content, success|
#
# puts "Error converting: #{content}" unless success
# puts "Success converting: #{content}" if success
# end
#
def build( content, &block )
throw ArgumentError::new( "Invalid content node (#{content.class}) or Array." ) \
unless ( content.is_a?( Pipeline::Content::Base ) or content.is_a?( Array ) )
# Handle Array of content nodes also.
if ( content.is_a?( Array ) ) then
content.each do |c| parse_content( c ); end
else
parse_content( content )
end
# Iterate through the converters; invoking the 'prebuild' step,
# then the main build step. Post-process the build step results
# as required; ensuring any output is parsed.
result = true
@converters.each do |converter|
converter.prebuild( )
converter_result = converter.build( &block )
throw RuntimeError::new( "Invalid Converter #{converter.class}; returning non-Hash object." ) \
unless ( converter_result.is_a?( Hash ) )
result = false unless ( converter_result[:success] )
# Parse any content that has been passed in from a converter.
# This will be picked up by converters that run after the current
# one; and allows us to pass things to the RAGE Converter.
if ( converter_result.has_key?( :conversion ) ) then
throw RuntimeError::new( "Invalid Array of content nodes (#{converter_result[:conversion].class})." ) \
unless ( converter_result[:conversion].is_a?( Array ) )
converter_result[:conversion].each do |c|
throw RuntimeError::new( "Invalid ContentNode (#{c.class})." ) \
unless ( c.is_a?( Content::Base ) )
pc = ProjectUtil::data_convert_content_to_platform_content( @project, @branch, c )
pc.each do |p| parse_content( p ); end
end
end
end
@converters.each do |converter|
converter.clear()
end
result
end
#
# Return converter output for the last call to 'build'. Iterates
# over each converter fetching the required data.
#
def get_converter_output( cmd = nil, error_regexp = nil, warning_regexp = nil)
return '','','' if ( @converters.nil? )
all_errors = ''
all_warnings = ''
build_output = ''
@converters.each do |converter|
next unless ( converter.methods.include?( 'get_last_output' ) )
if cmd.nil?
build_output << converter.get_last_output( )
else
# this will only return errors and warnings
full_cmd = "#{cmd} #{converter.log_filename} #{error_regexp} #{warning_regexp}"
status, stdout, stderr = systemu(full_cmd)
if (stderr)
stderr.each do |err|
build_output += "#{err}\n"
all_errors += "#{err}\n"
end
end
if (stdout)
stdout.each do |warn|
build_output += "#{warn}\n"
all_warnings += "#{warn}\n"
end
end
end
end
return build_output, all_errors, all_warnings
end
#
# Return the convert system log object.
#
def ConvertSystem::log( )
@@log = Log.new( 'convert' ) if ( @@log.nil? )
@@log
end
#---------------------------------------------------------------------
# Protected
#---------------------------------------------------------------------
protected
@@log = nil
CONVERTERS_PATH = '$(toolslib)/pipeline/resourcing/converters/*.rb'
# We no longer dynamically load our converters; we want more control
# on which ones are loaded. This just iterates through the list of
# registered ocnverters and adds them.
def add_default_converters( )
# Loop through our registered converters and register them with
# the convert pipeline if they appear valid.
ConverterBase::registered_converter_types.each do |conv|
converter = conv::new( @project, @branch )
if ( ( converter.methods.include?( 'can_convert?' ) ) and
( converter.methods.include?( 'build' ) ) and
( converter.methods.include?( 'clear' ) ) and
( converter.methods.include?( 'add_content' ) ) ) then
@converters << converter
ConvertSystem::log().info( "Registered Converter #{conv}." )
else
ConvertSystem::log().warn( "Ignoring Converter: #{conv} (verification failed)." )
end
end
end
# Now public as accessed in RageConverter.
public
# Determine whether the content needs converting. This used to be in
# each converter but it made chaining dependencies tricky. Its not
# centralised so this function may need additions as new content nodes
# are added.
#
# Note: *** these conditions are order dependent ***
#
def need_convert?( content )
# If the content is a directory we will need to walk it's inputs
return ( true ) if content.is_a?( Content::Directory )
return ( false ) if ( 0 == content.inputs.size )
if ( content.is_a?( Content::File ) ) then
return ( true ) if ( not File::exists?( content.filename ) )
end
# Target dependency check for Ragebuilder timestamp.
if ( content.is_a?( Target ) ) then
ragebuilder_timestamp = File::mtime( @tools[content.target.platform].path )
return ( true ) if ( ragebuilder_timestamp > File::mtime( content.filename ) )
end
# Regenerate this content if we're rebuilding.
return ( true ) if ( @rebuild )
# Need to create this content if any of its inputs are going to be
# re-created; abstracted from next 'inputs' loop as that checks for
# the input file existing first which may not be the case.
content.inputs.each do |input|
return ( true ) if ( need_convert?( input ) )
end
# Need to create this content file if any input is newer than the
# existing content file itself.
mtime = File::mtime( content.filename )
content.inputs.each do |input|
if ( input.is_a?( Content::File ) ) then
# If the input file doesn't exist then we need to build
# ourselves; we assume it will be there by then.
return ( true ) if ( not File::exists?( input.filename ) )
return ( true ) if ( ( File::mtime( input.filename ) <=> mtime ) > 0 )
elsif ( input.is_a?( Content::Group ) ) then
# DHM 2011/04/20: this additional Group parsing has
# been added for the ped pipeline in 3dsmax. It
# constructs Groups as input nodes to build up the
# IDD, ILD files etc. Not sure whether the
# 'inputs.inputs' recursion is a good idea!!
nodes = ( input.children + input.inputs )
nodes.each do |child|
next unless ( child.is_a?( Content::File ) )
mtime_child = File::mtime( child.filename )
return ( true ) if ( ( mtime_child <=> mtime ) > 0 )
end
end
end
return ( false )
end
protected
# Parse content node; adding to the correct converter.
def parse_content( content )
throw ArgumentError::new( "Invalid content node (#{content.class})" ) \
unless ( content.is_a?( Pipeline::Content::Base ) )
# There should only be one converter available for each content node
# type. We now determine the correct converter to use.
converter = nil
@converters.each do |c|
# Skip converter if it can't convert this content node.
next unless ( c.can_convert?( content ) )
converter = c
break
end
if ( converter.nil? ) then
# Lets not worry about it; there are plenty content nodes we
# do not have a specific converter for.
## ConvertSystem::log().warn( "No converter for #{content.name} (#{content.class})." )
else
# We have an allocated converter so lets send it the content
# and process the content's inputs too.
if ( need_convert?( content ) ) then
ConvertSystem::log().info( "Converting: #{content.name} for #{content.target.platform}" )
if ( converter.add_content( content ) ) then
ConvertSystem::log().info( "Converter queued: #{content.filename}." ) \
if ( content.is_a?( Content::File ) )
ConvertSystem::log().info( "Converter queued: #{content.name}." ) \
unless ( content.is_a?( Content::File ) )
# Parse content outputs; we're typically passed an input
# node so need to walk down the tree.
content.outputs.each do |output|
parse_content( output )
end
else
ConvertSystem::log().warn( "Converter #{converter} discarded content: #{content}." )
end
else
#ConvertSystem::log().info( "No need to convert content: #{content.filename}." ) \
# if ( content.is_a?( Content::File ) )
#ConvertSystem::log().info( "No need to convert content: #{content.name}." ) \
# unless ( content.is_a?( Content::File ) )
end
end
if content.class <= Pipeline::Content::SupportChildren then
content.children.each do |c|
ConvertSystem::log().error( "Content node nil parent: #{content}" ) \
if ( c.nil? )
parse_content( c )
end
end
end
end
end # Resourcing module
end # Pipeline module
# %RS_TOOLSLIB%/pipeline/resourcing/convert.rb