common serialization problems…

It so happened that the two projects im working on these days, all of a sudden needed serialization. Both the cases had massive object graphs, and both the times we faced the same problem of entities involved not being serializable

Proc or lambda is almost second nature to Ruby developers, probably because of all the awesomeness it has got(binding and stuff). Before i played this serialization piece, all my callbacks used to be blocks or procs or lambdas. But since proc is not serializable now they are all messages and receivers. For example…i had to move from….

1
@callback_on_completion && @callback_on_completion.call

to….

1
2
@callback_on_completion.is_a?(Proc) && @callback_on_completion.call
@callback_on_completion.is_a?(Symbol) && @callback_receiver.send(@callback_on_completion)

all over the place….

This may not look like too much trouble….. but if one has to do it in multiple places doing things in multiple ways… it boils down to writing and fixing many more tests which just eats into development time.

Thats not all… if there are object singletons are used, and the object has to be serialized, its gonna fail, because the singleton is not serializable. We had to revert a checkin(which was supposedly a bug fix, and re-implement the whole thing, just because the first solution was dependent on some meta-programming happening on the object singleton).

LESSON(s) LERNT:
Its not a bad thing to use proc and lambda and singleton as such… but it should be done when absolutely needed and only when there is no serialization requirement waiting down the road. I used the message and receiver kind of thing for the event handlers(that used to be blocks a few days back), and it worked absolutely well and it is really neat.

WHAT IF IT CANNOT BE AVOIDED:
Well i had a scenario like that. The object used a Gosu::Window and a loaded a lot of assets(images, sound clips etc.) and non of them were serializable.
I used a lo-fi solution for it, which was to expose a method window=(gosu_window) and using that accessor to load the other assets(like image and sound clips). For example, a typical class now looks like this…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Util::Animator
  def initialize strip_file_name, tile_width, tile_heights, options = {}, &block
    @tile_width, @tile_heights = tile_width, tile_heights
    @strip_file_name = strip_file_name
    options = {:play_both_ways => false, :chunk_slice_width => 1, :run_indefinitly => false}.merge(options)
    @play_both_ways = options[:play_both_ways]
    @callback_on_completion = block || options[:call_on_completion]
    @callback_receiver = options[:callback_receiver]
    @chunk_slice_width = options[:chunk_slice_width]
    @run_indefinitly = options[:run_indefinitly]
  end
 
  def window= window
    @slides = Gosu::Image::load_tiles(window, @strip_file_name, @tile_width, @tile_heights, true)
    create_animated_sequence
    @current_anim_sequence = []
  end
  ...
  ...
  ...
end

I added serialization/de-serialization routines to the unserializables, so that it doesn’t error out, but note that they de-serialize to nil.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
module SerializationDefaulter
  def self.included(base)
    base.extend ClassMethods
  end
 
  def _dump depth
    return ''
  end
 
  module ClassMethods
    def _load serialized_window
      return nil
    end
  end
end
 
Gosu::Window.send(:include, SerializationDefaulter)
Gosu::Image.send(:include, SerializationDefaulter)
Gosu::Sample.send(:include, SerializationDefaulter)
Gosu::Font.send(:include, SerializationDefaulter)

This solved the problem!!! now i have an option to do something like this for instantiation of my objects…

1
2
3
4
5
6
7
8
9
module Buildable
  def build context, window, from = nil, caption = 'Bakery'
    instance = from ? Marshal.load(File.open(from, 'r').read) : new(context)
    instance.window= window
    window.caption = caption
    window.listner = instance
    instance
  end
end

So what it essentially does is, if from(which is a file name) is available, it picks up the serialized object and loads it… and if a file is not given, it instantiates a new one…
But what about the things that came back as nil when we loaded the dump????
Well… the next line(4) takes care of them…. It sets the window irrespective of wether it is a new object or a loaded one….

But what about the other items(like images)… didn’t they get loaded as nil???
Indeed… thats exactly what the Util::Animator window= method handles. It loads back all the images/soundclips/tiles/fonts… everything that needs a window to load itself.

Problem solved!!!

About these ads

About this entry