This commit is contained in:
parent
1f5b779aa1
commit
2fe611c0d5
|
@ -10,7 +10,7 @@ AnimatorStateTransition:
|
|||
m_Conditions:
|
||||
- m_ConditionMode: 4
|
||||
m_ConditionEvent: SqrMagnitude
|
||||
m_EventTreshold: 0.16
|
||||
m_EventTreshold: 0.08
|
||||
m_DstStateMachine: {fileID: 0}
|
||||
m_DstState: {fileID: 8660854411730663567}
|
||||
m_Solo: 0
|
||||
|
@ -799,7 +799,7 @@ AnimatorStateTransition:
|
|||
m_Conditions:
|
||||
- m_ConditionMode: 3
|
||||
m_ConditionEvent: SqrMagnitude
|
||||
m_EventTreshold: 0.16
|
||||
m_EventTreshold: 0.08
|
||||
m_DstStateMachine: {fileID: 0}
|
||||
m_DstState: {fileID: 5953952884183743852}
|
||||
m_Solo: 0
|
||||
|
@ -1867,7 +1867,7 @@ AnimatorState:
|
|||
m_IKOnFeet: 0
|
||||
m_WriteDefaultValues: 1
|
||||
m_Mirror: 0
|
||||
m_SpeedParameterActive: 0
|
||||
m_SpeedParameterActive: 1
|
||||
m_MirrorParameterActive: 0
|
||||
m_CycleOffsetParameterActive: 0
|
||||
m_TimeParameterActive: 0
|
||||
|
|
|
@ -206,7 +206,7 @@ MonoBehaviour:
|
|||
type: {class: BulletServiceSingleton, ns: BITFALL, asm: BITFALL.Bullet.Runtime}
|
||||
data:
|
||||
- rid: 1055089355266719758
|
||||
type: {class: MeleeServiceSingleton, ns: BITFALL.Melee, asm: BITFALL.Melee.Runtime}
|
||||
type: {class: MeleeServiceSingleton, ns: BITFALL.Combat, asm: BITFALL.Melee.Runtime}
|
||||
data:
|
||||
--- !u!1 &3116384759055216959
|
||||
GameObject:
|
||||
|
@ -275,7 +275,7 @@ MonoBehaviour:
|
|||
- rid: -2
|
||||
type: {class: , ns: , asm: }
|
||||
- rid: 1055089355266719756
|
||||
type: {class: MeleeServiceSingleton, ns: BITFALL.Melee, asm: BITFALL.Melee.Runtime}
|
||||
type: {class: MeleeServiceSingleton, ns: BITFALL.Combat, asm: BITFALL.Melee.Runtime}
|
||||
data:
|
||||
--- !u!1 &3282564729361582311
|
||||
GameObject:
|
||||
|
@ -424,7 +424,7 @@ MonoBehaviour:
|
|||
- rid: -2
|
||||
type: {class: , ns: , asm: }
|
||||
- rid: 806583703969988608
|
||||
type: {class: MeleeServiceSingleton, ns: BITFALL.Melee, asm: BITFALL.Melee.Runtime}
|
||||
type: {class: MeleeServiceSingleton, ns: BITFALL.Combat, asm: BITFALL.Melee.Runtime}
|
||||
data:
|
||||
--- !u!1 &4066453597750257159
|
||||
GameObject:
|
||||
|
@ -1024,7 +1024,7 @@ MonoBehaviour:
|
|||
type: {class: BulletServiceSingleton, ns: BITFALL, asm: BITFALL.Bullet.Runtime}
|
||||
data:
|
||||
- rid: 1055089355266719760
|
||||
type: {class: MeleeServiceSingleton, ns: BITFALL.Melee, asm: BITFALL.Melee.Runtime}
|
||||
type: {class: MeleeServiceSingleton, ns: BITFALL.Combat, asm: BITFALL.Melee.Runtime}
|
||||
data:
|
||||
--- !u!1 &5568622665405429642
|
||||
GameObject:
|
||||
|
@ -1104,7 +1104,7 @@ MonoBehaviour:
|
|||
type: {class: BulletServiceSingleton, ns: BITFALL, asm: BITFALL.Bullet.Runtime}
|
||||
data:
|
||||
- rid: 1055089355266719759
|
||||
type: {class: MeleeServiceSingleton, ns: BITFALL.Melee, asm: BITFALL.Melee.Runtime}
|
||||
type: {class: MeleeServiceSingleton, ns: BITFALL.Combat, asm: BITFALL.Melee.Runtime}
|
||||
data:
|
||||
--- !u!1 &7407409373653409842
|
||||
GameObject:
|
||||
|
|
|
@ -734,7 +734,7 @@ MonoBehaviour:
|
|||
- rid: -2
|
||||
type: {class: , ns: , asm: }
|
||||
- rid: 1055089217745715201
|
||||
type: {class: MeleeServiceSingleton, ns: BITFALL.Melee, asm: BITFALL.Melee.Runtime}
|
||||
type: {class: MeleeServiceSingleton, ns: BITFALL.Combat, asm: BITFALL.Melee.Runtime}
|
||||
data:
|
||||
--- !u!1 &2809084911145372184
|
||||
GameObject:
|
||||
|
@ -1678,7 +1678,7 @@ MonoBehaviour:
|
|||
version: 2
|
||||
RefIds:
|
||||
- rid: 806583545631342608
|
||||
type: {class: MeleeServiceSingleton, ns: BITFALL.Melee, asm: BITFALL.Melee.Runtime}
|
||||
type: {class: MeleeServiceSingleton, ns: BITFALL.Combat, asm: BITFALL.Melee.Runtime}
|
||||
data:
|
||||
--- !u!114 &3865076157975442983
|
||||
MonoBehaviour:
|
||||
|
@ -3237,7 +3237,7 @@ MonoBehaviour:
|
|||
- rid: -2
|
||||
type: {class: , ns: , asm: }
|
||||
- rid: 806583703969988652
|
||||
type: {class: MeleeServiceSingleton, ns: BITFALL.Melee, asm: BITFALL.Melee.Runtime}
|
||||
type: {class: MeleeServiceSingleton, ns: BITFALL.Combat, asm: BITFALL.Melee.Runtime}
|
||||
data:
|
||||
--- !u!1 &5633551467716821190
|
||||
GameObject:
|
||||
|
@ -5007,13 +5007,8 @@ PrefabInstance:
|
|||
- target: {fileID: 3555939717849507634, guid: 4d2e2ea1a5d15fa4a90ca3a79baa74fa,
|
||||
type: 3}
|
||||
propertyPath: renderers.Array.size
|
||||
value: 3
|
||||
value: 2
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 3555939717849507634, guid: 4d2e2ea1a5d15fa4a90ca3a79baa74fa,
|
||||
type: 3}
|
||||
propertyPath: renderers.Array.data[2]
|
||||
value:
|
||||
objectReference: {fileID: 1691712603686457520}
|
||||
- target: {fileID: 3808369081287560470, guid: 4d2e2ea1a5d15fa4a90ca3a79baa74fa,
|
||||
type: 3}
|
||||
propertyPath: m_LocalPosition.x
|
||||
|
@ -5201,23 +5196,13 @@ PrefabInstance:
|
|||
objectReference: {fileID: 0}
|
||||
m_RemovedComponents: []
|
||||
m_RemovedGameObjects: []
|
||||
m_AddedGameObjects:
|
||||
- targetCorrespondingSourceObject: {fileID: 847274249425463477, guid: 4d2e2ea1a5d15fa4a90ca3a79baa74fa,
|
||||
type: 3}
|
||||
insertIndex: -1
|
||||
addedObject: {fileID: 518203656061299679}
|
||||
m_AddedGameObjects: []
|
||||
m_AddedComponents:
|
||||
- targetCorrespondingSourceObject: {fileID: 4580683282014377900, guid: 4d2e2ea1a5d15fa4a90ca3a79baa74fa,
|
||||
type: 3}
|
||||
insertIndex: -1
|
||||
addedObject: {fileID: 4059732203151340930}
|
||||
m_SourcePrefab: {fileID: 100100000, guid: 4d2e2ea1a5d15fa4a90ca3a79baa74fa, type: 3}
|
||||
--- !u!4 &461740354935876669 stripped
|
||||
Transform:
|
||||
m_CorrespondingSourceObject: {fileID: 847274249425463477, guid: 4d2e2ea1a5d15fa4a90ca3a79baa74fa,
|
||||
type: 3}
|
||||
m_PrefabInstance: {fileID: 984723819954259080}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
--- !u!137 &2348331999583567347 stripped
|
||||
SkinnedMeshRenderer:
|
||||
m_CorrespondingSourceObject: {fileID: 3259623828982151547, guid: 4d2e2ea1a5d15fa4a90ca3a79baa74fa,
|
||||
|
@ -5359,92 +5344,6 @@ Transform:
|
|||
type: 3}
|
||||
m_PrefabInstance: {fileID: 1040364863550748081}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
--- !u!1001 &1451497798110618150
|
||||
PrefabInstance:
|
||||
m_ObjectHideFlags: 0
|
||||
serializedVersion: 2
|
||||
m_Modification:
|
||||
serializedVersion: 3
|
||||
m_TransformParent: {fileID: 461740354935876669}
|
||||
m_Modifications:
|
||||
- target: {fileID: 1375224013855558137, guid: 6db7680dca4eb2748a7339cb3bc13d14,
|
||||
type: 3}
|
||||
propertyPath: m_LocalPosition.x
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1375224013855558137, guid: 6db7680dca4eb2748a7339cb3bc13d14,
|
||||
type: 3}
|
||||
propertyPath: m_LocalPosition.y
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1375224013855558137, guid: 6db7680dca4eb2748a7339cb3bc13d14,
|
||||
type: 3}
|
||||
propertyPath: m_LocalPosition.z
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1375224013855558137, guid: 6db7680dca4eb2748a7339cb3bc13d14,
|
||||
type: 3}
|
||||
propertyPath: m_LocalRotation.w
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1375224013855558137, guid: 6db7680dca4eb2748a7339cb3bc13d14,
|
||||
type: 3}
|
||||
propertyPath: m_LocalRotation.x
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1375224013855558137, guid: 6db7680dca4eb2748a7339cb3bc13d14,
|
||||
type: 3}
|
||||
propertyPath: m_LocalRotation.y
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1375224013855558137, guid: 6db7680dca4eb2748a7339cb3bc13d14,
|
||||
type: 3}
|
||||
propertyPath: m_LocalRotation.z
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1375224013855558137, guid: 6db7680dca4eb2748a7339cb3bc13d14,
|
||||
type: 3}
|
||||
propertyPath: m_LocalEulerAnglesHint.x
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1375224013855558137, guid: 6db7680dca4eb2748a7339cb3bc13d14,
|
||||
type: 3}
|
||||
propertyPath: m_LocalEulerAnglesHint.y
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1375224013855558137, guid: 6db7680dca4eb2748a7339cb3bc13d14,
|
||||
type: 3}
|
||||
propertyPath: m_LocalEulerAnglesHint.z
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1755865475335770947, guid: 6db7680dca4eb2748a7339cb3bc13d14,
|
||||
type: 3}
|
||||
propertyPath: m_Name
|
||||
value: Apple_01
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1755865475335770947, guid: 6db7680dca4eb2748a7339cb3bc13d14,
|
||||
type: 3}
|
||||
propertyPath: m_Layer
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
m_RemovedComponents:
|
||||
- {fileID: 2120823588654661318, guid: 6db7680dca4eb2748a7339cb3bc13d14, type: 3}
|
||||
m_RemovedGameObjects: []
|
||||
m_AddedGameObjects: []
|
||||
m_AddedComponents: []
|
||||
m_SourcePrefab: {fileID: 100100000, guid: 6db7680dca4eb2748a7339cb3bc13d14, type: 3}
|
||||
--- !u!4 &518203656061299679 stripped
|
||||
Transform:
|
||||
m_CorrespondingSourceObject: {fileID: 1375224013855558137, guid: 6db7680dca4eb2748a7339cb3bc13d14,
|
||||
type: 3}
|
||||
m_PrefabInstance: {fileID: 1451497798110618150}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
--- !u!23 &1691712603686457520 stripped
|
||||
MeshRenderer:
|
||||
m_CorrespondingSourceObject: {fileID: 242888891679208086, guid: 6db7680dca4eb2748a7339cb3bc13d14,
|
||||
type: 3}
|
||||
m_PrefabInstance: {fileID: 1451497798110618150}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
--- !u!1001 &1455826409223114458
|
||||
PrefabInstance:
|
||||
m_ObjectHideFlags: 0
|
||||
|
@ -6076,7 +5975,7 @@ MonoBehaviour:
|
|||
data:
|
||||
meleeController: {fileID: 2426015728857496741}
|
||||
- rid: 806583484692037644
|
||||
type: {class: MeleeServiceSingleton, ns: BITFALL.Melee, asm: BITFALL.Melee.Runtime}
|
||||
type: {class: MeleeServiceSingleton, ns: BITFALL.Combat, asm: BITFALL.Melee.Runtime}
|
||||
data:
|
||||
- rid: 806583545631342604
|
||||
type: {class: Blocking, ns: BITFALL.Entities.Equipment.Melee, asm: BITFALL.Equip}
|
||||
|
@ -7277,7 +7176,7 @@ MonoBehaviour:
|
|||
data:
|
||||
useController: {fileID: 316352626332989457}
|
||||
- rid: 806583484692037659
|
||||
type: {class: MeleeServiceSingleton, ns: BITFALL.Melee, asm: BITFALL.Melee.Runtime}
|
||||
type: {class: MeleeServiceSingleton, ns: BITFALL.Combat, asm: BITFALL.Melee.Runtime}
|
||||
data:
|
||||
--- !u!114 &2486523390929547415
|
||||
MonoBehaviour:
|
||||
|
@ -9997,7 +9896,7 @@ MonoBehaviour:
|
|||
- rid: -2
|
||||
type: {class: , ns: , asm: }
|
||||
- rid: 806583611772633088
|
||||
type: {class: MeleeServiceSingleton, ns: BITFALL.Melee, asm: BITFALL.Melee.Runtime}
|
||||
type: {class: MeleeServiceSingleton, ns: BITFALL.Combat, asm: BITFALL.Melee.Runtime}
|
||||
data:
|
||||
--- !u!114 &8391365113968233690
|
||||
MonoBehaviour:
|
||||
|
@ -10369,7 +10268,7 @@ MonoBehaviour:
|
|||
- rid: -2
|
||||
type: {class: , ns: , asm: }
|
||||
- rid: 806583526086934616
|
||||
type: {class: MeleeServiceSingleton, ns: BITFALL.Melee, asm: BITFALL.Melee.Runtime}
|
||||
type: {class: MeleeServiceSingleton, ns: BITFALL.Combat, asm: BITFALL.Melee.Runtime}
|
||||
data:
|
||||
--- !u!114 &2801954557211680929
|
||||
MonoBehaviour:
|
||||
|
@ -12297,7 +12196,7 @@ MonoBehaviour:
|
|||
- rid: -2
|
||||
type: {class: , ns: , asm: }
|
||||
- rid: 806583526086934616
|
||||
type: {class: MeleeServiceSingleton, ns: BITFALL.Melee, asm: BITFALL.Melee.Runtime}
|
||||
type: {class: MeleeServiceSingleton, ns: BITFALL.Combat, asm: BITFALL.Melee.Runtime}
|
||||
data:
|
||||
--- !u!114 &3339523282537532634
|
||||
MonoBehaviour:
|
||||
|
|
|
@ -454,7 +454,7 @@ MonoBehaviour:
|
|||
- rid: -2
|
||||
type: {class: , ns: , asm: }
|
||||
- rid: 806583325261299712
|
||||
type: {class: MeleeServiceSingleton, ns: BITFALL.Melee, asm: BITFALL.Melee.Runtime}
|
||||
type: {class: MeleeServiceSingleton, ns: BITFALL.Combat, asm: BITFALL.Melee.Runtime}
|
||||
data:
|
||||
- rid: 806583384995266560
|
||||
type: {class: Holster, ns: BITFALL.Guns.States, asm: BITFALL.Equip}
|
||||
|
|
|
@ -114,7 +114,7 @@ MonoBehaviour:
|
|||
version: 2
|
||||
RefIds:
|
||||
- rid: 1055089825765916695
|
||||
type: {class: MeleeServiceSingleton, ns: BITFALL.Melee, asm: BITFALL.Melee.Runtime}
|
||||
type: {class: MeleeServiceSingleton, ns: BITFALL.Combat, asm: BITFALL.Melee.Runtime}
|
||||
data:
|
||||
--- !u!114 &4077778554487099840 stripped
|
||||
MonoBehaviour:
|
||||
|
|
|
@ -783,7 +783,7 @@ MonoBehaviour:
|
|||
- rid: -2
|
||||
type: {class: , ns: , asm: }
|
||||
- rid: 806583325261299712
|
||||
type: {class: MeleeServiceSingleton, ns: BITFALL.Melee, asm: BITFALL.Melee.Runtime}
|
||||
type: {class: MeleeServiceSingleton, ns: BITFALL.Combat, asm: BITFALL.Melee.Runtime}
|
||||
data:
|
||||
- rid: 806583384995266560
|
||||
type: {class: Holster, ns: BITFALL.Guns.States, asm: BITFALL.Equip}
|
||||
|
|
|
@ -416,7 +416,7 @@ MonoBehaviour:
|
|||
version: 2
|
||||
RefIds:
|
||||
- rid: 806583545631342593
|
||||
type: {class: MeleeServiceSingleton, ns: BITFALL.Melee, asm: BITFALL.Melee.Runtime}
|
||||
type: {class: MeleeServiceSingleton, ns: BITFALL.Combat, asm: BITFALL.Melee.Runtime}
|
||||
data:
|
||||
- rid: 806583545631342595
|
||||
type: {class: Draw, ns: BITFALL.Entities.Equipment.Universal.States, asm: BITFALL.Equip}
|
||||
|
|
|
@ -10,7 +10,6 @@ GameObject:
|
|||
m_Component:
|
||||
- component: {fileID: 5964803017347144284}
|
||||
- component: {fileID: 2126668844316887849}
|
||||
- component: {fileID: 8579368653672686413}
|
||||
- component: {fileID: 673930142905110667}
|
||||
- component: {fileID: 1765543596226114749}
|
||||
- component: {fileID: 4827747552512862242}
|
||||
|
@ -71,29 +70,6 @@ MonoBehaviour:
|
|||
references:
|
||||
version: 2
|
||||
RefIds: []
|
||||
--- !u!136 &8579368653672686413
|
||||
CapsuleCollider:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 1610736432900663163}
|
||||
m_Material: {fileID: 0}
|
||||
m_IncludeLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 0
|
||||
m_ExcludeLayers:
|
||||
serializedVersion: 2
|
||||
m_Bits: 0
|
||||
m_LayerOverridePriority: 0
|
||||
m_IsTrigger: 0
|
||||
m_ProvidesContacts: 0
|
||||
m_Enabled: 1
|
||||
serializedVersion: 2
|
||||
m_Radius: 0.32
|
||||
m_Height: 1.6
|
||||
m_Direction: 1
|
||||
m_Center: {x: 0, y: 0.8, z: 0}
|
||||
--- !u!95 &673930142905110667
|
||||
Animator:
|
||||
serializedVersion: 5
|
||||
|
@ -226,7 +202,7 @@ MonoBehaviour:
|
|||
_zoomFactor: 1
|
||||
_firstActivation: 2
|
||||
_enableAction: 1
|
||||
_disableAction: 2
|
||||
_disableAction: 0
|
||||
_lockBoundGraphPrefabOverrides: 1
|
||||
_preInitializeSubGraphs: 0
|
||||
_updateMode: 0
|
||||
|
@ -318,7 +294,7 @@ MonoBehaviour:
|
|||
version: 2
|
||||
RefIds:
|
||||
- rid: 806583325261299714
|
||||
type: {class: MeleeServiceSingleton, ns: BITFALL.Melee, asm: BITFALL.Melee.Runtime}
|
||||
type: {class: MeleeServiceSingleton, ns: BITFALL.Combat, asm: BITFALL.Melee.Runtime}
|
||||
data:
|
||||
--- !u!114 &403058373197954028
|
||||
MonoBehaviour:
|
||||
|
@ -429,8 +405,8 @@ MonoBehaviour:
|
|||
_getDamage:
|
||||
rid: 806583346156273669
|
||||
aliveCollider:
|
||||
allow: 1
|
||||
value: {fileID: 8579368653672686413}
|
||||
allow: 0
|
||||
value: {fileID: 0}
|
||||
allowAnimatorParameter: 0
|
||||
physicsBasedAnimation:
|
||||
allow: 0
|
||||
|
@ -639,12 +615,12 @@ PrefabInstance:
|
|||
- target: {fileID: 421487889729838279, guid: d6020a576e13b6d43bd1143a33ff7f8e,
|
||||
type: 3}
|
||||
propertyPath: m_LocalPosition.z
|
||||
value: 0.000000029802322
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 421487889729838279, guid: d6020a576e13b6d43bd1143a33ff7f8e,
|
||||
type: 3}
|
||||
propertyPath: m_LocalRotation.w
|
||||
value: -0.00000033102052
|
||||
value: -0.0000001986123
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 421487889729838279, guid: d6020a576e13b6d43bd1143a33ff7f8e,
|
||||
type: 3}
|
||||
|
@ -659,7 +635,7 @@ PrefabInstance:
|
|||
- target: {fileID: 421487889729838279, guid: d6020a576e13b6d43bd1143a33ff7f8e,
|
||||
type: 3}
|
||||
propertyPath: m_LocalRotation.z
|
||||
value: 0.0000004634289
|
||||
value: 0.00000033102063
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 913909496426237269, guid: d6020a576e13b6d43bd1143a33ff7f8e,
|
||||
type: 3}
|
||||
|
@ -679,7 +655,7 @@ PrefabInstance:
|
|||
- target: {fileID: 1286267001055454466, guid: d6020a576e13b6d43bd1143a33ff7f8e,
|
||||
type: 3}
|
||||
propertyPath: m_LocalPosition.z
|
||||
value: -0.000000007450581
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 1286267001055454466, guid: d6020a576e13b6d43bd1143a33ff7f8e,
|
||||
type: 3}
|
||||
|
@ -839,27 +815,27 @@ PrefabInstance:
|
|||
- target: {fileID: 5977324375018029203, guid: d6020a576e13b6d43bd1143a33ff7f8e,
|
||||
type: 3}
|
||||
propertyPath: m_LocalPosition.z
|
||||
value: -0.000000029802322
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 5977324375018029203, guid: d6020a576e13b6d43bd1143a33ff7f8e,
|
||||
type: 3}
|
||||
propertyPath: m_LocalRotation.w
|
||||
value: 0.00000046342882
|
||||
value: -0.00000013240823
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 5977324375018029203, guid: d6020a576e13b6d43bd1143a33ff7f8e,
|
||||
type: 3}
|
||||
propertyPath: m_LocalRotation.x
|
||||
value: -0.70710677
|
||||
value: 0.7071068
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 5977324375018029203, guid: d6020a576e13b6d43bd1143a33ff7f8e,
|
||||
type: 3}
|
||||
propertyPath: m_LocalRotation.y
|
||||
value: 0.70710677
|
||||
value: -0.7071068
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 5977324375018029203, guid: d6020a576e13b6d43bd1143a33ff7f8e,
|
||||
type: 3}
|
||||
propertyPath: m_LocalRotation.z
|
||||
value: 0.000000066204116
|
||||
value: -0.00000013240823
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 5997892003019056750, guid: d6020a576e13b6d43bd1143a33ff7f8e,
|
||||
type: 3}
|
||||
|
@ -884,27 +860,27 @@ PrefabInstance:
|
|||
- target: {fileID: 6658073127811331136, guid: d6020a576e13b6d43bd1143a33ff7f8e,
|
||||
type: 3}
|
||||
propertyPath: m_LocalPosition.z
|
||||
value: -0.000000029802322
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 6658073127811331136, guid: d6020a576e13b6d43bd1143a33ff7f8e,
|
||||
type: 3}
|
||||
propertyPath: m_LocalRotation.w
|
||||
value: 0.00000039722462
|
||||
value: -0.0000001986123
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 6658073127811331136, guid: d6020a576e13b6d43bd1143a33ff7f8e,
|
||||
type: 3}
|
||||
propertyPath: m_LocalRotation.x
|
||||
value: -0.70710665
|
||||
value: 0.70710665
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 6658073127811331136, guid: d6020a576e13b6d43bd1143a33ff7f8e,
|
||||
type: 3}
|
||||
propertyPath: m_LocalRotation.y
|
||||
value: 0.70710695
|
||||
value: -0.70710695
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 6658073127811331136, guid: d6020a576e13b6d43bd1143a33ff7f8e,
|
||||
type: 3}
|
||||
propertyPath: m_LocalRotation.z
|
||||
value: 0.0000003972248
|
||||
value: -0.00000033102063
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 7136674892077359098, guid: d6020a576e13b6d43bd1143a33ff7f8e,
|
||||
type: 3}
|
||||
|
@ -914,27 +890,27 @@ PrefabInstance:
|
|||
- target: {fileID: 7136674892077359098, guid: d6020a576e13b6d43bd1143a33ff7f8e,
|
||||
type: 3}
|
||||
propertyPath: m_LocalPosition.z
|
||||
value: 0.000000029802322
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 7136674892077359098, guid: d6020a576e13b6d43bd1143a33ff7f8e,
|
||||
type: 3}
|
||||
propertyPath: m_LocalRotation.w
|
||||
value: -0.00000013240823
|
||||
value: 0.00000013240823
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 7136674892077359098, guid: d6020a576e13b6d43bd1143a33ff7f8e,
|
||||
type: 3}
|
||||
propertyPath: m_LocalRotation.x
|
||||
value: 0.7071068
|
||||
value: -0.7071068
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 7136674892077359098, guid: d6020a576e13b6d43bd1143a33ff7f8e,
|
||||
type: 3}
|
||||
propertyPath: m_LocalRotation.y
|
||||
value: 0.7071068
|
||||
value: -0.70710677
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 7136674892077359098, guid: d6020a576e13b6d43bd1143a33ff7f8e,
|
||||
type: 3}
|
||||
propertyPath: m_LocalRotation.z
|
||||
value: 0.00000013240823
|
||||
value: -0.00000013240822
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 7520077443289668456, guid: d6020a576e13b6d43bd1143a33ff7f8e,
|
||||
type: 3}
|
||||
|
@ -954,12 +930,12 @@ PrefabInstance:
|
|||
- target: {fileID: 7875696957335602972, guid: d6020a576e13b6d43bd1143a33ff7f8e,
|
||||
type: 3}
|
||||
propertyPath: m_LocalPosition.z
|
||||
value: -0.000000007450581
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 7875696957335602972, guid: d6020a576e13b6d43bd1143a33ff7f8e,
|
||||
type: 3}
|
||||
propertyPath: m_LocalRotation.w
|
||||
value: 0
|
||||
value: 8.7659685e-15
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 7875696957335602972, guid: d6020a576e13b6d43bd1143a33ff7f8e,
|
||||
type: 3}
|
||||
|
@ -974,7 +950,7 @@ PrefabInstance:
|
|||
- target: {fileID: 7875696957335602972, guid: d6020a576e13b6d43bd1143a33ff7f8e,
|
||||
type: 3}
|
||||
propertyPath: m_LocalRotation.z
|
||||
value: -0
|
||||
value: 0.00000009362675
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 8632246090726818035, guid: d6020a576e13b6d43bd1143a33ff7f8e,
|
||||
type: 3}
|
||||
|
@ -989,7 +965,7 @@ PrefabInstance:
|
|||
- target: {fileID: 8632246090726818035, guid: d6020a576e13b6d43bd1143a33ff7f8e,
|
||||
type: 3}
|
||||
propertyPath: m_LocalPosition.z
|
||||
value: 0.000000007450581
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 8632246090726818035, guid: d6020a576e13b6d43bd1143a33ff7f8e,
|
||||
type: 3}
|
||||
|
@ -999,12 +975,12 @@ PrefabInstance:
|
|||
- target: {fileID: 8632246090726818035, guid: d6020a576e13b6d43bd1143a33ff7f8e,
|
||||
type: 3}
|
||||
propertyPath: m_LocalRotation.x
|
||||
value: -1
|
||||
value: 1
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 8632246090726818035, guid: d6020a576e13b6d43bd1143a33ff7f8e,
|
||||
type: 3}
|
||||
propertyPath: m_LocalRotation.y
|
||||
value: 0.00000009362675
|
||||
value: -0.00000009362675
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 8632246090726818035, guid: d6020a576e13b6d43bd1143a33ff7f8e,
|
||||
type: 3}
|
||||
|
@ -1024,7 +1000,7 @@ PrefabInstance:
|
|||
- target: {fileID: 8917115317957186730, guid: d6020a576e13b6d43bd1143a33ff7f8e,
|
||||
type: 3}
|
||||
propertyPath: m_LocalPosition.z
|
||||
value: 0.000000007450581
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 8917115317957186730, guid: d6020a576e13b6d43bd1143a33ff7f8e,
|
||||
type: 3}
|
||||
|
@ -1059,7 +1035,7 @@ PrefabInstance:
|
|||
- target: {fileID: 9002121479163606272, guid: d6020a576e13b6d43bd1143a33ff7f8e,
|
||||
type: 3}
|
||||
propertyPath: m_LocalPosition.z
|
||||
value: 0.000000007450581
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 9002121479163606272, guid: d6020a576e13b6d43bd1143a33ff7f8e,
|
||||
type: 3}
|
||||
|
@ -1165,11 +1141,6 @@ PrefabInstance:
|
|||
propertyPath: fov
|
||||
value: 0
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 8772707305530588351, guid: 2ad4d682518910847b19ed8106c0071a,
|
||||
type: 3}
|
||||
propertyPath: ignoreColliders.Array.data[0]
|
||||
value:
|
||||
objectReference: {fileID: 8579368653672686413}
|
||||
- target: {fileID: 9015502658869033244, guid: 2ad4d682518910847b19ed8106c0071a,
|
||||
type: 3}
|
||||
propertyPath: ignoreTags.allow
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -91,12 +91,12 @@ namespace BITKit
|
|||
{
|
||||
|
||||
}
|
||||
public void Movement(Vector3 relativeVector)
|
||||
public void OnMovement(Vector3 relativeVector)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void Movement(InputAction.CallbackContext context)
|
||||
public void OnMovement(InputAction.CallbackContext context)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
|
|
@ -369,7 +369,13 @@ namespace BITFALL.Guns
|
|||
animator.animator.SetBool((int)BITHash.Player.Cancel,
|
||||
recoilSpring.value.GetLength()>0.1f);
|
||||
animator.animator.SetFloat((int)BITHash.Player.Aim,_equipService.Zoom.Value);
|
||||
animator.animator.SetFloat(BITConstant.Player.SqrMagnitude,_movement.LocomotionBasedVelocity.sqrMagnitude);
|
||||
animator.animator.SetFloat(BITConstant.Player.SqrMagnitude,
|
||||
math.clamp(
|
||||
_movement.LocomotionBasedVelocity.sqrMagnitude
|
||||
,0,1
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
recoilSpring.Update(deltaTime,default);
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ using Unity.Collections;
|
|||
using UnityEngine;
|
||||
using UnityEngine.VFX;
|
||||
|
||||
namespace BITFALL.Melee
|
||||
namespace BITFALL.Combat
|
||||
{
|
||||
[Serializable]
|
||||
public class MeleeServiceSingleton : IMeleeService
|
||||
|
|
|
@ -13,6 +13,7 @@ using BITKit.Entities.Movement;
|
|||
using BITKit.Entities.Physics;
|
||||
using BITKit.Entities.Player;
|
||||
using BITKit.PlayerCamera;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using Lightbug.CharacterControllerPro.Core;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
|
@ -151,11 +152,11 @@ namespace BITFALL.Entities.Player.Movement
|
|||
TransitionState<Walk>();
|
||||
|
||||
|
||||
_inputActionGroup.RegisterCallback(movementAction, Movement);
|
||||
_inputActionGroup.RegisterCallback(viewAction, View);
|
||||
_inputActionGroup.RegisterCallback(jumpAction, Jump);
|
||||
_inputActionGroup.RegisterCallback(crouchAction, Crouch);
|
||||
_inputActionGroup.RegisterCallback(runAction, Run);
|
||||
_inputActionGroup.RegisterCallback(movementAction, OnMovement);
|
||||
_inputActionGroup.RegisterCallback(viewAction, OnView);
|
||||
_inputActionGroup.RegisterCallback(jumpAction, OnJump);
|
||||
_inputActionGroup.RegisterCallback(crouchAction, OnCrouch);
|
||||
_inputActionGroup.RegisterCallback(runAction, OnRun);
|
||||
}
|
||||
private void OnSetPhysics(bool obj)
|
||||
{
|
||||
|
@ -167,8 +168,9 @@ namespace BITFALL.Entities.Player.Movement
|
|||
|
||||
private void OnSetAlive(bool obj)
|
||||
{
|
||||
allowMovement.SetElements(this,obj);
|
||||
allowRun.SetElements(this,obj);
|
||||
|
||||
allowMovement.SetElements(123,obj);
|
||||
allowRun.SetElements(123,obj);
|
||||
if (obj)
|
||||
{
|
||||
if (!isDead) return;
|
||||
|
@ -186,11 +188,11 @@ namespace BITFALL.Entities.Player.Movement
|
|||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
public void Movement(Vector3 relativeVector)
|
||||
public void OnMovement(Vector3 relativeVector)
|
||||
{
|
||||
MovementInput = relativeVector;
|
||||
}
|
||||
public void Movement(InputAction.CallbackContext context)
|
||||
public void OnMovement(InputAction.CallbackContext context)
|
||||
{
|
||||
MovementInput = context.ReadValue<Vector2>();
|
||||
MovementInput = new Vector3(MovementInput.x,0,MovementInput.y);
|
||||
|
@ -205,8 +207,8 @@ namespace BITFALL.Entities.Player.Movement
|
|||
ExpectSprint.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
public void ExecuteCommand<T>(T command=default)
|
||||
|
||||
public void ExecuteCommand<T>(T command=default)
|
||||
{
|
||||
foreach (var x in StateDictionary.Values)
|
||||
{
|
||||
|
@ -233,7 +235,7 @@ namespace BITFALL.Entities.Player.Movement
|
|||
gravityDamping.Release(addGravityDampingCommand.Damping);
|
||||
break;
|
||||
case PlayerPauseRunCommand pauseRunCommand:
|
||||
pauseRun.SetElements(this,pauseRunCommand.Pause);
|
||||
pauseRun.SetElements(123,pauseRunCommand.Pause);
|
||||
break;
|
||||
case PlayerFocusCommand focusCommand:
|
||||
allowFocus.SetElements(focusCommand.Sender,focusCommand.Focus);
|
||||
|
@ -253,7 +255,7 @@ namespace BITFALL.Entities.Player.Movement
|
|||
}
|
||||
public event Action<object> OnCommand;
|
||||
|
||||
public void View(InputAction.CallbackContext context)
|
||||
public void OnView(InputAction.CallbackContext context)
|
||||
{
|
||||
if (BITAppForUnity.AllowCursor) return;
|
||||
var playerConfig = PlayerConfig.Singleton;
|
||||
|
@ -293,12 +295,9 @@ namespace BITFALL.Entities.Player.Movement
|
|||
-raw.x
|
||||
);
|
||||
}
|
||||
public void Jump(InputAction.CallbackContext context)
|
||||
public void OnJump(InputAction.CallbackContext context)
|
||||
{
|
||||
if (_knockdown.IsKnockdown) return;
|
||||
|
||||
var y = actor.ColliderComponent.Size.y;
|
||||
|
||||
|
||||
|
||||
RequestClimb = context switch
|
||||
|
@ -306,10 +305,9 @@ namespace BITFALL.Entities.Player.Movement
|
|||
{ interaction: PressInteraction, performed: true } => true,
|
||||
{ interaction: PressInteraction, canceled: true } => false,
|
||||
_ => RequestClimb
|
||||
};
|
||||
};
|
||||
|
||||
if (ExpectJump.shouldBe) return;
|
||||
if (context is not {interaction:PressInteraction , performed:true}) return;
|
||||
|
||||
switch (CurrentState)
|
||||
{
|
||||
|
@ -373,7 +371,7 @@ namespace BITFALL.Entities.Player.Movement
|
|||
}
|
||||
ExpectCrouch.Reset();
|
||||
}
|
||||
public void Run(InputAction.CallbackContext context)
|
||||
public void OnRun(InputAction.CallbackContext context)
|
||||
{
|
||||
if (allowRun.Allow is false) return;
|
||||
if (_knockdown.IsKnockdown) return;
|
||||
|
@ -419,7 +417,7 @@ namespace BITFALL.Entities.Player.Movement
|
|||
|
||||
}
|
||||
|
||||
public void Crouch(InputAction.CallbackContext context)
|
||||
public void OnCrouch(InputAction.CallbackContext context)
|
||||
{
|
||||
if (_knockdown.IsKnockdown) return;
|
||||
|
||||
|
@ -524,12 +522,9 @@ namespace BITFALL.Entities.Player.Movement
|
|||
}
|
||||
|
||||
if (allowMovement.Allow is false) return;
|
||||
var additiveTransform = locationAdditive.transform;
|
||||
|
||||
|
||||
var rotation = Quaternion.Euler(LookInput);
|
||||
|
||||
|
||||
|
||||
if (LimitViewAngle is not 0)
|
||||
{
|
||||
float angleDifference = Quaternion.Angle(rotation, FpvRotation);
|
||||
|
|
|
@ -72,6 +72,8 @@ namespace BITFALL.UX
|
|||
[UXBindPath("bloody-container")]
|
||||
private VisualElement bloodyContainer;
|
||||
|
||||
private VisualElement[] swayElements;
|
||||
|
||||
private float _currentHealthLerp;
|
||||
|
||||
protected CancellationTokenSource bloodyScreenCancellationTokenSource;
|
||||
|
@ -91,6 +93,8 @@ namespace BITFALL.UX
|
|||
|
||||
bloodyContainer.SetOpacity(0);
|
||||
injuryContainer.SetOpacity(0);
|
||||
|
||||
swayElements = document.rootVisualElement.Query(className: "swap").ToList().ToArray();
|
||||
}
|
||||
|
||||
private void OnInspect(InputAction.CallbackContext obj)
|
||||
|
@ -268,7 +272,6 @@ namespace BITFALL.UX
|
|||
}
|
||||
|
||||
{
|
||||
var currentPos = document.rootVisualElement.transform.position;
|
||||
var movementVelocity = _entityMovement.LocomotionBasedVelocity * 16;
|
||||
var velocity =
|
||||
-_entityMovement.AngularVelocity * 10
|
||||
|
@ -278,8 +281,16 @@ namespace BITFALL.UX
|
|||
x = velocity.y,
|
||||
y = velocity.x,
|
||||
};
|
||||
newPos = Vector3.Lerp(currentPos, newPos, Time.deltaTime * 10);
|
||||
document.rootVisualElement.transform.position = newPos;
|
||||
|
||||
|
||||
foreach (var x in swayElements)
|
||||
{
|
||||
var currentPos = x.transform.position;
|
||||
|
||||
newPos = Vector3.Lerp(currentPos, newPos, Time.deltaTime * 10);
|
||||
|
||||
x.transform.position = newPos;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -305,7 +316,7 @@ namespace BITFALL.UX
|
|||
healthBar.Set(Mathf.Clamp(hp,0,100) * 0.01f);
|
||||
|
||||
injuryContainer.SetOpacity(
|
||||
hp<50 ? 1 : 0
|
||||
hp< 60 ? Mathf.InverseLerp(40,60,hp) : 0
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,9 +12,6 @@
|
|||
<ui:VisualElement name="injury-container" class="root">
|
||||
<ui:VisualElement name="additive" class="root" style="background-image: url('project://database/Assets/Standard%20Assets/DamageHUD/Content/Art/UI/BloodyScreen.psd?fileID=2800000&guid=2dd072ebafd2e84438f73c131198c399&type=3#BloodyScreen'); -unity-background-image-tint-color: rgb(114, 0, 0);" />
|
||||
<ui:VisualElement name="additive" class="root" style="background-image: url('project://database/Assets/Standard%20Assets/DamageHUD/Content/Art/UI/BloodSplat.psd?fileID=2800000&guid=cf1d6c5375931344d9eb49fd8eac024b&type=3#BloodSplat'); -unity-background-image-tint-color: rgb(255, 255, 255);" />
|
||||
<ui:VisualElement name="additive" class="root" style="background-image: url('project://database/Assets/Standard%20Assets/DamageHUD/Content/Art/UI/BloodSplat.psd?fileID=2800000&guid=cf1d6c5375931344d9eb49fd8eac024b&type=3#BloodSplat'); -unity-background-image-tint-color: rgb(255, 255, 255);" />
|
||||
<ui:VisualElement name="additive" class="root" style="background-image: url('project://database/Assets/Standard%20Assets/DamageHUD/Content/Art/UI/BloodSplat.psd?fileID=2800000&guid=cf1d6c5375931344d9eb49fd8eac024b&type=3#BloodSplat'); -unity-background-image-tint-color: rgb(255, 255, 255);" />
|
||||
<ui:VisualElement name="additive" class="root" style="background-image: url('project://database/Assets/Standard%20Assets/DamageHUD/Content/Art/UI/BloodSplat.psd?fileID=2800000&guid=cf1d6c5375931344d9eb49fd8eac024b&type=3#BloodSplat'); -unity-background-image-tint-color: rgb(255, 255, 255);" />
|
||||
</ui:VisualElement>
|
||||
<ui:VisualElement name="bloody-container" class="root" style="background-image: url('project://database/Assets/Standard%20Assets/DamageHUD/Content/Art/UI/BloodyScreen.psd?fileID=2800000&guid=2dd072ebafd2e84438f73c131198c399&type=3#BloodyScreen'); -unity-background-image-tint-color: rgb(213, 6, 6); opacity: 0;" />
|
||||
</ui:VisualElement>
|
||||
|
@ -32,7 +29,7 @@
|
|||
<ui:VisualElement name="point-render" style="position: absolute; width: 32px; height: 32px; background-image: url('project://database/Assets/BITKit/Unity/Art/Icons/EditorIcons/winbtn_mac_close_a@2x.png?fileID=2800000&guid=15fcf531feceef8438c9e35edbc55be3&type=3#winbtn_mac_close_a@2x'); top: 0; left: 0;" />
|
||||
</ui:VisualElement>
|
||||
</ui:VisualElement>
|
||||
<ui:VisualElement name="quest-container" style="top: 64px; right: 64px; position: absolute; display: none;">
|
||||
<ui:VisualElement name="quest-container" class="swap" style="top: 64px; right: 64px; position: absolute; display: none;">
|
||||
<ui:Instance template="BITQuestElement" name="BITQuestElement" class="completed" />
|
||||
<ui:Instance template="BITQuestElement" name="BITQuestElement" />
|
||||
</ui:VisualElement>
|
||||
|
@ -52,7 +49,7 @@
|
|||
<ui:VisualElement name="crosshair-image" class="flex-center" style="width: 48px; height: 48px; background-image: url('project://database/Assets/Artists/Arts/Icons/Crosshair_No_Dot.png?fileID=2800000&guid=507fe62dcf74ad84d966ea70a924771e&type=3#Crosshair_No_Dot');" />
|
||||
</ui:VisualElement>
|
||||
</ui:VisualElement>
|
||||
<ui:VisualElement name="equips-container" style="position: absolute; right: 64px; bottom: 32px; align-items: flex-end;">
|
||||
<ui:VisualElement name="equips-container" class="swap" style="position: absolute; right: 64px; bottom: 32px; align-items: flex-end;">
|
||||
<ui:VisualElement name="equipSelector-container" style="align-items: flex-end;">
|
||||
<ui:Instance template="ItemContainer_256x128px_flat" name="ItemContainer_256x128px_flat" class="active" />
|
||||
<ui:Instance template="ItemContainer_256x128px_flat" name="ItemContainer_256x128px_flat" />
|
||||
|
@ -86,7 +83,7 @@
|
|||
</ui:VisualElement>
|
||||
</ui:VisualElement>
|
||||
</ui:VisualElement>
|
||||
<ui:VisualElement name="player-container" style="width: 404px; height: 194px; position: absolute; left: 80px; top: 844px; padding-left: 0; padding-right: 0; padding-top: 0; padding-bottom: 0;">
|
||||
<ui:VisualElement name="player-container" class="swap" style="width: 404px; height: 194px; position: absolute; left: 80px; top: 844px; padding-left: 0; padding-right: 0; padding-top: 0; padding-bottom: 0;">
|
||||
<ui:VisualElement name="VisualElement" style="height: 24px;" />
|
||||
<ui:VisualElement style="flex-direction: row;">
|
||||
<ui:VisualElement name="playerAvatar-image" style="width: 128px; height: 128px; background-image: url('project://database/Assets/Artists/Arts/Icons/Logo.jpg?fileID=2800000&guid=3ded0edcf28c6794f95fb07d3c684769&type=3#Logo');" />
|
||||
|
|
|
@ -113,12 +113,12 @@ namespace BITKit.Entities
|
|||
/// 基于相对坐标的移动
|
||||
/// </summary>
|
||||
/// <param name="relativeVector"></param>
|
||||
void Movement(Vector3 relativeVector);
|
||||
void OnMovement(Vector3 relativeVector);
|
||||
/// <summary>
|
||||
/// 基于InputAction的移动
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
void Movement(InputAction.CallbackContext context);
|
||||
void OnMovement(InputAction.CallbackContext context);
|
||||
/// <summary>
|
||||
/// 执行命令
|
||||
/// </summary>
|
||||
|
|
|
@ -35,11 +35,11 @@ namespace BITKit.Entities.Movement
|
|||
{
|
||||
}
|
||||
|
||||
public void Movement(Vector3 relativeVector)
|
||||
public void OnMovement(Vector3 relativeVector)
|
||||
{
|
||||
}
|
||||
|
||||
public void Movement(InputAction.CallbackContext context)
|
||||
public void OnMovement(InputAction.CallbackContext context)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -1,663 +0,0 @@
|
|||
# Fast/Live Script Reload
|
||||
|
||||
Tool will allow you to iterate quicker on your code. You simply go into play mode, make a change to any file and it'll be compiled on the fly and hot-reloaded in your running play-mode session.
|
||||
|
||||
## Getting started
|
||||
1) Import
|
||||
2) Welcome screen will open - it contains all needed info to get started as well as support links and configuration.
|
||||
`You can always get back to this screen via 'Window -> Fast/Live Script Reload -> Start Screen'`
|
||||
3) Go to Launch Demo -> Basic Example window
|
||||
4) Follow instructions listed there
|
||||
|
||||
> During startup asset will check if Unity Auto Refresh is enabled and offer to adjust it in order to work properly. Depending on your workflow you want it in 'EnabledOutsidePlaymode' or 'Disabled' - this is to ensure Unity will not trigger script compilation on changes and will let Fast Script Reload to work.
|
||||
|
||||
> Sometimes Unity can be quite stubborn and try to auto-reload scripts even with Auto-Reload turned off, if you can still see standard 'Reloading script assemblies' progress bar on change please go to:
|
||||
> 'Window -> Fast Script Reload -> Start Screen -> Reload -> Force prevent assembly reload during playmode'.
|
||||
> This way tool will lock reload in code when you enter playmode.
|
||||
|
||||
```
|
||||
Example scene 'Point' material should automatically detect URP or surface shader
|
||||
If it shows pink, please adjust by picking shader manually:
|
||||
1) URP: 'Shader Graphs/Point URP'
|
||||
2) Surface: 'Graph/Point Surface'
|
||||
```
|
||||
|
||||
## On-Device Hot-Reload - Live Script reload
|
||||
There's an addon to this tool - Live Script Reload - that'll allow you to use same functionality over the network in device build, eg:
|
||||
- android (including VR headsets like Quest 2)
|
||||
- standalone windows
|
||||
|
||||
[Find more details here](https://immersivevrtools.com/redirect/fast-script-reload/live-script-reload-extension)
|
||||
|
||||
**Live Script Reload is using Fast Script Reload as a base asset - documentation is combined, if you don't use Live Script Reload you can skip any sections in this document prefixed with [Live-Reload]**
|
||||
|
||||
## How Unity Hot Reload functionality is made? Technical Approach Breakdown
|
||||
If you'd like to know how tool works from technical point of view - have a look at blog series which gets deeper into the subject:
|
||||
|
||||
[1) How to build Hot Reload For Unity](https://immersivevrtools.com/Blog/how-to-build-hot-reload-functionality-for-unity)
|
||||
|
||||
[2) Hot Reload - on device, (eg Android / Windows Exe)](https://immersivevrtools.com/Blog/how-to-build-unity-hot-reload-on-device)
|
||||
|
||||
## Reporting Compilation Errors
|
||||
I've put lots of effort to test various code patterns in various codebases. Still - it's possible you'll find some instances where code would not compile, it's easiest to:
|
||||
1) Look at compiler error and compare with generated source code, usually it'll be very obvious why issue is occuring
|
||||
2) Refactor problematic part (look at limitations section as they'll explain how)
|
||||
3) Let me know via support email and I'll get it fixed
|
||||
|
||||
## Executing custom code on hot reload
|
||||
Custom code can be executed on hot reload by adding a method to changed script.
|
||||
|
||||
**You can see example by adjusting code in 'Graph.cs' file.**
|
||||
|
||||
```
|
||||
void OnScriptHotReload()
|
||||
{
|
||||
//do whatever you want to do with access to instance via 'this'
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
static void OnScriptHotReloadNoInstance()
|
||||
{
|
||||
//do whatever you want to do without instance
|
||||
//useful if you've added brand new type
|
||||
// or want to simply execute some code without |any instance created.
|
||||
//Like reload scene, call test function etc
|
||||
}
|
||||
```
|
||||
|
||||
## EXPERIMENTAL Adding New Fields
|
||||
Asset has an experimental support for adding new fields at runtime which will also render in Editor and allow you to tweak values - same as with normal fields.
|
||||
|
||||
To enable, please:
|
||||
`Window -> Fast Script Reload -> Start Window -> New Fields -> Enable experimental added field support`.
|
||||
|
||||
> As this is an experimental feature please expect it to break more often! It'd be great help if you could report any issues via Discord / email.
|
||||
|
||||
### New Fields - specific limitations
|
||||
|
||||
- outside classes can not call new fields added at runtime
|
||||
- new fields will only show in editor if they were already used at least once
|
||||
- eg if you've added a variable into a method, on first call that variable will be initialized and will start showing in editor
|
||||
|
||||
### New Fields - performance
|
||||
For new fields to work your code will be adjusted. Instead of calling fields directly your code will call into a method that retrieves value from dynamic dictionary.
|
||||
|
||||
Due to that there'll be some overhead with:
|
||||
- looking up proper object and new-field value in dictionary
|
||||
- initializing values
|
||||
- use of dynamic type, which will introduce some additional casts
|
||||
|
||||
> All that shouldn't really add too much overhead on dev-machine - you may lose few FPS though.
|
||||
|
||||
|
||||
## User Defined Script Overrides
|
||||
For asset to hot reload your changes it needs to make some adjustments to code. This could cause some issues as described in limitations section.
|
||||
|
||||
I continuously work on mitigating limitations. When they happen you can help yourself quickly by creating user defined script override.
|
||||
|
||||
Those are simply overrides on a per-file / method basis - when specified their contents will be used for final source code. Allowing you to simply fix any issues.
|
||||
This is especially helpful with larger files.
|
||||
|
||||
### Replacing methods
|
||||
You can replace existing methods by specifying their full signature in correct type - contents will be then replaced.
|
||||
> If no match for the method signature is found it'll be ignored
|
||||
|
||||
For example, let's look at limitation with assigning Singleton to Instance using 'this' keyword
|
||||
> That limitation is already solved although it'll serve as a clean example illustrating the feature
|
||||
|
||||
Following code:
|
||||
```
|
||||
public class MySingleton: MonoBehaviour {
|
||||
public static MySingleton Instance;
|
||||
|
||||
void Start() {
|
||||
Instance = this;
|
||||
}
|
||||
|
||||
void SomeOtherMethod() {
|
||||
//some other logic
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Would be rewritten to:
|
||||
```
|
||||
public class MySingleton__Patched_: MonoBehaviour { //FSR: name change to include __Patched_ postfix to prevent name clashes
|
||||
public static MySingleton Instance;
|
||||
|
||||
void Start() {
|
||||
//assigning 'this' (now of type MySingleton__Patched_) to
|
||||
//'Instance' of type MySingleton will now show compilation error due to type mismach
|
||||
Instance = this;
|
||||
}
|
||||
|
||||
void SomeOtherMethod() {
|
||||
//some other logic
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In that case you can create a user script rewrite override, to do so:
|
||||
1) In Unity project panel right-click on 'MySingleton.cs'
|
||||
2) Select `Fast Script Reload -> Add \ Open User Script Rewrite Override`
|
||||
3) Asset will create override file for you with some template already in (there's also description in top comment how to use)
|
||||
|
||||
Override:
|
||||
```
|
||||
public class MySingleton__Patched_: MonoBehaviour {
|
||||
void Start() { //override will target class specified above and only defined methods, in that case Start()
|
||||
Instance = (MySingleton)(object)this; //contents will be changed as they are visible here, casting to object and then to expected type will correct the issue
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Adding types
|
||||
When your code is recompiled it lands in new assembly. This can cause some issues, for example if your class is using `internal`
|
||||
interface - after recompile it won't be able to access that interface.
|
||||
|
||||
Following code will fail to compile when changing MyClass as it won't be able to access IInterface which is `internal`:
|
||||
```
|
||||
//Defined in file MyClass.cs
|
||||
public class MyClass: IInterface {
|
||||
|
||||
}
|
||||
|
||||
//Defined in other file, IInterface.cs
|
||||
interface IInterface { //no access modifier for interfaces will infer 'internal' - only available to assembly it's defined in.
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
In that case you can create a user script rewrite override, to do so:
|
||||
1) In Unity project panel right-click on 'MyClass.cs'
|
||||
2) Select `Fast Script Reload -> Add \ Open User Script Rewrite Override`
|
||||
3) Asset will create override file for you with some template already in (there's also description in top comment how to use)
|
||||
|
||||
And put following in the override file
|
||||
```
|
||||
interface IInterface {
|
||||
// add any definitions as needed
|
||||
}
|
||||
```
|
||||
|
||||
This will then be simply added to newly compiled file and interface will be accessible.
|
||||
```
|
||||
//now both in same assembly/file when hot reloaded
|
||||
public class MyClass: IInterface {
|
||||
|
||||
}
|
||||
|
||||
|
||||
interface IInterface {
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
### Managing existing User Script Rewrite Overrides
|
||||
You can see all your overrides by:
|
||||
|
||||
1) Right clicking on file in Unity project panel
|
||||
2) Clicking `Fast Script Reload -> Show User Script Rewrite Overrides`
|
||||
|
||||
This will take you to tool settings window. You can also access it via:
|
||||
1) Clicking `Window -> Fast Script Reload -> Start Screen`
|
||||
2) Selecting `User Script Rewrite Overrides` side tab
|
||||
|
||||
## EXPERIMENTAL In-editor Hot Reload (outside of playmode)
|
||||
Asset has an experimental support for hot reload outside of playmode. However with limitations as they are now - it
|
||||
is intended for some specific use cases (like iterating on editor scripts).
|
||||
|
||||
> **Feature is not intended as a replacement for Unity compile / reload mechanism**
|
||||
|
||||
To enable, please:
|
||||
`Window -> Fast Script Reload -> Start Window -> Editor Hot-Reload -> Enable Hot-Reload outside of play mode`.
|
||||
|
||||
> As this is an experimental feature please expect it to break more often! It'd be great help if you could report any issues via Discord / email.
|
||||
|
||||
## Debugging
|
||||
Debugging is fully supported although breakpoints in your original file won't be hit.
|
||||
|
||||
Once change is compiled, you'll get an option to open generated file [via clickable link in console window] in which you can set breakpoints.
|
||||
|
||||
Tool can also auto-open generated files for you on change for simpler access, you can find option via 'Window -> Fast Script Reload -> Start Screen -> Debugging -> Auto open generated source file for debugging'
|
||||
|
||||
> Debugging with Rider for Unity 2019 and 2020 is having some issues, you can only open debuggable files with auto-open feature. Clicking a file in console causes subtle static-variables reload (not full domain reload) that'll break your play-session.
|
||||
|
||||
### Adding Function Breakpoint
|
||||
If for whatever reason debugger breakpoint is not hit you can try setting Function Breakpoint in your IDE.
|
||||
|
||||
For type name you want to include `<OriginalTypeName>__Patched_`, the `__Patched_` postfix is auto-added by asset to prevent name clash.
|
||||
Function name remains unchanged.
|
||||
|
||||
## Production Build
|
||||
For Fast Script Reload asset code will be excluded from any builds.
|
||||
|
||||
For Live Script Reload you should exclude it from final production build, do that via:
|
||||
- 'Window -> Fast Script Reload -> Welcome Screen -> Build -> Enable Hot Reload For Build' - untick
|
||||
|
||||
**When building via File -> Build Settings - you'll also see Live Script Reload status under 'Build' button. You can click 'Adjust' button which will take you to build page for asset.**
|
||||
```This is designed to make sure you don't accidentally build tool into release althoguh best approach would be to ensure your release process takes care of that.```
|
||||
|
||||
## Options
|
||||
You can access Welcome Screen / Options via 'Window -> Fast/Live Script Reload -> Start Screen' - it contains useful information as well as options.
|
||||
```
|
||||
Context menus will be prefixed with used version, either 'Fast Script Reload' or 'Live Script Reload'
|
||||
```
|
||||
|
||||
### Auto Hot-Reload
|
||||
By default tool will pick changes made to any file in playmode. You can add exclusions to that behaviour, more on that later.
|
||||
|
||||
You can also manually manage reload, to do so:
|
||||
1) Un-tick 'Enable auto Hot-Reload for changed files' in Options -> Reload page
|
||||
2) Click Window -> Fast Script Reload -> Force Reload to trigger
|
||||
3) or call `FastScriptReloadManager.TriggerReloadForChangedFiles()` method from code
|
||||
|
||||
> You can also use Editor -> Hotkeys to bind Force Reload to specific key.
|
||||
|
||||
### [Live-Reload] Hot-Reload over Network
|
||||
With on-device build, your code changes will be distributed over the network in real-time.
|
||||
|
||||
By default running application will send a broadcast and try to discover editor running the tool.
|
||||
|
||||
Broadcast is initiated from device where build is running on (not from editor) - this means device running editor needs to allow the connection.
|
||||
|
||||
### [Live-Reload] Troubleshooting network issues
|
||||
If for whatever reason reload over network doesn't work, please:
|
||||
|
||||
1) go to 'Window -> Live Script Reload -> Options/Network'
|
||||
2) make sure port used is not already used by any other application
|
||||
3) make sure your Firewall allows connections on that port
|
||||
4) If you think broadcast doesn't work in your network it's best to specify IP Address explicitly (tick 'Force specific UP address for clients to receive Hot-Reload updates' and add IP)
|
||||
- this will allow client (build on device) connect directly to specified address
|
||||
|
||||
### [Live-Reload] Connected Client
|
||||
In playmode, message will be logged when clients connects. Also Options/Network will display connected client, eg Android phone could be identified as:
|
||||
|
||||
`SM-100232 connected from 192.189.168.68:12548`
|
||||
|
||||
**Only 1 client can be connected at any time.**
|
||||
|
||||
### [Live-Reload] Testing with Editor
|
||||
By default, editor will reflect any changes you made without using network. If you want to force editor to behave as networked client:
|
||||
1) Press play
|
||||
2) Find DontDestroyOnLoadObject 'NetworkedAssemblyChangesLoader' -
|
||||
3) tick 'IsDebug'
|
||||
4) tick 'Editor Acts as Remote Client'
|
||||
5) enable NetworkedAssemblyChangesLoader component
|
||||
|
||||
### Managing file exclusions
|
||||
Files can be excluded from auto-compilation.
|
||||
|
||||
#### via 'Project' context menu
|
||||
1) Right click on any *.cs file
|
||||
2) Click Fast Script Reload
|
||||
3) Add Hot-Reload Exclusion
|
||||
|
||||
*You can remove exclusion from same menu*
|
||||
|
||||
#### via Exclusions page
|
||||
To view all exclusions:
|
||||
1) Right click on any *.cs file
|
||||
2) Click Fast Script Reload
|
||||
3) Click Show Exclusions
|
||||
|
||||
#### via class attribute
|
||||
You can also add `[PreventHotReload]` attribute to a class to prevent hot reload for that class.
|
||||
|
||||
### Batch script changes and reload every N seconds
|
||||
Script will batch all your playmode changes and Hot-Reload them in bulk every 3 seconds - you can change duration from 'Reload' options page.
|
||||
|
||||
### Disable added/removed fields check
|
||||
By default if you add / remove fields, tool will not redirect method calls for recompiled class.
|
||||
This is to ensure there are no issues as that is generally not supported.
|
||||
|
||||
Some assets however will use IL weaving to adjust your classes (eg Mirror) as a post compile step. In that case it's quite likely hot-reload will still work.
|
||||
|
||||
### Managing reference exclusions
|
||||
Asset will reference all .dll files that original code is referencing. In some cases that causes compilation error (eg 'Type XYZ is defined in both assembly a.dll and b.dll).
|
||||
You can use those options to exclude specific references from being added.
|
||||
|
||||
> 'Start Screen -> Exclude References (Advanced) -> adjust as needed'.
|
||||
|
||||
## Performance
|
||||
|
||||
Your app performance won't be affected in any meaningful way.
|
||||
Biggest bit is additional memory used for your re-compiled code.
|
||||
Won't be visible unless you make 100s of changes in same play-session.
|
||||
|
||||
### File Watchers Performance Overhead
|
||||
In some cases watching for file changes is causing significant performance overhead. This is down to the Unity FileWatcher which I'm unable to change or provide suitable replacement for. If you're experiencing this issue please go to `Window -> Fast Script Reload -> File Watcher (Advanced Setup)` and narrow down watchers to specific path where you're working in. You can watch multiple folders in this manner.
|
||||
|
||||
## LIMITATIONS -please make sure to read those
|
||||
There are some limitation due to the approach taken to Hot-Reload your scripts. I've tried to minimise the impact to standard dev-workflow as much as possible.
|
||||
|
||||
In some cases however you may need to use workarounds as described below.
|
||||
|
||||
> In most cases you'll be able to use [User Defined Script Overrides](#user-defined-script-overrides) to overcome limitations and make hot reload code compilable.
|
||||
|
||||
### Generic methods and classes won't be Hot-Reloaded
|
||||
Unfortunately generics will not be Hot-Reloaded, to workaround you'd need to move code to non-generic class / method.
|
||||
|
||||
Tool will try to change non-generic methods in those files and will simply skip generic ones.
|
||||
|
||||
*Note - you can still Hot-Reload for class implementations that derive from generic base class but are not generic themselves, eg.*
|
||||
```
|
||||
|
||||
class SingletonImplementation: SingletonBase<SomeConcreteType> {
|
||||
public void SomeSpecificFunctionality() {
|
||||
//you can change code here and it'll be Hot-Reloaded as type itself is not generic
|
||||
}
|
||||
|
||||
public void GenericMethod<T>(T arg) {
|
||||
//changes here won't be Hot-Reloaded as method is generic
|
||||
}
|
||||
}
|
||||
|
||||
class SingletonBase<T> where T: new() {
|
||||
public T Instance;
|
||||
|
||||
public void Init() {
|
||||
Instance = new T(); //if you change this code it won't be Hot-Reloaded as it's in generic type
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Adding new fields
|
||||
Experimental support with 1.3, minor limitations remaining:
|
||||
- outside classes can not call new fields added at runtime
|
||||
- new fields will only show in editor if they were already used at least once
|
||||
|
||||
> You need to opt in via start screen -> 'Window -> Fast Script Reload -> Start Screen -> New Fields -> enable'!
|
||||
|
||||
### Passing `this` reference to method that expect concrete class implementation
|
||||
|
||||
|
||||
`**By default experimental setting 'Enable method calls with 'this' as argument fix' is turned on in options, and should fix 'this' calls/assignment issue.
|
||||
If you see issues with that please turn setting off and get in touch via support email.**
|
||||
|
||||
Unless experimental setting is on - it'll throw compilation error `The best overloaded method match for xxx has some invalid arguments` - this is due to the fact that changed code is technically different type.
|
||||
The code will need to be adjusted to depend on some abstraction instead (before hot-reload).
|
||||
|
||||
This code would cause the above error.
|
||||
```
|
||||
public class EnemyController: MonoBehaviour {
|
||||
EnemyManager m_EnemyManager;
|
||||
|
||||
void Start()
|
||||
{
|
||||
//calling 'this' causes issues as after hot-reload the type of EnemyController will change to 'EnemyController__Patched_'
|
||||
m_EnemyManager.RegisterEnemy(this);
|
||||
}
|
||||
}
|
||||
|
||||
public class EnemyManager : MonoBehaviour {
|
||||
public void RegisterEnemy(EnemyController enemy) { //RegisterEnemy method expects parameter of concrete type (EnemyController)
|
||||
//impementation
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
It could be changed to support Hot-Reload in following way:
|
||||
|
||||
|
||||
1) Don't depend on concrete implementations, instead use interfaces/abstraction
|
||||
```
|
||||
public class EnemyController: MonoBehaviour, IRegistrableEnemy {
|
||||
EnemyManager m_EnemyManager;
|
||||
|
||||
void Start()
|
||||
{
|
||||
//calling this causes issues as after hot-reload the type of EnemyController will change
|
||||
m_EnemyManager.RegisterEnemy(this);
|
||||
}
|
||||
}
|
||||
|
||||
public class EnemyManager : MonoBehaviour {
|
||||
public void RegisterEnemy(IRegistrableEnemy enemy) { //Using interface will go around error
|
||||
//impementation
|
||||
}
|
||||
}
|
||||
|
||||
public interface IRegistrableEnemy
|
||||
{
|
||||
//implementation
|
||||
}
|
||||
```
|
||||
|
||||
2) Adjust method param to have common base class
|
||||
```
|
||||
public class EnemyManager : MonoBehaviour {
|
||||
public void RegisterEnemy(MonoBehaviour enemy) { //Using common MonoBehaviour will go around error
|
||||
//impementation
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Assigning `this` to a field references
|
||||
Similar as above, this could cause some trouble although 'Enable method calls with 'this' as argument fix' setting will fix most of the issues.
|
||||
|
||||
Especially visible with singletons.
|
||||
eg.
|
||||
|
||||
```
|
||||
public class MySingleton: MonoBehaviour {
|
||||
public static MySingleton Instance;
|
||||
|
||||
void Start() {
|
||||
Instance = this;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Calling internal class members from changed code
|
||||
> You can use [User Defined Script Overrides](#user-defined-script-overrides) to overcome this limitation
|
||||
|
||||
Technically, once your changed code is compiled it'll be in a separate assembly. As a result this changed code won't be able to access internal classes from assembly it originated from.
|
||||
|
||||
### Extensive use of nested classed / structs
|
||||
> You can use [User Defined Script Overrides](#user-defined-script-overrides) to overcome this limitation
|
||||
|
||||
If your code-base contains lots of nested classes - you may see more compilation errors.
|
||||
|
||||
Easy workaround is to move those nested classes away so they are top-level.
|
||||
|
||||
### Creating new public methods
|
||||
Hot-reload for new methods will only work with private methods (only called by changed code).
|
||||
|
||||
### Adding new references
|
||||
When you're trying to reference new code in play-mode session that'll fail if assembly is not yet referencing that (most often happens when using AsmDefs that are not yet referencing each other)
|
||||
|
||||
### Changing class that uses extension and passes itself as a reference
|
||||
> You can use [User Defined Script Overrides](#user-defined-script-overrides) to overcome this limitation
|
||||
|
||||
Changing class that uses extension method and passes itself as a reference will create compiler error.
|
||||
|
||||
Generally that shouldn't be an issue, extension methods are primarily used as a syntatic sugar to extend a class that you do not have access to.
|
||||
You shouldn't need to create extension methods for types you own (instead those are generally instance methods or base class methods).
|
||||
|
||||
Given example:
|
||||
```
|
||||
public class ExtensionMethodTest
|
||||
{
|
||||
public string Name;
|
||||
|
||||
void Update()
|
||||
{
|
||||
this.PrintName();
|
||||
}
|
||||
}
|
||||
|
||||
//separate extension file
|
||||
public static ExtensionMethodTestExtensions
|
||||
{
|
||||
public static void PrintName(this ExtensionMethodTest obj)
|
||||
{
|
||||
Debug.Log(obj.Name);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
When changing `ExtensionMethodTest` you'll get compile error. Workaround would be to include method call in your type, eg:
|
||||
```
|
||||
public class ExtensionMethodTest {
|
||||
public string Name;
|
||||
|
||||
void Update() {
|
||||
this.PrintName();
|
||||
}
|
||||
|
||||
private void PrintName() {
|
||||
Debug.Log(Name);
|
||||
}
|
||||
}
|
||||
```
|
||||
*Arguably that's what should be done in the first place.*
|
||||
|
||||
Adjusting classes that use extension methods without passing itself as a reference - will work correctly. eg:
|
||||
```
|
||||
public class ObjectFromExternalAssembly()
|
||||
{
|
||||
//included just to illustrate example, that'd be in compiled assembly
|
||||
//that you can't change and use extension method approach
|
||||
|
||||
public string Name;
|
||||
}
|
||||
|
||||
public class ExtensionMethodTester
|
||||
{
|
||||
void Update()
|
||||
{
|
||||
var t = new ExtensionMethodTest();
|
||||
t.PrintName()
|
||||
}
|
||||
}
|
||||
|
||||
//separate extension file
|
||||
public static ObjectFromExternalAssemblyExtensions
|
||||
{
|
||||
public static void PrintName(this ObjectFromExternalAssembly obj)
|
||||
{
|
||||
Debug.Log(obj.Name);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Changing class that implements internal interface can trigger compilation error
|
||||
> You can use [User Defined Script Overrides](#user-defined-script-overrides) to overcome this limitation
|
||||
|
||||
If class is implementing interface that's defined in different file as internal (default for no access modifier) - then changes to that class will fail to compile.
|
||||
|
||||
eg.
|
||||
```
|
||||
//file IInterface.cs
|
||||
interface IInterface { //default interface access-modifier is 'internal'
|
||||
//declaration
|
||||
}
|
||||
|
||||
//file ClassImplementingIInterface.cs
|
||||
class ClassImplementingIInterface: IInterface {
|
||||
//changing this class will cause CS0122 'IInterface' is inaccessible due to it's protection level
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
> Quick workaround is to declare that interface as public
|
||||
>
|
||||
|
||||
### Changing class that accesses `private protected` members
|
||||
> You can use [User Defined Script Overrides](#user-defined-script-overrides) to overcome this limitation
|
||||
|
||||
With C# 7.2 `private protected` access modifier was introduced. It works as `protected` access modifier in a sense that inherited classes can access it but.
|
||||
Addition of `private` also limits it to same assembly. Your changes are technically compiled into separate assembly and at the moment trying to access `private protected` in changed code will produce compiler error.
|
||||
|
||||
> Easiest workaround for now is to declare those `private protected` members as `protected`.
|
||||
|
||||
### Limited debugger support for Rider when using Unity 2019 and 2020
|
||||
Once breakpoint has been hit it'll stop asset from hot-reloading in that play-session. Newer Unity versions are supporting debugging.
|
||||
|
||||
### No IL2CPP support
|
||||
Asset runs based on specific .NET functionality, IL2CPP builds will not be supported. Although as this is development workflow aid you can build your APK with Mono backend (android) and change later.
|
||||
|
||||
### Partial classes
|
||||
Partial classes are not yet supported
|
||||
|
||||
## FAQ
|
||||
### How is this asset different than Live Script Reload?
|
||||
Fast Script Reload
|
||||
- hot reload in editor
|
||||
|
||||
Live Script Reload
|
||||
- same hot reload in editor as FSR (includes FSR)
|
||||
+ **AND hot reload directly in builds / on-device**
|
||||
+ you don't have to download them both separately, LSR is also 1 package import
|
||||
+ standalone extension priced at $35, or if you've already bought Fast Script Reload it's $5 upgrade
|
||||
|
||||
### Editor makes full reload on any change in playmode
|
||||
|
||||
Unity Editor has an option to auto recompile changes. **For tool to work properly you want to have that either disabled or enabled only outside of playmode.**
|
||||
|
||||
You can adjusted auto-reload at any time via `Edit -> Preferences -> Asset Pipeline -> Auto Refresh`.
|
||||
|
||||
*Tool will also offer to disable auto-refresh on startup.*
|
||||
|
||||
It's possible to set auto-refresh to enabled but only outside of playmode. Depending on editor version used this can be found in:
|
||||
- `Edit -> Preferences -> General -> Script Changes While Playing -> Recompile After Finished Playing`
|
||||
- or `Edit -> Preferences -> Asset Pipeline -> Auto Refresh -> Enabled Outside Playmode`
|
||||
|
||||
### VS Code shows console shows errors
|
||||
VS Code proj file (csproj) generate with NetFramework version 4.7.1. One of the plugin DLLs is targeting version 4.8.
|
||||
Even though VS Code does not compile that code (Unity does) - it'll still show error as it didn't load the library for code-completion.
|
||||
|
||||
It's not a plugin issue as such, other dlls will have same troubles.
|
||||
Best solution I've found is to force csproj files to be generated with 4.8 version instead.
|
||||
This can be achieved with following editor script
|
||||
```
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
public class VisualStudioProjectGenerationPostProcess : AssetPostprocessor
|
||||
{
|
||||
private static void OnGeneratedCSProjectFiles()
|
||||
{
|
||||
Debug.Log("OnGeneratedCSProjectFiles");
|
||||
var dir = Directory.GetCurrentDirectory();
|
||||
var files = Directory.GetFiles(dir, "*.csproj");
|
||||
foreach (var file in files)
|
||||
ChangeTargetFrameworkInfProjectFiles(file);
|
||||
}
|
||||
|
||||
static void ChangeTargetFrameworkInfProjectFiles(string file)
|
||||
{
|
||||
var text = File.ReadAllText(file);
|
||||
var find = "TargetFrameworkVersion>v4.7.1</TargetFrameworkVersion";
|
||||
var replace = "TargetFrameworkVersion>v4.8</TargetFrameworkVersion";
|
||||
|
||||
if (text.IndexOf(find) != -1)
|
||||
{
|
||||
text = Regex.Replace(text, find, replace);
|
||||
File.WriteAllText(file, text);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
> As a one-off you may also need to go to Edit -> Preferences -> External Tools -> click 'Regenerate project files' buttons
|
||||
|
||||
### When importing I'm getting error: 'Unable to update following assemblies: (...)/ImmersiveVRTools.Common.Runtime.dll'
|
||||
This happens occasionally, especially on upgrade between versions. It's harmless error that'll go away on play mode.
|
||||
|
||||
### When upgrading between versions, eg 1.1 to 1.2 example scene cubes are pink
|
||||
This is down to reimporting 'Point' prefab. Right now plugin will make sure it's using correct shader eg. URP / Built-in but only on initial import.
|
||||
|
||||
To fix please go to `FastScripReload\Examples\Point\Point.prefab` and search for 'Point' shader.
|
||||
|
||||
## Roadmap
|
||||
- ~~add Mac/Linux support~~ (added with 1.1)
|
||||
- ~~add debugger support for hot-reloaded scripts~~ (added with 1.2)
|
||||
- ~~allow to add new fields (adjustable in Editor)~~ (added with 1.3)
|
||||
- better compiler support to work around limitations
|
Binary file not shown.
|
@ -1,33 +0,0 @@
|
|||
1.4
|
||||
- added experimental editor-mode hot-reload, you'll need to opt in via Start Screen -> Editor Hot-Reload (please read the notes on that page, playmode workflow is still far superior)
|
||||
- you can now define User Script Rewrite Overrides - which will allow you to overcome most of existing limitations that cause compilation errors (on a one by one basis)
|
||||
- big optimization in how long hot-reload part will take, first call will be unaffected when type cache is build but subsequent calls will be significantly quicker
|
||||
- added 'Exclude References' options - this allows to remove specific dll references from dynamically compiled code (as in some cases you may get 'type defined in both assembly x.dll and y.dll'
|
||||
- destructors will no longer cause compilation error
|
||||
- (options opt-in) script rewriting can optionally emit comment - why change was made to help with troubleshooting issues
|
||||
- Unity assembly reload can be forced off via LockAssemblyReload if specified in options - sometimes even though Auto-Refresh is turned off Editor still tries to recompile changes in playmode which prevents FSR from working
|
||||
- new fields support (experimental) will be enabled by default
|
||||
|
||||
1.3 - (Experimental) - New fields added in playmode
|
||||
New fields can be added and used in code
|
||||
New fields can be adjusted in editor (same as standard fields)
|
||||
New fields will be initialized to whatever value is specified in code or default value
|
||||
Experimental feature - at this stage expect some issues
|
||||
Opt-in - disabled by default to enable go to 'Window -> Fast Script Reload -> Start Screen -> New Fields -> enable'
|
||||
|
||||
1.2 - Debugger support
|
||||
- added debugger support
|
||||
|
||||
1.1 - Mac support / bug fixes
|
||||
**Added Mac support (only INTEL editor version, SILICON still not supported)**
|
||||
Added Linux support
|
||||
fixed namespace clash with Unity.Collections package
|
||||
common code lib will not be included in builds
|
||||
added check for auto-refresh in Editor - will proide guidance and option to adjust as otherwise full editor reload is triggered for changes
|
||||
added workaround for Unity file-watcher returning wrong file path on some editor versions
|
||||
added option to allow disabling DidFieldCountCheck - allowing to detour methods in those cases (eg for Mirror where it'll adjust IL and cause mismatch)
|
||||
added option to configure FileWatcher paths/filters as in some cases watching root directory was causing performance issues
|
||||
added minor initial-load optimisations - using session-state for items that do not need to be resolved on every reload
|
||||
|
||||
1.0 - First release, included features:
|
||||
Fast script reload in editor / play-mode (compiles only changed files and hot-reloads them into current play session)
|
|
@ -1,21 +0,0 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2017 Andreas Pardeike
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
|
@ -1,3 +0,0 @@
|
|||
This code is part of the product and can be used with same licence as the one you purchased it with.
|
||||
|
||||
Left in Plugins folder solely for convenience.
|
Binary file not shown.
|
@ -1 +0,0 @@
|
|||
library is excluded from any compilation by default. It's only purpose is to serve as a reference for dynamic keyword when targetting NET Standard 2.1 - as this is required by ThisRewriters to function properly
|
Binary file not shown.
Binary file not shown.
|
@ -1,23 +0,0 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) .NET Foundation and Contributors
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,84 +0,0 @@
|
|||
using System.Linq;
|
||||
using FastScriptReload.Runtime;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
|
||||
namespace FastScriptReload.Editor.Compilation.CodeRewriting
|
||||
{
|
||||
class ConstructorRewriter : FastScriptReloadCodeRewriterBase
|
||||
{
|
||||
private readonly bool _adjustCtorOnlyForNonNestedTypes;
|
||||
|
||||
public ConstructorRewriter(bool adjustCtorOnlyForNonNestedTypes, bool writeRewriteReasonAsComment)
|
||||
: base(writeRewriteReasonAsComment)
|
||||
{
|
||||
_adjustCtorOnlyForNonNestedTypes = adjustCtorOnlyForNonNestedTypes;
|
||||
}
|
||||
|
||||
public override SyntaxNode VisitConstructorDeclaration(ConstructorDeclarationSyntax node)
|
||||
{
|
||||
if (_adjustCtorOnlyForNonNestedTypes)
|
||||
{
|
||||
var typeNestedLevel = node.Ancestors().Count(a => a is TypeDeclarationSyntax);
|
||||
if (typeNestedLevel == 1)
|
||||
{
|
||||
return AdjustCtorOrDestructorNameForTypeAdjustment(node, node.Identifier);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return AdjustCtorOrDestructorNameForTypeAdjustment(node, node.Identifier);
|
||||
}
|
||||
|
||||
return base.VisitConstructorDeclaration(node);
|
||||
}
|
||||
|
||||
public override SyntaxNode VisitDestructorDeclaration(DestructorDeclarationSyntax node)
|
||||
{
|
||||
if (_adjustCtorOnlyForNonNestedTypes)
|
||||
{
|
||||
var typeNestedLevel = node.Ancestors().Count(a => a is TypeDeclarationSyntax);
|
||||
if (typeNestedLevel == 1)
|
||||
{
|
||||
return AdjustCtorOrDestructorNameForTypeAdjustment(node, node.Identifier);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return AdjustCtorOrDestructorNameForTypeAdjustment(node, node.Identifier);
|
||||
}
|
||||
|
||||
return base.VisitDestructorDeclaration(node);
|
||||
}
|
||||
|
||||
private SyntaxNode AdjustCtorOrDestructorNameForTypeAdjustment(BaseMethodDeclarationSyntax node, SyntaxToken nodeIdentifier)
|
||||
{
|
||||
var typeName = (node.Ancestors().First(n => n is TypeDeclarationSyntax) as TypeDeclarationSyntax).Identifier.ToString();
|
||||
if (!nodeIdentifier.ToFullString().Contains(typeName))
|
||||
{
|
||||
//Used Roslyn version bug, some static methods are also interpreted as ctors, eg
|
||||
// public static void Method()
|
||||
// {
|
||||
// Bar(); //treated as Ctor declaration...
|
||||
// }
|
||||
//
|
||||
// private static void Bar()
|
||||
// {
|
||||
//
|
||||
// }
|
||||
return node;
|
||||
}
|
||||
|
||||
if (!typeName.EndsWith(AssemblyChangesLoader.ClassnamePatchedPostfix))
|
||||
{
|
||||
typeName += AssemblyChangesLoader.ClassnamePatchedPostfix;
|
||||
}
|
||||
|
||||
return AddRewriteCommentIfNeeded(
|
||||
node.ReplaceToken(nodeIdentifier, SyntaxFactory.Identifier(typeName)),
|
||||
$"{nameof(ConstructorRewriter)}:{nameof(AdjustCtorOrDestructorNameForTypeAdjustment)}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,150 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using ImmersiveVrToolsCommon.Runtime.Logging;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
|
||||
namespace FastScriptReload.Editor.Compilation.CodeRewriting
|
||||
{
|
||||
class CreateNewFieldInitMethodRewriter: FastScriptReloadCodeRewriterBase {
|
||||
private readonly Dictionary<string, List<string>> _typeToNewFieldDeclarations;
|
||||
private static readonly string NewFieldsToCreateValueFnDictionaryFieldName = "__Patched_NewFieldNameToInitialValueFn";
|
||||
private static readonly string NewFieldsToGetTypeFnDictionaryFieldName = "__Patched_NewFieldsToGetTypeFnDictionaryFieldName";
|
||||
private static readonly string DictionaryFullNamespaceTypeName = "System.Collections.Generic.Dictionary";
|
||||
|
||||
public static Dictionary<string, Func<object>> ResolveNewFieldsToCreateValueFn(Type forType)
|
||||
{
|
||||
return (Dictionary<string, Func<object>>) forType.GetField(NewFieldsToCreateValueFnDictionaryFieldName, BindingFlags.NonPublic | BindingFlags.Static).GetValue(null);
|
||||
}
|
||||
|
||||
public static Dictionary<string, Func<object>> ResolveNewFieldsToTypeFn(Type forType)
|
||||
{
|
||||
return (Dictionary<string, Func<object>>) forType.GetField(NewFieldsToGetTypeFnDictionaryFieldName, BindingFlags.NonPublic | BindingFlags.Static).GetValue(null);
|
||||
}
|
||||
|
||||
public CreateNewFieldInitMethodRewriter(Dictionary<string, List<string>> typeToNewFieldDeclarations, bool writeRewriteReasonAsComment)
|
||||
:base(writeRewriteReasonAsComment)
|
||||
{
|
||||
_typeToNewFieldDeclarations = typeToNewFieldDeclarations;
|
||||
}
|
||||
|
||||
public override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node)
|
||||
{
|
||||
var fullClassName = RoslynUtils.GetMemberFQDN(node, node.Identifier.ToString());
|
||||
if (!_typeToNewFieldDeclarations.TryGetValue(fullClassName, out var newClassFields))
|
||||
{
|
||||
LoggerScoped.LogWarning($"Unable to find new-fields for type: {fullClassName}, this is not an issue if there are no new fields for that type.");
|
||||
}
|
||||
|
||||
Func<FieldDeclarationSyntax, ExpressionSyntax> getObjectFnSyntax = fieldDeclarationNode => fieldDeclarationNode.Declaration.Variables[0].Initializer?.Value //value captured from initializer
|
||||
?? SyntaxFactory.DefaultExpression(SyntaxFactory.IdentifierName(fieldDeclarationNode.Declaration.Type.ToString()));
|
||||
var withDictionaryFieldNameToInitFieldValue = CreateNewFieldNameToGetObjectFnDictionary(node, newClassFields, getObjectFnSyntax, NewFieldsToCreateValueFnDictionaryFieldName);
|
||||
|
||||
Func<FieldDeclarationSyntax, ExpressionSyntax> getObjectTypeFnSyntax = fieldDeclarationNode => SyntaxFactory.TypeOfExpression(fieldDeclarationNode.Declaration.Type);
|
||||
return CreateNewFieldNameToGetObjectFnDictionary(withDictionaryFieldNameToInitFieldValue, newClassFields, getObjectTypeFnSyntax, NewFieldsToGetTypeFnDictionaryFieldName);
|
||||
}
|
||||
|
||||
private ClassDeclarationSyntax CreateNewFieldNameToGetObjectFnDictionary(ClassDeclarationSyntax node,
|
||||
List<string> newClassFields, Func<FieldDeclarationSyntax, ExpressionSyntax> getObjectFnSyntax, string dictionaryFieldName)
|
||||
{
|
||||
var dictionaryKeyToInitValueNodes = newClassFields.SelectMany(fieldName =>
|
||||
{
|
||||
var fieldDeclarationNode = node.ChildNodes().OfType<FieldDeclarationSyntax>()
|
||||
.Single(f => f.Declaration.Variables.First().Identifier.ToString() == fieldName);
|
||||
|
||||
return new[]
|
||||
{
|
||||
(SyntaxNodeOrToken)SyntaxFactory.AssignmentExpression(
|
||||
SyntaxKind.SimpleAssignmentExpression,
|
||||
SyntaxFactory.ImplicitElementAccess()
|
||||
.WithArgumentList(
|
||||
SyntaxFactory.BracketedArgumentList(
|
||||
SyntaxFactory.SingletonSeparatedList<ArgumentSyntax>(
|
||||
SyntaxFactory.Argument(
|
||||
SyntaxFactory.LiteralExpression(
|
||||
SyntaxKind.StringLiteralExpression,
|
||||
SyntaxFactory.Literal(fieldDeclarationNode.Declaration.Variables.First()
|
||||
.Identifier.ToString())))))), //variable name
|
||||
SyntaxFactory.ParenthesizedLambdaExpression(getObjectFnSyntax(fieldDeclarationNode))),
|
||||
SyntaxFactory.Token(SyntaxKind.CommaToken) //comma, add for all
|
||||
};
|
||||
}).ToArray();
|
||||
|
||||
|
||||
var dictionaryInitializer =
|
||||
SyntaxFactory.InitializerExpression(
|
||||
SyntaxKind.ObjectInitializerExpression,
|
||||
SyntaxFactory.SeparatedList<ExpressionSyntax>(
|
||||
dictionaryKeyToInitValueNodes.ToArray()
|
||||
));
|
||||
|
||||
var withDictionaryFieldNameToInitFieldValue = node.AddMembers(
|
||||
SyntaxFactory.FieldDeclaration(
|
||||
SyntaxFactory.VariableDeclaration(
|
||||
SyntaxFactory.GenericName(
|
||||
SyntaxFactory.Identifier(DictionaryFullNamespaceTypeName))
|
||||
.WithTypeArgumentList(
|
||||
SyntaxFactory.TypeArgumentList(
|
||||
SyntaxFactory.SeparatedList<TypeSyntax>(
|
||||
new SyntaxNodeOrToken[]
|
||||
{
|
||||
SyntaxFactory.PredefinedType(
|
||||
SyntaxFactory.Token(SyntaxKind.StringKeyword)),
|
||||
SyntaxFactory.Token(SyntaxKind.CommaToken),
|
||||
SyntaxFactory.GenericName(
|
||||
SyntaxFactory.Identifier("System.Func"))
|
||||
.WithTypeArgumentList(
|
||||
SyntaxFactory.TypeArgumentList(
|
||||
SyntaxFactory.SingletonSeparatedList<TypeSyntax>(
|
||||
SyntaxFactory.PredefinedType(
|
||||
SyntaxFactory.Token(SyntaxKind.ObjectKeyword)))))
|
||||
}))))
|
||||
.WithVariables(
|
||||
SyntaxFactory.SingletonSeparatedList<VariableDeclaratorSyntax>(
|
||||
SyntaxFactory.VariableDeclarator(
|
||||
SyntaxFactory.Identifier(dictionaryFieldName))
|
||||
.WithInitializer(
|
||||
SyntaxFactory.EqualsValueClause(
|
||||
SyntaxFactory.ObjectCreationExpression(
|
||||
SyntaxFactory.GenericName(
|
||||
SyntaxFactory.Identifier(DictionaryFullNamespaceTypeName))
|
||||
.WithTypeArgumentList(
|
||||
SyntaxFactory.TypeArgumentList(
|
||||
SyntaxFactory.SeparatedList<TypeSyntax>(
|
||||
new SyntaxNodeOrToken[]
|
||||
{
|
||||
SyntaxFactory.PredefinedType(
|
||||
SyntaxFactory.Token(SyntaxKind.StringKeyword)),
|
||||
SyntaxFactory.Token(SyntaxKind.CommaToken),
|
||||
SyntaxFactory.QualifiedName(
|
||||
SyntaxFactory.IdentifierName("System"),
|
||||
SyntaxFactory.GenericName(
|
||||
SyntaxFactory.Identifier("Func"))
|
||||
.WithTypeArgumentList(
|
||||
SyntaxFactory.TypeArgumentList(
|
||||
SyntaxFactory
|
||||
.SingletonSeparatedList<
|
||||
TypeSyntax>(
|
||||
SyntaxFactory
|
||||
.PredefinedType(
|
||||
SyntaxFactory.Token(
|
||||
SyntaxKind
|
||||
.ObjectKeyword))))))
|
||||
}))))
|
||||
.WithInitializer(dictionaryInitializer))))))
|
||||
.WithTriviaFrom(node)
|
||||
.WithModifiers(
|
||||
SyntaxFactory.TokenList(
|
||||
SyntaxFactory.Token(SyntaxKind.PrivateKeyword),
|
||||
SyntaxFactory.Token(SyntaxKind.StaticKeyword)))
|
||||
.NormalizeWhitespace()
|
||||
.WithLeadingTrivia(SyntaxFactory.TriviaList(SyntaxFactory.ElasticCarriageReturnLineFeed))
|
||||
.WithTrailingTrivia(SyntaxFactory.TriviaList(SyntaxFactory.ElasticCarriageReturnLineFeed, SyntaxFactory.ElasticCarriageReturnLineFeed))
|
||||
);
|
||||
return AddRewriteCommentIfNeeded(withDictionaryFieldNameToInitFieldValue, $"{nameof(CreateNewFieldInitMethodRewriter)}", true);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,64 +0,0 @@
|
|||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
|
||||
namespace FastScriptReload.Editor.Compilation.CodeRewriting
|
||||
{
|
||||
public abstract class FastScriptReloadCodeRewriterBase : CSharpSyntaxRewriter
|
||||
{
|
||||
protected readonly bool _writeRewriteReasonAsComment;
|
||||
|
||||
protected FastScriptReloadCodeRewriterBase(bool writeRewriteReasonAsComment, bool visitIntoStructuredTrivia = false) : base(visitIntoStructuredTrivia)
|
||||
{
|
||||
_writeRewriteReasonAsComment = writeRewriteReasonAsComment;
|
||||
}
|
||||
|
||||
protected SyntaxToken AddRewriteCommentIfNeeded(SyntaxToken syntaxToken, string commentText, bool append = false)
|
||||
{
|
||||
return AddRewriteCommentIfNeeded(syntaxToken, commentText, _writeRewriteReasonAsComment, append);
|
||||
}
|
||||
|
||||
public static SyntaxToken AddRewriteCommentIfNeeded(SyntaxToken syntaxToken, string commentText, bool writeRewriteReasonAsComment, bool append)
|
||||
{
|
||||
if (writeRewriteReasonAsComment)
|
||||
{
|
||||
if (append)
|
||||
{
|
||||
return syntaxToken.WithLeadingTrivia(
|
||||
syntaxToken.LeadingTrivia.Add(SyntaxFactory.Comment($"/*FSR:{commentText}*/")));
|
||||
}
|
||||
else
|
||||
{
|
||||
return syntaxToken.WithTrailingTrivia(
|
||||
syntaxToken.TrailingTrivia.Add(SyntaxFactory.Comment($"/*FSR:{commentText}*/")));
|
||||
}
|
||||
}
|
||||
|
||||
return syntaxToken;
|
||||
}
|
||||
|
||||
protected T AddRewriteCommentIfNeeded<T>(T syntaxNode, string commentText, bool append = false)
|
||||
where T : SyntaxNode
|
||||
{
|
||||
return AddRewriteCommentIfNeeded(syntaxNode, commentText, _writeRewriteReasonAsComment, append);
|
||||
}
|
||||
|
||||
public static T AddRewriteCommentIfNeeded<T>(T syntaxNode, string commentText, bool writeRewriteReasonAsComment, bool append) where T : SyntaxNode
|
||||
{
|
||||
if (writeRewriteReasonAsComment)
|
||||
{
|
||||
if (append)
|
||||
{
|
||||
return syntaxNode.WithLeadingTrivia(syntaxNode.GetLeadingTrivia()
|
||||
.Add(SyntaxFactory.Comment($"/*FSR:{commentText}*/")));
|
||||
}
|
||||
else
|
||||
{
|
||||
return syntaxNode.WithTrailingTrivia(syntaxNode.GetTrailingTrivia()
|
||||
.Add(SyntaxFactory.Comment($"/*FSR:{commentText}*/")));
|
||||
}
|
||||
}
|
||||
|
||||
return syntaxNode;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
|
||||
namespace FastScriptReload.Editor.Compilation.CodeRewriting
|
||||
{
|
||||
class FieldsWalker : CSharpSyntaxWalker {
|
||||
private readonly Dictionary<string, List<NewFieldDeclaration>> _typeNameToFieldDeclarations = new Dictionary<string, List<NewFieldDeclaration>>();
|
||||
|
||||
public override void VisitClassDeclaration(ClassDeclarationSyntax node)
|
||||
{
|
||||
var className = node.Identifier;
|
||||
var fullClassName = RoslynUtils.GetMemberFQDN(node, className.ToString());
|
||||
if(!_typeNameToFieldDeclarations.ContainsKey(fullClassName)) {
|
||||
_typeNameToFieldDeclarations[fullClassName] = new List<NewFieldDeclaration>();
|
||||
}
|
||||
|
||||
base.VisitClassDeclaration(node);
|
||||
}
|
||||
|
||||
public override void VisitFieldDeclaration(FieldDeclarationSyntax node)
|
||||
{
|
||||
var fieldName = node.Declaration.Variables.First().Identifier.ToString();
|
||||
var fullClassName = RoslynUtils.GetMemberFQDNWithoutMemberName(node);
|
||||
if(!_typeNameToFieldDeclarations.ContainsKey(fullClassName)) {
|
||||
_typeNameToFieldDeclarations[fullClassName] = new List<NewFieldDeclaration>();
|
||||
}
|
||||
|
||||
_typeNameToFieldDeclarations[fullClassName].Add(new NewFieldDeclaration(fieldName, node.Declaration.Type.ToString(), node));
|
||||
|
||||
base.VisitFieldDeclaration(node);
|
||||
}
|
||||
|
||||
public Dictionary<string, List<NewFieldDeclaration>> GetTypeToFieldDeclarations() {
|
||||
return _typeNameToFieldDeclarations;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,69 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using FastScriptReload.Runtime;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
|
||||
namespace FastScriptReload.Editor.Compilation.CodeRewriting
|
||||
{
|
||||
class HotReloadCompliantRewriter : FastScriptReloadCodeRewriterBase
|
||||
{
|
||||
public List<string> StrippedUsingDirectives = new List<string>();
|
||||
|
||||
public HotReloadCompliantRewriter(bool writeRewriteReasonAsComment, bool visitIntoStructuredTrivia = false)
|
||||
: base(writeRewriteReasonAsComment, visitIntoStructuredTrivia)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node)
|
||||
{
|
||||
return AddPatchedPostfixToTopLevelDeclarations(node, node.Identifier);
|
||||
//if subclasses need to be adjusted, it's done via recursion.
|
||||
// foreach (var childNode in node.ChildNodes().OfType<ClassDeclarationSyntax>())
|
||||
// {
|
||||
// var changed = Visit(childNode);
|
||||
// node = node.ReplaceNode(childNode, changed);
|
||||
// }
|
||||
}
|
||||
|
||||
public override SyntaxNode VisitStructDeclaration(StructDeclarationSyntax node)
|
||||
{
|
||||
return AddPatchedPostfixToTopLevelDeclarations(node, node.Identifier);
|
||||
}
|
||||
|
||||
public override SyntaxNode VisitEnumDeclaration(EnumDeclarationSyntax node)
|
||||
{
|
||||
return AddPatchedPostfixToTopLevelDeclarations(node, node.Identifier);
|
||||
}
|
||||
|
||||
public override SyntaxNode VisitDelegateDeclaration(DelegateDeclarationSyntax node)
|
||||
{
|
||||
return AddPatchedPostfixToTopLevelDeclarations(node, node.Identifier);
|
||||
}
|
||||
|
||||
public override SyntaxNode VisitInterfaceDeclaration(InterfaceDeclarationSyntax node)
|
||||
{
|
||||
return AddPatchedPostfixToTopLevelDeclarations(node, node.Identifier);
|
||||
}
|
||||
|
||||
public override SyntaxNode VisitUsingDirective(UsingDirectiveSyntax node)
|
||||
{
|
||||
if (node.Parent is CompilationUnitSyntax)
|
||||
{
|
||||
StrippedUsingDirectives.Add(node.ToFullString());
|
||||
return null;
|
||||
}
|
||||
|
||||
return base.VisitUsingDirective(node);
|
||||
}
|
||||
|
||||
private SyntaxNode AddPatchedPostfixToTopLevelDeclarations(CSharpSyntaxNode node, SyntaxToken identifier)
|
||||
{
|
||||
var newIdentifier = SyntaxFactory.Identifier(identifier + AssemblyChangesLoader.ClassnamePatchedPostfix);
|
||||
newIdentifier = AddRewriteCommentIfNeeded(newIdentifier, $"{nameof(HotReloadCompliantRewriter)}:{nameof(AddPatchedPostfixToTopLevelDeclarations)}");
|
||||
node = node.ReplaceToken(identifier, newIdentifier);
|
||||
return node;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,127 +0,0 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
|
||||
namespace FastScriptReload.Editor.Compilation.CodeRewriting
|
||||
{
|
||||
public class ManualUserDefinedScriptOverridesRewriter : FastScriptReloadCodeRewriterBase
|
||||
{
|
||||
private readonly SyntaxNode _userDefinedOverridesRoot;
|
||||
|
||||
public ManualUserDefinedScriptOverridesRewriter(SyntaxNode userDefinedOverridesRoot, bool writeRewriteReasonAsComment, bool visitIntoStructuredTrivia = false)
|
||||
: base(writeRewriteReasonAsComment, visitIntoStructuredTrivia)
|
||||
{
|
||||
_userDefinedOverridesRoot = userDefinedOverridesRoot;
|
||||
}
|
||||
|
||||
//TODO: refactor to use OverrideDeclarationWithMatchingUserDefinedIfExists
|
||||
public override SyntaxNode VisitConversionOperatorDeclaration(ConversionOperatorDeclarationSyntax node)
|
||||
{
|
||||
var methodFQDN = RoslynUtils.GetMemberFQDN(node, "operator");
|
||||
var matchingInOverride = _userDefinedOverridesRoot.DescendantNodes()
|
||||
//implicit conversion operators do not have name, just parameter list
|
||||
.OfType<BaseMethodDeclarationSyntax>()
|
||||
.FirstOrDefault(m => m.ParameterList.ToString() == node.ParameterList.ToString() //parameter lists is type / order / names, all good for targetting if there's a proper match
|
||||
&& methodFQDN == RoslynUtils.GetMemberFQDN(m, "operator") //make sure same FQDN, even though there's no name there could be more implicit operators in file
|
||||
);
|
||||
|
||||
if (matchingInOverride != null)
|
||||
{
|
||||
return AddRewriteCommentIfNeeded(matchingInOverride.WithTriviaFrom(node), $"User defined custom conversion override", true);
|
||||
}
|
||||
else {
|
||||
return base.VisitConversionOperatorDeclaration(node);
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: refactor to use OverrideDeclarationWithMatchingUserDefinedIfExists
|
||||
public override SyntaxNode VisitMethodDeclaration(MethodDeclarationSyntax node)
|
||||
{
|
||||
var methodName = node.Identifier.ValueText;
|
||||
var methodFQDN = RoslynUtils.GetMemberFQDN(node, node.Identifier.ToString());
|
||||
var matchingInOverride = _userDefinedOverridesRoot.DescendantNodes()
|
||||
.OfType<MethodDeclarationSyntax>()
|
||||
.FirstOrDefault(m => m.Identifier.ValueText == methodName
|
||||
&& m.ParameterList.Parameters.Count == node.ParameterList.Parameters.Count
|
||||
&& m.ParameterList.ToString() == node.ParameterList.ToString() //parameter lists is type / order / names, all good for targetting if there's a proper match
|
||||
&& m.TypeParameterList?.ToString() == node.TypeParameterList?.ToString() //typed paratemets are for generics, also check
|
||||
&& methodFQDN == RoslynUtils.GetMemberFQDN(m, m.Identifier.ToString()) //last check for mathod FQDN (potentially slower than others)
|
||||
);
|
||||
|
||||
if (matchingInOverride != null)
|
||||
{
|
||||
return AddRewriteCommentIfNeeded(matchingInOverride.WithTriviaFrom(node), $"User defined custom method override", true);
|
||||
}
|
||||
else {
|
||||
return base.VisitMethodDeclaration(node);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public override SyntaxNode VisitConstructorDeclaration(ConstructorDeclarationSyntax node)
|
||||
{
|
||||
return OverrideDeclarationWithMatchingUserDefinedIfExists(
|
||||
node,
|
||||
(d) => d.Identifier.ValueText,
|
||||
(d) => HasSameParametersPredicate(node.ParameterList)(d.ParameterList),
|
||||
(d) => base.VisitConstructorDeclaration(d)
|
||||
);
|
||||
}
|
||||
|
||||
public override SyntaxNode VisitDestructorDeclaration(DestructorDeclarationSyntax node)
|
||||
{
|
||||
return OverrideDeclarationWithMatchingUserDefinedIfExists(
|
||||
node,
|
||||
(d) => d.Identifier.ValueText,
|
||||
(d) => HasSameParametersPredicate(node.ParameterList)(d.ParameterList),
|
||||
(d) => base.VisitDestructorDeclaration(d)
|
||||
);
|
||||
}
|
||||
|
||||
public override SyntaxNode VisitPropertyDeclaration(PropertyDeclarationSyntax node)
|
||||
{
|
||||
return OverrideDeclarationWithMatchingUserDefinedIfExists(
|
||||
node,
|
||||
(d) => d.Identifier.ValueText,
|
||||
(d) => true,
|
||||
(d) => base.VisitPropertyDeclaration(d)
|
||||
);
|
||||
}
|
||||
|
||||
private SyntaxNode OverrideDeclarationWithMatchingUserDefinedIfExists<T>(T node, Func<T, string> getName,
|
||||
Func<T, bool> customFindMatchInOverridePredicate, Func<T, SyntaxNode> visitDefault)
|
||||
where T: MemberDeclarationSyntax
|
||||
{
|
||||
var name = getName(node);
|
||||
var fqdn = RoslynUtils.GetMemberFQDN(node, name);
|
||||
var matchingInOverride = _userDefinedOverridesRoot.DescendantNodes()
|
||||
.OfType<T>()
|
||||
.FirstOrDefault(d =>
|
||||
{
|
||||
var declarationName = getName(d);
|
||||
return declarationName == name
|
||||
&& customFindMatchInOverridePredicate(d)
|
||||
&& fqdn == RoslynUtils.GetMemberFQDN(d, declarationName); //last check for mathod FQDN (potentially slower than others)
|
||||
}
|
||||
|
||||
);
|
||||
|
||||
if (matchingInOverride != null)
|
||||
{
|
||||
return AddRewriteCommentIfNeeded(matchingInOverride.WithTriviaFrom(node),
|
||||
$"User defined custom {typeof(T)} override", true);
|
||||
}
|
||||
else
|
||||
{
|
||||
return visitDefault(node);
|
||||
}
|
||||
}
|
||||
|
||||
private Func<ParameterListSyntax, bool> HasSameParametersPredicate(ParameterListSyntax parameters)
|
||||
{
|
||||
return (resolvedParams) => resolvedParams.Parameters.Count == parameters.Parameters.Count
|
||||
&& resolvedParams.ToString() == parameters.ToString();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
|
||||
namespace FastScriptReload.Editor.Compilation.CodeRewriting
|
||||
{
|
||||
public class NewFieldDeclaration
|
||||
{
|
||||
public string FieldName { get; }
|
||||
public string TypeName { get; }
|
||||
public FieldDeclarationSyntax FieldDeclarationSyntax { get; } //TODO: PERF: will that block whole tree from being garbage collected
|
||||
|
||||
public NewFieldDeclaration(string fieldName, string typeName, FieldDeclarationSyntax fieldDeclarationSyntax)
|
||||
{
|
||||
FieldName = fieldName;
|
||||
TypeName = typeName;
|
||||
FieldDeclarationSyntax = fieldDeclarationSyntax;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,135 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using FastScriptReload.Runtime;
|
||||
using FastScriptReload.Scripts.Runtime;
|
||||
using ImmersiveVrToolsCommon.Runtime.Logging;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
|
||||
namespace FastScriptReload.Editor.Compilation.CodeRewriting
|
||||
{
|
||||
class NewFieldsRewriter : FastScriptReloadCodeRewriterBase
|
||||
{
|
||||
private readonly Dictionary<string, List<string>> _typeToNewFieldDeclarations;
|
||||
|
||||
public NewFieldsRewriter(Dictionary<string, List<string>> typeToNewFieldDeclarations, bool writeRewriteReasonAsComment)
|
||||
:base(writeRewriteReasonAsComment)
|
||||
{
|
||||
_typeToNewFieldDeclarations = typeToNewFieldDeclarations;
|
||||
}
|
||||
|
||||
public static List<MemberInfo> GetReplaceableMembers(Type type)
|
||||
{ //TODO: later other might need to be included? props?
|
||||
return type.GetFields(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic).Cast<MemberInfo>().ToList();
|
||||
}
|
||||
|
||||
|
||||
public override SyntaxNode VisitInvocationExpression(InvocationExpressionSyntax node)
|
||||
{
|
||||
if (node.Expression.ToString() == "nameof")
|
||||
{
|
||||
var classNode = node.Ancestors().OfType<ClassDeclarationSyntax>().FirstOrDefault();
|
||||
if (classNode != null)
|
||||
{
|
||||
var fullClassName = RoslynUtils.GetMemberFQDN(classNode, classNode.Identifier.ToString());
|
||||
if (!string.IsNullOrEmpty(fullClassName))
|
||||
{
|
||||
var nameofExpressionParts = node.ArgumentList.Arguments.First().ToFullString().Split('.'); //nameof could have multiple . like NewFieldCustomClass.FieldInThatClass
|
||||
var fieldName = nameofExpressionParts.First(); // should take first part only to determine if new field eg. 'NewFieldCustomClass'
|
||||
if (_typeToNewFieldDeclarations.TryGetValue(fullClassName, out var allNewFieldNamesForClass))
|
||||
{
|
||||
if (allNewFieldNamesForClass.Contains(fieldName))
|
||||
{
|
||||
return AddRewriteCommentIfNeeded(
|
||||
SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal(nameofExpressionParts.Last())), // should take last part only to for actual string eg. 'FieldInThatClass'
|
||||
$"{nameof(NewFieldsRewriter)}:{nameof(VisitInvocationExpression)}");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return base.VisitInvocationExpression(node);
|
||||
}
|
||||
|
||||
public override SyntaxNode VisitIdentifierName(IdentifierNameSyntax node)
|
||||
{
|
||||
var classNode = node.Ancestors().OfType<ClassDeclarationSyntax>().FirstOrDefault();
|
||||
if (classNode != null)
|
||||
{
|
||||
var fullClassName = RoslynUtils.GetMemberFQDN(classNode, classNode.Identifier.ToString());
|
||||
if (!string.IsNullOrEmpty(fullClassName))
|
||||
{
|
||||
var fieldName = node.Identifier.ToString();
|
||||
if (_typeToNewFieldDeclarations.TryGetValue(fullClassName, out var allNewFieldNamesForClass))
|
||||
{
|
||||
if (allNewFieldNamesForClass.Contains(fieldName))
|
||||
{
|
||||
var isNameOfExpression = node.Ancestors().OfType<InvocationExpressionSyntax>().Any(e => e.Expression.ToString() == "nameof");
|
||||
if (!isNameOfExpression) //nameof expression will be rewritten via VisitInvocationExpression
|
||||
{
|
||||
return
|
||||
AddRewriteCommentIfNeeded(
|
||||
SyntaxFactory.MemberAccessExpression(
|
||||
SyntaxKind.SimpleMemberAccessExpression,
|
||||
SyntaxFactory.InvocationExpression(
|
||||
SyntaxFactory.MemberAccessExpression(
|
||||
SyntaxKind.SimpleMemberAccessExpression,
|
||||
SyntaxFactory.IdentifierName(typeof(TemporaryNewFieldValues).FullName),
|
||||
SyntaxFactory.GenericName(
|
||||
SyntaxFactory.Identifier(nameof(TemporaryNewFieldValues.ResolvePatchedObject)))
|
||||
.WithTypeArgumentList(
|
||||
SyntaxFactory.TypeArgumentList(
|
||||
SyntaxFactory.SingletonSeparatedList<TypeSyntax>(
|
||||
SyntaxFactory.IdentifierName(fullClassName + AssemblyChangesLoader.ClassnamePatchedPostfix))))))
|
||||
.WithArgumentList(
|
||||
SyntaxFactory.ArgumentList(
|
||||
SyntaxFactory.SingletonSeparatedList<ArgumentSyntax>(
|
||||
SyntaxFactory.Argument(
|
||||
SyntaxFactory.ThisExpression())))),
|
||||
SyntaxFactory.IdentifierName(fieldName))
|
||||
.WithTriviaFrom(node),
|
||||
$"{nameof(NewFieldsRewriter)}:{nameof(VisitIdentifierName)}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LoggerScoped.LogWarning($"Unable to find type: {fullClassName}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return base.VisitIdentifierName(node);
|
||||
}
|
||||
|
||||
public override SyntaxNode VisitFieldDeclaration(FieldDeclarationSyntax node)
|
||||
{
|
||||
var fieldName = node.Declaration.Variables.First().Identifier.ToString();
|
||||
var fullClassName = RoslynUtils.GetMemberFQDNWithoutMemberName(node);
|
||||
|
||||
if (_typeToNewFieldDeclarations.TryGetValue(fullClassName, out var newFields))
|
||||
{
|
||||
if (newFields.Contains(fieldName))
|
||||
{
|
||||
var existingLeading = node.GetLeadingTrivia();
|
||||
var existingTrailing = node.GetTrailingTrivia();
|
||||
|
||||
return AddRewriteCommentIfNeeded(
|
||||
node
|
||||
.WithLeadingTrivia(existingLeading.Add(SyntaxFactory.Comment("/* ")))
|
||||
.WithTrailingTrivia(existingTrailing.Insert(0, SyntaxFactory.Comment(" */ //Auto-excluded to prevent exceptions - see docs"))),
|
||||
$"{nameof(NewFieldsRewriter)}:{nameof(VisitFieldDeclaration)}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return base.VisitFieldDeclaration(node);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
|
||||
namespace FastScriptReload.Editor.Compilation.CodeRewriting
|
||||
{
|
||||
public class RoslynUtils
|
||||
{
|
||||
public static string GetMemberFQDN(MemberDeclarationSyntax memberNode, string memberName) //TODO: try get rid of member name (needs to cast to whatever member it could be to get identifier)
|
||||
{
|
||||
var outer = GetMemberFQDNWithoutMemberName(memberNode);
|
||||
return !string.IsNullOrEmpty(outer)
|
||||
? $"{outer}.{memberName}"
|
||||
: memberName;
|
||||
}
|
||||
|
||||
public static string GetMemberFQDNWithoutMemberName(MemberDeclarationSyntax memberNode) //TODO: move out to helper class
|
||||
{
|
||||
var fullTypeContibutingAncestorNames = memberNode.Ancestors().OfType<MemberDeclarationSyntax>().Select(da =>
|
||||
{
|
||||
if (da is TypeDeclarationSyntax t) return t.Identifier.ToString();
|
||||
else if (da is NamespaceDeclarationSyntax n) return n.Name.ToString();
|
||||
else throw new Exception("Unable to resolve full field name");
|
||||
}).Reverse().ToList();
|
||||
|
||||
return string.Join(".", fullTypeContibutingAncestorNames);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
|
||||
namespace FastScriptReload.Editor.Compilation.CodeRewriting
|
||||
{
|
||||
class ThisAssignmentRewriter: ThisRewriterBase {
|
||||
public ThisAssignmentRewriter(bool writeRewriteReasonAsComment, bool visitIntoStructuredTrivia = false)
|
||||
: base(writeRewriteReasonAsComment, visitIntoStructuredTrivia)
|
||||
{
|
||||
}
|
||||
|
||||
public override SyntaxNode VisitThisExpression(ThisExpressionSyntax node)
|
||||
{
|
||||
if (node.Parent is AssignmentExpressionSyntax) {
|
||||
return CreateCastedThisExpression(node);
|
||||
}
|
||||
|
||||
return base.VisitThisExpression(node);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
|
||||
namespace FastScriptReload.Editor.Compilation.CodeRewriting
|
||||
{
|
||||
class ThisCallRewriter : ThisRewriterBase
|
||||
{
|
||||
public ThisCallRewriter(bool writeRewriteReasonAsComment, bool visitIntoStructuredTrivia = false)
|
||||
: base(writeRewriteReasonAsComment, visitIntoStructuredTrivia)
|
||||
{
|
||||
}
|
||||
|
||||
public override SyntaxNode VisitThisExpression(ThisExpressionSyntax node)
|
||||
{
|
||||
if (node.Parent is ArgumentSyntax)
|
||||
{
|
||||
return CreateCastedThisExpression(node);
|
||||
}
|
||||
return base.VisitThisExpression(node);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
using System.Linq;
|
||||
using ImmersiveVrToolsCommon.Runtime.Logging;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
|
||||
namespace FastScriptReload.Editor.Compilation.CodeRewriting
|
||||
{
|
||||
abstract class ThisRewriterBase : FastScriptReloadCodeRewriterBase
|
||||
{
|
||||
protected ThisRewriterBase(bool writeRewriteReasonAsComment, bool visitIntoStructuredTrivia = false)
|
||||
: base(writeRewriteReasonAsComment, visitIntoStructuredTrivia)
|
||||
{
|
||||
}
|
||||
|
||||
protected SyntaxNode CreateCastedThisExpression(ThisExpressionSyntax node)
|
||||
{
|
||||
var ancestors = node.Ancestors().Where(n => n is TypeDeclarationSyntax).Cast<TypeDeclarationSyntax>().ToList();
|
||||
if (ancestors.Count() > 1)
|
||||
{
|
||||
LoggerScoped.LogWarning($"ThisRewriter: for class: '{ancestors.First().Identifier}' - 'this' call/assignment in nested class / struct. Dynamic cast will be used but this could cause issues in some cases:" +
|
||||
$"\r\n\r\n1) - If called method has multiple overrides, using dynamic will cause compiler issue as it'll no longer be able to pick correct one" +
|
||||
$"\r\n\r\n If you see any issues with that message, please look at 'Limitation' section in documentation as this outlines how to deal with it.");
|
||||
|
||||
//TODO: casting to dynamic seems to be best option (and one that doesn't fail for nested classes), what's the performance overhead?
|
||||
return SyntaxFactory.CastExpression(
|
||||
SyntaxFactory.ParseTypeName("dynamic"),
|
||||
node
|
||||
);
|
||||
}
|
||||
|
||||
var firstAncestor = ancestors.FirstOrDefault();
|
||||
if (firstAncestor == null)
|
||||
{
|
||||
LoggerScoped.LogWarning($"Unable to find first ancestor for node: {node.ToFullString()}, this rewrite will not be applied");
|
||||
return node;
|
||||
}
|
||||
|
||||
var methodInType = firstAncestor.Identifier.ToString();
|
||||
var resultNode = SyntaxFactory.CastExpression(
|
||||
SyntaxFactory.ParseTypeName(methodInType),
|
||||
SyntaxFactory.CastExpression(
|
||||
SyntaxFactory.ParseTypeName(typeof(object).FullName),
|
||||
node
|
||||
)
|
||||
);
|
||||
|
||||
return AddRewriteCommentIfNeeded(resultNode, $"{nameof(ThisRewriterBase)}:{nameof(CreateCastedThisExpression)}");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,264 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using FastScriptReload.Runtime;
|
||||
using ImmersiveVRTools.Editor.Common.Cache;
|
||||
using ImmersiveVRTools.Runtime.Common;
|
||||
using ImmersiveVrToolsCommon.Runtime.Logging;
|
||||
using UnityEditor;
|
||||
using Debug = UnityEngine.Debug;
|
||||
|
||||
namespace FastScriptReload.Editor.Compilation
|
||||
{
|
||||
[InitializeOnLoad]
|
||||
public class DotnetExeDynamicCompilation: DynamicCompilationBase
|
||||
{
|
||||
private static string _dotnetExePath;
|
||||
private static string _cscDll;
|
||||
private static string _tempFolder;
|
||||
|
||||
private static string ApplicationContentsPath = EditorApplication.applicationContentsPath;
|
||||
private static readonly List<string> _createdFilesToCleanUp = new List<string>();
|
||||
|
||||
static DotnetExeDynamicCompilation()
|
||||
{
|
||||
#if UNITY_EDITOR_WIN
|
||||
const string dotnetExecutablePath = "dotnet.exe";
|
||||
#else
|
||||
const string dotnetExecutablePath = "dotnet"; //mac and linux, no extension
|
||||
#endif
|
||||
|
||||
_dotnetExePath = FindFileOrThrow(dotnetExecutablePath);
|
||||
_cscDll = FindFileOrThrow("csc.dll"); //even on mac/linux need to find dll and use, not no extension one
|
||||
_tempFolder = Path.GetTempPath();
|
||||
|
||||
EditorApplication.playModeStateChanged += obj =>
|
||||
{
|
||||
if (obj == PlayModeStateChange.ExitingPlayMode && _createdFilesToCleanUp.Any())
|
||||
{
|
||||
LoggerScoped.LogDebug($"Removing temporary files: [{string.Join(",", _createdFilesToCleanUp)}]");
|
||||
|
||||
foreach (var fileToCleanup in _createdFilesToCleanUp)
|
||||
{
|
||||
File.Delete(fileToCleanup);
|
||||
}
|
||||
_createdFilesToCleanUp.Clear();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static string FindFileOrThrow(string fileName)
|
||||
{
|
||||
return SessionStateCache.GetOrCreateString($"FSR:FilePath_{fileName}", () =>
|
||||
{
|
||||
var foundFile = Directory
|
||||
.GetFiles(ApplicationContentsPath, fileName, SearchOption.AllDirectories)
|
||||
.FirstOrDefault();
|
||||
if (foundFile == null)
|
||||
{
|
||||
throw new Exception($"Unable to find '{fileName}', make sure Editor version supports it. You can also add preprocessor directive 'FastScriptReload_CompileViaMCS' which will use Mono compiler instead");
|
||||
}
|
||||
|
||||
return foundFile;
|
||||
});
|
||||
}
|
||||
|
||||
public static CompileResult Compile(List<string> filePathsWithSourceCode, UnityMainThreadDispatcher unityMainThreadDispatcher)
|
||||
{
|
||||
try
|
||||
{
|
||||
var asmName = Guid.NewGuid().ToString().Replace("-", "");
|
||||
var rspFile = _tempFolder + $"{asmName}.rsp";
|
||||
var assemblyAttributeFilePath = _tempFolder + $"{asmName}.DynamicallyCreatedAssemblyAttribute.cs";
|
||||
var sourceCodeCombinedFilePath = _tempFolder + $"{asmName}.SourceCodeCombined.cs";
|
||||
var outLibraryPath = $"{_tempFolder}{asmName}.dll";
|
||||
|
||||
var sourceCodeCombined = CreateSourceCodeCombinedContents(filePathsWithSourceCode, ActiveScriptCompilationDefines.ToList());
|
||||
CreateFileAndTrackAsCleanup(sourceCodeCombinedFilePath, sourceCodeCombined, _createdFilesToCleanUp);
|
||||
#if UNITY_EDITOR
|
||||
unityMainThreadDispatcher.Enqueue(() =>
|
||||
{
|
||||
if ((bool)FastScriptReloadPreference.IsAutoOpenGeneratedSourceFileOnChangeEnabled.GetEditorPersistedValueOrDefault())
|
||||
{
|
||||
UnityEditorInternal.InternalEditorUtility.OpenFileAtLineExternal(sourceCodeCombinedFilePath, 0);
|
||||
}
|
||||
});
|
||||
#endif
|
||||
|
||||
var rspFileContent = GenerateCompilerArgsRspFileContents(outLibraryPath, _tempFolder, asmName, sourceCodeCombinedFilePath, assemblyAttributeFilePath);
|
||||
CreateFileAndTrackAsCleanup(rspFile, rspFileContent, _createdFilesToCleanUp);
|
||||
CreateFileAndTrackAsCleanup(assemblyAttributeFilePath, DynamicallyCreatedAssemblyAttributeSourceCode, _createdFilesToCleanUp);
|
||||
|
||||
var exitCode = ExecuteDotnetExeCompilation(_dotnetExePath, _cscDll, rspFile, outLibraryPath, out var outputMessages);
|
||||
|
||||
var compiledAssembly = Assembly.LoadFrom(outLibraryPath);
|
||||
return new CompileResult(outLibraryPath, outputMessages, exitCode, compiledAssembly, sourceCodeCombined, sourceCodeCombinedFilePath);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
LoggerScoped.LogError($"Compilation error: temporary files were not removed so they can be inspected: "
|
||||
+ string.Join(", ", _createdFilesToCleanUp
|
||||
.Select(f => $"<a href=\"{f}\" line=\"1\">{f}</a>")));
|
||||
if (LogHowToFixMessageOnCompilationError)
|
||||
{
|
||||
LoggerScoped.LogWarning($@"HOW TO FIX - INSTRUCTIONS:
|
||||
|
||||
1) Open file that caused issue by looking at error log starting with: 'FSR: Compilation error: temporary files were not removed so they can be inspected: '. And click on file path to open.
|
||||
2) Look up other error in the console, which will be like 'Error When updating files:' - this one contains exact line that failed to compile (in XXX_SourceCodeGenerated.cs file). Those are same compilation errors as you see in Unity/IDE when developing.
|
||||
3) Read compiler error message as it'll help understand the issue
|
||||
|
||||
Error could be caused by a normal compilation issue that you created in source file (eg typo), in that case please fix and it'll recompile.
|
||||
|
||||
It's possible compilation fails due to existing limitation, in that case:
|
||||
|
||||
<b><color='orange'>You can quickly specify custom script rewrite override for part of code that's failing.</color></b>
|
||||
|
||||
Please use project panel to:
|
||||
1) Right-click on the original file that has compilation issue
|
||||
2) Click Fast Script Reload -> Add / Open User Script Rewrite Override
|
||||
3) Read top comment in opened file and it'll explain how to create overrides
|
||||
|
||||
I'm continuously working on mitigating limitations.
|
||||
|
||||
If you could please get in touch with me via 'support@immersivevrtools.com' and include error you see in the console as well as created files (from paths in previous error). This way I can get it fixed for you.
|
||||
|
||||
You can also:
|
||||
1) Look at 'limitation' section in the docs - which will explain bit more around limitations and workarounds
|
||||
2) Move some of the code that you want to work on to different file - compilation happens on whole file, if you have multiple types there it could increase the chance of issues
|
||||
3) Have a look at compilation error, it shows error line (in the '*.SourceCodeCombined.cs' file, it's going to be something that compiler does not accept, likely easy to spot. To workaround you can change that part of code in original file. It's specific patterns that'll break it.
|
||||
|
||||
*If you want to prevent that message from reappearing please go to Window -> Fast Script Reload -> Start Screen -> Logging -> tick off 'Log how to fix message on compilation error'*");
|
||||
|
||||
}
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private static void CreateFileAndTrackAsCleanup(string filePath, string contents, List<string> createdFilesToCleanUp)
|
||||
{
|
||||
File.WriteAllText(filePath, contents);
|
||||
createdFilesToCleanUp.Add(filePath);
|
||||
}
|
||||
|
||||
private static string GenerateCompilerArgsRspFileContents(string outLibraryPath, string tempFolder, string asmName,
|
||||
string sourceCodeCombinedFilePath, string assemblyAttributeFilePath)
|
||||
{
|
||||
var rspContents = new StringBuilder();
|
||||
rspContents.AppendLine("-target:library");
|
||||
rspContents.AppendLine($"-out:\"{outLibraryPath}\"");
|
||||
rspContents.AppendLine($"-refout:\"{tempFolder}{asmName}.ref.dll\""); //TODO: what's that?
|
||||
foreach (var symbol in ActiveScriptCompilationDefines)
|
||||
{
|
||||
rspContents.AppendLine($"-define:{symbol}");
|
||||
}
|
||||
|
||||
foreach (var referenceToAdd in ResolveReferencesToAdd(new List<string>()))
|
||||
{
|
||||
rspContents.AppendLine($"-r:\"{referenceToAdd}\"");
|
||||
}
|
||||
|
||||
rspContents.AppendLine($"\"{sourceCodeCombinedFilePath}\"");
|
||||
rspContents.AppendLine($"\"{assemblyAttributeFilePath}\"");
|
||||
|
||||
rspContents.AppendLine($"-langversion:latest");
|
||||
|
||||
rspContents.AppendLine("/deterministic");
|
||||
rspContents.AppendLine("/optimize-");
|
||||
rspContents.AppendLine("/debug:portable");
|
||||
rspContents.AppendLine("/nologo");
|
||||
rspContents.AppendLine("/RuntimeMetadataVersion:v4.0.30319");
|
||||
|
||||
rspContents.AppendLine("/nowarn:0169");
|
||||
rspContents.AppendLine("/nowarn:0649");
|
||||
rspContents.AppendLine("/nowarn:1701");
|
||||
rspContents.AppendLine("/nowarn:1702");
|
||||
rspContents.AppendLine("/utf8output");
|
||||
rspContents.AppendLine("/preferreduilang:en-US");
|
||||
|
||||
var rspContentsString = rspContents.ToString();
|
||||
return rspContentsString;
|
||||
}
|
||||
|
||||
private static int ExecuteDotnetExeCompilation(string dotnetExePath, string cscDll, string rspFile,
|
||||
string outLibraryPath, out List<string> outputMessages)
|
||||
{
|
||||
var process = new Process();
|
||||
process.StartInfo.FileName = dotnetExePath;
|
||||
process.StartInfo.Arguments = $"exec \"{cscDll}\" /nostdlib /noconfig /shared \"@{rspFile}\"";
|
||||
|
||||
var outMessages = new List<string>();
|
||||
|
||||
var stderr_completed = new ManualResetEvent(false);
|
||||
var stdout_completed = new ManualResetEvent(false);
|
||||
|
||||
process.StartInfo.CreateNoWindow = true;
|
||||
process.StartInfo.UseShellExecute = false;
|
||||
process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
|
||||
process.StartInfo.RedirectStandardOutput = true;
|
||||
process.StartInfo.RedirectStandardError = true;
|
||||
process.ErrorDataReceived += (sender, args) =>
|
||||
{
|
||||
if (args.Data != null)
|
||||
outMessages.Add(args.Data);
|
||||
else
|
||||
stderr_completed.Set();
|
||||
};
|
||||
process.OutputDataReceived += (sender, args) =>
|
||||
{
|
||||
if (args.Data != null)
|
||||
{
|
||||
outMessages.Add(args.Data);
|
||||
return;
|
||||
}
|
||||
|
||||
stdout_completed.Set();
|
||||
};
|
||||
process.StartInfo.StandardOutputEncoding = process.StartInfo.StandardErrorEncoding = Encoding.UTF8;
|
||||
|
||||
try
|
||||
{
|
||||
process.Start();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (ex is Win32Exception win32Exception)
|
||||
throw new SystemException(string.Format("Error running {0}: {1}", process.StartInfo.FileName,
|
||||
typeof(Win32Exception)
|
||||
.GetMethod("GetErrorMessage", BindingFlags.Static | BindingFlags.NonPublic)?
|
||||
.Invoke(null, new object[] { win32Exception.NativeErrorCode }) ??
|
||||
$"<Unable to resolve GetErrorMessage function>, NativeErrorCode: {win32Exception.NativeErrorCode}"));
|
||||
throw;
|
||||
}
|
||||
|
||||
int exitCode = -1;
|
||||
try
|
||||
{
|
||||
process.BeginOutputReadLine();
|
||||
process.BeginErrorReadLine();
|
||||
process.WaitForExit();
|
||||
|
||||
exitCode = process.ExitCode;
|
||||
}
|
||||
finally
|
||||
{
|
||||
stderr_completed.WaitOne(TimeSpan.FromSeconds(30.0));
|
||||
stdout_completed.WaitOne(TimeSpan.FromSeconds(30.0));
|
||||
process.Close();
|
||||
}
|
||||
|
||||
if (!File.Exists(outLibraryPath))
|
||||
throw new Exception("Compiler failed to produce the assembly. Output: '" +
|
||||
string.Join(Environment.NewLine + Environment.NewLine, outMessages) + "'");
|
||||
|
||||
outputMessages = new List<string>();
|
||||
outputMessages.AddRange(outMessages);
|
||||
return exitCode;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using FastScriptReload.Runtime;
|
||||
using ImmersiveVRTools.Runtime.Common;
|
||||
using ImmersiveVrToolsCommon.Runtime.Logging;
|
||||
using Debug = UnityEngine.Debug;
|
||||
|
||||
namespace FastScriptReload.Editor.Compilation
|
||||
{
|
||||
public class DynamicAssemblyCompiler
|
||||
{
|
||||
public static CompileResult Compile(List<string> filePathsWithSourceCode, UnityMainThreadDispatcher unityMainThreadDispatcher)
|
||||
{
|
||||
var sw = new Stopwatch();
|
||||
sw.Start();
|
||||
|
||||
#if FastScriptReload_CompileViaMCS
|
||||
var result = McsExeDynamicCompilation.Compile(filePathsWithSourceCode);
|
||||
#else
|
||||
var compileResult = DotnetExeDynamicCompilation.Compile(filePathsWithSourceCode, unityMainThreadDispatcher);
|
||||
#endif
|
||||
|
||||
LoggerScoped.Log($"Files: {string.Join(",", filePathsWithSourceCode.Select(fn => new FileInfo(fn).Name))} changed " +
|
||||
#if UNITY_2021_1_OR_NEWER
|
||||
$"<a href=\"{compileResult.SourceCodeCombinedFileLocation}\" line=\"1\">(click here to debug [in bottom details pane])</a>" +
|
||||
#else
|
||||
"(to debug go to Fast Script Reload -> Start Screen -> Debugging -> Auto-open generated source file for debugging)" +
|
||||
#endif
|
||||
$" - compilation (took {sw.ElapsedMilliseconds}ms)");
|
||||
|
||||
return compileResult;
|
||||
}
|
||||
}
|
||||
|
||||
public class CompileResult
|
||||
{
|
||||
public Assembly CompiledAssembly { get; }
|
||||
public string CompiledAssemblyPath { get; }
|
||||
public List<string> MessagesFromCompilerProcess { get; }
|
||||
public bool IsError => string.IsNullOrEmpty(CompiledAssemblyPath);
|
||||
public int NativeCompilerReturnValue { get; }
|
||||
public string SourceCodeCombined { get; }
|
||||
public string SourceCodeCombinedFileLocation { get; }
|
||||
|
||||
public CompileResult(string compiledAssemblyPath, List<string> messagesFromCompilerProcess, int nativeCompilerReturnValue, Assembly compiledAssembly, string sourceCodeCombined, string sourceCodeCombinedFileLocation)
|
||||
{
|
||||
CompiledAssemblyPath = compiledAssemblyPath;
|
||||
MessagesFromCompilerProcess = messagesFromCompilerProcess;
|
||||
NativeCompilerReturnValue = nativeCompilerReturnValue;
|
||||
CompiledAssembly = compiledAssembly;
|
||||
SourceCodeCombined = sourceCodeCombined;
|
||||
SourceCodeCombinedFileLocation = sourceCodeCombinedFileLocation;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,316 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using FastScriptReload.Editor.Compilation.CodeRewriting;
|
||||
using FastScriptReload.Editor.Compilation.ScriptGenerationOverrides;
|
||||
using FastScriptReload.Runtime;
|
||||
using FastScriptReload.Scripts.Runtime;
|
||||
using ImmersiveVRTools.Editor.Common.Cache;
|
||||
using ImmersiveVRTools.Runtime.Common.Utilities;
|
||||
using ImmersiveVrToolsCommon.Runtime.Logging;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
namespace FastScriptReload.Editor.Compilation
|
||||
{
|
||||
[InitializeOnLoad]
|
||||
public class DynamicCompilationBase
|
||||
{
|
||||
public static bool DebugWriteRewriteReasonAsComment;
|
||||
public static bool LogHowToFixMessageOnCompilationError;
|
||||
public static bool EnableExperimentalThisCallLimitationFix;
|
||||
public static List<string> ReferencesExcludedFromHotReload = new List<string>();
|
||||
|
||||
public const string DebuggingInformationComment =
|
||||
@"// DEBUGGING READ-ME " +
|
||||
#if !UNITY_2021_1_OR_NEWER
|
||||
"WARN: on Unity versions prior to 2021, opening files in that manner can cause static values to be reinitialized"
|
||||
#else
|
||||
""
|
||||
#endif
|
||||
+
|
||||
@"//
|
||||
// To debug simply add a breakpoint in this file.
|
||||
//
|
||||
// With every code change - new file is generated, currently you'll need to re-set breakpoints after each change.
|
||||
// You can also:
|
||||
// - step into the function that was changed (and that will get you to correct source file)
|
||||
// - add a function breakpoint in your IDE (this way you won't have to re-add it every time)
|
||||
//
|
||||
// Tool can automatically open dynamically-compiled code file every time to make setting breakpoints easier.
|
||||
// You can adjust that behaviour via 'Window -> FastScriptReload -> Start Screen -> Debugging -> Do not auto-open generated cs file'.
|
||||
//
|
||||
// You can always open generated file when needed by clicking link in console, eg.
|
||||
// 'FSR: Files: FunctionLibrary.cs changed (click here to debug [in bottom details pane]) - compilation (took 240ms)'
|
||||
|
||||
|
||||
";
|
||||
|
||||
public static readonly string[] ActiveScriptCompilationDefines;
|
||||
protected static readonly string DynamicallyCreatedAssemblyAttributeSourceCode = $"[assembly: {typeof(DynamicallyCreatedAssemblyAttribute).FullName}()]";
|
||||
private static readonly string AssemblyCsharpFullPath;
|
||||
|
||||
static DynamicCompilationBase()
|
||||
{
|
||||
//needs to be set from main thread
|
||||
ActiveScriptCompilationDefines = EditorUserBuildSettings.activeScriptCompilationDefines;
|
||||
AssemblyCsharpFullPath = SessionStateCache.GetOrCreateString(
|
||||
$"FSR:AssemblyCsharpFullPath",
|
||||
() => AssetDatabase.FindAssets("Microsoft.CSharp")
|
||||
.Select(g => new System.IO.FileInfo(UnityEngine.Application.dataPath + "/../" + AssetDatabase.GUIDToAssetPath(g)))
|
||||
.First(fi => fi.Name.ToLower() == "Microsoft.CSharp.dll".ToLower()).FullName
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
protected static string CreateSourceCodeCombinedContents(List<string> sourceCodeFiles, List<string> definedPreprocessorSymbols)
|
||||
{
|
||||
var combinedUsingStatements = new List<string>();
|
||||
|
||||
var sourceCodeWithAdjustments = sourceCodeFiles.Select(sourceCodeFile =>
|
||||
{
|
||||
var fileCode = File.ReadAllText(sourceCodeFile);
|
||||
var tree = CSharpSyntaxTree.ParseText(fileCode, new CSharpParseOptions(preprocessorSymbols: definedPreprocessorSymbols));
|
||||
var root = tree.GetRoot();
|
||||
|
||||
var typeToNewFieldDeclarations = new Dictionary<string, List<string>>();
|
||||
if (FastScriptReloadManager.Instance.AssemblyChangesLoaderEditorOptionsNeededInBuild.EnableExperimentalAddedFieldsSupport)
|
||||
{
|
||||
//WARN: needs to walk before root class name changes, otherwise it'll resolve wrong name
|
||||
var fieldsWalker = new FieldsWalker();
|
||||
fieldsWalker.Visit(root);
|
||||
|
||||
var typeToFieldDeclarations = fieldsWalker.GetTypeToFieldDeclarations();
|
||||
typeToNewFieldDeclarations = typeToFieldDeclarations.ToDictionary(
|
||||
t => t.Key,
|
||||
t =>
|
||||
{
|
||||
if (!ProjectTypeCache.AllTypesInNonDynamicGeneratedAssemblies.TryGetValue(t.Key, out var existingType))
|
||||
{
|
||||
LoggerScoped.LogDebug($"Unable to find type: {t.Key} in loaded assemblies. If that's the class you've added field to then it may not be properly working. It's possible the class was not yet loaded / used and you can ignore that warning. If it's causing any issues please contact support");
|
||||
return new List<string>();
|
||||
}
|
||||
|
||||
var existingTypeMembersToReplace = NewFieldsRewriter.GetReplaceableMembers(existingType).Select(m => m.Name).ToList();
|
||||
|
||||
var newFields = t.Value.Where(fD => !existingTypeMembersToReplace.Contains(fD.FieldName)).ToList();
|
||||
|
||||
//TODO: ideally that registration would happen outside of this class
|
||||
//TODO: to work for LSR it needs to be handled in runtime
|
||||
TemporaryNewFieldValues.RegisterNewFields(
|
||||
existingType,
|
||||
newFields.ToDictionary(
|
||||
fD => fD.FieldName,
|
||||
fD => new TemporaryNewFieldValues.GetNewFieldInitialValue((Type forNewlyGeneratedType) =>
|
||||
{
|
||||
//TODO: PERF: could cache those - they run to init every new value (for every instance when accessed)
|
||||
return CreateNewFieldInitMethodRewriter.ResolveNewFieldsToCreateValueFn(forNewlyGeneratedType)[fD.FieldName]();
|
||||
})
|
||||
),
|
||||
newFields.ToDictionary(
|
||||
fD => fD.FieldName,
|
||||
fD => new TemporaryNewFieldValues.GetNewFieldType((Type forNewlyGeneratedType) =>
|
||||
{
|
||||
//TODO: PERF: could cache those - they run to init every new value (for every instance when accessed)
|
||||
return (Type)CreateNewFieldInitMethodRewriter.ResolveNewFieldsToTypeFn(forNewlyGeneratedType)[fD.FieldName]();
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
return newFields.Select(fD => fD.FieldName).ToList();
|
||||
}
|
||||
);
|
||||
|
||||
#if LiveScriptReload_Enabled
|
||||
if (typeToNewFieldDeclarations.Any(kv => kv.Value.Any()))
|
||||
{
|
||||
LoggerScoped.LogWarning($"{nameof(FastScriptReloadManager.Instance.AssemblyChangesLoaderEditorOptionsNeededInBuild.EnableExperimentalAddedFieldsSupport)} is enabled. This is not supported in running build. Quite likely it'll crash remote client.");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
//WARN: application order is important, eg ctors need to happen before class names as otherwise ctors will not be recognised as ctors
|
||||
if (FastScriptReloadManager.Instance.EnableExperimentalThisCallLimitationFix)
|
||||
{
|
||||
root = new ThisCallRewriter(DebugWriteRewriteReasonAsComment).Visit(root);
|
||||
root = new ThisAssignmentRewriter(DebugWriteRewriteReasonAsComment).Visit(root);
|
||||
}
|
||||
|
||||
if (FastScriptReloadManager.Instance.AssemblyChangesLoaderEditorOptionsNeededInBuild.EnableExperimentalAddedFieldsSupport)
|
||||
{
|
||||
root = new NewFieldsRewriter(typeToNewFieldDeclarations, DebugWriteRewriteReasonAsComment).Visit(root);
|
||||
root = new CreateNewFieldInitMethodRewriter(typeToNewFieldDeclarations, DebugWriteRewriteReasonAsComment).Visit(root);
|
||||
}
|
||||
|
||||
root = new ConstructorRewriter(adjustCtorOnlyForNonNestedTypes: true, DebugWriteRewriteReasonAsComment).Visit(root);
|
||||
|
||||
var hotReloadCompliantRewriter = new HotReloadCompliantRewriter(DebugWriteRewriteReasonAsComment);
|
||||
root = hotReloadCompliantRewriter.Visit(root);
|
||||
combinedUsingStatements.AddRange(hotReloadCompliantRewriter.StrippedUsingDirectives);
|
||||
|
||||
//processed as last step to simply rewrite all changes made before
|
||||
if (TryResolveUserDefinedOverridesRoot(sourceCodeFile, definedPreprocessorSymbols, out var userDefinedOverridesRoot))
|
||||
{
|
||||
root = ProcessUserDefinedOverridesReplacements(sourceCodeFile, root, userDefinedOverridesRoot);
|
||||
root = AddUserDefinedOverridenTypes(userDefinedOverridesRoot, root);
|
||||
}
|
||||
|
||||
return root.ToFullString();
|
||||
}).ToList();
|
||||
|
||||
var sourceCodeCombinedSb = new StringBuilder();
|
||||
sourceCodeCombinedSb.Append(DebuggingInformationComment);
|
||||
|
||||
foreach (var usingStatement in combinedUsingStatements.Distinct())
|
||||
{
|
||||
sourceCodeCombinedSb.Append(usingStatement);
|
||||
}
|
||||
|
||||
foreach (var sourceCodeWithAdjustment in sourceCodeWithAdjustments)
|
||||
{
|
||||
sourceCodeCombinedSb.AppendLine(sourceCodeWithAdjustment);
|
||||
}
|
||||
|
||||
LoggerScoped.LogDebug("Source Code Created:\r\n\r\n" + sourceCodeCombinedSb);
|
||||
return sourceCodeCombinedSb.ToString();
|
||||
}
|
||||
|
||||
private static SyntaxNode AddUserDefinedOverridenTypes(SyntaxNode userDefinedOverridesRoot, SyntaxNode root)
|
||||
{
|
||||
try
|
||||
{
|
||||
var userDefinedOverrideTypes = userDefinedOverridesRoot.DescendantNodes().OfType<TypeDeclarationSyntax>()
|
||||
.ToDictionary(n => RoslynUtils.GetMemberFQDN(n, n.Identifier.ToString()));
|
||||
var allDefinedTypesInRecompiledFile = root.DescendantNodes().OfType<TypeDeclarationSyntax>()
|
||||
.ToDictionary(n => RoslynUtils.GetMemberFQDN(n, n.Identifier.ToString())); //what about nested types?
|
||||
|
||||
var userDefinedOverrideTypesWithoutMatchnigInRecompiledFile = userDefinedOverrideTypes.Select(overridenType =>
|
||||
{
|
||||
if (!allDefinedTypesInRecompiledFile.ContainsKey(overridenType.Key))
|
||||
{
|
||||
return overridenType;
|
||||
}
|
||||
|
||||
return default(KeyValuePair<string, TypeDeclarationSyntax>);
|
||||
})
|
||||
.Where(kv => kv.Key != default(string))
|
||||
.ToList();
|
||||
|
||||
//types should be added either to root namespace or root of document
|
||||
var rootNamespace = root.DescendantNodes().OfType<NamespaceDeclarationSyntax>().FirstOrDefault();
|
||||
foreach (var overridenTypeToAdd in userDefinedOverrideTypesWithoutMatchnigInRecompiledFile)
|
||||
{
|
||||
var newMember = FastScriptReloadCodeRewriterBase.AddRewriteCommentIfNeeded(overridenTypeToAdd.Value,
|
||||
"New type defined in override file",
|
||||
true, //always write reason so it's not easy to miss in generated file
|
||||
true);
|
||||
if (rootNamespace != null)
|
||||
{
|
||||
rootNamespace =
|
||||
root.DescendantNodes().OfType<NamespaceDeclarationSyntax>()
|
||||
.FirstOrDefault(); //need to search again to make sure it didn't change
|
||||
var newRootNamespace = rootNamespace.AddMembers(newMember);
|
||||
root = root.ReplaceNode(rootNamespace, newRootNamespace);
|
||||
}
|
||||
else
|
||||
{
|
||||
root = ((CompilationUnitSyntax)root).AddMembers(newMember);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogError($"Unable to add user defined override types. {e}");
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private static bool TryResolveUserDefinedOverridesRoot(string sourceCodeFile, List<string> definedPreprocessorSymbols, out SyntaxNode userDefinedOverridesRoot)
|
||||
{
|
||||
if (ScriptGenerationOverridesManager.TryGetScriptOverride(new FileInfo(sourceCodeFile), out var userDefinedOverridesFile))
|
||||
{
|
||||
try
|
||||
{
|
||||
userDefinedOverridesRoot = CSharpSyntaxTree.ParseText(File.ReadAllText(userDefinedOverridesFile.FullName), new CSharpParseOptions(preprocessorSymbols: definedPreprocessorSymbols)).GetRoot();
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError($"Unable to resolve user defined overrides for file: '{userDefinedOverridesFile.FullName}' - please make sure it's compilable. Error: '{ex}'");
|
||||
}
|
||||
}
|
||||
|
||||
userDefinedOverridesRoot = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static SyntaxNode ProcessUserDefinedOverridesReplacements(string sourceCodeFile, SyntaxNode root, SyntaxNode userDefinedOverridesRoot)
|
||||
{
|
||||
if (ScriptGenerationOverridesManager.TryGetScriptOverride(new FileInfo(sourceCodeFile), out var userDefinedOverridesFile))
|
||||
{
|
||||
try
|
||||
{
|
||||
var userDefinedScriptOverridesRewriter = new ManualUserDefinedScriptOverridesRewriter(userDefinedOverridesRoot,
|
||||
true); //always write rewrite reason so it's not easy to miss
|
||||
root = userDefinedScriptOverridesRewriter.Visit(root);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError($"Unable to resolve user defined overrides for file: '{userDefinedOverridesFile.FullName}' - please make sure it's compilable. Error: '{ex}'");
|
||||
}
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
protected static List<string> ResolveReferencesToAdd(List<string> excludeAssyNames)
|
||||
{
|
||||
var referencesToAdd = new List<string>();
|
||||
foreach (var assembly in AppDomain.CurrentDomain
|
||||
.GetAssemblies() //TODO: PERF: just need to load once and cache? or get assembly based on changed file only?
|
||||
.Where(a => excludeAssyNames.All(assyName => !a.FullName.StartsWith(assyName))
|
||||
&& CustomAttributeExtensions.GetCustomAttribute<DynamicallyCreatedAssemblyAttribute>((Assembly)a) == null))
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(assembly.Location))
|
||||
{
|
||||
LoggerScoped.LogDebug($"FastScriptReload: Assembly location is null, usually dynamic assembly, harmless.");
|
||||
continue;
|
||||
}
|
||||
|
||||
referencesToAdd.Add(assembly.Location);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
LoggerScoped.LogDebug($"Unable to add a reference to assembly as unable to get location or null: {assembly.FullName} when hot-reloading, this is likely dynamic assembly and won't cause issues");
|
||||
}
|
||||
}
|
||||
|
||||
referencesToAdd = referencesToAdd.Where(r => !ReferencesExcludedFromHotReload.Any(rTe => r.EndsWith(rTe))).ToList();
|
||||
|
||||
if (EnableExperimentalThisCallLimitationFix || FastScriptReloadManager.Instance.AssemblyChangesLoaderEditorOptionsNeededInBuild.EnableExperimentalAddedFieldsSupport)
|
||||
{
|
||||
IncludeMicrosoftCsharpReferenceToSupportDynamicKeyword(referencesToAdd);
|
||||
}
|
||||
|
||||
return referencesToAdd;
|
||||
}
|
||||
|
||||
private static void IncludeMicrosoftCsharpReferenceToSupportDynamicKeyword(List<string> referencesToAdd)
|
||||
{
|
||||
//TODO: check .net4.5 backend not breaking?
|
||||
//ThisRewriters will cast to dynamic - if using .NET Standard 2.1 - reference is required
|
||||
referencesToAdd.Add(AssemblyCsharpFullPath);
|
||||
// referencesToAdd.Add(@"C:\Program Files\Unity\Hub\Editor\2021.3.12f1\Editor\Data\UnityReferenceAssemblies\unity-4.8-api\Microsoft.CSharp.dll");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,64 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
#if FastScriptReload_CompileViaMCS
|
||||
public class McsExeDynamicCompilation : DynamicCompilationBase
|
||||
{
|
||||
private const int ReferenceLenghtCountWarningThreshold = 32767 - 2000; //windows can accept up to 32767 chars as args, then it starts thorowing exceptions. MCS.exe is adding references via command /r:<full path>
|
||||
|
||||
private static CompileResult Compile(List<string> filePathsWithSourceCode)
|
||||
{
|
||||
var fileSourceCode = filePathsWithSourceCode.Select(File.ReadAllText);
|
||||
|
||||
var providerOptions = new Dictionary<string, string>();
|
||||
var provider = new Microsoft.CSharp.CSharpCodeProvider(providerOptions);
|
||||
var param = new System.CodeDom.Compiler.CompilerParameters();
|
||||
|
||||
var excludeAssyNames = new List<string>
|
||||
{
|
||||
"mscorlib"
|
||||
};
|
||||
var referencesToAdd = ResolveReferencesToAdd(excludeAssyNames);
|
||||
|
||||
var referencePathCharLenght = referencesToAdd.Sum(r => r.Length);
|
||||
if (referencePathCharLenght > ReferenceLenghtCountWarningThreshold)
|
||||
{
|
||||
LoggerScoped.LogWarning(
|
||||
"Windows can accept up to 32767 chars as args, then it starts throwing exceptions. Dynamic compilation will use MCS.exe and will add references via command /r:<full path>, " +
|
||||
$"currently your assembly have {referencesToAdd.Count} references which full paths amount to: {referencePathCharLenght} chars." +
|
||||
$"\r\nIf you see this warning likely compilation will fail, you can:" +
|
||||
$"\r\n1) Move your project to be more top-level, as references take full paths, eg 'c:\\my-source\\stuff\\unity\\my-project\\' - this then gets repeated for many references, moving it close to top level will help" +
|
||||
$"\r\n2) Remove some of the assemblies if you don't need them" +
|
||||
"\r\n Please let me know via support email if that's causing you issues, there may be a fix if it's affecting many users, sorry about that!");
|
||||
|
||||
//TODO: the process is started from Microsoft.CSharp.CSharpCodeGenerator.FromFileBatch, potentially all it'd be possible to patch that class to maybe copy all
|
||||
//assemblies to some top-level location and change parameters to run from this folder, with a working directory set, this would drastically reduce char count used by full refs
|
||||
//also mcs.exe allows to compile with -pkg:package1[,packageN], which somewhat bundles multiple references, maybe all unity engine refs could go in there, or all refs in general
|
||||
}
|
||||
|
||||
param.ReferencedAssemblies.AddRange(referencesToAdd.ToArray());
|
||||
param.GenerateExecutable = false;
|
||||
param.GenerateInMemory = false;
|
||||
providerOptions.Add(PatchMcsArgsGeneration.PreprocessorDirectivesProviderOptionsKey,
|
||||
string.Join(";", ActiveScriptCompilationDefines));
|
||||
|
||||
var sourceCodeCombined = CreateSourceCodeCombinedContents(fileSourceCode);
|
||||
var result = provider.CompileAssemblyFromSource(param, sourceCodeCombined, DynamicallyCreatedAssemblyAttributeSourceCode);
|
||||
var errors = new List<string>();
|
||||
foreach (var error in result.Errors)
|
||||
{
|
||||
errors.Add(error.ToString());
|
||||
}
|
||||
return new CompileResult(
|
||||
result.CompiledAssembly.FullName,
|
||||
errors,
|
||||
result.NativeCompilerReturnValue,
|
||||
result.CompiledAssembly,
|
||||
sourceCodeCombined,
|
||||
string.Empty
|
||||
);
|
||||
}
|
||||
}
|
||||
#endif
|
|
@ -1,110 +0,0 @@
|
|||
#if FastScriptReload_CompileViaMCS
|
||||
using System;
|
||||
using System.CodeDom.Compiler;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using FastScriptReload.Runtime;
|
||||
using HarmonyLib;
|
||||
using UnityEditor;
|
||||
|
||||
[InitializeOnLoad]
|
||||
[PreventHotReload]
|
||||
public class PatchMcsArgsGeneration
|
||||
{
|
||||
public const string PreprocessorDirectivesProviderOptionsKey = "PreprocessorDirectives";
|
||||
|
||||
static PatchMcsArgsGeneration()
|
||||
{
|
||||
var harmony = new Harmony(nameof(PatchMcsArgsGeneration));
|
||||
|
||||
var original = AccessTools.Method("Microsoft.CSharp.CSharpCodeGenerator:BuildArgs");
|
||||
var postfix = AccessTools.Method(typeof(PatchMcsArgsGeneration), nameof(BuildArgsPostfix));
|
||||
|
||||
harmony.Patch(original, postfix: new HarmonyMethod(postfix));
|
||||
}
|
||||
|
||||
//Copied from Microsoft.CSharp.CSharpCodeGenerator.BuildArgs
|
||||
private static void BuildArgsPostfix(
|
||||
CompilerParameters options,
|
||||
string[] fileNames,
|
||||
IDictionary<string, string> providerOptions,
|
||||
ref string __result)
|
||||
{
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
if (options.GenerateExecutable)
|
||||
stringBuilder.Append("/target:exe ");
|
||||
else
|
||||
stringBuilder.Append("/target:library ");
|
||||
string privateBinPath = AppDomain.CurrentDomain.SetupInformation.PrivateBinPath;
|
||||
if (privateBinPath != null && privateBinPath.Length > 0)
|
||||
stringBuilder.AppendFormat("/lib:\"{0}\" ", (object) privateBinPath);
|
||||
if (options.Win32Resource != null)
|
||||
stringBuilder.AppendFormat("/win32res:\"{0}\" ", (object) options.Win32Resource);
|
||||
if (options.IncludeDebugInformation)
|
||||
stringBuilder.Append("/debug+ /optimize- ");
|
||||
else
|
||||
stringBuilder.Append("/debug- /optimize+ ");
|
||||
if (options.TreatWarningsAsErrors)
|
||||
stringBuilder.Append("/warnaserror ");
|
||||
if (options.WarningLevel >= 0)
|
||||
stringBuilder.AppendFormat("/warn:{0} ", (object) options.WarningLevel);
|
||||
if (options.OutputAssembly == null || options.OutputAssembly.Length == 0)
|
||||
{
|
||||
string extension = options.GenerateExecutable ? "exe" : "dll"; //TODO:readd
|
||||
// options.OutputAssembly = CSharpCodeGenerator.GetTempFileNameWithExtension(options.TempFiles, extension, !options.GenerateInMemory);
|
||||
}
|
||||
stringBuilder.AppendFormat("/out:\"{0}\" ", (object) options.OutputAssembly);
|
||||
foreach (string referencedAssembly in options.ReferencedAssemblies)
|
||||
{
|
||||
if (referencedAssembly != null && referencedAssembly.Length != 0)
|
||||
stringBuilder.AppendFormat("/r:\"{0}\" ", (object) referencedAssembly);
|
||||
}
|
||||
if (options.CompilerOptions != null)
|
||||
{
|
||||
stringBuilder.Append(options.CompilerOptions);
|
||||
stringBuilder.Append(" ");
|
||||
}
|
||||
foreach (string embeddedResource in options.EmbeddedResources)
|
||||
stringBuilder.AppendFormat("/resource:\"{0}\" ", (object) embeddedResource);
|
||||
foreach (string linkedResource in options.LinkedResources)
|
||||
stringBuilder.AppendFormat("/linkresource:\"{0}\" ", (object) linkedResource);
|
||||
|
||||
//WARN: that's how it's in source, quite odd, doesn't do much if compiler version specified?
|
||||
// if (providerOptions != null && providerOptions.Count > 0)
|
||||
// {
|
||||
// string str;
|
||||
// if (!providerOptions.TryGetValue("CompilerVersion", out str))
|
||||
// str = "3.5";
|
||||
// if (str.Length >= 1 && str[0] == 'v')
|
||||
// str = str.Substring(1);
|
||||
// if (str != "2.0")
|
||||
// {
|
||||
// }
|
||||
// else
|
||||
// stringBuilder.Append("/langversion:ISO-2 ");
|
||||
// }
|
||||
|
||||
stringBuilder.Append("/langversion:experimental ");
|
||||
|
||||
CustomPatchAdditionAddPreprocessorDirectives(providerOptions, stringBuilder);
|
||||
|
||||
stringBuilder.Append("/noconfig ");
|
||||
stringBuilder.Append(" -- ");
|
||||
foreach (string fileName in fileNames)
|
||||
stringBuilder.AppendFormat("\"{0}\" ", (object) fileName);
|
||||
|
||||
__result = stringBuilder.ToString();
|
||||
}
|
||||
|
||||
private static void CustomPatchAdditionAddPreprocessorDirectives(IDictionary<string, string> providerOptions, StringBuilder stringBuilder)
|
||||
{
|
||||
if (providerOptions != null && providerOptions.Count > 0)
|
||||
{
|
||||
if (providerOptions.TryGetValue(PreprocessorDirectivesProviderOptionsKey, out var preprocessorDirectives))
|
||||
{
|
||||
stringBuilder.Append($"/d:\"{preprocessorDirectives}\" ");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
|
@ -1,204 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using FastScriptReload.Runtime;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using UnityEditor;
|
||||
using UnityEditorInternal;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FastScriptReload.Editor.Compilation.ScriptGenerationOverrides
|
||||
{
|
||||
[InitializeOnLoad]
|
||||
public static class ScriptGenerationOverridesManager
|
||||
{
|
||||
private static float LoadOverridesFolderFilesEveryNSeconds = 5;
|
||||
|
||||
private static readonly string TemplateInterfaceDeclaration = @"
|
||||
|
||||
//New interface declaration, this is very useful in cases where code depends on some internal interfaces that re-compiled code can no longer access. Simply define them here and code will compile.
|
||||
//You can add any type in that manner
|
||||
public interface ITestNewInterface {
|
||||
bool Test { get; set; }
|
||||
}";
|
||||
|
||||
private static readonly string TemplateTopComment = @"// You can use this file to specify custom code overrides. Those will be applied to resulting code.
|
||||
// This approach is very useful if your code is failing to compile due to one of the existing limitations.
|
||||
//
|
||||
// While I work on reducing limitations you can simply specify override with proper code to make sure you can continue working.
|
||||
//
|
||||
// 1) Simply define code with same structure as your original class, make sure to include any namespace.
|
||||
// 2) Rename classes and types to have '<ClassPostfix>' postfix.
|
||||
//
|
||||
// eg. 'MyClassName' needs to be changed to MyClassName<ClassPostfix> otherwise it won't be properly connected.
|
||||
//
|
||||
// 3) Add any methods that you want to override, using same method signature. Whole method body will be replaced and no code adjustments will be run on it.
|
||||
// 4) You can add any additional types - this is quite useful when you hit limitation with internal interfaces - where compiler can not access them due to protection modifiers.
|
||||
// You can simply redefine those here, while not ideal it'll allow you to continue using Hot-Reload without modifying your code.
|
||||
//
|
||||
// Tool will now attempt to create a template file for you with first found class and first method as override, please adjust as necessary.
|
||||
// It'll also create an example redefined interface.
|
||||
// If you can't see anything please refer to the above and create overrides file manually.
|
||||
//
|
||||
// You can also refer to documentation section 'User defined script rewrite overrides'
|
||||
";
|
||||
|
||||
public static DirectoryInfo UserDefinedScriptRewriteOverridesFolder { get; }
|
||||
private static double _lastTimeOverridesFolderFilesRead;
|
||||
|
||||
public static List<UserDefinedScriptOverride> UserDefinedScriptOverrides { get; } = new List<UserDefinedScriptOverride>();
|
||||
|
||||
static ScriptGenerationOverridesManager()
|
||||
{
|
||||
//TODO: allow to customize later from code, eg for user that'd like to include in source control
|
||||
UserDefinedScriptRewriteOverridesFolder = new DirectoryInfo(Application.persistentDataPath + @"\FastScriptReload\ScriptOverrides");
|
||||
UpdateUserDefinedScriptOverridesFileCache();
|
||||
EditorApplication.update += Update;
|
||||
}
|
||||
|
||||
private static void Update()
|
||||
{
|
||||
var timeSinceStartup = EditorApplication.timeSinceStartup;
|
||||
if (_lastTimeOverridesFolderFilesRead + LoadOverridesFolderFilesEveryNSeconds < timeSinceStartup)
|
||||
{
|
||||
_lastTimeOverridesFolderFilesRead = timeSinceStartup;
|
||||
|
||||
UpdateUserDefinedScriptOverridesFileCache();
|
||||
}
|
||||
}
|
||||
|
||||
private static void UpdateUserDefinedScriptOverridesFileCache()
|
||||
{
|
||||
UserDefinedScriptOverrides.Clear();
|
||||
if (UserDefinedScriptRewriteOverridesFolder.Exists)
|
||||
{
|
||||
UserDefinedScriptOverrides.AddRange(UserDefinedScriptRewriteOverridesFolder.GetFiles().Select(f => new UserDefinedScriptOverride(f)));
|
||||
}
|
||||
}
|
||||
|
||||
public static void AddScriptOverride(MonoScript script)
|
||||
{
|
||||
EnsureOverrideFolderExists();
|
||||
|
||||
var overridenFile = new FileInfo(Path.Combine(UserDefinedScriptRewriteOverridesFolder.FullName, script.name + ".cs"));
|
||||
if (!overridenFile.Exists)
|
||||
{
|
||||
var originalFile = new FileInfo(Path.Combine(Path.Combine(Application.dataPath + "//..", AssetDatabase.GetAssetPath(script))));
|
||||
|
||||
var templateString = string.Empty;
|
||||
try
|
||||
{
|
||||
var fileCode = File.ReadAllText(originalFile.FullName);
|
||||
var tree = CSharpSyntaxTree.ParseText(fileCode, new CSharpParseOptions(preprocessorSymbols: DynamicCompilationBase.ActiveScriptCompilationDefines));
|
||||
var root = tree.GetRoot();
|
||||
|
||||
var firstType = root.DescendantNodes().OfType<ClassDeclarationSyntax>().FirstOrDefault();
|
||||
if (firstType != null)
|
||||
{
|
||||
var members = new SyntaxList<MemberDeclarationSyntax>();
|
||||
var firstMethod = firstType.DescendantNodes().OfType<MethodDeclarationSyntax>().FirstOrDefault(m => m.Body != null);
|
||||
if (firstMethod != null)
|
||||
{
|
||||
var block = SyntaxFactory.Block();
|
||||
block = block.AddStatements(SyntaxFactory.EmptyStatement().WithLeadingTrivia(
|
||||
SyntaxFactory.Comment(@"/* Any code will be replaced with original method of same signature in same type*/"))
|
||||
);
|
||||
firstMethod = firstMethod
|
||||
.WithBody(block)
|
||||
.WithTriviaFrom(firstMethod);
|
||||
members = members.Add(firstMethod);
|
||||
}
|
||||
|
||||
root = root.ReplaceNode(firstType, firstType
|
||||
.ReplaceToken(
|
||||
firstType.Identifier,
|
||||
SyntaxFactory.Identifier(firstType.Identifier.ValueText + AssemblyChangesLoader.ClassnamePatchedPostfix)
|
||||
)
|
||||
.WithMembers(members)).NormalizeWhitespace();
|
||||
|
||||
var interfaceDeclaration = CSharpSyntaxTree.ParseText(TemplateInterfaceDeclaration);
|
||||
|
||||
root = ((CompilationUnitSyntax)root).AddMembers(
|
||||
interfaceDeclaration.GetRoot().DescendantNodes().OfType<InterfaceDeclarationSyntax>().First()
|
||||
);
|
||||
}
|
||||
|
||||
templateString = root.ToFullString();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogError($"Unable to generate user defined script override template from your file, please refer to note at the start of the file. {e}");
|
||||
}
|
||||
|
||||
if (!overridenFile.Exists)
|
||||
{
|
||||
File.WriteAllText(overridenFile.FullName,
|
||||
TemplateTopComment.Replace("<ClassPostfix>", AssemblyChangesLoader.ClassnamePatchedPostfix) + templateString
|
||||
);
|
||||
UpdateUserDefinedScriptOverridesFileCache();
|
||||
}
|
||||
}
|
||||
|
||||
InternalEditorUtility.OpenFileAtLineExternal(overridenFile.FullName, 0);
|
||||
}
|
||||
|
||||
public static bool TryRemoveScriptOverride(MonoScript originalScript)
|
||||
{
|
||||
EnsureOverrideFolderExists();
|
||||
|
||||
var overridenFile = new FileInfo(Path.Combine(UserDefinedScriptRewriteOverridesFolder.FullName, originalScript.name + ".cs"));
|
||||
if (overridenFile.Exists)
|
||||
{
|
||||
return TryRemoveScriptOverride(overridenFile);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool TryRemoveScriptOverride(FileInfo overridenFile)
|
||||
{
|
||||
try
|
||||
{
|
||||
overridenFile.Delete();
|
||||
UpdateUserDefinedScriptOverridesFileCache();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Debug.Log($"Unable to remove: '{overridenFile.Name}' - make sure it's not locked / open in editor");
|
||||
throw;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool TryRemoveScriptOverride(UserDefinedScriptOverride scriptOverride)
|
||||
{
|
||||
return TryRemoveScriptOverride(scriptOverride.File);
|
||||
}
|
||||
|
||||
public static bool TryGetScriptOverride(FileInfo changedFile, out FileInfo overridesFile)
|
||||
{
|
||||
overridesFile = UserDefinedScriptOverrides.FirstOrDefault(f => f.File.Name == changedFile.Name && f.File.Exists)?.File;
|
||||
return overridesFile?.Exists ?? false;
|
||||
}
|
||||
|
||||
private static void EnsureOverrideFolderExists()
|
||||
{
|
||||
if (!UserDefinedScriptRewriteOverridesFolder.Exists)
|
||||
UserDefinedScriptRewriteOverridesFolder.Create();
|
||||
}
|
||||
}
|
||||
|
||||
public class UserDefinedScriptOverride
|
||||
{
|
||||
public FileInfo File { get; }
|
||||
|
||||
public UserDefinedScriptOverride(FileInfo file)
|
||||
{
|
||||
File = file;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
{
|
||||
"name": "FastScriptReload.Editor",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"GUID:b75b497805046c443a21f90e84a5ff4f"
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": true,
|
||||
"precompiledReferences": [
|
||||
"0Harmony.dll",
|
||||
"Microsoft.CodeAnalysis.dll",
|
||||
"Microsoft.CodeAnalysis.CSharp.dll",
|
||||
"ImmersiveVRTools.Common.Editor.dll",
|
||||
"ImmersiveVRTools.Common.Runtime.dll"
|
||||
],
|
||||
"autoReferenced": false,
|
||||
"defineConstraints": [
|
||||
"FastScriptReload"
|
||||
],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
|
@ -1,493 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using FastScriptReload.Editor.Compilation;
|
||||
using FastScriptReload.Editor.Compilation.ScriptGenerationOverrides;
|
||||
using FastScriptReload.Runtime;
|
||||
using ImmersiveVRTools.Runtime.Common;
|
||||
using ImmersiveVrToolsCommon.Runtime.Logging;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Compilation;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FastScriptReload.Editor
|
||||
{
|
||||
[InitializeOnLoad]
|
||||
[PreventHotReload]
|
||||
public class FastScriptReloadManager
|
||||
{
|
||||
private static FastScriptReloadManager _instance;
|
||||
public static FastScriptReloadManager Instance
|
||||
{
|
||||
get {
|
||||
if (_instance == null)
|
||||
{
|
||||
_instance = new FastScriptReloadManager();
|
||||
LoggerScoped.LogDebug("Created Manager");
|
||||
}
|
||||
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
private static string DataPath = Application.dataPath;
|
||||
|
||||
public const string FileWatcherReplacementTokenForApplicationDataPath = "<Application.dataPath>";
|
||||
public Dictionary<string, Func<string>> FileWatcherTokensToResolvePathFn = new Dictionary<string, Func<string>>
|
||||
{
|
||||
[FileWatcherReplacementTokenForApplicationDataPath] = () => DataPath
|
||||
};
|
||||
|
||||
private bool _wasLockReloadAssembliesCalled;
|
||||
private PlayModeStateChange _lastPlayModeStateChange;
|
||||
private List<FileSystemWatcher> _fileWatchers = new List<FileSystemWatcher>();
|
||||
private IEnumerable<string> _currentFileExclusions;
|
||||
private int _triggerDomainReloadIfOverNDynamicallyLoadedAssembles = 100;
|
||||
public bool EnableExperimentalThisCallLimitationFix { get; private set; }
|
||||
#pragma warning disable 0618
|
||||
public AssemblyChangesLoaderEditorOptionsNeededInBuild AssemblyChangesLoaderEditorOptionsNeededInBuild { get; private set; } = new AssemblyChangesLoaderEditorOptionsNeededInBuild();
|
||||
#pragma warning restore 0618
|
||||
|
||||
private List<DynamicFileHotReloadState> _dynamicFileHotReloadStateEntries = new List<DynamicFileHotReloadState>();
|
||||
|
||||
private DateTime _lastTimeChangeBatchRun = default(DateTime);
|
||||
private bool _assemblyChangesLoaderResolverResolutionAlreadyCalled;
|
||||
private bool _isEditorModeHotReloadEnabled;
|
||||
private int _hotReloadPerformedCount = 0;
|
||||
private bool _isOnDemandHotReloadEnabled;
|
||||
|
||||
private void OnWatchedFileChange(object source, FileSystemEventArgs e)
|
||||
{
|
||||
if (!_isEditorModeHotReloadEnabled && _lastPlayModeStateChange != PlayModeStateChange.EnteredPlayMode)
|
||||
{
|
||||
#if ImmersiveVrTools_DebugEnabled
|
||||
LoggerScoped.Log($"Application not playing, change to: {e.Name} won't be compiled and hot reloaded");
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
var filePathToUse = e.FullPath;
|
||||
if (!File.Exists(filePathToUse))
|
||||
{
|
||||
LoggerScoped.LogWarning(@"Fast Script Reload - Unity File Path Bug - Warning!
|
||||
Path for changed file passed by Unity does not exist. This is a known editor bug, more info: https://issuetracker.unity3d.com/issues/filesystemwatcher-returns-bad-file-path
|
||||
|
||||
Best course of action is to update editor as issue is already fixed in newer (minor and major) versions.
|
||||
|
||||
As a workaround asset will try to resolve paths via directory search.
|
||||
|
||||
Workaround will search in all folders (under project root) and will use first found file. This means it's possible it'll pick up wrong file as there's no directory information available.");
|
||||
|
||||
var changedFileName = new FileInfo(filePathToUse).Name;
|
||||
//TODO: try to look in all file watcher configured paths, some users might have code outside of assets, eg packages
|
||||
// var fileFoundInAssets = FastScriptReloadPreference.FileWatcherSetupEntries.GetElementsTyped().SelectMany(setupEntries => Directory.GetFiles(DataPath, setupEntries.path, SearchOption.AllDirectories)).ToList();
|
||||
|
||||
var fileFoundInAssets = Directory.GetFiles(DataPath, changedFileName, SearchOption.AllDirectories);
|
||||
if (fileFoundInAssets.Length == 0)
|
||||
{
|
||||
LoggerScoped.LogError($"FileWatcherBugWorkaround: Unable to find file '{changedFileName}', changes will not be reloaded. Please update unity editor.");
|
||||
return;
|
||||
}
|
||||
else if(fileFoundInAssets.Length == 1)
|
||||
{
|
||||
LoggerScoped.Log($"FileWatcherBugWorkaround: Original Unity passed file path: '{e.FullPath}' adjusted to found: '{fileFoundInAssets[0]}'");
|
||||
filePathToUse = fileFoundInAssets[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
LoggerScoped.LogWarning($"FileWatcherBugWorkaround: Multiple files found. Original Unity passed file path: '{e.FullPath}' adjusted to found: '{fileFoundInAssets[0]}'");
|
||||
filePathToUse = fileFoundInAssets[0];
|
||||
}
|
||||
}
|
||||
|
||||
if (_currentFileExclusions != null && _currentFileExclusions.Any(fp => filePathToUse.Replace("\\", "/").EndsWith(fp)))
|
||||
{
|
||||
LoggerScoped.LogWarning($"FastScriptReload: File: '{filePathToUse}' changed, but marked as exclusion. Hot-Reload will not be performed. You can manage exclusions via" +
|
||||
$"\r\nRight click context menu (Fast Script Reload > Add / Remove Hot-Reload exclusion)" +
|
||||
$"\r\nor via Window -> Fast Script Reload -> Start Screen -> Exclusion menu");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const int msThresholdToConsiderSameChangeFromDifferentFileWatchers = 500;
|
||||
var isDuplicatedChangesComingFromDifferentFileWatcher = _dynamicFileHotReloadStateEntries
|
||||
.Any(f => f.FullFileName == filePathToUse
|
||||
&& (DateTime.UtcNow - f.FileChangedOn).TotalMilliseconds < msThresholdToConsiderSameChangeFromDifferentFileWatchers);
|
||||
if (isDuplicatedChangesComingFromDifferentFileWatcher)
|
||||
{
|
||||
LoggerScoped.LogWarning($"FastScriptReload: Looks like change to: {filePathToUse} have already been added for processing. This can happen if you have multiple file watchers set in a way that they overlap.");
|
||||
return;
|
||||
}
|
||||
|
||||
_dynamicFileHotReloadStateEntries.Add(new DynamicFileHotReloadState(filePathToUse, DateTime.UtcNow));
|
||||
}
|
||||
|
||||
public void StartWatchingDirectoryAndSubdirectories(string directoryPath, string filter, bool includeSubdirectories)
|
||||
{
|
||||
foreach (var kv in FileWatcherTokensToResolvePathFn)
|
||||
{
|
||||
directoryPath = directoryPath.Replace(kv.Key, kv.Value());
|
||||
}
|
||||
|
||||
var directoryInfo = new DirectoryInfo(directoryPath);
|
||||
|
||||
if (!directoryInfo.Exists)
|
||||
{
|
||||
LoggerScoped.LogWarning($"FastScriptReload: Directory: '{directoryPath}' does not exist, make sure file-watcher setup is correct. You can access via: Window -> Fast Script Reload -> File Watcher (Advanced Setup)");
|
||||
}
|
||||
|
||||
var fileWatcher = new FileSystemWatcher();
|
||||
|
||||
fileWatcher.Path = directoryInfo.FullName;
|
||||
fileWatcher.IncludeSubdirectories = includeSubdirectories;
|
||||
fileWatcher.Filter = filter;
|
||||
fileWatcher.NotifyFilter = NotifyFilters.LastWrite;
|
||||
fileWatcher.Changed += OnWatchedFileChange;
|
||||
|
||||
fileWatcher.EnableRaisingEvents = true;
|
||||
|
||||
_fileWatchers.Add(fileWatcher);
|
||||
}
|
||||
|
||||
static FastScriptReloadManager()
|
||||
{
|
||||
//do not add init code in here as with domain reload turned off it won't be properly set on play-mode enter, use Init method instead
|
||||
EditorApplication.update += Instance.Update;
|
||||
EditorApplication.playModeStateChanged += Instance.OnEditorApplicationOnplayModeStateChanged;
|
||||
}
|
||||
|
||||
~FastScriptReloadManager()
|
||||
{
|
||||
LoggerScoped.LogDebug("Destroying FSR Manager ");
|
||||
if (_instance != null)
|
||||
{
|
||||
if (_lastPlayModeStateChange == PlayModeStateChange.EnteredPlayMode)
|
||||
{
|
||||
LoggerScoped.LogError("Manager is being destroyed in play session, this indicates some sort of issue where static variables were reset, hot reload will not function properly please reset. " +
|
||||
"This is usually caused by Unity triggering that reset for some reason that's outside of asset control - other static variables will also be affected and recovering just hot reload would hide wider issue.");
|
||||
}
|
||||
ClearFileWatchers();
|
||||
}
|
||||
}
|
||||
|
||||
private const int BaseMenuItemPriority_ManualScriptOverride = 100;
|
||||
[MenuItem("Assets/Fast Script Reload/Add \\ Open User Script Rewrite Override", false, BaseMenuItemPriority_ManualScriptOverride + 1)]
|
||||
public static void AddHotReloadManualScriptOverride()
|
||||
{
|
||||
if (Selection.activeObject is MonoScript script)
|
||||
{
|
||||
ScriptGenerationOverridesManager.AddScriptOverride(script);
|
||||
}
|
||||
}
|
||||
|
||||
[MenuItem("Assets/Fast Script Reload/Add \\ Open User Script Rewrite Override", true)]
|
||||
public static bool AddHotReloadManualScriptOverrideValidateFn()
|
||||
{
|
||||
return Selection.activeObject is MonoScript;
|
||||
}
|
||||
|
||||
[MenuItem("Assets/Fast Script Reload/Remove User Script Rewrite Override", false, BaseMenuItemPriority_ManualScriptOverride + 2)]
|
||||
public static void RemoveHotReloadManualScriptOverride()
|
||||
{
|
||||
if (Selection.activeObject is MonoScript script)
|
||||
{
|
||||
ScriptGenerationOverridesManager.TryRemoveScriptOverride(script);
|
||||
}
|
||||
}
|
||||
|
||||
[MenuItem("Assets/Fast Script Reload/Remove User Script Rewrite Override", true)]
|
||||
public static bool RemoveHotReloadManualScriptOverrideValidateFn()
|
||||
{
|
||||
if (Selection.activeObject is MonoScript script)
|
||||
{
|
||||
return ScriptGenerationOverridesManager.TryGetScriptOverride(
|
||||
new FileInfo(Path.Combine(Path.Combine(Application.dataPath + "//..", AssetDatabase.GetAssetPath(script)))),
|
||||
out var _
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
[MenuItem("Assets/Fast Script Reload/Show User Script Rewrite Overrides", false, BaseMenuItemPriority_ManualScriptOverride + 3)]
|
||||
public static void ShowManualScriptRewriteOverridesInUi()
|
||||
{
|
||||
var window = FastScriptReloadWelcomeScreen.Init();
|
||||
window.OpenUserScriptRewriteOverridesSection();
|
||||
}
|
||||
|
||||
private const int BaseMenuItemPriority_Exclusions = 200;
|
||||
[MenuItem("Assets/Fast Script Reload/Add Hot-Reload Exclusion", false, BaseMenuItemPriority_Exclusions + 1)]
|
||||
public static void AddFileAsExcluded()
|
||||
{
|
||||
FastScriptReloadPreference.FilesExcludedFromHotReload.AddElement(ResolveRelativeToAssetDirectoryFilePath(Selection.activeObject));
|
||||
}
|
||||
|
||||
[MenuItem("Assets/Fast Script Reload/Add Hot-Reload Exclusion", true)]
|
||||
public static bool AddFileAsExcludedValidateFn()
|
||||
{
|
||||
return Selection.activeObject is MonoScript
|
||||
&& !((FastScriptReloadPreference.FilesExcludedFromHotReload.GetEditorPersistedValueOrDefault() as IEnumerable<string>) ?? Array.Empty<string>())
|
||||
.Contains(ResolveRelativeToAssetDirectoryFilePath(Selection.activeObject));
|
||||
}
|
||||
|
||||
[MenuItem("Assets/Fast Script Reload/Remove Hot-Reload Exclusion", false, BaseMenuItemPriority_Exclusions + 2)]
|
||||
public static void RemoveFileAsExcluded()
|
||||
{
|
||||
FastScriptReloadPreference.FilesExcludedFromHotReload.RemoveElement(ResolveRelativeToAssetDirectoryFilePath(Selection.activeObject));
|
||||
}
|
||||
|
||||
[MenuItem("Assets/Fast Script Reload/Remove Hot-Reload Exclusion", true)]
|
||||
public static bool RemoveFileAsExcludedValidateFn()
|
||||
{
|
||||
return Selection.activeObject is MonoScript
|
||||
&& ((FastScriptReloadPreference.FilesExcludedFromHotReload.GetEditorPersistedValueOrDefault() as IEnumerable<string>) ?? Array.Empty<string>())
|
||||
.Contains(ResolveRelativeToAssetDirectoryFilePath(Selection.activeObject));
|
||||
}
|
||||
|
||||
[MenuItem("Assets/Fast Script Reload/Show Exclusions", false, BaseMenuItemPriority_Exclusions + 3)]
|
||||
public static void ShowExcludedFilesInUi()
|
||||
{
|
||||
var window = FastScriptReloadWelcomeScreen.Init();
|
||||
window.OpenExclusionsSection();
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static string ResolveRelativeToAssetDirectoryFilePath(UnityEngine.Object obj)
|
||||
{
|
||||
return AssetDatabase.GetAssetPath(obj.GetInstanceID());
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
_isEditorModeHotReloadEnabled = (bool)FastScriptReloadPreference.EnableExperimentalEditorHotReloadSupport.GetEditorPersistedValueOrDefault();
|
||||
if (_lastPlayModeStateChange == PlayModeStateChange.ExitingPlayMode && Instance._fileWatchers.Any())
|
||||
{
|
||||
ClearFileWatchers();
|
||||
}
|
||||
|
||||
if (!_isEditorModeHotReloadEnabled && !EditorApplication.isPlaying)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_isEditorModeHotReloadEnabled)
|
||||
{
|
||||
EnsureInitialized();
|
||||
}
|
||||
else if (_lastPlayModeStateChange == PlayModeStateChange.EnteredPlayMode)
|
||||
{
|
||||
|
||||
EnsureInitialized();
|
||||
|
||||
// if (_lastPlayModeStateChange != PlayModeStateChange.ExitingPlayMode && Application.isPlaying && Instance._fileWatchers.Count == 0 && FastScriptReloadPreference.FileWatcherSetupEntries.GetElementsTyped().Count > 0)
|
||||
// {
|
||||
// LoggerScoped.LogWarning("Reinitializing file-watchers as defined configuration does not match current instance setup. If hot reload still doesn't work you'll need to reset play session.");
|
||||
// ClearFileWatchers();
|
||||
// EnsureInitialized();
|
||||
// }
|
||||
}
|
||||
|
||||
AssignConfigValuesThatCanNotBeAccessedOutsideOfMainThread();
|
||||
|
||||
if (!_assemblyChangesLoaderResolverResolutionAlreadyCalled)
|
||||
{
|
||||
AssemblyChangesLoaderResolver.Instance.Resolve(); //WARN: need to resolve initially in case monobehaviour singleton is not created
|
||||
_assemblyChangesLoaderResolverResolutionAlreadyCalled = true;
|
||||
}
|
||||
|
||||
if ((bool)FastScriptReloadPreference.EnableAutoReloadForChangedFiles.GetEditorPersistedValueOrDefault() &&
|
||||
(DateTime.UtcNow - _lastTimeChangeBatchRun).TotalSeconds > (int)FastScriptReloadPreference.BatchScriptChangesAndReloadEveryNSeconds.GetEditorPersistedValueOrDefault())
|
||||
{
|
||||
TriggerReloadForChangedFiles();
|
||||
}
|
||||
}
|
||||
|
||||
private static void ClearFileWatchers()
|
||||
{
|
||||
foreach (var fileWatcher in Instance._fileWatchers)
|
||||
{
|
||||
fileWatcher.Dispose();
|
||||
}
|
||||
|
||||
Instance._fileWatchers.Clear();
|
||||
}
|
||||
|
||||
private void AssignConfigValuesThatCanNotBeAccessedOutsideOfMainThread()
|
||||
{
|
||||
//TODO: PERF: needed in file watcher but when run on non-main thread causes exception.
|
||||
_currentFileExclusions = FastScriptReloadPreference.FilesExcludedFromHotReload.GetElements();
|
||||
_triggerDomainReloadIfOverNDynamicallyLoadedAssembles = (int)FastScriptReloadPreference.TriggerDomainReloadIfOverNDynamicallyLoadedAssembles.GetEditorPersistedValueOrDefault();
|
||||
_isOnDemandHotReloadEnabled = (bool)FastScriptReloadPreference.EnableOnDemandReload.GetEditorPersistedValueOrDefault();
|
||||
EnableExperimentalThisCallLimitationFix = (bool)FastScriptReloadPreference.EnableExperimentalThisCallLimitationFix.GetEditorPersistedValueOrDefault();
|
||||
AssemblyChangesLoaderEditorOptionsNeededInBuild.UpdateValues(
|
||||
(bool)FastScriptReloadPreference.IsDidFieldsOrPropertyCountChangedCheckDisabled.GetEditorPersistedValueOrDefault(),
|
||||
(bool)FastScriptReloadPreference.EnableExperimentalAddedFieldsSupport.GetEditorPersistedValueOrDefault()
|
||||
);
|
||||
}
|
||||
|
||||
public void TriggerReloadForChangedFiles()
|
||||
{
|
||||
if (!Application.isPlaying && _hotReloadPerformedCount > _triggerDomainReloadIfOverNDynamicallyLoadedAssembles)
|
||||
{
|
||||
LoggerScoped.LogWarning($"Dynamically created assembles reached over: {_triggerDomainReloadIfOverNDynamicallyLoadedAssembles} - triggering full domain reload to clean up. You can adjust that value in settings.");
|
||||
#if UNITY_2019_3_OR_NEWER
|
||||
CompilationPipeline.RequestScriptCompilation(); //TODO: add some timer to ensure this does not go into some kind of loop
|
||||
#elif UNITY_2017_1_OR_NEWER
|
||||
var editorAssembly = Assembly.GetAssembly(typeof(Editor));
|
||||
var editorCompilationInterfaceType = editorAssembly.GetType("UnityEditor.Scripting.ScriptCompilation.EditorCompilationInterface");
|
||||
var dirtyAllScriptsMethod = editorCompilationInterfaceType.GetMethod("DirtyAllScripts", BindingFlags.Static | BindingFlags.Public);
|
||||
dirtyAllScriptsMethod.Invoke(editorCompilationInterfaceType, null);
|
||||
#endif
|
||||
}
|
||||
|
||||
var assemblyChangesLoader = AssemblyChangesLoaderResolver.Instance.Resolve();
|
||||
var changesAwaitingHotReload = _dynamicFileHotReloadStateEntries
|
||||
.Where(e => e.IsAwaitingCompilation)
|
||||
.ToList();
|
||||
|
||||
if (changesAwaitingHotReload.Any())
|
||||
{
|
||||
changesAwaitingHotReload.ForEach(c => { c.IsBeingProcessed = true; });
|
||||
|
||||
var unityMainThreadDispatcher = UnityMainThreadDispatcher.Instance.EnsureInitialized(); //need to pass that in, resolving on other than main thread will cause exception
|
||||
Task.Run(() =>
|
||||
{
|
||||
List<string> sourceCodeFilesWithUniqueChangesAwaitingHotReload = null;
|
||||
try
|
||||
{
|
||||
sourceCodeFilesWithUniqueChangesAwaitingHotReload = changesAwaitingHotReload
|
||||
.GroupBy(e => e.FullFileName)
|
||||
.Select(e => e.First().FullFileName).ToList();
|
||||
|
||||
var dynamicallyLoadedAssemblyCompilerResult = DynamicAssemblyCompiler.Compile(sourceCodeFilesWithUniqueChangesAwaitingHotReload, unityMainThreadDispatcher);
|
||||
if (!dynamicallyLoadedAssemblyCompilerResult.IsError)
|
||||
{
|
||||
changesAwaitingHotReload.ForEach(c =>
|
||||
{
|
||||
c.FileCompiledOn = DateTime.UtcNow;
|
||||
c.AssemblyNameCompiledIn = dynamicallyLoadedAssemblyCompilerResult.CompiledAssemblyPath;
|
||||
});
|
||||
|
||||
//TODO: return some proper results to make sure entries are correctly updated
|
||||
assemblyChangesLoader.DynamicallyUpdateMethodsForCreatedAssembly(dynamicallyLoadedAssemblyCompilerResult.CompiledAssembly, AssemblyChangesLoaderEditorOptionsNeededInBuild);
|
||||
changesAwaitingHotReload.ForEach(c =>
|
||||
{
|
||||
c.HotSwappedOn = DateTime.UtcNow;
|
||||
c.IsBeingProcessed = false;
|
||||
}); //TODO: technically not all were hot swapped at same time
|
||||
|
||||
_hotReloadPerformedCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (dynamicallyLoadedAssemblyCompilerResult.MessagesFromCompilerProcess.Count > 0)
|
||||
{
|
||||
var msg = new StringBuilder();
|
||||
foreach (string message in dynamicallyLoadedAssemblyCompilerResult.MessagesFromCompilerProcess)
|
||||
{
|
||||
msg.AppendLine($"Error when compiling, it's best to check code and make sure it's compilable \r\n {message}\n");
|
||||
}
|
||||
|
||||
var errorMessage = msg.ToString();
|
||||
|
||||
changesAwaitingHotReload.ForEach(c =>
|
||||
{
|
||||
c.ErrorOn = DateTime.UtcNow;
|
||||
c.ErrorText = errorMessage;
|
||||
});
|
||||
|
||||
throw new Exception(errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerScoped.LogError($"Error when updating files: '{(sourceCodeFilesWithUniqueChangesAwaitingHotReload != null ? string.Join(",", sourceCodeFilesWithUniqueChangesAwaitingHotReload.Select(fn => new FileInfo(fn).Name)) : "unknown")}', {ex}");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_lastTimeChangeBatchRun = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
private void OnEditorApplicationOnplayModeStateChanged(PlayModeStateChange obj)
|
||||
{
|
||||
Instance._lastPlayModeStateChange = obj;
|
||||
|
||||
if ((bool)FastScriptReloadPreference.IsForceLockAssembliesViaCode.GetEditorPersistedValueOrDefault())
|
||||
{
|
||||
if (obj == PlayModeStateChange.EnteredPlayMode)
|
||||
{
|
||||
EditorApplication.LockReloadAssemblies();
|
||||
_wasLockReloadAssembliesCalled = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(obj == PlayModeStateChange.EnteredEditMode && _wasLockReloadAssembliesCalled)
|
||||
{
|
||||
EditorApplication.UnlockReloadAssemblies();
|
||||
_wasLockReloadAssembliesCalled = false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool HotReloadDisabled_WarningMessageShownAlready;
|
||||
|
||||
private static void EnsureInitialized()
|
||||
{
|
||||
if (!(bool)FastScriptReloadPreference.EnableAutoReloadForChangedFiles.GetEditorPersistedValueOrDefault()
|
||||
&& !(bool)FastScriptReloadPreference.EnableOnDemandReload.GetEditorPersistedValueOrDefault())
|
||||
{
|
||||
if (!HotReloadDisabled_WarningMessageShownAlready)
|
||||
{
|
||||
LoggerScoped.LogWarning($"Both auto hot reload and on-demand reload are disabled, file watchers will not be initialized. Please adjust settings and restart if you want hot reload to work.");
|
||||
HotReloadDisabled_WarningMessageShownAlready = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (Instance._fileWatchers.Count == 0)
|
||||
{
|
||||
var fileWatcherSetupEntries = FastScriptReloadPreference.FileWatcherSetupEntries.GetElementsTyped();
|
||||
if (fileWatcherSetupEntries.Count == 0)
|
||||
{
|
||||
LoggerScoped.LogWarning($"There are no file watcher setup entries. Tool will not be able to pick changes automatically");
|
||||
}
|
||||
|
||||
foreach (var fileWatcherSetupEntry in fileWatcherSetupEntries)
|
||||
{
|
||||
Instance.StartWatchingDirectoryAndSubdirectories(fileWatcherSetupEntry.path, fileWatcherSetupEntry.filter, fileWatcherSetupEntry.includeSubdirectories);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class DynamicFileHotReloadState
|
||||
{
|
||||
public string FullFileName { get; set; }
|
||||
public DateTime FileChangedOn { get; set; }
|
||||
public bool IsAwaitingCompilation => !IsFileCompiled && !ErrorOn.HasValue && !IsBeingProcessed;
|
||||
public bool IsFileCompiled => FileCompiledOn.HasValue;
|
||||
public DateTime? FileCompiledOn { get; set; }
|
||||
|
||||
public string AssemblyNameCompiledIn { get; set; }
|
||||
|
||||
public bool IsAwaitingHotSwap => IsFileCompiled && !HotSwappedOn.HasValue;
|
||||
public DateTime? HotSwappedOn { get; set; }
|
||||
public bool IsChangeHotSwapped {get; set; }
|
||||
|
||||
public string ErrorText { get; set; }
|
||||
public DateTime? ErrorOn { get; set; }
|
||||
public bool IsBeingProcessed { get; set; }
|
||||
|
||||
public DynamicFileHotReloadState(string fullFileName, DateTime fileChangedOn)
|
||||
{
|
||||
FullFileName = fullFileName;
|
||||
FileChangedOn = fileChangedOn;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,874 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FastScriptReload.Editor.Compilation;
|
||||
using FastScriptReload.Editor.Compilation.ScriptGenerationOverrides;
|
||||
using FastScriptReload.Runtime;
|
||||
using ImmersiveVRTools.Editor.Common.Utilities;
|
||||
using ImmersiveVRTools.Editor.Common.WelcomeScreen;
|
||||
using ImmersiveVRTools.Editor.Common.WelcomeScreen.GuiElements;
|
||||
using ImmersiveVRTools.Editor.Common.WelcomeScreen.PreferenceDefinition;
|
||||
using ImmersiveVRTools.Editor.Common.WelcomeScreen.Utilities;
|
||||
using ImmersiveVrToolsCommon.Runtime.Logging;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Compilation;
|
||||
using UnityEditorInternal;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
namespace FastScriptReload.Editor
|
||||
{
|
||||
public class FastScriptReloadWelcomeScreen : ProductWelcomeScreenBase
|
||||
{
|
||||
public static string BaseUrl = "https://immersivevrtools.com";
|
||||
public static string GenerateGetUpdatesUrl(string userId, string versionId)
|
||||
{
|
||||
//WARN: the URL can sometimes be adjusted, make sure updated correctly
|
||||
return $"{BaseUrl}/updates/fast-script-reload/{userId}?CurrentVersion={versionId}";
|
||||
}
|
||||
public static string VersionId = "1.4";
|
||||
private static readonly string ProjectIconName = "ProductIcon64";
|
||||
public static readonly string ProjectName = "fast-script-reload";
|
||||
|
||||
private static Vector2 _WindowSizePx = new Vector2(650, 500);
|
||||
private static string _WindowTitle = "Fast Script Reload";
|
||||
|
||||
public static ChangeMainViewButton ExclusionsSection { get; private set; }
|
||||
public static ChangeMainViewButton EditorHotReloadSection { get; private set; }
|
||||
public static ChangeMainViewButton NewFieldsSection { get; private set; }
|
||||
public static ChangeMainViewButton UserScriptRewriteOverrides { get; private set; }
|
||||
|
||||
public void OpenExclusionsSection()
|
||||
{
|
||||
ExclusionsSection.OnClick(this);
|
||||
}
|
||||
|
||||
public void OpenUserScriptRewriteOverridesSection()
|
||||
{
|
||||
UserScriptRewriteOverrides.OnClick(this);
|
||||
}
|
||||
|
||||
public void OpenEditorHotReloadSection()
|
||||
{
|
||||
EditorHotReloadSection.OnClick(this);
|
||||
}
|
||||
|
||||
public void OpenNewFieldsSection()
|
||||
{
|
||||
NewFieldsSection.OnClick(this);
|
||||
}
|
||||
|
||||
private static readonly ScrollViewGuiSection MainScrollViewSection = new ScrollViewGuiSection(
|
||||
"", (screen) =>
|
||||
{
|
||||
GenerateCommonWelcomeText(FastScriptReloadPreference.ProductName, screen);
|
||||
|
||||
GUILayout.Label("Enabled Features:", screen.LabelStyle);
|
||||
using (LayoutHelper.LabelWidth(350))
|
||||
{
|
||||
ProductPreferenceBase.RenderGuiAndPersistInput(FastScriptReloadPreference.EnableAutoReloadForChangedFiles);
|
||||
RenderSettingsWithCheckLimitationsButton(FastScriptReloadPreference.EnableExperimentalAddedFieldsSupport, true, () => ((FastScriptReloadWelcomeScreen)screen).OpenNewFieldsSection());
|
||||
RenderSettingsWithCheckLimitationsButton(FastScriptReloadPreference.EnableExperimentalEditorHotReloadSupport, false, () => ((FastScriptReloadWelcomeScreen)screen).OpenEditorHotReloadSection());
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
private static void RenderSettingsWithCheckLimitationsButton(ToggleProjectEditorPreferenceDefinition preferenceDefinition, bool allowChange, Action onCheckLimitationsClick)
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
if (!allowChange)
|
||||
{
|
||||
using (LayoutHelper.LabelWidth(313))
|
||||
{
|
||||
EditorGUILayout.LabelField(preferenceDefinition.Label);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ProductPreferenceBase.RenderGuiAndPersistInput(preferenceDefinition);
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Check limitations"))
|
||||
{
|
||||
onCheckLimitationsClick();
|
||||
}
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
private static readonly List<GuiSection> LeftSections = CreateLeftSections(new List<ChangeMainViewButton>
|
||||
{
|
||||
new ChangeMainViewButton("On-Device\r\nHot-Reload",
|
||||
(screen) =>
|
||||
{
|
||||
EditorGUILayout.LabelField("Live Script Reload", screen.BoldTextStyle);
|
||||
|
||||
GUILayout.Space(10);
|
||||
EditorGUILayout.LabelField(@"There's an extension to this asset that'll allow you to include Hot-Reload capability in builds (standalone / Android), please click the button below to learn more.", screen.TextStyle);
|
||||
|
||||
GUILayout.Space(20);
|
||||
if (GUILayout.Button("View Live Script Reload on Asset Store"))
|
||||
{
|
||||
Application.OpenURL($"{RedirectBaseUrl}/live-script-reload-extension");
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
new LaunchSceneButton("Basic Example", (s) => GetScenePath("ExampleScene"), (screen) =>
|
||||
{
|
||||
GUILayout.Label(
|
||||
$@"Asset is very simple to use:
|
||||
|
||||
1) Hit play to start.
|
||||
2) Go to 'FunctionLibrary.cs' ({@"Assets/FastScriptReload/Examples/Scripts/"})", screen.TextStyle);
|
||||
|
||||
CreateOpenFunctionLibraryOnRippleMethodButton();
|
||||
|
||||
|
||||
GUILayout.Label(
|
||||
$@"3) Change 'Ripple' method (eg change line before return statement to 'p.z = v * 10'
|
||||
4) Save file
|
||||
5) See change immediately",
|
||||
screen.TextStyle
|
||||
);
|
||||
|
||||
GUILayout.Space(10);
|
||||
EditorGUILayout.HelpBox("There are some limitations to what can be Hot-Reloaded, documentation lists them under 'limitations' section.", MessageType.Warning);
|
||||
}), MainScrollViewSection);
|
||||
|
||||
protected static List<GuiSection> CreateLeftSections(List<ChangeMainViewButton> additionalSections, LaunchSceneButton launchSceneButton, ScrollViewGuiSection mainScrollViewSection)
|
||||
{
|
||||
return new List<GuiSection>() {
|
||||
new GuiSection("", new List<ClickableElement>
|
||||
{
|
||||
new LastUpdateButton("New Update!", (screen) => LastUpdateUpdateScrollViewSection.RenderMainScrollViewSection(screen)),
|
||||
new ChangeMainViewButton("Welcome", (screen) => mainScrollViewSection.RenderMainScrollViewSection(screen)),
|
||||
}),
|
||||
new GuiSection("Options", new List<ClickableElement>
|
||||
{
|
||||
new ChangeMainViewButton("Reload", (screen) =>
|
||||
{
|
||||
const int sectionBreakHeight = 15;
|
||||
GUILayout.Label(
|
||||
@"Asset watches all script files and automatically hot-reloads on change, you can disable that behaviour and reload on demand.",
|
||||
screen.TextStyle
|
||||
);
|
||||
|
||||
using (LayoutHelper.LabelWidth(320))
|
||||
{
|
||||
ProductPreferenceBase.RenderGuiAndPersistInput(FastScriptReloadPreference.EnableAutoReloadForChangedFiles);
|
||||
}
|
||||
GUILayout.Space(sectionBreakHeight);
|
||||
|
||||
EditorGUILayout.HelpBox("On demand reload :\r\n(only works if you opted in below, this is to avoid unnecessary file watching)\r\nvia Window -> Fast Script Reload -> Force Reload, \r\nor by calling 'FastScriptIterationManager.Instance.TriggerReloadForChangedFiles()'", MessageType.Warning);
|
||||
|
||||
using (LayoutHelper.LabelWidth(320))
|
||||
{
|
||||
ProductPreferenceBase.RenderGuiAndPersistInput(FastScriptReloadPreference.EnableOnDemandReload);
|
||||
}
|
||||
|
||||
GUILayout.Space(sectionBreakHeight);
|
||||
|
||||
GUILayout.Label(
|
||||
@"For performance reasons script changes are batched are reloaded every N seconds",
|
||||
screen.TextStyle
|
||||
);
|
||||
|
||||
using (LayoutHelper.LabelWidth(300))
|
||||
{
|
||||
ProductPreferenceBase.RenderGuiAndPersistInput(FastScriptReloadPreference.BatchScriptChangesAndReloadEveryNSeconds);
|
||||
}
|
||||
|
||||
GUILayout.Space(sectionBreakHeight);
|
||||
|
||||
using (LayoutHelper.LabelWidth(350))
|
||||
{
|
||||
ProductPreferenceBase.RenderGuiAndPersistInput(FastScriptReloadPreference.EnableExperimentalThisCallLimitationFix);
|
||||
}
|
||||
EditorGUILayout.HelpBox("Method calls utilizing 'this' will trigger compiler exception, if enabled tool will rewrite the calls to have proper type after adjustments." +
|
||||
"\r\n\r\nIn case you're seeing compile errors relating to 'this' keyword please let me know via support page. Also turning this setting off will prevent rewrite.", MessageType.Info);
|
||||
|
||||
GUILayout.Space(sectionBreakHeight);
|
||||
|
||||
using (LayoutHelper.LabelWidth(350))
|
||||
{
|
||||
ProductPreferenceBase.RenderGuiAndPersistInput(FastScriptReloadPreference.IsForceLockAssembliesViaCode);
|
||||
}
|
||||
EditorGUILayout.HelpBox(
|
||||
@"Sometimes Unity continues to reload assemblies on change in playmode even when Auto-Refresh is turned off.
|
||||
|
||||
Use this setting to force lock assemblies via code."
|
||||
, MessageType.Info);
|
||||
GUILayout.Space(sectionBreakHeight);
|
||||
|
||||
|
||||
using (LayoutHelper.LabelWidth(350))
|
||||
{
|
||||
ProductPreferenceBase.RenderGuiAndPersistInput(FastScriptReloadPreference.IsDidFieldsOrPropertyCountChangedCheckDisabled);
|
||||
}
|
||||
EditorGUILayout.HelpBox("By default if you add / remove fields, tool will not redirect method calls for recompiled class." +
|
||||
"\r\nYou can also enable added-fields support (experimental)." +
|
||||
"\r\n\r\nSome assets however will use IL weaving to adjust your classes (eg Mirror) as a post compile step. In that case it's quite likely hot-reload will still work. " +
|
||||
"\r\n\r\nTick this box for tool to try and reload changes when that happens."
|
||||
|
||||
, MessageType.Info);
|
||||
|
||||
}),
|
||||
(UserScriptRewriteOverrides = new ChangeMainViewButton("User Script\r\nRewrite Overrides", (screen) =>
|
||||
{
|
||||
EditorGUILayout.HelpBox(
|
||||
$@"For tool to work it'll need to slightly adjust your code to make it compilable. Sometimes due to existing limitations this can fail and you'll see an error.
|
||||
|
||||
You can specify custom script rewrite overrides, those are specified for specific parts of code that fail, eg method.
|
||||
|
||||
It will help overcome limitations in the short run while I work on implementing proper solution."
|
||||
, MessageType.Info);
|
||||
|
||||
EditorGUILayout.HelpBox(
|
||||
$@"To add:
|
||||
1) right-click in project panel on the file that causes the issue.
|
||||
2) select Fast Script Reload -> Add / Open User Script Rewrite Override
|
||||
|
||||
It'll open override file with template already in. You can read top comments that describe how to use it."
|
||||
, MessageType.Warning);
|
||||
|
||||
EditorGUILayout.LabelField("Existing User Defined Script Overrides:", screen.BoldTextStyle);
|
||||
Action executeAfterIteration = null;
|
||||
foreach (var scriptOverride in ScriptGenerationOverridesManager.UserDefinedScriptOverrides)
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
|
||||
EditorGUILayout.LabelField(scriptOverride.File.Name);
|
||||
if (GUILayout.Button("Open"))
|
||||
{
|
||||
InternalEditorUtility.OpenFileAtLineExternal(scriptOverride.File.FullName, 0);
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Delete"))
|
||||
{
|
||||
executeAfterIteration = () =>
|
||||
{
|
||||
if (EditorUtility.DisplayDialog("Are you sure", "This will permanently remove override file.", "Delete", "Keep File"))
|
||||
{
|
||||
ScriptGenerationOverridesManager.TryRemoveScriptOverride(scriptOverride);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
executeAfterIteration?.Invoke();
|
||||
})),
|
||||
(ExclusionsSection = new ChangeMainViewButton("Exclusions", (screen) =>
|
||||
{
|
||||
EditorGUILayout.HelpBox("Those are easiest to manage from Project window by right clicking on script file and selecting: " +
|
||||
"\r\nFast Script Reload -> Add Hot-Reload Exclusion " +
|
||||
"\r\nFast Script Reload -> Remove Hot-Reload Exclusion", MessageType.Info);
|
||||
GUILayout.Space(10);
|
||||
|
||||
ProductPreferenceBase.RenderGuiAndPersistInput(FastScriptReloadPreference.FilesExcludedFromHotReload);
|
||||
})),
|
||||
new ChangeMainViewButton("Debugging", (screen) =>
|
||||
{
|
||||
EditorGUILayout.HelpBox(
|
||||
@"To debug you'll need to set breakpoints in dynamically-compiled file.
|
||||
|
||||
BREAKPOINTS IN ORIGINAL FILE WON'T BE HIT!", MessageType.Error);
|
||||
|
||||
EditorGUILayout.HelpBox(
|
||||
@"You can do that via:
|
||||
- clicking link in console-window after change, eg
|
||||
'FSR: Files: FunctionLibrary.cs changed (click here to debug [in bottom details pane]) (...)'
|
||||
(it needs to be clicked in bottom details pane, double click will simply take you to log location)", MessageType.Warning);
|
||||
GUILayout.Space(10);
|
||||
|
||||
EditorGUILayout.HelpBox(@"Tool can also auto-open generated file on every change, to do so select below option", MessageType.Info);
|
||||
using (LayoutHelper.LabelWidth(350))
|
||||
{
|
||||
ProductPreferenceBase.RenderGuiAndPersistInput(FastScriptReloadPreference.IsAutoOpenGeneratedSourceFileOnChangeEnabled);
|
||||
}
|
||||
|
||||
GUILayout.Space(20);
|
||||
using (LayoutHelper.LabelWidth(350))
|
||||
{
|
||||
EditorGUILayout.LabelField("Logging", screen.BoldTextStyle);
|
||||
GUILayout.Space(5);
|
||||
ProductPreferenceBase.RenderGuiAndPersistInput(FastScriptReloadPreference.EnableDetailedDebugLogging);
|
||||
ProductPreferenceBase.RenderGuiAndPersistInput(FastScriptReloadPreference.LogHowToFixMessageOnCompilationError);
|
||||
ProductPreferenceBase.RenderGuiAndPersistInput(FastScriptReloadPreference.StopShowingAutoReloadEnabledDialogBox);
|
||||
ProductPreferenceBase.RenderGuiAndPersistInput(FastScriptReloadPreference.DebugWriteRewriteReasonAsComment);
|
||||
}
|
||||
})
|
||||
}.Concat(additionalSections).ToList()),
|
||||
new GuiSection("Experimental", new List<ClickableElement>
|
||||
{
|
||||
(NewFieldsSection = new ChangeMainViewButton("New Fields", (screen) =>
|
||||
{
|
||||
#if LiveScriptReload_Enabled
|
||||
EditorGUILayout.HelpBox(
|
||||
@"On Device Reload (in running build) - Not Supported
|
||||
If you enable - new fields WILL show in editor and work as expected but link with the device will be broken and changes won't be visible there!", MessageType.Error);
|
||||
GUILayout.Space(10);
|
||||
#endif
|
||||
|
||||
EditorGUILayout.HelpBox(
|
||||
@"Adding new fields is still in experimental mode, it will have issues.
|
||||
|
||||
When you encounter them please get in touch (via any support links above) and I'll be sure to sort them out. Thanks!", MessageType.Error);
|
||||
GUILayout.Space(10);
|
||||
|
||||
EditorGUILayout.HelpBox(
|
||||
@"Adding new fields will affect performance, behind the scenes your code is rewritten to access field via static dictionary.
|
||||
|
||||
Once you exit playmode and do a full recompile they'll turn to standard fields as you'd expect.
|
||||
|
||||
New fields will also show in editor - you can tweak them as normal variables.", MessageType.Warning);
|
||||
GUILayout.Space(10);
|
||||
|
||||
EditorGUILayout.HelpBox(
|
||||
@"LIMITATIONS: (full list and more info in docs)
|
||||
- outside classes can not call new fields added at runtime
|
||||
- new fields will only show in editor if they were already used at least once", MessageType.Info);
|
||||
GUILayout.Space(10);
|
||||
|
||||
using (LayoutHelper.LabelWidth(300))
|
||||
{
|
||||
ProductPreferenceBase.RenderGuiAndPersistInput(FastScriptReloadPreference.EnableExperimentalAddedFieldsSupport);
|
||||
}
|
||||
GUILayout.Space(10);
|
||||
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
EditorGUILayout.HelpBox(@"You're in playmode, for option to start working you need to restart playmode.", MessageType.Warning);
|
||||
}
|
||||
|
||||
GUILayout.Space(10);
|
||||
})),
|
||||
(EditorHotReloadSection = new ChangeMainViewButton("Editor Hot-Reload", (screen) =>
|
||||
{
|
||||
EditorGUILayout.HelpBox(@"Currently asset hot-reloads only in play-mode, you can enable experimental editor mode support here.
|
||||
|
||||
Please make sure to read limitation section as not all changes can be performed", MessageType.Warning);
|
||||
|
||||
EditorGUILayout.HelpBox(@"As an experimental feature it may be unstable and is not as reliable as play-mode workflow.
|
||||
|
||||
In some cases it can lock/crash editor.", MessageType.Error);
|
||||
GUILayout.Space(10);
|
||||
|
||||
using (LayoutHelper.LabelWidth(320))
|
||||
{
|
||||
var valueBefore = (bool)FastScriptReloadPreference.EnableExperimentalEditorHotReloadSupport.GetEditorPersistedValueOrDefault();
|
||||
ProductPreferenceBase.RenderGuiAndPersistInput(FastScriptReloadPreference.EnableExperimentalEditorHotReloadSupport);
|
||||
var valueAfter = (bool)FastScriptReloadPreference.EnableExperimentalEditorHotReloadSupport.GetEditorPersistedValueOrDefault();
|
||||
if (!valueBefore && valueAfter)
|
||||
{
|
||||
EditorUtility.DisplayDialog("Experimental feature",
|
||||
"Reloading outside of playmode is still in experimental phase. " +
|
||||
"\r\n\r\nIt's not as good as in-playmode workflow",
|
||||
"Ok");
|
||||
|
||||
#if UNITY_2019_3_OR_NEWER
|
||||
CompilationPipeline.RequestScriptCompilation();
|
||||
#elif UNITY_2017_1_OR_NEWER
|
||||
var editorAssembly = Assembly.GetAssembly(typeof(Editor));
|
||||
var editorCompilationInterfaceType = editorAssembly.GetType("UnityEditor.Scripting.ScriptCompilation.EditorCompilationInterface");
|
||||
var dirtyAllScriptsMethod = editorCompilationInterfaceType.GetMethod("DirtyAllScripts", BindingFlags.Static | BindingFlags.Public);
|
||||
dirtyAllScriptsMethod.Invoke(editorCompilationInterfaceType, null);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.Space(10);
|
||||
|
||||
EditorGUILayout.HelpBox(@"Tool will automatically trigger full domain reload after number of hot-reloads specified below has been reached.
|
||||
This is to ensure dynamically created and loaded assembles are cleared out properly", MessageType.Info);
|
||||
GUILayout.Space(10);
|
||||
|
||||
using (LayoutHelper.LabelWidth(420))
|
||||
{
|
||||
ProductPreferenceBase.RenderGuiAndPersistInput(FastScriptReloadPreference.TriggerDomainReloadIfOverNDynamicallyLoadedAssembles);
|
||||
}
|
||||
GUILayout.Space(10);
|
||||
}))
|
||||
}),
|
||||
new GuiSection("Advanced", new List<ClickableElement>
|
||||
{
|
||||
new ChangeMainViewButton("File Watchers", (screen) =>
|
||||
{
|
||||
EditorGUILayout.HelpBox(
|
||||
$@"Asset watches .cs files for changes. Unfortunately Unity's FileWatcher
|
||||
implementation has some performance issues.
|
||||
|
||||
By default all project directories can be watched, you can adjust that here.
|
||||
|
||||
path - which directory to watch
|
||||
filter - narrow down files to match filter, eg all *.cs files (*.cs)
|
||||
includeSubdirectories - whether child directories should be watched as well
|
||||
|
||||
{FastScriptReloadManager.FileWatcherReplacementTokenForApplicationDataPath} - you can use that token and it'll be replaced with your /Assets folder"
|
||||
, MessageType.Info);
|
||||
|
||||
EditorGUILayout.HelpBox("Recompile after making changes for file watchers to re-load.", MessageType.Warning);
|
||||
|
||||
ProductPreferenceBase.RenderGuiAndPersistInput(FastScriptReloadPreference.FileWatcherSetupEntries);
|
||||
}),
|
||||
new ChangeMainViewButton("Exclude References", (screen) =>
|
||||
{
|
||||
EditorGUILayout.HelpBox(
|
||||
$@"Asset pulls in all the references from changed assembly. If you're encountering some compilation errors relating to those - please use list below to exclude specific ones."
|
||||
, MessageType.Info);
|
||||
|
||||
EditorGUILayout.HelpBox($@"By default asset removes ExCSS.Unity as it collides with the Tuple type. If you need that library in changed code - please remove from the list", MessageType.Warning);
|
||||
|
||||
ProductPreferenceBase.RenderGuiAndPersistInput(FastScriptReloadPreference.ReferencesExcludedFromHotReload);
|
||||
})
|
||||
}),
|
||||
new GuiSection("Launch Demo", new List<ClickableElement>
|
||||
{
|
||||
launchSceneButton
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
private static readonly string RedirectBaseUrl = "https://immersivevrtools.com/redirect/fast-script-reload";
|
||||
private static readonly GuiSection TopSection = CreateTopSectionButtons(RedirectBaseUrl);
|
||||
|
||||
protected static GuiSection CreateTopSectionButtons(string redirectBaseUrl)
|
||||
{
|
||||
return new GuiSection("Support", new List<ClickableElement>
|
||||
{
|
||||
new OpenUrlButton("Documentation", $"{redirectBaseUrl}/documentation"),
|
||||
new OpenUrlButton("Discord", $"{redirectBaseUrl}/discord"),
|
||||
new OpenUrlButton("Unity Forum", $"{redirectBaseUrl}/unity-forum"),
|
||||
new OpenUrlButton("Contact", $"{redirectBaseUrl}/contact")
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private static readonly GuiSection BottomSection = new GuiSection(
|
||||
"I want to make this tool better. And I need your help!",
|
||||
$"It'd be great if you could share your feedback (good and bad) with me. I'm very keen to make this tool better and that can only happen with your help. Please use:",
|
||||
new List<ClickableElement>
|
||||
{
|
||||
new OpenUrlButton(" Unity Forum", $"{RedirectBaseUrl}/unity-forum"),
|
||||
new OpenUrlButton(" or Write a Short Review", $"{RedirectBaseUrl}/asset-store-review"),
|
||||
}
|
||||
);
|
||||
|
||||
public override string WindowTitle { get; } = _WindowTitle;
|
||||
public override Vector2 WindowSizePx { get; } = _WindowSizePx;
|
||||
|
||||
#if !LiveScriptReload_Enabled
|
||||
[MenuItem("Window/Fast Script Reload/Start Screen", false, 1999)]
|
||||
#endif
|
||||
public static FastScriptReloadWelcomeScreen Init()
|
||||
{
|
||||
return OpenWindow<FastScriptReloadWelcomeScreen>(_WindowTitle, _WindowSizePx);
|
||||
}
|
||||
|
||||
#if !LiveScriptReload_Enabled
|
||||
[MenuItem("Window/Fast Script Reload/Force Reload", true, 1999)]
|
||||
#endif
|
||||
public static bool ForceReloadValidate()
|
||||
{
|
||||
return EditorApplication.isPlaying && (bool)FastScriptReloadPreference.EnableOnDemandReload.GetEditorPersistedValueOrDefault();
|
||||
}
|
||||
|
||||
#if !LiveScriptReload_Enabled
|
||||
[MenuItem("Window/Fast Script Reload/Force Reload", false, 1999)]
|
||||
#endif
|
||||
public static void ForceReload()
|
||||
{
|
||||
if (!(bool)FastScriptReloadPreference.EnableOnDemandReload.GetEditorPersistedValueOrDefault())
|
||||
{
|
||||
LoggerScoped.LogWarning("On demand hot reload is disabled, can't perform. You can enable it via 'Window -> Fast Script Reload -> Start Screen -> Reload -> Enable on demand reload'");
|
||||
return;
|
||||
}
|
||||
|
||||
FastScriptReloadManager.Instance.TriggerReloadForChangedFiles();
|
||||
}
|
||||
|
||||
public void OnEnable()
|
||||
{
|
||||
OnEnableCommon(ProjectIconName);
|
||||
}
|
||||
|
||||
public void OnGUI()
|
||||
{
|
||||
RenderGUI(LeftSections, TopSection, BottomSection, MainScrollViewSection);
|
||||
}
|
||||
|
||||
protected static void CreateOpenFunctionLibraryOnRippleMethodButton()
|
||||
{
|
||||
if (GUILayout.Button("Open 'FunctionLibrary.cs'"))
|
||||
{
|
||||
var codeComponent = AssetDatabase.LoadAssetAtPath<MonoScript>(AssetDatabase.GUIDToAssetPath(AssetDatabase.FindAssets($"t:Script FunctionLibrary")[0]));
|
||||
CodeEditorManager.GotoScript(codeComponent, "Ripple");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class FastScriptReloadPreference : ProductPreferenceBase
|
||||
{
|
||||
public const string BuildSymbol_DetailedDebugLogging = "ImmersiveVrTools_DebugEnabled";
|
||||
|
||||
public const string ProductName = "Fast Script Reload";
|
||||
private static string[] ProductKeywords = new[] { "productivity", "tools" };
|
||||
|
||||
public static readonly IntProjectEditorPreferenceDefinition BatchScriptChangesAndReloadEveryNSeconds = new IntProjectEditorPreferenceDefinition(
|
||||
"Batch script changes and reload every N seconds", "BatchScriptChangesAndReloadEveryNSeconds", 1);
|
||||
|
||||
public static readonly ToggleProjectEditorPreferenceDefinition EnableAutoReloadForChangedFiles = new ToggleProjectEditorPreferenceDefinition(
|
||||
"Enable auto Hot-Reload for changed files (in play mode)", "EnableAutoReloadForChangedFiles", true);
|
||||
|
||||
public static readonly ToggleProjectEditorPreferenceDefinition EnableOnDemandReload = new ToggleProjectEditorPreferenceDefinition(
|
||||
"Enable on demand hot reload", "EnableOnDemandReload", false);
|
||||
|
||||
public static readonly ToggleProjectEditorPreferenceDefinition EnableExperimentalThisCallLimitationFix = new ToggleProjectEditorPreferenceDefinition(
|
||||
"(Experimental) Enable method calls with 'this' as argument fix", "EnableExperimentalThisCallLimitationFix", true, (object newValue, object oldValue) =>
|
||||
{
|
||||
DynamicCompilationBase.EnableExperimentalThisCallLimitationFix = (bool)newValue;
|
||||
},
|
||||
(value) =>
|
||||
{
|
||||
DynamicCompilationBase.EnableExperimentalThisCallLimitationFix = (bool)value;
|
||||
});
|
||||
|
||||
public static readonly StringListProjectEditorPreferenceDefinition FilesExcludedFromHotReload = new StringListProjectEditorPreferenceDefinition(
|
||||
"Files excluded from Hot-Reload", "FilesExcludedFromHotReload", new List<string> {}, isReadonly: true);
|
||||
|
||||
public static readonly StringListProjectEditorPreferenceDefinition ReferencesExcludedFromHotReload = new StringListProjectEditorPreferenceDefinition(
|
||||
"References to exclude from Hot-Reload", "ReferencesExcludedFromHotReload", new List<string>
|
||||
{
|
||||
"ExCSS.Unity.dll"
|
||||
}, (newValue, oldValue) =>
|
||||
{
|
||||
DynamicCompilationBase.ReferencesExcludedFromHotReload = (List<string>)newValue;
|
||||
},
|
||||
(value) =>
|
||||
{
|
||||
DynamicCompilationBase.ReferencesExcludedFromHotReload = (List<string>)value;
|
||||
});
|
||||
|
||||
public static readonly ToggleProjectEditorPreferenceDefinition LogHowToFixMessageOnCompilationError = new ToggleProjectEditorPreferenceDefinition(
|
||||
"Log how to fix message on compilation error", "LogHowToFixMessageOnCompilationError", true, (object newValue, object oldValue) =>
|
||||
{
|
||||
DynamicCompilationBase.LogHowToFixMessageOnCompilationError = (bool)newValue;
|
||||
},
|
||||
(value) =>
|
||||
{
|
||||
DynamicCompilationBase.LogHowToFixMessageOnCompilationError = (bool)value;
|
||||
}
|
||||
);
|
||||
|
||||
public static readonly ToggleProjectEditorPreferenceDefinition DebugWriteRewriteReasonAsComment = new ToggleProjectEditorPreferenceDefinition(
|
||||
"Write rewrite reason as comment in changed file", "DebugWriteRewriteReasonAsComment", false, (object newValue, object oldValue) =>
|
||||
{
|
||||
DynamicCompilationBase.DebugWriteRewriteReasonAsComment = (bool)newValue;
|
||||
},
|
||||
(value) =>
|
||||
{
|
||||
DynamicCompilationBase.DebugWriteRewriteReasonAsComment = (bool)value;
|
||||
});
|
||||
|
||||
public static readonly ToggleProjectEditorPreferenceDefinition IsAutoOpenGeneratedSourceFileOnChangeEnabled = new ToggleProjectEditorPreferenceDefinition(
|
||||
"Auto-open generated source file for debugging", "IsAutoOpenGeneratedSourceFileOnChangeEnabled", false);
|
||||
|
||||
public static readonly ToggleProjectEditorPreferenceDefinition StopShowingAutoReloadEnabledDialogBox = new ToggleProjectEditorPreferenceDefinition(
|
||||
"Stop showing assets/script auto-reload enabled warning", "StopShowingAutoReloadEnabledDialogBox", false);
|
||||
public static readonly ToggleProjectEditorPreferenceDefinition EnableDetailedDebugLogging = new ToggleProjectEditorPreferenceDefinition(
|
||||
"Enable detailed debug logging", "EnableDetailedDebugLogging", false,
|
||||
(object newValue, object oldValue) =>
|
||||
{
|
||||
BuildDefineSymbolManager.SetBuildDefineSymbolState(BuildSymbol_DetailedDebugLogging, (bool)newValue);
|
||||
},
|
||||
(value) =>
|
||||
{
|
||||
BuildDefineSymbolManager.SetBuildDefineSymbolState(BuildSymbol_DetailedDebugLogging, (bool)value);
|
||||
}
|
||||
);
|
||||
|
||||
public static readonly ToggleProjectEditorPreferenceDefinition IsDidFieldsOrPropertyCountChangedCheckDisabled = new ToggleProjectEditorPreferenceDefinition(
|
||||
"Disable added/removed fields check", "IsDidFieldsOrPropertyCountChangedCheckDisabled", false,
|
||||
(object newValue, object oldValue) =>
|
||||
{
|
||||
FastScriptReloadManager.Instance.AssemblyChangesLoaderEditorOptionsNeededInBuild.IsDidFieldsOrPropertyCountChangedCheckDisabled = (bool)newValue;
|
||||
},
|
||||
(value) =>
|
||||
{
|
||||
FastScriptReloadManager.Instance.AssemblyChangesLoaderEditorOptionsNeededInBuild.IsDidFieldsOrPropertyCountChangedCheckDisabled = (bool)value;
|
||||
}
|
||||
);
|
||||
|
||||
public static readonly ToggleProjectEditorPreferenceDefinition IsForceLockAssembliesViaCode = new ToggleProjectEditorPreferenceDefinition(
|
||||
"Force prevent assembly reload during playmode", "IsForceLockAssembliesViaCode", false);
|
||||
|
||||
public static readonly JsonObjectListProjectEditorPreferenceDefinition<FileWatcherSetupEntry> FileWatcherSetupEntries = new JsonObjectListProjectEditorPreferenceDefinition<FileWatcherSetupEntry>(
|
||||
"File Watchers Setup", "FileWatcherSetupEntries", new List<string>
|
||||
{
|
||||
JsonUtility.ToJson(new FileWatcherSetupEntry("<Application.dataPath>", "*.cs", true))
|
||||
},
|
||||
() => new FileWatcherSetupEntry("<Application.dataPath>", "*.cs", true)
|
||||
);
|
||||
|
||||
public static readonly ToggleProjectEditorPreferenceDefinition EnableExperimentalAddedFieldsSupport = new ToggleProjectEditorPreferenceDefinition(
|
||||
"(Experimental) Enable runtime added field support", "EnableExperimentalAddedFieldsSupport", true,
|
||||
(object newValue, object oldValue) =>
|
||||
{
|
||||
FastScriptReloadManager.Instance.AssemblyChangesLoaderEditorOptionsNeededInBuild.EnableExperimentalAddedFieldsSupport = (bool)newValue;
|
||||
},
|
||||
(value) =>
|
||||
{
|
||||
FastScriptReloadManager.Instance.AssemblyChangesLoaderEditorOptionsNeededInBuild.EnableExperimentalAddedFieldsSupport = (bool)value;
|
||||
});
|
||||
|
||||
public static readonly ToggleProjectEditorPreferenceDefinition EnableExperimentalEditorHotReloadSupport = new ToggleProjectEditorPreferenceDefinition(
|
||||
"(Experimental) Enable Hot-Reload outside of play mode", "EnableExperimentalEditorHotReloadSupport", false);
|
||||
|
||||
//TODO: potentially that's just a normal settings (also in playmode) - but in playmode user is unlikely to make this many changes
|
||||
public static readonly IntProjectEditorPreferenceDefinition TriggerDomainReloadIfOverNDynamicallyLoadedAssembles = new IntProjectEditorPreferenceDefinition(
|
||||
"Trigger full domain reload after N hot-reloads (when not in play mode)", "TriggerDomainReloadIfOverNDynamicallyLoadedAssembles", 50);
|
||||
|
||||
public static void SetCommonMaterialsShader(ShadersMode newShaderModeValue)
|
||||
{
|
||||
var rootToolFolder = AssetPathResolver.GetAssetFolderPathRelativeToScript(ScriptableObject.CreateInstance(typeof(FastScriptReloadWelcomeScreen)), 1);
|
||||
if (rootToolFolder.Contains("/Scripts"))
|
||||
{
|
||||
rootToolFolder = rootToolFolder.Replace("/Scripts", ""); //if nested remove that and go higher level
|
||||
}
|
||||
var assets = AssetDatabase.FindAssets("t:Material Point", new[] { rootToolFolder });
|
||||
|
||||
try
|
||||
{
|
||||
Shader shaderToUse = null;
|
||||
switch (newShaderModeValue)
|
||||
{
|
||||
case ShadersMode.HDRP: shaderToUse = Shader.Find("Shader Graphs/Point URP"); break;
|
||||
case ShadersMode.URP: shaderToUse = Shader.Find("Shader Graphs/Point URP"); break;
|
||||
case ShadersMode.Surface: shaderToUse = Shader.Find("Graph/Point Surface"); break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
foreach (var guid in assets)
|
||||
{
|
||||
var material = AssetDatabase.LoadAssetAtPath<Material>(AssetDatabase.GUIDToAssetPath(guid));
|
||||
if (material.shader.name != shaderToUse.name)
|
||||
{
|
||||
material.shader = shaderToUse;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LoggerScoped.LogWarning($"Shader does not exist: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public static List<ProjectEditorPreferenceDefinitionBase> PreferenceDefinitions = new List<ProjectEditorPreferenceDefinitionBase>()
|
||||
{
|
||||
CreateDefaultShowOptionPreferenceDefinition(),
|
||||
BatchScriptChangesAndReloadEveryNSeconds,
|
||||
EnableAutoReloadForChangedFiles,
|
||||
EnableExperimentalThisCallLimitationFix,
|
||||
LogHowToFixMessageOnCompilationError,
|
||||
StopShowingAutoReloadEnabledDialogBox,
|
||||
IsDidFieldsOrPropertyCountChangedCheckDisabled,
|
||||
FileWatcherSetupEntries,
|
||||
IsAutoOpenGeneratedSourceFileOnChangeEnabled,
|
||||
EnableExperimentalAddedFieldsSupport,
|
||||
ReferencesExcludedFromHotReload,
|
||||
EnableExperimentalEditorHotReloadSupport,
|
||||
TriggerDomainReloadIfOverNDynamicallyLoadedAssembles,
|
||||
IsForceLockAssembliesViaCode
|
||||
};
|
||||
|
||||
private static bool PrefsLoaded = false;
|
||||
|
||||
|
||||
#if !LiveScriptReload_Enabled
|
||||
#if UNITY_2019_1_OR_NEWER
|
||||
[SettingsProvider]
|
||||
public static SettingsProvider ImpostorsSettings()
|
||||
{
|
||||
return GenerateProvider(ProductName, ProductKeywords, PreferencesGUI);
|
||||
}
|
||||
|
||||
#else
|
||||
[PreferenceItem(ProductName)]
|
||||
#endif
|
||||
#endif
|
||||
public static void PreferencesGUI()
|
||||
{
|
||||
if (!PrefsLoaded)
|
||||
{
|
||||
LoadDefaults(PreferenceDefinitions);
|
||||
PrefsLoaded = true;
|
||||
}
|
||||
|
||||
RenderGuiCommon(PreferenceDefinitions);
|
||||
}
|
||||
|
||||
public enum ShadersMode
|
||||
{
|
||||
HDRP,
|
||||
URP,
|
||||
Surface
|
||||
}
|
||||
}
|
||||
|
||||
#if !LiveScriptReload_Enabled
|
||||
[InitializeOnLoad]
|
||||
#endif
|
||||
public class FastScriptReloadWelcomeScreenInitializer : WelcomeScreenInitializerBase
|
||||
{
|
||||
#if !LiveScriptReload_Enabled
|
||||
static FastScriptReloadWelcomeScreenInitializer()
|
||||
{
|
||||
var userId = ProductPreferenceBase.CreateDefaultUserIdDefinition(FastScriptReloadWelcomeScreen.ProjectName).GetEditorPersistedValueOrDefault().ToString();
|
||||
|
||||
HandleUnityStartup(
|
||||
() => FastScriptReloadWelcomeScreen.Init(),
|
||||
FastScriptReloadWelcomeScreen.GenerateGetUpdatesUrl(userId, FastScriptReloadWelcomeScreen.VersionId),
|
||||
new List<ProjectEditorPreferenceDefinitionBase>(),
|
||||
(isFirstRun) =>
|
||||
{
|
||||
AutoDetectAndSetShaderMode();
|
||||
}
|
||||
);
|
||||
|
||||
InitCommon();
|
||||
}
|
||||
#endif
|
||||
|
||||
protected static void InitCommon()
|
||||
{
|
||||
DisplayMessageIfLastDetourPotentiallyCrashedEditor();
|
||||
EnsureUserAwareOfAutoRefresh();
|
||||
|
||||
DynamicCompilationBase.LogHowToFixMessageOnCompilationError = (bool)FastScriptReloadPreference.LogHowToFixMessageOnCompilationError.GetEditorPersistedValueOrDefault();
|
||||
DynamicCompilationBase.DebugWriteRewriteReasonAsComment = (bool)FastScriptReloadPreference.DebugWriteRewriteReasonAsComment.GetEditorPersistedValueOrDefault();
|
||||
DynamicCompilationBase.ReferencesExcludedFromHotReload = (List<string>)FastScriptReloadPreference.ReferencesExcludedFromHotReload.GetElements();
|
||||
FastScriptReloadManager.Instance.AssemblyChangesLoaderEditorOptionsNeededInBuild.UpdateValues(
|
||||
(bool)FastScriptReloadPreference.IsDidFieldsOrPropertyCountChangedCheckDisabled.GetEditorPersistedValueOrDefault(),
|
||||
(bool)FastScriptReloadPreference.EnableExperimentalAddedFieldsSupport.GetEditorPersistedValueOrDefault()
|
||||
);
|
||||
|
||||
BuildDefineSymbolManager.SetBuildDefineSymbolState(FastScriptReloadPreference.BuildSymbol_DetailedDebugLogging,
|
||||
(bool)FastScriptReloadPreference.EnableDetailedDebugLogging.GetEditorPersistedValueOrDefault()
|
||||
);
|
||||
}
|
||||
|
||||
private static void EnsureUserAwareOfAutoRefresh()
|
||||
{
|
||||
var autoRefreshMode = (AssetPipelineAutoRefreshMode)EditorPrefs.GetInt("kAutoRefreshMode", EditorPrefs.GetBool("kAutoRefresh") ? 1 : 0);
|
||||
if (autoRefreshMode != AssetPipelineAutoRefreshMode.Enabled)
|
||||
return;
|
||||
|
||||
if ((bool)FastScriptReloadPreference.IsForceLockAssembliesViaCode.GetEditorPersistedValueOrDefault())
|
||||
return;
|
||||
|
||||
LoggerScoped.LogWarning("Fast Script Reload - asset auto refresh enabled - full reload will be triggered unless editor preference adjusted - see documentation for more details.");
|
||||
|
||||
if ((bool)FastScriptReloadPreference.StopShowingAutoReloadEnabledDialogBox.GetEditorPersistedValueOrDefault())
|
||||
return;
|
||||
|
||||
var chosenOption = EditorUtility.DisplayDialogComplex("Fast Script Reload - Warning",
|
||||
"Auto reload for assets/scripts is enabled." +
|
||||
$"\n\nThis means any change made in playmode will likely trigger full recompile." +
|
||||
$"\r\n\r\nIt's an editor setting and can be adjusted at any time via Edit -> Preferences -> Asset Pipeline -> Auto Refresh" +
|
||||
$"\r\n\r\nI can also adjust that for you now - that means you'll need to manually load changes (outside of playmode) via Assets -> Refresh (CTRL + R)." +
|
||||
$"\r\n\r\nIn some editor versions you can also set script compilation to happen outside of playmode and don't have to manually refresh. " +
|
||||
$"\r\n\r\nDepending on version you'll find it via: " +
|
||||
$"\r\n1) Edit -> Preferences -> General -> Script Changes While Playing -> Recompile After Finished Playing." +
|
||||
$"\r\n2) Edit -> Preferences -> Asset Pipeline -> Auto Refresh -> Enabled Outside Playmode",
|
||||
"Ok, disable asset auto refresh (I'll refresh manually when needed)",
|
||||
"No, don't change (stop showing this message)",
|
||||
"No, don't change"
|
||||
);
|
||||
|
||||
switch (chosenOption)
|
||||
{
|
||||
// change.
|
||||
case 0:
|
||||
EditorPrefs.SetInt("kAutoRefreshMode", (int)AssetPipelineAutoRefreshMode.Disabled);
|
||||
EditorPrefs.SetInt("kAutoRefresh", 0); //older unity versions
|
||||
break;
|
||||
|
||||
// don't change and stop showing message.
|
||||
case 1:
|
||||
FastScriptReloadPreference.StopShowingAutoReloadEnabledDialogBox.SetEditorPersistedValue(true);
|
||||
|
||||
break;
|
||||
|
||||
// don't change
|
||||
case 2:
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
LoggerScoped.LogError("Unrecognized option.");
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
//copied from internal UnityEditor.AssetPipelineAutoRefreshMode
|
||||
internal enum AssetPipelineAutoRefreshMode
|
||||
{
|
||||
Disabled,
|
||||
Enabled,
|
||||
EnabledOutsidePlaymode,
|
||||
}
|
||||
|
||||
private static void DisplayMessageIfLastDetourPotentiallyCrashedEditor()
|
||||
{
|
||||
const string firstInitSessionKey = "FastScriptReloadWelcomeScreenInitializer_FirstInitDone";
|
||||
if (!SessionState.GetBool(firstInitSessionKey, false))
|
||||
{
|
||||
SessionState.SetBool(firstInitSessionKey, true);
|
||||
|
||||
var lastDetour = DetourCrashHandler.RetrieveLastDetour();
|
||||
if (!string.IsNullOrEmpty(lastDetour))
|
||||
{
|
||||
EditorUtility.DisplayDialog("Fast Script Reload",
|
||||
$@"That's embarrassing!
|
||||
|
||||
It seems like I've crashed your editor, sorry!
|
||||
|
||||
Last detoured method was: '{lastDetour}'
|
||||
|
||||
If this happens again, please reach out via support and we'll sort it out.
|
||||
|
||||
In the meantime, you can exclude any file from Hot-Reload by
|
||||
1) right-clicking on .cs file in Project menu
|
||||
2) Fast Script Reload
|
||||
3) Add Hot-Reload Exclusion
|
||||
", "Ok");
|
||||
DetourCrashHandler.ClearDetourLog();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected static void AutoDetectAndSetShaderMode()
|
||||
{
|
||||
var usedShaderMode = FastScriptReloadPreference.ShadersMode.Surface;
|
||||
var renderPipelineAsset = GraphicsSettings.renderPipelineAsset;
|
||||
if (renderPipelineAsset == null)
|
||||
{
|
||||
usedShaderMode = FastScriptReloadPreference.ShadersMode.Surface;
|
||||
}
|
||||
else if (renderPipelineAsset.GetType().Name.Contains("HDRenderPipelineAsset"))
|
||||
{
|
||||
usedShaderMode = FastScriptReloadPreference.ShadersMode.HDRP;
|
||||
}
|
||||
else if (renderPipelineAsset.GetType().Name.Contains("UniversalRenderPipelineAsset"))
|
||||
{
|
||||
usedShaderMode = FastScriptReloadPreference.ShadersMode.URP;
|
||||
}
|
||||
|
||||
FastScriptReloadPreference.SetCommonMaterialsShader(usedShaderMode);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using ImmersiveVRTools.Editor.Common.WelcomeScreen.PreferenceDefinition;
|
||||
|
||||
namespace FastScriptReload.Editor
|
||||
{
|
||||
[Serializable]
|
||||
public class FileWatcherSetupEntry: JsonObjectListSerializable<FileWatcherSetupEntry>
|
||||
{
|
||||
public string path;
|
||||
public string filter;
|
||||
public bool includeSubdirectories;
|
||||
|
||||
public FileWatcherSetupEntry(string path, string filter, bool includeSubdirectories)
|
||||
{
|
||||
this.path = path;
|
||||
this.filter = filter;
|
||||
this.includeSubdirectories = includeSubdirectories;
|
||||
}
|
||||
|
||||
[Obsolete("Serialization required")]
|
||||
public FileWatcherSetupEntry()
|
||||
{
|
||||
}
|
||||
|
||||
public override List<IJsonObjectRepresentationRenderingInfo> GenerateRenderingInfo()
|
||||
{
|
||||
return new List<IJsonObjectRepresentationRenderingInfo>
|
||||
{
|
||||
new JsonObjectRepresentationStringRenderingInfo<FileWatcherSetupEntry>("Path", (e) => e.path, (o, val) => o.path = val, 230),
|
||||
new JsonObjectRepresentationStringRenderingInfo<FileWatcherSetupEntry>("Filter", (e) => e.filter, (o, val) => o.filter = val, 100),
|
||||
new JsonObjectRepresentationBoolRenderingInfo<FileWatcherSetupEntry>("Include Subdirectories", (e) => e.includeSubdirectories, (o, val) => o.includeSubdirectories = val, 145),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,126 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using FastScriptReload.Editor.Compilation.CodeRewriting;
|
||||
using FastScriptReload.Runtime;
|
||||
using FastScriptReload.Scripts.Runtime;
|
||||
using HarmonyLib;
|
||||
using ImmersiveVRTools.Editor.Common.Utilities;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FastScriptReload.Editor.NewFields
|
||||
{
|
||||
[InitializeOnLoad]
|
||||
public class NewFieldsRendererDefaultEditorPatch
|
||||
{
|
||||
private static List<string> _cachedKeys = new List<string>();
|
||||
|
||||
static NewFieldsRendererDefaultEditorPatch()
|
||||
{
|
||||
if ((bool)FastScriptReloadPreference.EnableExperimentalAddedFieldsSupport.GetEditorPersistedValueOrDefault())
|
||||
{
|
||||
var harmony = new Harmony(nameof(NewFieldsRendererDefaultEditorPatch));
|
||||
|
||||
var renderAdditionalFieldsOnOptimizedGuiPostfix = AccessTools.Method(typeof(NewFieldsRendererDefaultEditorPatch), nameof(OnOptimizedInspectorGUI));
|
||||
var noCustomEditorOriginalRenderingMethdod = AccessTools.Method("UnityEditor.GenericInspector:OnOptimizedInspectorGUI");
|
||||
harmony.Patch(noCustomEditorOriginalRenderingMethdod, postfix: new HarmonyMethod(renderAdditionalFieldsOnOptimizedGuiPostfix));
|
||||
|
||||
var renderAdditionalFieldsDrawDefaultInspectorPostfix = AccessTools.Method(typeof(NewFieldsRendererDefaultEditorPatch), nameof(DrawDefaultInspector));
|
||||
var customEditorRenderingMethod = AccessTools.Method("UnityEditor.Editor:DrawDefaultInspector");
|
||||
harmony.Patch(customEditorRenderingMethod, postfix: new HarmonyMethod(renderAdditionalFieldsDrawDefaultInspectorPostfix));
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnOptimizedInspectorGUI(Rect contentRect, UnityEditor.Editor __instance)
|
||||
{
|
||||
RenderNewlyAddedFields(__instance);
|
||||
}
|
||||
|
||||
private static void DrawDefaultInspector(UnityEditor.Editor __instance)
|
||||
{
|
||||
RenderNewlyAddedFields(__instance);
|
||||
}
|
||||
|
||||
private static void RenderNewlyAddedFields(UnityEditor.Editor __instance)
|
||||
{
|
||||
//TODO: perf optimize, this will be used for many types, perhaps keep which types changed and just pass type?
|
||||
if (__instance.target)
|
||||
{
|
||||
if (TemporaryNewFieldValues.TryGetDynamicallyAddedFieldValues(__instance.target, out var addedFieldValues))
|
||||
{
|
||||
EditorGUILayout.Space(10);
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.LabelField("[FSR] Dynamically Added Fields:");
|
||||
GuiTooltipHelper.AddHelperTooltip("Fields were dynamically added for hot-reload, you can adjust their values and on full reload they'll disappear from this section and move back to main one.");
|
||||
EditorGUILayout.EndHorizontal();
|
||||
EditorGUILayout.Space(5);
|
||||
|
||||
try
|
||||
{
|
||||
_cachedKeys.AddRange(addedFieldValues.Keys); //otherwise collection changed exception can happen
|
||||
|
||||
var newFieldNameToGetTypeFn = CreateNewFieldInitMethodRewriter.ResolveNewFieldsToTypeFn(
|
||||
AssemblyChangesLoader.Instance.GetRedirectedType(__instance.target.GetType())
|
||||
);
|
||||
|
||||
if(newFieldNameToGetTypeFn.Count == 0)
|
||||
return;
|
||||
|
||||
foreach (var addedFieldValueKey in _cachedKeys)
|
||||
{
|
||||
var newFieldType = (Type)newFieldNameToGetTypeFn[addedFieldValueKey]();
|
||||
|
||||
//rendering types come from UnityEditor.EditorGUI.DefaultPropertyField - that should handle all cases that editor can render
|
||||
if (newFieldType == typeof(int)) addedFieldValues[addedFieldValueKey] = EditorGUILayout.IntField(addedFieldValueKey, (int)addedFieldValues[addedFieldValueKey]);
|
||||
else if (newFieldType == typeof(bool)) addedFieldValues[addedFieldValueKey] = EditorGUILayout.Toggle(addedFieldValueKey, (bool)addedFieldValues[addedFieldValueKey]);
|
||||
else if (newFieldType == typeof(float)) addedFieldValues[addedFieldValueKey] = EditorGUILayout.FloatField(addedFieldValueKey, (float)addedFieldValues[addedFieldValueKey]);
|
||||
else if (newFieldType == typeof(string)) addedFieldValues[addedFieldValueKey] = EditorGUILayout.TextField(addedFieldValueKey, (string)addedFieldValues[addedFieldValueKey]);
|
||||
else if (newFieldType == typeof(Color)) addedFieldValues[addedFieldValueKey] = EditorGUILayout.ColorField(addedFieldValueKey, (Color)addedFieldValues[addedFieldValueKey]);
|
||||
//TODO: SerializedPropertyType.LayerMask
|
||||
else if (newFieldType == typeof(Enum)) addedFieldValues[addedFieldValueKey] = EditorGUILayout.EnumPopup(addedFieldValueKey, (Enum)addedFieldValues[addedFieldValueKey]);
|
||||
else if (newFieldType == typeof(Vector2)) addedFieldValues[addedFieldValueKey] = EditorGUILayout.Vector2Field(addedFieldValueKey, (Vector2)addedFieldValues[addedFieldValueKey]);
|
||||
else if (newFieldType == typeof(Vector3)) addedFieldValues[addedFieldValueKey] = EditorGUILayout.Vector3Field(addedFieldValueKey, (Vector3)addedFieldValues[addedFieldValueKey]);
|
||||
else if (newFieldType == typeof(Vector4)) addedFieldValues[addedFieldValueKey] = EditorGUILayout.Vector4Field(addedFieldValueKey, (Vector4)addedFieldValues[addedFieldValueKey]);
|
||||
else if (newFieldType == typeof(Rect)) addedFieldValues[addedFieldValueKey] = EditorGUILayout.RectField(addedFieldValueKey, (Rect)addedFieldValues[addedFieldValueKey]);
|
||||
//TODO: SerializedPropertyType.ArraySize
|
||||
//TODO: SerializedPropertyType.Character
|
||||
// else if (existingValueType == typeof(char)) addedFieldValues[addedFieldValueKey] = EditorGUILayout.TextField(addedFieldValueKey, (char)addedFieldValues[addedFieldValueKey]);
|
||||
else if (newFieldType == typeof(AnimationCurve)) addedFieldValues[addedFieldValueKey] = EditorGUILayout.CurveField(addedFieldValueKey, (AnimationCurve)addedFieldValues[addedFieldValueKey]);
|
||||
else if (newFieldType == typeof(Bounds)) addedFieldValues[addedFieldValueKey] = EditorGUILayout.BoundsField(addedFieldValueKey, (Bounds)addedFieldValues[addedFieldValueKey]);
|
||||
else if (newFieldType == typeof(Gradient)) addedFieldValues[addedFieldValueKey] = EditorGUILayout.GradientField(addedFieldValueKey, (Gradient)addedFieldValues[addedFieldValueKey]);
|
||||
//TODO: SerializedPropertyType.FixedBufferSize
|
||||
else if (newFieldType == typeof(Vector2Int)) addedFieldValues[addedFieldValueKey] = EditorGUILayout.Vector2IntField(addedFieldValueKey, (Vector2Int)addedFieldValues[addedFieldValueKey]);
|
||||
else if (newFieldType == typeof(Vector3Int)) addedFieldValues[addedFieldValueKey] = EditorGUILayout.Vector3IntField(addedFieldValueKey, (Vector3Int)addedFieldValues[addedFieldValueKey]);
|
||||
else if (newFieldType == typeof(RectInt)) addedFieldValues[addedFieldValueKey] = EditorGUILayout.RectIntField(addedFieldValueKey, (RectInt)addedFieldValues[addedFieldValueKey]);
|
||||
else if (newFieldType == typeof(BoundsInt)) addedFieldValues[addedFieldValueKey] = EditorGUILayout.BoundsIntField(addedFieldValueKey, (BoundsInt)addedFieldValues[addedFieldValueKey]);
|
||||
//TODO: SerializedPropertyType.Hash128
|
||||
else if (newFieldType == typeof(Quaternion))
|
||||
{
|
||||
//Quaternions are rendered as euler angles in editor
|
||||
addedFieldValues[addedFieldValueKey] = Quaternion.Euler(EditorGUILayout.Vector3Field(addedFieldValueKey, ((Quaternion)addedFieldValues[addedFieldValueKey]).eulerAngles));
|
||||
}
|
||||
else if (typeof(UnityEngine.Object).IsAssignableFrom(newFieldType))
|
||||
{
|
||||
addedFieldValues[addedFieldValueKey] = EditorGUILayout.ObjectField(new GUIContent(addedFieldValueKey), (UnityEngine.Object)addedFieldValues[addedFieldValueKey], newFieldType, __instance.target);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.LabelField($"{newFieldType.Name} - Unable to render");
|
||||
GuiTooltipHelper.AddHelperTooltip(
|
||||
$"Unable to handle added-field rendering for type: {newFieldType.Name}, it won't be rendered. Best workaround is to not add this type dynamically in current version.");
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_cachedKeys.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,243 +0,0 @@
|
|||
#if UNITY_EDITOR || LiveScriptReload_Enabled
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using HarmonyLib;
|
||||
using ImmersiveVRTools.Runtime.Common;
|
||||
using ImmersiveVRTools.Runtime.Common.Extensions;
|
||||
using ImmersiveVrToolsCommon.Runtime.Logging;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FastScriptReload.Runtime
|
||||
{
|
||||
[PreventHotReload]
|
||||
#if UNITY_EDITOR
|
||||
[UnityEditor.InitializeOnLoad]
|
||||
#endif
|
||||
public class AssemblyChangesLoader: IAssemblyChangesLoader
|
||||
{
|
||||
const BindingFlags ALL_BINDING_FLAGS = BindingFlags.Public | BindingFlags.NonPublic |
|
||||
BindingFlags.Static | BindingFlags.Instance |
|
||||
BindingFlags.FlattenHierarchy;
|
||||
|
||||
const BindingFlags ALL_DECLARED_METHODS_BINDING_FLAGS = BindingFlags.Public | BindingFlags.NonPublic |
|
||||
BindingFlags.Static | BindingFlags.Instance |
|
||||
BindingFlags.DeclaredOnly; //only declared methods can be redirected, otherwise it'll result in hang
|
||||
|
||||
public const string ClassnamePatchedPostfix = "__Patched_";
|
||||
public const string ON_HOT_RELOAD_METHOD_NAME = "OnScriptHotReload";
|
||||
public const string ON_HOT_RELOAD_NO_INSTANCE_STATIC_METHOD_NAME = "OnScriptHotReloadNoInstance";
|
||||
|
||||
private static readonly List<Type> ExcludeMethodsDefinedOnTypes = new List<Type>
|
||||
{
|
||||
typeof(MonoBehaviour),
|
||||
typeof(Behaviour),
|
||||
typeof(UnityEngine.Object),
|
||||
typeof(Component),
|
||||
typeof(System.Object)
|
||||
}; //TODO: move out and possibly define a way to exclude all non-client created code? as this will crash editor
|
||||
|
||||
private static AssemblyChangesLoader _instance;
|
||||
public static AssemblyChangesLoader Instance => _instance ?? (_instance = new AssemblyChangesLoader());
|
||||
|
||||
private Dictionary<Type, Type> _existingTypeToRedirectedType = new Dictionary<Type, Type>();
|
||||
|
||||
public void DynamicallyUpdateMethodsForCreatedAssembly(Assembly dynamicallyLoadedAssemblyWithUpdates, AssemblyChangesLoaderEditorOptionsNeededInBuild editorOptions)
|
||||
{
|
||||
try
|
||||
{
|
||||
var sw = new Stopwatch();
|
||||
sw.Start();
|
||||
|
||||
foreach (var createdType in dynamicallyLoadedAssemblyWithUpdates.GetTypes()
|
||||
.Where(t => (t.IsClass
|
||||
&& !typeof(Delegate).IsAssignableFrom(t)) //don't redirect delegates
|
||||
// || (t.IsValueType && !t.IsPrimitive) //struct check, ensure works
|
||||
)
|
||||
)
|
||||
{
|
||||
if (createdType.GetCustomAttribute<PreventHotReload>() != null)
|
||||
{
|
||||
//TODO: ideally type would be excluded from compilation not just from detour
|
||||
LoggerScoped.Log($"Type: {createdType.Name} marked as {nameof(PreventHotReload)} - ignoring change.");
|
||||
continue;
|
||||
}
|
||||
|
||||
var createdTypeNameWithoutPatchedPostfix = RemoveClassPostfix(createdType.FullName);
|
||||
if (ProjectTypeCache.AllTypesInNonDynamicGeneratedAssemblies.TryGetValue(createdTypeNameWithoutPatchedPostfix, out var matchingTypeInExistingAssemblies))
|
||||
{
|
||||
_existingTypeToRedirectedType[matchingTypeInExistingAssemblies] = createdType;
|
||||
|
||||
if (!editorOptions.IsDidFieldsOrPropertyCountChangedCheckDisabled
|
||||
&& !editorOptions.EnableExperimentalAddedFieldsSupport
|
||||
&& DidFieldsOrPropertyCountChanged(createdType, matchingTypeInExistingAssemblies))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var allDeclaredMethodsInExistingType = matchingTypeInExistingAssemblies.GetMethods(ALL_DECLARED_METHODS_BINDING_FLAGS)
|
||||
.Where(m => !ExcludeMethodsDefinedOnTypes.Contains(m.DeclaringType))
|
||||
.ToList();
|
||||
foreach (var createdTypeMethodToUpdate in createdType.GetMethods(ALL_DECLARED_METHODS_BINDING_FLAGS)
|
||||
.Where(m => !ExcludeMethodsDefinedOnTypes.Contains(m.DeclaringType)))
|
||||
{
|
||||
var createdTypeMethodToUpdateFullDescriptionWithoutPatchedClassPostfix = RemoveClassPostfix(createdTypeMethodToUpdate.FullDescription());
|
||||
var matchingMethodInExistingType = allDeclaredMethodsInExistingType.SingleOrDefault(m => m.FullDescription() == createdTypeMethodToUpdateFullDescriptionWithoutPatchedClassPostfix);
|
||||
if (matchingMethodInExistingType != null)
|
||||
{
|
||||
if (matchingMethodInExistingType.IsGenericMethod)
|
||||
{
|
||||
LoggerScoped.LogWarning($"Method: '{matchingMethodInExistingType.FullDescription()}' is generic. Hot-Reload for generic methods is not supported yet, you won't see changes for that method.");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (matchingMethodInExistingType.DeclaringType != null && matchingMethodInExistingType.DeclaringType.IsGenericType)
|
||||
{
|
||||
LoggerScoped.LogWarning($"Type for method: '{matchingMethodInExistingType.FullDescription()}' is generic. Hot-Reload for generic types is not supported yet, you won't see changes for that type.");
|
||||
continue;
|
||||
}
|
||||
|
||||
LoggerScoped.LogDebug($"Trying to detour method, from: '{matchingMethodInExistingType.FullDescription()}' to: '{createdTypeMethodToUpdate.FullDescription()}'");
|
||||
DetourCrashHandler.LogDetour(matchingMethodInExistingType.ResolveFullName());
|
||||
Memory.DetourMethod(matchingMethodInExistingType, createdTypeMethodToUpdate);
|
||||
}
|
||||
else
|
||||
{
|
||||
LoggerScoped.LogWarning($"Method: {createdTypeMethodToUpdate.FullDescription()} does not exist in initially compiled type: {matchingTypeInExistingAssemblies.FullName}. " +
|
||||
$"Adding new methods at runtime is not fully supported. \r\n" +
|
||||
$"It'll only work new method is only used by declaring class (eg private method)\r\n" +
|
||||
$"Make sure to add method before initial compilation.");
|
||||
}
|
||||
}
|
||||
|
||||
FindAndExecuteStaticOnScriptHotReloadNoInstance(createdType);
|
||||
FindAndExecuteOnScriptHotReload(matchingTypeInExistingAssemblies);
|
||||
}
|
||||
else
|
||||
{
|
||||
LoggerScoped.LogWarning($"FSR: Unable to find existing type for: '{createdType.FullName}', this is not an issue if you added new type. <color=orange>If it's an existing type please do a full domain-reload - one of optimisations is to cache existing types for later lookup on first call.</color>");
|
||||
FindAndExecuteStaticOnScriptHotReloadNoInstance(createdType);
|
||||
FindAndExecuteOnScriptHotReload(createdType);
|
||||
}
|
||||
}
|
||||
|
||||
LoggerScoped.Log($"Hot-reload completed (took {sw.ElapsedMilliseconds}ms)");
|
||||
}
|
||||
finally
|
||||
{
|
||||
DetourCrashHandler.ClearDetourLog();
|
||||
}
|
||||
}
|
||||
|
||||
public Type GetRedirectedType(Type forExistingType)
|
||||
{
|
||||
return _existingTypeToRedirectedType[forExistingType];
|
||||
}
|
||||
|
||||
private static bool DidFieldsOrPropertyCountChanged(Type createdType, Type matchingTypeInExistingAssemblies)
|
||||
{
|
||||
var createdTypeFieldAndProperties = createdType.GetFields(ALL_BINDING_FLAGS).Concat(createdType.GetProperties(ALL_BINDING_FLAGS).Cast<MemberInfo>()).ToList();
|
||||
var matchingTypeFieldAndProperties = matchingTypeInExistingAssemblies.GetFields(ALL_BINDING_FLAGS).Concat(matchingTypeInExistingAssemblies.GetProperties(ALL_BINDING_FLAGS).Cast<MemberInfo>()).ToList();
|
||||
if (createdTypeFieldAndProperties.Count != matchingTypeFieldAndProperties.Count)
|
||||
{
|
||||
var addedMemberNames = createdTypeFieldAndProperties.Select(m => m.Name).Except(matchingTypeFieldAndProperties.Select(m => m.Name)).ToList();
|
||||
LoggerScoped.LogError($"It seems you've added/removed field to changed script. This is not supported and will result in undefined behaviour. Hot-reload will not be performed for type: {matchingTypeInExistingAssemblies.Name}" +
|
||||
$"\r\n\r\nYou can skip the check and force reload anyway if needed, to do so go to: 'Window -> Fast Script Reload -> Start Screen -> Reload -> tick 'Disable added/removed fields check'" +
|
||||
(addedMemberNames.Any() ? $"\r\nAdded: {string.Join(", ", addedMemberNames)}" : ""));
|
||||
LoggerScoped.Log(
|
||||
$"<color=orange>There's an experimental feature that allows to add new fields (which are adjustable in editor), to enable please:</color>" +
|
||||
$"\r\n - Open Settings 'Window -> Fast Script Reload -> Start Screen -> New Fields -> tick 'Enable experimental added field support'");
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void FindAndExecuteStaticOnScriptHotReloadNoInstance(Type createdType)
|
||||
{
|
||||
var onScriptHotReloadStaticFnForType = createdType.GetMethod(ON_HOT_RELOAD_NO_INSTANCE_STATIC_METHOD_NAME,
|
||||
BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
if (onScriptHotReloadStaticFnForType != null)
|
||||
{
|
||||
UnityMainThreadDispatcher.Instance.Enqueue(() =>
|
||||
{
|
||||
onScriptHotReloadStaticFnForType.Invoke(null, null);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static void FindAndExecuteOnScriptHotReload(Type type)
|
||||
{
|
||||
var onScriptHotReloadFnForType = type.GetMethod(ON_HOT_RELOAD_METHOD_NAME, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
if (onScriptHotReloadFnForType != null)
|
||||
{
|
||||
UnityMainThreadDispatcher.Instance.Enqueue(() =>
|
||||
{
|
||||
if (!typeof(MonoBehaviour).IsAssignableFrom(type)) {
|
||||
LoggerScoped.LogWarning($"Type: {type.Name} is not {nameof(MonoBehaviour)}, {ON_HOT_RELOAD_METHOD_NAME} method can't be executed. You can still use static version: {ON_HOT_RELOAD_NO_INSTANCE_STATIC_METHOD_NAME}");
|
||||
return;
|
||||
}
|
||||
foreach (var instanceOfType in GameObject.FindObjectsOfType(type)) //TODO: perf - could find them in different way?
|
||||
{
|
||||
onScriptHotReloadFnForType.Invoke(instanceOfType, null);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static string RemoveClassPostfix(string fqdn)
|
||||
{
|
||||
return fqdn.Replace(ClassnamePatchedPostfix, string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[AttributeUsage(AttributeTargets.Assembly)]
|
||||
public class DynamicallyCreatedAssemblyAttribute : Attribute
|
||||
{
|
||||
public DynamicallyCreatedAssemblyAttribute()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class PreventHotReload : Attribute
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public interface IAssemblyChangesLoader
|
||||
{
|
||||
void DynamicallyUpdateMethodsForCreatedAssembly(Assembly dynamicallyLoadedAssemblyWithUpdates, AssemblyChangesLoaderEditorOptionsNeededInBuild editorOptions);
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class AssemblyChangesLoaderEditorOptionsNeededInBuild
|
||||
{
|
||||
public bool IsDidFieldsOrPropertyCountChangedCheckDisabled;
|
||||
public bool EnableExperimentalAddedFieldsSupport;
|
||||
|
||||
public AssemblyChangesLoaderEditorOptionsNeededInBuild(bool isDidFieldsOrPropertyCountChangedCheckDisabled, bool enableExperimentalAddedFieldsSupport)
|
||||
{
|
||||
IsDidFieldsOrPropertyCountChangedCheckDisabled = isDidFieldsOrPropertyCountChangedCheckDisabled;
|
||||
EnableExperimentalAddedFieldsSupport = enableExperimentalAddedFieldsSupport;
|
||||
}
|
||||
#pragma warning disable 0618
|
||||
[Obsolete("Needed for network serialization")]
|
||||
#pragma warning enable 0618
|
||||
public AssemblyChangesLoaderEditorOptionsNeededInBuild()
|
||||
{
|
||||
}
|
||||
|
||||
//WARN: make sure it has same params as ctor
|
||||
public void UpdateValues(bool isDidFieldsOrPropertyCountChangedCheckDisabled, bool enableExperimentalAddedFieldsSupport)
|
||||
{
|
||||
IsDidFieldsOrPropertyCountChangedCheckDisabled = isDidFieldsOrPropertyCountChangedCheckDisabled;
|
||||
EnableExperimentalAddedFieldsSupport = enableExperimentalAddedFieldsSupport;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
|
@ -1,43 +0,0 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using ImmersiveVRTools.Runtime.Common.Utilities;
|
||||
|
||||
#if UNITY_EDITOR || LiveScriptReload_Enabled
|
||||
|
||||
namespace FastScriptReload.Runtime
|
||||
{
|
||||
public class AssemblyChangesLoaderResolver
|
||||
{
|
||||
private static AssemblyChangesLoaderResolver _instance;
|
||||
public static AssemblyChangesLoaderResolver Instance => _instance ?? (_instance = new AssemblyChangesLoaderResolver());
|
||||
|
||||
private IAssemblyChangesLoader _cachedNetworkLoader;
|
||||
|
||||
public IAssemblyChangesLoader Resolve()
|
||||
{
|
||||
#if LiveScriptReload_Enabled
|
||||
//network loader is in add-on that's not referenced by this lib, use reflection to get instance
|
||||
if (_cachedNetworkLoader == null)
|
||||
{
|
||||
_cachedNetworkLoader = (IAssemblyChangesLoader)ReflectionHelper.GetAllTypes()
|
||||
.First(t => t.FullName == "LiveScriptReload.Runtime.NetworkedAssemblyChangesSender")
|
||||
.GetProperty("Instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy)
|
||||
.GetValue(null);
|
||||
}
|
||||
|
||||
if (_cachedNetworkLoader == null)
|
||||
{
|
||||
throw new Exception("Unable to resolve NetworkedAssemblyChangesSender, Live Script Reload will not work - please contact support");
|
||||
}
|
||||
|
||||
return _cachedNetworkLoader;
|
||||
#else
|
||||
return AssemblyChangesLoader.Instance;
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -1,76 +0,0 @@
|
|||
#if UNITY_EDITOR || LiveScriptReload_Enabled
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using ImmersiveVrToolsCommon.Runtime.Logging;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FastScriptReload.Runtime
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
[UnityEditor.InitializeOnLoad]
|
||||
#endif
|
||||
public class DetourCrashHandler
|
||||
{
|
||||
//TODO: add device support / android crashes / how to report issues back?
|
||||
public static string LastDetourFilePath;
|
||||
|
||||
static DetourCrashHandler()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
Init();
|
||||
#else
|
||||
LoggerScoped.Log($"{nameof(DetourCrashHandler)}: currently only supported in Editor");
|
||||
#endif
|
||||
}
|
||||
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterAssembliesLoaded)]
|
||||
static void Init()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
LastDetourFilePath = Path.GetTempPath() + Application.productName + "-last-detour.txt";
|
||||
foreach (var c in Path.GetInvalidFileNameChars())
|
||||
{
|
||||
LastDetourFilePath = LastDetourFilePath.Replace(c, '-');
|
||||
}
|
||||
#else
|
||||
LoggerScoped.Log($"{nameof(DetourCrashHandler)}: currently only supported in Editor");
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void LogDetour(string fullName)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
File.AppendAllText(LastDetourFilePath, fullName + Environment.NewLine);
|
||||
#else
|
||||
LoggerScoped.Log($"{nameof(DetourCrashHandler)}: currently only supported in Editor");
|
||||
#endif
|
||||
}
|
||||
|
||||
public static string RetrieveLastDetour()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
if (File.Exists(LastDetourFilePath))
|
||||
{
|
||||
var lines = File.ReadAllLines(LastDetourFilePath);
|
||||
return lines.Length > 0 ? lines.Last() : string.Empty;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
#else
|
||||
LoggerScoped.Log($"{nameof(DetourCrashHandler)}: currently only supported in Editor");
|
||||
return string.Empty;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void ClearDetourLog()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
File.Delete(LastDetourFilePath);
|
||||
#else
|
||||
LoggerScoped.Log($"{nameof(DetourCrashHandler)}: currently only supported in Editor");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
|
@ -1,20 +0,0 @@
|
|||
{
|
||||
"name": "FastScriptReload.Runtime",
|
||||
"rootNamespace": "",
|
||||
"references": [],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": true,
|
||||
"precompiledReferences": [
|
||||
"0Harmony.dll",
|
||||
"ImmersiveVRTools.Common.Runtime.dll"
|
||||
],
|
||||
"autoReferenced": false,
|
||||
"defineConstraints": [
|
||||
"UNITY_EDITOR || LiveScriptReload_IncludeInBuild_Enabled",
|
||||
"FastScriptReload"
|
||||
],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
using ImmersiveVrToolsCommon.Runtime.Logging;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FastScriptReload.Runtime
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
[UnityEditor.InitializeOnLoad]
|
||||
#endif
|
||||
public static class LoggerScopedInitializer
|
||||
{
|
||||
static LoggerScopedInitializer()
|
||||
{
|
||||
Init();
|
||||
}
|
||||
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterAssembliesLoaded)]
|
||||
static void Init()
|
||||
{
|
||||
LoggerScoped.LogPrefix = "FSR: ";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
#if UNITY_EDITOR || LiveScriptReload_Enabled
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace FastScriptReload.Runtime
|
||||
{
|
||||
public class ProjectTypeCache
|
||||
{
|
||||
private static bool _isInitialized;
|
||||
private static Dictionary<string, Type> _allTypesInNonDynamicGeneratedAssemblies;
|
||||
public static Dictionary<string, Type> AllTypesInNonDynamicGeneratedAssemblies
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!_isInitialized)
|
||||
{
|
||||
Init();
|
||||
}
|
||||
|
||||
return _allTypesInNonDynamicGeneratedAssemblies;
|
||||
}
|
||||
}
|
||||
|
||||
private static void Init()
|
||||
{
|
||||
if (_allTypesInNonDynamicGeneratedAssemblies == null)
|
||||
{
|
||||
var typeLookupSw = new Stopwatch();
|
||||
typeLookupSw.Start();
|
||||
|
||||
_allTypesInNonDynamicGeneratedAssemblies = AppDomain.CurrentDomain.GetAssemblies()
|
||||
.Where(a => !CustomAttributeExtensions.GetCustomAttributes<DynamicallyCreatedAssemblyAttribute>((Assembly)a).Any())
|
||||
.SelectMany(a => a.GetTypes())
|
||||
.GroupBy(t => t.FullName)
|
||||
.Select(g => g.First()) //TODO: quite odd that same type full name can be defined multiple times? eg Microsoft.CodeAnalysis.EmbeddedAttribute throws 'An item with the same key has already been added'
|
||||
.ToDictionary(t => t.FullName, t => t);
|
||||
|
||||
#if ImmersiveVrTools_DebugEnabled
|
||||
LoggerScoped.Log($"Initialized type-lookup dictionary, took: {typeLookupSw.ElapsedMilliseconds}ms - cached");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
#endif
|
|
@ -1,94 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Dynamic;
|
||||
using UnityEngine;
|
||||
using Object = System.Object;
|
||||
|
||||
namespace FastScriptReload.Scripts.Runtime
|
||||
{
|
||||
public static class TemporaryNewFieldValues
|
||||
{
|
||||
public delegate object GetNewFieldInitialValue(Type forNewlyGeneratedType);
|
||||
public delegate Type GetNewFieldType(Type forNewlyGeneratedType);
|
||||
|
||||
private static readonly Dictionary<object, ExpandoForType> _existingObjectToFiledNameValueMap = new Dictionary<object, ExpandoForType>();
|
||||
private static readonly Dictionary<Type, Dictionary<string, GetNewFieldInitialValue>> _existingObjectTypeToFieldNameToCreateDetaultValueFn = new Dictionary<Type, Dictionary<string, GetNewFieldInitialValue>>();
|
||||
private static readonly Dictionary<Type, Dictionary<string, GetNewFieldType>> _existingObjectTypeToFieldNameToType = new Dictionary<Type, Dictionary<string, GetNewFieldType>>();
|
||||
|
||||
//Unity by default will auto init some classes, like gradient, but those are not value types so need to be initialized manually
|
||||
private static Dictionary<Type, Func<object>> ReferenceTypeToCreateDefaultValueFn = new Dictionary<Type, Func<object>>()
|
||||
{
|
||||
[typeof(Gradient)] = () => new Gradient(),
|
||||
[typeof(AnimationCurve)] = () => new AnimationCurve(),
|
||||
};
|
||||
|
||||
public static void RegisterNewFields(Type existingType, Dictionary<string, GetNewFieldInitialValue> fieldNameToGenerateDefaultValueFn, Dictionary<string, GetNewFieldType> fieldNameToGetTypeFn)
|
||||
{
|
||||
_existingObjectTypeToFieldNameToCreateDetaultValueFn[existingType] = fieldNameToGenerateDefaultValueFn;
|
||||
_existingObjectTypeToFieldNameToType[existingType] = fieldNameToGetTypeFn;
|
||||
}
|
||||
|
||||
public static dynamic ResolvePatchedObject<TCreatedType>(object original)
|
||||
{
|
||||
if (!_existingObjectToFiledNameValueMap.TryGetValue(original, out var existingExpandoToObjectTypePair))
|
||||
{
|
||||
var patchedObject = new ExpandoObject();
|
||||
var expandoForType = new ExpandoForType { ForType = typeof(TCreatedType), Object = patchedObject };
|
||||
|
||||
InitializeAdditionalFieldValues<TCreatedType>(original, patchedObject);
|
||||
_existingObjectToFiledNameValueMap[original] = expandoForType;
|
||||
|
||||
return patchedObject;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (existingExpandoToObjectTypePair.ForType != typeof(TCreatedType))
|
||||
{
|
||||
InitializeAdditionalFieldValues<TCreatedType>(original, existingExpandoToObjectTypePair.Object);
|
||||
existingExpandoToObjectTypePair.ForType = typeof(TCreatedType);
|
||||
}
|
||||
|
||||
return existingExpandoToObjectTypePair.Object;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool TryGetDynamicallyAddedFieldValues(object forObject, out IDictionary<string, object> addedFieldValues)
|
||||
{
|
||||
if (_existingObjectToFiledNameValueMap.TryGetValue(forObject, out var expandoForType))
|
||||
{
|
||||
addedFieldValues = expandoForType.Object;
|
||||
return true;
|
||||
}
|
||||
|
||||
addedFieldValues = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void InitializeAdditionalFieldValues<TCreatedType>(object original, ExpandoObject patchedObject)
|
||||
{
|
||||
var originalType = original.GetType(); //TODO: PERF: resolve via TOriginal, not getType
|
||||
var patchedObjectAsDict = patchedObject as IDictionary<string, Object>;
|
||||
foreach (var fieldNameToGenerateDefaultValueFn in _existingObjectTypeToFieldNameToCreateDetaultValueFn[originalType])
|
||||
{
|
||||
if (!patchedObjectAsDict.ContainsKey(fieldNameToGenerateDefaultValueFn.Key))
|
||||
{
|
||||
patchedObjectAsDict[fieldNameToGenerateDefaultValueFn.Key] = fieldNameToGenerateDefaultValueFn.Value(typeof(TCreatedType));
|
||||
|
||||
if (patchedObjectAsDict[fieldNameToGenerateDefaultValueFn.Key] == null)
|
||||
{
|
||||
var fieldType = _existingObjectTypeToFieldNameToType[originalType][fieldNameToGenerateDefaultValueFn.Key](typeof(TCreatedType));
|
||||
if (ReferenceTypeToCreateDefaultValueFn.TryGetValue(fieldType, out var createValueFn))
|
||||
{
|
||||
patchedObjectAsDict[fieldNameToGenerateDefaultValueFn.Key] = createValueFn();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class ExpandoForType {
|
||||
public Type ForType;
|
||||
public ExpandoObject Object;
|
||||
}
|
||||
}
|
|
@ -1,102 +0,0 @@
|
|||
Asset is governed by the Asset Store EULA; however, the following components are governed by the licenses indicated below:
|
||||
|
||||
1) Harmony
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 Andreas Pardeike
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
2) Roslyn
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) .NET Foundation and Contributors
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
3) ImmersiveVrToolsCommon
|
||||
This code is part of the product and can be used with same licence as the one you purchased it with.
|
||||
|
||||
Left in Plugins folder solely for convenience.
|
||||
|
||||
4) MonoMod.Common
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2019 - 2020 0x0ade
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
4) Mono.Cecil
|
||||
Copyright (c) 2008 - 2015 Jb Evain
|
||||
Copyright (c) 2008 - 2011 Novell, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -136,8 +136,9 @@ PlayerSettings:
|
|||
vulkanEnableLateAcquireNextImage: 0
|
||||
vulkanEnableCommandBufferRecycling: 1
|
||||
loadStoreDebugModeEnabled: 0
|
||||
bundleVersion: 0.1.34
|
||||
preloadedAssets: []
|
||||
bundleVersion: 0.1.41
|
||||
preloadedAssets:
|
||||
- {fileID: 11400000, guid: 7b9d430162275634bb11a99d38617f9e, type: 2}
|
||||
metroInputSource: 0
|
||||
wsaTransparentSwapchain: 0
|
||||
m_HolographicPauseOnTrackingLoss: 1
|
||||
|
|
Loading…
Reference in New Issue