[PD-cvs] externals/gridflow/base bitpacking.c, NONE, 1.1 flow_objects.c, NONE, 1.1 flow_objects.rb, NONE, 1.1 flow_objects_for_image.c, NONE, 1.1 flow_objects_for_matrix.c, NONE, 1.1 grid.c, NONE, 1.1 grid.h, NONE, 1.1 main.c, NONE, 1.1 main.rb, NONE, 1.1 number.c, NONE, 1.1 source_filter.rb, NONE, 1.1 test.rb, NONE, 1.1

Mathieu Bouchard matju at users.sourceforge.net
Tue Oct 4 04:02:15 CEST 2005


Update of /cvsroot/pure-data/externals/gridflow/base
In directory sc8-pr-cvs1.sourceforge.net:/tmp/cvs-serv21117/base

Added Files:
	bitpacking.c flow_objects.c flow_objects.rb 
	flow_objects_for_image.c flow_objects_for_matrix.c grid.c 
	grid.h main.c main.rb number.c source_filter.rb test.rb 
Log Message:
starting to commit gridflow 0.8.0 ...
if you know how to use "cvs import" please mail me and i'll use it for 0.8.1


--- NEW FILE: source_filter.rb ---
$keywords = %w(class decl def end grdecl)
$stack = []
$classes = []

ClassDecl = Struct.new(:name,:supername,:methods,:grins,:attrs,:info)
MethodDecl = Struct.new(:rettype,:selector,:arglist,:minargs,:maxargs,:where)
Arg = Struct.new(:type,:name,:default)

class MethodDecl
	def ==(o)
		return false unless rettype==o.rettype &&
		maxargs==o.maxargs # && minargs==o.minargs 
		arglist.each_index{|i| arglist[i] == o.arglist[i] or return false }
		return true
	end
end

class Arg
	def ==(o)
		type==o.type && name==o.name # && default==o.default
	end
end

In = File.open ARGV[0], "r"
Out = File.open ARGV[1], "w"

def handle_class(line)
	raise "already in class #{where}" if $stack[-1] and ClassDecl===$stack[-1]
	#STDERR.puts "class: #{line}"
	/^(\w+)(?:\s*<\s*(\w+))?$/.match line or raise "syntax error #{where}"
	q=ClassDecl.new($1,$2,{},{},{},false)
	$stack << q
	$classes << q
	Out.puts ""
end

def parse_methoddecl(line,term)
	/^(\w+)\s+(\w+)\s*\(([^\)]*)\)\s*#{term}/.match line or
		raise "syntax error #{where} #{line}"
	rettype,selector,arglist = $1,$2,$3
	arglist,minargs,maxargs = parse_arglist arglist
	MethodDecl.new(rettype,selector,arglist,minargs,maxargs,where)
end

def parse_arglist(arglist)
	arglist = arglist.split(/,/)
	maxargs = arglist.length
	args = arglist.map {|arg|
		if /^\s*\.\.\.\s*$/.match arg then maxargs=-1; next end
		/^\s*([\w\s\*<>]+)\s*\b(\w+)\s*(?:\=(.*))?/.match arg or
			raise "syntax error in \"#{arg}\" #{where}"
		type,name,default=$1,$2,$3
		Arg.new(type.sub(/\s+$/,""),name,default)
	}.compact
	minargs = args.length
	minargs-=1 while minargs>0 and args[minargs-1].default
	[args,minargs,maxargs]
end

def unparse_arglist(arglist,with_default=true)
	arglist.map {|arg|
		x="#{arg.type} #{arg.name} "
		x<<'='<<arg.default if with_default and arg.default
		x
	}.join(", ")
end

def where
  "[#{ARGV[0]}:#{$linenumber}]"
end

def handle_attr(line)
	type = line.gsub(%r"//.*$","").gsub(%r"/\*.*\*/","").gsub(%r";?\s*$","")
	name = type.slice!(/\w+$/)
	raise "missing \\class #{where}" if
		not $stack[-1] or not ClassDecl===$stack[-1]
	$stack[-1].attrs[name]=Arg.new(type,name,nil)
	Out.print line
	Out.puts "//FCS"
	handle_decl "void _0_#{name}_m (#{type} #{name});"
	Out.puts "# #{$linenumber}"
end

def handle_decl(line)
	raise "missing \\class #{where}" if
		not $stack[-1] or not ClassDecl===$stack[-1]
	classname = $stack[-1].name
	m = parse_methoddecl(line,";\s*$")
	$stack[-1].methods[m.selector] = m

	Out.print "#{m.rettype} #{m.selector}(int argc, Ruby *argv"
	Out.print "," if m.arglist.length>0
	Out.print "#{unparse_arglist m.arglist});"
	Out.puts "static Ruby #{m.selector}_wrap"+
	"(int argc, Ruby *argv, Ruby rself);//FCS"
	Out.puts "# #{$linenumber+1}"
end

def handle_def(line)
	m = parse_methoddecl(line,"\{?.*$")
	term = line[/\{.*/]
	qlass = $stack[-1]
	raise "missing \\class #{where}" if not qlass or not ClassDecl===qlass
	classname = qlass.name
	if qlass.methods[m.selector]
		n = m; m = qlass.methods[m.selector]
		if m!=n then
			STDERR.puts "warning: def does not match decl:"
			STDERR.puts "#{m.where}: \\decl #{m.inspect}"
			STDERR.puts "#{n.where}: \\def #{n.inspect}"
		end
	else
		qlass.methods[m.selector] = m
	end

	Out.print "Ruby #{classname}::#{m.selector}_wrap"+
	"(int argc, Ruby *argv, Ruby rself) {"+
	"static const char *methodspec = "+
	"\"#{qlass.name}::#{m.selector}(#{unparse_arglist m.arglist,false})\";"+
	"DGS(#{classname});"

	Out.print "if (argc<#{m.minargs}"
	Out.print "||argc>#{m.maxargs}" if m.maxargs!=-1
	Out.print ") RAISE(\"got %d args instead of %d..%d in %s\""+
		",argc,#{m.minargs},#{m.maxargs},methodspec);"

	error = proc {|x,y|
		"RAISE(\"got %s instead of #{x} in %s\","+
		"rb_str_ptr(rb_inspect(rb_obj_class(#{y}))),methodspec)"
	}

	m.arglist.each_with_index{|arg,i|
		case arg.type
		when "Symbol"
			Out.print "if (argc>#{i} && TYPE(argv[#{i}])!=T_SYMBOL) "+
			error[arg.type,"argv[#{i}]"]+";"
		when "Array"
			Out.print "if (argc>#{i} && TYPE(argv[#{i}])!=T_ARRAY) "+
			error[arg.type,"argv[#{i}]"]+";"
		when "String"
			Out.print "if (argc>#{i} && TYPE(argv[#{i}])==T_SYMBOL) "+
			"argv[#{i}]=rb_funcall(argv[#{i}],SI(to_s),0);"
			Out.print "if (argc>#{i} && TYPE(argv[#{i}])!=T_STRING) "+
			error[arg.type,"argv[#{i}]"]+";"
		end
	}

#	Out.print "return " if m.rettype!="void"
	Out.print "VALUE foo = " if m.rettype!="void" ###

	Out.print " self->#{m.selector}(argc,argv"
	m.arglist.each_with_index{|arg,i|
		if arg.default then
			Out.print ",argc<#{i+1}?#{arg.default}:convert(argv[#{i}],(#{arg.type}*)0)"
		else
			Out.print ",convert(argv[#{i}],(#{arg.type}*)0)"
		end
	}
	Out.print ");"
	Out.print "self->check_magic();"
	Out.print "return Qnil;" if m.rettype=="void"
	Out.print "return foo;" if m.rettype!="void" ###
	Out.print "} #{m.rettype} #{classname}::#{m.selector}(int argc, Ruby *argv"
	Out.print "," if m.arglist.length>0
	Out.puts "#{unparse_arglist m.arglist, false})#{term}//FCS"
end

def handle_classinfo(line)
	frame = $stack[-1]
	cl = frame.name
	line="{}" if /^\s*$/ =~ line
	Out.puts "static void #{cl}_startup (Ruby rself);"
	Out.puts "static void *#{cl}_allocator () {return new #{cl};}"
	Out.puts "static MethodDecl #{cl}_methods[] = {"
	Out.puts frame.methods.map {|foo,method|
		c,s = frame.name,method.selector
		"{ \"#{s}\",(RMethod)#{c}::#{s}_wrap }"
	}.join(",")
	Out.puts "}; FClass ci#{cl} = { #{cl}_allocator, #{cl}_startup,"
	Out.puts "#{cl.inspect}, COUNT(#{cl}_methods), #{cl}_methods };"
	Out.puts "void #{frame.name}_startup (Ruby rself) "+line
end

def handle_grin(line)
	fields = line.split(/\s+/)
	i = fields[0].to_i
	c = $stack[-1].name
	Out.print "template <class T> void grin_#{i}(GridInlet *in, int n, Pt<T> data);"
	Out.print "template <class T> static void grinw_#{i} (GridInlet *in, int n, Pt<T> data);"
	Out.print "static GridHandler grid_#{i}_hand;"
	handle_decl "Ruby _#{i}_grid(...);"
	$stack[-1].grins[i] = fields.dup
end

def handle_end(line)
	frame = $stack.pop
	fields = line.split(/\s+/)
	n = fields.length
	if ClassDecl===frame then
		#handle_classinfo if not frame.info
		cl = frame.name
		if fields[0]!="class" or
		(n>1 and fields[1]!=cl)
		then raise "end not matching #{where}" end
		$stack.push frame
		frame.attrs.each {|name,attr|
			type,name,default = attr.to_a
			#STDERR.puts "type=#{type} name=#{name} default=#{default}"
			handle_def "void _0_#{name}_m (#{type} #{name}) { this->#{name}=#{name}; }"
		}
		frame.grins.each {|i,v|
			k = case v[1]
			when nil; '4'
			when 'int32'; '1'
			when 'int'; '2'
			when 'float'; 'F'
			else raise 'BORK BORK BORK' end
			Out.print "static GridHandler #{cl}_grid_#{i}_hand = GRIN#{k}(#{cl},#{i});"
			handle_def "Ruby _#{i}_grid(...) {"+
				"if (in.size()<=#{i}) in.resize(#{i}+1);"+
				"if (!in[#{i}]) in[#{i}]=new GridInlet((GridObject *)this,&#{cl}_grid_#{i}_hand);"+
				"return in[#{i}]->begin(argc,argv);}"

		}
		$stack.pop
		Out.puts "# #{$linenumber}"
	end
	if :ruby==frame then
		if fields[0]!="ruby" then raise "expected \\end ruby" end
	end
	Out.puts ""
end

def handle_startall(line)
	$classes.each {|q|
		Out.print "rb_funcall(EVAL(\"GridFlow\"),SI(fclass_install),2,PTR2FIX(&ci#{q.name}),"
		if q.supername then
			Out.print "EVAL(\"GridFlow::#{q.supername}\"));"
		else
			Out.print "Qnil);"
		end
	}
	Out.puts ""
end

def handle_ruby(line)
	Out.puts ""
	$stack.push :ruby
end

$rubymode=false
$linenumber=1
loop{
	x = In.gets
	break if not x
	if /^\s*\\(\w+)\s*(.*)$/.match x then
		begin
			send("handle_#{$1}",$2)
		rescue StandardError => e
			STDERR.puts e.inspect
			STDERR.puts "at line #{$linenumber}"
			STDERR.puts e.backtrace
			File.unlink ARGV[1]
			exit 1
		end
	else
		if $stack[-1]==:ruby then
			x.gsub!(/([\\\"])/) { "\\"+$1 }
			x="\"#{x.chomp}\\n\"\n"
		end
		Out.puts x
	end
	$linenumber+=1
}

--- NEW FILE: flow_objects_for_matrix.c ---
/*
	$Id: flow_objects_for_matrix.c,v 1.1 2005/10/04 02:02:13 matju Exp $

	GridFlow
	Copyright (c) 2001,2002,2003 by Mathieu Bouchard

	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License
	as published by the Free Software Foundation; either version 2
	of the License, or (at your option) any later version.

	See file ../COPYING for further informations on licensing terms.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/

#include <math.h>
#include "grid.h.fcs"

// produce an upper triangular matrix with ones on the diagonal
// will also affect any additional columns using the same row-operations

void expect_complete_matrix (P<Dim> d) {
	if (d->n!=2) RAISE("bletch");
	if (d->get(0)>d->get(1)) RAISE("argh");
}

\class GridMatrixSolve < GridObject
struct GridMatrixSolve : GridObject {
	Numop *op_sub;
	Numop *op_mul;
	Numop *op_div;
	PtrGrid matrix;
	GridMatrixSolve() {
		matrix.constrain(expect_complete_matrix);
	}
	\decl void initialize ();
	\grin 0 float
};

GRID_INPUT(GridMatrixSolve,0,matrix) {
	int n = matrix->dim->get(0); // # rows
	int m = matrix->dim->get(1); // # columns
	Pt<T> mat = (Pt<T>)*matrix;
	for (int j=0; j<n; j++) {
		op_div->map(m,mat+j*m,mat[j*m+j]);
		for (int i=j+1; i<n; i++) {
			STACK_ARRAY(T,row,m);
			COPY(row,mat+j,m);
			op_mul->map(m,row,mat[i*m+j]);
			op_sub->zip(m,mat+i*m,row);
		}
	}
	GridOutlet out(this,0,matrix->dim);
	out.send(n*m,mat);
} GRID_END

\def void initialize () {
	rb_call_super(argc,argv);
	this->op_sub = op_sub;
	this->op_mul = op_mul;
	this->op_div = op_div;
}

\classinfo { IEVAL(rself,"install '#matrix_solve',1,1"); }
\end class

void startup_flow_objects_for_matrix () {
	\startall
}

--- NEW FILE: main.rb ---
=begin
	$Id: main.rb,v 1.1 2005/10/04 02:02:13 matju Exp $

	GridFlow
	Copyright (c) 2001,2002 by Mathieu Bouchard

	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License
	as published by the Free Software Foundation; either version 2
	of the License, or (at your option) any later version.

	See file ../COPYING for further informations on licensing terms.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
=end

# ENV["RUBY_VERBOSE_GC"]="yes"

# this file gets loaded by main.c upon startup
# module GridFlow is supposed to be created by main.c
# this includes GridFlow.post_string(s)

# because Ruby1.6 has no #object_id and Ruby1.8 warns on #id
unless Object.instance_methods(true).include? "object_id"
	class Object; alias object_id id end
end

# in case of bug in Ruby ("Error: Success")
module Errno; class E000 < StandardError; end; end

#$post_log = File.open "/tmp/gridflow.log", "w"
$post_log = nil

class Array
	def split(elem)
		r=[]
		j=0
		for i in 0...length
			(r<<self[j,i-j]; j=i+1) if self[i]==elem
		end
		r<<self[j,length-j]
	end
end

module GridFlow #------------------

def self.post(s,*a)
	post_string(sprintf("%s"+s,post_header,*a))
	($post_log << sprintf(s,*a); $post_log.flush) if $post_log
end

class<<self
	attr_accessor :data_path
	attr_accessor :post_header
	attr_accessor :verbose
	attr_reader :fobjects
	attr_reader :fclasses
	attr_reader :cpu_hertz
	attr_reader :subprocesses
	attr_reader :bridge_name
	alias gfpost post
end

@subprocesses={}
@verbose=false
@data_path=[]
if GridFlow.respond_to? :config then
  @data_path << GridFlow.config["PUREDATA_PATH"]+"/extra/gridflow/images"
end

def self.hunt_zombies
	#STDERR.puts "GridFlow.hunt_zombies"
	# the $$ value is bogus
	begin
		died = []
		subprocesses.each {|x,v|
			Process.waitpid2(x,Process::WNOHANG) and died<<x
		}
	rescue Errno::ECHILD
	end
	#STDERR.puts died.inspect
	died.each {|x| subprocesses.delete x }
end

def self.packstring_for_nt(nt)
	case nt
	when :u, :u8,  :uint8; "C*"
	when :s, :i16, :int16; "s*"
	when :i, :i32, :int32; "l*"
	when :f, :f32, :float32; "f*"
	when :d, :f64, :float64; "d*"
	else raise "no decoder for #{nt.inspect}"
	end
end

self.post_header = "[gf] "

def self.gfpost2(fmt,s); post("%s",s) end

if GridFlow.bridge_name then
  post "This is GridFlow #{GridFlow::GF_VERSION} within Ruby version #{RUBY_VERSION}"
  post "base/main.c was compiled on #{GridFlow::GF_COMPILE_TIME}"
  post "Please use at least 1.6.6 if you plan to use sockets" if RUBY_VERSION<"1.6.6"
end

if not GridFlow.bridge_name then
  require "gridflow/bridge/placebo"
end

Brace1 = "{".intern
Brace2 = "}".intern
Paren1 = "(".intern
Paren2 = ")".intern

def self.parse(m)
	m = m.gsub(/(\{|\})/," \\1 ").split(/\s+/)
	m.map! {|x| case x
		when Integer, Symbol; x
		when /^[+\-]?[0-9]+$/; x.to_i
		when String; x.intern
		end
	}
	m
end

def self.stringify_list(argv)
	argv.map {|x| stringify x }.join(" ")
end

def self.stringify(arg)
	case arg
	when Integer, Float, Symbol; arg.to_s
	when Array; "{#{stringify_list arg}}"
	end
end

::Object.module_eval do def FloatOrSymbol(x) Float(x) rescue x.intern end end

# adding some functionality to that:
class FObject
	@broken_ok = false
	@do_loadbangs = true
	class<<self
		# global
		attr_accessor :broken_ok
		# per-class
		attr_reader :ninlets
		attr_reader :noutlets
		attr_accessor :do_loadbangs
		attr_accessor :comment
		def foreign_name; @foreign_name if defined? @foreign_name end
	end
	def post(*a) GridFlow.post(*a) end
	def self.subclass(*args,&b)
		qlass = Class.new self
		qlass.install(*args)
		qlass.module_eval(&b)
	end
	alias :total_time :total_time_get
	alias :total_time= :total_time_set
	attr_writer :args # String
	attr_accessor :argv # Array
	attr_reader :outlets
	attr_accessor :parent_patcher
	attr_accessor :properties
	attr_accessor :classname
	def initialize2; end
	def args
		if defined? @args
			@args
		else
			"[#{self.class} ...]"
		end
	end
	alias info args
	def connect outlet, object, inlet
		@outlets ||= []
		@outlets[outlet] ||= []
		@outlets[outlet].push [object, inlet]
	end
	def self.name_lookup sym
		qlasses = GridFlow.fclasses
		qlass = qlasses[sym.to_s]
		if not qlass
			return qlasses['broken'] if @broken_ok
			raise "object class '#{sym}' not found"
		end
		qlass
	end
	def self.[](*m)
		o=nil
		if m.length==1 and m[0] =~ / /
			o="[#{m[0]}]"
			m=GridFlow.parse(m[0]) 
		else
			o=m.inspect
		end
		GridFlow.handle_braces!(m)
		ms = m.split ','.intern
		m = ms.shift
		qlass = m.shift
		qlassname = qlass.to_s
		qlass = name_lookup qlass.to_s unless Class===qlass
		r = qlass.new(*m)
		r.classname = qlassname
		GridFlow.post "%s",r.args if GridFlow.verbose
		for x in ms do r.send_in(-2, *x) end if FObject.do_loadbangs
		r
	end
	def inspect
		if args then "#<#{self.class} #{args}>" else super end
	end
	def initialize(*argv)
		s = GridFlow.stringify_list argv
		@argv = argv
		@args = "["
		@args << (self.class.foreign_name || self.to_s)
		@args << " " if s.length>0
		@args << s << "]"
		@parent_patcher = nil
		@properties = {}
		@init_messages = []
	end
end

class FPatcher < FObject
	class << self
		attr_reader :fobjects
		attr_reader :wires
	end
	def initialize(*)
		super
		fobjects = self.class.fobjects
		wires = self.class.wires
		@fobjects = fobjects.map {|x| if String===x then FObject[x] else x.call end }
		@inlets = []
		@ninlets = self.class.ninlets or raise "oops"
		i=0
		@fobjects << self
		while i<wires.length do
			a,b,c,d = wires[i,4]
			if a==-1 then
				a=self
				@inlets[b]||=[]
				@inlets[b] << [@fobjects[c],d]
			else
				if c==-1 then
					@fobjects[a].connect b,self,d+ at ninlets
				else
					@fobjects[a].connect b, at fobjects[c],d
				end
			end
			i+=4
		end
	end
	def method_missing(sym,*args)
		sym=sym.to_s
		if sym =~ /^_(\d)_(.*)/ then
			inl = Integer $1
			sym = $2.intern
			if inl<@ninlets then
			raise "#{inspect} has not @inlets[#{inl}]" if not @inlets[inl]
				for x in @inlets[inl] do
				 x[0].send_in x[1],sym,*args end
			else
				send_out(inl- at ninlets,sym,*args)
			end
		else super end
	end
end

def GridFlow.estimate_cpu_clock
	u0,t0=GridFlow.rdtsc,Time.new.to_f; sleep 0.01
	u1,t1=GridFlow.rdtsc,Time.new.to_f; (u1-u0)/(t1-t0)
end

begin
	@cpu_hertz = (0...3).map {
		GridFlow.estimate_cpu_clock
	}.sort[1] # median of three tries
rescue
	GridFlow.post $!
end

def GridFlow.find_file s
	s=s.to_s
	if s==File.basename(s) then
		dir = GridFlow.data_path.find {|x| File.exist? "#{x}/#{s}" }
		if dir then "#{dir}/#{s}" else s end
	elsif GridFlow.respond_to? :find_file_2
		GridFlow.find_file_2 s
	else
		s
	end
end

def GridFlow.macerr(i)
  begin
    f=File.open("/System/Library/Frameworks/CoreServices.framework/"+
      "Versions/A/Frameworks/CarbonCore.framework/Versions/A/Headers/"+
      "MacErrors.h")
    while f.gets
      m = /^\s*(\w+)\s*=\s*(-\d+),\s*\/\*\s*(.*)\s*\*\/$/.match $_
      next if not m
      if m[2].to_i == i then return "#{m[2]}: \"#{m[3]}\"" end
    end
    return "no error message available for this error number"
  rescue FileError
    return "Can't find Apple's precious copyrighted list of error messages on this system."
  ensure
    f.close if f	
  end
end

end # module GridFlow

class IO
  def nonblock= flag
    bit = Fcntl::O_NONBLOCK
    state = fcntl(Fcntl::F_GETFL, 0)
    fcntl(Fcntl::F_SETFL, (state & ~bit) |
      (if flag; bit else 0 end))
  end
end

def protect
  yield
rescue Exception => e
  STDERR.puts "#{e.class}: #{e}"
  STDERR.puts e.backtrace
end

def GridFlow.load_user_config
	require "gridflow/bridge/puredata.rb" if GridFlow.bridge_name == "puredata"
	user_config_file = ENV["HOME"] + "/.gridflow_startup"
	begin
		load user_config_file if File.exist? user_config_file
	rescue Exception => e
		GridFlow.post "#{e.class}: #{e}:\n" + e.backtrace.join("\n")
		GridFlow.post "while loading ~/.gridflow_startup"
	end
end

require "gridflow/base/flow_objects.rb"
require "gridflow/format/main.rb"

%w(
  # #for #finished #type #dim #transpose #perspective #store #outer
  #grade #redim #import #export #export_list #cast
  #scale_by #downscale_by #draw_polygon #draw_image #layer
  #print #pack #export_symbol #rotate
  #in #out
).each {|k|
	GridFlow::FObject.name_lookup(k).add_creator k.gsub(/#/,"@")
}

END {
	GridFlow.fobjects.each {|k,v| k.delete if k.respond_to? :delete }
	GridFlow.fobjects.clear
	GC.start
}


--- NEW FILE: flow_objects_for_image.c ---
/*
	$Id: flow_objects_for_image.c,v 1.1 2005/10/04 02:02:13 matju Exp $

	GridFlow
	Copyright (c) 2001,2002,2003 by Mathieu Bouchard

	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License
	as published by the Free Software Foundation; either version 2
	of the License, or (at your option) any later version.

	See file ../COPYING for further informations on licensing terms.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/

#include <math.h>
#include "grid.h.fcs"

static void expect_picture (P<Dim> d) {
	if (d->n!=3) RAISE("(height,width,chans) dimensions please");}
static void expect_rgb_picture (P<Dim> d) {
	expect_picture(d);
	if (d->get(2)!=3) RAISE("(red,green,blue) channels please");}
static void expect_rgba_picture (P<Dim> d) {
	expect_picture(d);
	if (d->get(2)!=4) RAISE("(red,green,blue,alpha) channels please");}
static void expect_max_one_dim (P<Dim> d) {
	if (d->n>1) { RAISE("expecting Dim[] or Dim[n], got %s",d->to_s()); }}

//****************************************************************
//{ Dim[A,B,*Cs]<T>,Dim[D,E]<T> -> Dim[A,B,*Cs]<T> }

static void expect_convolution_matrix (P<Dim> d) {
	if (d->n != 2) RAISE("only exactly two dimensions allowed for now (got %d)",
		d->n);
}

// entry in a compiled convolution kernel
struct PlanEntry { int y,x; bool neutral; };

\class GridConvolve < GridObject
struct GridConvolve : GridObject {
	\attr Numop *op_para;
	\attr Numop *op_fold;
	\attr PtrGrid seed;
	\attr PtrGrid b;
	PtrGrid a;
	int plann;
	PlanEntry *plan; //Pt?
	int margx,margy; // margins
	GridConvolve () : plan(0) { b.constrain(expect_convolution_matrix); plan=0; }
	\decl void initialize (Grid *r=0);
	\decl void _0_op   (Numop *op);
	\decl void _0_fold (Numop *op);
	\decl void _0_seed (Grid *seed);
	\grin 0
	\grin 1
	template <class T> void copy_row (Pt<T> buf, int sx, int y, int x);
	template <class T> void make_plan (T bogus);
	~GridConvolve () {if (plan) delete[] plan;}
};

template <class T> void GridConvolve::copy_row (Pt<T> buf, int sx, int y, int x) {
	int day = a->dim->get(0), dax = a->dim->get(1), dac = a->dim->prod(2);
	y=mod(y,day); x=mod(x,dax);
	Pt<T> ap = (Pt<T>)*a + y*dax*dac;
	while (sx) {
		int sx1 = min(sx,dax-x);
		COPY(buf,ap+x*dac,sx1*dac);
		x=0;
		buf += sx1*dac;
		sx -= sx1;
	}
}

static Numop *OP(Ruby x) {return FIX2PTR(Numop,rb_hash_aref(op_dict,x));}

template <class T> void GridConvolve::make_plan (T bogus) {
	P<Dim> da = a->dim, db = b->dim;
	int dby = db->get(0);
	int dbx = db->get(1);
	if (plan) delete[] plan;
	plan = new PlanEntry[dbx*dby];
	int i=0;
	for (int y=0; y<dby; y++) {
		for (int x=0; x<dbx; x++) {
			T rh = ((Pt<T>)*b)[y*dbx+x];
			bool neutral = op_para->on(rh)->is_neutral(rh,at_right);
			bool absorbent = op_para->on(rh)->is_absorbent(rh,at_right);
			STACK_ARRAY(T,foo,1);
			if (absorbent) {
				foo[0] = 0;
				op_para->map(1,foo,rh);
				absorbent = op_fold->on(rh)->is_neutral(foo[0],at_right);
			}
			if (absorbent) continue;
			plan[i].y = y;
			plan[i].x = x;
			plan[i].neutral = neutral;
			i++;
		}
	}
	plann = i;
}

GRID_INLET(GridConvolve,0) {
	SAME_TYPE(in,b);
	SAME_TYPE(in,seed);
	P<Dim> da = in->dim, db = b->dim;
	if (!db) RAISE("right inlet has no grid");
	if (!seed) RAISE("seed missing");
	if (db->n != 2) RAISE("right grid must have two dimensions");
	if (da->n < 2) RAISE("left grid has less than two dimensions");
	if (seed->dim->n != 0) RAISE("seed must be scalar");
	if (da->get(0) < db->get(0)) RAISE("grid too small (y): %d < %d", da->get(0), db->get(0));
	if (da->get(1) < db->get(1)) RAISE("grid too small (x): %d < %d", da->get(1), db->get(1));
	margy = (db->get(0)-1)/2;
	margx = (db->get(1)-1)/2;
	a=new Grid(in->dim,in->nt);
	out=new GridOutlet(this,0,da,in->nt);
} GRID_FLOW {
	COPY((Pt<T>)*a+in->dex, data, n);
} GRID_FINISH {
	Numop *op_put = OP(SYM(put));
	make_plan((T)0);
	int dbx = b->dim->get(1);
	int day = a->dim->get(0);
	int n = a->dim->prod(1);
	int sx = a->dim->get(1)+dbx-1;
	int n2 = sx*a->dim->prod(2);
	STACK_ARRAY(T,buf,n);
	STACK_ARRAY(T,buf2,n2);
	T orh=0;
	for (int iy=0; iy<day; iy++) {
		op_put->map(n,buf,*(T *)*seed);
		for (int i=0; i<plann; i++) {
			int jy = plan[i].y;
			int jx = plan[i].x;
			T rh = ((Pt<T>)*b)[jy*dbx+jx];
			if (i==0 || plan[i].y!=plan[i-1].y || orh!=rh) {
				copy_row(buf2,sx,iy+jy-margy,-margx);
				if (!plan[i].neutral) op_para->map(n2,buf2,rh);
			}
			op_fold->zip(n,buf,buf2+jx*a->dim->prod(2));
			orh=rh;
		}
		out->send(n,buf);
	}
	a=0;
} GRID_END

GRID_INPUT(GridConvolve,1,b) {} GRID_END

\def void _0_op   (Numop *op ) { this->op_para=op; }
\def void _0_fold (Numop *op ) { this->op_fold=op; }
\def void _0_seed (Grid *seed) { this->seed=seed; }

\def void initialize (Grid *r) {
	rb_call_super(argc,argv);
	this->op_para = op_mul;
	this->op_fold = op_add;
	this->seed = new Grid(new Dim(),int32_e,true);
	this->b= r ? r : new Grid(new Dim(1,1),int32_e,true);
}

\classinfo { IEVAL(rself,"install '#convolve',2,1"); }
\end class GridConvolve

/* ---------------------------------------------------------------- */
/* "#scale_by" does quick scaling of pictures by integer factors */
/*{ Dim[A,B,3]<T> -> Dim[C,D,3]<T> }*/
\class GridScaleBy < GridObject
struct GridScaleBy : GridObject {
	\attr PtrGrid scale; // integer scale factor
	int scaley;
	int scalex;
	\decl void initialize (Grid *factor=0);
	\grin 0
	\grin 1
	void prepare_scale_factor () {
		scaley = ((Pt<int32>)*scale)[0];
		scalex = ((Pt<int32>)*scale)[scale->dim->prod()==1 ? 0 : 1];
		if (scaley<1) scaley=2;
		if (scalex<1) scalex=2;
	}
};

GRID_INLET(GridScaleBy,0) {
	P<Dim> a = in->dim;
	expect_picture(a);
	out=new GridOutlet(this,0,new Dim(a->get(0)*scaley,a->get(1)*scalex,a->get(2)),in->nt);
	in->set_factor(a->get(1)*a->get(2));
} GRID_FLOW {
	int rowsize = in->dim->prod(1);
	STACK_ARRAY(T,buf,rowsize*scalex);
	int chans = in->dim->get(2);
	#define Z(z) buf[p+z]=data[i+z]
	for (; n>0; data+=rowsize, n-=rowsize) {
		int p=0;
		#define LOOP(z) \
			for (int i=0; i<rowsize; i+=z) \
			for (int k=0; k<scalex; k++, p+=3)
		switch (chans) {
		case 3: LOOP(3) {Z(0);Z(1);Z(2);} break;
		case 4: LOOP(4) {Z(0);Z(1);Z(2);Z(3);} break;
		default: LOOP(chans) {for (int c=0; c<chans; c++) Z(c);}
		}
		#undef LOOP
		for (int j=0; j<scaley; j++) out->send(rowsize*scalex,buf);
	}
	#undef Z
} GRID_END

static void expect_scale_factor (P<Dim> dim) {
	if (dim->prod()!=1 && dim->prod()!=2)
		RAISE("expecting only one or two numbers");
}

GRID_INPUT(GridScaleBy,1,scale) { prepare_scale_factor(); } GRID_END

\def void initialize (Grid *factor) {
	scale.constrain(expect_scale_factor);
	rb_call_super(argc,argv);
	scale=new Grid(INT2NUM(2));
	if (factor) scale=factor;
	prepare_scale_factor();
}

\classinfo { IEVAL(rself,"install '#scale_by',2,1"); }
\end class GridScaleBy

// ----------------------------------------------------------------
//{ Dim[A,B,3]<T> -> Dim[C,D,3]<T> }
\class GridDownscaleBy < GridObject
struct GridDownscaleBy : GridObject {
	\attr PtrGrid scale;
	\attr bool smoothly;
	int scaley;
	int scalex;
	PtrGrid temp;
	\decl void initialize (Grid *factor=0, Symbol option=Qnil);
	\grin 0
	\grin 1
	void prepare_scale_factor () {
		scaley = ((Pt<int32>)*scale)[0];
		scalex = ((Pt<int32>)*scale)[scale->dim->prod()==1 ? 0 : 1];
		if (scaley<1) scaley=2;
		if (scalex<1) scalex=2;
	}
};

GRID_INLET(GridDownscaleBy,0) {

	P<Dim> a = in->dim;
	if (a->n!=3) RAISE("(height,width,chans) please");
	out=new GridOutlet(this,0,new Dim(a->get(0)/scaley,a->get(1)/scalex,a->get(2)),in->nt);
	in->set_factor(a->get(1)*a->get(2));
	// i don't remember why two rows instead of just one.
	temp=new Grid(new Dim(2,in->dim->get(1)/scalex,in->dim->get(2)),in->nt);
} GRID_FLOW {
	int rowsize = in->dim->prod(1);
	int rowsize2 = temp->dim->prod(1);
	Pt<T> buf = (Pt<T>)*temp; //!@#$ maybe should be something else than T ?
	int xinc = in->dim->get(2)*scalex;
	int y = in->dex / rowsize;
	int chans=in->dim->get(2);
	#define Z(z) buf[p+z]+=data[i+z]
	if (smoothly) {
		while (n>0) {
			if (y%scaley==0) CLEAR(buf,rowsize2);
			#define LOOP(z) \
				for (int i=0,p=0; p<rowsize2; p+=z) \
				for (int j=0; j<scalex; j++,i+=z)
			switch (chans) {
			case 1: LOOP(1) {Z(0);} break;
			case 2: LOOP(2) {Z(0);Z(1);} break;
			case 3: LOOP(3) {Z(0);Z(1);Z(2);} break;
			case 4: LOOP(4) {Z(0);Z(1);Z(2);Z(3);} break;
			default:LOOP(chans) {for (int k=0; k<chans; k++) Z(k);} break;
			}
			#undef LOOP
			y++;
			if (y%scaley==0 && out->dim) {
				op_div->map(rowsize2,buf,(T)(scalex*scaley));
				out->send(rowsize2,buf);
				CLEAR(buf,rowsize2);
			}
			data+=rowsize;
			n-=rowsize;
		}
	#undef Z
	} else {
	#define Z(z) buf[p+z]=data[i+z]
		for (; n>0 && out->dim; data+=rowsize, n-=rowsize,y++) {
			if (y%scaley!=0) continue;
			#define LOOP(z) for (int i=0,p=0; p<rowsize2; i+=xinc, p+=z)
			switch(in->dim->get(2)) {
			case 1: LOOP(1) {Z(0);} break;
			case 2: LOOP(2) {Z(0);Z(1);} break;
			case 3: LOOP(3) {Z(0);Z(1);Z(2);} break;
			case 4: LOOP(4) {Z(0);Z(1);Z(2);Z(3);} break;
			default:LOOP(chans) {for (int k=0; k<chans; k++) Z(k);}break;
			}
			#undef LOOP
			out->send(rowsize2,buf);
		}
	}
	#undef Z
} GRID_END

GRID_INPUT(GridDownscaleBy,1,scale) { prepare_scale_factor(); } GRID_END

\def void initialize (Grid *factor, Symbol option) {
	scale.constrain(expect_scale_factor);
	rb_call_super(argc,argv);
	scale=new Grid(INT2NUM(2));
	if (factor) scale=factor;
	prepare_scale_factor();
	smoothly = option==SYM(smoothly);
}

\classinfo { IEVAL(rself,"install '#downscale_by',2,1"); }
\end class GridDownscaleBy

//****************************************************************
\class GridLayer < GridObject
struct GridLayer : GridObject {
	PtrGrid r;
	GridLayer() { r.constrain(expect_rgb_picture); }
	\grin 0 int
	\grin 1 int
};

GRID_INLET(GridLayer,0) {
	NOTEMPTY(r);
	SAME_TYPE(in,r);
	P<Dim> a = in->dim;
	expect_rgba_picture(a);
	if (a->get(1)!=r->dim->get(1)) RAISE("same width please");
	if (a->get(0)!=r->dim->get(0)) RAISE("same height please");
	in->set_factor(a->prod(2));
	out=new GridOutlet(this,0,r->dim);
} GRID_FLOW {
	Pt<T> rr = ((Pt<T>)*r) + in->dex*3/4;
	STACK_ARRAY(T,foo,n*3/4);
#define COMPUTE_ALPHA(c,a) \
	foo[j+c] = (data[i+c]*data[i+a] + rr[j+c]*(256-data[i+a])) >> 8
	for (int i=0,j=0; i<n; i+=4,j+=3) {
		COMPUTE_ALPHA(0,3);
		COMPUTE_ALPHA(1,3);
		COMPUTE_ALPHA(2,3);
	}
#undef COMPUTE_ALPHA
	out->send(n*3/4,foo);
} GRID_END

GRID_INPUT(GridLayer,1,r) {} GRID_END

\classinfo { IEVAL(rself,"install '#layer',2,1"); }
\end class GridLayer

// ****************************************************************
// pad1,pad2 only are there for 32-byte alignment
struct Line { int32 y1,x1,y2,x2,x,m,pad1,pad2; };

static void expect_polygon (P<Dim> d) {
	if (d->n!=2 || d->get(1)!=2) RAISE("expecting Dim[n,2] polygon");
}

\class DrawPolygon < GridObject
struct DrawPolygon : GridObject {
	\attr Numop *op;
	\attr PtrGrid color;
	\attr PtrGrid polygon;
	PtrGrid color2;
	PtrGrid lines;
	int lines_start;
	int lines_stop;
	DrawPolygon() {
		color.constrain(expect_max_one_dim);
		polygon.constrain(expect_polygon);
	}
	\decl void initialize (Numop *op, Grid *color=0, Grid *polygon=0);
	\grin 0
	\grin 1
	\grin 2 int32
	void init_lines();

};

void DrawPolygon::init_lines () {
	int nl = polygon->dim->get(0);
	lines=new Grid(new Dim(nl,8), int32_e);
	Pt<Line> ld = Pt<Line>((Line *)(int32 *)*lines,nl);
	Pt<int32> pd = *polygon;
	for (int i=0,j=0; i<nl; i++) {
		ld[i].y1 = pd[j+0];
		ld[i].x1 = pd[j+1];
		j=(j+2)%(2*nl);
		ld[i].y2 = pd[j+0];
		ld[i].x2 = pd[j+1];
		if (ld[i].y1>ld[i].y2) memswap(Pt<int32>(ld+i)+0,Pt<int32>(ld+i)+2,2);
	}
}

static int order_by_starting_scanline (const void *a, const void *b) {
	return ((Line *)a)->y1 - ((Line *)b)->y1;
}

static int order_by_column (const void *a, const void *b) {
	return ((Line *)a)->x - ((Line *)b)->x;
}

GRID_INLET(DrawPolygon,0) {
	NOTEMPTY(color);
	NOTEMPTY(polygon);
	NOTEMPTY(lines);
	SAME_TYPE(in,color);
	if (in->dim->n!=3) RAISE("expecting 3 dimensions");
	if (in->dim->get(2)!=color->dim->get(0))
		RAISE("image does not have same number of channels as stored color");
	out=new GridOutlet(this,0,in->dim,in->nt);
	lines_start = lines_stop = 0;
	in->set_factor(in->dim->get(1)*in->dim->get(2));
	int nl = polygon->dim->get(0);
	qsort((int32 *)*lines,nl,sizeof(Line),order_by_starting_scanline);
	int cn = color->dim->prod();
	color2=new Grid(new Dim(cn*16), color->nt);
	for (int i=0; i<16; i++) COPY((Pt<T>)*color2+cn*i,(Pt<T>)*color,cn);
} GRID_FLOW {
	int nl = polygon->dim->get(0);
	Pt<Line> ld = Pt<Line>((Line *)(int32 *)*lines,nl);
	int f = in->factor();
	int y = in->dex/f;
	int cn = color->dim->prod();
	Pt<T> cd = (Pt<T>)*color2;
	
	while (n) {
		while (lines_stop != nl && ld[lines_stop].y1<=y) lines_stop++;
		for (int i=lines_start; i<lines_stop; i++) {
			if (ld[i].y2<=y) {
				memswap(ld+i,ld+lines_start,1);
				lines_start++;
			}
		}
		if (lines_start == lines_stop) {
			out->send(f,data);
		} else {
			int32 xl = in->dim->get(1);
			Pt<T> data2 = ARRAY_NEW(T,f);
			COPY(data2,data,f);
			for (int i=lines_start; i<lines_stop; i++) {
				Line &l = ld[i];
				l.x = l.x1 + (y-l.y1)*(l.x2-l.x1+1)/(l.y2-l.y1+1);
			}
			qsort(ld+lines_start,lines_stop-lines_start,
				sizeof(Line),order_by_column);
			for (int i=lines_start; i<lines_stop-1; i+=2) {
				int xs = max(ld[i].x,(int32)0), xe = min(ld[i+1].x,xl);
				if (xs>=xe) continue; /* !@#$ WHAT? */
				while (xe-xs>=16) { op->zip(16*cn,data2+cn*xs,cd); xs+=16; }
				op->zip((xe-xs)*cn,data2+cn*xs,cd);
			}
			out->give(f,data2);
		}
		n-=f;
		data+=f;
		y++;
	}
} GRID_END

GRID_INPUT(DrawPolygon,1,color) {} GRID_END
GRID_INPUT(DrawPolygon,2,polygon) {init_lines();} GRID_END

\def void initialize (Numop *op, Grid *color, Grid *polygon) {
	rb_call_super(argc,argv);
	this->op = op;
	if (color) this->color=color;
	if (polygon) { this->polygon=polygon; init_lines(); }
}

\classinfo { IEVAL(rself,"install '#draw_polygon',3,1"); }
\end class DrawPolygon

//****************************************************************
static void expect_position(P<Dim> d) {
	if (d->n!=1) RAISE("position should have 1 dimension, not %d", d->n);
	if (d->v[0]!=2) RAISE("position dim 0 should have 2 elements, not %d", d->v[0]);
}

\class DrawImage < GridObject
struct DrawImage : GridObject {
	\attr Numop *op;
	\attr PtrGrid image;
	\attr PtrGrid position;
	\attr bool alpha;
	\attr bool tile;
	
	DrawImage() : alpha(false), tile(false) {
		position.constrain(expect_position);
		image.constrain(expect_picture);
	}

	\decl void initialize (Numop *op, Grid *image=0, Grid *position=0);
	\decl void _0_alpha (bool v=true);
	\decl void _0_tile (bool v=true);
	\grin 0
	\grin 1
	\grin 2 int32
	// draw row # ry of right image in row buffer buf, starting at xs
	// overflow on both sides has to be handled automatically by this method
	template <class T> void draw_segment(Pt<T> obuf, Pt<T> ibuf, int ry, int x0);
};

#define COMPUTE_ALPHA(c,a) obuf[j+(c)] = ibuf[j+(c)] + (rbuf[a])*(obuf[j+(c)]-ibuf[j+(c)])/256;
#define COMPUTE_ALPHA4(b) \
	COMPUTE_ALPHA(b+0,b+3); \
	COMPUTE_ALPHA(b+1,b+3); \
	COMPUTE_ALPHA(b+2,b+3); \
	obuf[b+3] = rbuf[b+3] + (255-rbuf[b+3])*(ibuf[j+b+3])/256;

template <class T> void DrawImage::draw_segment(Pt<T> obuf, Pt<T> ibuf, int ry, int x0) {
	if (ry<0 || ry>=image->dim->get(0)) return; // outside of image
	int sx = in[0]->dim->get(1), rsx = image->dim->get(1);
	int sc = in[0]->dim->get(2), rsc = image->dim->get(2);
	Pt<T> rbuf = (Pt<T>)*image + ry*rsx*rsc;
	if (x0>sx || x0<=-rsx) return; // outside of buffer
	int n=rsx;
	if (x0+n>sx) n=sx-x0;
	if (x0<0) { rbuf-=rsc*x0; n+=x0; x0=0; }
	if (alpha && rsc==4 && sc==3) { // RGB by RGBA //!@#$ optimise
		int j=sc*x0;
		for (; n; n--, rbuf+=4, j+=3) {
			op->zip(sc,obuf+j,rbuf); COMPUTE_ALPHA(0,3); COMPUTE_ALPHA(1,3); COMPUTE_ALPHA(2,3);
		}
	} else if (alpha && rsc==4 && sc==4) { // RGBA by RGBA
		op->zip(n*rsc,obuf+x0*rsc,rbuf);
		int j=sc*x0;
		for (; n>=4; n-=4, rbuf+=16, j+=16) {
			COMPUTE_ALPHA4(0);COMPUTE_ALPHA4(4);
			COMPUTE_ALPHA4(8);COMPUTE_ALPHA4(12);
		}
		for (; n; n--, rbuf+=4, j+=4) {
			COMPUTE_ALPHA4(0);
		}
	} else { // RGB by RGB, etc
		op->zip(n*rsc,obuf+sc*x0,rbuf);
	}
}

GRID_INLET(DrawImage,0) {
	NOTEMPTY(image);
	NOTEMPTY(position);
	SAME_TYPE(in,image);
	if (in->dim->n!=3) RAISE("expecting 3 dimensions");
	int lchan = in->dim->get(2);
	int rchan = image->dim->get(2);
	if (alpha && rchan!=4) {
		RAISE("alpha mode works only with 4 channels in right_hand");
	}
	if (lchan != rchan-(alpha?1:0) && lchan != rchan) {
		RAISE("right_hand has %d channels, alpha=%d, left_hand has %d, expecting %d or %d",
			rchan, alpha?1:0, lchan, rchan-(alpha?1:0), rchan);
	}
	out=new GridOutlet(this,0,in->dim,in->nt);
	in->set_factor(in->dim->get(1)*in->dim->get(2));
} GRID_FLOW {
	int f = in->factor();
	int y = in->dex/f;
	if (position->nt != int32_e) RAISE("position has to be int32");
	int py = ((int32*)*position)[0], rsy = image->dim->v[0], sy=in->dim->get(0);
	int px = ((int32*)*position)[1], rsx = image->dim->v[1], sx=in->dim->get(1);
	for (; n; y++, n-=f, data+=f) {
		int ty = div2(y-py,rsy);
		if (tile || ty==0) {
			Pt<T> data2 = ARRAY_NEW(T,f);
			COPY(data2,data,f);
			if (tile) {
				for (int x=px-div2(px+rsx-1,rsx)*rsx; x<sx; x+=rsx) {
					draw_segment(data2,data,mod(y-py,rsy),x);
				}
			} else {
				draw_segment(data2,data,y-py,px);
			}
			out->give(f,data2);
		} else {
			out->send(f,data);
		}
	}
} GRID_END

GRID_INPUT(DrawImage,1,image) {} GRID_END
GRID_INPUT(DrawImage,2,position) {} GRID_END
\def void _0_alpha (bool v=true) { alpha = v; gfpost("ALPHA=%d",v); }
\def void _0_tile (bool v=true) {   tile = v; }

\def void initialize (Numop *op, Grid *image, Grid *position) {
	rb_call_super(argc,argv);
	this->op = op;
	if (image) this->image=image;
	if (position) this->position=position;
	else this->position=new Grid(new Dim(2),int32_e,true);
}

\classinfo { IEVAL(rself,"install '#draw_image',3,1"); }
\end class DrawImage

void startup_flow_objects_for_image () {
	\startall
}

--- NEW FILE: number.c ---
/*
	$Id: number.c,v 1.1 2005/10/04 02:02:13 matju Exp $

	GridFlow
	Copyright (c) 2001,2002,2003,2004 by Mathieu Bouchard

	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License
	as published by the Free Software Foundation; either version 2
	of the License, or (at your option) any later version.

	See file ../COPYING for further informations on licensing terms.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/

#include "grid.h.fcs"
#include <math.h>
#include <stdlib.h>
#include <stdio.h>
#include <limits.h>

#ifdef PASS1
NumberType number_type_table[] = {
#define FOO(_sym_,_size_,_flags_,args...) NumberType( #_sym_, _size_, _flags_, args ),
NUMBER_TYPES(FOO)
#undef FOO
};
const long number_type_table_n = COUNT(number_type_table);
#endif

// those are bogus class-templates in the sense that you don't create
// objects from those, you just call static functions. The same kind
// of pattern is present in STL to overcome some limitations of C++.

template <class T> class Op {
public:
	// I call abort() on those because I can't say they're purevirtual.
	static T f(T a, T b) {abort();}
	static bool is_neutral(T x, LeftRight side)   {assert(!"Op::is_neutral called?");}
	static bool is_absorbent(T x, LeftRight side) {assert(!"Op::is_absorbent called?");}
};

template <class O> class OpLoops {
public:
	template <class T> static void op_map (int n, T *as, T b) {
		if (!n) return;
#define FOO(I) as[I]=O::f(as[I],b);
	UNROLL_8(FOO,n,as)
#undef FOO
	}
	template <class T> static void op_zip (int n, T *as, T *bs) {
		if (!n) return;
		int ba=bs-as; // really!
#define FOO(I) as[I]=O::f(as[I],as[ba+I]);
	UNROLL_8(FOO,n,as)
#undef FOO
	}
	// disabled
	template <class T> static void op_zip2 (int n, T *as, T *bs, T *cs) {
		if (!n) return;
		int ba=bs-as, ca=cs-as;
#define FOO(I) as[ca+I]=O::f(as[I],as[ba+I]);
	UNROLL_8(FOO,n,as)
#undef FOO
	}
#define W(i) as[i]=O::f(as[i],bs[i]);
#define Z(i,j) as[i]=O::f(O::f(O::f(O::f(as[i],bs[i]),bs[i+j]),bs[i+j+j]),bs[i+j+j+j]);
	template <class T> static void op_fold (int an, int n, T *as, T *bs) {
		switch (an) {
		case 1: for (; (n&3)!=0; bs++, n--) W(0);
			for (; n; bs+=4, n-=4) { Z(0,1); } break;
		case 2: for (; (n&3)!=0; bs+=2, n--) { W(0); W(1); }
			for (; n; bs+=8, n-=4) { Z(0,2); Z(1,2); } break;
		case 3: for (; (n&3)!=0; bs+=3, n--) { W(0); W(1); W(2); }
			for (; n; bs+=12, n-=4) { Z(0,3); Z(1,3); Z(2,3); } break;
		case 4: for (; (n&3)!=0; bs+=4, n--) { W(0); W(1); W(2); W(3); }
			for (; n; bs+=16, n-=4) { Z(0,4); Z(1,4); Z(2,4); Z(3,4); } break;
		default:for (; n--; ) {
				int i=0;
				for (; i<(an&-4); i+=4, bs+=4) {
					as[i+0]=O::f(as[i+0],bs[0]);
					as[i+1]=O::f(as[i+1],bs[1]);
					as[i+2]=O::f(as[i+2],bs[2]);
					as[i+3]=O::f(as[i+3],bs[3]);
				}
				for (; i<an; i++, bs++) as[i] = O::f(as[i],*bs);
			}
		}
	}
	template <class T> static void op_scan (int an, int n, T *as, T *bs) {
		for (; n--; as=bs-an) {
			for (int i=0; i<an; i++, as++, bs++) *bs=O::f(*as,*bs);
		}
	}
};

template <class T>
static void quick_mod_map (int n, T *as, T b) {
	if (!b) return;
#define FOO(I) as[I]=mod(as[I],b);
	UNROLL_8(FOO,n,as)
#undef FOO
}

template <class T> static void quick_ign_map (int n, T *as, T b) {}
template <class T> static void quick_ign_zip (int n, T *as, T *bs) {}
template <class T> static void quick_put_map (int n, T *as, T b) {
#define FOO(I) as[I]=b;
	UNROLL_8(FOO,n,as)
#undef FOO
}

#ifdef PASS1
void quick_put_map (int n, int16 *as, int16 b) {
	if (n&1!=0 && (long)as&4!=0) { *as++=b; n--; }
	quick_put_map (n>>1, (int32 *)as, (int32)(b<<16)+b);
	if (n&1!=0) *as++=b;
}
void quick_put_map (int n, uint8 *as, uint8 b) {
	while (n&3!=0 && (long)as&4!=0) { *as++=b; n--; }
	int32 c=(b<<8)+b; c+=c<<16;
	quick_put_map (n>>2, (int32 *)as, c);
	while (n&3!=0) *as++=b;
}
#endif
template <class T> static void quick_put_zip (int n, T *as, T *bs) {
	gfmemcopy((uint8 *)as, (uint8 *)bs, n*sizeof(T));
}

// classic two-input operator
#define DEF_OP(op,expr,neutral,absorbent) \
	template <class T> class Y##op : Op<T> { public: \
		inline static T f(T a, T b) { return expr; } \
		inline static bool is_neutral  (T x, LeftRight side) { return neutral; } \
		inline static bool is_absorbent(T x, LeftRight side) { return absorbent; } };
#define DEF_OPFT(op,expr,neutral,absorbent,T) \
	template <> class Y##op<T> : Op<T> { public: \
		inline static T f(T a, T b) { return expr; } \
		inline static bool is_neutral  (T x, LeftRight side) { return neutral; } \
		inline static bool is_absorbent(T x, LeftRight side) { return absorbent; } }; \
// this macro is for operators that have different code for the float version
#define DEF_OPF(op,expr,expr2,neutral,absorbent) \
	DEF_OP( op,expr,      neutral,absorbent) \
	DEF_OPFT(op,expr2,neutral,absorbent,float32) \
	DEF_OPFT(op,expr2,neutral,absorbent,float64)

#define DECL_OPON(base,op,T) NumopOn<T>( \
	&base<Y##op<T> >::op_map, &base<Y##op<T> >::op_zip, \
	&base<Y##op<T> >::op_fold, &base<Y##op<T> >::op_scan, \
	&Y##op<T>::is_neutral, &Y##op<T>::is_absorbent)
#define DECL_OPON_NOFOLD(base,op,T) NumopOn<T>( \
	&base<Y##op<T> >::op_map, &base<Y##op<T> >::op_zip, 0,0, \
	&Y##op<T>::is_neutral, &Y##op<T>::is_absorbent)
#define DECL_OP(op,sym,flags) Numop(0, sym, \
	DECL_OPON(OpLoops,op,uint8), DECL_OPON(OpLoops,op,int16), \
	DECL_OPON(OpLoops,op,int32) NONLITE(, DECL_OPON(OpLoops,op,int64), \
	DECL_OPON(OpLoops,op,float32), DECL_OPON(OpLoops,op,float64)), flags)
#define DECL_OP_NOFLOAT(op,sym,flags) Numop(0, sym, \
	DECL_OPON(OpLoops,op,uint8), DECL_OPON(OpLoops,op,int16), \
	DECL_OPON(OpLoops,op,int32) NONLITE(, DECL_OPON(OpLoops,op,int64), \
	NumopOn<float32>(0,0,0,0,0,0), NumopOn<float64>(0,0,0,0,0,0)), flags)
#define DECL_OP_NOFOLD(op,sym,flags) Numop(0, sym, \
	DECL_OPON_NOFOLD(OpLoops,op,uint8), DECL_OPON_NOFOLD(OpLoops,op,int16), \
	DECL_OPON_NOFOLD(OpLoops,op,int32) NONLITE(, DECL_OPON_NOFOLD(OpLoops,op,int64), \
	DECL_OPON_NOFOLD(OpLoops,op,float32), DECL_OPON_NOFOLD(OpLoops,op,float64)), flags)

template <class T> static inline T gf_floor (T a) {
	return (T) floor((double)a); }
template <class T> static inline T gf_trunc (T a) {
	return (T) floor(abs((double)a)) * (a<0?-1:1); }

/*
uint8 clipadd(uint8 a, uint8 b) { int32 c=a+b; return c<0?0:c>255?255:c; }
int16 clipadd(int16 a, int16 b) { int32 c=a+b; return c<-0x8000?-0x8000:c>0x7fff?0x7fff:c; }
int32 clipadd(int32 a, int32 b) { int64 c=a+b; return c<-0x80000000?-0x80000000:c>0x7fffffff?0x7fffffff:c; }
int64 clipadd(int64 a, int64 b) { int64 c=(a>>1)+(b>>1)+(a&b&1);
	return c<(nt_smallest(0LL)/2?nt_smallest(0LL):c>nt_greatest(0LL)/2?nt_greatest(0LL):a+b; }
uint8 clipsub(uint8 a, uint8 b) { int32 c=a-b; return c<0?0:c>255?255:c; }
int16 clipsub(int16 a, int16 b) { int32 c=a-b; return c<-0x8000?-0x8000:c>0x7fff?0x7fff:c; }
int32 clipsub(int32 a, int32 b) { int64 c=a-b; return c<-0x80000000?-0x80000000:c>0x7fffffff?0x7fffffff:c; }
int64 clipsub(int64 a, int64 b) { int64 c=(a>>1)-(b>>1); //???
	return c<(nt_smallest(0LL)/2?nt_smallest(0LL):c>nt_greatest(0LL)/2?nt_greatest(0LL):a-b; }
*/

#ifdef PASS1
DEF_OP(ignore, a, side==at_right, side==at_left)
DEF_OP(put, b, side==at_left, side==at_right)
DEF_OP(add, a+b, x==0, false)
DEF_OP(sub, a-b, side==at_right && x==0, false)
DEF_OP(bus, b-a, side==at_left  && x==0, false)
DEF_OP(mul, a*b, x==1, x==0)
DEF_OP(mulshr8, ((int32)a*(int32)b)>>8, (int64)x==256, x==0) //!@#$ bug with int64
DEF_OP(div, b==0 ? 0 : a/b, side==at_right && x==1, false)
DEF_OP(div2, b==0 ? 0 : div2(a,b), side==at_right && x==1, false)
DEF_OP(vid, a==0 ? 0 : b/a, side==at_left && x==1, false)
DEF_OP(vid2, a==0 ? 0 : div2(b,a), side==at_left && x==1, false)
DEF_OPF(mod, b==0 ? 0 : mod(a,b), b==0 ? 0 : a-b*gf_floor(a/b),
	false, side==at_left && x==0 || side==at_right && x==1)
DEF_OPF(dom, a==0 ? 0 : mod(b,a), a==0 ? 0 : b-a*gf_floor(b/a),
	false, side==at_left && x==0 || side==at_right && x==1)
//DEF_OPF(rem, b==0 ? 0 : a%b, b==0 ? 0 : a-b*gf_trunc(a/b))
//DEF_OPF(mer, a==0 ? 0 : b%a, a==0 ? 0 : b-a*gf_trunc(b/a))
DEF_OP(rem, b==0?0:a%b, false, side==at_left&&x==0 || side==at_right&&x==1)
DEF_OP(mer, a==0?0:b%a, false, side==at_left&&x==0 || side==at_right&&x==1)
#endif
#ifdef PASS2
DEF_OP(gcd, gcd(a,b), x==0, x==1)
DEF_OP(gcd2, gcd2(a,b), x==0, x==1) // should test those and pick one of the two
DEF_OP(lcm, a==0 || b==0 ? 0 : lcm(a,b), x==1, x==0)
DEF_OPF(or , a|b, (float32)((int32)a | (int32)b), x==0, x==nt_all_ones(&x))
DEF_OPF(xor, a^b, (float32)((int32)a ^ (int32)b), x==0, false)
DEF_OPF(and, a&b, (float32)((int32)a & (int32)b), x==nt_all_ones(&x), x==0)
DEF_OPF(shl, a<<b, a*pow(2.0,+b), side==at_right && x==0, false)
DEF_OPF(shr, a>>b, a*pow(2.0,-b), side==at_right && x==0, false)
DEF_OP(sc_and, a ? b : a, side==at_left && x!=0, side==at_left && x==0)
DEF_OP(sc_or,  a ? a : b, side==at_left && x==0, side==at_left && x!=0)
DEF_OP(min, min(a,b), x==nt_greatest(&x), x==nt_smallest(&x))
DEF_OP(max, max(a,b), x==nt_smallest(&x), x==nt_greatest(&x))
#endif
#ifdef PASS3
DEF_OP(cmp, cmp(a,b), false, false)
DEF_OP(eq,  a == b, false, false)
DEF_OP(ne,  a != b, false, false)
DEF_OP(gt,  a >  b, false, side==at_left&&x==nt_smallest(&x)||side==at_right&&x==nt_greatest(&x))
DEF_OP(le,  a <= b, false, side==at_left&&x==nt_smallest(&x)||side==at_right&&x==nt_greatest(&x))
DEF_OP(lt,  a <  b, false, side==at_left&&x==nt_greatest(&x)||side==at_right&&x==nt_smallest(&x))
DEF_OP(ge,  a >= b, false, side==at_left&&x==nt_greatest(&x)||side==at_right&&x==nt_smallest(&x))
DEF_OP(sin, (T)(b * sin(a * (M_PI / 18000))), false, false) // "LN=9000+36000n RA=0 LA=..."
DEF_OP(cos, (T)(b * cos(a * (M_PI / 18000))), false, false) // "LN=36000n RA=0 LA=..."
DEF_OP(atan, (T)(atan2(a,b) * (18000 / M_PI)), false, false) // "LA=0"
DEF_OP(tanh, (T)(b * tanh(a * (M_PI / 18000))), false, x==0)
DEF_OP(gamma, b<=0 ? 0 : (T)(0+floor(pow(a/256.0,256.0/b)*256.0)), false, false) // "RN=256"
DEF_OP(pow, ipow(a,b), false, false) // "RN=1"
DEF_OP(log, (T)(a==0 ? 0 : b * log(gf_abs(a))), false, false) // "RA=0"
// 0.7.8
//DEF_OPF(clipadd, clipadd(a,b), a+b, x==0, false)
//DEF_OPF(clipsub, clipsub(a,b), a-b, side==at_right && x==0, false)
DEF_OP(abssub,  gf_abs(a-b), false, false)
DEF_OP(sqsub, (a-b)*(a-b), false, false)
DEF_OP(avg, (a+b)/2, false, false)
DEF_OP(hypot, (T)(0+floor(sqrt(a*a+b*b))), false, false)
DEF_OP(sqrt, (T)(0+floor(sqrt(a))), false, false)
DEF_OP(rand, a==0 ? 0 : random()%(int32)a, false, false)
//DEF_OP(erf,"erf*", 0)
#endif

extern Numop      op_table1[], op_table2[], op_table3[];
extern const long op_table1_n, op_table2_n, op_table3_n;

#ifdef PASS1
Numop op_table1[] = {
	DECL_OP(ignore, "ignore", OP_ASSOC),
	DECL_OP(put, "put", OP_ASSOC),
	DECL_OP(add, "+", OP_ASSOC|OP_COMM), // "LINV=sub"
	DECL_OP(sub, "-", 0),
	DECL_OP(bus, "inv+", 0),
	DECL_OP(mul, "*", OP_ASSOC|OP_COMM),
	DECL_OP_NOFLOAT(mulshr8, "*>>8", OP_ASSOC|OP_COMM),
	DECL_OP(div, "/", 0),
	DECL_OP_NOFLOAT(div2, "div", 0),
	DECL_OP(vid, "inv*", 0),
	DECL_OP_NOFLOAT(vid2, "swapdiv", 0),
	DECL_OP_NOFLOAT(mod, "%", 0),
	DECL_OP_NOFLOAT(dom, "swap%", 0),
	DECL_OP_NOFLOAT(rem, "rem", 0),
	DECL_OP_NOFLOAT(mer, "swaprem", 0),
};
const long op_table1_n = COUNT(op_table1);
#endif
#ifdef PASS2
Numop op_table2[] = {
	DECL_OP_NOFLOAT(gcd, "gcd", OP_ASSOC|OP_COMM),
	DECL_OP_NOFLOAT(gcd2, "gcd2", OP_ASSOC|OP_COMM),
	DECL_OP_NOFLOAT(lcm, "lcm", OP_ASSOC|OP_COMM),
	DECL_OP(or , "|", OP_ASSOC|OP_COMM),
	DECL_OP(xor, "^", OP_ASSOC|OP_COMM),
	DECL_OP(and, "&", OP_ASSOC|OP_COMM),
	DECL_OP_NOFOLD(shl, "<<", 0),
	DECL_OP_NOFOLD(shr, ">>", 0),
	DECL_OP_NOFOLD(sc_and,"&&", 0),
	DECL_OP_NOFOLD(sc_or, "||", 0),
	DECL_OP(min, "min", OP_ASSOC|OP_COMM),
	DECL_OP(max, "max", OP_ASSOC|OP_COMM),
};
const long op_table2_n = COUNT(op_table2);
#endif
#ifdef PASS3
Numop op_table3[] = {
	DECL_OP_NOFOLD(eq,  "==", OP_COMM),
	DECL_OP_NOFOLD(ne,  "!=", OP_COMM),
	DECL_OP_NOFOLD(gt,  ">", 0),
	DECL_OP_NOFOLD(le,  "<=", 0),
	DECL_OP_NOFOLD(lt,  "<", 0),
	DECL_OP_NOFOLD(ge,  ">=", 0),
	DECL_OP_NOFOLD(cmp, "cmp", 0),
	DECL_OP_NOFOLD(sin, "sin*", 0),
	DECL_OP_NOFOLD(cos, "cos*", 0),
	DECL_OP_NOFOLD(atan, "atan", 0),
	DECL_OP_NOFOLD(tanh, "tanh*", 0),
	DECL_OP_NOFOLD(gamma, "gamma", 0),
	DECL_OP_NOFOLD(pow, "**", 0),
	DECL_OP_NOFOLD(log, "log*", 0),
// 0.7.8
//	DECL_OP(clipadd,"clip+", OP_ASSOC|OP_COMM),
//	DECL_OP(clipsub,"clip-", 0),
	DECL_OP_NOFOLD(abssub,"abs-", OP_COMM),
	DECL_OP_NOFOLD(sqsub,"sq-", OP_COMM),
	DECL_OP_NOFOLD(avg,"avg", OP_COMM),
	DECL_OP_NOFOLD(hypot,"hypot", OP_COMM),
	DECL_OP_NOFOLD(sqrt,"sqrt", 0),
	DECL_OP_NOFOLD(rand,"rand", 0),
	//DECL_OP_NOFOLD(erf,"erf*", 0),
};
const long op_table3_n = COUNT(op_table3);
#endif

// D=dictionary, A=table, A##_n=table count.
#define INIT_TABLE(D,A) { D=IEVAL(mGridFlow,"@"#D" ||= {}"); \
	for(int i=0; i<A##_n; i++) { \
		A[i].sym = ID2SYM(rb_intern(A[i].name)); \
		rb_hash_aset(D,A[i].sym,PTR2FIX((A+i)));}}

#ifdef PASS1
Ruby op_dict = Qnil;
Ruby number_type_dict = Qnil;
void startup_number () {
	INIT_TABLE(op_dict,op_table1)
	INIT_TABLE(op_dict,op_table2)
	INIT_TABLE(op_dict,op_table3)
	INIT_TABLE(number_type_dict,number_type_table)

	for (int i=0; i<COUNT(number_type_table); i++) {
		number_type_table[i].index = (NumberTypeE) i;
		char a[64];
		strcpy(a,number_type_table[i].aliases);
		char *b = strchr(a,',');
		if (b) {
			*b=0;
			rb_hash_aset(number_type_dict, ID2SYM(rb_intern(b+1)),
				PTR2FIX(&number_type_table[i]));
		}
		rb_hash_aset(number_type_dict, ID2SYM(rb_intern(a)),
				PTR2FIX(&number_type_table[i]));
	}
// S:name; M:mode; F:replacement function;
#define OVERRIDE_INT(S,M,F) { \
	Numop *foo = FIX2PTR(Numop,rb_hash_aref(op_dict,SYM(S))); \
	foo->on_uint8.op_##M=F; \
	foo->on_int16.op_##M=F; \
	foo->on_int32.op_##M=F; }
	OVERRIDE_INT(ignore,map,quick_ign_map);
	OVERRIDE_INT(ignore,zip,quick_ign_zip);
	//OVERRIDE_INT(put,map,quick_put_map);
	//OVERRIDE_INT(put,zip,quick_put_zip);
	//OVERRIDE_INT(%,map,quick_mod_map); // !@#$ does that make an improvement at all?
}
#endif

--- NEW FILE: flow_objects.rb ---
=begin
	$Id: flow_objects.rb,v 1.1 2005/10/04 02:02:13 matju Exp $

	GridFlow
	Copyright (c) 2001,2002,2003,2004 by Mathieu Bouchard

	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License
	as published by the Free Software Foundation; either version 2
	of the License, or (at your option) any later version.

	See file ../COPYING for further informations on licensing terms.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
[...1437 lines suppressed...]
  def _0_float i; @i=i; send_out 0, *@a[@i]; end
  def _1_list(*l) @a[@i]=l; end
  def _0_save(filename,format=nil)
    f=File.open(filename.to_s,"w")
    if format then
      @a.each {|x| f.puts(format.to_s%x) }
    else
      @a.each {|x| f.puts(x.join(",")) }
    end
    f.close
  end
  def _0_load(filename)
    f=File.open(filename.to_s,"r")
    @a.clear
    f.each {|x| @a.push x.split(",").map {|y| Float(y) rescue y.intern }}
    f.close
  end
}

end # module GridFlow

--- NEW FILE: test.rb ---
# $Id: test.rb,v 1.1 2005/10/04 02:02:13 matju Exp $

$:.delete_if {|x| x=='.' }
require "gridflow"

include GridFlow
GridFlow.verbose=true

$imdir = "./images"
$animdir = "/opt/mex"
srand Time.new.to_i
$port = 4200+rand(100)

def pressakey; puts "press return to continue."; readline; end

class Expect < FObject
	def praise(*a)
		#raise(*a)
		puts a
[...1047 lines suppressed...]
#test_anim "open file #{$imdir}/g001.jpg"#,"loop 0"
#test_anim "open ppm file #{$animdir}/b.ppm.cat"
#test_anim "open jpeg file #{$imdir}/rgb.jpeg.cat"
#test_anim "open quicktime file BLAH"
#test_anim "open quicktime file #{$imdir}/rgb_uncompressed.mov"
#test_anim "open quicktime file #{$imdir}/test_mjpega.mov"
#test_anim "open ppm gzfile motion_tracking.ppm.cat.gz"
#test_anim "open videodev /dev/video","channel 1","size 480 640"
#test_anim "open videodev /dev/video1 noinit","transfer read"
#test_anim "open videodev /dev/video","channel 1","size 120 160"
#test_anim "open mpeg file /home/matju/net/Animations/washington_zoom_in.mpeg"
#test_anim "open quicktime file /home/matju/Shauna/part_1.mov"
#test_anim "open quicktime file #{$imdir}/gt.mov"
#test_anim "open quicktime file /home/matju/pics/domopers_hi.mov"
#test_anim "open quicktime file /home/matju/net/c.mov"
#test_formats
#test_tcp
#test_sound
#test_metro
#$mainloop.loop

--- NEW FILE: flow_objects.c ---
/*
	$Id: flow_objects.c,v 1.1 2005/10/04 02:02:13 matju Exp $

	GridFlow
	Copyright (c) 2001,2002,2003,2004 by Mathieu Bouchard

	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License
	as published by the Free Software Foundation; either version 2
	of the License, or (at your option) any later version.

	See file ../COPYING for further informations on licensing terms.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
[...1150 lines suppressed...]
	}	
} GRID_END

\def void initialize (int32 z) {rb_call_super(argc,argv); this->z=z; }
\classinfo { IEVAL(rself,"install '#perspective',1,1"); }
\end class GridPerspective

static Numop *OP(Ruby x) { return FIX2PTR(Numop,rb_hash_aref(op_dict,x)); }

void startup_flow_objects () {
	op_add = OP(SYM(+));
	op_sub = OP(SYM(-));
	op_mul = OP(SYM(*));
	op_shl = OP(SYM(<<));
	op_mod = OP(SYM(%));
	op_and = OP(SYM(&));
	op_div = OP(SYM(/));
	op_put = OP(SYM(put));
	\startall
}

--- NEW FILE: bitpacking.c ---
/*
	$Id: bitpacking.c,v 1.1 2005/10/04 02:02:13 matju Exp $

	GridFlow
	Copyright (c) 2001,2002,2003,2004 by Mathieu Bouchard

	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License
	as published by the Free Software Foundation; either version 2
	of the License, or (at your option) any later version.

	See file ../COPYING for further informations on licensing terms.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/

#include "grid.h.fcs"
#include <math.h>
#include <stdlib.h>
#include <stdio.h>

#define CONVERT1 t = \
	(((in[0] << hb[0]) >> 7) & mask[0]) | \
	(((in[1] << hb[1]) >> 7) & mask[1]) | \
	(((in[2] << hb[2]) >> 7) & mask[2])

#define CONVERT2 \
	for (t=0,i=0; i<self->size; i++) t |= (((in[i] << hb[i]) >> 7) & mask[i]);

#define CONVERT3 \
	for (t=0,i=0; i<self->size; i++) { \
		t |= ((in[i]>>(7-hb[i]))|(in[i]<<(hb[i]-7))) & mask[i]; \
	}

#define WRITE_LE \
	for (int bytes = self->bytes; bytes; bytes--, t>>=8) *out++ = t;

#define WRITE_BE { int bytes; \
	bytes = self->bytes; \
	while (bytes--) { out[bytes] = t; t>>=8; }\
	out += self->bytes; }

/* this macro would be faster if the _increment_
   was done only once every loop. or maybe gcc does it, i dunno */
#define NTIMES(_x_) \
	for (; n>=4; n-=4) { _x_ _x_ _x_ _x_ } \
	for (; n; n--) { _x_ }

/* this could be faster (use asm) */
void swap32 (int n, Pt<uint32> data) {
	NTIMES({
		uint32 x = *data;
		x = (x<<16) | (x>>16);
		x = ((x&0xff00ff)<<8) | ((x>>8)&0xff00ff);
		*data++ = x;
	})
}

/* this could be faster (use asm or do it in int32 chunks) */
void swap16 (int n, Pt<uint16> data) {
	NTIMES({ uint16 x = *data; *data++ = (x<<8) | (x>>8); })
}

/* **************************************************************** */

template <class T>
static void default_pack(BitPacking *self, int n, Pt<T> in, Pt<uint8> out) {
	uint32 t;
	int i;
	int hb[4];
	uint32 mask[4];
	int sameorder = self->endian==2 || self->endian==::is_le();
	int size = self->size;

	for (i=0; i<self->size; i++) hb[i] = highest_bit(self->mask[i]);
	memcpy(mask,self->mask,size*sizeof(uint32));

	if (sameorder && size==3) {
		switch(self->bytes) {
		case 2:	NTIMES(t=CONVERT1; *((int16 *)out)=t; out+=2; in+=3;) return;
		case 4:	NTIMES(t=CONVERT1; *((int32 *)out)=t; out+=4; in+=3;) return;
		}
	}
	if (self->is_le()) {
		switch (size) {
		case 3: for (; n--; in+=3) {CONVERT1; WRITE_LE;} break;
		case 4:	for (; n--; in+=4) {CONVERT3; WRITE_LE;} break;
		default:for (; n--; in+=size) {CONVERT2; WRITE_LE;}}
	} else {
		switch (size) {
		case 3: for (; n--; in+=3) {CONVERT1; WRITE_BE;} break;
		case 4: for (; n--; in+=4) {CONVERT3; WRITE_BE;} break;
		default:for (; n--; in+=size) {CONVERT2; WRITE_BE;}}
	}
}

#define LOOP_UNPACK(_reader_) \
	for (; n; n--) { \
		int bytes=0; uint32 temp=0; _reader_; \
		for (int i=0; i<self->size; i++, out++) { \
			uint32 t=temp&self->mask[i]; \
			*out = (t<<(7-hb[i]))|(t>>(hb[i]-7)); \
		} \
	}
//			*out++ = ((temp & self->mask[i]) << 7) >> hb[i];

template <class T>
static void default_unpack(BitPacking *self, int n, Pt<uint8> in, Pt<T> out) {
	int hb[4];
	for (int i=0; i<self->size; i++) hb[i] = highest_bit(self->mask[i]);
	if (is_le()) { // smallest byte first
		LOOP_UNPACK(
			for(; self->bytes>bytes; bytes++, in++) temp |= *in<<(8*bytes);
		)
	} else { // biggest byte first
		LOOP_UNPACK(
			bytes=self->bytes; for (; bytes; bytes--, in++) temp=(temp<<8)|*in;
		)
	}
}

/* **************************************************************** */

template <class T>
static void pack2_565(BitPacking *self, int n, Pt<T> in, Pt<uint8> out) {
	const int hb[3] = {15,10,4};
	const uint32 mask[3] = {0x0000f800,0x000007e0,0x0000001f};
	uint32 t;
	NTIMES( t=CONVERT1; *((short *)out)=t; out+=2; in+=3; )
}

template <class T>
static void pack3_888(BitPacking *self, int n, Pt<T> in, Pt<uint8> out) {
	Pt<int32> o32 = (Pt<int32>)out;
	while (n>=4) {
		o32[0] = (in[5]<<24) | (in[ 0]<<16) | (in[ 1]<<8) | in[2];
		o32[1] = (in[7]<<24) | (in[ 8]<<16) | (in[ 3]<<8) | in[4];
		o32[2] = (in[9]<<24) | (in[10]<<16) | (in[11]<<8) | in[6];
		o32+=3; in+=12;
		n-=4;
	}
	out = (Pt<uint8>)o32;
	NTIMES( out[2]=in[0]; out[1]=in[1]; out[0]=in[2]; out+=3; in+=3; )
}

/*
template <>
static void pack3_888(BitPacking *self, int n, Pt<uint8> in, Pt<uint8> out) {
	Pt<uint32> o32 = Pt<uint32>((uint32 *)out.p,n*3/4);
	Pt<uint32> i32 = Pt<uint32>((uint32 *)in.p,n*3/4);
	while (n>=4) {
#define Z(w,i) ((word##w>>(i*8))&255)
		uint32 word0 = i32[0];
		uint32 word1 = i32[1];
		uint32 word2 = i32[2];
		o32[0] = (Z(1,1)<<24) | (Z(0,0)<<16) | (Z(0,1)<<8) | Z(0,2);
		o32[1] = (Z(1,3)<<24) | (Z(2,0)<<16) | (Z(0,3)<<8) | Z(1,0);
		o32[2] = (Z(2,1)<<24) | (Z(2,2)<<16) | (Z(2,3)<<8) | Z(1,2);
		o32+=3; i32+=3;
		n-=4;
	}
#undef Z
	out = (Pt<uint8>)o32;
	in  = (Pt<uint8>)i32;
	NTIMES( out[2]=in[0]; out[1]=in[1]; out[0]=in[2]; out+=3; in+=3; )
}
*/

template <class T>
static void pack3_888b(BitPacking *self, int n, Pt<T> in, Pt<uint8> out) {
	Pt<int32> o32 = (Pt<int32>)out;
	while (n>=4) {
		o32[0] = (in[0]<<16) | (in[1]<<8) | in[2];
		o32[1] = (in[3]<<16) | (in[4]<<8) | in[5];
		o32[2] = (in[6]<<16) | (in[7]<<8) | in[8];
		o32[3] = (in[9]<<16) | (in[10]<<8) | in[11];
		o32+=4; in+=12;
		n-=4;
	}
	NTIMES( o32[0] = (in[0]<<16) | (in[1]<<8) | in[2]; o32++; in+=3; )
}

/* (R,G,B,?) -> B:8,G:8,R:8,0:8 */
template <class T>
static void pack3_bgrn8888(BitPacking *self, int n, Pt<T> in, Pt<uint8> out) {
/* NTIMES( out[2]=in[0]; out[1]=in[1]; out[0]=in[2]; out+=4; in+=4; ) */
	Pt<int32> i32 = (Pt<int32>)in;
	Pt<int32> o32 = (Pt<int32>)out;
	while (n>=4) {
		o32[0] = ((i32[0]&0xff)<<16) | (i32[0]&0xff00) | ((i32[0]>>16)&0xff);
		o32[1] = ((i32[1]&0xff)<<16) | (i32[1]&0xff00) | ((i32[1]>>16)&0xff);
		o32[2] = ((i32[2]&0xff)<<16) | (i32[2]&0xff00) | ((i32[2]>>16)&0xff);
		o32[3] = ((i32[3]&0xff)<<16) | (i32[3]&0xff00) | ((i32[3]>>16)&0xff);
		o32+=4; i32+=4; n-=4;
	}
	NTIMES( o32[0] = ((i32[0]&0xff)<<16) | (i32[0]&0xff00) | ((i32[0]>>16)&0xff); o32++; i32++; )
}

static uint32 bp_masks[][4] = {
	{0x0000f800,0x000007e0,0x0000001f,0},
	{0x00ff0000,0x0000ff00,0x000000ff,0},
};

static Packer bp_packers[] = {
	{default_pack, default_pack, default_pack},
	{pack2_565, pack2_565, pack2_565},
	{pack3_888, pack3_888, pack3_888},
	{pack3_888b, default_pack, default_pack},
	{pack3_bgrn8888, default_pack, default_pack},
};

static Unpacker bp_unpackers[] = {
	{default_unpack, default_unpack, default_unpack},
};	

static BitPacking builtin_bitpackers[] = {
	BitPacking(2, 2, 3, bp_masks[0], &bp_packers[1], &bp_unpackers[0]),
	BitPacking(1, 3, 3, bp_masks[1], &bp_packers[2], &bp_unpackers[0]),
	BitPacking(1, 4, 3, bp_masks[1], &bp_packers[3], &bp_unpackers[0]),
	BitPacking(1, 4, 4, bp_masks[1], &bp_packers[4], &bp_unpackers[0]),
};

/* **************************************************************** */

bool BitPacking::eq(BitPacking *o) {
	if (!(bytes == o->bytes)) return false;
	if (!(size == o->size)) return false;
	for (int i=0; i<size; i++) {
		if (!(mask[i] == o->mask[i])) return false;
	}
	if (endian==o->endian) return true;
	/* same==little on a little-endian; same==big on a big-endian */
	return (endian ^ o->endian ^ ::is_le()) == 2;
}

BitPacking::BitPacking(int endian, int bytes, int size, uint32 *mask,
Packer *packer, Unpacker *unpacker) {
	this->endian = endian;
	this->bytes = bytes;
	this->size = size;
	for (int i=0; i<size; i++) this->mask[i] = mask[i];
	if (packer) {
		this->packer = packer;
		this->unpacker = unpacker;
		return;
	}
	int packeri=-1;
	this->packer = &bp_packers[0];
	this->unpacker = &bp_unpackers[0];

	for (int i=0; i<(int)(sizeof(builtin_bitpackers)/sizeof(BitPacking)); i++) {
		BitPacking *bp = &builtin_bitpackers[i];
		if (this->eq(bp)) {
			this->packer = bp->packer;
			this->unpacker = bp->unpacker;
			packeri=i;
			goto end;
		}
	}
end:;
/*
	::gfpost("Bitpacking: endian=%d bytes=%d size=%d packeri=%d",
		endian, bytes, size, packeri);
	::gfpost("  packer=0x%08x unpacker=0x%08x",this->packer,this->unpacker);
	::gfpost("  mask=[0x%08x,0x%08x,0x%08x,0x%08x]",mask[0],mask[1],mask[2],mask[3]);
*/
}

bool BitPacking::is_le() {
	return endian==1 || (endian ^ ::is_le())==3;
}

template <class T>
void BitPacking::pack(int n, Pt<T> in, Pt<uint8> out) {
	switch (NumberTypeE_type_of(*in)) {
	case uint8_e: packer->as_uint8(this,n,(Pt<uint8>)in,out); break;
	case int16_e: packer->as_int16(this,n,(Pt<int16>)in,out); break;
	case int32_e: packer->as_int32(this,n,(Pt<int32>)in,out); break;
	default: RAISE("argh");
	}
}

template <class T>
void BitPacking::unpack(int n, Pt<uint8> in, Pt<T> out) {
	switch (NumberTypeE_type_of(*out)) {
	case uint8_e: unpacker->as_uint8(this,n,in,(Pt<uint8>)out); break;
	case int16_e: unpacker->as_int16(this,n,in,(Pt<int16>)out); break;
	case int32_e: unpacker->as_int32(this,n,in,(Pt<int32>)out); break;
	default: RAISE("argh");
	}
}

// i'm sorry... see the end of grid.c for an explanation...
//static
void make_hocus_pocus () {
//	exit(1);
#define FOO(S) \
	((BitPacking*)0)->pack(0,Pt<S>(),Pt<uint8>()); \
	((BitPacking*)0)->unpack(0,Pt<uint8>(),Pt<S>());
EACH_NUMBER_TYPE(FOO)
#undef FOO
}

--- NEW FILE: grid.h ---
/*
	$Id: grid.h,v 1.1 2005/10/04 02:02:13 matju Exp $

	GridFlow
	Copyright (c) 2001,2002,2003,2004 by Mathieu Bouchard

	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License
	as published by the Free Software Foundation; either version 2
	of the License, or (at your option) any later version.

	See file ../COPYING for further informations on licensing terms.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
[...1070 lines suppressed...]
	GFStackFrame s[GF_STACK_MAX];
	int n;
	GFStack() { n = 0; }
	void push (FObject *o) __attribute__((noinline));
	void pop () __attribute__((noinline));
};
extern GFStack gf_stack;
struct GFStackMarker {
	int n;
	bool flag;
	GFStackMarker(FObject *o) { n = gf_stack.n; gf_stack.push(o); flag=true; }
	~GFStackMarker() { while (gf_stack.n != n) gf_stack.pop(); }
	bool once () {
		if (flag) { flag=false; return true; } else return false;
	}
};

typedef GridObject Format;

#endif // __GF_GRID_H

--- NEW FILE: main.c ---
/*
	$Id: main.c,v 1.1 2005/10/04 02:02:13 matju Exp $

	GridFlow
	Copyright (c) 2001,2002,2003,2004 by Mathieu Bouchard

	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License
	as published by the Free Software Foundation; either version 2
	of the License, or (at your option) any later version.

	See file ../COPYING for further informations on licensing terms.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/

#include <stdlib.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <time.h>
#include <stdarg.h>
#include <string.h>
#include <signal.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdarg.h>

#include "grid.h.fcs"
#include "../config.h"
#include <assert.h>
#include <limits.h>

BuiltinSymbols bsym;
GFStack gf_stack;
Ruby mGridFlow;
Ruby cFObject;

extern "C"{
void rb_raise0(
const char *file, int line, const char *func, VALUE exc, const char *fmt, ...) {
	va_list args;
	char buf[BUFSIZ];
	va_start(args,fmt);
	vsnprintf(buf, BUFSIZ, fmt, args);
	buf[BUFSIZ-1]=0;
	va_end(args);
	VALUE e = rb_exc_new2(exc, buf);
	char buf2[BUFSIZ];
	snprintf(buf2, BUFSIZ, "%s:%d:in `%s'", file, line, func);
	buf2[BUFSIZ-1]=0;
	VALUE ary = rb_funcall(e,SI(caller),0);
	if (gf_stack.n) {
		rb_funcall(ary,SI(unshift),2,rb_str_new2(buf2),
			rb_str_new2(INFO(gf_stack.s[gf_stack.n-1].o)));
	} else {
		rb_funcall(ary,SI(unshift),1,rb_str_new2(buf2));
	}
	rb_funcall(e,SI(set_backtrace),1,ary);
	rb_exc_raise(e);
}};

Ruby rb_ary_fetch(Ruby rself, int i) {
	Ruby argv[] = { INT2NUM(i) };
	return rb_ary_aref(COUNT(argv),argv,rself);
}

//----------------------------------------------------------------
// CObject

static void CObject_mark (void *z) {}
void CObject_free (void *foo) {
	CObject *self = (CObject *)foo;
	self->check_magic();
	if (!self->rself) {
		fprintf(stderr,"attempt to free object that has no rself\n");
		abort();
	}
	self->rself = 0; /* paranoia */
	delete self;
}

//----------------------------------------------------------------
// Dim

void Dim::check() {
	if (n>MAX_DIMENSIONS) RAISE("too many dimensions");
	for (int i=0; i<n; i++) if (v[i]<0) RAISE("Dim: negative dimension");
}

// !@#$ big leak machine?
// returns a string like "Dim[240,320,3]"
char *Dim::to_s() {
	// if you blow 256 chars it's your own fault
	char buf[256];
	char *s = buf;
	s += sprintf(s,"Dim[");
	for(int i=0; i<n; i++) s += sprintf(s,"%s%d", ","+!i, v[i]);
	s += sprintf(s,"]");
	return strdup(buf);
}

//----------------------------------------------------------------
\class FObject < CObject

static void FObject_prepare_message(int &argc, Ruby *&argv, Ruby &sym, FObject *foo=0) {
	if (argc<1) {
		sym = bsym._bang;
	} else if (argc>1 && !SYMBOL_P(*argv)) {
		sym = bsym._list;
	} else if (INTEGER_P(*argv)||FLOAT_P(*argv)) {
		sym = bsym._float;
	} else if (SYMBOL_P(*argv)) {
		sym = *argv;
		argc--, argv++;
	} else if (argc==1 && TYPE(*argv)==T_ARRAY) {
		sym = bsym._list;
		argc = rb_ary_len(*argv);
		argv = rb_ary_ptr(*argv);
	} else {
		RAISE("%s received bad message: argc=%d; argv[0]=%s",foo?INFO(foo):"", argc,
			argc ? rb_str_ptr(rb_inspect(argv[0])) : "");
	}
}

struct Helper {
	int argc;
	Ruby *argv;
	FObject *self;
	Ruby rself;
	int n; // stack level
};

static Ruby GridFlow_handle_braces(Ruby rself, Ruby argv);

// inlet #-1 is reserved for SystemInlet messages
// inlet #-2 is for inlet #0 messages that happen at start time
static void send_in_2 (Helper *h) { PROF(h->self) {
	int argc = h->argc;
	Ruby *argv = h->argv;
	if (h->argc<1) RAISE("not enough args");
	int inlet = INT(argv[0]);
	argc--, argv++;
	Ruby foo;
	if (argc==1 && TYPE(argv[0])==T_STRING /* && argv[0] =~ / / */) {
		foo = rb_funcall(mGridFlow,SI(parse),1,argv[0]);
		argc = rb_ary_len(foo);
		argv = rb_ary_ptr(foo);
	}
	if (argc>1) {
		foo = rb_ary_new4(argc,argv);
		GridFlow_handle_braces(0,foo);
		argc = rb_ary_len(foo);
		argv = rb_ary_ptr(foo);
	}
	if (inlet==-2) {
		Array init_messages = rb_ivar_get(h->rself,SI(@init_messages));
		rb_ary_push(init_messages, rb_ary_new4(argc,argv));
		inlet=0;
	}
	if (inlet<0 || inlet>9 /*|| inlet>real_inlet_max*/)
		if (inlet!=-3 && inlet!=-1) RAISE("invalid inlet number: %d", inlet);
	Ruby sym;
	FObject_prepare_message(argc,argv,sym,h->self);
//	if (rb_const_get(mGridFlow,SI(@verbose))==Qtrue) gfpost m.inspect
	char buf[256];
	if (inlet==-1) sprintf(buf,"_sys_%s",rb_sym_name(sym));
	else           sprintf(buf,"_%d_%s",inlet,rb_sym_name(sym));
	rb_funcall2(h->rself,rb_intern(buf),argc,argv);
} /* PROF */ }

static void send_in_3 (Helper *h) {
	while (gf_stack.n > h->n) gf_stack.pop();
}

\def void send_in (...) {
	Helper h = {argc,argv,this,rself,gf_stack.n};
	rb_ensure(
		(RMethod)send_in_2,(Ruby)&h,
		(RMethod)send_in_3,(Ruby)&h);
}

\def void send_out (...) {
	int n=0;
	if (argc<1) RAISE("not enough args");
	int outlet = INT(*argv);
	if (outlet<0 || outlet>9 /*|| outlet>real_outlet_max*/)
		RAISE("invalid outlet number: %d",outlet);
	argc--, argv++;
	Ruby sym;
	FObject_prepare_message(argc,argv,sym,this);
	Ruby noutlets2 = rb_ivar_get(rb_obj_class(rself),SYM2ID(SYM(@noutlets)));
	if (TYPE(noutlets2)!=T_FIXNUM) {
		IEVAL(rself,"STDERR.puts inspect");
		RAISE("don't know how many outlets this has");
	}
	int noutlets = INT(noutlets2);
	//if (outlet<0 || outlet>=noutlets) RAISE("outlet %d does not exist",outlet);
	// was PROF(0) a hack because of exception-handling problems?
	PROF(0) {
	Ruby argv2[argc+2];
	for (int i=0; i<argc; i++) argv2[2+i] = argv[i];
	argv2[0] = INT2NUM(outlet);
	argv2[1] = sym;
	rb_funcall2(rself,SI(send_out2), argc+2, argv2);

	Ruby ary = rb_ivar_defined(rself,SYM2ID(bsym.iv_outlets)) ?
		rb_ivar_get(rself,SYM2ID(bsym.iv_outlets)) : Qnil;
	if (ary==Qnil) goto end;
	if (TYPE(ary)!=T_ARRAY) RAISE("send_out: expected array");
	ary = rb_ary_fetch(ary,outlet);
	if (ary==Qnil) goto end;
	if (TYPE(ary)!=T_ARRAY) RAISE("send_out: expected array");
	n = rb_ary_len(ary);

	for (int i=0; i<n; i++) {
		Ruby conn = rb_ary_fetch(ary,i);
		Ruby rec = rb_ary_fetch(conn,0);
		int inl = INT(rb_ary_fetch(conn,1));
		argv2[0] = INT2NUM(inl);
		rb_funcall2(rec,SI(send_in),argc+2,argv2);
	}
	} /* PROF */
end:;
}

Ruby FObject_s_new(Ruby argc, Ruby *argv, Ruby qlass) {
	Ruby allocator = rb_ivar_defined(qlass,SI(@allocator)) ?
		rb_ivar_get(qlass,SI(@allocator)) : Qnil;
	FObject *self;
	if (allocator==Qnil) {
		// this is a pure-ruby FObject/GridObject
		// !@#$ GridObject is in FObject constructor (ugly)
		self = new GridObject;
	} else {
		// this is a C++ FObject/GridObject
		void*(*alloc)() = (void*(*)())FIX2PTR(void,allocator);
		self = (FObject *)alloc();
	}
	self->check_magic();
	Ruby keep = rb_ivar_get(mGridFlow, SI(@fobjects));
	self->bself = 0;
	Ruby rself = Data_Wrap_Struct(qlass, CObject_mark, CObject_free, self);
	self->rself = rself;
	rb_hash_aset(keep,rself,Qtrue); // prevent sweeping
	rb_funcall2(rself,SI(initialize),argc,argv);
	return rself;
}

Ruby FObject_s_install(Ruby rself, Ruby name, Ruby inlets2, Ruby outlets2) {
	int inlets, outlets;
	Ruby name2;
	if (SYMBOL_P(name)) {
		name2 = rb_funcall(name,SI(to_str),0);
	} else if (TYPE(name) == T_STRING) {
		name2 = rb_funcall(name,SI(dup),0);
	} else {
		RAISE("expect symbol or string");
	}
	inlets  =  INT(inlets2); if ( inlets<0 ||  inlets>9) RAISE("...");
	outlets = INT(outlets2); if (outlets<0 || outlets>9) RAISE("...");
	rb_ivar_set(rself,SI(@ninlets),INT2NUM(inlets));
	rb_ivar_set(rself,SI(@noutlets),INT2NUM(outlets));
	rb_ivar_set(rself,SI(@foreign_name),name2);
	rb_hash_aset(rb_ivar_get(mGridFlow,SI(@fclasses)), name2, rself);
	rb_funcall(rself, SI(install2), 1, name2);
	return Qnil;
}

\def Ruby total_time_get () {return gf_ull2num(total_time);}

\def Ruby total_time_set (Ruby x) {
	if (argc<1) RAISE("muh");
	total_time = TO(uint64,x);
	return argv[0];
}

\def void delete_m () {
	Ruby keep = rb_ivar_get(mGridFlow, SI(@fobjects));
	rb_funcall(keep,SI(delete),1,rself);
}

\classinfo
\end class FObject

/* ---------------------------------------------------------------- */
/* C++<->Ruby bridge for classes/functions in base/number.c */

static Ruby String_swap32_f (Ruby rself) {
	int n = rb_str_len(rself)/4;
	swap32(n,Pt<uint32>((uint32 *)rb_str_ptr(rself),n));
	return rself;
}

static Ruby String_swap16_f (Ruby rself) {
	int n = rb_str_len(rself)/2;
	swap16(n,Pt<uint16>((uint16 *)rb_str_ptr(rself),n));
	return rself;
}

NumberTypeE NumberTypeE_find (Ruby sym) {
	if (TYPE(sym)!=T_SYMBOL) RAISE("expected symbol (not %s)",
		rb_str_ptr(rb_inspect(rb_obj_class(sym))));
	Ruby nt_dict = rb_ivar_get(mGridFlow,SI(@number_type_dict));
	Ruby v = rb_hash_aref(nt_dict,sym);
	if (v!=Qnil) return FIX2PTR(NumberType,v)->index;
	RAISE("unknown number type \"%s\"", rb_sym_name(sym));
}

/* **************************************************************** */
\class BitPacking < CObject

\def void initialize(Ruby foo1, Ruby foo2, Ruby foo3) {}

// !@#$ doesn't support number types
\def String pack2 (String ins, String outs=Qnil) {
	int n = rb_str_len(ins) / sizeof(int32) / size;
	Pt<int32> in = Pt<int32>((int32 *)rb_str_ptr(ins),rb_str_len(ins));
	int bytes2 = n*bytes;
	Ruby out = outs!=Qnil ? rb_str_resize(outs,bytes2) : rb_str_new("",bytes2);
	rb_str_modify(out);
	pack(n,Pt<int32>(in,n),Pt<uint8>((uint8 *)rb_str_ptr(out),bytes2));
	return out;
}

// !@#$ doesn't support number types
\def String unpack2 (String ins, String outs=Qnil) {
	int n = rb_str_len(argv[0]) / bytes;
	Pt<uint8> in = Pt<uint8>((uint8 *)rb_str_ptr(ins),rb_str_len(ins));
	int bytes2 = n*size*sizeof(int32);
	Ruby out = outs!=Qnil ? rb_str_resize(outs,bytes2) : rb_str_new("",bytes2);
	rb_str_modify(out);
	unpack(n,Pt<uint8>((uint8 *)in,bytes2),Pt<int32>((int32 *)rb_str_ptr(out),n));
	return out;
}

static Ruby BitPacking_s_new(Ruby argc, Ruby *argv, Ruby qlass) {
	Ruby keep = rb_ivar_get(mGridFlow, rb_intern("@fobjects"));
	if (argc!=3) RAISE("bad args");
	if (TYPE(argv[2])!=T_ARRAY) RAISE("bad mask");
	int endian = INT(argv[0]);
	int bytes = INT(argv[1]);
	Ruby *masks = rb_ary_ptr(argv[2]);
	uint32 masks2[4];
	int size = rb_ary_len(argv[2]);
	if (size<1) RAISE("not enough masks");
	if (size>4) RAISE("too many masks (%d)",size);
	for (int i=0; i<size; i++) masks2[i] = NUM2UINT(masks[i]);
	BitPacking *self = new BitPacking(endian,bytes,size,masks2);
	Ruby rself = Data_Wrap_Struct(qlass, 0, CObject_free, self);
	self->rself = rself;
	rb_hash_aset(keep,rself,Qtrue); // prevent sweeping (leak) (!@#$ WHAT???)
	rb_funcall2(rself,SI(initialize),argc,argv);
	return rself;
}

\classinfo
\end class BitPacking

void gfpost(const char *fmt, ...) {
	va_list args;
	int length;
	va_start(args,fmt);
	const int n=256;
	char post_s[n];
	length = vsnprintf(post_s,n,fmt,args);
	if (length<0 || length>=n) sprintf(post_s+n-6,"[...]"); /* safety */
	va_end(args);
	rb_funcall(mGridFlow,SI(gfpost2),2,rb_str_new2(fmt),rb_str_new2(post_s));
}

void define_many_methods(Ruby rself, int n, MethodDecl *methods) {
	for (int i=0; i<n; i++) {
		MethodDecl *md = &methods[i];
		char *buf = strdup(md->selector);
		if (strlen(buf)>2 && strcmp(buf+strlen(buf)-2,"_m")==0)
			buf[strlen(buf)-2]=0;
		rb_define_method(rself,buf,(RMethod)md->method,-1);
		rb_enable_super(rself,buf);
		free(buf);
	}
}

static Ruby GridFlow_fclass_install(Ruby rself_, Ruby fc_, Ruby super) {
	FClass *fc = FIX2PTR(FClass,fc_);
	Ruby rself = super!=Qnil ?
		rb_define_class_under(mGridFlow, fc->name, super) :
		rb_funcall(mGridFlow,SI(const_get),1,rb_str_new2(fc->name));
	define_many_methods(rself,fc->methodsn,fc->methods);
	rb_ivar_set(rself,SI(@allocator),PTR2FIX((void*)(fc->allocator))); //#!@$??
	if (fc->startup) fc->startup(rself);
	return Qnil;
}

//----------------------------------------------------------------
// GridFlow.class
//\class GridFlow_s < patate

typedef void (*Callback)(void*);
static Ruby GridFlow_exec (Ruby rself, Ruby data, Ruby func) {
	void *data2 = FIX2PTR(void,data);
	Callback func2 = (Callback) FIX2PTR(void,func);
	func2(data2);
	return Qnil;
}

static Ruby GridFlow_get_id (Ruby rself, Ruby arg) {
	fprintf(stderr,"%ld\n",arg);
	return INT2NUM((int)arg);
}

Ruby GridFlow_rdtsc (Ruby rself) { return gf_ull2num(rdtsc()); }

/* This code handles nested lists because PureData (0.38) doesn't do it */
static Ruby GridFlow_handle_braces(Ruby rself, Ruby argv) {
	int stack[16];
	int stackn=0;
	Ruby *av = rb_ary_ptr(argv);
	int ac = rb_ary_len(argv);
	int j=0;
	for (int i=0; i<ac; ) {
		int close=0;
		if (SYMBOL_P(av[i])) {
			const char *s = rb_sym_name(av[i]);
			while (*s=='(' || *s=='{') {
				if (stackn==16) RAISE("too many nested lists (>16)");
				stack[stackn++]=j;
				s++;
			}
			const char *se = s+strlen(s);
			while (se[-1]==')' || se[-1]=='}') { se--; close++; }
			if (s!=se) {
				Ruby u = rb_str_new(s,se-s);
				av[j++] = rb_funcall(rself,SI(FloatOrSymbol),1,u);
			}
		} else {
			av[j++]=av[i];
		}
		i++;
		while (close--) {
			if (!stackn) RAISE("unbalanced '}' or ')'",av[i]);
			Ruby a2 = rb_ary_new();
			int j2 = stack[--stackn];
			for (int k=j2; k<j; k++) rb_ary_push(a2,av[k]);
			j=j2;
			av[j++] = a2;
		}
	}
	if (stackn) RAISE("unbalanced '{' or '(' (stackn=%d)",stackn);
	RARRAY(argv)->len = j;
	return rself;
}

/* ---------------------------------------------------------------- */

static uint32 memcpy_calls = 0;
static uint64 memcpy_bytes = 0;
static uint64 memcpy_time  = 0;
static uint32 malloc_calls = 0; /* only new not delete */
static uint64 malloc_bytes = 0; /* only new not delete */
static uint64 malloc_time  = 0; /* in cpu ticks */

// don't touch.
static void gfmemcopy32(int32 *as, int32 *bs, int n) {
	int32 ba = bs-as;
#define FOO(I) as[I] = (as+ba)[I];
		UNROLL_8(FOO,n,as)
#undef FOO

}

void gfmemcopy(uint8 *out, const uint8 *in, int n) {
	uint64 t = rdtsc();
	memcpy_calls++;
	memcpy_bytes+=n;
	for (; n>16; in+=16, out+=16, n-=16) {
		((int32*)out)[0] = ((int32*)in)[0];
		((int32*)out)[1] = ((int32*)in)[1];
		((int32*)out)[2] = ((int32*)in)[2];
		((int32*)out)[3] = ((int32*)in)[3];
	}
	for (; n>4; in+=4, out+=4, n-=4) { *(int32*)out = *(int32*)in; }
	for (; n; in++, out++, n--) { *out = *in; }
	t=rdtsc()-t;
	memcpy_time+=t;
}

extern "C" {
void *gfmalloc(size_t n) {
	uint64 t = rdtsc();
	void *p = malloc(n);
	long align = (long)p & 7;
	if (align) fprintf(stderr,"malloc alignment = %ld mod 8\n",align);
	t=rdtsc()-t;
	malloc_time+=t;
	malloc_calls++;
	malloc_bytes+=n;
	return p;
}
void gffree(void *p) {
	uint64 t = rdtsc();
	free(p);
	t=rdtsc()-t;
	malloc_time+=t;
}};

Ruby GridFlow_memcpy_calls (Ruby rself) { return   LONG2NUM(memcpy_calls); }
Ruby GridFlow_memcpy_bytes (Ruby rself) { return gf_ull2num(memcpy_bytes); }
Ruby GridFlow_memcpy_time  (Ruby rself) { return gf_ull2num(memcpy_time); }
Ruby GridFlow_malloc_calls (Ruby rself) { return   LONG2NUM(malloc_calls); }
Ruby GridFlow_malloc_bytes (Ruby rself) { return gf_ull2num(malloc_bytes); }
Ruby GridFlow_malloc_time  (Ruby rself) { return gf_ull2num(malloc_time); }

Ruby GridFlow_profiler_reset2 (Ruby rself) {
	memcpy_calls = memcpy_bytes = memcpy_time = 0;
	malloc_calls = malloc_bytes = malloc_time = 0;
	return Qnil;
}

/* ---------------------------------------------------------------- */

void startup_number();
void startup_grid();
void startup_flow_objects();
void startup_flow_objects_for_image();
void startup_flow_objects_for_matrix();

Ruby cFormat;

#define SDEF(_class_,_name_,_argc_) \
	rb_define_singleton_method(c##_class_,#_name_,(RMethod)_class_##_s_##_name_,_argc_)
#define SDEF2(_name1_,_name2_,_argc_) \
	rb_define_singleton_method(mGridFlow,_name1_,(RMethod)_name2_,_argc_)

STARTUP_LIST(void)

// Ruby's entrypoint.
void Init_gridflow () {
#define FOO(_sym_,_name_) bsym._sym_ = ID2SYM(rb_intern(_name_));
BUILTIN_SYMBOLS(FOO)
#undef FOO
	signal(11,SIG_DFL); // paranoia
	mGridFlow = EVAL("module GridFlow; CObject = ::Object; "
		"class<<self; attr_reader :bridge_name; end; "
		"def post_string(s) STDERR.puts s end; "
		"self end");
	SDEF2("exec",GridFlow_exec,2);
	SDEF2("get_id",GridFlow_get_id,1);
	SDEF2("rdtsc",GridFlow_rdtsc,0);
	SDEF2("profiler_reset2",GridFlow_profiler_reset2,0);
	SDEF2("memcpy_calls",GridFlow_memcpy_calls,0);
	SDEF2("memcpy_bytes",GridFlow_memcpy_bytes,0);
	SDEF2("memcpy_time", GridFlow_memcpy_time,0);
	SDEF2("malloc_calls",GridFlow_malloc_calls,0);
	SDEF2("malloc_bytes",GridFlow_malloc_bytes,0);
	SDEF2("malloc_time", GridFlow_malloc_time,0);
	SDEF2("handle_braces!",GridFlow_handle_braces,1);
	SDEF2("fclass_install",GridFlow_fclass_install,2);

//#define FOO(A) fprintf(stderr,"sizeof("#A")=%d\n",sizeof(A));
//FOO(Dim) FOO(BitPacking) FOO(GridHandler) FOO(GridInlet) FOO(GridOutlet) FOO(GridObject)
//#undef FOO

	rb_ivar_set(mGridFlow, SI(@fobjects), rb_hash_new());
	rb_ivar_set(mGridFlow, SI(@fclasses), rb_hash_new());
	rb_ivar_set(mGridFlow, SI(@bsym), PTR2FIX(&bsym));
	rb_define_const(mGridFlow, "GF_VERSION", rb_str_new2(GF_VERSION));
	rb_define_const(mGridFlow, "GF_COMPILE_TIME", rb_str_new2(GF_COMPILE_TIME));

	cFObject = rb_define_class_under(mGridFlow, "FObject", rb_cObject);
	EVAL(
\ruby
	module GridFlow
		class FObject
		def send_out2(*) end
		def self.install2(*) end
		def self.add_creator(name)
			name=name.to_str.dup
			GridFlow.fclasses[name]=self
			GridFlow.add_creator_2 name end
		end
	end
\end ruby
);
	define_many_methods(cFObject,COUNT(FObject_methods),FObject_methods);
	SDEF(FObject, install, 3);
	SDEF(FObject, new, -1);
	ID gbi = SI(gf_bridge_init);
	if (rb_respond_to(rb_cData,gbi)) rb_funcall(rb_cData,gbi,0);
	Ruby cBitPacking =
		rb_define_class_under(mGridFlow, "BitPacking", rb_cObject);
	define_many_methods(cBitPacking,
		ciBitPacking.methodsn,
		ciBitPacking.methods);
	SDEF(BitPacking,new,-1);
	rb_define_method(rb_cString, "swap32!", (RMethod)String_swap32_f, 0);
	rb_define_method(rb_cString, "swap16!", (RMethod)String_swap16_f, 0);

	startup_number();
	startup_grid();
	startup_flow_objects();
	startup_flow_objects_for_image();
	startup_flow_objects_for_matrix();
	if (!EVAL("begin require 'gridflow/base/main.rb'; true\n"
		"rescue Exception => e; "
		"STDERR.puts \"can't load: #{$!}\n"
		"backtrace: #{$!.backtrace.join\"\n\"}\n"
		"$: = #{$:.inspect}\"\n; false end")) return;
	cFormat = EVAL("GridFlow::Format");
	STARTUP_LIST()
	EVAL("h=GridFlow.fclasses; h['#io:window'] = h['#io:quartz']||h['#io:x11']||h['#io:sdl']");
	EVAL("GridFlow.load_user_config");
	signal(11,SIG_DFL); // paranoia
}

void GFStack::push (FObject *o) {
	void *bp = &o; // really. just finding our position on the stack.
	if (n>=GF_STACK_MAX)
		RAISE("stack overflow (maximum %d FObject activations at once)", GF_STACK_MAX);
	uint64 t = rdtsc();
	if (n) s[n-1].time = t - s[n-1].time;
	s[n].o = o;
	s[n].bp = bp;
	s[n].time = t;
	n++;
}

void GFStack::pop () {
	uint64 t = rdtsc();
	if (!n) RAISE("stack underflow (WHAT?)");
	n--;
	if (s[n].o) s[n].o->total_time += t - s[n].time;
	if (n) s[n-1].time = t - s[n-1].time;
}

uint64 gf_timeofday () {
	timeval t;
	gettimeofday(&t,0);
	return t.tv_sec*1000000+t.tv_usec;
}

--- NEW FILE: grid.c ---
/*
	$Id: grid.c,v 1.1 2005/10/04 02:02:13 matju Exp $

	GridFlow
	Copyright (c) 2001,2002,2003,2004 by Mathieu Bouchard

	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License
	as published by the Free Software Foundation; either version 2
	of the License, or (at your option) any later version.

	See file ../COPYING for further informations on licensing terms.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/

#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
#include "grid.h.fcs"
#include <ctype.h>

/* copied from bridge/puredata.c (sorry: linkage issue) */
struct Pointer : CObject { void *p; Pointer(void *_p) : p(_p) {}};
Ruby Pointer_s_noo (void *ptr) {
	return Data_Wrap_Struct(EVAL("GridFlow::Pointer"), 0, 0, new Pointer(ptr));}
static void *Pointer_gut (Ruby rself) {DGS(Pointer); return self->p;}

//#define TRACE fprintf(stderr,"%s %s [%s:%d]\n",INFO(parent),__PRETTY_FUNCTION__,__FILE__,__LINE__);assert(this);
#define TRACE assert(this);

#define CHECK_TYPE(d) \
	if (NumberTypeE_type_of(d)!=this->nt) RAISE("%s(%s): " \
		"type mismatch during transmission (got %s expecting %s)", \
		INFO(parent), \
		__PRETTY_FUNCTION__, \
		number_type_table[NumberTypeE_type_of(d)].name, \
		number_type_table[this->nt].name);

#define CHECK_BUSY(s) \
	if (!dim) RAISE("%s: " #s " not busy",INFO(parent));

#define CHECK_ALIGN(d) \
	{int bytes = number_type_table[nt].size/8; \
	int align = ((long)(void*)d)%bytes; \
	if (align) {L;gfpost("%s(%s): Alignment Warning: %p is not %d-aligned: %d", \
		INFO(parent), __PRETTY_FUNCTION__, (void*)d,bytes,align);}}

#define CHECK_ALIGN2(d,nt) \
	{int bytes = number_type_table[nt].size/8; \
	int align = ((long)(void*)d)%bytes; \
	if (align) {L;gfpost("Alignment Warning: %p is not %d-aligned: %d", \
		(void*)d,bytes,align);}}

// **************** Grid ******************************************

#define FOO(S) static inline void NUM(Ruby x, S &y) {y=convert(x,(int32*)0);}
EACH_INT_TYPE(FOO)
#undef FOO

#define FOO(S) \
static inline void NUM(Ruby x, S &y) { \
	if (TYPE(x)==T_FLOAT) y = RFLOAT(x)->value; \
	else if (INTEGER_P(x)) y = convert(x,(S*)0); \
	else RAISE("expected Float (or at least Integer)");}
EACH_FLOAT_TYPE(FOO)
#undef FOO

void Grid::init_from_ruby_list(int n, Ruby *a, NumberTypeE nt) {
	Ruby delim = SYM(#);
	for (int i=0; i<n; i++) {
		if (a[i] == delim) {
			STACK_ARRAY(int32,v,i);
			if (i!=0 && TYPE(a[i-1])==T_SYMBOL) nt=NumberTypeE_find(a[--i]);
			for (int j=0; j<i; j++) v[j] = convert(a[j],(int32*)0);
			init(new Dim(i,v),nt);
			CHECK_ALIGN2(this->data,nt);
			if (a[i] != delim) i++;
			i++; a+=i; n-=i;
			goto fill;
		}
	}
	if (n!=0 && TYPE(a[0])==T_SYMBOL) {
		nt = NumberTypeE_find(a[0]);
		a++, n--;
	}
	init(new Dim(n),nt);
	CHECK_ALIGN2(this->data,nt);
	fill:
	int nn = dim->prod();
	n = min(n,nn);
#define FOO(type) { \
	Pt<type> p = (Pt<type>)*this; \
	if (n==0) CLEAR(p,nn); \
	else { \
		for (int i=0; i<n; i++) NUM(a[i],p[i]); \
		for (int i=n; i<nn; i+=n) COPY(p+i,p,min(n,nn-i)); }}
	TYPESWITCH(nt,FOO,)
#undef FOO
}

void Grid::init_from_ruby(Ruby x) {
	if (TYPE(x)==T_ARRAY) {
		init_from_ruby_list(rb_ary_len(x),rb_ary_ptr(x));
	} else if (INTEGER_P(x) || FLOAT_P(x)) {
		init(new Dim(),int32_e);
		CHECK_ALIGN2(this->data,nt);
		((Pt<int32>)*this)[0] = INT(x);
	} else {
		rb_funcall(
		EVAL("proc{|x| raise \"can't convert to grid: #{x.inspect}\"}"),
		SI(call),1,x);
	}
}

// **************** GridInlet *************************************

// must be set before the end of GRID_BEGIN phase, and so cannot be changed
// afterwards. This is to allow some optimisations. Anyway there is no good reason
// why this would be changed afterwards.
void GridInlet::set_factor(int factor) {
	if(!dim) RAISE("huh?");
	if(factor<=0) RAISE("%s: factor=%d should be >= 1",INFO(parent),factor);
	if (dim->prod() && dim->prod() % factor)
		RAISE("%s: set_factor: expecting divisor",INFO(parent));
	if (factor > 1) {
		buf=new Grid(new Dim(factor), nt);
		bufi=0;
	} else {
		buf=0;
	}
}

static Ruby GridInlet_begin_1(GridInlet *self) {
#define FOO(T) self->gh->flow(self,-1,Pt<T>()); break;
	TYPESWITCH(self->nt,FOO,)
#undef FOO
	return Qnil;
}

static Ruby GridInlet_begin_2(GridInlet *self) {
	self->dim = 0; // hack
	return (Ruby) 0;
}

bool GridInlet::supports_type(NumberTypeE nt) {
#define FOO(T) return !! gh->flow_##T;
	TYPESWITCH(nt,FOO,return false)
#undef FOO
}

Ruby GridInlet::begin(int argc, Ruby *argv) {TRACE;
	if (!argc) return PTR2FIX(this);
	GridOutlet *back_out = (GridOutlet *) Pointer_gut(argv[0]);
	nt = (NumberTypeE) INT(argv[1]);
	argc-=2, argv+=2;
	PROF(parent) {
	if (dim) {
		gfpost("%s: grid inlet conflict; aborting %s in favour of %s",
			INFO(parent), INFO(sender), INFO(back_out->parent));
		abort();
	}
	sender = back_out->parent;
	if ((int)nt<0 || (int)nt>=(int)number_type_table_end)
		RAISE("%s: inlet: unknown number type",INFO(parent));
	if (!supports_type(nt))
		RAISE("%s: number type %s not supported here",
			INFO(parent), number_type_table[nt].name);
	STACK_ARRAY(int32,v,argc);
	for (int i=0; i<argc; i++) v[i] = NUM2INT(argv[i]);
	P<Dim> dim = this->dim = new Dim(argc,v);
	dex=0;
	buf=0;
	int r = rb_ensure(
		(RMethod)GridInlet_begin_1,(Ruby)this,
		(RMethod)GridInlet_begin_2,(Ruby)this);
	if (!r) {abort(); goto hell;}
	this->dim = dim;
	back_out->callback(this);
	hell:;} // PROF
	return Qnil;
}

template <class T> void GridInlet::flow(int mode, int n, Pt<T> data) {TRACE;
	CHECK_BUSY(inlet);
	CHECK_TYPE(*data);
	CHECK_ALIGN(data);
	PROF(parent) {
	if (this->mode==0) {dex += n; return;} // ignore data
	if (n==0) return; // no data
	switch(mode) {
	case 4:{
		int d = dex + bufi;
		if (d+n > dim->prod()) {
			gfpost("grid input overflow: %d of %d from [%s] to [%s]",
				d+n, dim->prod(), INFO(sender), 0);
			n = dim->prod() - d;
			if (n<=0) return;
		}
		int bufn = factor();
		if (buf && bufi) {
			Pt<T> bufd = (Pt<T>)*buf;
			int k = min(n,bufn-bufi);
			COPY(bufd+bufi,data,k);
			bufi+=k; data+=k; n-=k;
			if (bufi==bufn) {
				int newdex = dex+bufn;
				if (this->mode==6) {
					Pt<T> data2 = ARRAY_NEW(T,bufn);
					COPY(data2,bufd,bufn);
					CHECK_ALIGN(data2);
					gh->flow(this,bufn,data2);
				} else {
					CHECK_ALIGN(bufd);
					gh->flow(this,bufn,bufd);
				}
				dex = newdex;
				bufi = 0;
			}
		}
		int m = (n/bufn)*bufn;
		if (m) {
			int newdex = dex + m;
			if (this->mode==6) {
				Pt<T> data2 = ARRAY_NEW(T,m);
				COPY(data2,data,m);
				CHECK_ALIGN(data2);
				gh->flow(this,m,data2);
			} else {
				gh->flow(this,m,data);
			}
			dex = newdex;
		}
		data += m;
		n -= m;
		if (buf && n>0) COPY((Pt<T>)*buf+bufi,data,n), bufi+=n;
	}break;
	case 6:{
		assert(!buf);
		int newdex = dex + n;
		gh->flow(this,n,data);
		if (this->mode==4) delete[] (T *)data;
		dex = newdex;
	}break;
	case 0: break; // ignore data
	default: RAISE("%s: unknown inlet mode",INFO(parent));
	}} // PROF
}

void GridInlet::end() {TRACE;
	assert(this);
	if (!dim) RAISE("%s: inlet not busy",INFO(parent));
	if (dim->prod() != dex) {
		gfpost("incomplete grid: %d of %d from [%s] to [%s]",
			dex, dim->prod(), INFO(sender), INFO(parent));
	}
	PROF(parent) {
#define FOO(T) gh->flow(this,-2,Pt<T>());
	TYPESWITCH(nt,FOO,)
#undef FOO
	} // PROF
	dim=0;
	buf=0;
	dex=0;
}

template <class T> void GridInlet::from_grid2(Grid *g, T foo) {TRACE;
	nt = g->nt;
	dim = g->dim;
	int n = g->dim->prod();
	gh->flow(this,-1,Pt<T>());
	if (n>0 && this->mode!=0) {
		Pt<T> data = (Pt<T>)*g;
		CHECK_ALIGN(data);
		int size = g->dim->prod();
		if (this->mode==6) {
			Pt<T> d = data;
			data = ARRAY_NEW(T,size);  // problem with int64,float64 here.
			COPY(data,d,size);
			CHECK_ALIGN(data);
			gh->flow(this,n,data);
		} else {
			int ntsz = number_type_table[nt].size;
			int m = GridOutlet::MAX_PACKET_SIZE/*/ntsz*//factor();
			if (!m) m++;
			m *= factor();
			while (n) {
				if (m>n) m=n;
				CHECK_ALIGN(data);
				gh->flow(this,m,data);
				data+=m; n-=m; dex+=m;
			}			
		}
	}
	gh->flow(this,-2,Pt<T>());
	//!@#$ add error handling.
	// rescue; abort(); end ???
	dim = 0;
	dex = 0;
}

void GridInlet::from_grid(Grid *g) {TRACE;
	if (!supports_type(g->nt))
		RAISE("%s: number type %s not supported here",
			INFO(parent), number_type_table[g->nt].name);
#define FOO(T) from_grid2(g,(T)0);
	TYPESWITCH(g->nt,FOO,)
#undef FOO
}

/* **************** GridOutlet ************************************ */

void GridOutlet::begin(int woutlet, P<Dim> dim, NumberTypeE nt) {TRACE;
	int n = dim->count();
	this->nt = nt;
	this->dim = dim;
	Ruby a[n+4];
	a[0] = INT2NUM(woutlet);
	a[1] = bsym._grid;
	a[2] = Pointer_s_noo(this);
	a[3] = INT2NUM(nt);
	for(int i=0; i<n; i++) a[4+i] = INT2NUM(dim->get(i));
	parent->send_out(COUNT(a),a);
	frozen=true;
	if (!dim->prod()) {end(); return;}
	int32 lcm_factor = 1;
	for (uint32 i=0; i<inlets.size(); i++) lcm_factor = lcm(lcm_factor,inlets[i]->factor());
	if (nt != buf->nt) {
		// biggest packet size divisible by lcm_factor
		int32 v = (MAX_PACKET_SIZE/lcm_factor)*lcm_factor;
		if (v==0) v=MAX_PACKET_SIZE; // factor too big. don't have a choice.
		buf=new Grid(new Dim(v),nt);
	}
}

// send modifies dex; send_direct doesn't
template <class T>
void GridOutlet::send_direct(int n, Pt<T> data) {TRACE;
	assert(data); assert(frozen);
	CHECK_BUSY(outlet); CHECK_TYPE(*data); CHECK_ALIGN(data);
	for (; n>0; ) {
		int pn = min(n,MAX_PACKET_SIZE);
		for (uint32 i=0; i<inlets.size(); i++) inlets[i]->flow(4,pn,data);
		data+=pn, n-=pn;
	}
}

void GridOutlet::flush() {TRACE;
	if (!bufi) return;
#define FOO(T) send_direct(bufi,(Pt<T>)*buf);
	TYPESWITCH(buf->nt,FOO,)
#undef FOO
	bufi = 0;
}

template <class T, class S>
static void convert_number_type(int n, Pt<T> out, Pt<S> in) {
	for (int i=0; i<n; i++) out[i]=(T)in[i];
}

//!@#$ buffering in outlet still is 8x faster...?
//!@#$ should use BitPacking for conversion...?
// send modifies dex; send_direct doesn't
template <class T>
void GridOutlet::send(int n, Pt<T> data) {TRACE;
	assert(data); assert(frozen);
	if (!n) return;
	CHECK_BUSY(outlet); CHECK_ALIGN(data);
	if (NumberTypeE_type_of(*data)!=nt) {
		int bs = MAX_PACKET_SIZE;
#define FOO(T) { \
	STACK_ARRAY(T,data2,bs); \
	for (;n>=bs;n-=bs,data+=bs) { \
		convert_number_type(bs,data2,data); send(bs,data2);} \
	convert_number_type(n,data2,data); \
	send(n,data2); }
		TYPESWITCH(nt,FOO,)
#undef FOO
	} else {
		dex += n;
		assert(dex <= dim->prod());
		if (n > MIN_PACKET_SIZE || bufi + n > MAX_PACKET_SIZE) flush();
		if (n > MIN_PACKET_SIZE) {
			send_direct(n,data);
		} else {
			COPY((Pt<T>)*buf+bufi,data,n);
			bufi += n;
		}
		if (dex==dim->prod()) end();
	}
}

template <class T>
void GridOutlet::give(int n, Pt<T> data) {TRACE;
	assert(data); CHECK_BUSY(outlet); assert(frozen);
	assert(dex+n <= dim->prod()); CHECK_ALIGN(data);
	if (NumberTypeE_type_of(*data)!=nt) {
		send(n,data);
		delete[] (T *)data;
		return;
	}
	if (inlets.size()==1 && inlets[0]->mode == 6) {
		// this is the copyless buffer passing
		flush();
		inlets[0]->flow(6,n,data);
		dex += n;
	} else {
		flush();
		send_direct(n,data);
		dex += n;
		delete[] (T *)data;
	}
	if (dex==dim->prod()) end();
}

void GridOutlet::callback(GridInlet *in) {TRACE;
	CHECK_BUSY(outlet); assert(!frozen);
	int mode = in->mode;
	assert(mode==6 || mode==4 || mode==0);
	inlets.push_back(in);
}

\class GridObject < FObject

//!@#$ does not handle types properly
//!@#$ most possibly a big hack
template <class T>
void GridObject_r_flow(GridInlet *in, int n, Pt<T> data) {
	GridObject *self = in->parent;
	uint32 i;
	for (i=0; i<self->in.size(); i++) if (in==self->in[i].p) break;
	if (i==self->in.size()) RAISE("inlet not found?");
	if (n==-1) {
		rb_funcall(self->rself,SI(send_in),2,INT2NUM(i),SYM(rgrid_begin));
	} else if (n>=0) {
		Ruby buf = rb_str_new((char *)((uint8 *)data),n*sizeof(T));
		rb_funcall(self->rself,SI(send_in),3,INT2NUM(i),SYM(rgrid_flow),buf);
	} else {
		rb_funcall(self->rself,SI(send_in),2,INT2NUM(i),SYM(rgrid_end));
	}
}

\def Symbol inlet_nt (int inln) {
	if (inln<0 || inln>=(int)in.size()) RAISE("bad inlet number");
	P<GridInlet> inl = in[inln];
	if (!inl) RAISE("no such inlet #%d",inln);
	if (!inl->dim) return Qnil;
	return number_type_table[inl->nt].sym;
}

\def Array inlet_dim (int inln) {
	if (inln<0 || inln>=(int)in.size()) RAISE("bad inlet number");
	P<GridInlet> inl = in[inln];
	if (!inl) RAISE("no such inlet #%d",inln);
	if (!inl->dim) return Qnil;
	int n=inl->dim->count();
	Ruby a = rb_ary_new2(n);
	for(int i=0; i<n; i++) rb_ary_push(a,INT2NUM(inl->dim->v[i]));
	return a;
}

\def void inlet_set_factor (int inln, int factor) {
	if (inln<0 || inln>=(int)in.size()) RAISE("bad inlet number");
	P<GridInlet> inl = in[inln];
	if (!inl) RAISE("no such inlet #%d",inln);
	if (!inl->dim) RAISE("inlet #%d not active",inln);
	inl->set_factor(factor);
}

\def void send_out_grid_begin (int outlet, Array buf, NumberTypeE nt=int32_e) {
	if (outlet<0) RAISE("bad outlet number");
	int n = rb_ary_len(buf);
	Ruby *p = rb_ary_ptr(buf);
	STACK_ARRAY(int32,v,n);
	for (int i=0; i<n; i++) v[i] = convert(p[i],(int32*)0);
	out = new GridOutlet(this,outlet,new Dim(n,v),nt); // valgrind says leak?
}

template <class T>
void send_out_grid_flow_2(P<GridOutlet> go, Ruby s, T bogus) {
	int n = rb_str_len(s) / sizeof(T);
	Pt<T> p = rb_str_pt(s,T);
	go->send(n,p);
}

\def void send_out_grid_flow (int outlet, String buf, NumberTypeE nt=int32_e) {
	if (outlet<0) RAISE("bad outlet number");
#define FOO(T) send_out_grid_flow_2(out,argv[1],(T)0);
	TYPESWITCH(nt,FOO,)
#undef FOO
}

// install_rgrid(Integer inlet, Boolean multi_type? = true)
static Ruby GridObject_s_install_rgrid(int argc, Ruby *argv, Ruby rself) {
	if (argc<1 || argc>2) RAISE("er...");
	IEVAL(rself,"@handlers||=[]");
	Ruby handlers = rb_ivar_get(rself,SI(@handlers));
	GridHandler *gh = new GridHandler;
	bool mt = argc>1 ? argv[1]==Qtrue : 0; /* multi_type? */
	if (mt) {
#define FOO(S) gh->flow_##S = GridObject_r_flow;
EACH_NUMBER_TYPE(FOO)
#undef FOO
	} else {
#define FOO(S) gh->flow_##S = 0;
EACH_NUMBER_TYPE(FOO)
#undef FOO
	}
	gh->flow_int32 = GridObject_r_flow;
	//IEVAL(rself,"self.class_eval { def _0_grid(*a) ___grid(0,*a) end }");
	rb_funcall(handlers,SI([]=),2,INT2NUM(INT(argv[0])),PTR2FIX(gh));
	return Qnil;
}

static Ruby GridObject_s_instance_methods(int argc, Ruby *argv, Ruby rself) {
	static const char *names[] = {"grid","list","float"};
	Ruby list = rb_class_instance_methods(argc,argv,rself);
	Ruby handlers = rb_ivar_get(rself,SI(@handlers));
	if (handlers==Qnil) return list;
	for (int i=0; i<rb_ary_len(handlers); i++) {
		Ruby ghp = rb_ary_ptr(handlers)[i];
		if (ghp==Qnil) continue;
		GridHandler *gh = FIX2PTR(GridHandler,ghp);
		char buf[256];
		for (int j=0; j<COUNT(names); j++) {
			sprintf(buf,"_%d_%s",i,names[j]);
			rb_ary_push(list,rb_str_new2(buf));
		}
	}
	return list;
}

// this does auto-conversion of list/float to grid
// this also (will) do grid inputs for ruby stuff.
\def Ruby method_missing (...) {
    {
	if (argc<1) RAISE("not enough arguments");
	if (!SYMBOL_P(argv[0])) RAISE("expected symbol");
	const char *name = rb_sym_name(argv[0]);
	char *endp;
	if (*name++!='_') goto hell;
	int i = strtol(name,&endp,10);
	if (name==endp) goto hell;
	if (*endp++!='_') goto hell;
	if (strcmp(endp,"grid")==0) {
		Ruby handlers = rb_ivar_get(rb_obj_class(rself),SI(@handlers));
		if (TYPE(handlers)!=T_ARRAY) {
			rb_p(handlers);
			RAISE("gridhandler-list missing (maybe forgot install_rgrid ?)"
			" while trying to receive on inlet %d",i);
		}
		if (i>=rb_ary_len(handlers)) RAISE("BORK");
		GridHandler *gh = FIX2PTR(GridHandler, rb_ary_ptr(handlers)[i]);
		if (in.size()<=(uint32)i) in.resize(i+1);
		if (!in[i]) in[i]=new GridInlet((GridObject *)this,gh);
		return in[i]->begin(argc-1,argv+1);
	}
	// we call the grid method recursively to ask it its GridInlet*
	// don't do this before checking the missing method is exactly that =)
	char foo[42];
	sprintf(foo,"_%d_grid",i);
	P<GridInlet> inl = FIX2PTR(GridInlet,rb_funcall(rself,rb_intern(foo),0));
	if (strcmp(endp,"list" )==0) return inl->from_ruby_list(argc-1,argv+1), Qnil;
	if (strcmp(endp,"float")==0) return inl->from_ruby     (argc-1,argv+1), Qnil;
    }
    hell: return rb_call_super(argc,argv);
}

\classinfo {
	IEVAL(rself,"install 'GridObject',0,0");
	// define in Ruby-metaclass
	rb_define_singleton_method(rself,"instance_methods",(RMethod)GridObject_s_instance_methods,-1);
	rb_define_singleton_method(rself,"install_rgrid",(RMethod)GridObject_s_install_rgrid,-1);
	rb_enable_super(rb_singleton_class(rself),"instance_methods");
}
\end class GridObject

Ruby cGridObject;

void startup_grid () {
	\startall
	cGridObject = rb_const_get(mGridFlow,SI(GridObject));
}

// never call this. this is a hack to make some things work.
// i'm trying to circumvent either a bug in the compiler or i don't have a clue. :-(
void make_gimmick () {
	GridOutlet foo(0,0,0);
#define FOO(S) foo.give(0,Pt<S>());
EACH_NUMBER_TYPE(FOO)
#undef FOO
}






More information about the Pd-cvs mailing list