2022-06-27 00:17:22 +00:00
import { isArray } from "@vue/shared" ;
2022-03-04 03:39:48 +00:00
import { globalBus } from "game/events" ;
2022-06-27 00:17:22 +00:00
import type { GenericLayer } from "game/layers" ;
import { addingLayers , persistentRefs } from "game/layers" ;
import type { DecimalSource } from "util/bignum" ;
import Decimal from "util/bignum" ;
2022-03-04 03:39:48 +00:00
import { ProxyState } from "util/proxies" ;
2022-06-27 00:17:22 +00:00
import type { Ref } from "vue" ;
import { isReactive , isRef , ref } from "vue" ;
2022-02-27 22:18:13 +00:00
export const PersistentState = Symbol ( "PersistentState" ) ;
export const DefaultValue = Symbol ( "DefaultValue" ) ;
2022-04-06 03:16:40 +00:00
export const StackTrace = Symbol ( "StackTrace" ) ;
export const Deleted = Symbol ( "Deleted" ) ;
2022-02-27 22:18:13 +00:00
// Note: This is a union of things that should be safely stringifiable without needing
// special processes for knowing what to load them in as
// - Decimals aren't allowed because we'd need to know to parse them back.
// - DecimalSources are allowed because the string is a valid value for them
export type State =
| string
| number
| boolean
| DecimalSource
| { [ key : string ] : State }
| { [ key : number ] : State } ;
2022-04-06 03:16:40 +00:00
export type Persistent < T extends State = State > = Ref < T > & {
2022-02-27 22:18:13 +00:00
[ PersistentState ] : Ref < T > ;
[ DefaultValue ] : T ;
2022-04-06 03:16:40 +00:00
[ StackTrace ] : string ;
[ Deleted ] : boolean ;
2022-02-27 22:18:13 +00:00
} ;
2022-04-06 03:16:40 +00:00
function getStackTrace() {
return (
new Error ( ) . stack
? . split ( "\n" )
. slice ( 3 , 5 )
. map ( line = > line . trim ( ) )
. join ( "\n" ) || ""
) ;
}
export function persistent < T extends State > ( defaultValue : T | Ref < T > ) : Persistent < T > {
2022-02-27 22:18:13 +00:00
const persistent = (
isRef ( defaultValue ) ? defaultValue : ( ref < T > ( defaultValue ) as unknown )
2022-04-06 03:16:40 +00:00
) as Persistent < T > ;
2022-02-27 22:18:13 +00:00
persistent [ PersistentState ] = persistent ;
persistent [ DefaultValue ] = isRef ( defaultValue ) ? defaultValue.value : defaultValue ;
2022-04-06 03:16:40 +00:00
persistent [ StackTrace ] = getStackTrace ( ) ;
persistent [ Deleted ] = false ;
2022-02-27 22:18:13 +00:00
2022-04-06 03:16:40 +00:00
if ( addingLayers . length === 0 ) {
console . warn (
"Creating a persistent ref outside of a layer. This is not officially supported" ,
persistent ,
"\nCreated at:\n" + persistent [ StackTrace ]
) ;
} else {
persistentRefs [ addingLayers [ addingLayers . length - 1 ] ] . add ( persistent ) ;
}
2022-02-27 22:18:13 +00:00
2022-04-06 03:16:40 +00:00
return persistent as Persistent < T > ;
}
export function deletePersistent ( persistent : Persistent ) {
if ( addingLayers . length === 0 ) {
console . warn ( "Deleting a persistent ref outside of a layer. Ignoring..." , persistent ) ;
2022-04-10 23:56:32 +00:00
} else {
persistentRefs [ addingLayers [ addingLayers . length - 1 ] ] . delete ( persistent ) ;
2022-04-06 03:16:40 +00:00
}
persistent [ Deleted ] = true ;
2022-02-27 22:18:13 +00:00
}
globalBus . on ( "addLayer" , ( layer : GenericLayer , saveData : Record < string , unknown > ) = > {
2022-02-28 00:07:21 +00:00
const features : { type : typeof Symbol } [ ] = [ ] ;
2022-02-27 22:18:13 +00:00
const handleObject = ( obj : Record < string , unknown > , path : string [ ] = [ ] ) : boolean = > {
let foundPersistent = false ;
Object . keys ( obj ) . forEach ( key = > {
const value = obj [ key ] ;
if ( value && typeof value === "object" ) {
if ( PersistentState in value ) {
foundPersistent = true ;
2022-04-06 03:16:40 +00:00
if ( ( value as Persistent ) [ Deleted ] ) {
console . warn (
"Deleted persistent ref present in returned object. Ignoring..." ,
value ,
"\nCreated at:\n" + ( value as Persistent ) [ StackTrace ]
) ;
return ;
}
persistentRefs [ layer . id ] . delete (
ProxyState in value
2022-04-10 23:56:32 +00:00
? // eslint-disable-next-line @typescript-eslint/no-explicit-any
( ( value as any ) [ ProxyState ] as Persistent )
2022-04-06 03:16:40 +00:00
: ( value as Persistent )
) ;
2022-02-27 22:18:13 +00:00
// Construct save path if it doesn't exist
const persistentState = path . reduce < Record < string , unknown > > ( ( acc , curr ) = > {
if ( ! ( curr in acc ) ) {
acc [ curr ] = { } ;
}
return acc [ curr ] as Record < string , unknown > ;
} , saveData ) ;
// Cache currently saved value
const savedValue = persistentState [ key ] ;
// Add ref to save data
persistentState [ key ] = ( value as Persistent ) [ PersistentState ] ;
// Load previously saved value
2022-04-03 23:41:52 +00:00
if ( isReactive ( persistentState ) ) {
if ( savedValue != null ) {
persistentState [ key ] = savedValue ;
} else {
persistentState [ key ] = ( value as Persistent ) [ DefaultValue ] ;
}
2022-02-27 22:18:13 +00:00
} else {
2022-04-03 23:41:52 +00:00
if ( savedValue != null ) {
( persistentState [ key ] as Ref < unknown > ) . value = savedValue ;
} else {
( persistentState [ key ] as Ref < unknown > ) . value = ( value as Persistent ) [
DefaultValue
] ;
}
2022-02-27 22:18:13 +00:00
}
2022-02-28 00:07:21 +00:00
} else if (
! ( value instanceof Decimal ) &&
! isRef ( value ) &&
// eslint-disable-next-line @typescript-eslint/no-explicit-any
! features . includes ( value as { type : typeof Symbol } )
) {
if ( typeof ( value as { type : typeof Symbol } ) . type === "symbol" ) {
features . push ( value as { type : typeof Symbol } ) ;
}
2022-02-27 22:18:13 +00:00
// Continue traversing
const foundPersistentInChild = handleObject ( value as Record < string , unknown > , [
. . . path ,
key
] ) ;
// Show warning for persistent values inside arrays
// TODO handle arrays better
if ( foundPersistentInChild ) {
if ( isArray ( value ) && ! isArray ( obj ) ) {
console . warn (
"Found array that contains persistent values when adding layer. Keep in mind changing the order of elements in the array will mess with existing player saves." ,
ProxyState in obj
? ( obj as Record < PropertyKey , unknown > ) [ ProxyState ]
: obj ,
key
) ;
} else {
foundPersistent = true ;
}
}
}
}
} ) ;
return foundPersistent ;
} ;
handleObject ( layer ) ;
2022-04-06 03:16:40 +00:00
persistentRefs [ layer . id ] . forEach ( persistent = > {
console . error (
` Created persistent ref in ${ layer . id } without registering it to the layer! Make sure to include everything persistent in the returned object ` ,
persistent ,
"\nCreated at:\n" + persistent [ StackTrace ]
) ;
} ) ;
persistentRefs [ layer . id ] . clear ( ) ;
2022-02-27 22:18:13 +00:00
} ) ;