import { upperFirst } from 'lodash' ; import { applyTagEdit , edit , isTagEdit , mergeEdits , remoteEdit , TagEditOp , } from '../aux-format-2' ; import { BotSpace , BotTags , Bot , ScriptTags , PrecalculatedBot , BOT_SPACE_TAG , getBotSpace , createPrecalculatedBot , BotSignatures , BotTagMasks , getTag , getTagMaskSpaces , hasValue , DEFAULT_TAG_MASK_SPACE , TAG_MASK_SPACE_PRIORITIES_REVERSE , TAG_MASK_SPACE_PRIORITIES , RuntimeBot , CLEAR_CHANGES_SYMBOL , SET_TAG_MASK_SYMBOL , CLEAR_TAG_MASKS_SYMBOL , CompiledBotListener , EDIT_TAG_SYMBOL , EDIT_TAG_MASK_SYMBOL , getOriginalObject , GET_TAG_MASKS_SYMBOL , RuntimeBotLinks , } from '../bots' ; import { createBotLink , isBot , isBotLink , ORIGINAL_OBJECT , } from '../bots/BotCalculations' ; import { CompiledBot } from './CompiledBot' ; import { RuntimeStateVersion } from './RuntimeStateVersion' ; /**
 * Defines an interface that contains runtime bots state.
 * That is, a map of bot IDs to the runtime bot instances.
 */ export interface RuntimeBotsState { [ id ] : RuntimeBot ; } param masks export function flattenTagMasks ( masks ) { let result = { } as BotTags ; if ( masks ) { for ( let space of TAG_MASK_SPACE_PRIORITIES_REVERSE ) { if ( ! ! masks [ space ] ) { Object . assign ( result , masks [ space ] ) ; } } } return result ; } param bot param manager param context export function createRuntimeBot ( bot , manager ) { if ( ! bot ) { return null ; } const constantTags = { id : bot . id , space : getBotSpace ( bot ) , } ; let changedRawTags = { } ; let rawTags = < ScriptTags > { ... bot . tags , } ; let rawMasks = flattenTagMasks ( bot . masks || { } ) ; let rawLinks = { } ; let changedMasks = { } ; const arrayModifyMethods = new Set ( [ 'push' , 'shift' , 'unshift' , 'pop' , 'splice' , 'fill' , 'sort' , ] ) ; const wrapValue = ( tag , value ) => { if ( Array . isArray ( value ) ) { const isTagValue = ( ) => value === manager . getRawValue ( bot , tag ) ; const isMaskValue = ( ) => value === manager . getTagMask ( bot , tag ) ; const proxy = new Proxy ( value , { get ( target , key , proxy ) { if ( arrayModifyMethods . has ( key ) ) { const func = Reflect . get ( target , key , proxy ) ; return function ( ) { const ret = func . apply ( this , arguments ) ; if ( isMaskValue ( ) ) { updateTagMask ( tag , value ) ; } if ( isTagValue ( ) ) { updateTag ( tag , value ) ; } return ret ; } ; } return Reflect . get ( target , key , proxy ) ; } , set ( target , key , proxy ) { const ret = Reflect . set ( target , key , proxy ) ; if ( isMaskValue ( ) ) { updateTagMask ( tag , value ) ; } if ( isTagValue ( ) ) { updateTag ( tag , value ) ; } return ret ; } , } ) ; Object . defineProperty ( proxy , ORIGINAL_OBJECT , { configurable : true , enumerable : false , writable : false , value : value , } ) ; return proxy ; } return value ; } ; const tagsProxy = new Proxy ( rawTags , { get ( target , key , proxy ) { if ( key === 'toJSON' ) { return Reflect . get ( target , key , proxy ) ; } else if ( key in constantTags ) { return constantTags [ < keyof typeof constantTags > key ] ; } return wrapValue ( key , manager . getValue ( bot , key ) ) ; } , set ( target , key , value , receiver ) { if ( key in constantTags ) { return true ; } updateTag ( key , getOriginalObject ( value ) ) ; return true ; } , deleteProperty ( target , key ) { if ( key in constantTags ) { return true ; } const value = null as any ; updateTag ( key , value ) ; return true ; } , ownKeys ( target ) { const keys = Object . keys ( bot . values ) ; return keys ; } , getOwnPropertyDescriptor ( target , property ) { if ( property === 'toJSON' ) { return Reflect . getOwnPropertyDescriptor ( target , property ) ; } return Reflect . getOwnPropertyDescriptor ( bot . values , property ) ; } , } ) ; const rawProxy = new Proxy ( rawTags , { get ( target , key , proxy ) { if ( key in constantTags ) { return constantTags [ < keyof typeof constantTags > key ] ; } return manager . getRawValue ( bot , key ) ; } , set ( target , key , value , receiver ) { if ( key in constantTags ) { return true ; } updateTag ( key , getOriginalObject ( value ) ) ; return true ; } , deleteProperty ( target , key ) { if ( key in constantTags ) { return true ; } const value = null as any ; updateTag ( key , value ) ; return true ; } , ownKeys ( target ) { const keys = Object . keys ( bot . tags ) ; return keys ; } , getOwnPropertyDescriptor ( target , property ) { if ( property === 'toJSON' ) { return Reflect . getOwnPropertyDescriptor ( target , property ) ; } return Reflect . getOwnPropertyDescriptor ( bot . tags , property ) ; } , } ) ; const listenersProxy = new Proxy ( bot . listeners , { get ( target , key , proxy ) { if ( key in constantTags ) { return null ; } return manager . getListener ( bot , key ) ; } , } ) ; const signaturesProxy = new Proxy ( bot . signatures || { } , { get ( target , key , proxy ) { if ( key in constantTags ) { return constantTags [ < keyof typeof constantTags > key ] ; } return manager . getSignature ( bot , key ) ; } , set ( target , key , proxy ) { return true ; } , deleteProperty ( target , key ) { return true ; } , } ) ; const maskProxy = new Proxy ( rawMasks , { get ( target , key , proxy ) { return wrapValue ( key , manager . getTagMask ( bot , key ) ) ; } , set ( target , key , value , proxy ) { if ( key in constantTags ) { return true ; } updateTagMask ( key , getOriginalObject ( value ) ) ; return true ; } , deleteProperty ( target , key ) { if ( key in constantTags ) { return true ; } const spaces = getTagMaskSpaces ( bot , key ) ; const mode = manager . updateTagMask ( bot , key , spaces , null ) ; if ( mode === RealtimeEditMode . Immediate ) { delete rawMasks [ key ] ; } changeTagMask ( key , null , spaces ) ; return true ; } , ownKeys ( target ) { const keys = Object . keys ( flattenTagMasks ( bot . masks ) ) ; return keys ; } , getOwnPropertyDescriptor ( target , property ) { if ( property === 'toJSON' ) { return Reflect . getOwnPropertyDescriptor ( target , property ) ; } const flat = flattenTagMasks ( bot . masks ) ; return Reflect . getOwnPropertyDescriptor ( flat , property ) ; } , } ) ; const linkProxy = new Proxy ( rawLinks , { set ( target , key , value , proxy ) { if ( key in constantTags ) { return true ; } if ( isBot ( value ) ) { updateTag ( key , createBotLink ( [ value . id ] ) ) ; } else if ( Array . isArray ( value ) ) { const tagValue = value . map ( ( v ) => ( isBot ( v ) ? v . id : null ) ) ; updateTag ( key , createBotLink ( tagValue ) ) ; } else if ( isBotLink ( value ) ) { updateTag ( key , value ) ; } else if ( typeof value === 'string' ) { updateTag ( key , createBotLink ( [ value ] ) ) ; } else if ( ! hasValue ( value ) && isBotLink ( manager . getValue ( bot , key ) ) ) { updateTag ( key , value ) ; } return true ; } , get ( target , key , proxy ) { if ( key === 'toJSON' ) { return Reflect . get ( target , key , proxy ) ; } else if ( key in constantTags ) { return undefined ; } return manager . getTagLink ( bot , key ) ; } , ownKeys ( target ) { const keys = Object . keys ( bot . values ) ; return keys . filter ( ( key ) => { return isBotLink ( manager . getValue ( bot , key ) ) ; } ) ; } , deleteProperty ( target , key ) { if ( key in constantTags ) { return true ; } if ( isBotLink ( manager . getValue ( bot , key ) ) ) { const value = null as any ; updateTag ( key , value ) ; } return true ; } , getOwnPropertyDescriptor ( target , property ) { if ( property === 'toJSON' ) { return Reflect . getOwnPropertyDescriptor ( target , property ) ; } if ( isBotLink ( manager . getValue ( bot , property ) ) ) { return Reflect . getOwnPropertyDescriptor ( bot . values , property ) ; } return undefined ; } , } ) ; Object . defineProperty ( tagsProxy , 'toJSON' , { value : ( ) => bot . tags , writable : false , enumerable : false , configurable : true , } ) ; Object . defineProperty ( linkProxy , 'toJSON' , { value : ( ) => { const linkKeys = Object . keys ( linkProxy ) ; let result = { } as any ; for ( let key of linkKeys ) { result [ key ] = manager . getValue ( bot , key ) ; } return result ; } , writable : false , enumerable : false , configurable : true , } ) ; let script = { id : bot . id , link : createBotLink ( [ bot . id ] ) , tags : tagsProxy , raw : rawProxy , masks : maskProxy , links : linkProxy , vars : { } , changes : changedRawTags , maskChanges : changedMasks , listeners : listenersProxy , signatures : signaturesProxy , [ CLEAR_CHANGES_SYMBOL ] : null , [ SET_TAG_MASK_SYMBOL ] : null , [ GET_TAG_MASKS_SYMBOL ] : null , [ CLEAR_TAG_MASKS_SYMBOL ] : null , [ EDIT_TAG_SYMBOL ] : null , [ EDIT_TAG_MASK_SYMBOL ] : null , } ; Object . defineProperty ( script , CLEAR_CHANGES_SYMBOL , { value : ( ) => { changedRawTags = { } ; changedMasks = { } ; script . changes = changedRawTags ; script . maskChanges = changedMasks ; } , configurable : false , enumerable : false , writable : false , } ) ; Object . defineProperty ( script , SET_TAG_MASK_SYMBOL , { value : ( key , value , space ) => { if ( key in constantTags ) { return true ; } const spaces = ! hasValue ( space ) ? hasValue ( value ) ? [ DEFAULT_TAG_MASK_SPACE ] : getTagMaskSpaces ( bot , key ) : [ space ] ; const valueToSet = getOriginalObject ( value ) ; const mode = manager . updateTagMask ( bot , key , spaces , valueToSet ) ; if ( mode === RealtimeEditMode . Immediate ) { rawMasks [ key ] = valueToSet ; } changeTagMask ( key , valueToSet , spaces ) ; return value ; } , configurable : false , enumerable : false , writable : false , } ) ; Object . defineProperty ( script , GET_TAG_MASKS_SYMBOL , { value : ( ) => { let masks = { } as BotTagMasks ; if ( bot . masks ) { for ( let space in bot . masks ) { let spaceMasks = { } as BotTags ; let hasSpaceMasks = false ; const botMasks = bot . masks [ space ] ; for ( let tag in botMasks ) { const val = botMasks [ tag ] ; if ( hasValue ( val ) ) { hasSpaceMasks = true ; spaceMasks [ tag ] = val ; } } if ( hasSpaceMasks ) { masks [ space ] = spaceMasks ; } } } return masks ; } , configurable : false , enumerable : false , writable : false , } ) ; Object . defineProperty ( script , CLEAR_TAG_MASKS_SYMBOL , { value : ( space ) => { if ( bot . masks ) { let spaces = hasValue ( space ) ? [ space ] : TAG_MASK_SPACE_PRIORITIES ; for ( let space of spaces ) { const tags = bot . masks [ space ] ; for ( let tag in tags ) { script [ SET_TAG_MASK_SYMBOL ] ( tag , null , space ) ; } } } } , configurable : false , enumerable : false , writable : false , } ) ; Object . defineProperty ( script , EDIT_TAG_SYMBOL , { value : ( tag , ops ) => { if ( tag in constantTags ) { return ; } const e = remoteEdit ( manager . currentVersion . vector , ... ops ) ; script . tags [ tag ] = e ; } , configurable : false , enumerable : false , writable : false , } ) ; Object . defineProperty ( script , EDIT_TAG_MASK_SYMBOL , { value : ( tag , ops , space ? ) => { if ( tag in constantTags ) { return ; } const e = remoteEdit ( manager . currentVersion . vector , ... ops ) ; if ( ! hasValue ( space ) ) { const availableSpaces = getTagMaskSpaces ( bot , tag ) ; if ( availableSpaces . length <= 0 ) { space = DEFAULT_TAG_MASK_SPACE ; } else { for ( let possibleSpace of TAG_MASK_SPACE_PRIORITIES ) { if ( availableSpaces . indexOf ( possibleSpace ) >= 0 ) { space = possibleSpace ; break ; } } } } script [ SET_TAG_MASK_SYMBOL ] ( tag , e , space ) ; } , configurable : false , enumerable : false , writable : false , } ) ; Object . defineProperty ( script , 'toJSON' , { value : ( ) => { if ( 'space' in bot ) { return { id : bot . id , space : bot . space , tags : tagsProxy , } ; } else { return { id : bot . id , tags : tagsProxy , } ; } } , writable : false , enumerable : false , configurable : true , } ) ; if ( BOT_SPACE_TAG in bot ) { script . space = bot . space ; } const scriptProxy = new Proxy ( script , { get ( target , prop , reciever ) { if ( prop in target ) { return Reflect . get ( target , prop , reciever ) ; } else if ( typeof prop === 'string' ) { const listener = manager . getListener ( bot , prop ) ; if ( listener ) { return listener ; } } return undefined ; } , } ) ; return scriptProxy ; function updateTag ( tag , value ) { const mode = manager . updateTag ( bot , tag , value ) ; if ( mode === RealtimeEditMode . Immediate ) { rawTags [ tag ] = value ; changeTag ( tag , value ) ; } else if ( mode === RealtimeEditMode . Delayed ) { changeTag ( tag , value ) ; } } function updateTagMask ( tag , value ) { const spaces = hasValue ( value ) ? [ DEFAULT_TAG_MASK_SPACE ] : getTagMaskSpaces ( bot , tag ) ; const mode = manager . updateTagMask ( bot , tag , spaces , value ) ; if ( mode === RealtimeEditMode . Immediate ) { rawMasks [ tag ] = value ; } changeTagMask ( tag , value , spaces ) ; } function changeTag ( tag , value ) { if ( isTagEdit ( value ) ) { const currentValue = changedRawTags [ tag ] ; if ( isTagEdit ( currentValue ) ) { value = mergeEdits ( currentValue , value ) ; } else if ( hasValue ( currentValue ) ) { value = applyTagEdit ( currentValue , value ) ; } } changedRawTags [ tag ] = value ; } function changeTagMask ( tag , value , spaces ) { for ( let space of spaces ) { if ( ! changedMasks [ space ] ) { changedMasks [ space ] = { } ; } if ( isTagEdit ( value ) ) { const currentValue = changedMasks [ space ] [ tag ] ; if ( isTagEdit ( currentValue ) ) { value = mergeEdits ( currentValue , value ) ; } else if ( hasValue ( currentValue ) ) { value = applyTagEdit ( currentValue , value ) ; } } changedMasks [ space ] [ tag ] = value ; } } } /**
 * Defines an interface for an object that provides the API that script bots use for housekeeping.
 * T is the type of bots that the generated script bots are representing.
 */ export interface RuntimeBotInterface extends RuntimeBatcher { param bot param tag param newValue updateTag ( bot , tag , newValue ) : RealtimeEditMode ; param bot param tag param space param value updateTagMask ( bot , tag , spaces , value ) : RealtimeEditMode ; param bot param tag getValue ( bot , tag ) : any ; param bot param tag getRawValue ( bot , tag ) : any ; param bot param tag param space getTagMask ( bot , tag ) : any ; param bot param tag getTagLink ( bot , tag ) : RuntimeBot | RuntimeBot [ ] ; param bot param tag getListener ( bot , tag ) : CompiledBotListener ; param bot param signature getSignature ( bot , signature ) : string ; /**
     * Gets the current version that the interface is at.
     */ currentVersion : RuntimeStateVersion ; } /**
 * Defines an interface for an object that is able to manage the creation and destruction of script bots in the runtime.
 */ export interface RuntimeBotFactory { param bot createRuntimeBot ( bot ) : RuntimeBot ; param bot destroyScriptBot ( bot ) : RealtimeEditMode ; } /**
 * Defines an interface for an object that is able to batch script results.
 */ export interface RuntimeBatcher { /**
     * Notifies the batcher that a change has happened and that it should schedule
     * a handler to grab the changes and apply them.
     */ notifyChange ( ) : void ; } /**
 * The list of possible realtime edit modes.
 */ export enum RealtimeEditMode { /**
     * Specifies that bots in this edit mode cannot be edited.
     */ None = 0 , /**
     * Specifies that all changes to the bot will be accepted.
     * This allows the changes to be immediately used.
     */ Immediate = 1 , /**
     * Specifies that some changes to the bot may be rejected.
     * This requires that changes be delayed until the related
     * partition accepts/denies them.
     */ Delayed = 2 }