#include // detect std::lib #include #include _hash_map> #include #ifdef _LIBCPP_VERSION // https://bugs.launchpad.net/libmemcached/+bug/1216521/comments/2 #include using std::hash; // llvm gets here [mac] #elif defined(HAVE_TR1_FUNCTIONAL) #include using std::tr1::hash; #else // punt! older gcc's using __gnu_cxx::hash; #endif // much code stolen shamelessly from lourens' cb gem... using google::<%= type %>_hash_map; // namespace where class lives by default <% if OS.posix? %> #include // ?? <% end %> extern "C" { // some helpers <% # not yet used... if false %> struct eqstr { bool operator()(const char* s1, const char* s2) const { return (s1 == s2) || (s1 && s2 && strcmp(s1, s2) == 0); } }; struct eqint { inline bool operator()(int s1, int s2) const { return s1 == s2; } }; <% end %> static ID id_eql, id_hash; <% if key_type == 'double' %> struct eqdouble { bool operator()(const double &f1, const double &f2) const { return f1 == f2; } }; extern hash inthash; struct hashdouble { size_t operator ()(const double& foo) const { return inthash((int)foo); } }; <% end %> <% if key_type == 'VALUE' %> // static hash H; // unused currently... // using it is like hash struct eqrb { bool operator()(const VALUE s1, const VALUE s2) const { // speeds up populate int 18/11 // slows down string 21/22 // ltodo if(s1 == s2) { return true; } // this line from object.c's rb_eql // lookup 0.278 -> 0.26 return RTEST(rb_funcall(s1, id_eql, 1, s2)); } }; #ifndef RBIGNUM_DIGITS # ifdef RBIGNUM /* 1.8 'ish */ # define RBIGNUM_DIGITS(a) RBIGNUM(a)->digits # else /* 2.2.0 has no way to do this? */ # endif #endif struct hashrb { size_t operator()(VALUE hash_me) const { // stolen from hash.c populate -> 0.64 0.625 // hmm // use our own custom hash function for well known types // to avoid a function call // this speeds up ints 29/44 // and speeds up string 26/22 // though I suppose we could switch (TYPE(hash_me)) { // ltodo does this help? // if so more types? case T_FIXNUM: case T_FLOAT: case T_SYMBOL: // ltodo return hash_me; // we don't actually do bignums yet, though rb_big2int might help here... // case T_BIGNUM: // return LONG2FIX(((long*)(RBIGNUM_DIGITS(hash_me)))[0]); // its first digit...I'm thinkin' // not sure if this is faster or not... //case T_STRING: //return H(StringValueCStr(hash_me)); // populate/lookup 0.26 -> 0.23 [core is 0.16 somehow] // perhaps they cache? //return H(RSTRING_PTR(hash_me)); // 0.23 -> -.22 } VALUE hval = rb_funcall(hash_me, id_hash, 0); retry: switch (TYPE(hval)) { case T_FIXNUM: return hval; //??case T_BIGNUM: //?? return LONG2FIX(((long*)(RBIGNUM_DIGITS(hval)))[0]); default: hval = rb_to_int(hval); goto retry; } } }; <% end %> typedef struct { <%= type %>_hash_map< <%= key_type %>, <%= value_type %> <%= extra_hash_params %> > *hash_map; } RCallback; static void mark_hash_map_values(RCallback *incoming) { <% if value_type == 'VALUE' || key_type == 'VALUE' %> // only need to iterate if we store *real* Ruby values for(<%= type %>_hash_map< <%= key_type %>, <%= value_type %> <%= extra_hash_params %> >::iterator it = incoming->hash_map->begin(); it != incoming->hash_map->end(); ++it) { <% if value_type == 'VALUE' %> rb_gc_mark(it->second); <% end %> <% if key_type == 'VALUE' %> rb_gc_mark(it->first); <% end %> } <% end %> } static void free_hash_callback(RCallback* cb) { delete cb->hash_map; // I had this line commented out once? huh? } static VALUE callback_alloc( VALUE klass ) { RCallback* cbs; VALUE current_instance = Data_Make_Struct(klass, RCallback, mark_hash_map_values, free_hash_callback, cbs); // XXXX the last parameter is just a pointer? huh? cbs->hash_map = new <%= type %>_hash_map< <%= key_type %>, <%= value_type %> <%= extra_hash_params %> >(); <% if type == 'dense' %> // needs both empty key and deleted keys [and different] for deletes... cbs->hash_map->set_empty_key(<%= unreachable_key %>); // in th eory could also call set_deleted_key "anytime" ... cbs->hash_map->set_deleted_key((<%= unreachable_key %>)-1); // hope this is also typically unreachable from ruby land [?] <% elsif type == 'sparse' %> cbs->hash_map->set_deleted_key(<%= unreachable_key %>); <% end %> return current_instance; } #define GetCallbackStruct(obj) (Check_Type(obj, T_DATA), (RCallback*)DATA_PTR(obj)) static VALUE rb_mri_hash_new(VALUE freshly_created) { // we don't actually have anything special to do here... // unless someone subclassed us or something [?] // XXXX test return freshly_created; } static VALUE rb_ghash_set(VALUE cb, VALUE set_this, VALUE to_this) { <% if assert_key_type %> if(!(TYPE(set_this) == <%= assert_key_type %>)) { <%= "if(!(TYPE(set_this) == #{assert_key_type2}))" if assert_key_type2 %> rb_raise(rb_eTypeError, "not valid key (expected <%= assert_key_type %>)"); } <% end %> <% if assert_value_type %> if(!(TYPE(to_this) == <%= assert_value_type %>)) { <%= "if(!(TYPE(to_this) == #{assert_value_type2}))" if assert_value_type2 %> rb_raise(rb_eTypeError, "not valid value <%=assert_value_type%>"); } <% end %> <%= options[:extra_set_code] %> <%= options[:extra_set_code2] %> RCallback* cbs = GetCallbackStruct(cb); (*cbs->hash_map)[ <%= convert_keys_from_ruby %>(set_this)] = <%= convert_values_from_ruby %>(to_this); return to_this; // ltodo test that it returns value... } static VALUE rb_ghash_get(VALUE cb, VALUE get_this, int just_check_for_presence, int delete_it) { // TODO optionally not type check assert anymore [if it slows down computationally, that is...] <%= options[:extra_get_code] %> <% if assert_key_type %> if(!(TYPE(get_this) == <%= assert_key_type %>)) { <%= "if(!(TYPE(get_this) == #{assert_key_type2}))" if assert_key_type2 %> rb_raise(rb_eTypeError, "not valid get key (expected <%=assert_key_type%>)"); } <% end %> RCallback* cbs = GetCallbackStruct(cb); <%= type %>_hash_map< <%= key_type %>, <%= value_type %> <%= extra_hash_params %> >::iterator out = cbs->hash_map->find(<%= convert_keys_from_ruby %>(get_this)); if(out == cbs->hash_map->end()) { // key not found in hashmap if(just_check_for_presence) return Qfalse; else { // key not found, or delete requested and key not found, return nil return Qnil; } } else { if(just_check_for_presence) return Qtrue; else { VALUE out2 = <%= convert_values_to_ruby %>(out->second); if(delete_it) { cbs->hash_map->erase(out); // still return it } return out2; } } } static VALUE rb_ghash_get_value(VALUE cb, VALUE get_this) { return rb_ghash_get(cb, get_this, 0, 0); } static VALUE rb_ghash_get_present(VALUE cb, VALUE get_this) { return rb_ghash_get(cb, get_this, 1, 0); } static VALUE rb_ghash_delete(VALUE cb, VALUE delete_this) { return rb_ghash_get(cb, delete_this, 0, 1); } static VALUE rb_ghash_size(VALUE cb) { RCallback* incoming = GetCallbackStruct(cb); return INT2FIX(incoming->hash_map->size()); } static VALUE rb_ghash_clear(VALUE cb) { RCallback* incoming = GetCallbackStruct(cb); incoming->hash_map->clear(); return cb; } static VALUE rb_ghash_each(VALUE cb) { RCallback* incoming = GetCallbackStruct(cb); for(<%= type %>_hash_map< <%= key_type %>, <%= value_type %> <%= extra_hash_params %> >::iterator it = incoming->hash_map->begin(); it != incoming->hash_map->end(); ++it) { rb_yield_values(2, <%= convert_keys_to_ruby %>(it->first), <%= convert_values_to_ruby %>(it->second)); } return cb; } static VALUE rb_ghash_values(VALUE cb) { RCallback* incoming = GetCallbackStruct(cb); VALUE out = rb_ary_new2(incoming->hash_map->size()); for(<%= type %>_hash_map< <%= key_type %>, <%= value_type %> <%= extra_hash_params %> >::iterator it = incoming->hash_map->begin(); it != incoming->hash_map->end(); ++it) { rb_ary_push(out, <%= convert_values_to_ruby %>(it->second)); } return out; } static VALUE rb_ghash_keys(VALUE cb) { RCallback* incoming = GetCallbackStruct(cb); VALUE out = rb_ary_new2(incoming->hash_map->size()); for(<%= type %>_hash_map< <%= key_type %>, <%= value_type %> <%= extra_hash_params %> >::iterator it = incoming->hash_map->begin(); it != incoming->hash_map->end(); ++it) { rb_ary_push(out, <%= convert_keys_to_ruby %>(it->first)); } return out; } // only does yields (blocks) for now :) static VALUE rb_ghash_combination_2(VALUE cb) { RCallback* incoming = GetCallbackStruct(cb); for(<%= type %>_hash_map< <%= key_type %>, <%= value_type %> <%= extra_hash_params %> >::iterator it = incoming->hash_map->begin(); it != incoming->hash_map->end(); ++it) { <%= type %>_hash_map< <%= key_type %>, <%= value_type %> <%= extra_hash_params %> >::iterator next = it; ++next; // advance it while(next != incoming->hash_map->end()) { rb_yield_values(2, <%= convert_keys_to_ruby %>(it->first), <%= convert_keys_to_ruby %>(next->first)); ++next; } } return cb; } void init_<%= type %>_<%= english_key_type %>_to_<%= english_value_type %>() { VALUE rb_cGoogleHashLocal; <% class_name = "GoogleHash#{type.capitalize + english_key_type.capitalize}To#{english_value_type.capitalize}" %> rb_cGoogleHashLocal = rb_define_class("<%= class_name %>", rb_cObject); rb_define_alloc_func(rb_cGoogleHashLocal, callback_alloc); // I guess it calls this for us, pre initialize... rb_define_method(rb_cGoogleHashLocal, "initialize", RUBY_METHOD_FUNC(rb_mri_hash_new), 0); rb_define_method(rb_cGoogleHashLocal, "[]=", RUBY_METHOD_FUNC(rb_ghash_set), 2); rb_define_method(rb_cGoogleHashLocal, "[]", RUBY_METHOD_FUNC(rb_ghash_get_value), 1); rb_define_method(rb_cGoogleHashLocal, "each", RUBY_METHOD_FUNC(rb_ghash_each), 0); rb_define_method(rb_cGoogleHashLocal, "values", RUBY_METHOD_FUNC(rb_ghash_values), 0); rb_define_method(rb_cGoogleHashLocal, "keys", RUBY_METHOD_FUNC(rb_ghash_keys), 0); rb_define_method(rb_cGoogleHashLocal, "has_key?", RUBY_METHOD_FUNC(rb_ghash_get_present), 1); rb_define_method(rb_cGoogleHashLocal, "delete", RUBY_METHOD_FUNC(rb_ghash_delete), 1); rb_define_method(rb_cGoogleHashLocal, "clear", RUBY_METHOD_FUNC(rb_ghash_clear), 0); rb_define_method(rb_cGoogleHashLocal, "key?", RUBY_METHOD_FUNC(rb_ghash_get_present), 1); rb_define_method(rb_cGoogleHashLocal, "member?", RUBY_METHOD_FUNC(rb_ghash_get_present), 1); rb_define_method(rb_cGoogleHashLocal, "include?", RUBY_METHOD_FUNC(rb_ghash_get_present), 1); rb_define_method(rb_cGoogleHashLocal, "keys_combination_2", RUBY_METHOD_FUNC(rb_ghash_combination_2), 0); rb_define_method(rb_cGoogleHashLocal, "length", RUBY_METHOD_FUNC(rb_ghash_size), 0); rb_define_method(rb_cGoogleHashLocal, "size", RUBY_METHOD_FUNC(rb_ghash_size), 0); // add a higher quality #inspect... rb_eval_string("class <%= class_name %>;def inspect;all = '<%= class_name %> {'; join = []; self.each{|k, v| join << (k.to_s + '=>' + v.to_s)}; all << join.join(',') << '}'; end; end"); id_eql = rb_intern("eql?"); id_hash = rb_intern("hash"); } }