2014-01-03 13:32:13 -05:00
{ clone , typedArraySupport } = require ' ./world_utils '
Vector = require ' ./vector '
if typedArraySupport
FloatArrayType = Float32Array # Better performance than Float64Array
bytesPerFloat = FloatArrayType . BYTES_PER_ELEMENT ? FloatArrayType . prototype . BYTES_PER_ELEMENT
else
bytesPerFloat = 4
module.exports = class ThangState
@className: " ThangState "
@trackedPropertyTypes: [
" boolean "
" number "
" string "
" array " # will turn everything into strings
" object " # grrr
" Vector "
" Thang " # serialized as ids, like strings
]
hasRestored: false
constructor: (thang) ->
@props = [ ] # parallel array to @thang's trackedPropertiesKeys/Types
return unless thang
@thang = thang
for prop , propIndex in thang . trackedPropertiesKeys
type = thang . trackedPropertiesTypes [ propIndex ]
value = thang [ prop ]
if type is ' Vector '
@ props . push value ? . copy ( ) # could try storing [x, y, z] or {x, y, z} here instead if this is expensive
2014-05-01 16:23:14 -04:00
else if type is ' object ' or type is ' array '
@ props . push clone ( value , true )
2014-01-03 13:32:13 -05:00
else
@ props . push value
# Either pass storage and type, or don't pass either of them
getStoredProp: (propIndex, type, storage) ->
# Optimize it
unless type
type = @ trackedPropertyTypes [ propIndex ]
storage = @ trackedPropertyValues [ propIndex ]
if type is " Vector "
value = new Vector storage [ 3 * @ frameIndex ] , storage [ 3 * @ frameIndex + 1 ] , storage [ 3 * @ frameIndex + 2 ]
else if type is ' string '
specialKey = storage [ @ frameIndex ]
value = @ specialKeysToValues [ specialKey ]
else if type is ' Thang '
specialKey = storage [ @ frameIndex ]
value = @ thang . world . getThangByID @ specialKeysToValues [ specialKey ]
else if type is ' array '
specialKey = storage [ @ frameIndex ]
2014-03-06 19:32:13 -05:00
valueString = @ specialKeysToValues [ specialKey ]
if valueString and valueString . length > 1
# Trim leading Group Separator and trailing Record Separator, split by Record Separators, restore string array.
value = valueString . substring ( 1 , valueString . length - 1 ) . split ' \x 1E '
else
value = [ ]
2014-01-03 13:32:13 -05:00
else
value = storage [ @ frameIndex ]
value
getStateForProp: (prop) ->
# Get the property, whether we have it stored in @props or in @trackedPropertyValues. Optimize it.
# Figured based on http://jsperf.com/object-vs-array-vs-native-linked-list/13 that it should be faster with small arrays to do the indexOf reads (each up to 24x faster) than to do a single object read, and then we don't have to maintain an extra @props object; just keep array
propIndex = @ trackedPropertyKeys . indexOf prop
return null if propIndex is - 1
value = @ props [ propIndex ]
return value if value isnt undefined or @ hasRestored
return @ props [ propIndex ] = @ getStoredProp propIndex
restore: ->
# Restore trackedProperties' values to @thang, retrieving them from @trackedPropertyValues if needed. Optimize it.
2014-02-17 14:53:52 -05:00
return @ if @ thang . _state is @ and not @ thang . partialState
2014-01-03 13:32:13 -05:00
unless @ hasRestored # Restoring in a deserialized World for first time
props = [ ]
for prop , propIndex in @ trackedPropertyKeys
type = @ trackedPropertyTypes [ propIndex ]
storage = @ trackedPropertyValues [ propIndex ]
props . push ( @ thang [ prop ] = @ getStoredProp propIndex , type , storage )
#console.log @frameIndex, @thang.id, prop, propIndex, type, storage, "got", @thang[prop]
@props = props
@trackedPropertyTypes = @trackedPropertyValues = @specialKeysToValues = null # leave @trackedPropertyKeys for indexing
@hasRestored = true
else # Restoring later times
for prop , propIndex in @ trackedPropertyKeys
@ thang [ prop ] = @ props [ propIndex ]
2014-02-17 14:53:52 -05:00
@thang.partialState = false
@
restorePartial: (ratio) ->
inverse = 1 - ratio
for prop , propIndex in @ trackedPropertyKeys when prop is " pos " or prop is " rotation "
if @ hasRestored
value = @ props [ propIndex ]
else
type = @ trackedPropertyTypes [ propIndex ]
storage = @ trackedPropertyValues [ propIndex ]
value = @ getStoredProp propIndex , type , storage
if prop is " pos "
2014-05-01 13:44:17 -04:00
if @ thang . teleport and @ thang . pos . distanceSquared ( value ) > 900
2014-04-03 19:16:53 -04:00
# Don't interpolate; it was probably a teleport. https://github.com/codecombat/codecombat/issues/738
@thang.pos = value
else
@thang.pos = @ thang . pos . copy ( )
@thang.pos.x = inverse * @ thang . pos . x + ratio * value . x
@thang.pos.y = inverse * @ thang . pos . y + ratio * value . y
@thang.pos.z = inverse * @ thang . pos . z + ratio * value . z
2014-02-17 14:53:52 -05:00
else if prop is " rotation "
@thang.rotation = inverse * @ thang . rotation + ratio * value
@thang.partialState = true
2014-01-03 13:32:13 -05:00
@
serialize: (frameIndex, trackedPropertyIndices, trackedPropertyTypes, trackedPropertyValues, specialValuesToKeys, specialKeysToValues) ->
# Performance hotspot--called once per tracked property per Thang per frame. Optimize the crap out of it.
for type , newPropIndex in trackedPropertyTypes
originalPropIndex = trackedPropertyIndices [ newPropIndex ]
storage = trackedPropertyValues [ newPropIndex ]
value = @ props [ originalPropIndex ]
if value
# undefined, null, false, 0 won't trigger in this serialization code scheme anyway, so we can't differentiate between them when deserializing
if type is " Vector "
storage [ 3 * frameIndex ] = value . x
storage [ 3 * frameIndex + 1 ] = value . y
storage [ 3 * frameIndex + 2 ] = value . z
else if type is ' string '
specialKey = specialValuesToKeys [ value ]
unless specialKey
specialKey = specialKeysToValues . length
specialValuesToKeys [ value ] = specialKey
specialKeysToValues . push value
storage [ frameIndex ] = specialKey
storage [ frameIndex ] = specialKey
else if type is ' Thang '
value = value . id
specialKey = specialValuesToKeys [ value ]
unless specialKey
specialKey = specialKeysToValues . length
specialValuesToKeys [ value ] = specialKey
specialKeysToValues . push value
storage [ frameIndex ] = specialKey
storage [ frameIndex ] = specialKey
else if type is ' array '
2014-03-06 17:53:37 -05:00
# We make sure the array keys won't collide with any string keys by using some unprintable characters.
2014-03-06 19:32:13 -05:00
stringPieces = [ ' \x 1D ' ] # Group Separator
for element in value
2014-05-01 16:23:14 -04:00
if element and element . isThang
element = element . id
2014-03-06 19:32:13 -05:00
stringPieces . push element , ' \x 1E ' # Record Separator(s)
value = stringPieces . join ( ' ' )
2014-01-03 13:32:13 -05:00
specialKey = specialValuesToKeys [ value ]
unless specialKey
specialKey = specialKeysToValues . length
specialValuesToKeys [ value ] = specialKey
specialKeysToValues . push value
storage [ frameIndex ] = specialKey
storage [ frameIndex ] = specialKey
else
storage [ frameIndex ] = value
#console.log @thang.id, "assigned prop", originalPropIndex, newPropIndex, value, type, "at", frameIndex, "to", storage[frameIndex]
null
@deserialize: (world, frameIndex, thang, trackedPropertyKeys, trackedPropertyTypes, trackedPropertyValues, specialKeysToValues) ->
# Optimize like no tomorrow--most performance-sensitive part of the whole app, called once per WorldFrame per Thang per trackedProperty, blocking the UI
ts = new ThangState
ts.thang = thang
ts.frameIndex = frameIndex
ts.trackedPropertyKeys = trackedPropertyKeys
ts.trackedPropertyTypes = trackedPropertyTypes
ts.trackedPropertyValues = trackedPropertyValues
ts.specialKeysToValues = specialKeysToValues
ts
@transferableBytesNeededForType: (type, nFrames) ->
bytes = switch type
when " boolean " then 1
when " number " then bytesPerFloat
when " Vector " then bytesPerFloat * 3
when " string " then 4
when " Thang " then 4 # turn them into strings of their ids
when " array " then 4 # turn them into strings and hope it doesn't explode?
else 0
# We need to be a multiple of bytesPerFloat otherwise bigger-byte array (Float64Array, etc.) offsets won't work
# http://www.kirupa.com/forum/showthread.php?378737-Typed-Arrays-Y-U-No-offset-at-values-other-than-multiples-of-element-size
bytesPerFloat * Math . ceil ( nFrames * bytes / bytesPerFloat )
@createArrayForType: (type, nFrames, buffer, offset) ->
bytes = @ transferableBytesNeededForType type , nFrames
storage = switch type
when " boolean "
new Uint8Array ( buffer , offset , nFrames )
when " number "
new FloatArrayType ( buffer , offset , nFrames )
when " Vector "
new FloatArrayType ( buffer , offset , nFrames * 3 )
when " string "
new Uint32Array ( buffer , offset , nFrames )
when " Thang "
new Uint32Array ( buffer , offset , nFrames )
when " array "
new Uint32Array ( buffer , offset , nFrames )
else
[ ]
[ storage , bytes ]
unless typedArraySupport
# Fall back to normal arrays in IE 9
ThangState.createArrayForType = (type, nFrames, buffer, offset) ->
bytes = @ transferableBytesNeededForType type , nFrames
elementsPerFrame = if type is ' Vector ' then 3 else 1
storage = ( 0 for i in [ 0 . . . nFrames * elementsPerFrame ] )
[ storage , bytes ]