Commits

Michael Granger committed b522ce3

Checkpoint commit; upgraded to Ruby 1.9.2 and RSpec 2.0

  • Participants
  • Parent commits 263e486

Comments (0)

Files changed (35)

 ChangeLog
 \.DS_Store
 ^hostid.rsa$
+mkmf\.log
 
 ### Classes
 
+* Server
 * Session (Observable)
 * Node (Observable)
 	- ObjectNode
 ### Observer Mixins
 
 * PingObserver
-* ConnectionObserver
 * SessionObserver
 
 * NodeObserver
 	libdir = basedir + "lib"
 	extdir = libdir + Config::CONFIG['sitearch']
 
+	$LOAD_PATH.unshift( basedir.to_s ) unless $LOAD_PATH.include?( basedir.to_s )
 	$LOAD_PATH.unshift( libdir.to_s ) unless $LOAD_PATH.include?( libdir.to_s )
 	$LOAD_PATH.unshift( extdir.to_s ) unless $LOAD_PATH.include?( extdir.to_s )
 }
 	PKG_VERSION = VERSION_FILE.read[ /VERSION\s*=\s*['"](\d+\.\d+\.\d+)['"]/, 1 ]
 end
 
-PKG_VERSION = '0.0.0' unless defined?( PKG_VERSION )
+PKG_VERSION = '0.0.0' unless defined?( PKG_VERSION ) && !PKG_VERSION.nil?
 
 PKG_FILE_NAME = "#{PKG_NAME.downcase}-#{PKG_VERSION}"
 GEM_FILE_NAME = "#{PKG_FILE_NAME}.gem"
 
 # Set the build ID if the mercurial executable is available
 if hg = which( 'hg' )
-	id = IO.read('|-') or exec hg.to_s, 'id', '-n'
+	id = `#{hg} id -n`.chomp
 	PKG_BUILD = "pre%03d" % [(id.chomp[ /^[[:xdigit:]]+/ ] || '1')]
 else
 	PKG_BUILD = 'pre000'
 PROJECT_SCPPUBURL = "#{PROJECT_HOST}:#{PROJECT_PUBDIR}"
 PROJECT_SCPDOCURL = "#{PROJECT_HOST}:#{PROJECT_DOCDIR}"
 
+GEM_PUBHOST = 'rubygems.org'
+
 # Gem dependencies: gemname => version
 DEPENDENCIES = {
 }
 	gem.files             = RELEASE_FILES
 	gem.test_files        = SPEC_FILES
 
+	# signing key and certificate chain
+	gem.signing_key       = '/Volumes/Keys/ged-private_gem_key.pem'
+	gem.cert_chain        = [File.expand_path('~/.gem/ged-public_gem_cert.pem')]
+
 	DEPENDENCIES.each do |name, version|
 		version = '>= 0' if version.length.zero?
 		gem.add_runtime_dependency( name, version )

File Rakefile.local

     task :text  => [ :compile ]
 end
 
+desc "Turn on $DEBUGging"
+task :debug do
+	$DEBUG = true
+end
+
 desc "Clobber the existing source for the node classes and replace them with blank templated source."
 task :remake_node_classes do
 	log "This will REMOVE all the child node classes and replace it with blank templated source."
 	ext.source_pattern = "*.{c,h}"
 	ext.cross_compile = true
 	ext.cross_platform = %w[i386-mswin32 i386-mingw32]
+	ext.config_options += ['-d']
 end
 
 
-# Make both the default task and the spec task depend on building the extension
 namespace :spec do
 
 	desc "Run specs under gdb"
 	task :gdb => [ :compile ] do |task|
 		require 'tempfile'
+		require 'shellwords'
 
 	    cmd_parts = ['run']
 	    cmd_parts << '-Ilib:ext'
-	    cmd_parts << '/usr/bin/spec'
-	    cmd_parts += SPEC_FILES.collect { |fn| %["#{fn}"] }
-	    cmd_parts += COMMON_SPEC_OPTS + ['-f', 's', '-c']
+	    cmd_parts << which( 'rspec' )
+	    cmd_parts += SPEC_FILES.collect {|fn| fn.shellescape }
 
 		script = Tempfile.new( 'spec-gdbscript' )
 		script.puts( cmd_parts.join(' ') )
 require 'trollop'
 
 require 'verse'
-require 'verse/server'
+require 'verse/simpleserver'
 
-# Reference Verse Server frontend.
-# $Id$
-# 
-# This is a port of the reference server that comes with the Verse source. Please see 
-# the README file for details.
-# 
-# @author Michael Granger <ged@FaerieMUD.org>
-# 
-# Copyright (c) 2010 The FaerieMUD Consortium
-# 
-# All rights reserved.
-# 
-# Redistribution and use in source and binary forms, with or without modification, are
-# permitted provided that the following conditions are met:
-# 
-#  * Redistributions of source code must retain the above copyright notice, this
-#    list of conditions and the following disclaimer.
-#  
-#  * Redistributions in binary form must reproduce the above copyright notice, this
-#    list of conditions and the following disclaimer in the documentation and/or
-#    other materials provided with the distribution.
-#  
-#  * Neither the name of the authors, nor the names of its contributors may be used to
-#    endorse or promote products derived from this software without specific prior
-#    written permission.
-# 
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
-# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
-# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
-# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
-# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
-# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
-# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-# 
-# 
-#
+# A simple run-wrapper around the reference Verse::Server implementation.
 
 opts = Trollop.options do
 	progname = File.basename( $0 )
 	banner "#{progname} [OPTIONS]"
 	opt :port, "The port to listen on", :default => Verse::DEFAULT_PORT
+	opt :loglevel, "The logging verbosity level (debug, info, warn, fatal)",
+		:default => 'info', :type => :string
 end
 
-Verse::Server.new( opts ).run
+Verse::SimpleServer.instance.run( opts )
 
 

File examples/deadsimple_client.rb

 		puts "Connecting..."
 		@session.connect( 'user', 'pass' )
 		@running = true
-		Verse::Session.update while @running
+		Verse.update while @running
 		puts "Disconnected."
 	end
 

File examples/deadsimple_server.rb

 class MyServer < Verse::Server
 	require 'digest/sha1'
 
-	include Verse::ConnectionObserver
-
 	USERS = {
 		'bargle' => 'a76282512cb523ec1f82c79b4f51e34cdd5f11ea'
 	}

File examples/hello.rb

 	include Verse::SessionObserver,
 	        Verse::NodeObserver
 
-	def on_create_node( session, node )
-		node.add_observer( self )
+	def initialize( session )
+		self.observe( session )
+		@session = session
 	end
 
-	def on_set_name( node, name )
-		$stdout.puts "Node created: %p (%s)" % [ node, name ]
+	def on_connect_accept( avatar, addr, hostid )
+		$stderr.puts "Connection accepted; my avatar is: %p" % [ avatar ]
+		@session.subscribe_to_node_index( Verse::ObjectNode )
+	end
+
+	def on_node_created( node )
+		self.observe( node )
+	end
+
+	def on_node_destroy( node )
+		self.stop_observing( node )
+	end
+
+	def on_node_name_set( node, name )
+		$stdout.puts "Node %p is now named %p" % [ node, name ]
 	end
 
 end
 		ARGV.shift
 	end
 
-	session = Verse::Session.new( host, "spoo", "fleem" )
+	Verse.logger.level = Logger::DEBUG
+	Verse.logger.formatter = Verse::ColorLogFormatter.new( Verse.logger )
 
-	lister = NodeLister.new
-	session.add_observer( lister )
+	session = Verse::Session.new( host )
+	lister = NodeLister.new( session )
+	$stderr.puts "Connecting to %p" % [ host ]
 
-	Verse::Session.update until session.terminated?
+	session.connect( "spoo", "fleem" )
+
+	Verse.update( 2 ) while session.connected?
 end
 

File ext/audionode.c

 	rbverse_cVerseAudioNode = rb_define_class_under( rbverse_mVerse, "AudioNode", rbverse_cVerseNode );
 
     /* Constants */
-	rb_define_const( rbverse_cVerseAudioNode, "TYPE_NUMBER", INT2FIX(V_NT_AUDIO) );
+	rb_define_const( rbverse_cVerseAudioNode, "TYPE_NUMBER", rb_uint2inum(V_NT_AUDIO) );
 
 	/* Initializer */
 	rb_define_method( rbverse_cVerseAudioNode, "initialize", rbverse_verse_audionode_initialize, 0 );

File ext/bitmapnode.c

 	rbverse_cVerseBitmapNode = rb_define_class_under( rbverse_mVerse, "BitmapNode", rbverse_cVerseNode );
 
     /* Constants */
-	rb_define_const( rbverse_cVerseBitmapNode, "TYPE_NUMBER", INT2FIX(V_NT_BITMAP) );
+	rb_define_const( rbverse_cVerseBitmapNode, "TYPE_NUMBER", rb_uint2inum(V_NT_BITMAP) );
 
 	/* Initializer */
 	rb_define_method( rbverse_cVerseBitmapNode, "initialize", rbverse_verse_bitmapnode_initialize, 0 );

File ext/curvenode.c

 	rbverse_cVerseCurveNode = rb_define_class_under( rbverse_mVerse, "CurveNode", rbverse_cVerseNode );
 
     /* Constants */
-	rb_define_const( rbverse_cVerseCurveNode, "TYPE_NUMBER", INT2FIX(V_NT_CURVE) );
+	rb_define_const( rbverse_cVerseCurveNode, "TYPE_NUMBER", rb_uint2inum(V_NT_CURVE) );
 
 	/* Initializer */
 	rb_define_method( rbverse_cVerseCurveNode, "initialize", rbverse_verse_curvenode_initialize, 0 );

File ext/extconf.rb

 require 'mkmf'
 require 'fileutils'
 
-$CFLAGS << ' -Wall'
-$CFLAGS << ' -ggdb' << ' -DDEBUG'
+if $DEBUG
+	$CFLAGS << ' -Wall'
+	$CFLAGS << ' -ggdb' << ' -DDEBUG'
+end
 
 def fail( *messages )
 	$stderr.puts( *messages )

File ext/geometrynode.c

 	rbverse_cVerseGeometryNode = rb_define_class_under( rbverse_mVerse, "GeometryNode", rbverse_cVerseNode );
 
     /* Constants */
-	rb_define_const( rbverse_cVerseGeometryNode, "TYPE_NUMBER", INT2FIX(V_NT_GEOMETRY) );
+	rb_define_const( rbverse_cVerseGeometryNode, "TYPE_NUMBER", rb_uint2inum(V_NT_GEOMETRY) );
 
 	/* Initializer */
 	rb_define_method( rbverse_cVerseGeometryNode, "initialize", rbverse_verse_geometrynode_initialize, 0 );

File ext/materialnode.c

 	rbverse_cVerseMaterialNode = rb_define_class_under( rbverse_mVerse, "MaterialNode", rbverse_cVerseNode );
 
     /* Constants */
-	rb_define_const( rbverse_cVerseMaterialNode, "TYPE_NUMBER", INT2FIX(V_NT_MATERIAL) );
+	rb_define_const( rbverse_cVerseMaterialNode, "TYPE_NUMBER", rb_uint2inum(V_NT_MATERIAL) );
 
 	/* Initializer */
 	rb_define_method( rbverse_cVerseMaterialNode, "initialize", rbverse_verse_materialnode_initialize, 0 );
  */
 static VALUE
 rbverse_verse_node_s_allocate( VALUE klass ) {
-	if ( klass == rbverse_cVerseNode )
-		rb_raise( rb_eTypeError, "can't instantiate %s directly", rb_class2name(klass) );
 	return Data_Wrap_Struct( klass, rbverse_node_gc_mark, rbverse_node_gc_free, 0 );
 }
 
  */
 static VALUE
 rbverse_verse_node_initialize( VALUE self ) {
+	if ( rbverse_cVerseNode == CLASS_OF(self) )
+		rb_raise( rb_eTypeError, "can't instantiate %s directly", rb_obj_classname(self) );
+
 	if ( !rbverse_check_node(self) ) {
 		struct rbverse_node *node;
-
 		DATA_PTR( self ) = node = rbverse_node_alloc();
-
 		rb_call_super( 0, NULL );
 	} else {
 		rb_raise( rb_eRuntimeError,
 rbverse_verse_node_id_eq( VALUE self, VALUE newid ) {
 	struct rbverse_node *node = rbverse_get_node( self );
 
-	if ( node->id != ~0 )
+	if ( (signed int)node->id != ~0 )
 		rb_raise( rbverse_eVerseNodeError, "node's ID is already set" );
 
 	node->id = NUM2UINT( newid );
  * Iterator body for node_name_set observers.
  */
 static VALUE
-rbverse_cb_node_name_set_i( VALUE observer, VALUE node, int argc, VALUE *argv ) {
-	const VALUE name = argv[0];
-	VALUE cb_args = rb_ary_new2( 2 );
-
+rbverse_node_cb_name_set_i( VALUE observer, VALUE cb_args, int argc, VALUE *argv ) {
 	if ( !rb_obj_is_kind_of(observer, rbverse_mVerseNodeObserver) )
 		return Qnil;
 
-	RARRAY_PTR( cb_args )[0] = node;
-	RARRAY_PTR( cb_args )[1] = name;
-
 	rbverse_log( "debug", "Node_name_set callback: notifying observer: %s.",
 	             RSTRING_PTR(rb_inspect( observer )) );
 	return rb_funcall2( observer, rb_intern("on_node_name_set"), RARRAY_LEN(cb_args),
  * Call the node_name_set handler after aqcuiring the GVL.
  */
 static void *
-rbverse_cb_node_name_set_body( void *ptr ) {
+rbverse_node_cb_name_set_body( void *ptr ) {
 	struct rbverse_node_name_set_event *event = (struct rbverse_node_name_set_event *)ptr;
 	const VALUE node = rbverse_lookup_verse_node( event->node_id );
 	VALUE name = rb_str_new2( event->name );
-	VALUE observers;
+	VALUE observers, cb_args;
 
 	if ( RTEST(node) ) {
-		rbverse_log( "info", "Got node name '%s' for %p.", event->name,
-		             RSTRING_PTR(rb_inspect(node)) );
+		cb_args = rb_ary_new3( 2, node, name );
+		rbverse_log_with_context( node, "info", "Got node name '%s'.", event->name );
 
 		observers = rb_funcall( node, rb_intern("observers"), 0 );
-		rb_block_call( observers, rb_intern("each"), 1, &name, rbverse_cb_node_name_set_i, node );
+		rb_block_call( observers, rb_intern("each"), 0, 0, rbverse_node_cb_name_set_i, cb_args );
 
 	} else {
 		rbverse_log( "info", "Got node name for a node we haven't loaded (%d)", event->node_id );
  * Callback for the 'node_name_set' command
  */
 static void
-rbverse_cb_node_name_set( void *unused, VNodeID node_id, const char *name ) {
+rbverse_node_cb_name_set( void *unused, VNodeID node_id, const char *name ) {
 	struct rbverse_node_name_set_event event;
 
 	event.node_id = node_id;
 	DEBUGMSG( " Acquiring GVL for 'node_name_set' event.\n" );
 	fflush( stdout );
 
-	rb_thread_call_with_gvl( rbverse_cb_node_name_set_body, (void *)&event );
+	rb_thread_call_with_gvl( rbverse_node_cb_name_set_body, (void *)&event );
 }
 
 
 // static void
-// rbverse_cb_node_tag_group_create( void *unused ) {}
+// rbverse_node_cb_tag_group_create( void *unused ) {}
 // 
 // static void
-// rbverse_cb_node_tag_group_destroy( void *unused ) {}
+// rbverse_node_cb_tag_group_destroy( void *unused ) {}
 // 
 // static void
-// rbverse_cb_node_tag_group_subscribe( void *unused ) {}
+// rbverse_node_cb_tag_group_subscribe( void *unused ) {}
 // 
 // static void
-// rbverse_cb_node_tag_group_unsubscribe( void *unused ) {}
+// rbverse_node_cb_tag_group_unsubscribe( void *unused ) {}
 // 
 // static void
-// rbverse_cb_node_tag_create( void *unused ) {}
+// rbverse_node_cb_tag_create( void *unused ) {}
 // 
 // static void
-// rbverse_cb_node_tag_destroy( void *unused ) {}
+// rbverse_node_cb_tag_destroy( void *unused ) {}
 
 
 
 	rb_include_module( rbverse_cVerseNode, rbverse_mVerseLoggable );
 	rb_include_module( rbverse_cVerseNode, rbverse_mVerseObservable );
 
-	rb_define_const( rbverse_cVerseNode, "TYPE_NUMBER", INT2FIX(V_NT_SYSTEM) );
+	rb_define_const( rbverse_cVerseNode, "TYPE_NUMBER", rb_uint2inum(V_NT_SYSTEM) );
 
 	/* Class methods */
 	rb_define_alloc_func( rbverse_cVerseNode, rbverse_verse_node_s_allocate );
 	rb_define_method( rbverse_cVerseNode, "destroyed?", rbverse_verse_node_destroyed_p, 0 );
 
 	rb_define_method( rbverse_cVerseNode, "session", rbverse_verse_node_session, 0 );
+	rb_define_method( rbverse_cVerseNode, "session=", rbverse_verse_node_session_eq, 1 );
 
 	rb_define_method( rbverse_cVerseNode, "id", rbverse_verse_node_id, 0 );
 	rb_define_method( rbverse_cVerseNode, "id=", rbverse_verse_node_id_eq, 1 );
 
-	/* Protected instance methods */
-	rb_define_protected_method( rbverse_cVerseNode, "session=", rbverse_verse_node_session_eq, 1 );
-
 
 	/* Init child classes */
 	rbverse_init_verse_objectnode();
 	// DEBUGMSG( "Free function for CurveNode (%d) is: %p\n", V_NT_CURVE, node_free_funcs[V_NT_CURVE] );
 	// DEBUGMSG( "Free function for AudioNode (%d) is: %p\n", V_NT_AUDIO, node_free_funcs[V_NT_AUDIO] );
 
-	verse_callback_set( verse_send_node_name_set, rbverse_cb_node_name_set, NULL );
-	// verse_callback_set( verse_send_tag_group_create, rbverse_cb_node_tag_group_create, NULL );
-	// verse_callback_set( verse_send_tag_group_destroy, rbverse_cb_node_tag_group_destroy, NULL );
-	// verse_callback_set( verse_send_tag_group_subscribe, rbverse_cb_node_tag_group_subscribe, NULL );
-	// verse_callback_set( verse_send_tag_group_unsubscribe, rbverse_cb_node_tag_group_unsubscribe, NULL );
-	// verse_callback_set( verse_send_tag_create, rbverse_cb_node_tag_create, NULL );
-	// verse_callback_set( verse_send_tag_destroy, rbverse_cb_node_tag_destroy, NULL );
+	verse_callback_set( verse_send_node_name_set, rbverse_node_cb_name_set, NULL );
+	// verse_callback_set( verse_send_tag_group_create, rbverse_node_cb_tag_group_create, NULL );
+	// verse_callback_set( verse_send_tag_group_destroy, rbverse_node_cb_tag_group_destroy, NULL );
+	// verse_callback_set( verse_send_tag_group_subscribe, rbverse_node_cb_tag_group_subscribe, NULL );
+	// verse_callback_set( verse_send_tag_group_unsubscribe, rbverse_node_cb_tag_group_unsubscribe, NULL );
+	// verse_callback_set( verse_send_tag_create, rbverse_node_cb_tag_create, NULL );
+	// verse_callback_set( verse_send_tag_destroy, rbverse_node_cb_tag_destroy, NULL );
 }
 

File ext/nodeclass.template

 	rbverse_cVerse<%= nodetype.capitalize %>Node = rb_define_class_under( rbverse_mVerse, "<%= nodetype.capitalize %>Node", rbverse_cVerseNode );
 
     /* Constants */
-	rb_define_const( rbverse_cVerse<%= nodetype.capitalize %>Node, "TYPE_NUMBER", INT2FIX(V_NT_<%= nodetype.upcase %>) );
+	rb_define_const( rbverse_cVerse<%= nodetype.capitalize %>Node, "TYPE_NUMBER", rb_uint2inum(V_NT_<%= nodetype.upcase %>) );
 
 	/* Initializer */
 	rb_define_method( rbverse_cVerse<%= nodetype.capitalize %>Node, "initialize", rbverse_verse_<%= nodetype %>node_initialize, 0 );

File ext/objectnode.c

 	rbverse_cVerseObjectNode = rb_define_class_under( rbverse_mVerse, "ObjectNode", rbverse_cVerseNode );
 
     /* Constants */
-	rb_define_const( rbverse_cVerseObjectNode, "TYPE_NUMBER", INT2FIX(V_NT_OBJECT) );
+	rb_define_const( rbverse_cVerseObjectNode, "TYPE_NUMBER", rb_uint2inum(V_NT_OBJECT) );
 
 	/* Initializer */
 	rb_define_method( rbverse_cVerseObjectNode, "initialize", rbverse_verse_objectnode_initialize, 0 );

File ext/server.c

+/* 
+ * Verse::Server -- Verse server class
+ * $Id$
+ * 
+ * @author Michael Granger <ged@FaerieMUD.org>
+ * 
+ * Copyright (c) 2010 The FaerieMUD Consortium
+ * 
+ * All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without modification, are
+ * permitted provided that the following conditions are met:
+ * 
+ *  * Redistributions of source code must retain the above copyright notice, this
+ *    list of conditions and the following disclaimer.
+ *  
+ *  * Redistributions in binary form must reproduce the above copyright notice, this
+ *    list of conditions and the following disclaimer in the documentation and/or
+ *    other materials provided with the distribution.
+ *  
+ *  * Neither the name of the authors, nor the names of its contributors may be used to
+ *    endorse or promote products derived from this software without specific prior
+ *    written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * 
+ * 
+ */
+
+#include "verse_ext.h"
+
+VALUE rbverse_cVerseServer;
+static VALUE rbverse_verse_running_server;
+
+static void rbverse_server_cb_connect( void *, const char *, const char *, const char *, const uint8 * );
+static void rbverse_server_cb_index_subscribe( void *, uint32 );
+
+
+/* --------------------------------------------------------------
+ * Class methods
+ * -------------------------------------------------------------- */
+
+/*
+ * call-seq:
+ *    Verse::Server.running_instance   -> server
+ *
+ * Return the instance of Verse::Server that is currently running.
+ * 
+ * @return [Verse::Server, nil]  the currently-running server, if any.
+ * 
+ */
+static VALUE
+rbverse_verse_server_s_running_instance( VALUE class ) {
+	return rbverse_verse_running_server;
+}
+
+
+/* --------------------------------------------------------------
+ * Instance methods
+ * -------------------------------------------------------------- */
+
+/*
+ * call-seq:
+ *    Verse::Server#run
+ *
+ * Set up the server instance to handle connections and index subscriptions.
+ *
+ */
+static VALUE
+rbverse_verse_server_run( VALUE self ) {
+	if ( RTEST(rbverse_verse_running_server) )
+		rb_raise( rbverse_eVerseServerError, "another server is already running" );
+
+	rbverse_verse_running_server = self;
+
+	rbverse_log_with_context( self, "info", "Starting up." );
+	verse_callback_set( verse_send_connect, rbverse_server_cb_connect, NULL );
+	verse_callback_set( verse_send_node_index_subscribe, rbverse_server_cb_index_subscribe,
+	                    NULL );
+
+	return Qtrue;
+}
+
+
+/*
+ * call-seq:
+ *    Verse::Server#running?		-> true or false
+ *
+ * @return [boolean]  true if the receiving server is the running one.
+ *
+ */
+static VALUE
+rbverse_verse_server_running_p( VALUE self ) {
+	return ( rbverse_verse_running_server == self ? Qtrue : Qfalse );
+}
+
+
+/*
+ * call-seq:
+ *    Verse::Server#shutdown
+ *
+ * Tear down the server instance and stop handling connections and index subscriptions.
+ *
+ */
+static VALUE
+rbverse_verse_server_shutdown( VALUE self ) {
+	if ( rbverse_verse_running_server != self )
+		rb_raise( rbverse_eVerseServerError, "server isn't running" );
+
+	rbverse_log_with_context( self, "info", "Shutting down." );
+	verse_callback_set( verse_send_connect, NULL, NULL );
+	verse_callback_set( verse_send_node_index_subscribe, NULL, NULL );
+
+	rbverse_verse_running_server = Qnil;
+
+	return Qtrue;
+}
+
+
+/*
+ * call-seq:
+ *    server.accept_connection( avatar, address, host_id )
+ *
+ * Indicate acceptance of a client's connection request from +address+, assigning it the 
+ * given +avatar+ and +host_id+.
+ * 
+ * @param [Verse::ObjectNode] avatar   the node that will serve as the client's representation 
+ *                                     in the host's world.
+ * @param [String] address
+ * @param [String] host_id             the server's host ID, such as that generated by
+ *                                     Verse.create_host_id.
+ * 
+ * @return [Verse::Session]  the session that contains the new connection
+ */
+static VALUE
+rbverse_verse_accept_connection( VALUE self, VALUE avatar, VALUE address, VALUE host_id ) {
+	uint8 *id = rbverse_str2host_id( host_id );
+	const char *addr = NULL;
+	VNodeID avatar_node_id;
+	VSession session_id;
+	VALUE session = Qnil;
+
+	if ( rbverse_verse_running_server != self )
+		rb_raise( rbverse_eVerseServerError, "server isn't running" );
+
+	SafeStringValue( address );
+	addr = RSTRING_PTR( address );
+
+	if ( !IsObjectNode(avatar) )
+		rb_raise( rb_eTypeError, "can't convert %s into Verse::ObjectNode", rb_obj_classname(avatar) );
+	avatar_node_id = (VNodeID)FIX2ULONG( rb_funcall(avatar, rb_intern("id"), 0) );
+
+	rbverse_log( "debug", "Accepting connection from %s with avatar %lu", addr, avatar_node_id );
+	session_id = verse_send_connect_accept( avatar_node_id, addr, id );
+	session = rbverse_verse_session_from_vsession( session_id, address );
+
+	return session;
+}
+
+
+
+/* --------------------------------------------------------------
+ * Callbacks
+ * -------------------------------------------------------------- */
+
+/*
+ * Call the connect handler after acquiring the GVL.
+ */
+static void *
+rbverse_server_cb_connect_body( void *ptr ) {
+	const char **args = (const char **)ptr;
+	const VALUE cb_args = rb_ary_new2( 4 );
+
+	if ( RTEST(rbverse_verse_running_server) ) {
+		rb_ary_store( cb_args, 0, rb_str_new2(args[0]) );
+		rb_ary_store( cb_args, 1, rb_str_new2(args[1]) );
+		rb_ary_store( cb_args, 2, rb_str_new2(args[2]) );
+		rb_ary_store( cb_args, 3, rbverse_host_id2str((const uint8 *)args[3]) );
+
+		rb_funcall2( rbverse_verse_running_server, rb_intern("on_connect"),
+		             RARRAY_LEN(cb_args), RARRAY_PTR(cb_args) );
+	}
+
+	else {
+		rbverse_log( "warn", "on_connect event called with no server instance to handle it!" );
+	}
+
+	return NULL;
+}
+
+/*
+ * Callback for the 'connect' command.
+ */
+static void
+rbverse_server_cb_connect( void *unused, const char *name, const char *pass, const char *address,
+                    const uint8 *expected_host_id )
+{
+	const char *(args[4]) = { name, pass, address, (const char *)expected_host_id };
+	DEBUGMSG( "*** Acquiring GVL for 'connect' event. ***" );
+	rb_thread_call_with_gvl( rbverse_server_cb_connect_body, args );
+}
+
+
+/*
+ * Call the 'node_index_subscribe' handler after acquiring the GVL.
+ */
+static void *
+rbverse_server_cb_index_subscribe_body( void *ptr ) {
+	const uint32 mask = *((uint32 *)ptr);
+	const VALUE session = rbverse_get_current_session();
+	const VALUE cb_args = rb_ary_new3( 1, session );
+	VALUE node_class = Qnil;
+	VNodeType node_type;
+
+	if ( RTEST(rbverse_verse_running_server) ) {
+		rbverse_log( "debug", "Building the list of subscribed classes from mask: %u.", mask );
+		for ( node_type = V_NT_OBJECT; node_type < V_NT_NUM_TYPES; node_type++ ) {
+			if ( mask & (1 << node_type) ) {
+				node_class = rbverse_node_class_from_node_type( node_type );
+				rbverse_log( "debug", "  adding %s", rb_class2name(node_class) );
+				rb_ary_push( cb_args, node_class );
+			}
+		}
+
+		rbverse_log_with_context( rbverse_verse_running_server, "debug",
+		                          "Calling on_node_index_subscribe with %d node classes.",
+		                          RARRAY_LEN(cb_args) );
+		rb_funcall( rbverse_verse_running_server, rb_intern("on_node_index_subscribe"),
+		            RARRAY_LEN(cb_args), RARRAY_PTR(cb_args) );
+	}
+
+	else {
+		rbverse_log( "warn", "on_node_index_subscribe event called with no server instance to handle it!" );
+	}
+
+	return NULL;
+}
+
+
+/*
+ * Callback for the 'node_index_subscribe' command.
+ */
+static void
+rbverse_server_cb_index_subscribe( void *unused, uint32 mask ) {
+	rb_thread_call_with_gvl( rbverse_server_cb_index_subscribe_body, (void *)&mask );
+}
+
+
+
+/*
+ * Verse::Server class
+ */
+void
+rbverse_init_verse_server( void ) {
+	rbverse_log( "debug", "Initializing Verse::Server" );
+	rbverse_verse_running_server = Qnil;
+
+#ifdef FOR_RDOC
+	rbverse_mVerse = rb_define_module( "Verse" );
+#endif
+
+	rbverse_cVerseServer = rb_define_class_under( rbverse_mVerse, "Server", rb_cObject );
+
+	/* Class methods */
+	rb_define_singleton_method( rbverse_cVerseServer, "running_instance",
+	                            rbverse_verse_server_s_running_instance, 0 );
+
+	/* Public instance methods */
+	rb_define_method( rbverse_cVerseServer, "run", rbverse_verse_server_run, 0 );
+	rb_define_method( rbverse_cVerseServer, "running?", rbverse_verse_server_running_p, 0 );
+	rb_define_method( rbverse_cVerseServer, "shutdown", rbverse_verse_server_shutdown, 0 );
+
+	/* Protected instance methods */
+	rb_define_protected_method( rbverse_cVerseServer, "accept_connection",
+	                            rbverse_verse_accept_connection, 3 );
+
+	rb_require( "verse/server.rb" );
+}
+

File ext/session.c

 VALUE rbverse_mVerseSessionObserver;
 
 static VALUE rbverse_session_mutex;
-static st_table *session_table;
+st_table *session_table;
 
 /* Structs for passing callback data back into Ruby */
 struct rbverse_node_create_event {
-	VNodeID node_id;
-	VNodeType type;
-	VNodeOwner owner;
+	VNodeID    node_id;
+	VNodeType  node_type;
+	VNodeOwner node_owner;
 };
 
 struct rbverse_connect_accept_event {
-	VNodeID		    avatar;
-	const char	    *address;
-	uint8		    *hostid;
+	VNodeID    avatar;
+	const char *address;
+	uint8      *hostid;
 };
 
 
 }
 
 
-/* Body of rbverse_verse_callback_update after GVL is given up. */
-static VALUE
-rbverse_verse_session_s_update_body( void *ptr ) {
-	uint32 *microseconds = (uint32 *)ptr;
-	DEBUGMSG( "  calling verse_callback_update( %d ).", *microseconds );
-	verse_callback_update( *microseconds );
-	return Qtrue;
-}
-
-
-/* 
- * Iterator for rbverse_verse_session_update 
- */
-static int
-rbverse_verse_session_s_update_i( VSession id, VALUE session, st_data_t timeout ) {
-	rbverse_log( "debug", "Callback update for session %p (timeout=%u µs).", id, timeout );
-
-	verse_session_set( id );
-	rb_thread_blocking_region( rbverse_verse_session_s_update_body, (uint32 *)&timeout,
-		RUBY_UBF_IO, NULL );
-
-	rbverse_log( "debug", "  after the blocking region, there are %d client sessions",
-	             session_table->num_entries );
-
-	return ST_CONTINUE;
-}
-
-
-/*
- * call-seq:
- *     Verse::Session.update( timeout=0.1 )
- * 
- * Reads any incoming packets from the network, parses them (splits
- * them into commands) and issues calls to any callbacks that are
- * registered for the found commands. It will block for at most
- * +timeout+ microseconds and wait for something to arrive.
- * 
- * An application must call this function periodically in order to
- * service the connection with the other end of the Verse link; failure
- * to do so will cause the other end's packet buffer to grow monotonically,
- * which in turn might cause the connection to be terminated.
- * 
- * Any registered callbacks can be called as a result of calling this
- * function if the corresponding Verse event has happened. Execution
- * of this function is the only time during which callbacks can be
- * called. This means that a client program can rely on the fact that
- * calling some other Verse API function, such as any command-sending
- * function, is guaranteed to not cause a callback to be invoked.
- * 
- * @param [Float] timeout  the maximum amount of time (in decimal seconds) to
- *                         block waiting for updates
- */
-static VALUE
-rbverse_verse_session_s_update( int argc, VALUE *argv, VALUE module ) {
-	VALUE seconds = Qnil;
-	uint32 microseconds, slice;
-
-	if ( rb_scan_args(argc, argv, "01", &seconds) == 1 )
-		microseconds = floor( NUM2DBL(seconds) * 1000000 );
-	else
-		microseconds = DEFAULT_UPDATE_TIMEOUT;
-
-	slice = microseconds / ( session_table->num_entries + 1 );
-	DEBUGMSG( "Update timeslice is %d µs", slice );
-
-	DEBUGMSG( "  updating the global session" );
-	verse_session_set( 0 );
-	rb_thread_blocking_region( rbverse_verse_session_s_update_body, (uint32 *)&slice,
-		RUBY_UBF_IO, NULL );
-
-	if ( session_table->num_entries ) {
-		DEBUGMSG( "  updating %d client sessions", session_table->num_entries );
-		st_foreach( session_table, rbverse_verse_session_s_update_i, (st_data_t)slice );
-	} else {
-		DEBUGMSG( "  no client sessions to update" );
-	}
-
-	return Qtrue;
-}
-
-
 /*
  * Iterator body for rbverse_verse_session_s_all_connected
  */
  *
  * @return [Array<Verse::Session>]  all connected sessions
  */
-static VALUE
+VALUE
 rbverse_verse_session_s_all_connected( VALUE klass ) {
 	VALUE sessions = rb_ary_new();
 
  *    session.subscribe_to_node_index( *node_classes )
  *
  * Subscribe to creation and destruction events for the specified +node_classes+. Node 
- * creation will be sent to the Verse::SessionObservers via their #on_node_create method,
+ * creation will be sent to the Verse::SessionObservers via their #on_node_created method,
  * and when nodes are deleted, #on_node_destroy is called.
  *
  * @param [Array<Class>] node_classes  one or more subclasses of Verse::Node that indicate
 static VALUE
 rbverse_verse_session_subscribe_to_node_index( int argc, VALUE *argv, VALUE self ) {
 	struct rbverse_session *session = rbverse_get_session( self );
-	VALUE node_classes = Qnil;
-	uint32 typemask = ~0;
+	VALUE node_classes, typenum;
+	uint32 typemask = 0;
+
 	int i;
 
 	if ( !session->id )
 		rb_raise( rbverse_eVerseSessionError, "can't subscribe an unconnected session" );
 
 	if ( rb_scan_args(argc, argv, "0*", &node_classes) ) {
+		rbverse_log_with_context( self, "debug", "subscribing to %d node classes",
+		                          RARRAY_LEN(node_classes) );
 		for ( i = 0; i < RARRAY_LEN(node_classes); i++ ) {
-			VALUE typenum = rb_const_get( RARRAY_PTR(node_classes)[i], rb_intern("TYPE_NUMBER") );
-			typemask |= (1 << FIX2INT( typenum ));
+			typenum = rb_const_get( RARRAY_PTR(node_classes)[i], rb_intern("TYPE_NUMBER") );
+			rbverse_log_with_context( self, "debug", "  adding %s to the mask",
+			                          RSTRING_PTR(rb_inspect(typenum)) );
+			typemask |= (1 << FIX2UINT( typenum ));
 		}
+	} else {
+		typemask = ~0;
 	}
 
+	rbverse_log_with_context( self, "debug", "  built typemask: %x", typemask );
 	rbverse_with_session_lock( self, rbverse_verse_session_subscribe_to_node_index_l,
 	                           (VALUE)typemask );
 
  * call-seq:
  *    session.create_node( nodeclass ) {|node| ... }
  *
- * Ask the server to create a new node of the specified +nodeclass+. If the node is successfully 
- * created, the provided block will be called with the new node object. Note that this happens
- * asynchronously, so you shouldn't count on the callback being run when this method returns.
+ * Ask the server end of the session to create a new node of the specified +nodeclass+. If 
+ * the node is successfully created, the provided block will be called with the new node 
+ * object. Note that this happens asynchronously, so you shouldn't count on the callback 
+ * having been run when this method returns.
+ * 
+ * @param [Class<Verse::Node>] nodeclass   the class of node that is to be created.
  * 
  * @yield [node]  called when the node is created with the node object
  * @example Creating a new ObjectNode
  *     objectnode = nil
  *     session.create_node( Verse::ObjectNode ) {|node| objectnode = node }
- *     Verse::Session.update until objectnode
+ *     Verse.update until objectnode
  *   
  */
 static VALUE
 		callback = rb_block_proc();
 	if ( !RTEST(callback_queue) )
 		rb_raise( rbverse_eVerseSessionError,
-		          "don't know how to create %s objects (no callback queue)", 
+		          "don't know how to create %s objects (no callback queue)",
 		          rb_class2name(nodeclass) );
 
 	/* Add the callback to the queue. This isn't done inside the lock as
-	 * we don't really care if the nodes are created strictly in the order
+	 * we don't really care if the node callbacks are called strictly in the order
 	 * in which they're created. */
 	rb_ary_push( callback_queue, callback );
 	return rbverse_with_session_lock( self, rbverse_verse_session_create_node_l, nodeclass );
 }
 
 
+/* Synchronized portion of rbverse_verse_session_node_created() */
+static VALUE
+rbverse_verse_session_node_created_l( VALUE args ) {
+	VALUE nodeobj = RARRAY_PTR(args)[0];
+	VALUE session = RARRAY_PTR(args)[1];
+	struct rbverse_node *node = rbverse_get_node( nodeobj );
+	VNodeOwner owner = (node->session == session ? VN_OWNER_MINE : VN_OWNER_OTHER);
+
+	verse_send_node_create( node->id, node->type, owner );
+
+	return Qtrue;
+}
+
+
+/*
+ * call-seq:
+ *    session.node_created( node )
+ *
+ * Inform the client end of the session that the given +node+ was created.
+ * 
+ * @param [Verse::Node] node  the new node object
+ */
+static VALUE
+rbverse_verse_session_node_created( VALUE self, VALUE node ) {
+	VALUE args = rb_ary_new3( 2, node, self );
+	return rbverse_with_session_lock( self, rbverse_verse_session_node_created_l, args );
+}
+
 
 /* Synchronized portion of rbverse_verse_session_destroy_node() */
 static VALUE
  * @example Remove the node from the list of active objects once it's destroyed
  *     active_nodes = [ objectnode ]
  *     session.destroy_node( objectnode ) {|node| active_nodes.delete(node) }
- *     Verse::Session.update while active_nodes.include?( objectnode )
+ *     Verse.update while active_nodes.include?( objectnode )
  */
 static VALUE
 rbverse_verse_session_destroy_node( int argc, VALUE *argv, VALUE self ) {
 }
 
 
+/* Synchronized portion of rbverse_verse_session_node_destroyed() */
+static VALUE
+rbverse_verse_session_node_destroyed_l( VALUE nodeobj ) {
+	struct rbverse_node *node = rbverse_get_node( nodeobj );
+	verse_send_node_destroy( node->id );
+	return Qtrue;
+}
+
+
+/*
+ * call-seq:
+ *    session.node_destroyed( node )
+ *
+ * Inform the client end of the session that the given +node+ was destroyed.
+ * 
+ * @param [Verse::Node] node  the new node object
+ */
+static VALUE
+rbverse_verse_session_node_destroyed( VALUE self, VALUE node ) {
+	return rbverse_with_session_lock( self, rbverse_verse_session_node_destroyed_l, node );
+}
+
+
+
+
 /* --------------------------------------------------------------
  * Callbacks
  * -------------------------------------------------------------- */
  * Iterator body for connect_accept observers.
  */
 static VALUE
-rbverse_cb_session_connect_accept_i( VALUE observer, VALUE cb_args ) {
+rbverse_session_cb_connect_accept_i( VALUE observer, VALUE cb_args ) {
 	if ( !rb_obj_is_kind_of(observer, rbverse_mVerseSessionObserver) )
 		return Qnil;
 
-	rbverse_log( "debug", "Connect_accept callback: notifying observer: %s.",
-	             RSTRING_PTR(rb_inspect( observer )) );
-	return rb_funcall2( observer, rb_intern("on_connect_accept"), 
+	rbverse_log( "debug", "Connect_accept callback: notifying observer: %s with args: %s.",
+	             RSTRING_PTR(rb_inspect( observer )), RSTRING_PTR(rb_inspect(cb_args)) );
+	return rb_funcall2( observer, rb_intern("on_connect_accept"),
 	                    RARRAY_LEN(cb_args), RARRAY_PTR(cb_args) );
 }
 
 
 /*
  * Ruby handler for the 'connect_accept' message; called after re-establishing the GVL
- * from rbverse_cb_session_connect_accept().
+ * from rbverse_session_cb_connect_accept().
  */
 static void *
-rbverse_cb_session_connect_accept_body( void *ptr ) {
+rbverse_session_cb_connect_accept_body( void *ptr ) {
 	struct rbverse_connect_accept_event *event = (struct rbverse_connect_accept_event *)ptr;
-	const VALUE cb_args = rb_ary_new2( 3 );
+	VALUE cb_args = rb_ary_new();
 	VALUE session = rbverse_get_current_session();
 	VALUE observers = rb_funcall( session, rb_intern("observers"), 0 );
 
-	RARRAY_PTR(cb_args)[0] = INT2FIX( event->avatar );
-	RARRAY_PTR(cb_args)[1] = rb_str_new2( event->address );
-	RARRAY_PTR(cb_args)[2] = rbverse_host_id2str( event->hostid );
+	rb_ary_push( cb_args, INT2FIX(event->avatar) );
+	rb_ary_push( cb_args, rb_str_new2(event->address) );
+	rb_ary_push( cb_args, rbverse_host_id2str(event->hostid) );
 
-	rb_block_call( observers, rb_intern("each"), 0, 0, rbverse_cb_session_connect_accept_i, cb_args );
+	rb_block_call( observers, rb_intern("each"), 0, 0, rbverse_session_cb_connect_accept_i, cb_args );
 
 	return NULL;
 }
  * Verse callback for the 'connect_accept' message
  */
 static void
-rbverse_cb_session_connect_accept( void *unused, VNodeID avatar, const char *address, uint8 *host_id ) {
+rbverse_session_cb_connect_accept( void *unused, VNodeID avatar, const char *address, uint8 *host_id ) {
 	struct rbverse_connect_accept_event event;
 
 	event.avatar  = avatar;
 	event.address = address;
 	event.hostid  = host_id;
 
-	rb_thread_call_with_gvl( rbverse_cb_session_connect_accept_body, (void *)&event );
+	rb_thread_call_with_gvl( rbverse_session_cb_connect_accept_body, (void *)&event );
 }
 
 
  * Iterator body for connect_terminate observers.
  */
 static VALUE
-rbverse_cb_session_connect_terminate_i( VALUE observer, VALUE cb_args ) {
+rbverse_session_cb_connect_terminate_i( VALUE observer, VALUE cb_args ) {
 	if ( !rb_obj_is_kind_of(observer, rbverse_mVerseSessionObserver) )
 		return Qnil;
 
  * Call the connect_terminate handler after aqcuiring the GVL.
  */
 static void *
-rbverse_cb_session_connect_terminate_body( void *ptr ) {
+rbverse_session_cb_connect_terminate_body( void *ptr ) {
 	const char **args = (const char **)ptr;
-	const VALUE cb_args = rb_ary_new2( 2 );
+	const VALUE cb_args = rb_ary_new();
 	VALUE session, observers;
 
 	session = rbverse_get_current_session();
 	observers = rb_funcall( session, rb_intern("observers"), 0 );
 
-	RARRAY_PTR(cb_args)[0] = rb_str_new2( args[0] );
-	RARRAY_PTR(cb_args)[1] = rb_str_new2( args[1] );
+	rb_ary_push( cb_args, rb_str_new2(args[0]) );
+	rb_ary_push( cb_args, rb_str_new2(args[1]) );
 
-	rb_block_call( observers, rb_intern("each"), 0, 0, rbverse_cb_session_connect_terminate_i, cb_args );
+	rb_block_call( observers, rb_intern("each"), 0, 0,
+	               rbverse_session_cb_connect_terminate_i, cb_args );
 
 	return NULL;
 }
  * Callback for the 'connect_terminate' command.
  */
 static void
-rbverse_cb_session_connect_terminate( void *unused, const char *address, const char *msg ) {
+rbverse_session_cb_connect_terminate( void *unused, const char *address, const char *msg ) {
 	const char *(args[2]) = { address, msg };
 	DEBUGMSG( " Acquiring GVL for 'connect_terminate' event.\n" );
 	fflush( stdout );
-	rb_thread_call_with_gvl( rbverse_cb_session_connect_terminate_body, args );
+	rb_thread_call_with_gvl( rbverse_session_cb_connect_terminate_body, args );
+}
+
+
+/*
+ * Iterator body for on_create_node observers.
+ */
+static VALUE
+rbverse_session_on_create_node_i( VALUE observer, VALUE node_class ) {
+	if ( !rb_obj_is_kind_of(observer, rbverse_mVerseSessionObserver) )
+		return Qnil;
+
+	rbverse_log( "debug", "on_create_node callback: notifying observer: %s.",
+	             RSTRING_PTR(rb_inspect( observer )) );
+	return rb_funcall2( observer, rb_intern("on_create_node"), 1, &node_class );
+}
+
+
+/* Build a call to #on_create_node for the session's observers and call them. */
+static void
+rbverse_session_call_on_create_node( struct rbverse_node_create_event *event ) {
+	const VALUE self = rbverse_get_current_session();
+	const VALUE observers = rb_funcall( self, rb_intern("observers"), 0 );
+	VALUE node_class = rbverse_node_class_from_node_type( event->node_type );
+
+	rb_block_call( observers, rb_intern("each"), 0, 0,
+	               rbverse_session_on_create_node_i, node_class );
 }
 
 
  * Iterator body for node_create observers.
  */
 static VALUE
-rbverse_cb_session_node_create_i( VALUE observer, VALUE node ) {
+rbverse_session_on_node_created_i( VALUE observer, VALUE node ) {
 	if ( !rb_obj_is_kind_of(observer, rbverse_mVerseSessionObserver) )
 		return Qnil;
 
-	rbverse_log( "debug", "Node_create callback: notifying observer: %s.",
+	rbverse_log( "debug", "on_node_created callback: notifying observer: %s.",
 	             RSTRING_PTR(rb_inspect( observer )) );
-	return rb_funcall2( observer, rb_intern("on_node_create"), 1, &node );
+	return rb_funcall2( observer, rb_intern("on_node_created"), 1, &node );
 }
 
 
-/*
- * Call the node_create handler after aqcuiring the GVL.
- */
-static void *
-rbverse_cb_session_node_create_body( void *ptr ) {
-	struct rbverse_node_create_event *event = (struct rbverse_node_create_event *)ptr;
-	VALUE self, node, cb_queue, callback, observers;
-	struct rbverse_session *session = NULL;
-
-	node      = rbverse_wrap_verse_node( event->node_id, event->type, event->owner );
-	self      = rbverse_get_current_session();
-	session   = rbverse_get_session( self );
-	cb_queue  = rb_hash_aref( session->create_callbacks, rb_class_of(node) );
-	observers = rb_funcall( self, rb_intern("observers"), 0 );
+/* Build a call to #on_node_created for the session's observers and call them. */
+static void
+rbverse_session_call_on_node_created( struct rbverse_node_create_event *event ) {
+	const VALUE self = rbverse_get_current_session();
+	struct rbverse_session *session = rbverse_get_session( self );
+	const VALUE observers = rb_funcall( self, rb_intern("observers"), 0 );
+	const VALUE node =
+		rbverse_wrap_verse_node( event->node_id, event->node_type, event->node_owner );
+	const VALUE cb_queue = rb_hash_aref( session->create_callbacks, CLASS_OF(node) );
+	VALUE callback = Qnil;
 
 	/* Set the node's session */
-	rb_funcall3( node, rb_intern("session="), 1, &self );
+	rb_funcall2( node, rb_intern("session="), 1, &self );
 
 	/* If this session was the node's creator, and there's a creation
 	 * callback queue for the class of node that was created, and there's a callback in
 	 * the queue, shift it off and call it with the node object. */
-	if ( event->owner == VN_OWNER_MINE &&
+	if ( event->node_owner == VN_OWNER_MINE &&
 	     RTEST(cb_queue) &&
 	     RTEST(callback = rb_ary_shift(cb_queue)) )
 	{
 	}
 
 	/* Now notify the session's observers that a node was created */
-	rb_block_call( observers, rb_intern("each"), 0, 0, rbverse_cb_session_node_create_i, node );
+	rb_block_call( observers, rb_intern("each"), 0, 0,
+	               rbverse_session_on_node_created_i, node );
+}
+
+
+
+/*
+ * Decide which callback to call based on whether the event specifies a node 
+ * ID or not. If it doesn't, it's a client requesting the creation of a node by
+ * passing a class, so call #on_create_node. If there is a node ID, it's a server
+ * notifying a client that a node has been created, so call #on_node_created.
+ */
+static void *
+rbverse_session_cb_node_create_body( void *ptr ) {
+	struct rbverse_node_create_event *event = (struct rbverse_node_create_event *)ptr;
+
+	/* Call #on_create_node if there's no ID */
+	if ( event->node_id == ~0 ) {
+		rbverse_session_call_on_create_node( event );
+	}
+
+	/* Call #on_node_created if there's a node ID. */
+	else {
+		rbverse_session_call_on_node_created( event );
+	}
 
 	return NULL;
 }
 
+
 /*
  * Callback for the 'node_create' command.
  */
 static void
-rbverse_cb_session_node_create( void *unused, VNodeID node_id, VNodeType type, VNodeOwner owner ) {
+rbverse_session_cb_node_create( void *unused, VNodeID node_id, VNodeType type, VNodeOwner owner ) {
 	struct rbverse_node_create_event event;
 
-	event.node_id = node_id;
-	event.type    = type;
-	event.owner   = owner;
+	event.node_id    = node_id;
+	event.node_type  = type;
+	event.node_owner = owner;
 
 	DEBUGMSG( " Acquiring GVL for 'node_create' event.\n" );
 	fflush( stdout );
 
-	rb_thread_call_with_gvl( rbverse_cb_session_node_create_body, (void *)&event );
+	rb_thread_call_with_gvl( rbverse_session_cb_node_create_body, (void *)&event );
 }
 
 
  * Iterator body for node_destroy observers.
  */
 static VALUE
-rbverse_cb_session_node_destroy_i( VALUE observer, VALUE node ) {
+rbverse_session_cb_node_destroy_i( VALUE observer, VALUE node ) {
 	if ( !rb_obj_is_kind_of(observer, rbverse_mVerseSessionObserver) )
 		return Qnil;
 
  * Call the node_destroy handler after aqcuiring the GVL.
  */
 static void *
-rbverse_cb_session_node_destroy_body( void *ptr ) {
+rbverse_session_cb_node_destroy_body( void *ptr ) {
 	VNodeID *node_id = (VNodeID *)ptr;
 	VALUE node;
 
 		VALUE session = rb_funcall( node, rb_intern("session"), 0 );
 		VALUE observers = rb_funcall( session, rb_intern("observers"), 0 );
 		rbverse_mark_node_destroyed( node );
-		rb_block_call( observers, rb_intern("each"), 0, 0, rbverse_cb_session_node_destroy_i, node );
+		rb_block_call( observers, rb_intern("each"), 0, 0, rbverse_session_cb_node_destroy_i, node );
 	} else {
 		rbverse_log( "info", "destroy event received for unwrapped node %x", node_id );
 	}
  * Callback for the 'node_destroy' command.
  */
 static void
-rbverse_cb_session_node_destroy( void *unused, VNodeID node_id ) {
+rbverse_session_cb_node_destroy( void *unused, VNodeID node_id ) {
 	DEBUGMSG( " Acquiring GVL for 'node_destroy' event.\n" );
 	fflush( stdout );
 
-	rb_thread_call_with_gvl( rbverse_cb_session_node_destroy_body, (void *)&node_id );
+	rb_thread_call_with_gvl( rbverse_session_cb_node_destroy_body, (void *)&node_id );
 }
 
 
 #endif
 
 	/* Related modules */
-	rbverse_mVerseSessionObserver = rb_define_module_under( rbverse_mVerse, "SessionObserver" );
+	rbverse_mVerseSessionObserver =
+		rb_define_module_under( rbverse_mVerse, "SessionObserver" );
 
 	/* Class methods */
 	rbverse_cVerseSession = rb_define_class_under( rbverse_mVerse, "Session", rb_cObject );
 
 	rb_define_alloc_func( rbverse_cVerseSession, rbverse_verse_session_s_allocate );
 
-	rb_define_singleton_method( rbverse_cVerseSession, "update", rbverse_verse_session_s_update,
-	                            -1 );
-	rb_define_alias( rb_singleton_class(rbverse_cVerseSession), "callback_update", "update" );
-
 	rb_attr( rb_singleton_class(rbverse_cVerseSession), rb_intern("mutex"), Qtrue, Qfalse, Qtrue );
 	rb_iv_set( rbverse_cVerseSession, "@mutex", rbverse_session_mutex );
 	rb_define_singleton_method( rbverse_cVerseSession, "all_connected",
 
 	rb_define_method( rbverse_cVerseSession, "subscribe_to_node_index",
 	                  rbverse_verse_session_subscribe_to_node_index, -1 );
-	rb_define_method( rbverse_cVerseSession, "create_node", rbverse_verse_session_create_node, -1 );
-	rb_define_method( rbverse_cVerseSession, "destroy_node", rbverse_verse_session_destroy_node,
-	                  -1 );
+
+	rb_define_method( rbverse_cVerseSession, "create_node",
+	                  rbverse_verse_session_create_node, -1 );
+	rb_define_method( rbverse_cVerseSession, "destroy_node",
+	                  rbverse_verse_session_destroy_node, -1 );
+
+	rb_define_method( rbverse_cVerseSession, "node_created",
+	                  rbverse_verse_session_node_created, 2 );
+	rb_define_method( rbverse_cVerseSession, "node_destroyed",
+	                  rbverse_verse_session_node_destroyed, 1 );
 
 	// tag_group_create(VNodeID node_id, uint16 group_id, const char *name);
 	// tag_group_destroy(VNodeID node_id, uint16 group_id);
 
 	// node_name_set(VNodeID node_id, const char *name);
 
-	verse_callback_set( verse_send_connect_accept, rbverse_cb_session_connect_accept, NULL );
-	verse_callback_set( verse_send_connect_terminate, rbverse_cb_session_connect_terminate, NULL );
-	verse_callback_set( verse_send_node_create, rbverse_cb_session_node_create, NULL );
-	verse_callback_set( verse_send_node_destroy, rbverse_cb_session_node_destroy, NULL );
+	verse_callback_set( verse_send_connect_accept, rbverse_session_cb_connect_accept, NULL );
+	verse_callback_set( verse_send_connect_terminate, rbverse_session_cb_connect_terminate, NULL );
+	verse_callback_set( verse_send_node_create, rbverse_session_cb_node_create, NULL );
+	verse_callback_set( verse_send_node_destroy, rbverse_session_cb_node_destroy, NULL );
 }
 

File ext/textnode.c

 	rbverse_cVerseTextNode = rb_define_class_under( rbverse_mVerse, "TextNode", rbverse_cVerseNode );
 
     /* Constants */
-	rb_define_const( rbverse_cVerseTextNode, "TYPE_NUMBER", INT2FIX(V_NT_TEXT) );
+	rb_define_const( rbverse_cVerseTextNode, "TYPE_NUMBER", rb_uint2inum(V_NT_TEXT) );
 
 	/* Initializer */
 	rb_define_method( rbverse_cVerseTextNode, "initialize", rbverse_verse_textnode_initialize, 0 );

File ext/verse_ext.c

 VALUE rbverse_mVerseObservable;
 VALUE rbverse_mVerseObserver;
 VALUE rbverse_mVersePingObserver;
-VALUE rbverse_mVerseConnectionObserver;
 
 VALUE rbverse_eVerseError;
+VALUE rbverse_eVerseServerError;
 VALUE rbverse_eVerseConnectError;
 VALUE rbverse_eVerseSessionError;
 VALUE rbverse_eVerseNodeError;
 
 
 /*
- * call-seq:
- *    Verse.connect_accept( avatar, address, host_id )
- *
- * Indicate acceptance of a client's connection request from +address+, assigning it the 
- * given +avatar+ and +host_id+.
- *
- * @return [Verse::Session]  the session that contains the new connection
- * @todo Convert the 'avatar' parameter to a Verse::Node once that is defined.
- */
-static VALUE
-rbverse_verse_connect_accept( VALUE module, VALUE avatar, VALUE address, VALUE host_id ) {
-	VNodeID av       = NUM2ULONG( avatar );
-	const char *addr = NULL;
-	uint8 *id        = rbverse_str2host_id( host_id );
-	VSession session_id;
-	VALUE session = Qnil;
-
-	SafeStringValue( address );
-	addr = RSTRING_PTR( address );
-
-	session_id = verse_send_connect_accept( av, addr, id );
-	session = rbverse_verse_session_from_vsession( session_id, address );
-
-	return session;
-}
-
-
-/*
  *  call-seq:
  *     Verse.create_host_id   -> string
  *
 }
 
 
+/* Body of rbverse_verse_update after GVL is given up. */
+static VALUE
+rbverse_verse_update_body( void *ptr ) {
+	uint32 *microseconds = (uint32 *)ptr;
+	DEBUGMSG( "  calling verse_callback_update( %d ).", *microseconds );
+	verse_callback_update( *microseconds );
+	return Qtrue;
+}
+
+
+/* 
+ * Iterator for rbverse_verse_session_update 
+ */
+static int
+rbverse_verse_update_i( VSession id, VALUE session, st_data_t timeout ) {
+	DEBUGMSG( "Callback update for session %p (timeout=%u µs).", id, (uint32)timeout );
+
+	verse_session_set( id );
+	rb_thread_blocking_region( rbverse_verse_update_body, (uint32 *)&timeout,
+		RUBY_UBF_IO, NULL );
+
+	return ST_CONTINUE;
+}
+
+
+/*
+ * call-seq:
+ *     Verse.update( timeout=0.1 )
+ * 
+ * Reads any incoming packets from the network, parses them (splits
+ * them into commands) and issues calls to any callbacks that are
+ * registered for the found commands. It will block for at most
+ * +timeout+ microseconds while waiting for something to arrive.
+ * 
+ * An application must call this function periodically in order to
+ * service the connection with the other end of the Verse link; failure
+ * to do so will cause the other end's packet buffer to grow monotonically,
+ * which in turn might cause the connection to be terminated.
+ * 
+ * Any registered callbacks can be called as a result of calling this
+ * function if the corresponding Verse event has happened. Execution
+ * of this function is the only time during which callbacks can be
+ * called. This means that a client program can rely on the fact that
+ * calling some other Verse API function, such as any command-sending
+ * function, is guaranteed to not cause a callback to be invoked.
+ * 
+ * @param [Float] timeout  the maximum amount of time (in decimal seconds) to
+ *                         block waiting for updates
+ */
+static VALUE
+rbverse_verse_update( int argc, VALUE *argv, VALUE module ) {
+	VALUE seconds = Qnil;
+	uint32 microseconds, slice;
+
+	if ( rb_scan_args(argc, argv, "01", &seconds) == 1 )
+		microseconds = floor( NUM2DBL(seconds) * 1000000 );
+	else
+		microseconds = DEFAULT_UPDATE_TIMEOUT;
+
+	slice = microseconds / ( session_table->num_entries + 1 );
+	DEBUGMSG( "Update timeslice is %d µs", slice );
+
+	DEBUGMSG( "  updating the global session" );
+	verse_session_set( 0 );
+	rb_thread_blocking_region( rbverse_verse_update_body, (uint32 *)&slice,
+		RUBY_UBF_IO, NULL );
+
+	if ( session_table->num_entries ) {
+		DEBUGMSG( "  updating %lu client sessions", (long unsigned int)session_table->num_entries );
+		st_foreach( session_table, rbverse_verse_update_i, (st_data_t)slice );
+	} else {
+		DEBUGMSG( "  no client sessions to update" );
+	}
+
+	return Qtrue;
+}
+
+
 /* --------------------------------------------------------------
  * Observable Support
  * -------------------------------------------------------------- */
 	if ( !rb_obj_is_kind_of(observer, rbverse_mVersePingObserver) )
 		return Qnil;
 
-	rbverse_log( "debug", "Ping callback: notifying observer: %s.", 
+	rbverse_log( "debug", "Ping callback: notifying observer: %s.",
 	             RSTRING_PTR(rb_inspect( observer )) );
-	return rb_funcall2( observer, rb_intern("on_ping"), 2, RARRAY_PTR(cb_args) );
+	return rb_funcall2( observer, rb_intern("on_ping"), RARRAY_LEN(cb_args), RARRAY_PTR(cb_args) );
 }
 
 
 static void *
 rbverse_cb_ping_body( void *ptr ) {
 	const char **args = (const char **)ptr;
-	const VALUE cb_args = rb_ary_new2( 2 );
+	const VALUE cb_args = rb_ary_new();
 	const VALUE observers = rb_iv_get( rbverse_mVerse, "@observers" );
 
-	RARRAY_PTR(cb_args)[0] = rb_str_new2( args[0] );
-	RARRAY_PTR(cb_args)[1] = rb_str_new2( args[1] );
+	rb_ary_push( cb_args, rb_str_new2(args[0]) );
+	rb_ary_push( cb_args, rb_str_new2(args[1]) );
 
 	rb_block_call( observers, rb_intern("each"), 0, 0, rbverse_cb_ping_i, cb_args );
 
 
 
 /*
- * Iterator for connect callback.
- */
-static VALUE
-rbverse_cb_connect_i( VALUE observer, VALUE cb_args ) {
-	if ( !rb_obj_is_kind_of(observer, rbverse_mVerseConnectionObserver) )
-		return Qnil;
-
-	rbverse_log( "debug", "Connect callback: notifying observer: %s.", 
-	             RSTRING_PTR(rb_inspect( observer )) );
-	return rb_funcall2( observer, rb_intern("on_connect"), 4, RARRAY_PTR(cb_args) );
-}
-
-
-/*
- * Call the connect handler after acquiring the GVL.
- */
-static void *
-rbverse_cb_connect_body( void *ptr ) {
-	const char **args = (const char **)ptr;
-	const VALUE cb_args = rb_ary_new2( 4 );
-	const VALUE observers = rb_iv_get( rbverse_mVerse, "@observers" );
-
-	RARRAY_PTR(cb_args)[0] = rb_str_new2( args[0] );
-	RARRAY_PTR(cb_args)[1] = rb_str_new2( args[1] );
-	RARRAY_PTR(cb_args)[2] = rb_str_new2( args[2] );
-	RARRAY_PTR(cb_args)[3] = rbverse_host_id2str( (const uint8 *)args[3] );
-
-	DEBUGMSG( "Calling the cb_connect iterator." );
-	rb_block_call( observers, rb_intern("each"), 0, 0, rbverse_cb_connect_i, cb_args );
-
-	return NULL;
-}
-
-/*
- * Callback for the 'connect' command.
- */
-static void
-rbverse_cb_connect( void *unused, const char *name, const char *pass, const char *address,
-                    const uint8 *expected_host_id )
-{
-	const char *(args[4]) = { name, pass, address, (const char *)expected_host_id };
-	DEBUGMSG( "*** Acquiring GVL for 'connect' event. ***" );
-	rb_thread_call_with_gvl( rbverse_cb_connect_body, args );
-}
-
-
-/*
- * Iterator for the node-index subscription callback.
- */
-static VALUE
-rbverse_cb_node_index_subscribe_i( VALUE observer, VALUE cb_args ) {
-	if ( !rb_obj_is_kind_of(observer, rbverse_mVerseConnectionObserver) )
-		return Qnil;
-
-	rbverse_log( "debug", "Node-index subscription callback: notifying observer: %s.",
-	             RSTRING_PTR(rb_inspect( observer )) );
-	return rb_funcall2( observer, rb_intern("on_node_index_subscribe"),
-	                    RARRAY_LEN(cb_args), RARRAY_PTR(cb_args) );
-}
-
-
-/*
- * Call the 'node_index_subscribe' handler after acquiring the GVL.
- */
-static void *
-rbverse_cb_node_index_subscribe_body( void *ptr ) {
-	const uint32 mask = *((uint32 *)ptr);
-	const VALUE observers = rb_iv_get( rbverse_mVerse, "@observers" );
-	const VALUE cb_args = rb_ary_new();
-	VALUE node_class = Qnil;
-	VNodeType node_type;
-
-	rbverse_log( "debug", "Building the list of subscribed classes from mask: %u.", mask );
-	for ( node_type = V_NT_OBJECT; node_type < V_NT_NUM_TYPES; node_type++ ) {
-		if ( mask & (1 << node_type) ) {
-			node_class = rbverse_node_class_from_node_type( node_type );
-			rbverse_log( "debug", "  adding %s", rb_class2name(node_class) );
-			rb_ary_push( cb_args, node_class );
-		}
-	}
-
-	rbverse_log( "debug", "Calling node_index_subscribe iterator with %d node classes.",
-	             RARRAY_LEN(cb_args) );
-	rb_block_call( observers, rb_intern("each"), 0, 0, rbverse_cb_node_index_subscribe_i, cb_args );
-
-	return NULL;
-}
-
-
-/*
- * Callback for the 'node_index_subscribe' command.
- */
-static void
-rbverse_cb_node_index_subscribe( void *unused, uint32 mask ) {
-	rb_thread_call_with_gvl( rbverse_cb_node_index_subscribe_body, (void *)&mask );
-}
-
-
-/*
  * Verse namespace.
  * 
  */
 	rbverse_mVerseObservable = rb_define_module_under( rbverse_mVerse, "Observable" );
 
 	rbverse_mVersePingObserver = rb_define_module_under( rbverse_mVerse, "PingObserver" );
-	rbverse_mVerseConnectionObserver = rb_define_module_under( rbverse_mVerse, "ConnectionObserver" );
 
 	rbverse_eVerseError =
 		rb_define_class_under( rbverse_mVerse, "Error", rb_eRuntimeError );
+	rbverse_eVerseServerError =
+		rb_define_class_under( rbverse_mVerse, "ServerError", rbverse_eVerseError );
 	rbverse_eVerseConnectError =
 		rb_define_class_under( rbverse_mVerse, "ConnectError", rbverse_eVerseError );
 	rbverse_eVerseSessionError =
 	rbverse_eVerseNodeError =
 		rb_define_class_under( rbverse_mVerse, "NodeError", rbverse_eVerseError );
 
-	/* version constants */
+	/* Verse constants */
 	rb_define_const( rbverse_mVerse, "RELEASE_NUMBER", INT2FIX(V_RELEASE_NUMBER) );
 	rb_define_const( rbverse_mVerse, "RELEASE_PATCH",  INT2FIX(V_RELEASE_PATCH) );
 	rb_define_const( rbverse_mVerse, "RELEASE_LABEL",  rb_str_new2(V_RELEASE_LABEL) );
 
 	/* Module methods */
 	rb_define_singleton_method( rbverse_mVerse, "port=", rbverse_verse_port_eq, 1 );
-	rb_define_alias( rb_singleton_class(rbverse_mVerse), "connect_port=", "port=" );
+	rb_define_alias( CLASS_OF(rbverse_mVerse),  "connect_port=", "port=" );
 	rb_define_singleton_method( rbverse_mVerse, "create_host_id", rbverse_verse_create_host_id, 0 );
-	rb_define_alias( CLASS_OF(rbverse_mVerse), "make_host_id", "create_host_id" );
+	rb_define_alias( CLASS_OF(rbverse_mVerse),  "make_host_id", "create_host_id" );
 	rb_define_singleton_method( rbverse_mVerse, "host_id=", rbverse_verse_host_id_eq, 1 );
-	rb_define_singleton_method( rbverse_mVerse, "connect_accept", rbverse_verse_connect_accept, 3 );
 	rb_define_singleton_method( rbverse_mVerse, "ping", rbverse_verse_ping, 2 );
+	rb_define_singleton_method( rbverse_mVerse, "update", rbverse_verse_update, -1 );
+	rb_define_alias( CLASS_OF(rbverse_mVerse),  "callback_update", "update" );
 
 	/*
 	 * Constants
 
 	rb_define_const( rbverse_mVerseConstants, "HOST_ID_SIZE", INT2FIX(V_HOST_ID_SIZE) );
 
-	rb_define_const( rbverse_mVerseConstants, "V_NT_OBJECT", INT2FIX(V_NT_OBJECT) );
-	rb_define_const( rbverse_mVerseConstants, "V_NT_GEOMETRY", INT2FIX(V_NT_GEOMETRY) );
-	rb_define_const( rbverse_mVerseConstants, "V_NT_MATERIAL", INT2FIX(V_NT_MATERIAL) );
-	rb_define_const( rbverse_mVerseConstants, "V_NT_BITMAP", INT2FIX(V_NT_BITMAP) );
-	rb_define_const( rbverse_mVerseConstants, "V_NT_TEXT", INT2FIX(V_NT_TEXT) );
-	rb_define_const( rbverse_mVerseConstants, "V_NT_CURVE", INT2FIX(V_NT_CURVE) );
-	rb_define_const( rbverse_mVerseConstants, "V_NT_AUDIO", INT2FIX(V_NT_AUDIO) );
-	rb_define_const( rbverse_mVerseConstants, "V_NT_NUM_TYPES", INT2FIX(V_NT_NUM_TYPES) );
-	rb_define_const( rbverse_mVerseConstants, "V_NT_SYSTEM", INT2FIX(V_NT_SYSTEM) );
-	rb_define_const( rbverse_mVerseConstants, "V_NT_NUM_TYPES_NETPACK", INT2FIX(V_NT_NUM_TYPES_NETPACK) );
+	rb_define_const( rbverse_mVerseConstants, "V_NT_OBJECT", rb_uint2inum(V_NT_OBJECT) );
+	rb_define_const( rbverse_mVerseConstants, "V_NT_GEOMETRY", rb_uint2inum(V_NT_GEOMETRY) );
+	rb_define_const( rbverse_mVerseConstants, "V_NT_MATERIAL", rb_uint2inum(V_NT_MATERIAL) );
+	rb_define_const( rbverse_mVerseConstants, "V_NT_BITMAP", rb_uint2inum(V_NT_BITMAP) );
+	rb_define_const( rbverse_mVerseConstants, "V_NT_TEXT", rb_uint2inum(V_NT_TEXT) );
+	rb_define_const( rbverse_mVerseConstants, "V_NT_CURVE", rb_uint2inum(V_NT_CURVE) );
+	rb_define_const( rbverse_mVerseConstants, "V_NT_AUDIO", rb_uint2inum(V_NT_AUDIO) );
+	rb_define_const( rbverse_mVerseConstants, "V_NT_NUM_TYPES", rb_uint2inum(V_NT_NUM_TYPES) );
+	rb_define_const( rbverse_mVerseConstants, "V_NT_SYSTEM", rb_uint2inum(V_NT_SYSTEM) );
+	rb_define_const( rbverse_mVerseConstants, "V_NT_NUM_TYPES_NETPACK",
+	                 rb_uint2inum(V_NT_NUM_TYPES_NETPACK) );
 
 	/* Init the subordinate classes */
 	rbverse_init_verse_session();
+	rbverse_init_verse_server();
 	rbverse_init_verse_node();
 	rbverse_init_verse_mixins();
 
 	/* Set up calbacks */
 	verse_callback_set( verse_send_ping, rbverse_cb_ping, NULL );
-	verse_callback_set( verse_send_connect, rbverse_cb_connect, NULL );
-	verse_callback_set( verse_send_node_index_subscribe, rbverse_cb_node_index_subscribe, NULL );
 
 	rbverse_log( "debug", "Initialized the extension." );
 }

File ext/verse_ext.h

 extern VALUE rbverse_mVerseObserver;
 extern VALUE rbverse_mVerseObservable;
 extern VALUE rbverse_mVersePingObserver;
-extern VALUE rbverse_mVerseConnectionObserver;
 extern VALUE rbverse_mVerseSessionObserver;
 extern VALUE rbverse_mVerseNodeObserver;
 
 extern VALUE rbverse_cVerseAudioNode;
 
 extern VALUE rbverse_eVerseError;
+extern VALUE rbverse_eVerseServerError;
 extern VALUE rbverse_eVerseConnectError;
 extern VALUE rbverse_eVerseSessionError;
 extern VALUE rbverse_eVerseNodeError;
 
+extern st_table *session_table;
+
 
 /* --------------------------------------------------------------
  * Typedefs
 /* --------------------------------------------------------------
  * Macros
  * -------------------------------------------------------------- */
-#define IsSession( obj ) rb_obj_is_kind_of( (obj), rbverse_cVerseSession )
-#define IsServer( obj ) rb_obj_is_kind_of( (obj), rbverse_cVerseServer )
-
-#define IsNode( obj ) rb_obj_is_kind_of( (obj), rbverse_cVerseNode )
-#define IsAudioNode( obj ) rb_obj_is_kind_of( (obj), rbverse_cVerseAudioNode )
-#define IsBitmapNode( obj ) rb_obj_is_kind_of( (obj), rbverse_cVerseBitmapNode )
-#define IsCurveNode( obj ) rb_obj_is_kind_of( (obj), rbverse_cVerseCurveNode )
-#define IsGeometryNode( obj ) rb_obj_is_kind_of( (obj), rbverse_cVerseGeometryNode )
-#define IsMaterialNode( obj ) rb_obj_is_kind_of( (obj), rbverse_cVerseMaterialNode )
-#define IsObjectNode( obj ) rb_obj_is_kind_of( (obj), rbverse_cVerseObjectNode )
-#define IsTextNode( obj ) rb_obj_is_kind_of( (obj), rbverse_cVerseTextNode )
-
 #define DEFAULT_ADDRESS "127.0.0.1"
 #define DEFAULT_UPDATE_TIMEOUT 100000
 
 		rb_raise( rbverse_eVerseNodeError, "node is destroyed" );
 }
 
+/* Type-check functions */
+static inline boolean IsSession( VALUE obj ) {
+	return rb_obj_is_kind_of( obj, rbverse_cVerseSession ) ? TRUE : FALSE;
+}
+static inline boolean IsServer( VALUE obj ) {
+	return rb_obj_is_kind_of( obj, rbverse_cVerseServer ) ? TRUE : FALSE;
+}
+static inline boolean IsNode( VALUE obj ) {
+	return rb_obj_is_kind_of( obj, rbverse_cVerseNode ) ? TRUE : FALSE;
+}
+static inline boolean IsAudioNode( VALUE obj ) {
+	return rb_obj_is_kind_of( obj, rbverse_cVerseAudioNode ) ? TRUE : FALSE;
+}
+static inline boolean IsBitmapNode( VALUE obj ) {
+	return rb_obj_is_kind_of( obj, rbverse_cVerseBitmapNode ) ? TRUE : FALSE;
+}
+static inline boolean IsCurveNode( VALUE obj ) {
+	return rb_obj_is_kind_of( obj, rbverse_cVerseCurveNode ) ? TRUE : FALSE;
+}
+static inline boolean IsGeometryNode( VALUE obj ) {
+	return rb_obj_is_kind_of( obj, rbverse_cVerseGeometryNode ) ? TRUE : FALSE;
+}
+static inline boolean IsMaterialNode( VALUE obj ) {
+	return rb_obj_is_kind_of( obj, rbverse_cVerseMaterialNode ) ? TRUE : FALSE;
+}
+static inline boolean IsObjectNode( VALUE obj ) {
+	return rb_obj_is_kind_of( obj, rbverse_cVerseObjectNode ) ? TRUE : FALSE;
+}
+static inline boolean IsTextNode( VALUE obj ) {
+	return rb_obj_is_kind_of( obj, rbverse_cVerseTextNode ) ? TRUE : FALSE;
+}
+
 
 /* --------------------------------------------------------------
  * Declarations
 extern VALUE rbverse_get_current_session			_(( void ));
 extern VALUE rbverse_with_session_lock				_(( VALUE, VALUE (*)(ANYARGS), VALUE ));
 extern VALUE rbverse_verse_session_from_vsession	_(( VSession, VALUE ));
+extern VALUE rbverse_verse_session_s_all_connected  _(( VALUE ));
 
 /* node.c */
 extern VALUE rbverse_node_class_from_node_type		_(( VNodeType  ));

File lib/verse.rb

 #!/usr/bin/env ruby
 
+require 'pathname'
+require 'rbconfig'
 
 # A Ruby binding for the Verse network protocol library.
 module Verse
 	require 'verse/mixins'
 	require 'verse/constants'
 
+	include Verse::Constants
 	extend Verse::VersionUtilities,
 	       Verse::Observable
 
- 	if vvec( RUBY_VERSION ) < vvec( '1.9.1' )
-		warn ">>> This library was written for Ruby 1.9.1. It may or may not work " +
+ 	if vvec( RUBY_VERSION ) < vvec( '1.9.2' )
+		warn ">>> This library was written for Ruby 1.9.2. It may or may not work " +
 		     "with earlier versions. <<<"
 	end
 
 			raise "Oops, can't extract the major/minor version from #{RUBY_VERSION.dump}"
 		require "#{major_minor}/verse_ext"
 	else
-		require 'verse_ext'
+		archlib = Pathname( __FILE__ ).dirname + Config::CONFIG['arch']
+		requirepath = archlib + 'verse_ext'
+		require( requirepath.to_s )
 	end
 
 end # module Verse

File lib/verse/constants.rb

 	# The default verse protocol port
 	DEFAULT_PORT = 4950
 
+	# VNodeType
+	V_NT_OBJECT            = 0
+	V_NT_GEOMETRY          = 1
+	V_NT_MATERIAL          = 2
+	V_NT_BITMAP            = 3
+	V_NT_TEXT              = 4
+	V_NT_CURVE             = 5
+	V_NT_AUDIO             = 6
+	V_NT_NUM_TYPES         = 7
+	V_NT_SYSTEM            = V_NT_NUM_TYPES
+	V_NT_NUM_TYPES_NETPACK = 9
+
+
 end # module Verse::Constants
 

File lib/verse/mixins.rb

 # code were stolen from ThingFish under the BSD license.
 module Verse
 
-	### Add logging to a Verse class. Including classes get #log and #log_debug methods.
+	### Add logging to a Verse class. Including classes get #log and #log_debug 
+	### instance methods.
 	module Loggable
 
 		LEVEL = {
 	end # PingObserver
 
 
-	### A mixin for objects which wish to observe Verse connection events.
-	module ConnectionObserver
-		include Verse::Loggable,
-		        Verse::Observer
-
-		### Called when a Verse client connects.
-		### 
-		### @param [String]  user     the username provided by the connecting client
-		### @param [String]  pass     the password provided by the connecting client
-		### @param [String]  address  the address of the connecting client
-		### @param [String]  hostid   the expected hostid provided by the connecting client
-		def on_connect( user, pass, address, hostid )
-			self.log.debug "unhandled on_connect: '%s' connected from '%s'" % [ user, address ]
-		end
-
-
-		### Called when a Verse client requests subscriptions to create/destroy events
-		### for the specified node +classes+.
-		### 
-		### @param [Array<Class>] classes  the Verse::Node classes to subscribe to.
-		def on_node_index_subscribe( *classes )
-			classes.flatten!
-			if classes.empty?
-				self.log.debug "unhandled node_index_subscribe: clear subscriptions"
-			else
-				self.log.debug "unhandled node_index_subscribe: classes: %s" %
-					[ classes.collect {|kl| kl.name }.join(', ') ]
-			end
-		end
-
-	end # module ConnectionObserver
-
-
 	### A mixin for objects which wish to observe events on a Verse::Session.
 	module SessionObserver
 		include Verse::Loggable,
 
 
 		### Called when a new node is created (or indexed) on the server.
-		def on_node_create( node )
-			self.log.debug "unhandled on_node_create for %p" % [ node ]
+		def on_node_created( node )
+			self.log.debug "unhandled on_node_created for %p" % [ node ]
 		end
 
 

File lib/verse/server.rb

-#!/usr/bin/env ruby
-
-require 'pathname'
-require 'singleton'
-
-require 'verse'
-require 'verse/mixins'
-require 'verse/utils'
-
-# Basic Verse server object class
-class Verse::Server
-	include Verse::Loggable,
-	        Verse::PingObserver,
-	        Verse::ConnectionObserver,
-	        Verse::SessionObserver
-
-	# The path to the server's saved hostid
-	HOSTID_FILE = Pathname( 'hostid.rsa' )
-
-	# Connection struct -- stores connection details in the @connections hash
-	Connection = Struct.new( "VerseServerConnection", :address, :user, :session, :avatar )
-
-
-	#################################################################
-	###	I N S T A N C E   M E T H O D S
-	#################################################################
-
-	### Create a new Server configured with the specified +options+.
-	def initialize( options )
-		Verse.port   = options.port
-
-		@host_id      = self.load_host_id
-		@connections = {}
-		@nodes       = {}
-
-		@state       = :stopped
-	end
-
-
-	######
-	public
-	######
-
-	# The server's host id
-	attr_reader :host_id
-
-	# The hash of nodes registered with the server
-	attr_reader :nodes
-
-	# A Verse::Server::Connection per session, keyed by address.
-	attr_reader :connections
-
-	# The Verse::Nodes that are being managed by the server, keyed by id.
-	attr_reader :nodes
-
-
-	### Start listening for events.
-	def run
-		self.observe( Verse )
-		self.log.info "Starting run loop"
-
-		@state = :running
-		Verse::Session.update until @state != :running
-	end
-
-
-	### Shut the server down using the specified +reason+.
-	def shutdown( reason="No reason given." )
-		self.reset_signal_handlers
-		self.log.warn "Server shutdown: #{reason}"
-
-		self.stop_observing( Verse )
-		@connections.keys.each do |addr|
-			conn = @connections.delete( addr )
-			self.stop_observing( conn.session )
-			Verse.terminate_connection( addr, reason )
-		end
-
-		@state = :shutdown
-	end
-
-
-	#########
-	protected
-	#########
-
-	### Set up signal handlers to restart/shutdown the server.
-	def set_signal_handlers
-		Signal.trap( :INT ) { self.shutdown("Caught interrupt.") }
-		Signal.trap( :TERM ) { self.shutdown("Terminated.") }
-		Signal.trap( :HUP ) { self.reload }
-	end
-
-
-	### Set up signal handlers to restart/shutdown the server.
-	def reset_signal_handlers
-		Signal.trap( :INT, 'DEFAULT' )
-		Signal.trap( :TERM, 'DEFAULT' )
-		Signal.trap( :HUP, 'DEFAULT' )
-	end
-
-
-	### Load the hostid from the saved file if it exists. If it doesn't, generate a new one
-	### and save it before returning it.
-	def load_host_id
-		if HOSTID_FILE.exist?
-			return HOSTID_FILE.read
-		else
-			hostid = Verse.create_host_id
-			HOSTID_FILE.open( File::WRONLY|File::CREAT|File::EXCL, 0600 ) do |ofh|
-				ofh.write( hostid )
-			end
-			return hostid
-		end
-	end
-
-
-	### Add a node to the server, assigning it an ID and storing it.
-	def add_node( node )
-		
-	end