Jump To …
READMElib / psd / blend_mode.coffeelib / psd / channel_image.coffeelib / psd / color.coffeelib / psd / descriptor.coffeelib / psd / file.coffeelib / psd / header.coffeelib / psd / image.coffeelib / psd / image_export.coffeelib / psd / image_exports / png.coffeelib / psd / image_format.coffeelib / psd / image_formats / layer_raw.coffeelib / psd / image_formats / layer_rle.coffeelib / psd / image_formats / raw.coffeelib / psd / image_formats / rle.coffeelib / psd / image_mode.coffeelib / psd / image_modes / cmyk.coffeelib / psd / image_modes / greyscale.coffeelib / psd / image_modes / rgb.coffeelib / psd / init.coffeelib / psd / layer / blend_modes.coffeelib / psd / layer / blending_ranges.coffeelib / psd / layer / channel_image.coffeelib / psd / layer / helpers.coffeelib / psd / layer / info.coffeelib / psd / layer / mask.coffeelib / psd / layer / name.coffeelib / psd / layer / position_channels.coffeelib / psd / layer.coffeelib / psd / layer_info / blend_clipping_elements.coffeelib / psd / layer_info / blend_interior_elements.coffeelib / psd / layer_info / fill_opacity.coffeelib / psd / layer_info / gradient_fill.coffeelib / psd / layer_info / layer_id.coffeelib / psd / layer_info / layer_name_source.coffeelib / psd / layer_info / legacy_typetool.coffeelib / psd / layer_info / locked.coffeelib / psd / layer_info / metadata.coffeelib / psd / layer_info / nested_section_divider.coffeelib / psd / layer_info / object_effects.coffeelib / psd / layer_info / section_divider.coffeelib / psd / layer_info / solid_color.coffeelib / psd / layer_info / typetool.coffeelib / psd / layer_info / unicode_name.coffeelib / psd / layer_info / vector_mask.coffeelib / psd / layer_info / vector_origination.coffeelib / psd / layer_info / vector_stroke.coffeelib / psd / layer_info / vector_stroke_content.coffeelib / psd / layer_info.coffeelib / psd / layer_mask.coffeelib / psd / lazy_execute.coffeelib / psd / mask.coffeelib / psd / node.coffeelib / psd / nodes / ancestry.coffeelib / psd / nodes / build_preview.coffeelib / psd / nodes / group.coffeelib / psd / nodes / layer.coffeelib / psd / nodes / root.coffeelib / psd / nodes / search.coffeelib / psd / path_record.coffeelib / psd / resource.coffeelib / psd / resource_section.coffeelib / psd / resources / layer_comps.coffeelib / psd / resources.coffeelib / psd / util.coffeelib / psd.coffeeshims / init.coffeeshims / png.coffee

lazy_execute.coffee

lib/psd/

LazyExecute is very important when it comes to speed. Because some PSD documents can be extremely large and hold a LOT of data, we can significantly speed up parsing by temporarily skipping over chunks of the PSD. Chances are that you aren't going to use every piece of data in the document. This means, when you do request some data that's proxied through LazyExecute, we can parse it on the fly. This overhead should be incredibly minimal.

While not as elegant as the PSD.rb counterpart, it gets the job done. We look at the object we need to proxy, and define proxies for every item on its prototype on this one. The proxy checks to see if the object has been loaded first before passing on the call to the object.

If the object has not been loaded yet, we record our current position in the file, jump to the known start position of the data, parse it by calling a set method on the object, jump back to the original position in the file, and then call the proxied property.

Example

obj = new SomeObject()
data = new LazyExecute(obj, file)
  .now('skip')
  .later('parse')
  .ignore('foo', 'bar')
  .get()
module.exports = class LazyExecute
  constructor: (@obj, @file) ->
    @startPos = @file.tell()
    @loaded = false
    @loadMethod = null
    @loadArgs = []
    @passthru = []

This describes the method that we want to run at object instantiation. Typically this will skip over the data that we will parse on-demand later. We can pass any arguments we want to the method as well.

  now: (method, args...) ->
    @obj[method].apply(@obj, args)
    return @

Here we describe the method we want to run when the first method/property on the object is accessed. We can also define any arguments that need to be passed to the function.

  later: (method, args...) ->
    @loadMethod = method
    @loadArgs = args
    return @

Sometimes we don't have to parse the data in order to get some important information. For example, we can get the widht/height from the full preview image without parsing the image itself, since that data comes from the header. Purely convenience, but helps to optimize usage.

The arguments are a list of method/property names we don't want to trigger on-demand parsing.

  ignore: (args...) ->
    @passthru.concat args
    return @

This is called once all of the paramters of the proxy have been set up, i.e. now, later, and skip. This defines all items on the proxied objects prototype on this object, and checks to make sure the proxied object has been loaded before passing on the call.

  get: ->
    for key, val of @obj then do (key, val) =>
      return if @[key]?
      Object.defineProperty @, key,
        get: ->
          @load() if not @loaded and not (key in @passthru)
          @obj[key]

    @

If we are accessing a property for the first time, then this will call the load method, which was defined during setup with later(). The steps this performs are:

  1. Records the current file position.
  2. Jumps to the recorded start position for the proxied data.
  3. Calls the load method, which was defined with later().
  4. Jumps back to the original file position.
  5. Sets the @loaded flag to true so we know this object has been parsed.
  load: ->
    origPos = @file.tell()
    @file.seek @startPos

    @obj[@loadMethod].apply(@obj, @loadArgs)

    @file.seek origPos
    @loaded = true

generated Tue May 12 2015 11:08:14 GMT-0400 (EDT)