lighttroupe/luz

View on GitHub
engine/actor.rb

Summary

Maintainability
A
2 hrs
Test Coverage
multi_require 'user_object', 'actor_effect', 'color', 'drawing'

class Actor < ParentUserObject
    include Drawing

    WIDTH, HEIGHT = 1.0, 1.0
    RADIUS = 0.5         # (used by children)

    #
    # Class methods
    #
    dsl_flag :cache_rendering

    def self.available_categories
        [:transform,:color,:child_producer,:child_consumer,:canvas,:special]
    end

    #
    # Instance methods
    #
    empty_method :render, :with_canvas

    def new_renderer
        GuiActorRenderer.new(self)
    end

    def after_load
        super
        clear_cache
    end

    def deep_clone(*args)
        clear_cache        # We don't want to clone our GL cache
        super(*args)
    end

    def clear_cache
        GL.DeleteLists(@display_list, 1) if @display_list
        @display_list = nil
        self
    end

    def valid_child_class?(klass)
        klass.ancestors.include? ActorEffect
    end

    #
    # ticking
    #
    empty_method :tick

    #
    # rendering
    #
    $actor_render_stack ||= []
    ACTOR_RENDER_STACK_LIMIT = 20

    def render!
        return if $actor_render_stack.size > ACTOR_RENDER_STACK_LIMIT
        $actor_render_stack.push(self)
        user_object_try {
            # resolve_settings, and if it returns true (something changed) and we're using caching, clear it now
            clear_cache if resolve_settings and self.class.cache_rendering?
            tick!
            with_translation(@x, @y) {
                # TODO: add rotation with numbers from user point-and-click control?
                render_recursive {
                    render_after_effects
                }
            }
        }
        $actor_render_stack.pop
    end

    #
    # Offscreen rendering
    #
    def with_image
        @offscreen_buffer.with_image { yield } if @offscreen_buffer        # otherwise doesn't yield
    end

    def update_offscreen_buffer!
        @offscreen_buffer ||= get_offscreen_buffer(:medium)
        @offscreen_buffer.using { render! }
    end

private

    # calls render() on effect at 'effect_index', continuing effects chain once for each time it yields
    def render_recursive(effect_index = 0, &proc)
        if (effect_index and effect = effects[effect_index])
            if !effect.usable?
                # Simply skip this effect
                render_recursive(effect_index + 1, &proc)
            else
                effect.parent_user_object = self
                $engine.user_object_try(effect) {
                    effect.resolve_settings
                    effect.tick!

                    # Each time effect.render yields, render remaining effects, then
                    # finally the drawable object, using whatever GL state the effects set
                    effect.render { |*options|        # first and only param is options hash
                        if o=options[0]
                            with_env_hash(o) {
                                render_recursive(effect_index + 1, &proc)    # next effect in line
                            }
                        else
                            render_recursive(effect_index + 1, &proc)    # next effect in line
                        end
                    }
                }
                render_recursive(effect_index + 1, &proc) if effect.crashy? # continue to next effect in line if it *just* crashed
                effect.parent_user_object = nil        # children shouldn't store links to parents (breaks cloning, etc.)
            end
        else
            # we've reached the end of the effects chain
            proc.call
        end
    end

    def render_after_effects
        with_compiled_shader {
            if self.class.cache_rendering?
                @display_list ||= GL.RenderToList { render }
                GL.CallList(@display_list)
            else
                render
            end
        }
    end
end