Changeset 287
- Timestamp:
- 09/30/08 08:05:39 (7 weeks ago)
- Location:
- trunk
- Files:
-
- 5 added
- 14 modified
-
LICENSE (modified) (1 diff)
-
Rakefile (modified) (1 diff)
-
Rakefile.local (added)
-
lib/fm/composedobject.rb (modified) (8 diffs)
-
lib/fm/gameobject.rb (modified) (13 diffs)
-
lib/fm/locus.rb (modified) (6 diffs)
-
lib/fm/mixins.rb (modified) (17 diffs)
-
lib/fm/periodicobject.rb (modified) (9 diffs)
-
lib/fm/periodicobject/element.rb (modified) (5 diffs)
-
lib/fm/spirit.rb (modified) (1 diff)
-
lib/fm/statistic.rb (modified) (1 diff)
-
spec/fm/composedobject_spec.rb (modified) (1 diff)
-
spec/fm/gameobject_spec.rb (modified) (1 diff)
-
spec/fm/mixins_spec.rb (modified) (2 diffs)
-
spec/fm/periodicobject (added)
-
spec/fm/periodicobject/element_spec.rb (added)
-
spec/fm/periodicobject_spec.rb (modified) (2 diffs)
-
spec/fm/statistic_spec.rb (added)
-
spec/fm_spec.rb (added)
Legend:
- Unmodified
- Added
- Removed
-
trunk/LICENSE
r285 r287 1 Copyright (c) 2008, Michael Granger 2 All rights reserved. 1 3 2 Copyright (c) 2000-2008 The FaerieMUD Consortium. All rights reserved. 4 Redistribution and use in source and binary forms, with or without 5 modification, are permitted provided that the following conditions are met: 3 6 4 While the server this code runs on (MUES) is distributed under an Open Source license, the code for 5 the FaerieMUD game world is not (currently) Open Source or Free Software. In order to preserve what 6 we anticipate will be a unique game world, and to keep some aspects of the game semi-obscured, we 7 would rather keep the code from being widely distributed for the time being. It is entirely possible 8 that we may choose to distribute it under a Free Software or Open Source license in the future. 7 * Redistributions of source code must retain the above copyright notice, 8 this list of conditions and the following disclaimer. 9 9 10 THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT 11 LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. 10 * Redistributions in binary form must reproduce the above copyright notice, 11 this list of conditions and the following disclaimer in the documentation 12 and/or other materials provided with the distribution. 12 13 14 * Neither the name of the author/s, nor the names of the project's 15 contributors may be used to endorse or promote products derived from this 16 software without specific prior written permission. 17 18 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 22 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -
trunk/Rakefile
r285 r287 199 199 end 200 200 201 DEVELOPMENT_DEPENDENCIES.each do |name, version| 202 version = '>= 0' if version.length.zero? 203 gem.add_development_dependency( name, version ) 201 # Developmental dependencies don't work as of RubyGems 1.2.0 202 unless Gem::Version.new( Gem::RubyGemsVersion ) <= Gem::Version.new( "1.2.0" ) 203 DEVELOPMENT_DEPENDENCIES.each do |name, version| 204 version = '>= 0' if version.length.zero? 205 gem.add_development_dependency( name, version ) 206 end 204 207 end 205 208 -
trunk/lib/fm/composedobject.rb
r285 r287 59 59 ### parts which together define the behaviour of the whole. 60 60 class FaerieMUD::ComposedObject < FaerieMUD::Entity 61 include FaerieMUD::AbstractClass 62 61 63 contributors :ged, :scotus 62 64 … … 65 67 module Constituent 66 68 69 ### Methods which are added to including classes as class methods. 70 module ClassMethods 71 # A two-element Array containing the names of the aspects of 72 # the statistic that belongs to the Constituent -- 73 # developmental and linear. 74 attr_reader :statistic_names 75 76 ### Create accessors for the constituent's 77 ### FaerieMUD::Statistic object with the given +devel_name+, 78 ### which will return the Statistic's developmental aspect, 79 ### and +linear_name+, which will return the Statistic's 80 ### linear constituent. 81 def def_statistic( devel_name, linear_name ) 82 devel = devel_name.to_s.intern 83 linear = linear_name.to_s.intern 84 85 self.statistic_names.replace([ devel_name, linear_name ]) 86 87 define_method( devel ) { self.statistic.developmental } 88 define_method( "#{devel}=" ) {|arg| 89 check_type( arg, FaerieMUD::DevelopmentalObject ) 90 self.statistic.developmental = arg 91 } 92 93 define_method( linear ) { self.statistic.linear } 94 define_method( "#{linear}=" ) {|arg| 95 self.statistic.linear = arg 96 } 97 end 98 end 99 100 67 101 ### Inclusion callback -- Install a 'def_statistic' function in 68 102 ### including classes to auto-generate statistic accessors. 69 103 def self::included( klass ) 70 klass.instance_variable_set( :@statisticNames, [] ) 71 72 class << klass 73 74 # A two-element Array containing the names of the aspects of 75 # the statistic that belongs to the Constituent -- 76 # developmental and linear. 77 attr_reader :statisticNames 78 79 ### Create accessors for the constituent's 80 ### FaerieMUD::Statistic object with the given +devel_name+, 81 ### which will return the Statistic's developmental aspect, 82 ### and +linear_name+, which will return the Statistic's 83 ### linear constituent. 84 def def_statistic( devel_name, linear_name ) 85 devel = devel_name.to_s.intern 86 linear = linear_name.to_s.intern 87 88 self.statisticNames.replace([ devel_name, linear_name ]) 89 90 define_method( devel ) { self.statistic.developmental } 91 define_method( "#{devel}=" ) {|arg| 92 check_type( arg, FaerieMUD::DevelopmentalObject ) 93 self.statistic.developmental = arg 94 } 95 96 define_method( linear ) { self.statistic.linear } 97 define_method( "#{linear}=" ) {|arg| 98 self.statistic.linear = arg 99 } 100 end 101 end 102 end 103 104 klass.instance_variable_set( :@statistic_names, [] ) 105 klass.extend( ClassMethods ) 106 end 107 108 109 ############################################################# 110 ### I N S T A N C E M E T H O D S 111 ############################################################# 104 112 105 113 ### Initialize the "statistic" part of a Constituent object. It uses … … 107 115 def initialize( args={} ) 108 116 raise RuntimeError, "No statistic defined for #{self.class.inspect}" if 109 self.class.statistic Names.empty?117 self.class.statistic_names.empty? 110 118 111 119 # If the statistic is defined in the args, remove it from the … … 114 122 element = args.delete( :statistic ) || 1 115 123 @statistic = FaerieMUD::Statistic.new( 116 :devel_name => self.class.statisticNames[0],117 :linear_name => self.class.statisticNames[1],118 :element =>element )124 self.class.statistic_names[0], 125 self.class.statistic_names[1], 126 element ) 119 127 120 128 super … … 156 164 ### break. 157 165 class Bifurcation < FaerieMUD::Entity 166 include FaerieMUD::AbstractClass 167 158 168 contributors :ged 159 169 … … 187 197 188 198 ### Picker method: should return the "current" constituent. 189 abstract:current199 virtual :current 190 200 191 201 … … 296 306 # Make sure none of the aspect-names for the constituents' 297 307 # statistics collide with each other. 298 develAspect, linearAspect = klass.statistic Names308 develAspect, linearAspect = klass.statistic_names 299 309 if statAspects.key?( develAspect ) 300 310 raise FaerieMUD::ConstituentError, … … 335 345 # Gather symbols for the methods we'll be creating 336 346 nameEq = "#{name}=" 337 develAspect, linearAspect = klass.statistic Names347 develAspect, linearAspect = klass.statistic_names 338 348 develAspectEq = "#{develAspect}=".intern 339 349 linearAspectEq = "#{linearAspect}=".intern -
trunk/lib/fm/gameobject.rb
r285 r287 3 3 require 'digest/md5' 4 4 require 'logger' 5 require 'monitor' 5 require 'sync' 6 require 'uuidtools' 6 7 7 8 require 'fm' … … 31 32 # 32 33 class FaerieMUD::GameObject 33 include FaerieMUD::Propertied,34 FaerieMUD::Loggable,35 FaerieMUD::Debuggable,36 FaerieMUD::HTML,37 FaerieMUD::Linguistics38 39 include MonitorMixin34 include Sync_m, 35 FaerieMUD::AbstractClass, 36 FaerieMUD::Propertied, 37 FaerieMUD::Loggable, 38 FaerieMUD::Debuggable, 39 FaerieMUD::HTML, 40 FaerieMUD::Linguistics 40 41 41 42 ### Class constants … … 59 60 60 61 # The authors hash for this class 61 @authors = Hash ::new( 0 )62 @authors = Hash.new( 0 ) 62 63 @contributors = [] 63 64 class << self … … 77 78 def self::add_authors_data 78 79 unless self.instance_variables.include?( "@authors" ) 79 self.instance_variable_set( :@authors, Hash ::new(0) )80 self.instance_variable_set( :@authors, Hash.new(0) ) 80 81 self.instance_variable_set( :@contributors, [] ) 81 82 class << self # :nodoc: … … 100 101 ### stillflame@FaerieMUD.org for the algorithm. 101 102 def self::distribute_points( *people ) 102 points = Array ::new( people.nitems, 1 )103 points = Array.new( people.nitems, 1 ) 103 104 104 105 (PointTotal - people.nitems).times do |n| … … 112 113 end 113 114 114 chart = Hash ::new( 0 )115 chart = Hash.new( 0 ) 115 116 people.each_with_index do |person, i| 116 117 chart[person] += points[i] … … 121 122 122 123 123 ### Make a unique id for the object that can persist across multiple124 ### invocations (i.e., because #object_id won't).125 def self::make_object_id( obj )126 context = Digest::MD5::new127 context << "%d" % obj.object_id <<128 "%0.5f" % Time::now <<129 Thread::current.inspect130 131 return context.hexdigest132 end133 134 135 124 ### Versioning method -- return the subversion rev number for the object. 136 125 def self::rev 137 revno = self ::const_get( :SVNRev )126 revno = self.const_get( :SVNRev ) 138 127 if revno.match( /(\d+)/ ) 139 128 return Integer($1) 140 129 else 141 130 return 0 142 end143 end144 145 146 ### Define a method that will raise a NotImplementedError when147 ### called. This enforces implementation of a particular interface in148 ### subclasses of the defining class.149 def self::abstract( *syms )150 syms.each do |meth|151 define_method( meth ) do152 raise NotImplementedError,153 "No implementation for \#%s provided by %s" %154 [ meth, self.class.name ]155 end156 131 end 157 132 end … … 163 138 ### arguments. 164 139 def self::example( args={} ) 165 self::new( args )140 new( args ) 166 141 end 167 142 … … 179 154 180 155 # Add unique ID, rev, and mutex 181 @id = self.class::make_object_id( self ) 182 @rev = self.class::rev 183 @mutex = Sync::new 156 @id = UUID.timestamp_create 157 @rev = self.class.rev 184 158 185 159 # Call setters for named arguments … … 190 164 191 165 192 ### Copy initializer -- generate a unique id and mutexfor cloned objects.166 ### Copy initializer -- generate a unique id for cloned objects. 193 167 def initialize_copy( original ) 194 168 super 195 @id = self.class::make_object_id( self ) 196 @mutex = Sync::new 197 self.log.debug "Cloned #{self.class.name} #{original.id}." 169 @id = UUID.timestamp_create 198 170 end 199 171 … … 205 177 # The object's unique id 206 178 attr_reader :id 179 alias_method :uuid, :id 207 180 208 181 # The revision number of the class from which the object was instantiated at 209 182 # the time of its creation or last upgrade. 210 183 attr_reader :rev 211 212 # The Sync mutex for this object213 attr_reader :mutex214 184 215 185 … … 225 195 def readlocked 226 196 return unless block_given? 227 @mutex.synchronize( Sync::SH ) do228 yield ( @mutex )197 self.synchronize( Sync::SH ) do 198 yield 229 199 end 230 200 end … … 240 210 def writelocked 241 211 return unless block_given? 242 @mutex.synchronize( Sync::EX ) do243 yield ( @mutex )212 self.synchronize( Sync::EX ) do 213 yield 244 214 end 245 215 end -
trunk/lib/fm/locus.rb
r285 r287 160 160 obj.add_container(self) 161 161 end 162 @mutex.synchronize( Sync::EX ) { @contents |= objects }162 synchronize( Sync::EX ) { @contents |= objects } 163 163 end 164 164 … … 182 182 183 183 # Expand classes into the objects they match 184 @mutex.synchronize( Sync::SH ) do184 synchronize( Sync::SH ) do 185 185 expanded = objects.collect do |obj| 186 186 if obj.is_a?( Class ) … … 195 195 removedObjects = @contents & expanded 196 196 removedObjects.each {|obj| obj.remove_container(self) } 197 @mutex.synchronize( Sync::EX ) { @contents -= removedObjects }197 synchronize( Sync::EX ) { @contents -= removedObjects } 198 198 end 199 199 … … 204 204 ### Add the specified object to the list of the receiver's containers. 205 205 def add_container( object ) 206 @mutex.synchronize( Sync::SH ) {206 synchronize( Sync::SH ) { 207 207 if self.contains?( object ) 208 208 raise FaerieMUD::ContainmentError, … … 210 210 end 211 211 212 @mutex.synchronize( Sync::EX ) {212 synchronize( Sync::EX ) { 213 213 @containers |= [object] 214 214 } … … 220 220 ### containers. 221 221 def remove_container( object ) 222 @mutex.synchronize( Sync::EX ) { @containers -= [object] }222 synchronize( Sync::EX ) { @containers -= [object] } 223 223 end 224 224 -
trunk/lib/fm/mixins.rb
r285 r287 4 4 5 5 require 'fm/exceptions' 6 6 7 7 8 # [<tt>FaerieMUD::Propertied</tt>] … … 74 75 75 76 end # module Loggable 77 78 79 ### Hides your class's ::new method and adds a method generator called 'virtual' for 80 ### defining API methods. If subclasses of your class don't provide implementations of 81 ### "virtual" methods, NotImplementedErrors will be raised if they are called. 82 ### 83 ### # AbstractClass 84 ### class MyBaseClass 85 ### include ThingFish::AbstractClass 86 ### 87 ### # Define a method that will raise a NotImplementedError if called 88 ### virtual :api_method 89 ### end 90 ### 91 module AbstractClass 92 93 ### Methods to be added to including classes 94 module ClassMethods 95 96 ### Define one or more "virtual" methods which will raise 97 ### NotImplementedErrors when called via a concrete subclass. 98 def virtual( *syms ) 99 syms.each do |sym| 100 define_method( sym ) { 101 raise ::NotImplementedError, 102 "%p does not provide an implementation of #%s" % [ self.class, sym ], 103 caller(1) 104 } 105 end 106 end 107 108 109 ### Turn subclasses' new methods back to public. 110 def inherited( subclass ) 111 subclass.module_eval { public_class_method :new } 112 super 113 end 114 115 end # module ClassMethods 116 117 118 extend ClassMethods 119 120 ### Inclusion callback 121 def self::included( mod ) 122 super 123 if mod.respond_to?( :new ) 124 mod.extend( ClassMethods ) 125 mod.module_eval { private_class_method :new } 126 end 127 end 128 129 130 end # module AbstractClass 76 131 77 132 … … 524 579 define_method( sym ) { 525 580 if instance_variables.include?( ivarname ) 526 @mutex = Sync::new unless defined? @mutex 527 @mutex.synchronize( Sync::SH ) { 581 readlocked do 528 582 instance_variable_get( ivarname ) 529 }583 end 530 584 else 531 585 nil … … 537 591 if writer 538 592 define_method( "#{sym}=".intern ) {|arg| 539 @mutex = Sync::new unless defined? @mutex 540 @mutex.synchronize( Sync::EX ) { 593 writelocked do 541 594 instance_variable_set( ivarname, arg ) 542 }595 end 543 596 } 544 597 end … … 568 621 569 622 570 ### A collection of rudimentary functions which are useful for generating 571 ### HTML without a lot of string-catenating, etc. 623 ### Adds the #to_html method to including classes, which can dump the description of an 624 ### object as HTML. 625 ### 626 ### == Customization 627 ### You can customize the HTML generated for your class by overriding one of the following 628 ### protected methods: 629 ### 630 ### #html_object_header, #html_object_footer, #html_ivar_section, #html_wrapper 572 631 module HTML 632 633 require 'fm/linguistics' 634 require 'fm/gameobject' 573 635 574 636 ###### … … 577 639 578 640 ### Return the object as one or more HTML fragments. If +inline+ is 579 ### +true+, the result will be wrapped in a <span>element suitable for580 ### inline display. If it is +false+, it will be wrapped in a <div>641 ### +true+, the result will be wrapped in a SPAN element suitable for 642 ### inline display. If it is +false+, it will be wrapped in a DIV 581 643 ### element for block display. 582 644 def to_html( inline=false ) … … 604 666 return func.call( :class => "object-header" ) { 605 667 [ 606 FaerieMUD::HTML ::span( :class => "object-noun" ) {668 FaerieMUD::HTML.span( :class => "object-noun" ) { 607 669 FaerieMUD::Linguistics::make_noun( self ) 608 670 }, 609 FaerieMUD::HTML ::span( :class => "object-class" ) {671 FaerieMUD::HTML.span( :class => "object-class" ) { 610 672 self.class.name 611 673 }, 612 FaerieMUD::HTML ::span( :class => "object-id" ) {613 "#%s" % self. id674 FaerieMUD::HTML.span( :class => "object-id" ) { 675 "#%s" % self.object_id 614 676 }, 615 FaerieMUD::HTML ::span( :class => "object-class-version" ) {616 "(v%d)" % self.class.rev677 FaerieMUD::HTML.span( :class => "object-class-version" ) { 678 self.class.respond_to?( :rev ) ? "(v%d)" % self.class.rev : '(unknown)' 617 679 }, 618 680 ].join(" ") … … 624 686 def html_object_footer( inline ) 625 687 func = inline ? 626 FaerieMUD::HTML::method(:span) : 627 FaerieMUD::HTML::method(:div) 628 629 return func.call( :class => "object-footer" ) { 630 FaerieMUD::HTML::span( :class => "object-contrib" ) { 688 FaerieMUD::HTML.method(:span) : 689 FaerieMUD::HTML.method(:div) 690 691 contents = [] 692 693 if self.class.respond_to?( :authors ) 694 contents << FaerieMUD::HTML.span( :class => "object-contrib" ) do 631 695 "Authors: " + 632 self.class.authors.collect {|author,points|633 FaerieMUD::HTML ::span(696 self.class.authors.collect do |author,points| 697 FaerieMUD::HTML.span( 634 698 :class => "object-contrib-author" ) {author.to_s} + 635 699 ": " + 636 FaerieMUD::HTML ::span(700 FaerieMUD::HTML.span( 637 701 :class => "object-contrib-points" ) {points.to_s} 638 }.join("; ") 639 } 640 } 641 702 end.join("; ") 703 end 704 end 705 706 if self.class.const_defined?( :SVNId ) 707 contents << FaerieMUD::HTML.span( :class => 'object-svn-id' ) do 708 self.class.const_get(:SVNId) 709 end 710 end 711 712 return func.call( :class => "object-footer" ) { contents } 642 713 end 643 714 …
