Envision, Create, Share

Welcome to HBGames, a leading amateur game development forum and Discord server. All are welcome, and amongst our ranks you will find experts in their field from all aspects of video game design and development.

[Any Version] avarisc's Tack (aka quick-alias)

avarisc's Tack a.k.a. quick-alias


Just thought I'd drop in for a quick script release. Very, very simple this time: I get tired of having to alias stuff, so I set up a faster and easier way to do it. Usage and example are both in the script header.

No demo, no screenshots - because either would be entirely pointless for this specific case. It's a scripter's tool, and by itself will do absolutely nothing to the game.

Adds a Tack module with two methods: hook_after, and hook_before. Both methods require two symbols and a code block. The symbols are the class name, preceded by a ":" (a colon, without the quotes), and a function name, preceded by ":". A code block can be wrapped in between "do" and "end".

Usage example (changed - please review if updating):
Code:
Tack.hook_before :GameTimer, :start do |instance, args|

   args[0][0] += 5 #sorry about the double array here. nothing I can do about it that retains mutability.

end

 

Tack.hook_after :GameTimer, :start do |instance, args|

   instance.stop

end

 

hook_after will execute your code block after the target function is run; hook_before will execute your code block before the target function is triggered.

The new version should work with any of the standard RPG Maker products.

THE SCRIPT 2.1 (aka absolute sorcery edition)
Code:
# avarisc's Tack (a.k.a. quick-alias) | A simple mini-script that provides an

# v2.1 - 3/23/15  - Public Domain     | easier and cleaner alternative to alias

################################################################################

# Usage Examples:  [hook_before runs your block before, hook_after runs after]

# Tack.hook_before :Game_Timer, :start do |instance, args|

#   args[0][0] += 5

# end

# Tack.hook_after :Game_Timer, :start do |instance, args|

#   instance.stop

# end

################################################################################

 

module Tack

  begin @@b, @@i = Hash.new, Array.new end

  def self.hook_before(c, m, &b) tack(true, c, m, &b) end

  def self.hook_after(c, m, &b) tack(false, c, m, &b) end

  def self.tack(before, c_symbol, m_symbol, &block)

    caller_id = caller[1].gsub(/[^0-9a-z]/i, '').hash

    @@i.include?(caller_id) ? return : @@i << caller_id

    _class = Object.const_get(c_symbol)

    _class.send :define_method, :__tack_y__ do |*args|

      instance_eval do block.call(self, args) end

    end

    k = ((before ? 'b_' : 'a_') + (m_s=m_symbol.to_s)).hash

    @@b[k] = Array.new if !@@b.has_key? k

    @@b[k] << _class.instance_method(:__tack_y__)

    _class.send :remove_method, :__tack_y__

    f = ('__tack_stub_' + m_s + '__').to_sym

    if _class.method_defined?(f)

      _class.send :alias_method, m_symbol, f

    end

    _class.send :alias_method, f, m_symbol

    _class.send :define_method, m_symbol do |*args|

      if @@b.has_key?(k=('b_' + m_s).hash)

        @@b[k].each {|m|m.bind(self).call args}

      end

      self.send f, *args

      if @@b.has_key?(k=('a_' + m_s).hash)

        @@b[k].each {|m|m.bind(self).call args}

      end

    end

  end

end

But how does it work?
It takes the code block you pass, creates a temporary class method in the target class to house the block. It then unbinds the method from the class and stores it in a specially tracked array in the Tack module. The temporary method is then deleted. If this is the first hook for the targeted method, then the Tack module aliases it to add the wrapper code. The wrapper code simply checks the specially tracked array for unbound functions that should run before or after a targeted function, and binds and executes them, along with calling the wrapped method.
TL;DR - you were better off not asking.
 
Updated, the new version does not use alias, instead it uses a custom alias implementation. Advantages are unclear, but was interesting to make.
 
Updated again, somehow I overlooked that this didn't work on functions with arguments. Note that the first update apparently dropped support for RPG Maker XP due to requiring Ruby functions that weren't present then.

Latest version supports functions with arguments - no need to do anything different at all, it should just work. Don't specify the arguments while hooking - this is all done magically behind the scenes. If you need to intercept the arguments, you should be able to access the *args array while inside the block.
 
Updated to 2.0. This was more or less a rewrite. What can I say, I enjoy throwing the rulebook out the window and finding a creative solution.

Features:
Fixed compatibility with XP - it no longer uses 1.9.2 specific Ruby features.
Only makes a single alias per function to handle wrapping.
F12 proof - doesn't raise errors or increase stack levels on reset
Stack effective - it runs each hook parallel rather than daisy chained. No more stack overflow errors!
No risk of name collisions (like there would be with aliasing) as this uses the calling script/line number to ID callbacks.
Support for reading and modifying arguments and interacting with the instance itself.
No longer resides in Object, rather it's neatly packed into its own module now.
License changed to public domain. Go crazy.

Effectively, this thing is now half-API and half-code-generator. Awesomesauce.

I do hope someone enjoys using it.
 
You made me curious, so i progged my own solution :biggrin: :
Ruby:
class Object

    def get_new_sym(sym)

        new_sym = temp_sym = sym.to_s + self.hash.to_s

        while method_defined?(new_sym)

            new_sym = temp_sym.to_s + rand.hash.to_s

        end

        return new_sym

    end

    def b_alias_method(old_sym, &block)

        new_sym = get_new_sym(:old_sym)

        alias_method new_sym, old_sym

        define_method(old_sym) { |*args, &_block|

            self.instance_exec(*args, &block)

            send new_sym, *args

        }

    end

    def a_alias_method(old_sym, &block)

        new_sym = get_new_sym(:old_sym)

        alias_method new_sym, old_sym

        define_method(old_sym) { |*args, &_block|

            result = send new_sym, *args

            self.instance_exec(*args, &block)

            result

        }

    end

    def bb_alias_method(old_sym, &block)

        new_sym = get_new_sym(:old_sym)

        alias_method new_sym, old_sym

        define_method(old_sym) { |*args, &_block|

            self.instance_exec(_block, *args , &block)

            send new_sym, *args

        }

    end

    def ab_alias_method(old_sym, &block)

        new_sym = get_new_sym(:old_sym)

        alias_method new_sym, old_sym

        define_method(old_sym) { |*args, &_block|

            result = send new_sym, *args

            self.instance_exec(_block, *args , &block)

            result

        }

    end

end
Example:
Ruby:
class Test < Array

    b_alias_method(:size) { p "test" }

    a_alias_method(:size) { p "test" }

    bb_alias_method(:each) { |block, *args|

        for i in 0...size()

            block.call(self[i])

        end

    }

end

t = Test.new(5) { |i| i }

p t.size()

t.each { |e| p e }
 

Thank you for viewing

HBGames is a leading amateur video game development forum and Discord server open to all ability levels. Feel free to have a nosey around!

Discord

Join our growing and active Discord server to discuss all aspects of game making in a relaxed environment. Join Us

Content

  • Our Games
  • Games in Development
  • Emoji by Twemoji.
    Top