module AdventureRL # The TimingHandler has nice methods to handle timing. # It can #set_timeout or #set_interval for methods. class TimingHandler include Helpers::Error def initialize @queue = { timeouts: [], intervals: [] } @elapsed_seconds = 0.0 @deltatime = Deltatime.new @is_running = true end # #update should be called every frame, # this is where it checks if any methods need to be called # and calls them if necessary. def update return if (is_paused?) handle_timeouts handle_intervals @elapsed_seconds += @deltatime.dt @deltatime.update end def continue return if (is_running?) @is_running = true reset end def pause return if (is_paused?) @is_running = false end def is_running? return !!@is_running end def is_paused? return !is_running? end # Reset the Deltatime def reset @deltatime.reset end # Set a timeout for a method. # Call a method after a specified amount of time has passed. # The passed args Hash should include the following keys: # :method:: The method to be called. Can be one of the following: # - a Method -- method(:my_method) # - a Proc -- Proc.new { puts 'My method!' } # - a method name as a Symbol -- :my_method # :seconds _or_ :secs:: Integer or Float. The time to wait in seconds, before calling the method. # :arguments _or_ :args:: Optional Array of arguments, which will be passed to the target method. # :id:: Optional value which can be used to remove the timeout afterwards. See #remove_timeout. # You can also pass a block to the method, # which will be used instead of the :method key's value. def set_timeout args = {}, &block validate_args args, !!block _args = get_unified_args args, &block at = get_time_in _args[:seconds] @queue[:timeouts] << { method: _args[:method], at: at, arguments: _args[:arguments], id: _args[:id] } end alias_method :in, :set_timeout # Set an interval for a method. # Call a method in regular intervals. # The passed args Hash should include the following keys: # :method:: The method to be called. Can be one of the following: # - a Method -- method(:my_method) # - a Proc -- Proc.new { puts 'My method!' } # - a method name as a Symbol -- :my_method # :seconds _or_ :secs:: Integer or Float. The time to wait in seconds, before calling the method. # :arguments _or_ :args:: Optional Array of arguments, which will be passed to the target method. # :id:: Optional value which can be used to remove the interval afterwards. See #remove_interval. # You can also pass a block to the method, # which will be used instead of the :method key's value. def set_interval args = {}, &block validate_args args, !!block _args = get_unified_args args, &block at = get_time_in _args[:seconds] @queue[:intervals] << { method: _args[:method], interval: _args[:seconds], at: at, arguments: _args[:arguments], id: _args[:id] } end alias_method :every, :set_interval # If you passed an :id to your timeout when you set it with #set_timeout, # then you can remove / clear it before it executes by calling this method and # passing the same id. def remove_timeout id @queue[:timeouts].reject! do |timeout| next timeout[:id] == id end end alias_method :clear_timeout, :remove_timeout # If you passed an :id to your interval when you set it with #set_interval, # then you can remove / clear it by calling this method and # passing the same id. # If you did _not_ pass an id, then your interval will be running endlessly! def remove_interval id @queue[:intervals].reject! do |interval| next interval[:id] == id end end alias_method :clear_interval, :remove_interval # Returns true if the given id exists as a timeout, # and false if not. def has_timeout? id return @queue[:timeouts].any? do |timeout| next timeout[:id] == id end end # Returns true if the given id exists as an interval, # and false if not. def has_interval? id return @queue[:intervals].any? do |interval| next interval[:id] == id end end private def handle_timeouts current_seconds = get_elapsed_seconds @queue[:timeouts].reject! do |timeout| next false unless (current_seconds >= timeout[:at]) timeout[:method].call *timeout[:arguments] next true end end def handle_intervals current_seconds = get_elapsed_seconds @queue[:intervals].each do |interval| next unless (current_seconds >= interval[:at]) interval[:method].call *interval[:arguments] interval[:at] = get_time_in interval[:interval] end end def validate_args args, block_given = false error( "Passed argument must be a Hash." ) unless (args.is_a? Hash) unless (block_given) error( "Passed args Hash must include the key `:method'." ) unless (args.key? :method) method_class = args[:method].class error( "Key `:method' must be a Method, Proc, or Symbol, but is a `#{method_class.name}'" ) unless ([Method, Proc, Symbol].include? method_class) end error( "Passed args Hash must include the key `:seconds' or `:secs'." ) unless (args.key?(:seconds) || args.key?(:secs)) seconds_key = :secs if (args.key? :secs) seconds_key = :seconds if (args.key? :seconds) seconds_class = args[seconds_key].class error( "Key `:#{seconds_key.to_s}' must be an Integer or Float, but is a `#{seconds_class.name}'" ) unless ([Integer, Float].include? seconds_class) if (args.key?(:arguments) || args.key?(:args)) arguments_key = :args if (args.key? :args) arguments_key = :arguments if (args.key? :arguments) arguments_class = args[arguments_key].class error( "Key `:#{arguments_key.to_s}' must be an Array, but is a `#{arguments_class.name}'" ) unless (arguments_class == Array) end end def get_unified_args args, &block prc = get_proc_from(block || args[:method]) return { method: prc, seconds: args[:seconds] || args[:secs], arguments: args[:arguments] || args[:args] || [], id: args[:id] } end def get_proc_from meth prc = nil if (meth.is_a? Method) prc = meth.to_proc elsif (meth.is_a? Proc) prc = meth elsif (meth.is_a? Symbol) error( "Method `:#{meth.to_s}' is not available in this scope." ) unless (methods.include? meth) prc = method(meth).to_proc end return prc end def get_time_in seconds return get_elapsed_seconds + seconds end def get_elapsed_seconds return @elapsed_seconds end end end