diff --git a/.idea/.idea.HALLUCINATE/.idea/workspace.xml b/.idea/.idea.HALLUCINATE/.idea/workspace.xml index d52babcd..b0fea62f 100644 --- a/.idea/.idea.HALLUCINATE/.idea/workspace.xml +++ b/.idea/.idea.HALLUCINATE/.idea/workspace.xml @@ -6,18 +6,24 @@ - + + + + + - - - - + + - - - - + + + + + + + + + + diff --git a/Assets/Photon.meta b/Assets/Photon.meta new file mode 100644 index 00000000..77f5e7ad --- /dev/null +++ b/Assets/Photon.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: cb3038710ebdb584b92682fee3d58c63 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion.meta b/Assets/Photon/Fusion.meta new file mode 100644 index 00000000..d8eee399 --- /dev/null +++ b/Assets/Photon/Fusion.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: bafa2422cf58b4644a715c84eace4afb +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Assemblies.meta b/Assets/Photon/Fusion/Assemblies.meta new file mode 100644 index 00000000..8220c588 --- /dev/null +++ b/Assets/Photon/Fusion/Assemblies.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 9948d0a8df1aa5f48a3d08e3cb0730fb +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Assemblies/Debug.meta b/Assets/Photon/Fusion/Assemblies/Debug.meta new file mode 100644 index 00000000..0a3f953d --- /dev/null +++ b/Assets/Photon/Fusion/Assemblies/Debug.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 418b228c754fd71489425cd7e0b1f4a8 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Assemblies/Debug/Fusion.Common.dll.debug b/Assets/Photon/Fusion/Assemblies/Debug/Fusion.Common.dll.debug new file mode 100644 index 00000000..dc0c80f8 Binary files /dev/null and b/Assets/Photon/Fusion/Assemblies/Debug/Fusion.Common.dll.debug differ diff --git a/Assets/Photon/Fusion/Assemblies/Debug/Fusion.Common.dll.debug.meta b/Assets/Photon/Fusion/Assemblies/Debug/Fusion.Common.dll.debug.meta new file mode 100644 index 00000000..1329df44 --- /dev/null +++ b/Assets/Photon/Fusion/Assemblies/Debug/Fusion.Common.dll.debug.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 318382aa889ac534caef844842014a97 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Assemblies/Debug/Fusion.Log.dll.debug b/Assets/Photon/Fusion/Assemblies/Debug/Fusion.Log.dll.debug new file mode 100644 index 00000000..c948ff01 Binary files /dev/null and b/Assets/Photon/Fusion/Assemblies/Debug/Fusion.Log.dll.debug differ diff --git a/Assets/Photon/Fusion/Assemblies/Debug/Fusion.Log.dll.debug.meta b/Assets/Photon/Fusion/Assemblies/Debug/Fusion.Log.dll.debug.meta new file mode 100644 index 00000000..8bd5b536 --- /dev/null +++ b/Assets/Photon/Fusion/Assemblies/Debug/Fusion.Log.dll.debug.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 07c5108e22d44a940a193b472beec2ce +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Assemblies/Debug/Fusion.Realtime.dll.debug b/Assets/Photon/Fusion/Assemblies/Debug/Fusion.Realtime.dll.debug new file mode 100644 index 00000000..69029644 Binary files /dev/null and b/Assets/Photon/Fusion/Assemblies/Debug/Fusion.Realtime.dll.debug differ diff --git a/Assets/Photon/Fusion/Assemblies/Debug/Fusion.Realtime.dll.debug.meta b/Assets/Photon/Fusion/Assemblies/Debug/Fusion.Realtime.dll.debug.meta new file mode 100644 index 00000000..698d427a --- /dev/null +++ b/Assets/Photon/Fusion/Assemblies/Debug/Fusion.Realtime.dll.debug.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 8fbdaf13e9028e2428aef5554b1e2f8d +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Assemblies/Debug/Fusion.Runtime.dll.debug b/Assets/Photon/Fusion/Assemblies/Debug/Fusion.Runtime.dll.debug new file mode 100644 index 00000000..1328e544 Binary files /dev/null and b/Assets/Photon/Fusion/Assemblies/Debug/Fusion.Runtime.dll.debug differ diff --git a/Assets/Photon/Fusion/Assemblies/Debug/Fusion.Runtime.dll.debug.meta b/Assets/Photon/Fusion/Assemblies/Debug/Fusion.Runtime.dll.debug.meta new file mode 100644 index 00000000..c67cfb2f --- /dev/null +++ b/Assets/Photon/Fusion/Assemblies/Debug/Fusion.Runtime.dll.debug.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 1de2cc5af22faff4cae32c122f06a2ef +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Assemblies/Debug/Fusion.Sockets.dll.debug b/Assets/Photon/Fusion/Assemblies/Debug/Fusion.Sockets.dll.debug new file mode 100644 index 00000000..622c04e6 Binary files /dev/null and b/Assets/Photon/Fusion/Assemblies/Debug/Fusion.Sockets.dll.debug differ diff --git a/Assets/Photon/Fusion/Assemblies/Debug/Fusion.Sockets.dll.debug.meta b/Assets/Photon/Fusion/Assemblies/Debug/Fusion.Sockets.dll.debug.meta new file mode 100644 index 00000000..450b6db0 --- /dev/null +++ b/Assets/Photon/Fusion/Assemblies/Debug/Fusion.Sockets.dll.debug.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 747050e10a802ed489a60d778ee07713 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Assemblies/Fusion.Common.dll b/Assets/Photon/Fusion/Assemblies/Fusion.Common.dll new file mode 100644 index 00000000..dc0c80f8 Binary files /dev/null and b/Assets/Photon/Fusion/Assemblies/Fusion.Common.dll differ diff --git a/Assets/Photon/Fusion/Assemblies/Fusion.Common.dll.meta b/Assets/Photon/Fusion/Assemblies/Fusion.Common.dll.meta new file mode 100644 index 00000000..e02e4f2e --- /dev/null +++ b/Assets/Photon/Fusion/Assemblies/Fusion.Common.dll.meta @@ -0,0 +1,33 @@ +fileFormatVersion: 2 +guid: a4d1b22b416816f4a830209a6058fe35 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Any: + second: + enabled: 1 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + Windows Store Apps: WindowsStoreApps + second: + enabled: 0 + settings: + CPU: AnyCPU + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Assemblies/Fusion.Common.xml b/Assets/Photon/Fusion/Assemblies/Fusion.Common.xml new file mode 100644 index 00000000..b4dabe89 --- /dev/null +++ b/Assets/Photon/Fusion/Assemblies/Fusion.Common.xml @@ -0,0 +1,3127 @@ + + + + Fusion.Common + + + + + Abstract Async Operation Handler + + Built around the + + Result Type of the Async Operation + + + + Default Operation Timeout + + + + + Internal Task + + + + + Create a new Operation Handler + + Optional External Cancellation Token + Optional Custom Operation Timeout + Optional Custom Timeout Message + + + + Set the result of Async Operation + + Result to be set on the Operation + + + + Set the Async Operation as faulted using the Exception + + Exception to be set on the Operation + + + + Cancel the Async Operation via a + + + + + Cancel the Async Operation via a + + + + + Task Factory is used to create new Tasks and Schedule long running Tasks + + + + + Stores a Task Factory ready made to be used with Unity + + + + + Setup a new TaskFactory tailored to work with Unity + + + + + Starts a service task that will invoke a recurring action at specified intervals. + + The action to be invoked at each interval. + The cancellation token used to stop the service. + The interval in milliseconds between each invocation of the action. + An optional custom name for the service. + A task representing the service. + + + + Start a Service Task that will invoke a Recurring Action every each interval in millis + + Action invoked every interval. It can return false to stop the service + CancellationToken used to stop the service + Interval between action invoke + Custom id name for the Service + Service Task + + + + Run an Action asynchronously + + Action to be invoked + CancellationToken used to stop the Action + Extra Task Creation options + Async Task based on the Action + + + + Run a continuation Task after all other Tasks have completed + + List of pending tasks to wait + Action to run after the Tasks + ellationToken used to stop the Action + Async Task based on the Action + + + + Custom Task Delay method as Task.Delay is not supported by WebGL Builds + + Delay in milliseconds to wait + Cancellation Token used to stop the Delay + Awaitable Task + + + + Represents an atomic integer that provides thread-safe operations. + + + + + The underlying value of the atomic integer. + + + + + Initializes a new instance of the struct with the specified value. + + The initial value of the atomic integer. + + + + Gets the current value of the atomic integer. + + + + + Atomically increments the current value by one and returns the original value. + + The original value before the increment. + + + + Atomically increments the current value by one and returns the incremented value. + + The incremented value. + + + + Atomically decrements the current value by one and returns the decremented value. + + The decremented value. + + + + Atomically sets the value to the specified value and returns the original value. + + The value to set. + The original value before the exchange. + + + + Compares the current value with a specified value and, if they are equal, replaces the current value. + + The value to set if the comparison succeeds. + The value to compare to the current value. + The original value before the comparison. + + + + Utility class for binary data. + + + + + Converts a byte value to its hexadecimal string representation. + + The byte value to convert. + A string representing the hexadecimal value of the byte. + + + + Converts a buffer of bytes to a hexadecimal string representation. + + A pointer to the buffer containing the bytes to convert. + The number of bytes in the buffer. + The number of columns to format the output. Default is 16. + The string to use as a row separator. Default is newline. + The string to use as a column separator. Default is a space. + A string representing the hexadecimal values of the bytes in the buffer. + + + + Converts a buffer of 32-bit integers to a hexadecimal string representation. + + A pointer to the buffer containing the 32-bit integers to convert. + The number of 32-bit integers in the buffer. + The number of columns to format the output. Default is 4. + The string to use as a row separator. Default is newline. + The string to use as a column separator. Default is a space. + A string representing the hexadecimal values of the 32-bit integers in the buffer. + + + + Converts a buffer of 32-bit unsigned integers to a hexadecimal string representation. + + A pointer to the buffer containing the 32-bit unsigned integers to convert. + The number of 32-bit unsigned integers in the buffer. + The number of columns to format the output. Default is 4. + The string to use as a row separator. Default is newline. + The string to use as a column separator. Default is a space. + A string representing the hexadecimal values of the 32-bit unsigned integers in the buffer. + + + + Converts a buffer of 32-bit integers to a hexadecimal string representation. + + + + + Converts a buffer of 32-bit unsigned integers to a hexadecimal string representation. + + + + + Converts a hexadecimal string to a byte array. + + The hexadecimal string to convert. + A pointer to the buffer where the bytes will be stored. + The maximum number of bytes to store in the buffer. + The number of characters processed from the input string. + + + + Converts a hexadecimal string to an array of 32-bit integers. + + The hexadecimal string to convert. + A pointer to the buffer where the 32-bit integers will be stored. + The maximum number of 32-bit integers to store in the buffer. + A tuple containing the number of characters processed from the input string and the number of 32-bit integers stored in the buffer. + + + + Converts a byte array to its hexadecimal string representation. + + The byte array to convert. + The number of columns to format the output. Default is 16. + A string representing the hexadecimal values of the bytes in the array. + + + + Converts a byte array to its hexadecimal string representation. + + The byte array to convert. + The number of columns to format the output. Default is 16. + A string representing the hexadecimal values of the bytes in the array. + + + + Reads a value of type T from the given byte span. + + The type of the value to read. + The span of bytes to read from. + The value of type T read from the span. + + + + Reads a value of type T from the given int span. + + The type of the value to read. + The span of ints to read from. + The value of type T read from the span. + + + + Returns a managed pointer to a value of type T. + + + + + Returns a managed pointer to a value of type T. + + + + + Returns a pointer to a value of type T from the given byte span. + + The type of the value to point to. + The span of bytes to get the pointer from. + A pointer to the value of type T. + + + + Returns a pointer to a value of type T from the given int span. + + The type of the value to point to. + The span of ints to get the pointer from. + A pointer to the value of type T. + + + + Compress the byte array uisng GZip + + Original byte array + Compressed byte array + + + + Decompress the byte array using GZip + + Compressed byte array + Decompressed byte array + + + + Provides methods to compute CRC64 checksums. + + + + + Computes the CRC64 checksum for the given data. + + A pointer to the data to compute the checksum for. + The length of the data. + The computed CRC64 checksum. + + + + Computes the CRC64 checksum for the given data. + + A pointer to the data to compute the checksum for. + The computed CRC64 checksum. + + + + Computes the CRC64 checksum for the given data with an initial CRC value and offset. + + The initial CRC value. + A pointer to the data to compute the checksum for. + The offset in the data to start computing the checksum from. + The length of the data. + The computed CRC64 checksum. + + + + Provides a set of methods to profile the engine. + + + + + Callback for round trip time profiling. + + + + + Callback for resimulations profiling. + + + + + Callback for world snapshot size profiling. + + + + + Callback for input size profiling. + + + + + Callback for input queue profiling. + + + + + Callback for RPC in profiling. + + + + + Callback for RPC out profiling. + + + + + Callback for state receive delta profiling. + + + + + Callback for state receive delta deviation profiling. + + + + + Callback for interpolation speed profiling. + + + + + Callback for interpolation offset profiling. + + + + + Callback for interpolation offset deviation profiling. + + + + + Callback for input receive delta profiling. + + + + + Callback for simulation speed profiling. + + + + + Callback for input receive delta deviation profiling. + + + + + Callback for simulation offset profiling. + + + + + Callback for simulation offset deviation profiling. + + + + + Callback for simulation offset deviation profiling. + + + + + Callback for simulation offset deviation profiling. + + + + + Begins a profiling sample with the specified name. + + The name of the profiling sample. + + + + Ends the current profiling sample. + + + + + Invokes the round trip time callback with the specified value. + + The round trip time value. + + + + Invokes the resimulations callback with the specified value. + + The resimulations value. + + + + Invokes the world snapshot size callback with the specified value. + + The world snapshot size value. + + + + Invokes the input size callback with the specified value. + + The input size value. + + + + Invokes the input queue callback with the specified value. + + The input queue value. + + + + Invokes the RPC in callback with the specified value. + + The RPC in value. + + + + Invokes the RPC out callback with the specified value. + + The RPC out value. + + + + Invokes the state receive delta callback with the specified value. + + The state receive delta value. + + + + Invokes the state receive delta deviation callback with the specified value. + + The state receive delta deviation value. + + + + Invokes the interpolation speed callback with the specified value. + + The interpolation speed value. + + + + Invokes the interpolation offset callback with the specified value. + + The interpolation offset value. + + + + Invokes the interpolation offset deviation callback with the specified value. + + The interpolation offset deviation value. + + + + Invokes the input receive delta callback with the specified value. + + The input receive delta value. + + + + Invokes the input receive delta deviation callback with the specified value. + + The input receive delta deviation value. + + + + Invokes the simulation speed callback with the specified value. + + The simulation speed value. + + + + Invokes the simulation offset callback with the specified value. + + The simulation offset value. + + + + Invokes the simulation offset deviation callback with the specified value. + + The simulation offset deviation value. + + + + Invokes the simulation offset deviation callback with the specified value. + + + + + Invokes the simulation offset deviation callback with the specified value. + + + + + Enum representing internal simulation types. To be used with EngineProfiler callbacks. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Interface for a Communicator + + + + + Represents the current ID of the communicator. + + + + + Sends a package data using the communication system + + Event Code used to send the Package + Target Actor of the Package + Flag if this Package should be sent reliably + Data Buffer + Buffer Length + + + + Retrieve a Data Package + + Data Package Sender + Buffer to be filled with the Data + Buffer length + Total number of bytes written to buffer + + + + Check if there are data package to be retrieved + + True if the internal buffer has pendind data + + + + Push a new Package into the communicator queues + + Data Sender Actor + Event Code of the Package + Package + + + + Register a callback for a specific Message Type + + Message Type + Callback to be invoked when a Message of type T is received + + + + Send a Protocol Message using the communicator system + + Target Actor of the Protocol Message + Protocol Message to be sent + + + + Step the Communicator internals + + + + + Represents a Protocol Message + + Used to tag the Messages in . + + + + + Hold Exception Key used to attach extra data to exception within the SDK + + + + + Used to associate a unique ID to an Exception + It must contain a long value + + + + + Editor attribute for selecting the minimum and maximum length constraints for an array field. + + + + + Initializes a new instance of the class with the specified length. + + The length of the array. + + + + Initializes a new instance of the class with the specified minimum and maximum lengths. + + The minimum length of the array. + The maximum length of the array. + + + + Gets the minimum length of the array. + + + + + Gets the maximum length of the array. + + + + + Specifies that the attributed field represents the name of an assembly. + + + + + Gets or sets a value indicating whether the assembly requires unsafe code. + + + + + Specifies that the field represents binary data. + + + + + + + + Represents an attribute that specifies the number of bits in a bit set. + + + + + Initializes a new instance of the class with the specified number of bits. + + The number of bits in the bit set. + + + + Gets the number of bits in the bit set. + + + + + A base class for property attributes that decorate other property attributes. + + + + + The default order of the attribute. + + + + + Initializes a new instance with the default order. + + + + + Initializes a new instance with the specified order. + + + + + Specifies that the string field represents a path to directory. + + + + + Casts an enum or int value in the inspector to specific enum type for rendering of its popup list. + Supplying a method name rather than a type allows a property with the type Type to be used to dynamically get the enum type. + + + + + Initializes a new instance of the class with the specified enum type. + + The type of the enum. + + + + Initializes a new instance of the class with the specified enum type member name. + + The name of the member that returns the enum type. + + + + Gets the type of the enum. + + + + + Gets the name of the member that returns the enum type. + + + + + Specifies the display name for a field. + + + + + Field name to display. + + + + + Initializes a new instance of the class with the specified name. + + The display name. + + + + Comparison method for evaluating condition member value against compareToValues. + + + + + if condition member value equals compareToValue. + + + + + if condition member value is not equal to compareToValue. + + + + + if condition member value is less than compareToValue. + + + + + if condition member value is less than or equal to compareToValue. + + + + + if condition member value is greater than or equal to compareToValue. + + + + + if condition member value is greater than compareToValue. + + + + + Returns if the condition member evaluates to anything other than zero. + In the case of object references, this means for any non- value. + + + + + Returns if the condition member evaluates to zero. + In the case of object references, this means for any value. + + + + + Returns if the bitwise AND of the condition member and compareToValue is not zero. + + + + + Editor attribute for selective editor rendering. Condition member can be a property, field or method (with a + return value). + Value of condition method is converted to a long or a double. = 0, = 0, = 1, Unity Object = InstanceId + + + + + The double value to compare against. + + + + + Is the value to compare against a double? + + + + + The long value to compare against. + + + + + The comparison operator to use. + + + + + Condition member to evaluate. + + + + + If , an error will be thrown if the condition member is not found. + + + + + Initializes a new instance with a double value to compare against. + + + + + + + + Initializes a new instance with a long value to compare against. + + + + + + + + Initializes a new instance with a boolean value to compare against. + + + + + + + + A base class for property attributes that are used to draw properties in the inspector. + + + + + + + + Mode for the DrawIf attribute. If the condition is not met, should the field be hidden or just read-only? + + + + + Field is read-only if the condition is not met. + + + + + Field is hidden if the condition is not met. + + + + + Editor attribute for selectively drawing/hiding fields. Condition member can be a property, field or method + (with a return value). + Value of condition method is converted to a long. = 0, = 0, = 1, Unity Object = InstanceId + + + + + Instructs the attribute completely hide the field if not draw, rather than the default of just disabling it. + + + + + Should the field be hidden if the condition is not met? + + + + + + + + + + + + + + Initializes a new instance that will hide the field if the condition member is not equal to zero. + + + + + + Specifies that a field should be drawn inline in the inspector. + + + + + Specifies that a method should be displayed as a button in the Unity editor. + + + + + The label text to display on the button. + + + + + The visibility of the button in the Unity editor. + + + + + The priority of the button. Buttons with higher priority are displayed first. + + + + + Determines whether multiple targets are supported. + + + + + Determines whether the object should be marked as dirty after clicking the button. + + + + + Initializes a new instance of the class with the specified label, visibility, priority, and dirty object flag. + + The label text to display on the button. + The visibility of the button in the Unity editor. + The priority of the button. Buttons with higher priority are displayed first. + Determines whether the object should be marked as dirty after clicking the button. + + + + Initializes a new instance of the class with the specified visibility, priority, and dirty object flag. + + The visibility of the button in the Unity editor. + The priority of the button. Buttons with higher priority are displayed first. + Determines whether the object should be marked as dirty after clicking the button. + + + + Specifies the visibility options for an editor button. + + + + + The button is only visible in Play Mode. + + + + + The button is only visible in Edit Mode. + + + + + The button is always visible. + + + + + Editor attribute for adding notices to fields if the condition member evaluates as . + Condition member can be a property, field or method (with a return value). + + + + + The default error text, when an error is shown. + + + + + Should the error be shown as a box? + + + + + + + + + + + + + + Editor attribute that shows an enum as an expandable list of options in the inspector. + + + + + Always expand the enum in the inspector (no foldout) + + + + + Show the enum flags as buttons in the inspector. + + + + + Show inline help for enum values in the inspector. + + + + + Editor attribute to add a button that invokes a custom method in the inspector. + + + + + Button label. + + + + + Is it allowed to select multiple targets for the button? + + + + + The method to invoke when the button is clicked. + + + + + Initializes a new instance class with the specified label and target method. + + The label of the button + The method to invoke when the button is clicked + + + + Attribute used to hide the label of an array element in the inspector. + + + + + Initializes a new instance of the class. + + + + + If applied to a field, checks if there is a help text for the field or the field's type and shows it in the inspector. + + + + + Gets or sets a value indicating whether to show help for the type. + + + + + Initializes a new instance of the class. + + + + + Marks the last version that supports the attributed type/method/property/field. + + + + + Initializes a new instance of the class with the specified version. + + The last supported version. + + + + Specifies that an int field should be drawn as a layer field in the inspector. + + + + + Specifies that the integer array field should be drawn as a layer matrix in the inspector. + + + + + + + + Specifies that the string field should be drawn as a text field with a maximum byte count for given encoding. + + + + + Initializes a new instance of the class with the specified byte count and encoding. + + + + + + + Maximum byte count for the string. + + + + + The encoding of the string. + + + + + Represents an attribute that specifies a range of values for a field or property. + + + + + Gets the maximum value of the range. + + + + + Gets the minimum value of the range. + + + + + Gets or sets a value indicating whether the minimum value should be clamped. + + + + + Gets or sets a value indicating whether the maximum value should be clamped. + + + + + Gets or sets a value indicating whether a slider should be used for the range. + + + + + Initializes a new instance of the class with the specified minimum and maximum values. + + The minimum value of the range. + The maximum value of the range. + + + + Possible values. + + + + + Attribute used to mark a field as read-only. + + + + + Should the field be read-only in play mode? + + + + + Should the field be read-only in edit mode? + + + + + + + + Specifies that a string field represents a scene path. + + + + + Defines the appearance of the script header in the Unity inspector. + + + + + Hide the script header in the Unity inspector. + + + + + + + + + Color of the inspector header for this component type. None indicates no header graphic should be used. + + + + + + + + + + Style of the script header in the Unity inspector. + + + + + Use the default Unity header style. + + + + + Use the Photon header style. + + + + + Icon to be rendered on the component graphic header in the Unity inspector. + + + + + Color of the component graphic header in the Unity inspector. None indicates no header graphic should be used. + + + + + Specifies that either a string field represents a type name or sets additional options for field. + + + + + The base type of the picked type. + + + + + Should the type be stored as a full assembly qualified name. + + + + + Should a warning be shown if the field does not have a PreserveAttribute. + + + + + Attribute used to show a type picker for a field with [SerializeReference]. + + + + + Should the types be grouped by namespace? + + + + + Should the full name be shown? + + + + + Initializes a new instance of the class. + + The types to be picked. + + + + Gets the types to be picked. + + + + + Similar to UnityEngine.SpaceAttribute, but adds space after the property. + + + + + The default order for this attribute. + + + + + + + + Height of the space. + + + + + Specifies that the bool field should be drawn as the toggle on the left side of the label. + + + + + Unit Type for a certain field. + This helps to identify the unit that a certain value represents, like Seconds or Percentage + + + + + + + ticks + + + seconds - secs + + + millisecs - ms + + + kilobytes - kB + + + megabytes - MB + + + normalized - norm + + + multiplier - mult + + + % + + + normalized % - n% + + + degrees - \u00B0 + + + per sec - /sec + + + \u00B0 / sec - \u00B0/sec + + + radians - rad + + + radian / sec - rad/s + + + ticks / sec - tck/s + + + units - units + + + bytes - bytes + + + count - count + + + packets - packets + + + frames - frames + + + fps - fps + + + sqrMagnitude - sqrMag + + + + Unit Attribute class. + Used to mark a field with the respective + + + + + Selected Unit for the field. + + + + + Initializes a new instance of the class with the specified unit. + + + + + + Specifies that the string field represents a key for Unity Addressables. + + + + + Specifies that the string field represents a GUID of an asset. + + + + + A replacement for UnityEngine.PropertyAttribute. + + + + + Specifies that the string field represents a path to a Unity resource. + + + + + Initializes a new instance of the class with the specified resource type. + + + + + + The type of the resource. + + + + + Editor attribute for adding notices to fields if the condition member evaluates as . + Condition member can be a property, field or method (with a return value). + + + + + The default warning text, when a warning is shown. + + + + + Should the warning be shown as a box? + + + + + + + + + + + + + + Initializes a new instance that will hide the field if the condition member is not equal to zero. + + + + + + + Base class for . + + + + + The internal mask value. + + + + + Constructor for . + + + + + Constructor for . + + + + + Constructor for . + + + + + Implicitly convert to its long mask value. + + + + + Associates and displays a 64 bit mask which represents the field members of a struct. Makes it possible to treat a Struct like an Flags Enum. + NOTE: A attribute is required for proper rendering in the Inspector. + + + + + Constructor for . + + + + + Constructor for . + + + + + Constructor for . + + + + + Constructor for . + + + + + Mask256 is a 256-bit mask that can be used to store 256 boolean values. + + + + + Returns selected 64-bit value from the mask. + + + + + + Sets all bits to 0. + + + + + Sets a specific bit in the mask. + + + + + Returns a specific bit from the mask. + + + + + Creates a new mask with specified initial values. + + + + + Equivalent to mask[0] + + + + + Converts a long value to a mask. + + + + + Performs a logical-AND operation. + + + + + Performs a logical-OR operation. + + + + + Performs a logical-NOT operation. + + + + + Returns if the masks are equal. + + + + + Calculates the hash code of the mask. + + + + + Returns if the masks are equal. + + + + + Returns if the mask is empty. + + + + + + Converts the mask to a string in the form of "a:b:c:d". + + + + + A System.Type wrapper that can be serialized. + + + + + Type's assembly qualified name. + + + + + Is the type valid. + + + + + Create a new instance and stores full . To use shorter form, use . + + Type to store. Can be . + + + + Create a new instance and stores as . + + Type name. + + + + Retrieve the type. The value is obtained using and cached in a static + + + + + Converts and returns a short form, without version, culture etc. + + + + + Implicitly convert a to a . + + + + + Implicitly convert a to a . + + + + + Returns if the is the same. + + + + + Returns if is and the is the same. + + + + + Returns the hash code of the . + + + + + Converts the to a shorter form, without version, culture etc. + + + + + A generic version of that can be used to store types that inherit from a specific base type. + + The base type of the type stored + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A base class for ScriptableObjects that are meant to be globally accessible, at edit-time and runtime. The way such objects + are loaded is driven by usages of attributes. + + + + + + + + Is this instance a global instance. + + + + + Invoked when the instance is loaded as global. + + + + + Invoked when the instance is unloaded as global. + + + + + + If the current instance is global, unsets and calls + + + + + A singleton instance-like property. Loads or returns the current global instance. Derived classes can package it in a property + with a different name. Throws if loading an instance failed. + + + + + + Returns true if a global instance is loaded. Compared to , it does not attempt to load an instance. + + + + + Loads or returns the current global instance. Returns if loading an instance failed. + + + + + + + Unloads the global instance if it is loaded. + + if an instance was unloaded + + + + Provides additional information for a global scriptable object. + + + + + Creates a new instance. + + The default path for the asset. + + + + The default path for the asset. + + + + + The default contents for the asset, if it is a TextAsset. + + + + + Name of the method that is used to generate the default contents for the asset. + + + + + Base class for all attributes that can be used to load . + Attributes need to be registered at the assembly level. For instance, this snippet is used to register a default loader, + that attempts to load from Resources based on : + + [assembly: Fusion.FusionGlobalScriptableObjectResource(typeof(Fusion.FusionGlobalScriptableObject), Order = 2000, AllowFallback = true)] + + + + + Type or the base type of that this loader supports. + + + + Type or the base type of that this loader supports. + + + + + Order in which this loader will be executed. Lower values are executed first. + + + + + Can this loader be used in edit mode. + + + + + Does this loader allow fallback to the next loader? + + + + + Attempt to load the object of the specified type. Return if the object cannot be loaded. + + The requested type + + + + A delegate that can be used to unload a . + + + + + The result of . Contains the loaded object and an optional + unloader delegate. + + + + + Object instance. + + + + + An optional delegate that is used to unload . + + + + Object instance. + An optional delegate that is used to unload . + + + + Implicitly converts a to a . + + + + + Base class for all Fusion MonoBehaviours. + + + + + Base class for all Fusion scriptable objects. + + + + + Collection of simple JSON Utility methods + + + + + Removes from a JSON serialized by Unity Serializer the "referenes" field. + This aims to reduce the JSON size when sending accross the network + + JSON output of "JsonUtility.ToJson" call + Same JSON but without the "referenes" object + + + + Math utility methods. + + + + Uses fieldoffset to allow quick setting of the sign bit of floats. + + + + Primary Compression Method. Converts a quaternion into a ulong buffer. Depending on size most of the top bits will be 0. + + The quaternion to be compressed + A ulong buffer of the compressed quat. + + + + Primary Decompression Method. Decompress the 3 channels and missing channel ID from the serialized ULong buffer. + + The ulong that represents the compressed quaternion. + The restored Quaternion. + + + + Represents a structure that allows quick setting of the sign bit of floats using field offsets. + + + + + The mask used to clear the sign bit of a float. + + + + + The unsigned integer representation of the float. + + + + + The single-precision floating-point number. + + + + + Returns the size of the specified unmanaged type in bits. + + The unmanaged type. + The size of the type in bits. + + + + Calculates the number of bytes required to store the specified number of bits. + + The number of bits. + The number of bytes required. + + + + Calculates the number of integers required to store the specified number of bits. + + The number of bits. + The number of integers required. + + + + Calculates the number of bytes required to store the specified number of bits. + + The number of bits. + The number of bytes required. + + + + Converts a byte array to a string representation of its bits. + + Pointer to the byte array. + The number of bytes to convert. + A string representation of the bits. + + + + Determines the number of bits required to represent the specified integer. + + The integer to evaluate. + The number of bits required to represent the integer. + + + + Rounds a double value down to the nearest integer. + + The double value to round down. + The largest integer less than or equal to the specified value. + + + + Rounds a double value up to the nearest integer. + + The double value to round up. + The smallest integer greater than or equal to the specified value. + + + + Counts the number of bits set to 1 in a uint value, minus one. + + The uint value to count bits in. + The number of bits set to 1, minus one. + + + + Determines the number of bits required to represent a uint value. + + The uint value to evaluate. + The number of bits required to represent the value. + + + + Calculates the next power of two greater than or equal to a given uint value. + + The uint value to evaluate. + The next power of two greater than or equal to the value. + + + + Counts the number of bits set to 1 in a ulong value. + + The ulong value to count bits in. + The number of bits set to 1. + + + + Converts milliseconds to seconds. + + The time in milliseconds. + The time in seconds. + + + + Converts seconds to milliseconds. + + The time in seconds. + The time in milliseconds. + + + + Converts seconds to microseconds. + + The time in seconds. + The time in microseconds. + + + + Converts microseconds to seconds. + + The time in microseconds. + The time in seconds. + + + + Converts milliseconds to microseconds. + + The time in milliseconds. + The time in microseconds. + + + + Performs cosine interpolation between two values. + + The start value. + The end value. + The interpolation factor, typically between 0 and 1. + The interpolated value. + + + + Clamps an integer value to the range [0, 255] and returns it as a byte. + + The value to clamp. + The clamped value as a byte. + + + + Encodes an integer using ZigZag encoding. + + The integer to encode. + The ZigZag encoded integer. + + + + Decodes a ZigZag encoded integer. + + The ZigZag encoded integer to decode. + The decoded integer. + + + + Encodes a long integer using ZigZag encoding. + + The long integer to encode. + The ZigZag encoded long integer. + + + + Decodes a ZigZag encoded long integer. + + The ZigZag encoded long integer to decode. + The decoded long integer. + + + + Clamps an integer value to the specified range. + + The value to clamp. + The minimum value of the range. + The maximum value of the range. + The clamped value. + + + + Clamps an unsigned integer value to the specified range. + + The value to clamp. + The minimum value of the range. + The maximum value of the range. + The clamped value. + + + + Clamps a double value to the specified range. + + The value to clamp. + The minimum value of the range. + The maximum value of the range. + The clamped value. + + + + Clamps a float value to the specified range. + + The value to clamp. + The minimum value of the range. + The maximum value of the range. + The clamped value. + + + + Clamps a double value to the range [0, 1]. + + The value to clamp. + The clamped value. + + + + Clamps a float value to the range [0, 1]. + + The value to clamp. + The clamped value. + + + + Linearly interpolates between two float values. + + The start value. + The end value. + The interpolation factor, typically between 0 and 1. + The interpolated value. + + + + Linearly interpolates between two double values. + + The start value. + The end value. + The interpolation factor, typically between 0 and 1. + The interpolated value. + + + + Returns the minimum of two unsigned integer values. + + The first value. + The second value. + The minimum value. + + + + Finds the index of the most significant set bit (1-bit) in a 32-bit integer. + + The 32-bit integer to scan. + The index of the most significant set bit. + + + + Finds the index of the most significant set bit (1-bit) in a 32-bit unsigned integer. + + The 32-bit unsigned integer to scan. + The index of the most significant set bit. + + + + Finds the index of the most significant set bit (1-bit) in a 64-bit integer. + + The 64-bit integer to scan. + The index of the most significant set bit. + + + + Finds the index of the most significant set bit (1-bit) in a 64-bit unsigned integer. + + The 64-bit unsigned integer to scan. + The index of the most significant set bit. + + + + Native Memory Allocator + + + + + The alignment size in bytes. + + + + + The size of the cache line in bytes. + + + + + Moves a block of memory from the source to the destination. + + The destination pointer. + The source pointer. + The number of bytes to move. + + + + Copies a block of memory from the source to the destination. + + The destination pointer. + The source pointer. + The number of bytes to copy. + + + + Clears a block of memory by setting it to zero. + + The pointer to the memory block. + The number of bytes to clear. + + + + Clears a block of memory by setting it to zero. + + + + + Compares two blocks of memory. + + The first memory block pointer. + The second memory block pointer. + The number of bytes to compare. + An integer that indicates the relative order of the memory blocks being compared. + + + + Allocates a block of memory. + + The number of bytes to allocate. + A pointer to the allocated memory block. + Thrown when the size is less than or equal to zero or exceeds the maximum allowed size. + + + + Frees a block of memory. + + The pointer to the memory block to free. + + + + Gets the size of the specified type. + + The type to get the size of. + The size of the specified type in bytes. + + + + Gets the offset of the specified field. + + The field information. + The offset of the specified field in bytes. + + + + Frees a block of memory. + + The pointer to the memory block to free. + + + + Frees a block of memory. + + The pointer to the memory block to free. + + + + Frees a block of memory. + + The pointer to the memory block to free. + + + + Allocates and clears a block of memory. + + The number of bytes to allocate and clear. + A pointer to the allocated and cleared memory block. + + + + Allocates and clears a block of memory for a specified type. + + The type of the memory block to allocate and clear. + A pointer to the allocated and cleared memory block. + + + + Allocates a block of memory for a specified type. + + The type of the memory block to allocate. + A pointer to the allocated memory block. + + + + Allocates and clears an array of memory blocks. + + The size of each element in the array. + The number of elements in the array. + A pointer to the allocated and cleared array of memory blocks. + + + + Allocates and clears an array of memory blocks for a specified type. + + The type of the elements in the array. + The number of elements in the array. + A pointer to the allocated and cleared array of memory blocks. + + + + Allocates and clears an array of memory blocks for a specified type, ensuring a minimum length of 1. + + The type of the elements in the array. + The number of elements in the array. + A pointer to the allocated and cleared array of memory blocks. + + + + Allocates and clears an array of pointers for a specified type. + + The type of the elements in the array. + The number of elements in the array. + A pointer to the allocated and cleared array of pointers. + + + + Allocates and clears an array of pointers for a specified type, ensuring a minimum length of 1. + + The type of the elements in the array. + The number of elements in the array. + A pointer to the allocated and cleared array of pointers. + + + + Copies a range of elements from one array to another. + + The source array pointer. + The zero-based index in the source array at which copying begins. + The destination array pointer. + The zero-based index in the destination array at which storing begins. + The number of elements to copy. + The size of each element in bytes. + + + + Clears a range of elements in an array by setting them to zero. + + The type of the elements in the array. + The pointer to the array. + The number of elements to clear. + + + + Compares two arrays for equality. + + The type of the elements in the arrays. + The pointer to the first array. + The pointer to the second array. + The number of elements to compare. + An integer that indicates the relative order of the arrays being compared. + + + + Doubles the size of an array by expanding it to twice its current length. + + The type of the elements in the array. + The pointer to the array to double. + The current length of the array. + A pointer to the new array with doubled size. + + + + Expands an array to a new specified length. + + The type of the elements in the array. + The pointer to the array to expand. + The current length of the array. + The new length of the array. + A pointer to the new expanded array. + + + + Doubles the size of an array of pointers by expanding it to twice its current length. + + The type of the elements in the array. + The pointer to the array of pointers to double. + The current length of the array. + A pointer to the new array of pointers with doubled size. + + + + Expands an array of pointers to a new specified length. + + The type of the elements in the array. + The pointer to the array of pointers to expand. + The current length of the array. + The new length of the array. + A pointer to the new expanded array of pointers. + + + + Expands a buffer to a new specified size. + + The pointer to the buffer to expand. + The current size of the buffer. + The new size of the buffer. + A pointer to the new expanded buffer. + + + + Copies memory quickly for specific sizes. + + The destination pointer. + The source pointer. + The number of bytes to copy. + + + + Copies elements from a managed array to an unmanaged memory location. + + The type of the elements in the array. + The destination pointer. + The source array. + The number of bytes copied. + + + + Copies elements from an unmanaged memory location to a managed array. + + The type of the elements in the array. + The destination array. + The source pointer. + The number of bytes copied. + + + + Gets the byte count of a UTF-8 encoded string with a length prefix. + + The string to encode. + The byte count of the encoded string with the length prefix. + + + + Writes a length-prefixed UTF-8 encoded string to a memory location. + + The destination pointer. + The string to encode and write. + The number of bytes written. + + + + Reads a length-prefixed UTF-8 encoded string from a memory location. + + The source pointer. + The resulting string. + The number of bytes read. + + + + Checks if a pointer is aligned to a specified alignment. + + The pointer to check. + The alignment to check against. + True if the pointer is aligned, otherwise false. + + + + Aligns a pointer to a specified alignment. + + The pointer to align. + The alignment to apply. + The aligned pointer. + + + + Rounds a stride to the maximum alignment. + + The stride to round. + The rounded stride. + + + + Calculates the word count for a given stride and word size. + + The stride to calculate. + The size of each word. + The word count. + + + + Checks if a stride is aligned to a specified alignment. + + The stride to check. + The alignment to check against. + True if the stride is aligned, otherwise false. + + + + Rounds a stride to a specified alignment. + + The stride to round. + The alignment to apply. + The rounded stride. + Thrown when the alignment is invalid. + + + + Rounds a stride to a specified alignment. + + The stride to round. + The alignment to apply. + The rounded stride. + Thrown when the alignment is invalid. + + + + Returns an empty instance of the specified unmanaged type. + + The type of the instance to return. + An empty instance of the specified type. + + + + Rounds the given number of bits up to the nearest multiple of 64. + + The number of bits to round up. + The rounded number of bits. + + + + Rounds the given number of bits up to the nearest multiple of 32. + + The number of bits to round up. + The rounded number of bits. + + + + Gets the alignment of the specified unmanaged type. + + The type to get the alignment of. + The alignment of the specified type. + + + + Gets the alignment of the specified stride. + + The stride to get the alignment of. + The alignment of the specified stride. + + + + Gets the maximum alignment of two given strides. + + The first stride. + The second stride. + The maximum alignment of the two strides. + + + + Gets the maximum alignment of three given strides. + + The first stride. + The second stride. + The third stride. + The maximum alignment of the three strides. + + + + Gets the maximum alignment of four given strides. + + The first stride. + The second stride. + The third stride. + The fourth stride. + The maximum alignment of the four strides. + + + + Gets the maximum alignment of five given strides. + + The first stride. + The second stride. + The third stride. + The fourth stride. + The fifth stride. + The maximum alignment of the five strides. + + + + Converts a reference to an unmanaged type to a pointer. + + The type of the object. + The reference to the object. + A pointer to the object. + + + + Validates the block sentinels to ensure memory integrity. + + The pointer to the memory block. + The size of the memory block. + + + + Allocates a block of memory and clears it. + + + + + Allocates a block of memory and clears it. + + + + + Allocates a block of memory and clears it. + + + + + Allocates a block of memory and clears it. + + + + + Allocates a block of memory and clears it. + + + + + Allocates a block of memory and clears it. + + + + + Allocates a block of memory and clears it. + + + + + Allocates a block of memory and clears it. + + + + + Allocates a block of memory and clears it. + + + + + Allocates a block of memory and clears it. + + + + + Allocates a block of memory and clears it. + + + + + Attribute to mark a static field with a reset mode. + + + + + Initializes a new instance of the class with the specified reset mode. + + The reset mode for the static field. + + + + Initializes a new instance of the class with the default reset mode. + + + + + Gets the reset mode for the static field. + + + + + Attribute to mark a method as a static field reset method. + + + + + Initializes a new instance of the class with the specified automatic call flag. + + Indicates whether the method is called automatically. + + + + Initializes a new instance of the class. + + + + + Attribute to mark a constructor as a static constructor. + + + + + Specifies the reset mode for a static field. + + + + + No reset mode. + + + + + Manual reset mode. + + + + + Reset method mode. + + + + + Provides a set of methods to work with prime numbers. + + + + + Determines whether the specified value is a prime number. + + The value to check for primality. + true if the specified value is a prime number; otherwise, false. + + + + Gets the next prime number greater than the specified value. + + The value to find the next prime number for. + The next prime number greater than the specified value. + Thrown when there is no larger prime number in the table. + + + + Gets the next prime number greater than the specified unsigned value. + + The unsigned value to find the next prime number for. + The next prime number greater than the specified unsigned value. + Thrown when there is no larger prime number in the table. + + + + Represents a high-resolution timer. + + + + + Creates and starts a new timer. + + A new instance of the struct. + + + + Gets the elapsed time in ticks. + + + + + Gets the elapsed time in milliseconds. + + + + + Gets the elapsed time in seconds. + + + + + Gets a value indicating whether the timer is running. + + + + + Starts the timer if it is not already running. + + + + + Stops the timer if it is running and updates the elapsed time. + + + + + Resets the timer to its initial state. + + + + + Restarts the timer, setting the elapsed time to zero and starting it. + + + + + Contains methods to setup runtime flags for Unity. + + + + + Flags for BuildFlags + + + + + Checks if the UNITY_WEBGL flag is set and updates the flagsDotNetVersion accordingly. + + + + + Checks if the UNITY_XBOXONE flag is set and updates the flagsDotNetVersion accordingly. + + + + + Checks if the UNITY_GAMECORE flag is set and updates the flagsDotNetVersion accordingly. + + + + + Checks if the UNITY_EDITOR flag is set and updates the flagsDotNetVersion accordingly. + + + + + Checks if the UNITY_SWITCH flag is set and updates the flagsDotNetVersion accordingly. + + + + + Checks if the UNITY_2019_4_OR_NEWER flag is set and updates the flagsDotNetVersion accordingly. + + + + + Flags for BuildTypes + + + + + Checks if the ENABLE_MONO flag is set and updates the flagsDotNetVersion accordingly. + + + + + Checks if the ENABLE_IL2CPP flag is set and updates the flagsDotNetVersion accordingly. + + + + + Flags for DotNetVersion + + + + + Checks if the NET_4_6 flag is set and updates the flagsDotNetVersion accordingly. + + + + + Checks if the NETFX_CORE flag is set and updates the flagsDotNetVersion accordingly. + + + + + Checks if the NET_STANDARD_2_0 flag is set and updates the flagsDotNetVersion accordingly. + + + + + Resets all runtime flags to their default values. + + + + + Provides substitution for missing System.Runtime.CompilerServices.Unsafe + + + + + Casts the given object to the specified type. + + + + + Returns an unmanaged pointer corresponding to the original source pointer. + + + + + Returns a managed pointer to a value of type T. + + + + + Returns a managed pointer to a value of type T. + + + + + Writes a value of type T to the given location. + + + + + Writes a value of type T to the given location without assuming architecture dependent alignment of the destination address. + + + + + Writes a value of type T to the given location without assuming architecture dependent alignment of the destination address. + + + + + Writes a value of type T to the given location. + + + + + Writes a value of type T to the given location without assuming architecture dependent alignment of the destination address. + + + + + Writes a value of type T to the given location without assuming architecture dependent alignment of the destination address. + + + + + UTF32Tools provides a set of methods to work with UTF32 encoded strings. + + + + + Converts a UTF-16 encoded string to a UTF-32 encoded representation. + + The UTF-16 encoded string to convert. + A pointer to the destination buffer where the UTF-32 encoded result will be stored. + The capacity of the destination buffer. + A ConversionResult containing the number of characters and code points processed. + + + + Converts a UTF-16 encoded string to a UTF-32 encoded representation. + + A pointer to the UTF-16 encoded string to convert. + The length of the UTF-16 encoded string. + A pointer to the destination buffer where the UTF-32 encoded result will be stored. + The capacity of the destination buffer. + A ConversionResult containing the number of characters and code points processed. + + + + Gets the length of a UTF-32 encoded string. + + The UTF-16 encoded string to measure. + The length of the UTF-32 encoded string. + + + + Enumerates the characters in a UTF-32 encoded string. + + + + + Initializes a new instance of the struct. + + A pointer to the UTF-32 encoded string. + The length of the UTF-32 encoded string. + + + + Gets the current character in the enumeration. + + + + + Releases all resources used by the . + + + + + Advances the enumerator to the next character in the UTF-32 encoded string. + + true if the enumerator was successfully advanced to the next character; false if the enumerator has passed the end of the string. + + + + Sets the enumerator to its initial position, which is before the first character in the UTF-32 encoded string. + + + + + Represents the result of a conversion operation, containing the number of characters and code points processed. + + + + + Gets the number of characters processed. + + + + + Gets the number of code points processed. + + + + + Initializes a new instance of the struct. + + The number of code points processed. + The number of characters processed. + + + + The Versioning class provides methods and properties related to versioning. + + + + + Represents an invalid version with all components set to zero. + + + + + Gets the current version of the assembly. + + + + + Get the short version of the version. + + The version to get the short version of. + + + + Gets the assembly version as a string. + + The assembly version as a string. + + + diff --git a/Assets/Photon/Fusion/Assemblies/Fusion.Common.xml.meta b/Assets/Photon/Fusion/Assemblies/Fusion.Common.xml.meta new file mode 100644 index 00000000..8a66ae20 --- /dev/null +++ b/Assets/Photon/Fusion/Assemblies/Fusion.Common.xml.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 48752d1ef11e570418fbe363abf3a8f6 +labels: +- FusionCodeDoc +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Assemblies/Fusion.Log.dll b/Assets/Photon/Fusion/Assemblies/Fusion.Log.dll new file mode 100644 index 00000000..c948ff01 Binary files /dev/null and b/Assets/Photon/Fusion/Assemblies/Fusion.Log.dll differ diff --git a/Assets/Photon/Fusion/Assemblies/Fusion.Log.dll.meta b/Assets/Photon/Fusion/Assemblies/Fusion.Log.dll.meta new file mode 100644 index 00000000..db53e993 --- /dev/null +++ b/Assets/Photon/Fusion/Assemblies/Fusion.Log.dll.meta @@ -0,0 +1,33 @@ +fileFormatVersion: 2 +guid: 641f841cbef022e4f90b206df4a1f40c +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Any: + second: + enabled: 1 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + Windows Store Apps: WindowsStoreApps + second: + enabled: 0 + settings: + CPU: AnyCPU + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Assemblies/Fusion.Log.xml b/Assets/Photon/Fusion/Assemblies/Fusion.Log.xml new file mode 100644 index 00000000..b61d74a2 --- /dev/null +++ b/Assets/Photon/Fusion/Assemblies/Fusion.Log.xml @@ -0,0 +1,1553 @@ + + + + Fusion.Log + + + + + A proxy log streams that allows for filtering messages. Note that filtering is applied after the message is formatted, + so it is really mostly suitable for Unit tests. + + + + If a message contains this, it will not be logged + The actual log stream that will consume filtered messages + + + + + + + + + + + + + + + + + + + A logger that writes logs to the Unity console with the purpose of being used in the Unity Editor. + + + + + Logs a message to the Unity console if FUSION_EDITOR_TRACE is defined. Use in the Config context. + + A message + + + + Logs a warning to the Unity console if UNITY_EDITOR is defined. Use in the Config context. + + A message + + + + Logs a message to the Unity console if UNITY_EDITOR is defined. Use in the Config context. + + A message + + + + Logs an errpr to the Unity console if UNITY_EDITOR is defined. Use in the Config context. + + A message + + + + Logs a message to the Unity console if FUSION_EDITOR_TRACE is defined. Use in the Installer context. + + A message + + + + Logs a warning to the Unity console if UNITY_EDITOR is defined. Use in the Installer context. + + A message + + + + Logs a message to the Unity console if UNITY_EDITOR is defined. Use in the Installer context. + + A message + + + + Logs an errpr to the Unity console if UNITY_EDITOR is defined. Use in the Installer context. + + A message + + + + Sets the color of the prefix. + + + + + + Ensures the logger is initialized. Call in the main thread if you want to log from a different thread. + + + + + Logs an assertion with a message if the condition is > and UNITY_ASSERTIONS is defined. + + Value to check + An error message + + + + Logs an assertion if the condition is > and UNITY_ASSERTIONS is defined. + + Value to check + + + + Logs a message to the Unity console if FUSION_EDITOR_TRACE is defined. Used in the import process. + + Asset being imported + A message + + + + Logs a warning to the Unity console if UNITY_EDITOR is defined. Used in the import process. + + Asset being imported + A message + + + + Logs a message to the Unity console if UNITY_EDITOR is defined. Used in the import process. + + Asset being imported + A message + + + + Logs an error to the Unity console if UNITY_EDITOR is defined. Used in the import process. + + Asset being imported + A message + + + + Logs a message to the Unity console if FUSION_EDITOR_TRACE is defined. Used in the import process. + + A message + Asset being imported + + + + Logs a warning to the Unity console if UNITY_EDITOR is defined. Used in the import process. + + A message + Asset being imported + + + + Logs a message to the Unity console if UNITY_EDITOR is defined. Used in the import process. + + A message + Asset being imported + + + + Logs an error to the Unity console if UNITY_EDITOR is defined. Used in the import process. + + A message + Asset being imported + + + + Logs a message to the Unity console if UNITY_EDITOR is defined. + + A message + The source of the message + + + + Logs a message to the Unity console if UNITY_EDITOR is defined. + + A message + The source of the message + + + + Logs an error to the Unity console if UNITY_EDITOR is defined. + + A message + The source of the message + + + + Logs an exception to the Unity console if UNITY_EDITOR is defined. + + A message to log before the exception. + The exception to log + + + + Logs an exception to the Unity console if UNITY_EDITOR is defined. + + The exception to log + + + + Logs a message to the Unity console if FUSION_EDITOR_TRACE is defined. + + A message + + + + Logs a warning to the Unity console if UNITY_EDITOR is defined. + + A message + + + + Logs a message to the Unity console if UNITY_EDITOR is defined. + + A message + + + + Logs an errpr to the Unity console if UNITY_EDITOR is defined. + + A message + + + + Logs a message to the Unity console if FUSION_EDITOR_TRACE_IMPORT is defined. Use in the Import context. + + A message + + + + Logs a warning to the Unity console if UNITY_EDITOR is defined. Use in the Import context. + + A message + + + + Logs a message to the Unity console if UNITY_EDITOR is defined. Use in the Import context. + + A message + + + + Logs an errpr to the Unity console if UNITY_EDITOR is defined. Use in the Import context. + + A message + + + + Logs a message to the Unity console if FUSION_EDITOR_TRACE_INSPECTOR is defined. Use in the Inspector context. + + A message + + + + Logs a warning to the Unity console if UNITY_EDITOR is defined. Use in the Inspector context. + + A message + + + + Logs a message to the Unity console if UNITY_EDITOR is defined. Use in the Inspector context. + + A message + + + + Logs an errpr to the Unity console if UNITY_EDITOR is defined. Use in the Inspector context. + + A message + + + + Logs a message to the Unity console if FUSION_EDITOR_TRACE_TEST is defined. Use in the Test context. + + A message + + + + Logs a message to the Unity console if FUSION_EDITOR_TRACE_MPPM is defined. Use in the Mppm context. + + A message + + + + An exception that is thrown when an assertion fails. + + + + + Creates a new instance of the exception. + + + + + Creates a new instance of the exception with a message. + + A message + + + + Provides methods for asserting conditions. Throws an AssertException when a condition is not met. Methods are only invoked in DEBUG builds unless otherwise specified. + + + + + Always throws an AssertException. + + + + + + Always throws an AssertException with a message. + + An error message + + + + + Always throws an AssertException with a message. + + An error message format + + + + + + Throws an AssertException if the is null + + Value to check + An error message + + + + + Throws an AssertException if the is null + + Value to check + An error message + + + + + Throws an AssertException with a message if the is + + Value to check + An error message + + + + + Throws an AssertException with a message if the is + + Value to check + An error message format + + + + + Throws an AssertException with a message if the is + + Value to check + An error message format + + + + + Throws an AssertException with a message if the is + + Value to check + An error message format + + + + + Throws an AssertException with a message if the is + + Value to check + An error message format + + + + + Throws an AssertException with a message if the is . The message is composed from additional arguments. + + + Value to check + + + + + Throws an AssertException with a message if the is . The message is composed from additional arguments. + + Value to check + + + + + Throws an AssertException with a message if the is . The message is composed from additional arguments. + + Value to check + + + + + Throws an AssertException with a message if the is . The message is composed from additional arguments. + + Value to check + + + + + Throws an AssertException with a message if the is . The message is composed from additional arguments. + + Value to check + + + + + Throws an AssertException, even in non-DEBUG builds. + + + + + Throws an AssertException with a message, even in non-DEBUG builds. + + An error message + + + + + Throws an AssertException with a message, even in non-DEBUG builds. + + An error message + + + + + Throws an AssertException with a message, even in non-DEBUG builds. + + An error message + + + + + Throws an AssertException with a message if is , even in non-DEBUG builds. + + Value to check + An error message + + + + + Throws an AssertException with a message if is , even in non-DEBUG builds. + + Value to check + An error message format + + + + + Throws an AssertException with a message if is , even in non-DEBUG builds. + + Value to check + An error message format + + + + + Throws an AssertException with a message if is , even in non-DEBUG builds. + + Value to check + An error message format + + + + + Throws an AssertException with a message if is , even in non-DEBUG builds. + + Value to check + An error message format + + + + + Throws an AssertException with a message if the is , even in non-DEBUG builds. The message is composed from additional arguments. + + Value to check + + + + + Throws an AssertException with a message if the is , even in non-DEBUG builds. The message is composed from additional arguments. + + Value to check + + + + + Throws an AssertException with a message if the is , even in non-DEBUG builds. The message is composed from additional arguments. + + Value to check + + + + + Throws an AssertException with a message if the is , even in non-DEBUG builds. The message is composed from additional arguments. + + Value to check + + + + + Logs an assertion if the is . For Unity builds, log goes straight to UnityEngine.Debug. Otherwise, log goes to LogError stream. + + Value to check + + + + + Logs an assertion if the is . For Unity builds, log goes straight to UnityEngine.Debug. Otherwise, log goes to LogError stream. + + Value to check + + + + Logs an assertion if the is . For Unity builds, log goes straight to UnityEngine.Debug. Otherwise, log goes to LogError stream. + + Value to check + + + + + Logs an assertion if the is . For Unity builds, log goes straight to UnityEngine.Debug. Otherwise, log goes to LogError stream. + + Value to check + + + + + Logs an assertion if the is . For Unity builds, log goes straight to UnityEngine.Debug. Otherwise, log goes to LogError stream. + + Value to check + + + + + A log stream that writes log messages to the console with a specified color. + + + + + Initializes a new instance of the class. + + The console color to use for log messages. + An optional prefix to prepend to each log message. + + + + Logs a message with a source. + + The source of the log message. + The log message. + + + + Logs a message with a source and an exception. + + The source of the log message. + The log message. + The exception to log. + + + + Represents a debug log stream that supports logging to different streams. + + + + Stream for logging informational messages. + + + Stream for logging warning messages. + + + Stream for logging error messages. + + + + Initializes a new instance of the DebugLogStream class. + + The stream for informational messages. + The stream for warning messages. + The stream for error messages. + + + Logs a message with a source to the InfoStream. + + + Logs a message to the InfoStream. + + + Logs an info message with a source to the InfoStream. + + + Logs an info message to the InfoStream. + + + Logs an error message with a source to the ErrorStream. + + + Logs an error message to the ErrorStream. + + + Logs an exception to the ErrorStream. + + + Logs an exception to the ErrorStream. + + + Logs a warning message with a source to the WarnStream. + + + Logs a warning message to the WarnStream. + + + + Returns self if is true. + + + + + Returns self if is false and then sets it to true. + + + + Disposes the DebugLogStream and its underlying streams. + + + + An interface for log sources. Implement this interface to be able to pass a custom context to Log methods. + + + + + Unity object that is the source of the log message. + + + + + + Provides static log streams for different log levels. + + Internal log streams + + + Debug log stream. + + + Info log stream. + + + Warning log stream. + + + Error log stream. + + + Exception log stream. + + + LogTrace stream + + + LogTraceStun stream + + + LogTraceObject stream + + + LogTraceNetwork stream + + + LogTracePrefab stream + + + LogTraceSceneInfo stream + + + LogTraceSceneManager stream + + + LogTraceSimulationMessage stream + + + LogTraceHostMigration stream + + + LogTraceEncryption stream + + + LogTraceDummyTraffic stream + + + LogTraceRealtime stream + + + LogTraceMemoryTrack stream + + + LogTraceSnapshots stream + + + LogTraceTime stream + + + + Global logging class. Needs to be initialized before use. When in Unity, "FusionLogInitializer" takes care of that. + All static methods have conditional compilation directives: + + FUSION_LOGLEVEL_DEBUG enables "Debug" methods + FUSION_LOGLEVEL_INFO enables all the above and Info methods + FUSION_LOGLEVEL_WARN enables all the above and Warn methods + FUSION_LOGLEVEL_ERROR enables all the above and Error methods + + Trace channels are enabled separately with FUSION_TRACE_* directives. + + + + + Uninitializes the logger. All log streams are disposed. + + + + + A delegate to create a log stream. + + + + + Whether the logger is initialized. + + + + + Current log settings. + + + + + Initializes the logger with the specified log level and trace channels. + + The minimal log level. All messages with lower level are not going to be reported, regardless + of FUSION_LOGLEVEL defines. + Trace channels to activate. Trace channels not included will not get reported, regardless + of FUSION_TRACE defines. + + + + + + + + + + Initializes the logger to log to the console. + + + + + + Initializes the logger to log to the console. + + + + Logs a debug message. At least FUSION_LOGLEVEL_DEBUG needs to be defined and at least must have been used to initialize the logger. + + + Logs a debug warning message. At least FUSION_LOGLEVEL_DEBUG needs to be defined and at least must have been used to initialize the logger. + + + Logs a debug error message. At least FUSION_LOGLEVEL_DEBUG needs to be defined and at least must have been used to initialize the logger. + + + Logs a message. At least FUSION_LOGLEVEL_INFO needs to be defined and at least must have been used to initialize the logger. + + + Logs a message. At least FUSION_LOGLEVEL_INFO needs to be defined and at least must have been used to initialize the logger. + + + Logs a warning message. At least FUSION_LOGLEVEL_WARN needs to be defined and at least must have been used to initialize the logger. + + + Logs a warning message. At least FUSION_LOGLEVEL_WARN needs to be defined and at least must have been used to initialize the logger. + + + Logs an error message. At least FUSION_LOGLEVEL_ERROR needs to be defined and at least must have been used to initialize the logger. + + + Logs an error message. At least FUSION_LOGLEVEL_ERROR needs to be defined and at least must have been used to initialize the logger. + + + Logs an exception message. At least FUSION_LOGLEVEL_ERROR needs to be defined and at least must have been used to initialize the logger. + + + Logs an exception message. At least FUSION_LOGLEVEL_ERROR needs to be defined and at least must have been used to initialize the logger. + + + Logs an exception message. At least FUSION_LOGLEVEL_ERROR needs to be defined and at least must have been used to initialize the logger. + + + + Use instead. + + + + + Use instead. + + + + + Use instead. + + + + + Use instead. + + + + Logs a message if TRACE is defined. + The message to log. + + + Logs a warning if TRACE is defined. + The message to log. + + + Logs an error if TRACE is defined. + The message to log. + + + Logs a message if TRACE is defined. + The message source. + The message to log. + + + Logs a warning if TRACE is defined. + The message source. + The message to log. + + + Logs an error if TRACE is defined. + The message source. + The message to log. + + + Logs a message if DEBUG is defined. + The message to log. + + + Logs a warning if DEBUG is defined. + The message to log. + + + Logs an error if DEBUG is defined. + The message to log. + + + Logs a message if DEBUG is defined. + The message source. + The message to log. + + + Logs a warning if DEBUG is defined. + The message source. + The message to log. + + + Logs an error if DEBUG is defined. + The message source. + The message to log. + + + Logs a message of Info type. + The message to log. + + + Logs a message of Warn type. + The message to log. + + + Logs a message of Error type. + The message to log. + + + Enabled by FUSION_TRACE_GLOBAL + + + Enabled by FUSION_TRACE_GLOBAL + + + Enabled by FUSION_TRACE_GLOBAL + + + Enabled by FUSION_TRACE_GLOBAL + + + Enabled by FUSION_TRACE_GLOBAL + + + Enabled by FUSION_TRACE_GLOBAL + + + Enabled by FUSION_TRACE_SCENEMANAGER + + + Enabled by FUSION_TRACE_SCENEMANAGER + + + Enabled by FUSION_TRACE_SCENEMANAGER + + + Enabled by FUSION_TRACE_SCENEMANAGER + + + Enabled by FUSION_TRACE_SCENEMANAGER + + + Enabled by FUSION_TRACE_SCENEMANAGER + + + + Log type. + + + + + Error log type. + + + + + Warning log type. + + + + + Information log type. + + + + + Debug log type. + + + + + Trace log type. + + + + + Flags for logging. + + + + + This is a debug stream. + + + + + This is a trace stream. + + + + + The log level. Messages with a lower level than the current LogLevel will be ignored. + + + + + Debug messages. Dlls will only output Debug messages if compiled with DEBUG symbol. + + + + + General info messages. + + + + + Warning messages. + + + + + Error messages. + + + + + No messages will be output. + + + + + Extensions for + + + + + Returns a define used for a given log level. + + + + + Settings for the logging system. + + + + + The minimum log level to output. + + + + + Mask of log channels to output. + + + + + Creates a new instance of LogSettings. + + + + + Interface for logging streams. + + + + + Logs a message with a source. + + The source of the log message. + The log message. + + + + Logs a message. + + The log message. + + + + Logs a message with a source and an exception. + + The source of the log message. + The log message. + The exception to log. + + + + Logs a message with an exception. + + The source of the log message. + The exception to log. + + + + Logs a message with an exception. + + The log message. + The exception to log. + + + + Logs an exception. + + The exception to log. + + + + Returns self if is true. + + + + + Returns self if is false and then sets it to true. + + + + + + + + A logger that outputs messages to the Unity console. + + + + + If true, each log message that has a source parameter will be prefixed with a hash code of the source object. + + + + + A prefix tag added to each log. + + + + + Color of the global prefix (see ). + + + + + Max Random Color + + + + + Min Random Color + + + + + Customize logged object names from other threads. + + + + + Customize logged object names for destroyed objects. + + + + + If true, some parts of messages will be enclosed with <color> tags. + + + + + If true, all messages will be prefixed with [Fusion] tag + + + + + Create unity logger instance. + + + The thread used by or to use the current + thread. + + Should the logger use colors suited for Unity Editor dark mode + + + + Clean up resources. + + + + + Create a log stream using Unity logger. + + + + + + + + + + + + + + + + + + + + + + Append prefix to the log message. + + + + + + + + Append object name to the log message in a thread-safe way. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Default prefix color for light theme. + + + + + Default prefix color for dark theme. + + + + + Prefix for debug messages. + + + + + Prefix for trace messages. + + + + + A log stream that writes log messages to a . + + + + + Initializes a new instance of the class. + + The to write log messages to. + If set to true, the writer will be disposed when this instance is disposed. + An optional prefix to prepend to each log message. + Thrown if is null. + + + + Logs a message with a source. + + The source of the log message. + The log message. + + + + Logs a message. + + The log message. + + + + Logs a message with a source and an exception. + + The source of the log message. + The log message. + The exception to log. + + + + Logs a message with an exception. + + The log message. + The exception to log. + + + + Logs an exception. + + The exception to log. + + + + Disposes the instance and optionally disposes the underlying . + + + + + Represents a trace log stream that supports logging to different streams. + + + + Stream for logging informational messages. + + + Stream for logging warning messages. + + + Stream for logging error messages. + + + + Initializes a new instance of the TraceLogStream class. + + The stream for informational messages. + The stream for warning messages. + The stream for error messages. + + + Logs a message with a source to the InfoStream. + + + Logs a message to the InfoStream. + + + Logs an info message with a source to the InfoStream. + + + Logs an info message to the InfoStream. + + + Logs an error message with a source to the ErrorStream. + + + Logs an error message to the ErrorStream. + + + Logs an exception to the ErrorStream. + + + Logs an exception to the ErrorStream. + + + Logs a warning message with a source to the WarnStream. + + + Logs a warning message to the WarnStream. + + + + Returns self if is true. + + + + + Returns self if is false and then sets it to true. + + + + Disposes the TraceLogStream and its underlying streams. + + + + Interface for objects that can be dumped to a StringBuilder. + + + + + Dumps the object to the provided StringBuilder. + + The StringBuilder to dump the object to. + + + Console logger + + + Constructor + + + Logs a message with the specified log type and context. + + + Logs an exception with the specified context. + + + Obsolete interface for logging operations. + + + Logs a message with the specified log type and context. + + + Logs an exception with the specified context. + + + Obsolete interface for log source proxy. + + + Gets the log source. + + + Obsolete struct for log context information. + + + The prefix for the log message. + + + The source of the log message. + + + Initializes a new instance of the struct. + + + Obsolete class for logging to a TextWriter. + + + Initializes a new instance of the class. + + + Disposes the TextWriterLogger instance. + + + Logs a message with the specified log type and context. + + + Logs an exception with the specified context. + + + Trace channels + + + Global + + + Stun + + + Object + + + Network + + + Prefab + + + SceneInfo + + + SceneManager + + + SimulationMessage + + + HostMigration + + + Encryption + + + DummyTraffic + + + Realtime + + + MemoryTrack + + + Snapshots + + + Time + + + + Utility class for logging operations. + + + + + Gets a deferred dump of a pointer to an unmanaged object. + + The type of the unmanaged object. + Pointer to the unmanaged object. + A deferred dump of the pointer. + + + + Gets a deferred dump of a class object. + + The type of the class object. + The class object. + A deferred dump of the class object. + + + + Represents a deferred dump of a pointer to an unmanaged object. + + The type of the unmanaged object. + + + + Represents a deferred dump of a pointer to an unmanaged object. + + The type of the unmanaged object. + + + ToString + + + + Represents a deferred dump of an unmanaged object. + + The type of the unmanaged object. + + + + Represents a deferred dump of an unmanaged object. + + The type of the unmanaged object. + + + ToString + + + + Represents a deferred dump of a class object. + + + + + Represents a deferred dump of a class object. + + + + + Object to dump. + + + + ToString + + + diff --git a/Assets/Photon/Fusion/Assemblies/Fusion.Log.xml.meta b/Assets/Photon/Fusion/Assemblies/Fusion.Log.xml.meta new file mode 100644 index 00000000..5795fa80 --- /dev/null +++ b/Assets/Photon/Fusion/Assemblies/Fusion.Log.xml.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: ab0587b705cfcf7429767247d605fa5e +labels: +- FusionCodeDoc +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Assemblies/Fusion.Realtime.dll b/Assets/Photon/Fusion/Assemblies/Fusion.Realtime.dll new file mode 100644 index 00000000..69029644 Binary files /dev/null and b/Assets/Photon/Fusion/Assemblies/Fusion.Realtime.dll differ diff --git a/Assets/Photon/Fusion/Assemblies/Fusion.Realtime.dll.meta b/Assets/Photon/Fusion/Assemblies/Fusion.Realtime.dll.meta new file mode 100644 index 00000000..ac114ab1 --- /dev/null +++ b/Assets/Photon/Fusion/Assemblies/Fusion.Realtime.dll.meta @@ -0,0 +1,33 @@ +fileFormatVersion: 2 +guid: 7de3b8b9e1263ad479e2d0c4261b7646 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Any: + second: + enabled: 1 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + Windows Store Apps: WindowsStoreApps + second: + enabled: 0 + settings: + CPU: AnyCPU + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Assemblies/Fusion.Realtime.xml b/Assets/Photon/Fusion/Assemblies/Fusion.Realtime.xml new file mode 100644 index 00000000..4e010e79 --- /dev/null +++ b/Assets/Photon/Fusion/Assemblies/Fusion.Realtime.xml @@ -0,0 +1,5003 @@ + + + + Fusion.Realtime + + + + + Wrapper for realtime region information. + + + + + The code of the region. + + + + + The ping of the region. + + + + + Class to provide access to some realtime operations and API. + + + + + Time the cache will be available in seconds since the last request. + + + + + Responsible for running a thread that will keep the LoadBalancingClient connected to the Cloud + when the application is in the background + + + Fusion Realtime Client + + This will deal with all communication done with the Photon Cloud + + + Fusion Realtime Client + + This will deal with all communication done with the Photon Cloud + + + + + Reference to the background connection handler + + + + + Starts a new thread to send ACK to Cloud + + + + + Stops the currently running background thread. + + + + + Handles all received events from the Photon Cloud + + Event Data + + + + Send data to another Actor on the Room + + Target Actor of the Event + Event Code + Data to be sent + Buffer Length + Flag to set reliability on the Event + True if the event was sent, false otherwise + + + + Utility method used to extract the content of a from a object holder. + This is necessary so there are no references of Photon Lib DLLs other than on the Fusion.Realtime project + + Data Object Holder, this must be a reference of a + Buffer to write the content of the array slice + Output size of the written buffer + True if the extraction was done correctly, false otherwise + + + + Fusion Plugin Name for request + + + + + Alternative Name Server for CN Region + + + + + Flag to signal if the Client is Ready to perform cloud action and it is in a Room + + + + + Flag to signal if the Photon Cloud connection is encrypted by default + + + + + Signal if some room property has changed + + + + + Set if the Client should use the Default or Alternative Photon Cloud Ports + + + + + + + + + + Try to load the first implementation of found in the current AppDomain + + Type of the first implementation of found + + + + Change the Custom Properties of the current Room + + New set of Custom Properties + True if the change was made, false otherwise + + + + Change the IsVisible Property of the current Room + + New value of IsVisible + True if the change was made, false otherwise + + + + Change the IsOpen Property of the current Room + + New value of IsOpen + True if the change was made, false otherwise + + + + Used to keep the client communication + + + + + Build a new EnterRoomParams ref using the default configs and optional Room Name + + Which lobby the Room should exits + Room Name, if not set, a Random Name will be used + Set the max number of players per Session + Optional Room Custom Properties + Signal if the Session should be Open/Closed + Signal if the Session should Visible/Invisible + Signal fi the Default EmptyRoomTTL (0) should be used or a custom one + Signal if the Session should have an extended Time To Live + EnterRoomParams reference. + + + + Build a new OpJoinRandomRoomParams that will be used to setup which Room the local peer wants to join + + Type of Lobby to search rooms + Optional list of filter parameters + Optional Matchmaking Mode + OpJoinRandomRoomParams reference + + + + Convert a into a pair of and respectively + representing the custom properties of a session and the property names that will be published on the Lobby + + Dictionary to be converted + Hashtable with all allowed Custom Properties + String array with all public key names + + + + Settings for Photon application(s) and the server to connect to. + + + This is Serializable for Unity, so it can be included in ScriptableObject instances. + + + + AppId for Realtime or PUN. + + + AppId for Photon Fusion. + + + AppId for Photon Chat. + + + AppId for Photon Voice. + + + The AppVersion can be used to identify builds and will split the AppId distinct "Virtual AppIds" (important for matchmaking). + + + If false, the app will attempt to connect to a Master Server (which is obsolete but sometimes still necessary). + if true, Server points to a NameServer (or is null, using the default), else it points to a MasterServer. + + + Can be set to any of the Photon Cloud's region names to directly connect to that region. + if this IsNullOrEmpty() AND UseNameServer == true, use BestRegion. else, use a server + + + Set to a previous BestRegionSummary value before connecting. + + This is a value used when the client connects to the "Best Region".
+ If this is null or empty, all regions gets pinged. Providing a previous summary on connect, + speeds up best region selection and makes the previously selected region "sticky".
+ + Unity clients should store the BestRegionSummary in the PlayerPrefs. + You can store the new result by implementing . + If is not null, store this string. + To avoid storing the value multiple times, you could set SummaryToCache to null. +
+
+ + The address (hostname or IP) of the server to connect to. + + + If not null, this sets the port of the first Photon server to connect to (that will "forward" the client as needed). + + + The address (hostname or IP and port) of the proxy server. + + + The network level protocol to use. + + + Enables a fallback to another protocol in case a connect to the Name Server fails. + See: LoadBalancingClient.EnableProtocolFallback. + + + Defines how authentication is done. On each system, once or once via a WSS connection (safe). + + + If true, the client will request the list of currently available lobbies. + + + Log level for the network lib. + + + If true, the Server field contains a Master Server address (if any address at all). + + + If true, the client should fetch the region list from the Name Server and find the one with best ping. + See "Best Region" in the online docs. + + + If true, the default nameserver address for the Photon Cloud should be used. + + + If true, the default ports for a protocol will be used. + + + ToString but with more details. + + + Checks if a string is a Guid by attempting to create one. + The potential guid to check. + True if new Guid(val) did not fail. + + + + Copy the values of the current instance into another instance. + + The destination instance. + The destination instance with the copied values. + + + + Get a Copy from the into a new instance. + + Copy of + + + + Photon client to log information and statistics from. + + + + Option to let the fallback thread call Disconnect after the KeepAliveInBackground time. Default: false. + + If set to true, the thread will disconnect the client regularly, should the client not call SendOutgoingCommands / Service. + This may happen due to an app being in background (and not getting a lot of CPU time) or when loading assets. + + If false, a regular timeout time will have to pass (on top) to time out the client. + + + + Defines for how long the Fallback Thread should keep the connection, before it may time out as usual. + We want to the Client to keep it's connection when an app is in the background (and doesn't call Update / Service Clients should not keep their connection indefinitely in the background, so after some milliseconds, the Fallback Thread should stop keeping it up. + + + Counts how often the Fallback Thread called SendAcksOnly, which is purely of interest to monitor if the game logic called SendOutgoingCommands as intended. + + + True if a fallback thread is running. Will call the client's SendAcksOnly() method to keep the connection up. + + + Keeps the ConnectionHandler, even if a new scene gets loaded. + + + Indicates that the app is closing. Set in OnApplicationQuit(). + + + Indicates that the (Unity) app is Paused. This means the main thread is not running. + + + Indicates that the app was paused within the last 5 seconds. + + + Indicates that the app is not in focus. + + + Indicates that the app was out of focus within the last 5 seconds. + + + + Resets statics for Domain Reload + + + + + + + Called by Unity when the application gets closed. Disconnects if OnApplicationQuit() was called before. + + + Called by Unity when the application gets closed. The UnityEngine will also call OnDisable, which disconnects. + + + Called by Unity when the application gets paused or resumed. + + + Called by Unity when the application changes focus. + + + + When run in Unity, this returns Application.internetReachability != NetworkReachability.NotReachable. + + Application.internetReachability != NetworkReachability.NotReachable + + + Starts periodic calls of RealtimeFallbackThread. + + + Stops the periodic calls of RealtimeFallbackThread. + + + Used in WebGL builds which can't call RealtimeFallback(object state = null) with the state context parameter. + + + A thread which runs independently of the Update() calls. Keeps connections online while loading or in background. See . + + + + Internally used class, containing de/serialization methods for various Unity-specific classes. + Adding those to the Photon serialization protocol allows you to send them in events, etc. + + + + Register de/serializer methods for Unity specific types. Makes the types usable in RaiseEvent and PUN. + + + + This static class defines some useful extension methods for several existing classes (e.g. Vector3, float and others). + + + + + Merges all keys from addHash into the target. Adds new keys and updates the values of existing keys in target. + + The IDictionary to update. + The IDictionary containing data to merge into target. + + + + Merges keys of type string to target Hashtable. + + + Does not remove keys from target (so non-string keys CAN be in target if they were before). + + The target IDictionary passed in plus all string-typed keys from the addHash. + A IDictionary that should be merged partly into target to update it. + + + Helper method for debugging of IDictionary content, including type-information. Using this is not performant. + Should only be used for debugging as necessary. + Some Dictionary or Hashtable. + String of the content of the IDictionary. + + + Helper method for debugging of content. Using this is not performant. + Should only be used for debugging as necessary. + Any where T implements .ToString(). + A comma-separated string containing each value's ToString(). + + + Helper method for debugging of object[] content. Using this is not performant. + Should only be used for debugging as necessary. + Any object[]. + A comma-separated string containing each value's ToString(). + + + + This method copies all string-typed keys of the original into a new Hashtable. + + + Does not recurse (!) into hashes that might be values in the root-hash. + This does not modify the original. + + The original IDictonary to get string-typed keys from. + New Hashtable containing only string-typed keys of the original. + + + + This method copies all string-typed keys of the original into a new Hashtable. + + + Does not recurse (!) into hashes that might be values in the root-hash. + This does not modify the original. + + The original IDictonary to get string-typed keys from. + New Hashtable containing only string-typed keys of the original. + + + Used by StripKeysWithNullValues. + + By making keysWithNullValue a static variable to clear before using, allocations only happen during the warm-up phase + as the list needs to grow. Once it hit the high water mark for keys you need to remove. + + + + Removes all keys with null values. + + Photon properties are removed by setting their value to null. Changes the original IDictionary! + Uses lock(keysWithNullValue), which should be no problem in expected use cases. + + The IDictionary to strip of keys with null value. + + + Removes all keys with null values. + + Photon properties are removed by setting their value to null. Changes the original IDictionary! + Uses lock(keysWithNullValue), which should be no problem in expected use cases. + + The IDictionary to strip of keys with null value. + + + + Checks if a particular integer value is in an int-array. + + This might be useful to look up if a particular actorNumber is in the list of players of a room. + The array of ints to check. + The number to lookup in target. + True if nr was found in target. + + + + Used to store info about a friend's online state and in which room he/she is. + + + + + State values for a client, which handles switching Photon server types, some operations, etc. + + \ingroup publicApi + + + Peer is created but not used yet. + + + Transition state while connecting to a server. On the Photon Cloud this sends the AppId and AuthenticationValues (UserID). + + + Not Used. + + + The client sent an OpJoinLobby and if this was done on the Master Server, it will result in. Depending on the lobby, it gets room listings. + + + The client is in a lobby, connected to the MasterServer. Depending on the lobby, it gets room listings. + + + Transition from MasterServer to GameServer. + + + Transition to GameServer (client authenticates and joins/creates a room). + + + Connected to GameServer (going to auth and join game). + + + Transition state while joining or creating a room on GameServer. + + + The client entered a room. The CurrentRoom and Players are known and you can now raise events. + + + Transition state when leaving a room. + + + Transition from GameServer to MasterServer (after leaving a room/game). + + + Connecting to MasterServer (includes sending authentication values). + + + The client disconnects (from any server). This leads to state Disconnected. + + + The client is no longer connected (to any server). Connect to MasterServer to go on. + + + Connected to MasterServer. You might use matchmaking or join a lobby now. + + + Client connects to the NameServer. This process includes low level connecting and setting up encryption. When done, state becomes ConnectedToNameServer. + + + Client is connected to the NameServer and established encryption already. You should call OpGetRegions or ConnectToRegionMaster. + + + Clients disconnects (specifically) from the NameServer (usually to connect to the MasterServer). + + + Client was unable to connect to Name Server and will attempt to connect with an alternative network protocol (TCP). + + + + Internal state, how this peer gets into a particular room (joining it or creating it). + + + + This client creates a room, gets into it (no need to join) and can set room properties. + + + The room existed already and we join into it (not setting room properties). + + + Done on Master Server and (if successful) followed by a Join on Game Server. + + + Done on Master Server and (if successful) followed by a Join or Create on Game Server. + + + Client is either joining or creating a room. On Master- and Game-Server. + + + Enumeration of causes for Disconnects (used in LoadBalancingClient.DisconnectedCause). + Read the individual descriptions to find out what to do about this type of disconnect. + + + No error was tracked. + + + OnStatusChanged: The server is not available or the address is wrong. Make sure the port is provided and the server is up. + + + OnStatusChanged: Dns resolution for a hostname failed. The exception for this is being caught and logged with error level. + + + OnStatusChanged: The server address was parsed as IPv4 illegally. An illegal address would be e.g. 192.168.1.300. IPAddress.TryParse() will let this pass but our check won't. + + + OnStatusChanged: Some internal exception caused the socket code to fail. This may happen if you attempt to connect locally but the server is not available. In doubt: Contact Exit Games. + + + Send exception. + + + Receive exception. + + + OnStatusChanged: The server disconnected this client due to timing out (missing acknowledgement from the client). + + + OnStatusChanged: This client detected that the server's responses are not received in due time. + + + OnStatusChanged: The server disconnected this client from within the room's logic (the C# code). + + + OnStatusChanged: The server disconnected this client for unknown reasons. + + + OnOperationResponse: Authenticate in the Photon Cloud with invalid AppId. Update your subscription or contact Exit Games. + + + OnOperationResponse: Authenticate in the Photon Cloud with invalid client values or custom authentication setup in Cloud Dashboard. + + + The authentication ticket should provide access to any Photon Cloud server without doing another authentication-service call. However, the ticket expired. + + + OnOperationResponse: Authenticate (temporarily) failed when using a Photon Cloud subscription without CCU Burst. Update your subscription. + + + OnOperationResponse: Authenticate when the app's Photon Cloud subscription is locked to some (other) region(s). Update your subscription or master server address. + + + OnOperationResponse: Operation that's (currently) not available for this client (not authorized usually). Only tracked for op Authenticate. + + + OnStatusChanged: The client disconnected from within the logic (the C# code). + + + The client called an operation too frequently and got disconnected due to hitting the OperationLimit. This triggers a client-side disconnect, too. + To protect the server, some operations have a limit. When an OperationResponse fails with ErrorCode.OperationLimitReached, the client disconnects. + + + The client received a "Disconnect Message" from the server. Check the debug logs for details. + + + Used in case the application quits. Can be useful to not load new scenes or re-connect in OnDisconnected. + ConnectionHandler.OnDisable() will use this, if the Unity engine already called OnApplicationQuit (ConnectionHandler.AppQuits = true). + + + Available server (types) for internally used field: server. + Photon uses 3 different roles of servers: Name Server, Master Server and Game Server. + + + This server is where matchmaking gets done and where clients can get lists of rooms in lobbies. + + + This server handles a number of rooms to execute and relay the messages between players (in a room). + + + This server is used initially to get the address (IP) of a Master Server for a specific region. Not used for Photon OnPremise (self hosted). + + + Defines which sort of app the LoadBalancingClient is used for: Realtime or Voice. + + + Realtime apps are for gaming / interaction. Also used by PUN 2. + + + Voice apps stream audio. + + + Fusion clients are for matchmaking and relay in Photon Fusion. + + + + Defines how the communication gets encrypted. + + + + + This is the default encryption mode: Messages get encrypted only on demand (when you send operations with the "encrypt" parameter set to true). + + + + + Datagram Encryption with GCM. + + + + Container for port definitions. + + + Typical ports: UDP: 5058 or 27000, TCP: 4533, WSS: 19093 or 443. + + + Typical ports: UDP: 5056 or 27002, TCP: 4530, WSS: 19090 or 443. + + + Typical ports: UDP: 5055 or 27001, TCP: 4531, WSS: 19091 or 443. + + + + This class implements the Photon LoadBalancing workflow by using a LoadBalancingPeer. + It keeps a state and will automatically execute transitions between the Master and Game Servers. + + + This class (and the Player class) should be extended to implement your own game logic. + You can override CreatePlayer as "factory" method for Players and return your own Player instances. + The State of this class is essential to know when a client is in a lobby (or just on the master) + and when in a game where the actual gameplay should take place. + Extension notes: + An extension of this class should override the methods of the IPhotonPeerListener, as they + are called when the state changes. Call base.method first, then pick the operation or state you + want to react to and put it in a switch-case. + We try to provide demo to each platform where this api can be used, so lookout for those. + + + + + The client uses a LoadBalancingPeer as API to communicate with the server. + This is public for ease-of-use: Some methods like OpRaiseEvent are not relevant for the connection state and don't need a override. + + + + + Gets or sets the binary protocol version used by this client + + + Use this always instead of setting it via + () directly, especially when WSS protocol is used. + + + + The version of your client. A new version also creates a new "virtual app" to separate players from older client versions. + + + The AppID as assigned from the Photon Cloud. If you host yourself, this is the "regular" Photon Server Application Name (most likely: "LoadBalancing"). + + + The ClientAppType defines which sort of AppId should be expected. The LoadBalancingClient supports Realtime and Voice app types. Default: Realtime. + + + User authentication values to be sent to the Photon server right after connecting. + Set this property or pass AuthenticationValues by Connect(..., authValues). + + + Enables the new Authentication workflow. + + + Defines how the communication gets encrypted. + + + Optionally contains a protocol which will be used on Master- and GameServer. + + When using AuthMode = AuthModeOption.AuthOnceWss, the client uses a wss-connection on the NameServer but another protocol on the other servers. + As the NameServer sends an address, which is different per protocol, it needs to know the expected protocol. + + This is nullable by design. In many cases, the protocol on the NameServer is not different from the other servers. + If set, the operation AuthOnce will contain this value and the OpAuth response on the NameServer will execute a protocol switch. + + + + Simplifies getting the token for connect/init requests, if this feature is enabled. + + + Internally used cache for the server's token. Identifies a user/session and can be used to rejoin. + + + True if this client uses a NameServer to get the Master Server address. + This value is public, despite being an internal value, which should only be set by this client. + + + Name Server Host Name for Photon Cloud. Without port and without any prefix. + + + Name Server Address for Photon Cloud (based on current protocol). You can use the default values and usually won't have to set this value. + + + Name Server port per protocol (the UDP port is different than TCP, etc). + + + Replaced by ServerPortOverrides. + + + Defines overrides for server ports. Used per server-type if > 0. Important: You must change these when the protocol changes! + + Typical ports are listed in PhotonPortDefinition. + + Instead of using the port provided from the servers, the specified port is used (independent of the protocol). + If a value is 0 (default), the port is not being replaced. + + Different protocols have different typical ports per server-type. + https://doc.photonengine.com/en-us/pun/current/reference/tcp-and-udp-port-numbers + + In case of using the AuthMode AutOnceWss, the name server's protocol is wss, while udp or tcp will be used on the master server and game server. + Set the ports accordingly per protocol and server. + + + + Enables the fallback to WSS, should the initial connect to the Name Server fail. Some exceptions apply. + + For security reasons, a fallback to another protocol is not done when using WSS or AuthMode.AuthOnceWss. + That would compromise the expected security. + + If the fallback is impossible or if that connection also fails, the app logic must handle the case. + It might even make sense to just try the same connection settings once more (or ask the user to do something about + the network connectivity, firewalls, etc). + + The fallback will use the default Name Server port as defined by ProtocolToNameServerPort. + + + + The currently used server address (if any). The type of server is defined by Server property. + + + Your Master Server address. In PhotonCloud, call ConnectToRegionMaster() to find your Master Server. + + In the Photon Cloud, explicit definition of a Master Server Address is not best practice. + The Photon Cloud has a "Name Server" which redirects clients to a specific Master Server (per Region and AppId). + + + + The game server's address for a particular room. In use temporarily, as assigned by master. + + + Provides a custom function to re-write server addresses in case the client must use a third party relay. + + Discord Activities can only communicate with the domain discord.com. + A forwarding system based on paths is used to replace addresses that are not on that domain. + + + + The server this client is currently connected or connecting to. + + Each server (NameServer, MasterServer, GameServer) allow some operations and reject others. + + + + + Defines a proxy URL for WebSocket connections. Can be the proxy or point to a .pac file. + + + This URL supports various definitions: + + "user:pass@proxyaddress:port"
+ "proxyaddress:port"
+ "system:"
+ "pac:"
+ "pac:http://host/path/pacfile.pac"
+ + Important: Don't define a protocol, except to point to a pac file. the proxy address should not begin with http:// or https://. +
+
+ + Count of connections made to any server. + Statistical value. Increased by OnStatusChanged(StatusCode.Connect). + + + Backing field for property. + + + Current state this client is in. Careful: several states are "transitions" that lead to other states. + + + Returns if this client is currently connected or connecting to some type of server. + This is even true while switching servers. Use IsConnectedAndReady to check only for those states that enable you to send Operations. + + + + A refined version of IsConnected which is true only if your connection is ready to send operations. + + + Not all operations can be called on all types of servers. If an operation is unavailable on the currently connected server, + this will result in a OperationResponse with ErrorCode != 0. + + Examples: The NameServer allows OpGetRegions which is not available anywhere else. + The MasterServer does not allow you to send events (OpRaiseEvent) and on the GameServer you are unable to join a lobby (OpJoinLobby). + + To check which server you are on, use: . + + + + Register a method to be called when this client's ClientState gets set. + This can be useful to react to being connected, joined into a room, etc. + + + Register a method to be called when an event got dispatched. Gets called after the LoadBalancingClient handled the internal events first. + + This is an alternative to extending LoadBalancingClient to override OnEvent(). + + Note that OnEvent is calling EventReceived after it handled internal events first. + That means for example: Joining players will already be in the player list but leaving + players will already be removed from the room. + + + + Register a method to be called when an operation response is received. + + This is an alternative to extending LoadBalancingClient to override OnOperationResponse(). + + Note that OnOperationResponse gets executed before your Action is called. + That means for example: The OpJoinLobby response already set the state to "JoinedLobby" + and the response to OpLeave already triggered the Disconnect before this is called. + + + + Wraps up the target objects for a group of callbacks, so they can be called conveniently. + By using Add or Remove, objects can "subscribe" or "unsubscribe" for this group of callbacks. + + + Wraps up the target objects for a group of callbacks, so they can be called conveniently. + By using Add or Remove, objects can "subscribe" or "unsubscribe" for this group of callbacks. + + + Wraps up the target objects for a group of callbacks, so they can be called conveniently. + By using Add or Remove, objects can "subscribe" or "unsubscribe" for this group of callbacks. + + + Wraps up the target objects for a group of callbacks, so they can be called conveniently. + By using Add or Remove, objects can "subscribe" or "unsubscribe" for this group of callbacks. + + + Wraps up the target objects for a group of callbacks, so they can be called conveniently. + By using Add or Remove, objects can "subscribe" or "unsubscribe" for this group of callbacks. + + + Wraps up the target objects for a group of callbacks, so they can be called conveniently. + By using Add or Remove, objects can "subscribe" or "unsubscribe" for this group of callbacks. + + + Summarizes (aggregates) the different causes for disconnects of a client. + + A disconnect can be caused by: errors in the network connection or some vital operation failing + (which is considered "high level"). While operations always trigger a call to OnOperationResponse, + connection related changes are treated in OnStatusChanged. + The DisconnectCause is set in either case and summarizes the causes for any disconnect in a single + state value which can be used to display (or debug) the cause for disconnection. + + + + Defaults to null. Set when the client receives a disconnect info message with a debug string. Reset to null in connect methods. + + + Defines if this client sends telemetry / analytics about how the connection ends. + + + Tells us if "this session" was already reported. We want to send only one report in best case. Re-set on connect. + + + + After a to a connection loss or timeout, this summarizes the most relevant system conditions which might have contributed to the loss. + + + + + + Internal value if the client is in a lobby. + This is used to re-set this.State, when joining/creating a room fails. + + + The lobby this client currently uses. Defined when joining a lobby or creating rooms + + + + If enabled, the client will get a list of available lobbies from the Master Server. + + + Set this value before the client connects to the Master Server. While connected to the Master + Server, a change has no effect. + + Implement OptionalInfoCallbacks.OnLobbyStatisticsUpdate, to get the list of used lobbies. + + The lobby statistics can be useful if your title dynamically uses lobbies, depending (e.g.) + on current player activity or such. + In this case, getting a list of available lobbies, their room-count and player-count can + be useful info. + + ConnectUsingSettings sets this to the PhotonServerSettings value. + + + + Internal lobby stats cache, used by LobbyStatistics. + + + The local player is never null but not valid unless the client is in a room, too. The ID will be -1 outside of rooms. + + + + The nickname of the player (synced with others). Same as client.LocalPlayer.NickName. + + + + An ID for this user. Sent in OpAuthenticate when you connect. If not set, the PlayerName is applied during connect. + + On connect, if the UserId is null or empty, the client will copy the PlayName to UserId. If PlayerName is not set either + (before connect), the server applies a temporary ID which stays unknown to this client and other clients. + + The UserId is what's used in FindFriends and for fetching data for your account (with WebHooks e.g.). + + By convention, set this ID before you connect, not while being connected. + There is no error but the ID won't change while being connected. + + + + The current room this client is connected to (null if none available). + + + Is true while being in a room (this.state == ClientState.Joined). + + Aside from polling this value, game logic should implement IMatchmakingCallbacks in some class + and react when that gets called.
+ OpRaiseEvent, OpLeave and some other operations can only be used (successfully) when the client is in a room.. +
+
+ + Statistic value available on master server: Players on master (looking for games). + + + Statistic value available on master server: Players in rooms (playing). + + + Statistic value available on master server: Rooms currently created. + + + Internally used to decide if a room must be created or joined on game server. + + + Used when the client arrives on the GS, to join the room with the correct values. + + + Used to cache a failed "enter room" operation on the Game Server, to return to the Master Server before calling a fail-callback. + + + Maximum of userIDs that can be sent in one friend list request. + + + Contains the list of names of friends to look up their state on the server. + + + Internal flag to know if the client currently fetches a friend list. + + + The cloud region this client connects to. Set by ConnectToRegionMaster(). Not set if you don't use a NameServer! + + + The cluster name provided by the Name Server. + + The value is provided by the OpResponse for OpAuthenticate/OpAuthenticateOnce. + Default: null. This value only ever updates from the Name Server authenticate response. + + + + Contains the list if enabled regions this client may use. Null, unless the client got a response to OpGetRegions. + + + Stores the best region summary of a previous session to speed up connecting. + + + Set when the best region pinging is done. + + + Internal connection setting/flag. If the client should connect to the best region or not. + + It's set in the Connect...() methods. Only ConnectUsingSettings() sets it to true. + If true, client will ping available regions and select the best. + A bestRegionSummaryFromStorage can be used to cut the ping time short. + + + + Definition of parameters for encryption data (included in Authenticate operation response). + + + + Key for encryption mode + + + + + Key for first secret + + + + + Key for second secret + + + + Add if true, remove if false. + + + Creates a LoadBalancingClient with UDP protocol or the one specified. + Specifies the network protocol to use for connections. + + + Creates a LoadBalancingClient, setting various values needed before connecting. + The Master Server's address to connect to. Used in Connect. + The AppId of this title. Needed for the Photon Cloud. Find it in the Dashboard. + A version for this client/build. In the Photon Cloud, players are separated by AppId, GameVersion and Region. + Specifies the network protocol to use for connections. + + + + Gets the NameServer Address (with prefix and port), based on the set protocol (this.LoadBalancingPeer.TransportProtocol). + + NameServer Address (with prefix and port). + + + Build URI from address, use Scheme, Host and Path but set the port as defined by port-field or default port. Calls AddressRewriter if set. + + + + Starts the "process" to connect as defined by the appSettings (AppId, AppVersion, Transport Protocol, Port and more). + + A typical connection process wraps up these steps:
+ - Low level connect and init (which establishes a connection that enables operations and responses for the Realtime API).
+ - GetRegions and select best (unless FixedRegion is being used).
+ - Authenticate user for a specific region (this provides a Master Server address to go to and a token).
+ - Disconnect Name Server and connect to Master Server (using the token).
+ - The callback OnConnectedToMaster gets called.
+
+ Connecting to the servers is a process and this is a non-blocking method.
+ Implement and register the IConnectionCallbacks interface to get callbacks about success or failing connects.
+
+ Basically all settings for the connection, AppId and servers can be done via the provided parameter.
+
+ Connecting to the Photon Cloud might fail due to:
+ - Network issues
+ - Region not available
+ - Subscription CCU limit
+
+ + + Collection of settings defining this app and how to connect. + True if the client can attempt to connect. +
+ + + Starts the "process" to connect to a Master Server, using MasterServerAddress and AppId properties. + + + To connect to the Photon Cloud, use ConnectUsingSettings() or ConnectToRegionMaster(). + + The process to connect includes several steps: the actual connecting, establishing encryption, authentification + (of app and optionally the user) and connecting to the MasterServer + + Users can connect either anonymously or use "Custom Authentication" to verify each individual player's login. + Custom Authentication in Photon uses external services and communities to verify users. While the client provides a user's info, + the service setup is done in the Photon Cloud Dashboard. + The parameter authValues will set this.AuthValues and use them in the connect process. + + Connecting to the Photon Cloud might fail due to: + - Network issues (OnStatusChanged() StatusCode.ExceptionOnConnect) + - Region not available (OnOperationResponse() for OpAuthenticate with ReturnCode == ErrorCode.InvalidRegion) + - Subscription CCU limit reached (OnOperationResponse() for OpAuthenticate with ReturnCode == ErrorCode.MaxCcuReached) + + + + + Connects to the NameServer for Photon Cloud, where a region and server list can be obtained. + + + If the workflow was started or failed right away. + + + + Connects you to a specific region's Master Server, using the Name Server to get the IP. + + + If the region is null or empty, no connection will be made. + If the region (code) provided is not available, the connection process will fail on the Name Server. + This method connects only to the region defined. Any "Best Region" pinging should get done beforehand. + + To support "sharding", a region string may contain a "/*" to pick a random cluster or "/clustername" + to connect to a specific cluster. + With a "/" or no cluster postfix, the client connects to the default cluster (a specific one + for a region). + + By default, the region string provided by the Name Server does not contain a cluster (and only the + default cluster is used). + + If the operation could be sent. If false, no operation was sent. + + + + Privately used only for reconnecting. + + + + Can be used to reconnect to the master server after a disconnect. + Common use case: Press the Lock Button on a iOS device and you get disconnected immediately. + + + + Can be used to return to a room quickly by directly reconnecting to a game server to rejoin a room. + + + Rejoining room will not send any player properties. Instead client will receive up-to-date ones from server. + If you want to set new player properties, do it once rejoined. + + False, if the conditions are not met. Then, this client does not attempt the ReconnectAndRejoin. + + + Disconnects the peer from a server or stays disconnected. If the client / peer was connected, a callback will be triggered. + + Disconnect will attempt to notify the server of the client closing the connection. + + Clients that are in a room, will leave the room. If the room's playerTTL > 0, the player will just become inactive (and may rejoin). + + This method will not change the current State, if this client State is PeerCreated, Disconnecting or Disconnected. + In those cases, there is also no callback for the disconnect. The DisconnectedCause will only change if the client was connected. + + + + Disconnects the client / peer from a server or stays disconnected. Internal method that sets the DisconnectedCause as well. + + + + Private Disconnect variant that sets the state, too. + + + + + Useful to test loss of connection which will end in a client timeout. This modifies LoadBalancingPeer.NetworkSimulationSettings. Read remarks. + + + Use with care as this sets LoadBalancingPeer.IsSimulationEnabled.
+ Read LoadBalancingPeer.IsSimulationEnabled to check if this is on or off, if needed.
+ + If simulateTimeout is true, LoadBalancingPeer.NetworkSimulationSettings.IncomingLossPercentage and + LoadBalancingPeer.NetworkSimulationSettings.OutgoingLossPercentage will be set to 100.
+ Obviously, this overrides any network simulation settings done before.
+ + If you want fine-grained network simulation control, use the NetworkSimulationSettings.
+ + The timeout will lead to a call to , as usual in a client timeout. + + You could modify this method (or use NetworkSimulationSettings) to deliberately run into a server timeout by + just setting the OutgoingLossPercentage = 100 and the IncomingLossPercentage = 0. +
+ If true, a connection loss is simulated. If false, the simulation ends. +
+ + + This method dispatches all available incoming commands and then sends this client's outgoing commands. + It uses DispatchIncomingCommands and SendOutgoingCommands to do that. + + + The Photon client libraries are designed to fit easily into a game or application. The application + is in control of the context (thread) in which incoming events and responses are executed and has + full control of the creation of UDP/TCP packages. + + Sending packages and dispatching received messages are two separate tasks. Service combines them + into one method at the cost of control. It calls DispatchIncomingCommands and SendOutgoingCommands. + + Call this method regularly (10..50 times a second). + + This will Dispatch ANY received commands (unless a reliable command in-order is still missing) and + events AND will send queued outgoing commands. Fewer calls might be more effective if a device + cannot send many packets per second, as multiple operations might be combined into one package. + + + You could replace Service by: + + while (DispatchIncomingCommands()); //Dispatch until everything is Dispatched... + SendOutgoingCommands(); //Send a UDP/TCP package with outgoing messages + + + + + + + While on the NameServer, this gets you the list of regional servers (short names and their IPs to ping them). + + If the operation could be sent. If false, no operation was sent (e.g. while not connected to the NameServer). + + + + Request the rooms and online status for a list of friends. All clients should set a unique UserId before connecting. The result is available in this.FriendList. + + + Used on Master Server to find the rooms played by a selected list of users. + The result will be stored in LoadBalancingClient.FriendList, which is null before the first server response. + + Users identify themselves by setting a UserId in the LoadBalancingClient instance. + This will send the ID in OpAuthenticate during connect (to master and game servers). + Note: Changing a player's name doesn't make sense when using a friend list. + + The list of usernames must be fetched from some other source (not provided by Photon). + + + Internal:
+ The server response includes 2 arrays of info (each index matching a friend from the request):
+ ParameterCode.FindFriendsResponseOnlineList = bool[] of online states
+ ParameterCode.FindFriendsResponseRoomIdList = string[] of room names (empty string if not in a room)
+
+ The options may be used to define which state a room must match to be returned. +
+ Array of friend's names (make sure they are unique). + Options that affect the result of the FindFriends operation. + If the operation could be sent (requires connection). +
+ + If already connected to a Master Server, this joins the specified lobby. This request triggers an OnOperationResponse() call and the callback OnJoinedLobby(). + The lobby to join. Use null for default lobby. + If the operation could be sent. False, if the client is not IsConnectedAndReady or when it's not connected to a Master Server. + + + Opposite of joining a lobby. You don't have to explicitly leave a lobby to join another (client can be in one max, at any time). + If the operation could be sent (has to be connected). + + + + Joins a random room that matches the filter. Will callback: OnJoinedRoom or OnJoinRandomFailed. + + + Used for random matchmaking. You can join any room or one with specific properties defined in opJoinRandomRoomParams. + + You can use expectedCustomRoomProperties and expectedMaxPlayers as filters for accepting rooms. + If you set expectedCustomRoomProperties, a room must have the exact same key values set at Custom Properties. + You need to define which Custom Room Properties will be available for matchmaking when you create a room. + See: OpCreateRoom(string roomName, RoomOptions roomOptions, TypedLobby lobby) + + This operation fails if no rooms are fitting or available (all full, closed or not visible). + It may also fail when actually joining the room which was found. Rooms may close, become full or empty anytime. + + This method can only be called while the client is connected to a Master Server so you should + implement the callback OnConnectedToMaster. + Check the return value to make sure the operation will be called on the server. + Note: There will be no callbacks if this method returned false. + + + This client's State is set to ClientState.Joining immediately, when the operation could + be called. In the background, the client will switch servers and call various related operations. + + When you're in the room, this client's State will become ClientState.Joined. + + + When entering a room, this client's Player Custom Properties will be sent to the room. + Use LocalPlayer.SetCustomProperties to set them, even while not yet in the room. + Note that the player properties will be cached locally and are not wiped when leaving a room. + + More about matchmaking: + https://doc.photonengine.com/en-us/realtime/current/reference/matchmaking-and-lobby + + You can define an array of expectedUsers, to block player slots in the room for these users. + The corresponding feature in Photon is called "Slot Reservation" and can be found in the doc pages. + + Optional definition of properties to filter rooms in random matchmaking. + If the operation could be sent currently (requires connection to Master Server). + + + + Attempts to join a room that matches the specified filter and creates a room if none found. + + + This operation is a combination of filter-based random matchmaking with the option to create a new room, + if no fitting room exists. + The benefit of that is that the room creation is done by the same operation and the room can be found + by the very next client, looking for similar rooms. + + There are separate parameters for joining and creating a room. + + Tickets: Both parameter types have a Ticket value. It is enough to set the opJoinRandomRoomParams.Ticket. + The createRoomParams.Ticket will not be used. + + This method can only be called while connected to a Master Server. + This client's State is set to ClientState.Joining immediately. + + For success, IMatchmakingCallbacks.OnJoinedRoom or IMatchmakingCallbacks.OnCreatedRoom get called. + In error cases IMatchmakingCallbacks.OnJoinRoomFailed or IMatchmakingCallbacks.OnJoinRandomRoomFailed get called. + + More about matchmaking: + https://doc.photonengine.com/en-us/realtime/current/reference/matchmaking-and-lobby + + Check the return value to make sure the operation will be called on the server. + Note: There will be no callbacks if this method returned false. + + If the operation will be sent (requires connection to Master Server). + + + + Creates a new room. Will callback: OnCreatedRoom and OnJoinedRoom or OnCreateRoomFailed. + + + When successful, the client will enter the specified room and callback both OnCreatedRoom and OnJoinedRoom. + In all error cases, OnCreateRoomFailed gets called. + + Creating a room will fail if the room name is already in use or when the RoomOptions clashing + with one another. Check the EnterRoomParams reference for the various room creation options. + + + This method can only be called while the client is connected to a Master Server so you should + implement the callback OnConnectedToMaster. + Check the return value to make sure the operation will be called on the server. + Note: There will be no callbacks if this method returned false. + + + When you're in the room, this client's State will become ClientState.Joined. + + + When entering a room, this client's Player Custom Properties will be sent to the room. + Use LocalPlayer.SetCustomProperties to set them, even while not yet in the room. + Note that the player properties will be cached locally and are not wiped when leaving a room. + + You can define an array of expectedUsers, to block player slots in the room for these users. + The corresponding feature in Photon is called "Slot Reservation" and can be found in the doc pages. + + Definition of properties for the room to create. + If the operation could be sent currently (requires connection to Master Server). + + + + Joins a specific room by name and creates it on demand. Will callback: OnJoinedRoom or OnJoinRoomFailed. + + + Useful when players make up a room name to meet in: + All involved clients call the same method and whoever is first, also creates the room. + + When successful, the client will enter the specified room. + The client which creates the room, will callback both OnCreatedRoom and OnJoinedRoom. + Clients that join an existing room will only callback OnJoinedRoom. + In all error cases, OnJoinRoomFailed gets called. + + Joining a room will fail, if the room is full, closed or when the user + already is present in the room (checked by userId). + + To return to a room, use OpRejoinRoom. + + This method can only be called while the client is connected to a Master Server so you should + implement the callback OnConnectedToMaster. + Check the return value to make sure the operation will be called on the server. + Note: There will be no callbacks if this method returned false. + + This client's State is set to ClientState.Joining immediately, when the operation could + be called. In the background, the client will switch servers and call various related operations. + + When you're in the room, this client's State will become ClientState.Joined. + + + If you set room properties in roomOptions, they get ignored when the room is existing already. + This avoids changing the room properties by late joining players. + + When entering a room, this client's Player Custom Properties will be sent to the room. + Use LocalPlayer.SetCustomProperties to set them, even while not yet in the room. + Note that the player properties will be cached locally and are not wiped when leaving a room. + + You can define an array of expectedUsers, to block player slots in the room for these users. + The corresponding feature in Photon is called "Slot Reservation" and can be found in the doc pages. + + Definition of properties for the room to create or join. + If the operation could be sent currently (requires connection to Master Server). + + + + Joins a room by name. Will callback: OnJoinedRoom or OnJoinRoomFailed. + + + Useful when using lobbies or when players follow friends or invite each other. + + When successful, the client will enter the specified room and callback via OnJoinedRoom. + In all error cases, OnJoinRoomFailed gets called. + + Joining a room will fail if the room is full, closed, not existing or when the user + already is present in the room (checked by userId). + + To return to a room, use OpRejoinRoom. + When players invite each other and it's unclear who's first to respond, use OpJoinOrCreateRoom instead. + + This method can only be called while the client is connected to a Master Server so you should + implement the callback OnConnectedToMaster. + Check the return value to make sure the operation will be called on the server. + Note: There will be no callbacks if this method returned false. + + A room's name has to be unique (per region, appid and gameversion). + When your title uses a global matchmaking or invitations (e.g. an external solution), + keep regions and the game versions in mind to join a room. + + + This client's State is set to ClientState.Joining immediately, when the operation could + be called. In the background, the client will switch servers and call various related operations. + + When you're in the room, this client's State will become ClientState.Joined. + + + When entering a room, this client's Player Custom Properties will be sent to the room. + Use LocalPlayer.SetCustomProperties to set them, even while not yet in the room. + Note that the player properties will be cached locally and are not wiped when leaving a room. + + You can define an array of expectedUsers, to reserve player slots in the room for friends or party members. + The corresponding feature in Photon is called "Slot Reservation" and can be found in the doc pages. + + Definition of properties for the room to join. + If the operation could be sent currently (requires connection to Master Server). + + + + Rejoins a room by roomName (using the userID internally to return). Will callback: OnJoinedRoom or OnJoinRoomFailed. + + + Used to return to a room, before this user was removed from the players list. + Internally, the userID will be checked by the server, to make sure this user is in the room (active or inactice). + + In contrast to join, this operation never adds a players to a room. It will attempt to retake an existing + spot in the playerlist or fail. This makes sure the client doesn't accidentally join a room when the + game logic meant to re-activate an existing actor in an existing room. + + This method will fail on the server, when the room does not exist, can't be loaded (persistent rooms) or + when the userId is not in the player list of this room. This will lead to a callback OnJoinRoomFailed. + + Rejoining room will not send any player properties. Instead client will receive up-to-date ones from server. + If you want to set new player properties, do it once rejoined. + + Tickets: If the server requires use of Tickets or if the room was entered with a Ticket initially, + you will have to provide a ticket as argument. + + + + + Leaves the current room, optionally telling the server that the user is just becoming inactive. Will callback: OnLeftRoom. + + + + OpLeaveRoom skips execution when the room is null or the server is not GameServer or the client is disconnecting from GS already. + OpLeaveRoom returns false in those cases and won't change the state, so check return of this method. + + In some cases, this method will skip the OpLeave call and just call Disconnect(), + which not only leaves the room but also the server. Disconnect also triggers a leave and so that workflow is is quicker. + + If true, this player becomes inactive in the game and can return later (if PlayerTTL of the room is != 0). + WebFlag: Securely transmit the encrypted object AuthCookie to the web service in PathLeave webhook when available + If the current room could be left (impossible while not in a room). + + + Gets a list of rooms matching the (non empty) SQL filter for the given SQL-typed lobby. + + Operation is only available for lobbies of type SqlLobby and the filter can not be empty. + It will check those conditions and fail locally, returning false. + + This is an async request which triggers a OnOperationResponse() call. + + + The lobby to query. Has to be of type SqlLobby. + The sql query statement. + If the operation could be sent (has to be connected). + + + + Updates and synchronizes a Player's Custom Properties. Optionally, expectedProperties can be provided as condition. + + + Custom Properties are a set of string keys and arbitrary values which is synchronized + for the players in a Room. They are available when the client enters the room, as + they are in the response of OpJoin and OpCreate. + + Custom Properties either relate to the (current) Room or a Player (in that Room). + + Both classes locally cache the current key/values and make them available as + property: CustomProperties. This is provided only to read them. + You must use the method SetCustomProperties to set/modify them. + + Any client can set any Custom Properties anytime (when in a room). + It's up to the game logic to organize how they are best used. + + You should call SetCustomProperties only with key/values that are new or changed. This reduces + traffic and performance. + + Unless you define some expectedProperties, setting key/values is always permitted. + In this case, the property-setting client will not receive the new values from the server but + instead update its local cache in SetCustomProperties. + + If you define expectedProperties, the server will skip updates if the server property-cache + does not contain all expectedProperties with the same values. + In this case, the property-setting client will get an update from the server and update it's + cached key/values at about the same time as everyone else. + + The benefit of using expectedProperties can be only one client successfully sets a key from + one known value to another. + As example: Store who owns an item in a Custom Property "ownedBy". It's 0 initally. + When multiple players reach the item, they all attempt to change "ownedBy" from 0 to their + actorNumber. If you use expectedProperties {"ownedBy", 0} as condition, the first player to + take the item will have it (and the others fail to set the ownership). + + Properties get saved with the game state for Turnbased games (which use IsPersistent = true). + + Defines which player the Custom Properties belong to. ActorID of a player. + Hashtable of Custom Properties that changes. + Provide some keys/values to use as condition for setting the new values. Client must be in room. + Defines if the set properties should be forwarded to a WebHook. Client must be in room. + + False if propertiesToSet is null or empty or have zero string keys. + If not in a room, returns true if local player and expectedProperties and webFlags are null. + False if actorNr is lower than or equal to zero. + Otherwise, returns if the operation could be sent to the server. + + + + Internally used to cache and set properties (including well known properties). + Requires being in a room (because this attempts to send an operation which will fail otherwise). + + + + Updates and synchronizes this Room's Custom Properties. Optionally, expectedProperties can be provided as condition. + + + Custom Properties are a set of string keys and arbitrary values which is synchronized + for the players in a Room. They are available when the client enters the room, as + they are in the response of OpJoin and OpCreate. + + Custom Properties either relate to the (current) Room or a Player (in that Room). + + Both classes locally cache the current key/values and make them available as + property: CustomProperties. This is provided only to read them. + You must use the method SetCustomProperties to set/modify them. + + Any client can set any Custom Properties anytime (when in a room). + It's up to the game logic to organize how they are best used. + + You should call SetCustomProperties only with key/values that are new or changed. This reduces + traffic and performance. + + Unless you define some expectedProperties, setting key/values is always permitted. + In this case, the property-setting client will not receive the new values from the server but + instead update its local cache in SetCustomProperties. + + If you define expectedProperties, the server will skip updates if the server property-cache + does not contain all expectedProperties with the same values. + In this case, the property-setting client will get an update from the server and update it's + cached key/values at about the same time as everyone else. + + The benefit of using expectedProperties can be only one client successfully sets a key from + one known value to another. + As example: Store who owns an item in a Custom Property "ownedBy". It's 0 initally. + When multiple players reach the item, they all attempt to change "ownedBy" from 0 to their + actorNumber. If you use expectedProperties {"ownedBy", 0} as condition, the first player to + take the item will have it (and the others fail to set the ownership). + + Properties get saved with the game state for Turnbased games (which use IsPersistent = true). + + Hashtable of Custom Properties that changes. + Provide some keys/values to use as condition for setting the new values. + Defines web flags for an optional PathProperties webhook. + + False if propertiesToSet is null or empty or have zero string keys. + Otherwise, returns if the operation could be sent to the server. + + + + Internally used to cache and set properties (including well known properties). + Requires being in a room (because this attempts to send an operation which will fail otherwise). + + + + Send an event with custom code/type and any content to the other players in the same room. + + Identifies this type of event (and the content). Your game's event codes can start with 0. + Any serializable datatype (including Hashtable like the other OpRaiseEvent overloads). + Contains used send options. If you pass null, the default options will be used. + Send options for reliable, encryption etc + If operation could be enqueued for sending. Sent when calling: Service or SendOutgoingCommands. + + + + Operation to handle this client's interest groups (for events in room). + + + Note the difference between passing null and byte[0]: + null won't add/remove any groups. + byte[0] will add/remove all (existing) groups. + First, removing groups is executed. This way, you could leave all groups and join only the ones provided. + + Changes become active not immediately but when the server executes this operation (approximately RTT/2). + + Groups to remove from interest. Null will not remove any. A byte[0] will remove all. + Groups to add to interest. Null will not add any. A byte[0] will add all current. + If operation could be enqueued for sending. Sent when calling: Service or SendOutgoingCommands. + + + + Privately used to read-out properties coming from the server in events and operation responses (which might be a bit tricky). + + + + + Privately used only to read properties for a distinct actor (which might be the hashtable OR a key-pair value IN the actorProperties). + + + + Internally used to set the LocalPlayer's actorNumber (from -1 to the actual in-room value) and optionally the userId. + New actorNr assigned when joining a room. + Set true for offline mode. If true the player.UserId is set to this.AuthValues.UserId or a new GUID (mimicking online mode). + + + + Called internally, when a game was joined or created on the game server successfully. + + + This reads the response, finds out the local player's actorNumber (a.k.a. Player.ID) and applies properties of the room and players. + Errors for these operations are to be handled before this method is called. + + Contains the server's response for an operation called by this peer. + + + + Factory method to create a player instance - override to get your own player-type with custom features. + + The name of the player to be created. + The player ID (a.k.a. actorNumber) of the player to be created. + Sets the distinction if the player to be created is your player or if its assigned to someone else. + The custom properties for this new player + The newly created player + + + Internal "factory" method to create a room-instance. + + + Debug output of low level api (and this client). + This method is not responsible to keep up the state of a LoadBalancingClient. Calling base.DebugReturn on overrides is optional. + + + + Uses the OperationResponses provided by the server to advance the internal state and call ops as needed. + + + When this method finishes, it will call your OnOpResponseAction (if any). This way, you can get any + operation response without overriding this class. + + To implement a more complex game/app logic, you should implement your own class that inherits the + LoadBalancingClient. Override this method to use your own operation-responses easily. + + This method is essential to update the internal state of a LoadBalancingClient, so overriding methods + must call base.OnOperationResponse(). + + Contains the server's response for an operation called by this peer. + + + + Uses the connection's statusCodes to advance the internal state and call operations as needed. + + This method is essential to update the internal state of a LoadBalancingClient. Overriding methods must call base.OnStatusChanged. + + + + Uses the photonEvent's provided by the server to advance the internal state and call ops as needed. + + This method is essential to update the internal state of a LoadBalancingClient. Overriding methods must call base.OnEvent. + + + In Photon 4, "raw messages" will get their own callback method in the interface. Not used yet. + + + A callback of the RegionHandler, provided in OnRegionListReceived. + The regionHandler wraps up best region and other region relevant info. + + + + This operation makes Photon call your custom web-service by path/name with the given parameters (converted into Json). + Use as a callback. + + + A WebRPC calls a custom, http-based function on a server you provide. The uriPath is relative to a "base path" + which is configured server-side. The sent parameters get converted from C# types to Json. Vice versa, the response + of the web-service will be converted to C# types and sent back as normal operation response. + + To use this feature, you have to setup your server: + + For a Photon Cloud application, + visit the Dashboard and setup "WebHooks". The BaseUrl is used for WebRPCs as well. + + The class is a helper-class that extracts the most valuable content from the WebRPC + response. + + The url path to call, relative to the baseUrl configured on Photon's server-side. + The parameters to send to the web-service method. + Defines if the authentication cookie gets sent to a WebHook (if setup). + + + + Registers an object for callbacks for the implemented callback-interfaces. + + + Adding and removing callback targets is queued to not mess with callbacks in execution. + Internally, this means that the addition/removal is done before the LoadBalancingClient + calls the next callbacks. This detail should not affect a game's workflow. + + The covered callback interfaces are: IConnectionCallbacks, IMatchmakingCallbacks, + ILobbyCallbacks, IInRoomCallbacks, IOnEventCallback and IWebRpcCallback. + + See: DotNet Callbacks + + The object that registers to get callbacks from this client. + + + + Unregisters an object from callbacks for the implemented callback-interfaces. + + + Adding and removing callback targets is queued to not mess with callbacks in execution. + Internally, this means that the addition/removal is done before the LoadBalancingClient + calls the next callbacks. This detail should not affect a game's workflow. + + The covered callback interfaces are: IConnectionCallbacks, IMatchmakingCallbacks, + ILobbyCallbacks, IInRoomCallbacks, IOnEventCallback and IWebRpcCallback. + + See: + + The object that unregisters from getting callbacks. + + + + Applies queued callback cahnges from a queue to the actual containers. Will cause exceptions if used while callbacks execute. + + + There is no explicit check that this is not called during callbacks, however the implemented, private logic takes care of this. + + + + Helper method to cast and apply a target per (interface) type. + Either of the interfaces for callbacks. + The queued change to apply (add or remove) some target. + The container that calls callbacks on it's list of targets. + + + + Collection of "organizational" callbacks for the Realtime Api to cover: Connection and Regions. + + + Classes that implement this interface must be registered to get callbacks for various situations. + + To register for callbacks, call and pass the class implementing this interface + To stop getting callbacks, call and pass the class implementing this interface + + + \ingroup callbacks + + + + Called to signal that the "low level connection" got established but before the client can call operation on the server. + + + After the (low level transport) connection is established, the client will automatically send + the Authentication operation, which needs to get a response before the client can call other operations. + + Your logic should wait for either: OnRegionListReceived or OnConnectedToMaster. + + This callback is useful to detect if the server can be reached at all (technically). + Most often, it's enough to implement OnDisconnected(DisconnectCause cause) and check for the cause. + + This is not called for transitions from the masterserver to game servers. + + + + + Called when the client is connected to the Master Server and ready for matchmaking and other tasks. + + + The list of available rooms won't become available unless you join a lobby via LoadBalancingClient.OpJoinLobby. + You can join rooms and create them even without being in a lobby. The default lobby is used in that case. + + + + + Called after disconnecting from the Photon server. It could be a failure or an explicit disconnect call + + + The reason for this disconnect is provided as DisconnectCause. + + + + + Called when the Name Server provided a list of regions for your title. + + + This callback is called as soon as the list is available. No pings were sent for Best Region selection yet. + If the client is set to connect to the Best Region (lowest ping), one or more regions get pinged. + Not all regions are pinged. As soon as the results are final, the client will connect to the best region, + so you can check the ping results when connected to the Master Server. + + Check the RegionHandler class description, to make use of the provided values. + + The currently used RegionHandler. + + + + Called when your Custom Authentication service responds with additional data. + + + Custom Authentication services can include some custom data in their response. + When present, that data is made available in this callback as Dictionary. + While the keys of your data have to be strings, the values can be either string or a number (in Json). + You need to make extra sure, that the value type is the one you expect. Numbers become (currently) int64. + + Example: void OnCustomAuthenticationResponse(Dictionary<string, object> data) { ... } + + + + + + Called when the custom authentication failed. Followed by disconnect! + + + Custom Authentication can fail due to user-input, bad tokens/secrets. + If authentication is successful, this method is not called. Implement OnJoinedLobby() or OnConnectedToMaster() (as usual). + + During development of a game, it might also fail due to wrong configuration on the server side. + In those cases, logging the debugMessage is very important. + + Unless you setup a custom authentication service for your app (in the [Dashboard](https://dashboard.photonengine.com)), + this won't be called! + + Contains a debug message why authentication failed. This has to be fixed during development. + + + + Collection of "organizational" callbacks for the Realtime Api to cover the Lobby. + + + Classes that implement this interface must be registered to get callbacks for various situations. + + To register for callbacks, call and pass the class implementing this interface + To stop getting callbacks, call and pass the class implementing this interface + + + \ingroup callbacks + + + + Called on entering a lobby on the Master Server. The actual room-list updates will call OnRoomListUpdate. + + + While in the lobby, the roomlist is automatically updated in fixed intervals (which you can't modify in the public cloud). + The room list gets available via OnRoomListUpdate. + + + + + Called after leaving a lobby. + + + When you leave a lobby, [OpCreateRoom](@ref OpCreateRoom) and [OpJoinRandomRoom](@ref OpJoinRandomRoom) + automatically refer to the default lobby. + + + + + Called for any update of the room-listing while in a lobby (InLobby) on the Master Server. + + + Each item is a RoomInfo which might include custom properties (provided you defined those as lobby-listed when creating a room). + Not all types of lobbies provide a listing of rooms to the client. Some are silent and specialized for server-side matchmaking. + + The list is sorted using two criteria: open or closed, full or not. So the list is composed of three groups, in this order: + + first group: open and not full (joinable).
+ second group: full but not closed (not joinable).
+ third group: closed (not joinable, could be full or not).
+ + In each group, entries do not have any particular order (random). + + The list of rooms (or rooms' updates) is also limited in number, see Lobby Limits. +
+
+ + + Called when the Master Server sent an update for the Lobby Statistics. + + + This callback has two preconditions: + EnableLobbyStatistics must be set to true, before this client connects. + And the client has to be connected to the Master Server, which is providing the info about lobbies. + + + + + Collection of "organizational" callbacks for the Realtime Api to cover Matchmaking. + + + Classes that implement this interface must be registered to get callbacks for various situations. + + To register for callbacks, call and pass the class implementing this interface + To stop getting callbacks, call and pass the class implementing this interface + + + \ingroup callbacks + + + + Called when the server sent the response to a FindFriends request. + + + After calling OpFindFriends, the Master Server will cache the friend list and send updates to the friend + list. The friends includes the name, userId, online state and the room (if any) for each requested user/friend. + + Use the friendList to update your UI and store it, if the UI should highlight changes. + + + + + Called when this client created a room and entered it. OnJoinedRoom() will be called as well. + + + This callback is only called on the client which created a room (see OpCreateRoom). + + As any client might close (or drop connection) anytime, there is a chance that the + creator of a room does not execute OnCreatedRoom. + + If you need specific room properties or a "start signal", implement OnMasterClientSwitched() + and make each new MasterClient check the room's state. + + + + + Called when the server couldn't create a room (OpCreateRoom failed). + + + Creating a room may fail for various reasons. Most often, the room already exists (roomname in use) or + the RoomOptions clash and it's impossible to create the room. + + When creating a room fails on a Game Server: + The client will cache the failure internally and returns to the Master Server before it calls the fail-callback. + This way, the client is ready to find/create a room at the moment of the callback. + In this case, the client skips calling OnConnectedToMaster but returning to the Master Server will still call OnConnected. + Treat callbacks of OnConnected as pure information that the client could connect. + + Operation ReturnCode from the server. + Debug message for the error. + + + + Called when the LoadBalancingClient entered a room, no matter if this client created it or simply joined. + + + When this is called, you can access the existing players in Room.Players, their custom properties and Room.CustomProperties. + + In this callback, you could create player objects. For example in Unity, instantiate a prefab for the player. + + If you want a match to be started "actively", enable the user to signal "ready" (using OpRaiseEvent or a Custom Property). + + + + + Called when a previous OpJoinRoom call failed on the server. + + + Joining a room may fail for various reasons. Most often, the room is full or does not exist anymore + (due to someone else being faster or closing the room). + + When joining a room fails on a Game Server: + The client will cache the failure internally and returns to the Master Server before it calls the fail-callback. + This way, the client is ready to find/create a room at the moment of the callback. + In this case, the client skips calling OnConnectedToMaster but returning to the Master Server will still call OnConnected. + Treat callbacks of OnConnected as pure information that the client could connect. + + Operation ReturnCode from the server. + Debug message for the error. + + + + Called when a previous OpJoinRandom (or OpJoinRandomOrCreateRoom etc.) call failed on the server. + + + The most common causes are that a room is full or does not exist (due to someone else being faster or closing the room). + + This operation is only ever sent to the Master Server. Once a room is found by the Master Server, the client will + head off to the designated Game Server and use the operation Join on the Game Server. + + When using multiple lobbies (via OpJoinLobby or a TypedLobby parameter), another lobby might have more/fitting rooms.
+
+ Operation ReturnCode from the server. + Debug message for the error. +
+ + + Called when the local user/client left a room, so the game's logic can clean up it's internal state. + + + When leaving a room, the LoadBalancingClient will disconnect the Game Server and connect to the Master Server. + This wraps up multiple internal actions. + + Wait for the callback OnConnectedToMaster, before you use lobbies and join or create rooms. + + OnLeftRoom also gets called, when the application quits. + It makes sense to check static ConnectionHandler.AppQuits before loading scenes in OnLeftRoom(). + + + + + Collection of "in room" callbacks for the Realtime Api to cover: Players entering or leaving, property updates and Master Client switching. + + + Classes that implement this interface must be registered to get callbacks for various situations. + + To register for callbacks, call and pass the class implementing this interface + To stop getting callbacks, call and pass the class implementing this interface + + + \ingroup callbacks + + + + Called when a remote player entered the room. This Player is already added to the playerlist. + + + If your game starts with a certain number of players, this callback can be useful to check the + Room.playerCount and find out if you can start. + + + + + Called when a remote player left the room or became inactive. Check otherPlayer.IsInactive. + + + If another player leaves the room or if the server detects a lost connection, this callback will + be used to notify your game logic. + + Depending on the room's setup, players may become inactive, which means they may return and retake + their spot in the room. In such cases, the Player stays in the Room.Players dictionary. + + If the player is not just inactive, it gets removed from the Room.Players dictionary, before + the callback is called. + + + + + Called when room properties changed. The propertiesThatChanged contain only the keys that changed. + + + In most cases, this method gets called when some player changes the Room Properties. + However, there are also "Well Known Properties" (which use byte keys) and this callback may include them. + Especially when entering a room, the server will also send the required Well Known Properties and they + are not filtered out for the OnRoomPropertiesUpdate callback. + + You can safely ignore the byte typed keys in propertiesThatChanged. + + Changing properties is usually done by Room.SetCustomProperties. + + + + + + Called when custom player-properties are changed. + + + Changing properties must be done by Player.SetCustomProperties, which causes this callback locally, too. + + Contains Player that changed. + Contains the properties that changed. + + + + Called after switching to a new MasterClient when the current one leaves. + + + This is not called when this client enters a room. + The former MasterClient is still in the player list when this method get called. + + + + + Event callback for the Realtime Api. Covers events from the server and those sent by clients via OpRaiseEvent. + + + Classes that implement this interface must be registered to get callbacks for various situations. + + To register for callbacks, call and pass the class implementing this interface + To stop getting callbacks, call and pass the class implementing this interface + + + \ingroup callbacks + + + Called for any incoming events. + + To receive events, implement IOnEventCallback in any class and register it via AddCallbackTarget + (either in LoadBalancingClient or PhotonNetwork). + + With the EventData.Sender you can look up the Player who sent the event. + + It is best practice to assign an eventCode for each different type of content and action, so the Code + will be essential to read the incoming events. + + + + + Interface for "WebRpc" callbacks for the Realtime Api. Currently includes only responses for Web RPCs. + + + Classes that implement this interface must be registered to get callbacks for various situations. + + To register for callbacks, call and pass the class implementing this interface + To stop getting callbacks, call and pass the class implementing this interface + + + \ingroup callbacks + + + + Called when the response to a WebRPC is available. See . + + + Important: The response.ReturnCode is 0 if Photon was able to reach your web-service.
+ The content of the response is what your web-service sent. You can create a WebRpcResponse from it.
+ Example: WebRpcResponse webResponse = new WebRpcResponse(operationResponse);
+ + Please note: Class OperationResponse is in a namespace which needs to be "used":
+ using ExitGames.Client.Photon; // includes OperationResponse (and other classes) +
+ + public void OnWebRpcResponse(OperationResponse response) + { + Debug.LogFormat("WebRPC operation response {0}", response.ToStringFull()); + switch (response.ReturnCode) + { + case ErrorCode.Ok: + WebRpcResponse webRpcResponse = new WebRpcResponse(response); + Debug.LogFormat("Parsed WebRPC response {0}", response.ToStringFull()); + if (string.IsNullOrEmpty(webRpcResponse.Name)) + { + Debug.LogError("Unexpected: WebRPC response did not contain WebRPC method name"); + } + if (webRpcResponse.ResultCode == 0) // success + { + switch (webRpcResponse.Name) + { + // todo: add your code here + case GetGameListWebRpcMethodName: // example + // ... + break; + } + } + else if (webRpcResponse.ResultCode == -1) + { + Debug.LogErrorFormat("Web server did not return ResultCode for WebRPC method=\"{0}\", Message={1}", webRpcResponse.Name, webRpcResponse.Message); + } + else + { + Debug.LogErrorFormat("Web server returned ResultCode={0} for WebRPC method=\"{1}\", Message={2}", webRpcResponse.ResultCode, webRpcResponse.Name, webRpcResponse.Message); + } + break; + case ErrorCode.ExternalHttpCallFailed: // web service unreachable + Debug.LogErrorFormat("WebRPC call failed as request could not be sent to the server. {0}", response.DebugMessage); + break; + case ErrorCode.HttpLimitReached: // too many WebRPCs in a short period of time + // the debug message should contain the limit exceeded + Debug.LogErrorFormat("WebRPCs rate limit exceeded: {0}", response.DebugMessage); + break; + case ErrorCode.InvalidOperation: // WebRPC not configured at all OR not configured properly OR trying to send on name server + if (PhotonNetwork.Server == ServerConnection.NameServer) + { + Debug.LogErrorFormat("WebRPC not supported on NameServer. {0}", response.DebugMessage); + } + else + { + Debug.LogErrorFormat("WebRPC not properly configured or not configured at all. {0}", response.DebugMessage); + } + break; + default: + // other unknown error, unexpected + Debug.LogErrorFormat("Unexpected error, {0} {1}", response.ReturnCode, response.DebugMessage); + break; + } + } + + +
+ + + Interface for event callback for the Realtime Api. + + + Classes that implement this interface must be registered to get callbacks for various situations. + + To register for callbacks, call and pass the class implementing this interface + To stop getting callbacks, call and pass the class implementing this interface + + + \ingroup callbacks + + + + Called when the client receives an event from the server indicating that an error happened there. + + + In most cases this could be either: + 1. an error from webhooks plugin (if HasErrorInfo is enabled), read more here: + https://doc.photonengine.com/en-us/realtime/current/gameplay/web-extensions/webhooks#options + 2. an error sent from a custom server plugin via PluginHost.BroadcastErrorInfoEvent, see example here: + https://doc.photonengine.com/en-us/server/current/plugins/manual#handling_http_response + 3. an error sent from the server, for example, when the limit of cached events has been exceeded in the room + (all clients will be disconnected and the room will be closed in this case) + read more here: https://doc.photonengine.com/en-us/realtime/current/gameplay/cached-events#special_considerations + + If you implement or you will also get this event. + + Object containing information about the error + + + + Container type for callbacks defined by IConnectionCallbacks. See LoadBalancingCallbackTargets. + + + While the interfaces of callbacks wrap up the methods that will be called, + the container classes implement a simple way to call a method on all registered objects. + + + + + Container type for callbacks defined by IMatchmakingCallbacks. See MatchMakingCallbackTargets. + + + While the interfaces of callbacks wrap up the methods that will be called, + the container classes implement a simple way to call a method on all registered objects. + + + + + Container type for callbacks defined by IInRoomCallbacks. See InRoomCallbackTargets. + + + While the interfaces of callbacks wrap up the methods that will be called, + the container classes implement a simple way to call a method on all registered objects. + + + + + Container type for callbacks defined by ILobbyCallbacks. See LobbyCallbackTargets. + + + While the interfaces of callbacks wrap up the methods that will be called, + the container classes implement a simple way to call a method on all registered objects. + + + + + Container type for callbacks defined by IWebRpcCallback. See WebRpcCallbackTargets. + + + While the interfaces of callbacks wrap up the methods that will be called, + the container classes implement a simple way to call a method on all registered objects. + + + + + Container type for callbacks defined by . See . + + + While the interfaces of callbacks wrap up the methods that will be called, + the container classes implement a simple way to call a method on all registered objects. + + + + + Class wrapping the received event. + + + This is passed inside callback. + If you implement or you will also get but not parsed. + + In most cases this could be either: + 1. an error from webhooks plugin (if HasErrorInfo is enabled), read more here: + https://doc.photonengine.com/en-us/realtime/current/gameplay/web-extensions/webhooks#options + 2. an error sent from a custom server plugin via PluginHost.BroadcastErrorInfoEvent, see example here: + https://doc.photonengine.com/en-us/server/current/plugins/manual#handling_http_response + 3. an error sent from the server, for example, when the limit of cached events has been exceeded in the room + (all clients will be disconnected and the room will be closed in this case) + read more here: https://doc.photonengine.com/en-us/realtime/current/gameplay/cached-events#special_considerations + + + + + String containing information about the error. + + + + + A LoadBalancingPeer provides the operations and enum definitions needed to use the LoadBalancing server application which is also used in Photon Cloud. + + + This class is internally used. + The LoadBalancingPeer does not keep a state, instead this is done by a LoadBalancingClient. + + + + Obsolete accessor to the RegionHandler.PingImplementation. + + + + Creates a Peer with specified connection protocol. You need to set the Listener before using the peer. + + Each connection protocol has it's own default networking ports for Photon. + The preferred option is UDP. + + + + Creates a Peer with specified connection protocol and a Listener for callbacks. + + + + + Joins the lobby on the Master Server, where you get a list of RoomInfos of currently open rooms. + This is an async request which triggers a OnOperationResponse() call. + + The lobby join to. + If the operation could be sent (has to be connected). + + + + Leaves the lobby on the Master Server. + This is an async request which triggers a OnOperationResponse() call. + + If the operation could be sent (requires connection). + + + Used by OpJoinRoom and by OpCreateRoom alike. + + + + Creates a room (on either Master or Game Server). + The OperationResponse depends on the server the peer is connected to: + Master will return a Game Server to connect to. + Game Server will return the joined Room's data. + This is an async request which triggers a OnOperationResponse() call. + + + If the room is already existing, the OperationResponse will have a returnCode of ErrorCode.GameAlreadyExists. + + + + + Joins a room by name or creates new room if room with given name not exists. + The OperationResponse depends on the server the peer is connected to: + Master will return a Game Server to connect to. + Game Server will return the joined Room's data. + This is an async request which triggers a OnOperationResponse() call. + + + If the room is not existing (anymore), the OperationResponse will have a returnCode of ErrorCode.GameDoesNotExist. + Other possible ErrorCodes are: GameClosed, GameFull. + + If the operation could be sent (requires connection). + + + + Operation to join a random, available room. Overloads take additional player properties. + This is an async request which triggers a OnOperationResponse() call. + If all rooms are closed or full, the OperationResponse will have a returnCode of ErrorCode.NoRandomMatchFound. + If successful, the OperationResponse contains a gameserver address and the name of some room. + + If the operation could be sent currently (requires connection). + + + + Only used on the Master Server. It will assign a game server and room to join-or-create. + On the Game Server, the OpJoin is used with option "create if not exists". + + + + + Leaves a room with option to come back later or "for good". + + Async games can be re-joined (loaded) later on. Set to false, if you want to abandon a game entirely. + WebFlag: Securely transmit the encrypted object AuthCookie to the web service in PathLeave webhook when available + If the operation can be sent currently. + + + Gets a list of games matching a SQL-like where clause. + + Operation is only available in lobbies of type SqlLobby. + This is an async request which triggers a OnOperationResponse() call. + Returned game list is stored in RoomInfoList. + + + The lobby to query. Has to be of type SqlLobby. + The sql query statement. + If the operation could be sent (has to be connected). + + + + Request the rooms and online status for a list of friends (each client must set a unique username via OpAuthenticate). + + + Used on Master Server to find the rooms played by a selected list of users. + Users identify themselves by using OpAuthenticate with a unique user ID. + The list of user IDs must be fetched from some other source (not provided by Photon). + + The server response includes 2 arrays of info (each index matching a friend from the request):
+ ParameterCode.FindFriendsResponseOnlineList = bool[] of online states
+ ParameterCode.FindFriendsResponseRoomIdList = string[] of room names (empty string if not in a room)
+
+ The options may be used to define which state a room must match to be returned. +
+ Array of friend's names (make sure they are unique). + Options that affect the result of the FindFriends operation. + If the operation could be sent (requires connection). +
+ + + Sets properties of a player / actor. + Internally this uses OpSetProperties, which can be used to either set room or player properties. + + The payer ID (a.k.a. actorNumber) of the player to attach these properties to. + The properties to add or update. + If set, these must be in the current properties-set (on the server) to set actorProperties: CAS. + Set these to forward the properties to a WebHook as defined for this app (in Dashboard). + If the operation could be sent (requires connection). + + + + Sets properties of a room. + Internally this uses OpSetProperties, which can be used to either set room or player properties. + + The properties to add or update. + The properties expected when update occurs. (CAS : "Check And Swap") + WebFlag to indicate if request should be forwarded as "PathProperties" webhook or not. + If the operation could be sent (has to be connected). + + + + Sends this app's appId and appVersion to identify this application server side. + This is an async request which triggers a OnOperationResponse() call. + + + This operation makes use of encryption, if that is established before. + See: EstablishEncryption(). Check encryption with IsEncryptionAvailable. + This operation is allowed only once per connection (multiple calls will have ErrorCode != Ok). + + Your application's name or ID to authenticate. This is assigned by Photon Cloud (webpage). + The client's version (clients with differing client appVersions are separated and players don't meet). + Contains all values relevant for authentication. Even without account system (external Custom Auth), the clients are allowed to identify themselves. + Optional region code, if the client should connect to a specific Photon Cloud Region. + Set to true on Master Server to receive "Lobby Statistics" events. + If the operation could be sent (has to be connected). + + + + Sends this app's appId and appVersion to identify this application server side. + This is an async request which triggers a OnOperationResponse() call. + + + This operation makes use of encryption, if that is established before. + See: EstablishEncryption(). Check encryption with IsEncryptionAvailable. + This operation is allowed only once per connection (multiple calls will have ErrorCode != Ok). + + Your application's name or ID to authenticate. This is assigned by Photon Cloud (webpage). + The client's version (clients with differing client appVersions are separated and players don't meet). + Optional authentication values. The client can set no values or a UserId or some parameters for Custom Authentication by a server. + Optional region code, if the client should connect to a specific Photon Cloud Region. + + + If the operation could be sent (has to be connected). + + + + Operation to handle this client's interest groups (for events in room). + + + Note the difference between passing null and byte[0]: + null won't add/remove any groups. + byte[0] will add/remove all (existing) groups. + First, removing groups is executed. This way, you could leave all groups and join only the ones provided. + + Changes become active not immediately but when the server executes this operation (approximately RTT/2). + + Groups to remove from interest. Null will not remove any. A byte[0] will remove all. + Groups to add to interest. Null will not add any. A byte[0] will add all current. + If operation could be enqueued for sending. Sent when calling: Service or SendOutgoingCommands. + + + + Send an event with custom code/type and any content to the other players in the same room. + + This override explicitly uses another parameter order to not mix it up with the implementation for Hashtable only. + Identifies this type of event (and the content). Your game's event codes can start with 0. + Any serializable datatype (including Hashtable like the other OpRaiseEvent overloads). + Contains (slightly) less often used options. If you pass null, the default options will be used. + Send options for reliable, encryption etc + If operation could be enqueued for sending. Sent when calling: Service or SendOutgoingCommands. + + + + Internally used operation to set some "per server" settings. This is for the Master Server. + + Set to true, to get Lobby Statistics (lists of existing lobbies). + False if the operation could not be sent. + + + Used in the RoomOptionFlags parameter, this bitmask toggles options in the room. + + + + Options for OpFindFriends can be combined to filter which rooms of friends are returned. + + + + Include a friend's room only if it is created and confirmed by the game server. + + + Include a friend's room only if it is visible (using Room.IsVisible). + + + Include a friend's room only if it is open (using Room.IsOpen). + + + Turns the bool options into an integer, which is sent as option flags for Op FindFriends. + The options applied to bits of an integer. + + + + Parameters for the matchmaking of JoinRandomRoom and JoinRandomOrCreateRoom. + + + More about matchmaking: . + + + + The custom room properties a room must have to fit. All key-values must be present to match. In SQL Lobby, use SqlLobbyFilter instead. + + + Filters by the MaxPlayers value of rooms. + + + The MatchmakingMode affects how rooms get filled. By default, the server fills rooms. + + + The lobby in which to match. The type affects how filters are applied. + + + SQL query to filter room matches. For default-typed lobbies, use ExpectedCustomRoomProperties instead. + + + The expected users list blocks player slots for your friends or team mates to join the room, too. + See: https://doc.photonengine.com/en-us/pun/v2/lobby-and-matchmaking/matchmaking-and-lobby#matchmaking_slot_reservation + + + Ticket for matchmaking. Provided by a plugin / server and contains a list of party members who should join the same room (among other things). + + + Parameters for creating rooms. + + + The name of the room to create. If null, the server generates a unique name. If not null, it must be unique and new or will cause an error. + + + The RoomOptions define the optional behaviour of rooms. + + + A lobby to attach the new room to. If set, this overrides a joined lobby (if any). + + + The custom player properties that describe this client / user. Keys must be strings. + + + Internally used value to skip some values when the operation is sent to the Master Server. + + + Internally used value to check which join mode we should call. + + + A list of users who are expected to join the room along with this client. Reserves slots for rooms with MaxPlayers value. + + + Ticket for matchmaking. Provided by a plugin / server and contains a list of party members who should join the same room (among other things). + + + + ErrorCode defines the default codes associated with Photon client/server communication. + + + + (0) is always "OK", anything else an error or specific situation. + + + + (-3) Operation can't be executed yet (e.g. OpJoin can't be called before being authenticated, RaiseEvent cant be used before getting into a room). + + + Before you call any operations on the Cloud servers, the automated client workflow must complete its authorization. + Wait until State is: JoinedLobby or ConnectedToMasterServer + + + + (-2) The operation you called is not implemented on the server (application) you connect to. Make sure you run the fitting applications. + + + (-2) The operation you called could not be executed on the server. + + Make sure you are connected to the server you expect. + + This code is used in several cases: + The arguments/parameters of the operation might be out of range, missing entirely or conflicting. + The operation you called is not implemented on the server (application). Server-side plugins affect the available operations. + + + + (-1) Something went wrong in the server. Try to reproduce and contact Exit Games. + + + (32767) Authentication failed. Possible cause: AppId is unknown to Photon (in cloud service). + + + (32766) GameId (name) already in use (can't create another). Change name. + + + (32765) Game is full. This rarely happens when some player joined the room before your join completed. + + + (32764) Game is closed and can't be joined. Join another game. + + + (32762) All servers are busy. This is a temporary issue and the game logic should try again after a brief wait time. + + This error may happen for all operations that create rooms. The operation response will contain this error code. + + This error is very unlikely to happen as we monitor load on all servers and add them on demand. + However, it's good to be prepared for a shortage of machines or surge in CCUs. + + + + (32761) Not in use currently. + + + (32760) Random matchmaking only succeeds if a room exists thats neither closed nor full. Repeat in a few seconds or create a new room. + + + (32758) Join can fail if the room (name) is not existing (anymore). This can happen when players leave while you join. + + + (32757) Authorization on the Photon Cloud failed because the concurrent users (CCU) limit of the app's subscription is reached. + + Unless you have a plan with "CCU Burst", clients might fail the authentication step during connect. + Affected client are unable to call operations. Please note that players who end a game and return + to the master server will disconnect and re-connect, which means that they just played and are rejected + in the next minute / re-connect. + This is a temporary measure. Once the CCU is below the limit, players will be able to connect an play again. + + OpAuthorize is part of connection workflow but only on the Photon Cloud, this error can happen. + Self-hosted Photon servers with a CCU limited license won't let a client connect at all. + + + + (32756) Authorization on the Photon Cloud failed because the app's subscription does not allow to use a particular region's server. + + Some subscription plans for the Photon Cloud are region-bound. Servers of other regions can't be used then. + Check your master server address and compare it with your Photon Cloud Dashboard's info. + https://dashboard.photonengine.com + + OpAuthorize is part of connection workflow but only on the Photon Cloud, this error can happen. + Self-hosted Photon servers with a CCU limited license won't let a client connect at all. + + + + + (32755) Custom Authentication of the user failed due to setup reasons (see Cloud Dashboard) or the provided user data (like username or token). Check error message for details. + + + + (32753) The Authentication ticket expired. Usually, this is refreshed behind the scenes. Connect (and authorize) again. + + + + (32752) A server-side plugin or WebHook failed and reported an error. Check the OperationResponse.DebugMessage. + + A typical case is when a plugin prevents a user from creating or joining a room. + If this is prohibited, that reports to the client as a plugin error.
+ Same for WebHooks.
+
+ + + (32751) CreateGame/JoinGame/Join operation fails if expected plugin does not correspond to loaded one. + + + + + (32750) for join requests. Indicates the current peer already called join and is joined to the room. + + + + + (32749) for join requests. Indicates the list of InactiveActors already contains an actor with the requested ActorNr or UserId. + + + + + (32748) for join requests. Indicates the list of Actors (active and inactive) did not contain an actor with the requested ActorNr or UserId. + + + + + (32747) for join requests. Note: for future use - Indicates the requested UserId was found in the ExcludedList. + + + + + (32746) for join requests. Indicates the list of ActiveActors already contains an actor with the requested ActorNr or UserId. + + + + + (32745) for SetProperties and RaiseEvent (if flag HttpForward is true) requests. Indicates the maximum allowed http requests per minute was reached. + + + + + (32744) for WebRpc requests. Indicates the the call to the external service failed. + + + + + (32743) for operations with defined limits (as in calls per second, content count or size). + + + + + (32742) Server error during matchmaking with slot reservation. E.g. the reserved slots can not exceed MaxPlayers. + + + + + (32741) Server will react with this error if invalid encryption parameters provided by token + + + + + Class for constants. These (byte) values define "well known" properties for an Actor / Player. + + + These constants are used internally. + "Custom properties" have to use a string-type as key. They can be assigned at will. + + + + (255) Name of a player/actor. + + + (254) Tells you if the player is currently in this game (getting events live). + A server-set value for async games, where players can leave the game and return later. + + + (253) UserId of the player. Sent when room gets created with RoomOptions.PublishUserId = true. + + + + Class for constants. These (byte) values are for "well known" room/game properties used in Photon LoadBalancing. + + + These constants are used internally. + "Custom properties" have to use a string-type as key. They can be assigned at will. + + + + (255) Max number of players that "fit" into this room. 0 is for "unlimited". + + + (243) Integer-typed max number of players that "fit" into a room. 0 is for "unlimited". Important: Code changed. See remarks. + This was code 244 for a brief time (Realtime v4.1.7.2 to v4.1.7.4) and those versions must be replaced or edited! + + + (254) Makes this room listed or not in the lobby on master. + + + (253) Allows more players to join a room (or not). + + + (252) Current count of players in the room. Used only in the lobby on master. + + + (251) True if the room is to be removed from room listing (used in update to room list in lobby on master) + + + (250) A list of the room properties to pass to the RoomInfo list in a lobby. This is used in CreateRoom, which defines this list once per room. + + + (249) Equivalent of Operation Join parameter CleanupCacheOnLeave. + + + (248) Code for MasterClientId, which is synced by server. When sent as op-parameter this is (byte)203. As room property this is (byte)248. + Tightly related to ParameterCode.MasterClientId. + + + (247) Code for ExpectedUsers in a room. Matchmaking keeps a slot open for the players with these userIDs. + + + (246) Player Time To Live. How long any player can be inactive (due to disconnect or leave) before the user gets removed from the playerlist (freeing a slot). + + + (245) Room Time To Live. How long a room stays available (and in server-memory), after the last player becomes inactive. After this time, the room gets persisted or destroyed. + + + + Class for constants. These values are for events defined by Photon LoadBalancing. + + They start at 255 and go DOWN. Your own in-game events can start at 0. These constants are used internally. + + + (230) Initial list of RoomInfos (in lobby on Master) + + + (229) Update of RoomInfos to be merged into "initial" list (in lobby on Master) + + + (228) Currently not used. State of queueing in case of server-full + + + (227) Currently not used. Event for matchmaking + + + (226) Event with stats about this application (players, rooms, etc) + + + (224) This event provides a list of lobbies with their player and game counts. + + + (210) Internally used in case of hosting by Azure + + + (255) Event Join: someone joined the game. The new actorNumber is provided as well as the properties of that actor (if set in OpJoin). + + + (254) Event Leave: The player who left the game can be identified by the actorNumber. + + + (253) When you call OpSetProperties with the broadcast option "on", this event is fired. It contains the properties being set. + + + (253) When you call OpSetProperties with the broadcast option "on", this event is fired. It contains the properties being set. + + + (252) When player left game unexpected and the room has a playerTtl != 0, this event is fired to let everyone know about the timeout. + Obsolete. Replaced by Leave. public const byte Disconnect = LiteEventCode.Disconnect; + (251) Sent by Photon Cloud when a plugin-call or webhook-call failed or events cache limit exceeded. Usually, the execution on the server continues, despite the issue. Contains: ParameterCode.Info. + + + + (250) Sent by Photon whent he event cache slice was changed. Done by OpRaiseEvent. + + + (223) Sent by Photon to update a token before it times out. + + + Class for constants. Codes for parameters of Operations and Events. + These constants are used internally. + + + (237) A bool parameter for creating games. If set to true, no room events are sent to the clients on join and leave. Default: false (and not sent). + + + (236) Time To Live (TTL) for a room when the last player leaves. Keeps room in memory for case a player re-joins soon. In milliseconds. + + + (235) Time To Live (TTL) for an 'actor' in a room. If a client disconnects, this actor is inactive first and removed after this timeout. In milliseconds. + + + (234) Optional parameter of OpRaiseEvent and OpSetCustomProperties to forward the event/operation to a web-service. + + + (233) Optional parameter of OpLeave in async games. If false, the player does abandons the game (forever). By default players become inactive and can re-join. + + + (233) Used in EvLeave to describe if a user is inactive (and might come back) or not. In rooms with PlayerTTL, becoming inactive is the default case. + + + (232) Used when creating rooms to define if any userid can join the room only once. + + + (231) Code for "Check And Swap" (CAS) when changing properties. + + + (230) Address of a (game) server to use. + + + (229) Count of players in this application in a rooms (used in stats event) + + + (228) Count of games in this application (used in stats event) + + + (227) Count of players on the master server (in this app, looking for rooms) + + + (225) User's ID + + + (224) Your application's ID: a name on your own Photon or a GUID on the Photon Cloud + + + (223) Not used currently (as "Position"). If you get queued before connect, this is your position + + + (223) Modifies the matchmaking algorithm used for OpJoinRandom. Allowed parameter values are defined in enum MatchmakingMode. + + + (222) List of RoomInfos about open / listed rooms + + + (221) Internally used to establish encryption + + + (220) Version of your application + + + (210) Internally used in case of hosting by Azure + + + (209) Internally used in case of hosting by Azure + + + (208) Internally used in case of hosting by Azure + + + (255) Code for the gameId/roomName (a unique name per room). Used in OpJoin and similar. + + + (250) Code for broadcast parameter of OpSetProperties method. + + + (252) Code for list of players in a room. + + + (254) Code of the Actor of an operation. Used for property get and set. + + + (249) Code for property set (Hashtable). + + + (245) Code of data/custom content of an event. Used in OpRaiseEvent. + + + (245) Code of data of an event. Used in OpRaiseEvent. + + + (244) Code used when sending some code-related parameter, like OpRaiseEvent's event-code. + This is not the same as the Operation's code, which is no longer sent as part of the parameter Dictionary in Photon 3. + + + (248) Code for property set (Hashtable). + + + + (251) Code for property-set (Hashtable). This key is used when sending only one set of properties. + If either ActorProperties or GameProperties are used (or both), check those keys. + + + + (253) Code of the target Actor of an operation. Used for property set. Is 0 for game + + + (246) Code to select the receivers of events (used in Lite, Operation RaiseEvent). + + + (247) Code for caching events while raising them. + + + (241) Bool parameter of CreateGame Operation. If true, server cleans up roomcache of leaving players (their cached events get removed). + + + (240) Code for "group" operation-parameter (as used in Op RaiseEvent). + + + (239) The "Remove" operation-parameter can be used to remove something from a list. E.g. remove groups from player's interest groups. + + + (239) Used in Op Join to define if UserIds of the players are broadcast in the room. Useful for FindFriends and reserving slots for expected users. + + + (238) The "Add" operation-parameter can be used to add something to some list or set. E.g. add groups to player's interest groups. + + + (218) Content for EventCode.ErrorInfo and internal debug operations. + + + (217) This key's (byte) value defines the target custom authentication type/service the client connects with. Used in OpAuthenticate + + + (216) This key's (string) value provides parameters sent to the custom authentication type/service the client connects with. Used in OpAuthenticate + + + (215) The JoinMode enum defines which variant of joining a room will be executed: Join only if available, create if not exists or re-join. + Replaces CreateIfNotExists which was only a bool-value. + + + (214) This key's (string or byte[]) value provides parameters sent to the custom authentication service setup in Photon Dashboard. Used in OpAuthenticate + + + (203) Code for MasterClientId, which is synced by server. When sent as op-parameter this is code 203. + Tightly related to GamePropertyKey.MasterClientId. + + + (1) Used in Op FindFriends request. Value must be string[] of friends to look up. + + + (2) Used in Op FindFriends request. An integer containing option-flags to filter the results. + + + (1) Used in Op FindFriends response. Contains bool[] list of online states (false if not online). + + + (2) Used in Op FindFriends response. Contains string[] of room names ("" where not known or no room joined). + + + (213) Used in matchmaking-related methods and when creating a room to name a lobby (to join or to attach a room to). + + + (212) Used in matchmaking-related methods and when creating a room to define the type of a lobby. Combined with the lobby name this identifies the lobby. + + + (211) This (optional) parameter can be sent in Op Authenticate to turn on Lobby Stats (info about lobby names and their user- and game-counts). + + + (210) Used for region values in OpAuth and OpGetRegions. + + + (209) Path of the WebRPC that got called. Also known as "WebRpc Name". Type: string. + + + (208) Parameters for a WebRPC as: Dictionary<string, object>. This will get serialized to JSon. + + + (207) ReturnCode for the WebRPC, as sent by the web service (not by Photon, which uses ErrorCode). Type: byte. + + + (206) Message returned by WebRPC server. Analog to Photon's debug message. Type: string. + + + (205) Used to define a "slice" for cached events. Slices can easily be removed from cache. Type: int. + + + (204) Informs the server of the expected plugin setup. + + The operation will fail in case of a plugin mismatch returning error code PluginMismatch 32751(0x7FFF - 16). + Setting string[]{} means the client expects no plugin to be setup. + Note: for backwards compatibility null omits any check. + + + + (202) Used by the server in Operation Responses, when it sends the nickname of the client (the user's nickname). + + + (201) Informs user about name of plugin load to game + + + (200) Informs user about version of plugin load to game + + + (196) Cluster info provided in OpAuthenticate/OpAuthenticateOnce responses. + + + (195) Protocol which will be used by client to connect master/game servers. Used for nameserver. + + + (194) Set of custom parameters which are sent in auth request. + + + (193) How are we going to encrypt data. + + + (192) Parameter of Authentication, which contains encryption keys (depends on AuthMode and EncryptionMode). + + + (191) An int parameter summarizing several boolean room-options with bit-flags. + + + Matchmaking ticket (type object). + + + Used server side once the group is extracted from the ticket. Clients don't send this. + + + (188) Parameter key to let the server know it may queue the client in low-ccu matchmaking situations. + + + (187) This optional parameter from the server configures the client to send analytics or not. + + + + Class for constants. Contains operation codes. + + These constants are used internally. + + + (255) Code for OpJoin, to get into a room. + + + (231) Authenticates this peer and connects to a virtual application + + + (230) Authenticates this peer and connects to a virtual application + + + (229) Joins lobby (on master) + + + (228) Leaves lobby (on master) + + + (227) Creates a game (or fails if name exists) + + + (226) Join game (by name) + + + (225) Joins random game (on master) + + + (254) Code for OpLeave, to get out of a room. + + + (253) Raise event (in a room, for other actors/players) + + + (252) Set Properties (of room or actor/player) + + + (251) Get Properties + + + (248) Operation code to change interest groups in Rooms (Lite application and extending ones). + + + (222) Request the rooms and online status for a list of friends (by name, which should be unique). + + + (221) Request statistics about a specific list of lobbies (their user and game count). + + + (220) Get list of regional servers from a NameServer. + + + (219) WebRpc Operation. + + + (218) Operation to set some server settings. Used with different parameters on various servers. + + + (217) Get the game list matching a supplied sql filter (SqlListLobby only) + + + Defines possible values for OpJoinRoom and OpJoinOrCreate. It tells the server if the room can be only be joined normally, created implicitly or found on a web-service for Turnbased games. + These values are not directly used by a game but implicitly set. + + + Regular join. The room must exist. + + + Join or create the room if it's not existing. Used for OpJoinOrCreate for example. + + + The room might be out of memory and should be loaded (if possible) from a Turnbased web-service. + + + Only re-join will be allowed. If the user is not yet in the room, this will fail. + + + + Options for matchmaking rules for OpJoinRandom. + + + + Fills up rooms (oldest first) to get players together as fast as possible. Default. + Makes most sense with MaxPlayers > 0 and games that can only start with more players. + + + Distributes players across available rooms sequentially but takes filter into account. Without filter, rooms get players evenly distributed. + + + Joins a (fully) random room. Expected properties must match but aside from this, any available room might be selected. + + + + Lite - OpRaiseEvent lets you chose which actors in the room should receive events. + By default, events are sent to "Others" but you can overrule this. + + + + Default value (not sent). Anyone else gets my event. + + + Everyone in the current room (including this peer) will get this event. + + + The server sends this event only to the actor with the lowest actorNumber. + The "master client" does not have special rights but is the one who is in this room the longest time. + + + + Lite - OpRaiseEvent allows you to cache events and automatically send them to joining players in a room. + Events are cached per event code and player: Event 100 (example!) can be stored once per player. + Cached events can be modified, replaced and removed. + + + Caching works only combination with ReceiverGroup options Others and All. + + + + Default value (not sent). + + + Will merge this event's keys with those already cached. + + + Replaces the event cache for this eventCode with this event's content. + + + Removes this event (by eventCode) from the cache. + + + Adds an event to the room's cache + + + Adds this event to the cache for actor 0 (becoming a "globally owned" event in the cache). + + + Remove fitting event from the room's cache. + + + Removes events of players who already left the room (cleaning up). + + + Increase the index of the sliced cache. + + + Set the index of the sliced cache. You must set RaiseEventOptions.CacheSliceIndex for this. + + + Purge cache slice with index. Exactly one slice is removed from cache. You must set RaiseEventOptions.CacheSliceIndex for this. + + + Purge cache slices with specified index and anything lower than that. You must set RaiseEventOptions.CacheSliceIndex for this. + + + + Flags for "types of properties", being used as filter in OpGetProperties. + + + + (0x00) Flag type for no property type. + + + (0x01) Flag type for game-attached properties. + + + (0x02) Flag type for actor related propeties. + + + (0x01) Flag type for game AND actor properties. Equal to 'Game' + + + Wraps up common room properties needed when you create rooms. Read the individual entries for more details. + This directly maps to the fields in the Room class. + + + Defines if this room is listed in the lobby. If not, it also is not joined randomly. + + A room that is not visible will be excluded from the room lists that are sent to the clients in lobbies. + An invisible room can be joined by name but is excluded from random matchmaking. + + Use this to "hide" a room and simulate "private rooms". Players can exchange a roomname and create it + invisble to avoid anyone else joining it. + + + + Defines if this room can be joined at all. + + If a room is closed, no player can join this. As example this makes sense when 3 of 4 possible players + start their gameplay early and don't want anyone to join during the game. + The room can still be listed in the lobby (set isVisible to control lobby-visibility). + + + + Max number of players that can be in the room at any time. 0 means "no limit". + + + Time To Live (TTL) for an 'actor' in a room. If a client disconnects, this actor is inactive first and removed after this timeout. In milliseconds. + + + Time To Live (TTL) for a room when the last player leaves. Keeps room in memory for case a player re-joins soon. In milliseconds. + + + Removes a user's events and properties from the room when a user leaves. + + This makes sense when in rooms where players can't place items in the room and just vanish entirely. + When you disable this, the event history can become too long to load if the room stays in use indefinitely. + Default: true. Cleans up the cache and props of leaving users. + + + + The room's custom properties to set. Use string keys! + + Custom room properties are any key-values you need to define the game's setup. + The shorter your keys are, the better. + Example: Map, Mode (could be "m" when used with "Map"), TileSet (could be "t"). + + + + Defines the custom room properties that get listed in the lobby. + + Name the custom room properties that should be available to clients that are in a lobby. + Use with care. Unless a custom property is essential for matchmaking or user info, it should + not be sent to the lobby, which causes traffic and delays for clients in the lobby. + + Default: No custom properties are sent to the lobby. + + + + Informs the server of the expected plugin setup. + + The operation will fail in case of a plugin missmatch returning error code PluginMismatch 32757(0x7FFF - 10). + Setting string[]{} means the client expects no plugin to be setup. + Note: for backwards compatibility null omits any check. + + + + + Tells the server to skip room events for joining and leaving players. + + + Using this makes the client unaware of the other players in a room. + That can save some traffic if you have some server logic that updates players + but it can also limit the client's usability. + + + + Disables events join and leave from the server as well as property broadcasts in a room (to minimize traffic) + + + + Defines if the UserIds of players get "published" in the room. Useful for FindFriends, if players want to play another game together. + + + When you set this to true, Photon will publish the UserIds of the players in that room. + In that case, you can use PhotonPlayer.userId, to access any player's userID. + This is useful for FindFriends and to set "expected users" to reserve slots in a room. + + + + Optionally, properties get deleted, when null gets assigned as value. Defaults to off / false. + + When Op SetProperties is setting a key's value to null, the server and clients should remove the key/value from the Custom Properties. + By default, the server keeps the keys (and null values) and sends them to joining players. + + Important: Only when SetProperties does a "broadcast", the change (key, value = null) is sent to clients to update accordingly. + This applies to Custom Properties for rooms and actors/players. + + + + By default, property changes are sent back to the client that's setting them to avoid de-sync when properties are set concurrently. + + This option is enables by default to fix this scenario: + + 1) On server, room property ABC is set to value FOO, which triggers notifications to all the clients telling them that the property changed. + 2) While that notification is in flight, a client sets the ABC property to value BAR. + 3) Client receives notification from the server and changes its local copy of ABC to FOO. + 4) Server receives the set operation and changes the official value of ABC to BAR, but never notifies the client that sent the set operation that the value is now BAR. + + Without this option, the client that set the value to BAR never hears from the server that the official copy has been updated to BAR, and thus gets stuck with a value of FOO. + + + + Aggregates several less-often used options for operation RaiseEvent. See field descriptions for usage details. + + + Default options: CachingOption: DoNotCache, InterestGroup: 0, targetActors: null, receivers: Others, sequenceChannel: 0. + + + Defines if the server should simply send the event, put it in the cache or remove events that are like this one. + + When using option: SliceSetIndex, SlicePurgeIndex or SlicePurgeUpToIndex, set a CacheSliceIndex. All other options except SequenceChannel get ignored. + + + + The number of the Interest Group to send this to. 0 goes to all users but to get 1 and up, clients must subscribe to the group first. + + + A list of Player.ActorNumbers to send this event to. You can implement events that just go to specific users this way. + + + Sends the event to All, MasterClient or Others (default). Be careful with MasterClient, as the client might disconnect before it got the event and it gets lost. + + + Events are ordered per "channel". If you have events that are independent of others, they can go into another sequence or channel. + + + Optional flags to be used in Photon client SDKs with Op RaiseEvent and Op SetProperties. + Introduced mainly for webhooks 1.2 to control behavior of forwarded HTTP requests. + + + Types of lobbies define their behaviour and capabilities. Check each value for details. + Values of this enum must be matched by the server. + + + Standard type and behaviour: While joined to this lobby clients get room-lists and JoinRandomRoom can use a simple filter to match properties (perfectly). + + + This lobby type lists rooms like Default but JoinRandom has a parameter for SQL-like "where" clauses for filtering. This allows bigger, less, or and and combinations. + + + This lobby does not send lists of games. It is only used for OpJoinRandomRoom. It keeps rooms available for a while when there are only inactive users left. + + + Refers to a specific lobby on the server. + + Name and Type combined are the unique identifier for a lobby.
+ The server will create lobbies "on demand", so no registration or setup is required.
+ An empty or null Name always points to the "default lobby" as special case. +
+
+ + + Name of the lobby. Default: null, pointing to the "default lobby". + + + If Name is null or empty, a TypedLobby will point to the "default lobby". This ignores the Type value and always acts as . + + + + + Type (and behaviour) of the lobby. + + + An empty or null Name always points to the "default lobby" as special case. + + + + + A reference to the default lobby which is the unique lobby that uses null as name and is of type . + + + There is only a single lobby with an empty name on the server. It is always of type .
+ On the other hand, this is a shortcut and reusable reference to the default lobby.
+ Do not change Name or Type.
+
+
+ + + Returns whether or not this instance points to the "default lobby" (). + + + This comes up to checking if the Name is null or empty. + is not the same thing as the "default lobby" (). + + + + + Creates a TypedLobby instance. Unless Name is changed, this points to the "default lobby" (). + + + + + Sets Name and Type of the new instance. Make sure name is not empty or null, as that always points to the "default lobby" (). + + Some string to identify a lobby. + The type of a lobby defines it's capabilities and behaviour. + + + + Info for a lobby on the server. Used when is true. + + + + Count of players that currently joined this lobby. + + + Count of rooms currently associated with this lobby. + + + + Options for authentication modes. From "classic" auth on each server to AuthOnce (on NameServer). + + + + + Classic authentication mode. + + + + + Authenticate once on the NameServer. + + + + + Authenticate once on the NameServer using WebSocket Secure (WSS). + + + + + Options for optional "Custom Authentication" services used with Photon. Used by OpAuthenticate after connecting to Photon. + + + + Use a custom authentication service. Currently the only implemented option. + + + Authenticates users by their Steam Account. Set Steam's ticket as "ticket" via AddAuthParameter(). + + + Authenticates users by their Facebook Account. Set Facebooks's tocken as "token" via AddAuthParameter(). + + + Authenticates users by their Oculus Account and token. Set Oculus' userid as "userid" and nonce as "nonce" via AddAuthParameter(). + + + Authenticates users by their PSN Account and token on PS4. Set token as "token", env as "env" and userName as "userName" via AddAuthParameter(). + + + + + + Authenticates users by their Xbox Account. Pass the XSTS token via SetAuthPostData(). + + + Authenticates users by their HTC Viveport Account. Set userToken as "userToken" via AddAuthParameter(). + + + Authenticates users by their NSA ID. Set token as "token" and appversion as "appversion" via AddAuthParameter(). The appversion is optional. + + + Authenticates users by their PSN Account and token on PS5. Set token as "token", env as "env" and userName as "userName" via AddAuthParameter(). + + + + + + Authenticates users with Epic Online Services (EOS). Set token as "token" and ownershipToken as "ownershipToken" via AddAuthParameter(). The ownershipToken is optional. + + + Authenticates users with Facebook Gaming api. Set token as "token" via AddAuthParameter(). + + + Disables custom authentication. Same as not providing any AuthenticationValues for connect (more precisely for: OpAuthenticate). + + + + Container for user authentication in Photon. Set AuthValues before you connect - all else is handled. + + + On Photon, user authentication is optional but can be useful in many cases. + If you want to FindFriends, a unique ID per user is very practical. + + There are basically three options for user authentication: None at all, the client sets some UserId + or you can use some account web-service to authenticate a user (and set the UserId server-side). + + Custom Authentication lets you verify end-users by some kind of login or token. It sends those + values to Photon which will verify them before granting access or disconnecting the client. + + The AuthValues are sent in OpAuthenticate when you connect, so they must be set before you connect. + If the AuthValues.UserId is null or empty when it's sent to the server, then the Photon Server assigns a UserId! + + The Photon Cloud Dashboard will let you enable this feature and set important server values for it. + https://dashboard.photonengine.com + + + + See AuthType. + + + The type of authentication provider that should be used. Defaults to None (no auth whatsoever). + Several auth providers are available and CustomAuthenticationType.Custom can be used if you build your own service. + + + This string must contain any (http get) parameters expected by the used authentication service. By default, username and token. + + Maps to operation parameter 216. + Standard http get parameters are used here and passed on to the service that's defined in the server (Photon Cloud Dashboard). + + + + Data to be passed-on to the auth service via POST. Default: null (not sent). Either string or byte[] (see setters). + Maps to operation parameter 214. + + + Internal Photon token. After initial authentication, Photon provides a token for this client, subsequently used as (cached) validation. + Any token for custom authentication should be set via SetAuthPostData or AddAuthParameter. + + + The UserId should be a unique identifier per user. This is for finding friends, etc.. + See remarks of AuthValues for info about how this is set and used. + + + Creates empty auth values without any info. + + + Creates minimal info about the user. If this is authenticated or not, depends on the set AuthType. + Some UserId to set in Photon. + + + Sets the data to be passed-on to the auth service via POST. + AuthPostData is just one value. Each SetAuthPostData replaces any previous value. It can be either a string, a byte[] or a dictionary. + String data to be used in the body of the POST request. Null or empty string will set AuthPostData to null. + + + Sets the data to be passed-on to the auth service via POST. + AuthPostData is just one value. Each SetAuthPostData replaces any previous value. It can be either a string, a byte[] or a dictionary. + Binary token / auth-data to pass on. + + + Sets data to be passed-on to the auth service as Json (Content-Type: "application/json") via Post. + AuthPostData is just one value. Each SetAuthPostData replaces any previous value. It can be either a string, a byte[] or a dictionary. + A authentication-data dictionary will be converted to Json and passed to the Auth webservice via HTTP Post. + + + Adds a key-value pair to the get-parameters used for Custom Auth (AuthGetParameters). + This method does uri-encoding for you. + Key for the value to set. + Some value relevant for Custom Authentication. + + + + Transform this object into string. + + String info about this object's values. + + + + Make a copy of the current object. + + The object to be copied into. + The copied object. + + + + Abstract implementation of PhotonPing, ase for pinging servers to find the "Best Region". + + + + Caches the last exception/error message, if any. + + + True of the ping was successful. + + + True if there was any result. + + + Length of a ping. + + + Bytes to send in a (Photon UDP) ping. + + + Randomized number to identify a ping. + + + Begins sending a ping. + + + Check if done. + + + Dispose of this ping. + + + Initialize this ping (GotResult, Successful, PingId). + + + Uses C# Socket class from System.Net.Sockets (as Unity usually does). + Incompatible with Windows 8 Store/Phone API. + + + + Sends a "Photon Ping" to a server. + + Address in IPv4 or IPv6 format. An address containing a '.' will be interpreted as IPv4. + True if the Photon Ping could be sent. + + + Check if done. + + + Dispose of this ping. + + + + Summarizes a "player" within a room, identified (in that room) by ID (or "actorNumber"). + + + Each player has a actorNumber, valid for that room. It's -1 until assigned by server (and client logic). + + + + + Used internally to identify the masterclient of a room. + + + + Backing field for property. + + + Identifier of this player in current room. Also known as: actorNumber or actorNumber. It's -1 outside of rooms. + The ID is assigned per room and only valid in that context. It will change even on leave and re-join. IDs are never re-used per room. + + + Only one player is controlled by each client. Others are not local. + + + Background field for nickName. + + + Non-unique nickname of this player. Synced automatically in a room. + + A player might change his own playername in a room (it's only a property). + Setting this value updates the server and other players (using an operation). + + + + UserId of the player, available when the room got created with RoomOptions.PublishUserId = true. + Useful for and blocking slots in a room for expected players (e.g. in ). + + + + True if this player is the Master Client of the current room. + + + + If this player is active in the room (and getting events which are currently being sent). + + Inactive players keep their spot in a room but otherwise behave as if offline (no matter what their actual connection status is). + The room needs a PlayerTTL != 0. If a player is inactive for longer than PlayerTTL, the server will remove this player from the room. + For a client "rejoining" a room, is the same as joining it: It gets properties, cached events and then the live events. + + + + Read-only cache for custom properties of player. Set via Player.SetCustomProperties. + + Don't modify the content of this Hashtable. Use SetCustomProperties and the + properties of this class to modify values. When you use those, the client will + sync values with the server. + + + + + Can be used to store a reference that's useful to know "by player". + Example: Set a player's character as Tag by assigning the GameObject on Instantiate. + + + + Creates a player instance. + To extend and replace this Player, override LoadBalancingPeer.CreatePlayer(). + + NickName of the player (a "well known property"). + ID or ActorNumber of this player in the current room (a shortcut to identify each player in room) + If this is the local peer's player (or a remote one). + + + + Creates a player instance. + To extend and replace this Player, override LoadBalancingPeer.CreatePlayer(). + + NickName of the player (a "well known property"). + ID or ActorNumber of this player in the current room (a shortcut to identify each player in room) + If this is the local peer's player (or a remote one). + A Hashtable of custom properties to be synced. Must use String-typed keys and serializable datatypes as values. + + + + Get a Player by ActorNumber (Player.ID). + + ActorNumber of the a player in this room. + Player or null. + + + Gets this Player's next Player, as sorted by ActorNumber (Player.ID). Wraps around. + Player or null. + + + Gets a Player's next Player, as sorted by ActorNumber (Player.ID). Wraps around. + Useful when you pass something to the next player. For example: passing the turn to the next player. + The Player for which the next is being needed. + Player or null. + + + Gets a Player's next Player, as sorted by ActorNumber (Player.ID). Wraps around. + Useful when you pass something to the next player. For example: passing the turn to the next player. + The ActorNumber (Player.ID) for which the next is being needed. + Player or null. + + + Caches properties for new Players or when updates of remote players are received. Use SetCustomProperties() for a synced update. + + This only updates the CustomProperties and doesn't send them to the server. + Mostly used when creating new remote players, where the server sends their properties. + + + + + Brief summary string of the Player: ActorNumber and NickName + + + + + String summary of the Player: player.ID, name and all custom properties of this user. + + + Use with care and not every frame! + Converts the customProperties to a String on every single call. + + + + + If players are equal (by GetHasCode, which returns this.ID). + + + + + Accompanies Equals, using the ID (actorNumber) as HashCode to return. + + + + + Used internally, to update this client's playerID when assigned (doesn't change after assignment). + + + + + Updates and synchronizes this Player's Custom Properties. Optionally, expectedProperties can be provided as condition. + + + Custom Properties are a set of string keys and arbitrary values which is synchronized + for the players in a Room. They are available when the client enters the room, as + they are in the response of OpJoin and OpCreate. + + Custom Properties either relate to the (current) Room or a Player (in that Room). + + Both classes locally cache the current key/values and make them available as + property: CustomProperties. This is provided only to read them. + You must use the method SetCustomProperties to set/modify them. + + Any client can set any Custom Properties anytime (when in a room). + It's up to the game logic to organize how they are best used. + + You should call SetCustomProperties only with key/values that are new or changed. This reduces + traffic and performance. + + Unless you define some expectedProperties, setting key/values is always permitted. + In this case, the property-setting client will not receive the new values from the server but + instead update its local cache in SetCustomProperties. + + If you define expectedProperties, the server will skip updates if the server property-cache + does not contain all expectedProperties with the same values. + In this case, the property-setting client will get an update from the server and update it's + cached key/values at about the same time as everyone else. + + The benefit of using expectedProperties can be only one client successfully sets a key from + one known value to another. + As example: Store who owns an item in a Custom Property "ownedBy". It's 0 initally. + When multiple players reach the item, they all attempt to change "ownedBy" from 0 to their + actorNumber. If you use expectedProperties {"ownedBy", 0} as condition, the first player to + take the item will have it (and the others fail to set the ownership). + + Properties get saved with the game state for Turnbased games (which use IsPersistent = true). + + Hashtable of Custom Properties to be set. + If non-null, these are the property-values the server will check as condition for this update. + Defines if this SetCustomProperties-operation gets forwarded to your WebHooks. Client must be in room. + + False if propertiesToSet is null or empty or have zero string keys. + True in offline mode even if expectedProperties or webFlags are used. + If not in a room, returns true if local player and expectedValues and webFlags are null. + (Use this to cache properties to be sent when joining a room). + Otherwise, returns if this operation could be sent to the server. + + + + If there is a nickname in the room props, but it's not the current (local) one, update the room when joining/joined. + + + Uses OpSetPropertiesOfActor to sync this player's NickName (server is being updated with this.NickName). + + + Unlike the CloudRegionCode, this may contain cluster information. + + + Weighted ping time. + + Regions gets pinged 5 times (RegionPinger.Attempts). + Out of those, the worst rtt is discarded and the best will be counted two times for a weighted average. + + + + + Provides methods to work with Photon's regions (Photon Cloud) and can be use to find the one with best ping. + + + When a client uses a Name Server to fetch the list of available regions, the LoadBalancingClient will create a RegionHandler + and provide it via the OnRegionListReceived callback, as soon as the list is available. No pings were sent for Best Region selection yet. + + Your logic can decide to either connect to one of those regional servers, or it may use PingMinimumOfRegions to test + which region provides the best ping. Alternatively the client may be set to connect to the Best Region (lowest ping), one or + more regions get pinged. + Not all regions will be pinged. As soon as the results are final, the client will connect to the best region, + so you can check the ping results when connected to the Master Server. + + Regions gets pinged 5 times (RegionPinger.Attempts). + Out of those, the worst rtt is discarded and the best will be counted two times for a weighted average. + + Usually UDP will be used to ping the Master Servers. In WebGL, WSS is used instead. + + It makes sense to make clients "sticky" to a region when one gets selected. + This can be achieved by storing the SummaryToCache value, once pinging was done. + When the client connects again, the previous SummaryToCache helps limiting the number of regions to ping. + In best case, only the previously selected region gets re-pinged and if the current ping is not much worse, this one region is used again. + + + + The implementation of PhotonPing to use for region pinging (Best Region detection). + Defaults to null, which means the Type is set automatically. + + + A list of region names for the Photon Cloud. Set by the result of OpGetRegions(). + + Implement ILoadBalancingCallbacks and register for the callbacks to get OnRegionListReceived(RegionHandler regionHandler). + You can also put a "case OperationCode.GetRegions:" into your OnOperationResponse method to notice when the result is available. + + + + + When PingMinimumOfRegions was called and completed, the BestRegion is identified by best ping. + + + + + This value summarizes the results of pinging currently available regions (after PingMinimumOfRegions finished). + + + This value should be stored in the client by the game logic. + When connecting again, use it as previous summary to speed up pinging regions and to make the best region sticky for the client. + + + + Provides a list of regions and their pings as string. + + + Initializes the regions of this RegionHandler with values provided from the Name Server (as OperationResponse for OpGetRegions). + + + If non-zero, this port will be used to ping Master Servers on. + + + If the previous Best Region's ping is now higher by this much, ping all regions and find a new Best Region. + + + How much higher a region's ping can be from the absolute best, to be considered the Best Region (by ping and name). + + + If the region from a previous BestRegionSummary now has a ping higher than this limit, all regions get pinged again to find a better. Default: 90ms. + + Pinging all regions takes time, which is why a BestRegionSummary gets stored. + If that is available, the Best Region becomes sticky and is used again. + This limit introduces an exception: Should the pre-defined best region have a ping worse than this, all regions are considered. + + + + True if the available regions are being pinged currently. + + + True if the pinging of regions is being aborted. + + + + Creates a new RegionHandler. + If non-zero, this port will be used to ping Master Servers on. + + + Starts the process of pinging of all available regions. + Provide a method to call when all ping results are available. Aborting the pings will also cancel the callback. + A BestRegionSummary from an earlier RegionHandler run. This makes a selected best region "sticky" and keeps ping times lower. + If pining the regions gets started now. False if the current state prevent this. + + + Calling this will stop pinging the regions and suppress the onComplete callback. + + + Privately used to ping regions if the current best one isn't as fast as earlier. + If pinging can be started. + + + Wraps the ping attempts and workflow for a single region. + + + How often to ping a region. + + + How long to wait maximum for a response. + + + Ping result when pinging failed. + + + Current ping attempt count. + + + True if all attempts are done or timed out. + + + Set to true to abort pining this region. + + + Initializes a RegionPinger for the given region. + + + Selects the best fitting ping implementation or uses the one set in RegionHandler.PingImplementation. + PhotonPing instance to use. + + + + Starts the ping routine for the assigned region. + + + Pinging runs in a ThreadPool worker item or (if needed) in a Thread. + WebGL runs pinging on the Main Thread as coroutine. + + True unless Aborted. + + + Calling this will stop pinging the regions and cancel the onComplete callback. + + + Pings the region. To be called by a thread. + + + + Affected by frame-rate of app, as this Coroutine checks the socket for a result once per frame. + + + + Gets this region's results as string summary. + + + + Attempts to resolve a hostname into an IP string or returns empty string if that fails. + + + To be compatible with most platforms, the address family is checked like this:
+ if (ipAddress.AddressFamily.ToString().Contains("6")) // ipv6... +
+ Hostname to resolve. + IP string or empty string if resolution fails +
+ + + This class represents a room a client joins/joined. + + + Contains a list of current players, their properties and those of this room, too. + A room instance has a number of "well known" properties like IsOpen, MaxPlayers which can be changed. + Your own, custom properties can be set via SetCustomProperties() while being in the room. + + Typically, this class should be extended by a game-specific implementation with logic and extra features. + + + + + A reference to the LoadBalancingClient which is currently keeping the connection and state. + + + + The name of a room. Unique identifier (per region and virtual appid) for a room/match. + The name can't be changed once it's set by the server. + + + + Defines if the room can be joined. + + + This does not affect listing in a lobby but joining the room will fail if not open. + If not open, the room is excluded from random matchmaking. + Due to racing conditions, found matches might become closed while users are trying to join. + Simply re-connect to master and find another. + Use property "IsVisible" to not list the room. + + As part of RoomInfo this can't be set. + As part of a Room (which the player joined), the setter will update the server and all clients. + + + + + Defines if the room is listed in its lobby. + + + Rooms can be created invisible, or changed to invisible. + To change if a room can be joined, use property: open. + + As part of RoomInfo this can't be set. + As part of a Room (which the player joined), the setter will update the server and all clients. + + + + + Sets a limit of players to this room. This property is synced and shown in lobby, too. + If the room is full (players count == maxplayers), joining this room will fail. + + + As part of RoomInfo this can't be set. + As part of a Room (which the player joined), the setter will update the server and all clients. + + + + The count of players in this Room (using this.Players.Count). + + + While inside a Room, this is the list of players who are also in that room. + + + While inside a Room, this is the list of players who are also in that room. + + + + List of users who are expected to join this room. In matchmaking, Photon blocks a slot for each of these UserIDs out of the MaxPlayers. + + + The corresponding feature in Photon is called "Slot Reservation" and can be found in the doc pages. + Define expected players in the methods: , and . + + + + Player Time To Live. How long any player can be inactive (due to disconnect or leave) before the user gets removed from the playerlist (freeing a slot). + + + Room Time To Live. How long a room stays available (and in server-memory), after the last player becomes inactive. After this time, the room gets persisted or destroyed. + + + + The ID (actorNumber, actorNumber) of the player who's the master of this Room. + Note: This changes when the current master leaves the room. + + + + + Gets a list of custom properties that are in the RoomInfo of the Lobby. + This list is defined when creating the room and can't be changed afterwards. Compare: LoadBalancingClient.OpCreateRoom() + + You could name properties that are not set from the beginning. Those will be synced with the lobby when added later on. + + + + Gets if this room cleans up the event cache when a player (actor) leaves. + + + This affects which events joining players get. + + Set in room creation via RoomOptions.CleanupCacheOnLeave. + + Within PUN, auto cleanup of events means that cached RPCs and instantiated networked objects are deleted from the room. + + + + Define if the client who calls SetProperties should receive the properties update event or not. + + + Define if Join and Leave events should not be sent to clients in the room. + + + Extends SuppressRoomEvents: Define if Join and Leave events but also the actors' list and their respective properties should not be sent to clients. + + + Define if UserIds of the players are broadcast in the room. Useful for FindFriends and reserving slots for expected users. + + + Define if actor or room properties with null values are removed on the server or kept. + + + Creates a Room (representation) with given name and properties and the "listing options" as provided by parameters. + Name of the room (can be null until it's actually created on server). + Room options. + Signal if this room is only used locally. + + + Read (received) room option flags into related bool parameters. + This is for internal use. The operation response for join and create room operations is read this way. + + + + + Updates and synchronizes this Room's Custom Properties. Optionally, expectedProperties can be provided as condition. + + + Custom Properties are a set of string keys and arbitrary values which is synchronized + for the players in a Room. They are available when the client enters the room, as + they are in the response of OpJoin and OpCreate. + + Custom Properties either relate to the (current) Room or a Player (in that Room). + + Both classes locally cache the current key/values and make them available as + property: CustomProperties. This is provided only to read them. + You must use the method SetCustomProperties to set/modify them. + + Any client can set any Custom Properties anytime (when in a room). + It's up to the game logic to organize how they are best used. + + You should call SetCustomProperties only with key/values that are new or changed. This reduces + traffic and performance. + + Unless you define some expectedProperties, setting key/values is always permitted. + In this case, the property-setting client will not receive the new values from the server but + instead update its local cache in SetCustomProperties. + + If you define expectedProperties, the server will skip updates if the server property-cache + does not contain all expectedProperties with the same values. + In this case, the property-setting client will get an update from the server and update it's + cached key/values at about the same time as everyone else. + + The benefit of using expectedProperties can be only one client successfully sets a key from + one known value to another. + As example: Store who owns an item in a Custom Property "ownedBy". It's 0 initally. + When multiple players reach the item, they all attempt to change "ownedBy" from 0 to their + actorNumber. If you use expectedProperties {"ownedBy", 0} as condition, the first player to + take the item will have it (and the others fail to set the ownership). + + Properties get saved with the game state for Turnbased games (which use IsPersistent = true). + + Hashtable of Custom Properties that changes. + Provide some keys/values to use as condition for setting the new values. Client must be in room. + Defines if this SetCustomProperties-operation gets forwarded to your WebHooks. Client must be in room. + + False if propertiesToSet is null or empty or have zero string keys. + True in offline mode even if expectedProperties or webFlags are used. + Otherwise, returns if this operation could be sent to the server. + + + + + Enables you to define the properties available in the lobby if not all properties are needed to pick a room. + + + Limit the amount of properties sent to users in the lobby to improve speed and stability. + + An array of custom room property names to forward to the lobby. + If the operation could be sent to the server. + + + + Removes a player from this room's Players Dictionary. + This is internally used by the LoadBalancing API. There is usually no need to remove players yourself. + This is not a way to "kick" players. + + + + + Removes a player from this room's Players Dictionary. + + + + + Asks the server to assign another player as Master Client of your current room. + + + RaiseEvent has the option to send messages only to the Master Client of a room. + SetMasterClient affects which client gets those messages. + + This method calls an operation on the server to set a new Master Client, which takes a roundtrip. + In case of success, this client and the others get the new Master Client from the server. + + SetMasterClient tells the server which current Master Client should be replaced with the new one. + It will fail, if anything switches the Master Client moments earlier. There is no callback for this + error. All clients should get the new Master Client assigned by the server anyways. + + See also: MasterClientId + + The player to become the next Master Client. + False when this operation couldn't be done currently. Requires a v4 Photon Server. + + + + Checks if the player is in the room's list already and calls StorePlayer() if not. + + The new player - identified by ID. + False if the player could not be added (cause it was in the list already). + + + + Updates a player reference in the Players dictionary (no matter if it existed before or not). + + The Player instance to insert into the room. + + + + Tries to find the player with given actorNumber (a.k.a. ID). + Only useful when in a Room, as IDs are only valid per Room. + + ID to look for. + If true, the Master Client is returned for ID == 0. + The player with the ID or null. + + + + Attempts to remove all current expected users from the server's Slot Reservation list. + + + Note that this operation can conflict with new/other users joining. They might be + adding users to the list of expected users before or after this client called ClearExpectedUsers. + + This room's expectedUsers value will update, when the server sends a successful update. + + Internals: This methods wraps up setting the ExpectedUsers property of a room. + + If the operation could be sent to the server. + + + + Attempts to update the expected users from the server's Slot Reservation list. + + + Note that this operation can conflict with new/other users joining. They might be + adding users to the list of expected users before or after this client called SetExpectedUsers. + + This room's expectedUsers value will update, when the server sends a successful update. + + Internals: This methods wraps up setting the ExpectedUsers property of a room. + + The new array of UserIDs to be reserved in the room. + If the operation could be sent to the server. + + + Returns a summary of this Room instance as string. + Summary of this Room instance. + + + Returns a summary of this Room instance as longer string, including Custom Properties. + Summary of this Room instance. + + + + A simplified room with just the info required to list and join, used for the room listing in the lobby. + The properties are not settable (IsOpen, MaxPlayers, etc). + + + This class resembles info about available rooms, as sent by the Master server's lobby. + Consider all values as readonly. None are synced (only updated by events by server). + + + + Used in lobby, to mark rooms that are no longer listed (for being full, closed or hidden). + + + Backing field for property. + + + Backing field for property. + + + Backing field for property. + + + Backing field for property. + + + Backing field for property. + + + Backing field for property. + + + Backing field for property. + + + Backing field for property. False unless the GameProperty is set to true (else it's not sent). + + + Backing field for property. + + + Backing field for master client id (actorNumber). defined by server in room props and ev leave. + + + Backing field for property. + + + Read-only "cache" of custom properties of a room. Set via Room.SetCustomProperties (not available for RoomInfo class!). + All keys are string-typed and the values depend on the game/application. + + + + The name of a room. Unique identifier for a room/match (per AppId + game-Version). + + + + Count of players currently in room. This property is overwritten by the Room class (used when you're in a Room). + + + + + The limit of players for this room. This property is shown in lobby, too. + If the room is full (players count == maxplayers), joining this room will fail. + + + As part of RoomInfo this can't be set. + As part of a Room (which the player joined), the setter will update the server and all clients. + + + + + Defines if the room can be joined. + This does not affect listing in a lobby but joining the room will fail if not open. + If not open, the room is excluded from random matchmaking. + Due to racing conditions, found matches might become closed even while you join them. + Simply re-connect to master and find another. + Use property "IsVisible" to not list the room. + + + As part of RoomInfo this can't be set. + As part of a Room (which the player joined), the setter will update the server and all clients. + + + + + Defines if the room is listed in its lobby. + Rooms can be created invisible, or changed to invisible. + To change if a room can be joined, use property: open. + + + As part of RoomInfo this can't be set. + As part of a Room (which the player joined), the setter will update the server and all clients. + + + + + Constructs a RoomInfo to be used in room listings in lobby. + + Name of the room and unique ID at the same time. + Properties for this room. + + + + Makes RoomInfo comparable (by name). + + + + + Accompanies Equals, using the name's HashCode as return. + + + + + Returns most interesting room values as string. + Summary of this RoomInfo instance. + + + Returns most interesting room values as string, including custom properties. + Summary of this RoomInfo instance. + + + Copies "well known" properties to fields (IsVisible, etc) and caches the custom properties (string-keys only) in a local hashtable. + New or updated properties to store in this RoomInfo. + + + + Helper class to debug log basic information about Photon client and vital traffic statistics. + + + Set SupportLogger.Client for this to work. + + + + + Toggle to enable or disable traffic statistics logging. + + + + helps skip the initial OnApplicationPause call, which is not really of interest on start + + + + Photon client to log information and statistics from. + + + + + Debug logs vital traffic statistics about the attached Photon Client. + + + + + Debug logs basic information (AppId, AppVersion, PeerID, Server address, Region) about the attached Photon Client. + + + + + The SystemConnectionSummary (SBS) is useful to analyze low level connection issues in Unity. This requires a ConnectionHandler in the scene. + + + A LoadBalancingClient automatically creates a SystemConnectionSummary on these disconnect causes: + DisconnectCause.ExceptionOnConnect, DisconnectCause.Exception, DisconnectCause.ServerTimeout and DisconnectCause.ClientTimeout. + + The SBS can then be turned into an integer (ToInt()) or string to debug the situation or use in analytics. + Both, ToString and ToInt summarize the network-relevant conditions of the client at and before the connection fail, including the PhotonPeer.SocketErrorCode. + + Important: To correctly create the SBS instance, a ConnectionHandler component must be present and enabled in the + Unity scene hierarchy. In best case, keep the ConnectionHandler on a GameObject which is flagged as + DontDestroyOnLoad. + + + + 28 and up. 4 bits. + + + 25 and up. 3 bits. + + + + Creates a SystemConnectionSummary for an incident of a local LoadBalancingClient. This gets used automatically by the LoadBalancingClient! + + + If the LoadBalancingClient.SystemConnectionSummary is non-null after a connection-loss, you can call .ToInt() and send this to analytics or log it. + + + + + + + Creates a SystemConnectionSummary instance from an int (reversing ToInt()). This can then be turned into a string again. + + An int, as provided by ToInt(). No error checks yet. + + + + Turns the SystemConnectionSummary into an integer, which can be be used for analytics purposes. It contains a lot of info and can be used to instantiate a new SystemConnectionSummary. + + Compact representation of the context for a disconnect issue. + + + + A readable debug log string of the context for network problems. + + SystemConnectionSummary as readable string. + + + Applies bitval to bitpos (no matter value's initial bit value). + + + Applies bitvals via OR operation (expects bits in value to be 0 initially). + + + Reads an operation response of a WebRpc and provides convenient access to most common values. + + See LoadBalancingClient.OpWebRpc.
+ Create a WebRpcResponse to access common result values.
+ The operationResponse.OperationCode should be: OperationCode.WebRpc.
+
+
+ + Name of the WebRpc that was called. + + + ResultCode of the WebService that answered the WebRpc. + + 0 is: "OK" for WebRPCs.
+ -1 is: No ResultCode by WebRpc service (check ).
+ Other ResultCode are defined by the individual WebRpc and service. +
+
+ + Might be empty or null. + + + Other key/values returned by the webservice that answered the WebRpc. + + + An OperationResponse for a WebRpc is needed to read it's values. + + + Turns the response into an easier to read string. + String resembling the result. + + + + Optional flags to be used in Photon client SDKs with Op RaiseEvent and Op SetProperties. + Introduced mainly for webhooks 1.2 to control behavior of forwarded HTTP requests. + + + + + Indicates whether to forward HTTP request to web service or not. + + + + + Indicates whether to send AuthCookie of actor in the HTTP request to web service or not. + + + + + Indicates whether to send HTTP request synchronously or asynchronously to web service. + + + + + Indicates whether to send serialized game state in HTTP request to web service or not. + + + + + Asynchronously retrieves the list of regions from the NameServer. + + The LoadBalancingClient instance. + Indicates whether to throw an exception on error. Defaults to true. + Indicates whether to run the client's service during the operation. Defaults to true. + An optional cancellation token that can be used to cancel the operation. Defaults to an empty cancellation token. + A task that represents the asynchronous operation. The task result the region handler after pinging the regions. + + + + Connect to master server. + + Client + App settings + Runs client.Service() during the operation + Optional external cancellation token + When connected to master server callback was called. + Is thrown when the connection terminated + Is thrown when the authentication failed + Is thrown when the operation could not be started + Is thrown when the operation completed unsuccessfully + Is thrown when the operation timed out + + + + Asynchronously reconnects and rejoins a room. + + The client that is reconnecting and rejoining. + Indicates whether to throw an exception on error. Defaults to true. + Indicates whether to run the client's service during the operation. Defaults to true. + An optional cancellation token that can be used to cancel the operation. Defaults to an empty cancellation token. + A task that represents the asynchronous operation. The task result is a boolean indicating whether the operation was successful. + Thrown when the client is still connected or when the reconnecting operation fails to start. + Thrown when the connection is terminated during the operation. + Thrown when the operation completes unsuccessfully. + Thrown when the operation times out. + + + + Disconnects the client. + + Client. + Runs client.Service() during the operation + Optional external cancellation token + Returns when the client has successfully disconnected + Is thrown when the connection terminated + Is thrown when the operation could not be started + Is thrown when the operation completed unsuccessfully + Is thrown when the operation timed out + + + + Create and join a room. + + Client object + Enter room params + Set ErrorCode as result on RoomCreateFailed or RoomJoinFailed + Runs client.Service() during the operation + Optional external cancellation token + When the room has been entered + Is thrown when the connection terminated + Is thrown when the operation could not be started + Is thrown when the operation completed unsuccessfully + Is thrown when the operation timed out + + + + Create or Join a Room. + + Client object + Enter room params + Set ErrorCode as result on RoomCreateFailed or RoomJoinFailed + Runs client.Service() during the operation + Optional external cancellation token + When the room has been entered + Is thrown when the connection terminated + Is thrown when the operation could not be started + Is thrown when the operation completed unsuccessfully + Is thrown when the operation timed out + + + + Join room. + + Client object + Enter room params + Set ErrorCode as result when JoinRoomFailed + Runs client.Service() during the operation + Optional external cancellation token + When room has been entered + Is thrown when the connection terminated + Is thrown when the operation could not be started + Is thrown when the operation completed unsuccessfully + Is thrown when the operation timed out + + + + Join random or Create room + + Client object + Join random room params + Enter room params + Set ErrorCode as result when operation fails with ErrorCode + Runs client.Service() during the operation + Optional external cancellation token + When inside a room + Is thrown when the connection terminated + Is thrown when the operation could not be started + Is thrown when the operation completed unsuccessfully + Is thrown when the operation timed out + + + + Join a Random Room + + Client object + Join random room params + Set ErrorCode as result when operation fails with ErrorCode + Runs client.Service() during the operation + Optional external cancellation token + When inside a room + Is thrown when the connection terminated + Is thrown when the operation could not be started + Is thrown when the operation completed unsuccessfully + Is thrown when the operation timed out + + + + Join a Lobby + + Client object + Lobby to join + Set ErrorCode as result when operation fails with ErrorCode + Runs client.Service() during the operation + Optional external cancellation token + When inside a Lobby + + + + Create a instance, sets up the Photon callbacks, schedules removing them, create a connection service task. + The handler will monitor the Photon callbacks and complete, fault accordingly. + Use the callbacks to change the default handling. + can complete with ErrorCode.Ok, exception on errors and a timeout . + + Client + The default implementation will throw an exception on every unexpected result, set this to false to return a result instead + Runs client.Service() during the operation + Optional external cancellation token + Photon Connection Handler object + + + + Starts a task that calls every updateIntervalMs milliseconds. + The task is stopped by the cancellation token from . + It will set an exception on the TaskCompletionSource if after the timeout it is still not completed. + + Client + Cancellation token to stop the update loop + Completion source is notified on an exception in Service() + + + + Convert a into a + + + + + Convert a into a + + + + + Calculate the total size a would take when serialized + + Hashtable to check the size + Total size of the Hashtable in serialized format + + + + Convert the Room Custom Properties into a + + RoomInfo to extract the custom properties + with the data + + + + Calculate the total size a would take when serialized + + Dictionary to check the size + Total size of the Dictionary in serialized format + + + + Settings for Fusion application + + + + + Select the Relayed Connection Encryption Mode + + + + + Photon Session EmptyRoomTTL in milliseconds. Valid only in Shared Mode. + + + + + Returns a copy of the current object. + + Copy of the current object + + + + Returns a string that represents the current object. + + + + + Collection of connection-relevant settings, used internally by PhotonNetwork.ConnectUsingSettings. + + + Includes the AppSettings class from the Realtime APIs plus some other, PUN-relevant, settings. + + + + The photon settings class, which is wrapped by this ScriptableObject. + + + + Serialized server settings, written by the Setup Wizard for use in ConnectUsingSettings. + + + + Try to get the global settings + + Output settings + True if the settings are loaded, false otherwise + + + + Check if the global settings are loaded + + + + + Holds a Custom Session Property value + + + + + Internal stored value + + + + + Get the Type of the internal stored value + + + + + Signal if this Session Property is a int value + + + + + Signal if this Session Property is a string value + + + + + Signal if this Session Property is a bool value + + + + + Convert a into int + + + + + Convert a int into a + + + + + Convert a into string + + + + + Convert a string into a + + + + + Convert a into bool + + + + + Convert a bool into a + + + + + Signal if a particular object is supported as a + + Object ref to check + True if obj is of a supported type, false otherwise + + + + Convert a particular object into a . + Check the object type support using . + If the object type is not supported, will be thrown. + + Object reference to be converted + Instance of a if type is supported, null otherwise + If the object type passed as argument is not supported + + + + String representation of the + + +
+
diff --git a/Assets/Photon/Fusion/Assemblies/Fusion.Realtime.xml.meta b/Assets/Photon/Fusion/Assemblies/Fusion.Realtime.xml.meta new file mode 100644 index 00000000..33ad520b --- /dev/null +++ b/Assets/Photon/Fusion/Assemblies/Fusion.Realtime.xml.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 0e69dcdf71bbf0a418c882b5a3e024e3 +labels: +- FusionCodeDoc +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Assemblies/Fusion.Runtime.dll b/Assets/Photon/Fusion/Assemblies/Fusion.Runtime.dll new file mode 100644 index 00000000..1328e544 Binary files /dev/null and b/Assets/Photon/Fusion/Assemblies/Fusion.Runtime.dll differ diff --git a/Assets/Photon/Fusion/Assemblies/Fusion.Runtime.dll.meta b/Assets/Photon/Fusion/Assemblies/Fusion.Runtime.dll.meta new file mode 100644 index 00000000..a67cab8c --- /dev/null +++ b/Assets/Photon/Fusion/Assemblies/Fusion.Runtime.dll.meta @@ -0,0 +1,38 @@ +fileFormatVersion: 2 +guid: e725a070cec140c4caffb81624c8c787 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: + Fusion.HitboxRoot: -2000 + Fusion.NetworkMecanimAnimator: 600 + Fusion.NetworkObject: 5000 + Fusion.NetworkTransform: -1400 + Fusion.NetworkObjectInactivityGuard: -10000 + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Any: + second: + enabled: 1 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + Windows Store Apps: WindowsStoreApps + second: + enabled: 0 + settings: + CPU: AnyCPU + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Assemblies/Fusion.Runtime.xml b/Assets/Photon/Fusion/Assemblies/Fusion.Runtime.xml new file mode 100644 index 00000000..fbd3955e --- /dev/null +++ b/Assets/Photon/Fusion/Assemblies/Fusion.Runtime.xml @@ -0,0 +1,17979 @@ + + + + Fusion.Runtime + + + + + Memory Allocator + + + + Heap Alignment + + + Replicate Word Shift + + + Replicate Word Size + + + Replicate Word Align + + + Bucket Count + + + Bucket Invalid + + + + LogPointerInfo + + + + + Returns the total size in bytes used by the allocator segments. + + + + + Fills the given MemoryStatisticsSnapshot object with memory allocation statistics. + + + + + Allocates a memory segment of the specified size from the allocator. + + Size of the memory segment to allocate. + Pointer to the allocated memory segment. + Thrown if the size is less than 1 or greater than or equal to the block byte size. + Thrown if all buckets are full and memory cannot be allocated. + + + + + + + + + + + + + + + + Returns a string representation of the block. + + + + + Memory Allocator Configuration + + + + + Config Size + + + + + Default Block Shift + + + + + Default Block Count + + + + + Block Shift Config value + + + + + Block Count Config value + + + + + Globals Size Config value + + + + + Block Size in Bytes + + + + + Block Size in Words + + + + + Heap Size in Bytes + + + + + Heap Size Allocated in Bytes + + + + + Config Constructor + + Block Shift + Block Count + Globals Size + + + + Check Config equality + + Config Ref + True if has the same values + + + + Check Config equality + + Any object reference + True if obj is a and has the same values + + + + Get Hash Code + + Hash Code + + + + Config ToString + + + + + Page Bit Shift Lookup Table + + + + + Ptr + + + + + Ptr Equality Comparer + + + + + Ptr Equality Comparer + + Ptr X + Ptr Y + True if point to the same Address + + + + Get Hash Code + + Ptr + Ptr Address + + + + Ptr Size + + + + + Null Ptr + + + + + Ptr Address + + + + + Check Ptr equality + + Ptr Ref + True if points to the same Address + + + + Check Ptr equality + + Any object reference + True if obj is a and points to the same Address + + + + Hash Code, same as + + + + + + to String + + in Hexadecimal format + + + + Implicit Bool Operator + Check if is not 0 + + + to check + + + True if is not 0 + + + + + Implicit Ptr Equals Operator + + + A + + + B + + + True if is the same + + + + + Implicit Ptr Not Equals Operator + + + A + + + B + + + True if is not the same + + + + + Implicit Ptr Sum Operator + + + to add to + + + Value to add + + + with increased by + + + + + Implicit Ptr Subtraction Operator + + + to subtract from + + + Value to subtract + + + with decreased by + + + + + Capacity Attribute + + + + + Total Capacity + + + + + CapacityAttribute Constructor + + + + + + Default For Property Attribute + + For non-serialized properties + + + + + Property Name + + + + + Property Word Offset + + + + + Property Word Count + + + + + DefaultForPropertyAttribute Constructor + + + + + + + + Fixed Buffer Property Attribute + + + + + Fixed Buffer Type + + + + + Fixed Buffer Surrogate Type + + + + + Fixed Buffer Capacity + + + + + FixedBufferPropertyAttribute Constructor + + + + + + + + Network Assembly Ignore Attribute + + + + + Network Assembly Weaved Attribute + + + + + Network Behaviour Weaved Attribute + + + + + Word Count + + + + + NetworkBehaviourWeavedAttribute Constructor + + + + + + Network Deserialize Method Attribute + + + + + + Flags a property of for network state synchronization. + The property should have empty get and set defines, which will automatically be replaced with networking code via IL Weaving. + + + Inside of INetworkStruct, do not use AutoProperties (get; set;), as these will introduce managed types into the struct, which are not allowed. Instead use '=> default'. + + | [Networked]

+ | public string StringProp { get => default; set { } } +
+
+
+
+ + + Name of the field that holds the default value for this networked property. + + + + + Default constructor for NetworkedAttribute + + + + + OnChangedRender Attribute + + + This attribute is used to specify a method that should be called when the property changes. + + + + + Gets the name of the method to be called when the property changes. + + + + + Initializes a new instance of the class. + + The name of the method to be called when the property changes. + Thrown when is null or empty. + + + + Networked Weaved Attribute + + Networked Property Attribute + + + + + Networked Property Word Offset + + + + + Networked Property Word Count + + + + + NetworkedWeavedAttribute Constructor + + + + + + + An attribute emitted by the weaver to mark a string field as networked. + + + + Max capacity of the string (in characters) + Name of the field that servers as cache + + + + Max capacity of the string (in characters) + + + + + Name of the field that servers as cache + + + + + Network Input Weaved Attribute + + + + + Word Count + + + + + NetworkInputWeavedAttribute Constructor + + + + + + Network Prefab Attribute + + + + + Network Rpc Static Weaved Invoker Attribute + Contains info about a static weaved RPC Method + + + + + RPC Key + + + + + NetworkRpcStaticWeavedInvokerAttribute Constructor + + + + + + Network Rpc Weaved Invoker Attribute + Contains info about a weaved RPC Method + + + + + RPC Key + + + + + RPC Sources + + + + + RPC Targets + + + + + NetworkRpcWeavedInvokerAttribute Constructor + + + + + + + + Network Serialize Method Attribute + + + + + If set, this changes expected Wrap method signature to int Name(NetworkRunner, T, byte*) and Unwrap to int Name(NetworkRunner, byte*, ref T). + In both cases, the result is the number of bytes written/read and can not be greater than what's declared here. + + + + + Describes the total number of WORDs a uses. + + + + + Word Count + + + + + If the is Generic Composite + + + + + NetworkStructWeavedAttribute Constructor + + word count + + + + Enables a special inspector drawer for Unity Rect type, specially designed for editing RectTransforms using normalized values. + + + + + Signal if Y should be inverted + + + + + Set the Aspect Ratio + + + + + Constructor for . InvertY inverts Y handling, for RectTransforms which treat lowerRight as origin, rather than upper left. + + Invert Y handling + Expressed as Width/Height, this defines the ratio of the box shown in the inspector. Value of 0 indicates game window resolution will be used. + + + + Preserve In Plugin Attribute + + + + + + + + + + + + + + + Override default render settings for [Networked] properties. + + + + + + Force this property to be rendered in this . + + + This setting is prioritized over and overrides. + + + + + + + Force this property to be rendered using this (in the chosen ). + + + This setting is prioritized over and overrides. + + + + + + + Override the default interpolation method for this property. The method's signature must match: + + + static T MethodName(T from, T to, float alpha) { /* ... */ } + + + + + + Default constructor for RenderAttribute + + + + + RenderAttribute Constructor + + reference + reference + + + + Render Weaved Attribute + + + + + RenderWeavedAttribute Constructor + + + + + Resolve Network Prefab Source Attribute + + + + + Unity ContextMenuItemAttribute + + + + + ContextMenuItemAttribute Order + + + + + ContextMenuItemAttribute Constructor + + + + + Unity DelayedAttribute + + + + + DelayedAttribute Order + + + + + Unity HeaderAttribute + + + + + HeaderAttribute Order + + + + + HeaderAttribute Constructor + + + + + Unity MinAttribute + + + + + MinAttribute Order + + + + + MinAttribute Constructor + + + + + Unity MultilineAttribute + + + + + MultilineAttribute Order + + + + + Unity NonReorderableAttribute + + + + + NonReorderableAttribute Order + + + + + Unity RangeAttribute + + + + + RangeAttribute Order + + + + + RangeAttribute Constructor + + + + + Unity SerializeField + + + + + Unity SerializeReference + + + + + Unity SpaceAttribute + + + + + SpaceAttribute Order + + + + + SpaceAttribute Constructor + + + + + Unity TooltipAttribute + + + + + TooltipAttribute Order + + + + + TooltipAttribute Constructor + + + + + Unity NonSerializedAttribute + + + + + Unity FormerlySerializedAsAttribute + + + + + FormerlySerializedAsAttribute Constructor + + + + + Weaver Generated Attribute + + + + + Wrapper around the Fusion LBC Implementation + + It will control and manage the communication between Fusion and the Photon Cloud + + + + + Fusion LBC Client Reference + + + + + ID of this Communicator. This reflects the Actor Number of the Peer inside the Room + + + + + Flag to signal if this Communicator was extracted and will be reused + + + + + Responsible to deal with LoadBalancingClient Events + + + Responsible for dealing and managing the API used to communicate with the Photon Cloud. + This also includes: + - Send/Reply to Protocol Messages + - Query for Reflexive Information + - Perform NAT Punchthrough + - Manage the Realtime client + - Respond to/deal with Photon Cloud events + + + + + Signal if the local peer is connected to the Photon Cloud and can perform extra actions, like creating/joining a Room. + + + + + Photon Client UserID + + + + + Signal if the local peer is already inside a Room + + + + + Signal if the local peer is already inside a Lobby + + + + + Current Fusion Session Join Stage + + + + + Current ProtocolMessageVersion + + + + + Max Number of players a Session can handle + + + + + Signal if the local peer is also the Master Client of the Current Room + + + + + Get the internal used by the Client to perform the authentication + + + + + Reference to the current active communicator + + + + + Get the local client cached region summary + + + + + Signal if the local peer will try or accept connections using NAT Punch + + + + + Flag to signal if the Photon Cloud connection is encrypted by default + + + + + Custom STUN Server + + + + + Exposes the current NAT Type from the local Peer + + + + + player ref assigned by the cloud + + + + + Builds a new CloudService reference + + Reference to the current active Runner + Custom Photon App Settings + Optional external Communicator + + + + Extract the internal Communicator for later re-use + + Current used by this instance with all resetted settings + + + + Update and perform all pending actions related to the Photon Cloud communication + + + + + Connect the local peer to Photon Cloud using an async process. + + Custom Authentication Values used to Auth the local peer + Custom Photon App Settings + External CancellationToken + Signal if the LoadBalancingClient should use the Default or Alternative Ports + Async Task of the connect to Photon Cloud process. Can be used to wait for the process to be finished + + + + Join the Peer to a specific Lobby, either a prebuild or a custom one + + Lobby Type to Join + Custom Lobby ID + Custom Lobby Type + True if the operation could be completed. + + + + Make the local Peer Create/Join a Room based on Start Game Arguments + + --------------------->Yes--->CreateOrJoin + SharedMode--->| Valid Room Name | + --------------------->No---->[RandomRoomName]-->JoinRandomOrCreate + + --------------------->Yes--->CreateOrJoin + ClientMode--->| Valid Room Name | + --------------------->No---->[RandomRoomName]-->JoinRandom + + ServerMode-- --------------------->Yes----------------------------| + |->| Valid Room Name | v + HostMode---- --------------------->No---->[RandomRoomName]-->CreateOrJoin + + + Start Game Args ref + External Cancellation Token + Task of the Join Room process + + + + Disconnect the Local Peer from the Photon Cloud. + + Async Task of the disconnect from Photon Cloud process. Can be used to wait for the process to be finished + + + + Get the UserID of another Player Actor in the Room + + ActorID of a Player inside the Room + Player UserID + + + + Try to get the ActorId associated with a specific Client Unique Id + + Client Unique Id to check + ActorId associated with that Client Unique Id + True if the ActorId can be found + + + + Callback fire on every connection attempt with a remote Server. + + It is used while trying to hole-punch the remote server and enables the manager to swap the target endpoint in between attempts. + This is necessary to maintain a flow of attempts even if we exchange the local/public/relay endpoints + + Current attempt number + Max number of attempts + Flag if target EndPoint should change + New target EndPoint + + + + Start the connection process with a Remote Server + + Starting NAT Punch state, see for more info + Remote Server EndPoint to connect to + + + + Disposes the current + + + + + Callback invoked when any Room Property has changed + + + + + Callback invoked when the Room list is updated with data from the Cloud + + New List of + + + + Send a Protocol Message to Fusion Plugin + + Optional external cancellation token + + + + Send a Protocol Message to the Fusion Plugin + + Reference to the Project Config to be sent + + + + Send a Protocol Message to the Fusion Plugin + + Reference to StunResult used to build the Protocol Message + + + + Build and send the latest Server Snapshot to the Fusion Plugin + + + + + Handles a Confirmation Protocol Message sent by the Fusion Plugin + + Sender Actor Number + Join Protocol Message + + + + Handles a Protocol Message sent by the Fusion Plugin + + Sender Actor Number + Start Protocol Message + + + + Handles a Protocol Message sent by the Fusion Plugin + + Sender Actor Number + Disconnect Protocol Message + + + + Handles a Protocol Message sent by the Fusion Plugin + + Sender Actor Number + NetworkConfigSync Protocol Message + + + + Handles a Protocol Message sent by the Fusion Plugin + + Sender Actor Number + ReflexiveInfo Protocol Message + + + + Confirms or waits for confirmation from the Plugin of the Join Message + + True if the Join Confirmation was received, false otherwise + + + + Send an empty message to Photon Cloud so the LBC Connection keeps alive + + + + + Periodically sends a host migration snapshot if the runner is in host mode and is currently running. + + + A task that represents the asynchronous operation. The task result is a boolean indicating whether the service should continue running. + Returns true if the runner is not running, indicating that the service should continue. + Returns false if the runner is not in host mode, indicating that the service should stop. + + Thrown when the operation is cancelled. + + + + Reverse ping will send Empty UDP Packets to the RemoteAddr in order to setup the Routing Table + on the current NAT of the Server, forcing it to allow packages from the remote client to be received + + Remove EndPoint to ping + + + + Run the STUN Service to retrieve the current Reflexive Addresses of the local peer + + Running Task of the STUN Query Procedure + + + + Update the internal used to start the Fusion Runner + + New arguments + + + + Check if Remote Private EndPoint appears to be in the same Subnet + + True if in same Subnet + + + + Converts a to a + + + ref + Room Region + ref + + + + Holds information about the local peer used to Join/Start/Connect to a remote Peer + using the Photon Cloud as backend + + + + + Client Server Lobby + + + + + Shared Lobby + + + + + Reference to the initialization arguments set by the user. + They are used to start the Fusion Runner + + + + + Connection Stage related to the current EndPoint Type used by the client to connect a remote server + + + + + Describes the current state of the Join process + + + + + Describe the current protocol version we are using to communicate with the Plugin + + + + + Remote Server Reflexive Info. Stores private and public EndPoint of the remote server. + + + + + Local Reflexive Info. Stores private and public EndPoint of the local peer. + + + + + Stores the local peer Unique Id + + + + + Stores the local peer Unique Id + + + + + Stores the local Encryption Token + + + + + Stores requests sent by the plugin + + + + + Last Disconnect Msg Received from the Plugin + + + + + Mapping between Actor UniqueId and its ReflexiveInfo + + + + + Define a list of Requests that may be asked by the Plugin Server + + + + + No Request + + + + + Request for the Local Reflexive Info + + + + + Describes the current Target Address Type used in the NAT Punch procedure + + + + + No connection procedure is running + + + + + Trying to Connect to LAN EndPoint + + + + + Trying to Connect to WAN EndPoint + + + + + Trying to Connect to Relay EndPoint + + + + + Stage of the Join Process. + + When starting the peer, the first thing we need to make sure is to have Joined the Room + with a confirmation from the Plugin, this will signal the current stage of this + + + + + Join Request not sent yet + + + + + Join Request Sent, waiting for confirmation + + + + + Join Confirmation Received, all good + + + + + Failed to receive Join Confirmation after a timeout + + + + + Stores the data of a "Request to Ping" used by the Server in Client-Server Mode + to send arbitrary "pings" to a connecting Client. + + This allows the local NAT Table to be updated with the right mapping information + from the remote client, increasing the chance of the local Server to receive any + connect request from the remote peer. + + + + + Delay between pings + + + + + Total number of pings to send + + + + + Countdown for the next ping + + + + + Remote Client Reflexive Info, used to getter the Public EndPoint to send the ping + + + + + Extension methods to + + + + + Holds information about a Lobby + + + + + Flag to signal if the is ready for use. + This is only true if the peer is currently connected to a Lobby. + + + + + Lobby Name + + + + + Stores the current connected Region + + + + + Session Lobby Type + + + + + Invalid Session Lobby Type + + + + + ClientServer Lobby + + + + + Shared Lobby + + + + + Custom Lobby - works in conjuction with a Lobby Name/ID + + + + + Holds information about the Game Session + + + + + Flag to signal if the is ready for use + + + + + Stores the current Room Name + + + + + Stores the current connected Region + + + + + Signal if the current connected Room is visible + + + + + Signal if the current connected Room is open + + + + + Room Custom Properties + + + + + Current number of peers inside this Session, this includes the Server/Host and Clients + + + + + Max number of peer that can join this Session, this value always include an extra slot for the Server/Host + + + + + Check if the reference is not Null and is Valid. + + Session Info + + + + Update or change the Custom Properties of the current joined Room + + New custom properties + + + + String representation of a + + Formatted + + + + Interface for callback. + Called after the re-simulation loop (when applicable), and also after the forward simulation loop. + Implement this interface on and classes. + + + + + Called after the re-simulation loop (when applicable), and also after the forward simulation loop. + Only called on Updates where re-simulation or forward ticks are processed. + + True if this is being called during the re-simulation loop. False if during the forward simulation loop. + How many re-simulation or forward ticks are going to be processed. + + + + Callback interface for . + Called at the very start of the resimulation loop (on clients with prediction enabled), + immediately after state is set to the latest server snapshot. + Implement this interface on and classes. + + + + + Called at the very start of the resimulation loop (on clients with prediction enabled), + immediately after state is set to the latest server snapshot. + + + + + Used to mark NetworkBehaviors that need to be react after a Host Migration process + + + + + Invoked after the Host Migration happens in order to setup non-networked data on NetworkBehaviors + + + + + Interface for callback. + Called after the render loop. + + + + + Called after the render loop. + + + + + Interface for callback. + Called after the object is spawned. + + + + + Called after the object is spawned. + + + + + Interface for callback. + Called after each tick simulation completes. + Implement this interface on and classes. + + + + + Called after each tick simulation completes. + + + + + Interface for the callback, which is called at the end of each Fusion Update segment. + Implement this interface on and classes. + + + + + Called at the end of the Fusion Update loop, before all Unity MonoBehaviour.Update() callbacks. + + + + + Invoked after updating remote prefabs + + + + + Invoked after updating remote prefabs + + + + + Interface for callback. + Called before the re-simulation loop (when applicable), and also before the forward simulation loop. + Implement this interface on and classes. + + + + + Called before the re-simulation loop (when applicable), and also before the forward simulation loop. + Only called on Updates where re-simulation or forward ticks are processed. + + True if this is being called during the re-simulation loop. False if during the forward simulation loop. + How many re-simulation or forward ticks are going to be processed. + + + + Callback interface for . + Called at the very start of the re-simulation loop (on clients with prediction enabled), + before state is set to the latest server snapshot. + Implement this interface on and classes. + + + + + Called at the very start of the re-simulation loop (on clients with prediction enabled), + before state is set to the latest server snapshot. + + + + + Interface for callback. + Called before the copy of the previous state. + + + + + Called before the copy of the previous state. + + + + + Interface for callback. + Implement this interface on and classes. + + + + + Called immediately before the registers hitboxes in a snapshot. + + + + + Interface for callback. + Called before both the re-simulation (when applicable) and forward simulation loops. + Implement this interface on and classes. + + + + + Called before both the re-simulation (when applicable) and forward simulation loops. + Only called on updates that have forward ticks to process. + + How many forward ticks are going to be processed. + + + + Interface for callback. + Called before each tick is simulated. + Implement this interface on and classes. + + + + + Called before each tick is simulated. + + + + + Interface for the callback, which is called at the beginning of each Fusion Update segment. + Implement this interface on and classes. + + + + + Called at the start of the Fusion Update loop, before the Fusion simulation loop. + + + + + Invoked before updating remote prefabs + + + + + Invoked before updating remote prefabs + + + + + Interface for callback. + Called when a is despawned. + + + + + Called when a is despawned. + + NetworkRunner that despawned the . + Whether the has state. + + + + Interface for handling the event when a player enters the area of interest. + + + + + Method to be called when a player enters the area of interest. + + The player who entered the area of interest. + + + + Interface for handling the event when a player exits the area of interest. + + + + + Method to be called when a player exits the area of interest. + + The player who exited the area of interest. + + + + Interface for handling the event when a local prefab is created. + + + + + Method to be called when a local prefab is created. + + + + + Interface for handling the event when a player joins the game. + + + + + Method to be called when a player joins the game. + + The player who joined the game. + + + + Interface for handling the event when a player leaves the game. + + + + + Method to be called when a player leaves the game. + + The player who left the game. + + + + Tag Interface for all public facing Fusion interfaces + + + + + Interface for handling the event when a remote prefab is created. + + + + + Method to be called when a remote prefab is created. + + + + + Interface for handling the event when a scene load operation is completed. + + + + + Method to be called when a scene load operation is completed. + + The information about the loaded scene. + + + + Struct that contains information about a scene after it has been loaded. + + + + + Reference to the loaded scene. + + + + + Array of NetworkObjects present in the loaded scene. + + + + + The loaded Unity scene. + + + + + Array of root GameObjects present in the loaded Unity scene. + + + + + Constructs a new SceneLoadDoneArgs struct. + + Reference to the loaded scene. + Array of NetworkObjects present in the loaded scene. + The loaded Unity scene. + Array of root GameObjects present in the loaded Unity scene. + + + + Interface for handling the event when a scene load operation is started. + + + + + Method to be called when a scene load operation is started. + + Reference to the scene that is being loaded. + + + + Interface for callback. + Called when the joins AreaOfInterest. + Implement this interface on and classes. + + + + + Called when the joins AreaOfInterest. + Object is now receiving snapshot updates. + Object will execute FixedUpdateNetwork() and Render() methods until the object leaves simulation. + + + + + Interface for the callback. + Called when the leaves AreaOfInterest. + Implement this interface on and classes. + + + + + Called when the leaves AreaOfInterest. + Object is no longer receiving snapshot updates. + Object will stop executing FixedUpdateNetwork() and Render() methods until the object rejoins simulation. + + + + + Interface for handling the event when an object is spawned. + + + + + Method to be called when an object is spawned. + + + + + Interface for handling the event when the state authority changes. + + + + + Method to be called when the state authority changes. + + + + + Interface for handling the event when the input authority is gained. + + + + + Method to be called when the input authority is gained. + + + + + Interface for handling the event when the input authority is lost. + + + + + Method to be called when the input authority is lost. + + + + + Used in plugin. Indicates that is exported and can be assigned from + serialized data. + + + + + Word count read from serialized data. + + + + + Base class for Fusion network components, which are associated with a . + Derived from , components derived from this class are associated with a and . + Components derived from this class are associated with a parent . + and can use the on properties to automate state synchronization, + and can use the on methods, to automate messaging. + + + + + Gets a value indicating whether the state buffer is valid. + + + + + Gets the state buffer associated with the network behaviour. + + + + + On Render Default Change Detector instance + + + + + Gives access to the offset (in 32 bit words) and count (in 32 bit words) of this behaviour backing data + + + + + The tick the data on this networked behaviour changed + + + + + The unique identifier for this network behaviour. + + + + + Returns true if the of the associated is the designated as Input Source for this network entity. + + + + + Returns true if the associated is the State Source for this network entity. + + + + + Returns true if the associated is neither the Input nor State Authority for this network entity. + It is recommended to use ! or ! when possible instead, + as this check requires evaluating both authorities - and is therefore less performant than the individual checks. + + + + + Override this value for custom memory allocations. + This is for advanced use cases only, and cannot be used if is used in the derived class. + + + + + Returns true if the object is writable in the editor - i.e. when it is not attached + or has the state authority. + + + + + Gets a bitmask of flags, representing the current local authority over this . + + + + + Controls if this network behaviours state is replicated to a player or not + + The player to change replication status for + true = replicate, false = don't replicate + + + + Sets the default replicated state for this behaviour, this by default is true so calling ReplicateToAll(true) does nothing if ReplicateToAll(false) hasn't been called before + + The default state of this behaviour + + + + Copies entire state of passed in source + + Source to copy data from + + + + Fixed update callback for networked behaviours. + + + + + Resets the state of the object to the original state + + + + + Copies the backing fields to the state. This method is meant to be overridden in derived classes. + + Indicates whether this is the first time the method is called. + + + + Copies the state to the backing fields. This method is meant to be overridden in derived classes. + + + + + Post spawn callback. + + + + + Called before the network object is despawned + + The runner that owns the object + If the state of the behaviour is still accessible + + + + Allows read and write access to the internal state buffer + + The offset to generate a ref for, in integer words + The type of the ref to generate + Reference to the location in memory defined by offset + + + + Provides a reader for network arrays of type T. + + The type of elements in the network array. + + + + Contains data about the property to be read. + + + + + Contains the reader for the elements in the network array. + + + + + Reads a network array from the provided network behaviour buffer. + + The network behaviour buffer to read the network array from. + A read-only view of the network array. + + + + Provides a reader for network linked lists of type T. + + The type of elements in the network linked list. + + + + Contains data about the property to be read. + + + + + Contains the reader for the elements in the network array. + + + + + Reads a network linked list from the provided network behaviour buffer. + + The network behaviour buffer to read the network linked list from. + A read-only view of the network linked list. + + + + Provides a reader for network dictionaries with keys of type K and values of type V. + + The type of keys in the network dictionary. + The type of values in the network dictionary. + + + + Contains data about the property to be read. + + + + + Contains the reader for the keys in the network dictionary. + + + + + Contains the reader for the values in the network dictionary. + + + + + Reads a network dictionary from the provided network behaviour buffer. + + The network behaviour buffer to read the network dictionary from. + A read-only view of the network dictionary. + + + + Provides a reader for network behaviours of type T. + + The type of the network behaviour. + + + The NetworkRunner associated with the network behaviour. + + + The reader for the network behaviour's ID. + + + + Reads a network behaviour from the provided network behaviour buffer. + + The network behaviour buffer to read the network behaviour from. + The network behaviour of type T read from the buffer. Returns null if the behaviour is not found. + + + + Reads two network behaviours from the provided network behaviour buffers. + + The first network behaviour buffer to read the network behaviour from. + The second network behaviour buffer to read the network behaviour from. + A tuple containing the two network behaviours of type T read from the buffers. + + + + Provides a reader for properties of type T in a network behaviour. + + The type of the property in the network behaviour. Must be unmanaged. + + + + Contains data about the property to be read. + + + + + Constructs a new PropertyReader with the provided data. + + The data about the property to be read. + + + + Constructs a new PropertyReader with the provided offset. + + The offset of the property in the network behaviour buffer. + + + + Reads a property of type T from the provided network behaviour buffer. + + The network behaviour buffer to read the property from. + The property of type T read from the buffer. + + + + Reads a property of type T from the provided network behaviour buffers. + + The first network behaviour buffer to read the property from. + The second network behaviour buffer to read the property from. + A tuple containing the property of type T read from the first and second buffers. + + + + Gets a BehaviourReader for a network behaviour of type T. + + The type of the network behaviour. + The name of the property to be read. + A BehaviourReader for the network behaviour of type T. + + + + Gets an ArrayReader for a network array of type T. + + The type of elements in the network array. + The name of the property to be read. + An ArrayReader for the network array of type T. + + + + Gets a LinkListReader for a network linked list of type T. + + The type of elements in the network linked list. + The name of the property to be read. + A LinkListReader for the network linked list of type T. + + + + Gets a DictionaryReader for a network dictionary with keys of type K and values of type V. + + The type of keys in the network dictionary. + The type of values in the network dictionary. + The name of the property to be read. + A DictionaryReader for the network dictionary with keys of type K and values of type V. + + + + Gets a BehaviourReader for a network behaviour of type T. + + The type of the network behaviour. + The NetworkRunner associated with the network behaviour. + The type of the behaviour to be read. + The name of the property to be read. + A BehaviourReader for the network behaviour of type T. + + + + Gets a BehaviourReader for a network behaviour with a specific property of type TProperty. + + The type of the network behaviour. + The type of the property in the network behaviour. + The NetworkRunner associated with the network behaviour. + The name of the property to be read. + A BehaviourReader for the network behaviour with the specific property of type TProperty. + + + + Gets a PropertyReader for a property of type TProperty in a network behaviour of type TBehaviour. + + The type of the network behaviour. + The type of the property in the network behaviour. Must be unmanaged. + The name of the property to be read. + A PropertyReader for the property of type TProperty in the network behaviour of type TBehaviour. + + + + Gets a PropertyReader for a property of type T in a network behaviour of a specific type. + + The type of the property in the network behaviour. Must be unmanaged. + The type of the network behaviour. + The name of the property to be read. + A PropertyReader for the property of type T in the network behaviour of the specified type. + + + + Gets an ArrayReader for a network array of type T in a network behaviour of a specific type. + + The type of elements in the network array. + The type of the network behaviour. + The name of the property to be read. + + An ArrayReader for the network array of type T in the network behaviour of the specified type. + + + + Gets a DictionaryReader for a network dictionary with keys of type K and values of type V in a network behaviour of a specific type. + + The type of keys in the network dictionary. + The type of values in the network dictionary. + The type of the network behaviour. + The name of the property to be read. + + + A DictionaryReader for the network dictionary with keys of type K and values of type V in the network behaviour of the specified type. + + + + Gets a LinkListReader for a network linked list of type T in a network behaviour of a specific type. + + The type of elements in the network linked list. + The type of the network behaviour. + The name of the property to be read. + + A LinkListReader for the network linked list of type T in the network behaviour of the specified type. + + + + Gets a PropertyReader for a property of type T in this network behaviour. + + The type of the property in the network behaviour. Must be unmanaged. + The name of the property to be read. + A PropertyReader for the property of type T in this network behaviour. + + + + Creates a ChangeDetector for this network behaviour. + + The source of the change detector. + Indicates whether to copy the initial state of the network behaviour. + A ChangeDetector for this network behaviour. + + + + Tries to get the snapshot buffers for this network behaviour. + + The buffer representing the state of the network behaviour at the start of the render frame. + The buffer representing the state of the network behaviour at the end of the render frame. + The interpolation factor between the start and end of the render frame. + True if the snapshot buffers are valid, false otherwise. + + + + Determines whether to replicate the network behaviour to the specified player. + This method can be overridden in derived classes to implement custom replication logic. + + The player to potentially replicate the network behaviour to. + True if the network behaviour should be replicated to the player, false otherwise. The default implementation always returns true. + + + + + + + + + + Returns true if it a valid can be found for the current simulation tick (Typically this is used in ). + The returned input struct originates from the , + and if valid contains the inputs supplied by that for the current simulation tick. + + + + + Serializes a NetworkBehaviour into a byte array. + + + The NetworkBehaviour to be serialized. + The byte pointer to write the serialized data to. + The size of the serialized data in bytes. + + + + Deserializes a NetworkBehaviour from a byte array. + + The NetworkRunner associated with the NetworkBehaviour. + The byte pointer to read the serialized data from. + The NetworkBehaviour to store the deserialized data in. + The size of the deserialized data in bytes. + + + + Converts a NetworkBehaviour to a NetworkBehaviourId. + + The NetworkRunner associated with the NetworkBehaviour. + The NetworkBehaviour to be converted. + A NetworkBehaviourId representing the NetworkBehaviour. If the NetworkBehaviour is null or its NetworkObject's Id is default, returns a default NetworkBehaviourId. + + + + Converts a NetworkBehaviour to a NetworkBehaviourId. + + The NetworkBehaviour to be converted. + A NetworkBehaviourId representing the NetworkBehaviour. If the NetworkBehaviour is null or its NetworkObject's Id is default, returns a default NetworkBehaviourId. + + + + Converts a NetworkBehaviourId to a NetworkBehaviour. + + The NetworkRunner associated with the NetworkBehaviour. + The NetworkBehaviourId to be converted. + The NetworkBehaviour represented by the NetworkBehaviourId. If the NetworkBehaviourId is not valid or a NetworkBehaviour with the NetworkBehaviourId does not exist, returns null. + + + + Converts NetworkBehaviour to NetworkBehaviourId + + NetworkBehaviour to convert + NetworkBehaviourId representing the NetworkBehaviour + + + + This method needs to be invoked in user overrides of: + + + + + + + Creates a reference to a value of type T. This method is meant to be used only for [Networked] properties inline initialization. + + The type of the value. Must be unmanaged. + A reference to a value of type T. + Thrown because this method is meant to be used only for [Networked] properties inline initialization. + + + + Creates a reference to a value of type T, initializing it with the provided default value. This method is meant to be used only for [Networked] properties inline initialization. + + The type of the value. Must be unmanaged. + The default value to initialize the value with. + A reference to a value of type T. + Thrown because this method is meant to be used only for [Networked] properties inline initialization. + + + + Creates a pointer to a value of type T. This method is meant to be used only for [Networked] properties inline initialization. + + The type of the value. Must be unmanaged. + A pointer to a value of type T. + Thrown because this method is meant to be used only for [Networked] properties inline initialization. + + + + Creates a pointer to a value of type T, initializing it with the provided default value. This method is meant to be used only for [Networked] properties inline initialization. + + The type of the value. Must be unmanaged. + The default value to initialize the value with. + A pointer to a value of type T. + Thrown because this method is meant to be used only for [Networked] properties inline initialization. + + + + This is a special method that is meant to be used only for [Networked] properties inline initialization. + + + + + This is a special method that is meant to be used only for [Networked] properties inline initialization. + + + + + Change detector for a NetworkBehaviour + + + This class is used to detect changes in a NetworkBehaviour. + It can be used to detect changes in a NetworkBehaviour between two snapshots, or between the current state and a snapshot. + + + + Enum representing the source of a NetworkBehaviour's state. + + + The state is the current simulation state of the NetworkBehaviour. + + + The state is from a previous snapshot of the NetworkBehaviour. + + + The state is from a future snapshot of the NetworkBehaviour. + + + Struct representing a collection of changes detected in a NetworkBehaviour. + + + Array of property names that have changed. + + + The number of properties that have changed. + + + + Constructor for the Enumerable struct. + + Array of property names that have changed. + The number of properties that have changed. + + + + Gets an enumerator for the collection of changes. + + An Enumerator for the collection of changes. + + + + Checks if a property has changed. + + The name of the property to check. + True if the property has changed, false otherwise. + + + Enumerator for the collection of changes detected in a NetworkBehaviour. + + + Array of property names that have changed. + + + The number of properties that have changed. + + + The current index in the array of changed properties. + + + Gets the current property name in the array of changed properties. + + + + Constructor for the Enumerator struct. + + Array of property names that have changed. + The number of properties that have changed. + + + + Resets the enumerator to its initial state. + + + + + Advances the enumerator to the next property in the array of changed properties. + + True if the enumerator was successfully advanced to the next property, false if the enumerator has passed the end of the array. + + + + Finalizes an instance of the class. + + + + + Initializes the ChangeDetector for a given NetworkBehaviour. + + The NetworkBehaviour instance to initialize the ChangeDetector for. + The source of the NetworkBehaviour's state. + Whether to copy the initial state of the NetworkBehaviour. Defaults to true. + + + + Detects changes in a NetworkBehaviour and returns an Enumerable of the changes. + + The NetworkBehaviour instance to detect changes in. + The previous state of the NetworkBehaviour. + The current state of the NetworkBehaviour. + Whether to copy the changes detected. Defaults to true. + An Enumerable of the changes detected in the NetworkBehaviour. + + + + Detects changes in a NetworkBehaviour and returns an Enumerable of the changes. + + The NetworkBehaviour instance to detect changes in. + Whether to copy the changes detected. Defaults to true. + An Enumerable of the changes detected in the NetworkBehaviour. + + + + Provides low level accesss to data buffers that can be read using a NetworkBehaviour.Reader + + + + Gets the tick at which the buffer was created. + + + Gets the length of the buffer. + + + + Gets a value indicating whether the buffer is valid. + A buffer is considered valid if the pointer to the start of the buffer is not null and the length of the buffer is greater than 0. + + + + + Reinterprets the state of the buffer at a given offset as a specific type. + + The type to reinterpret the state as. Must be unmanaged. + The offset at which to start reinterpreting. Defaults to 0. + The state of the buffer at the given offset, reinterpreted as the specified type. + + + + Indexer to get the value at a specific index in the buffer. + + The index to get the value from. + The value at the specified index in the buffer. + + + + Reads a NetworkBehaviour from the buffer using the provided BehaviourReader. + + The type of NetworkBehaviour to read. Must be a subclass of NetworkBehaviour. + The BehaviourReader to use for reading the NetworkBehaviour. + The read NetworkBehaviour. + + + + Reads a property from the buffer using the provided PropertyReader. + + The type of the property to read. Must be unmanaged. + The PropertyReader to use for reading the property. + The read property. + + + + Reads a float property from the buffer using the provided PropertyReader. + + The PropertyReader to use for reading the float property. + The read float property. + + + + Reads a Vector2 property from the buffer using the provided PropertyReader. + + The PropertyReader to use for reading the Vector2 property. + The read Vector2 property. + + + + Reads a Vector3 property from the buffer using the provided PropertyReader. + + The PropertyReader to use for reading the Vector3 property. + The read Vector3 property. + + + + Reads a Vector4 property from the buffer using the provided PropertyReader. + + The PropertyReader to use for reading the Vector4 property. + The read Vector4 property. + + + + Reads a Quaternion property from the buffer using the provided PropertyReader. + + The PropertyReader to use for reading the Quaternion property. + The read Quaternion property. + + + + Implicit conversion operator to bool. + This allows a NetworkBehaviourBuffer instance to be used in conditions directly. + + The NetworkBehaviourBuffer instance to convert. + True if the buffer is valid, false otherwise. + + + + The NetworkBehaviourBufferInterpolator struct is used to interpolate between two NetworkBehaviourBuffer instances. + This is a read-only, ref struct, meaning it cannot be boxed and it can only be used on the stack. + + + + + The NetworkBehaviour instance that this interpolator is associated with. + + + + + The NetworkBehaviourBuffer instance representing the "from" state for interpolation. + + + + + The NetworkBehaviourBuffer instance representing the "to" state for interpolation. + + + + + The interpolation factor, ranging from 0 to 1. + This value is used to interpolate between the "from" and "to" states. + + + + + A value indicating whether this interpolator is valid. + An interpolator is considered valid if it has successfully retrieved the "from" and "to" buffers. + + + + + Constructor for the NetworkBehaviourBufferInterpolator struct. + + The NetworkBehaviour instance that this interpolator is associated with. + + + + Gets the interpolated angle of a property. + + The name of the property to get the angle of. + The interpolated angle of the property. + + + + Gets the interpolated angle of a property. + + The PropertyReader for the property to get the angle of. + The interpolated angle of the property. + + + + Gets the interpolated float value of a property. + + The name of the property to get the float value of. + The interpolated float value of the property. + + + + Gets the interpolated float value of a property. + + The PropertyReader for the property to get the float value of. + The interpolated float value of the property. + + + + Gets the interpolated integer value of a property. + + The name of the property to get the integer value of. + The interpolated integer value of the property. + + + + Gets the interpolated integer value of a property. + + The PropertyReader for the property to get the integer value of. + The interpolated integer value of the property. + + + + Gets the interpolated boolean value of a property. + + The PropertyReader for the property to get the boolean value of. + The interpolated boolean value of the property. + + + + Gets the interpolated boolean value of a property. + + The name of the property to get the boolean value of. + The interpolated boolean value of the property. + + + + Selects the interpolated value of a property by its name. + + The type of the property to select. Must be unmanaged. + The name of the property to select. + The interpolated value of the property. + + + + Selects the interpolated value of a property. + + The type of the property to select. Must be unmanaged. + The PropertyReader for the property to select. + The interpolated value of the property. + + + + Gets the interpolated Vector3 value of a property. + + The name of the property to get the Vector3 value of. + The interpolated Vector3 value of the property. + + + + Gets the interpolated Vector3 value of a property. + + The PropertyReader for the property to get the Vector3 value of. + The interpolated Vector3 value of the property. + + + + Gets the interpolated Vector2 value of a property. + + The name of the property to get the Vector2 value of. + The interpolated Vector2 value of the property. + + + + Gets the interpolated Vector2 value of a property. + + The PropertyReader for the property to get the Vector2 value of. + The interpolated Vector2 value of the property. + + + + Gets the interpolated Vector4 value of a property. + + The name of the property to get the Vector4 value of. + The interpolated Vector4 value of the property. + + + + Gets the interpolated Vector4 value of a property. + + The PropertyReader for the property to get the Vector4 value of. + The interpolated Vector4 value of the property. + + + + Gets the interpolated Quaternion value of a property. + + The name of the property to get the Quaternion value of. + The interpolated Quaternion value of the property. + + + + Gets the interpolated Quaternion value of a property. + + The PropertyReader for the property to get the Quaternion value of. + The interpolated Quaternion value of the property. + + + + Implicit conversion operator to bool. + This allows a NetworkBehaviourBufferInterpolator instance to be used in conditions directly. + + The NetworkBehaviourBufferInterpolator instance to convert. + True if the interpolator is valid, false otherwise. + + + + Represents the unique identifier for a NetworkBehaviour instance. + + + + Size of the NetworkBehaviourId structure in bytes. + + + + The NetworkId of the object this behaviour belongs to. + + + + + The identifier for the behaviour within the object. + + + + + Checks if the NetworkBehaviourId is valid. + A NetworkBehaviourId is valid if its Object is valid and its Behaviour is non-negative. + + + + + Returns a new NetworkBehaviourId with default values. + + + + + Checks if this NetworkBehaviourId is equal to another NetworkBehaviourId. + Two NetworkBehaviourIds are equal if their Objects and Behaviours are equal. + + The other NetworkBehaviourId to compare with. + True if the NetworkBehaviourIds are equal, false otherwise. + + + + Checks if this NetworkBehaviourId is equal to another object. + The object is considered equal if it is a NetworkBehaviourId and its Object and Behaviour are equal to this NetworkBehaviourId's. + + The object to compare with. + True if the object is a NetworkBehaviourId and is equal to this, false otherwise. + + + + Returns a hash code for this NetworkBehaviourId. + The hash code is computed based on the Object's hash code and the Behaviour. + + A hash code for this NetworkBehaviourId. + + + + Returns a string representation of the NetworkBehaviourId. + The string representation is in the format: [Object:{Object}, Behaviour:{Behaviour}]. + + A string representation of the NetworkBehaviourId. + + + + Determines whether two NetworkBehaviourId instances are equal. + Two NetworkBehaviourId instances are considered equal if their Objects and Behaviours are equal. + + The first NetworkBehaviourId to compare. + The second NetworkBehaviourId to compare. + True if the NetworkBehaviourId instances are equal, false otherwise. + + + + Determines whether two NetworkBehaviourId instances are not equal. + Two NetworkBehaviourId instances are considered not equal if their Objects or Behaviours are not equal. + + The first NetworkBehaviourId to compare. + The second NetworkBehaviourId to compare. + True if the NetworkBehaviourId instances are not equal, false otherwise. + + + + This static class provides utility methods for working with NetworkBehaviour objects. + + + + + This structure holds metadata for a NetworkBehaviour object. + + + + + A static field that determines whether to invoke RPC (Remote Procedure Call). + When set to true, RPCs are invoked. When set to false, RPCs are not invoked. + Default value is false. + + + + + Retrieves the metadata for a given type. + + The type for which to retrieve the metadata. + The metadata for the given type if it exists; otherwise, the default value. + + + + Registers the metadata for a given type. + This method checks if the type has an override for the "ReplicateTo" method and stores this information in the metadata. + If the metadata for the type already exists, this method does nothing. + + The type for which to register the metadata. + + + + Retrieves the word count for a given NetworkBehaviour. + If the NetworkBehaviour has a dynamic word count, it is returned. + Otherwise, the static word count for the type of the NetworkBehaviour is returned. + + The NetworkBehaviour for which to retrieve the word count. + The word count for the given NetworkBehaviour. + Thrown when the dynamic word count or the static word count is negative. + + + + Checks if a given type has a static word count. + A type has a static word count if it is a subclass of NetworkBehaviour and its word count is non-negative. + + The type to check. + True if the type has a static word count, false otherwise. + Thrown when the provided type is not a subclass of NetworkBehaviour. + Thrown when the provided type does not have a weaved attribute. + + + + Retrieves the static word count for a given type. + If the word count for the type has not been computed yet, it is computed and stored. + + The type for which to retrieve the static word count. + The static word count for the given type. + Thrown when the provided type is not a subclass of NetworkBehaviour. + Thrown when the provided type does not have a weaved attribute or its word count is negative. + + + + Determines whether the RPC (Remote Procedure Call) invoke delegates should be registered for a given type. + + The type for which to check the registration of RPC invoke delegates. + True if the RPC invoke delegates should be registered for the type, false otherwise. + + + + Registers the RPC (Remote Procedure Call) invoke delegates for a given type. + This method is only available when the FUSION_UNITY compilation symbol is defined. + + The type for which to register the RPC invoke delegates. + Thrown when the provided type is not a subclass of NetworkBehaviour. + Thrown when the provided type does not have a weaved attribute or its word count is negative. + + + + Tries to get the RPC (Remote Procedure Call) invoke delegate array for a given type. + + The type for which to get the RPC invoke delegate array. + When this method returns, contains the RPC invoke delegate array if the type has one; otherwise, null. + + true if the type has an RPC invoke delegate array; otherwise, false. + + + + + Retrieves the index of a static RPC (Remote Procedure Call) based on its key. + + The key of the static RPC. + The index of the static RPC if it exists. + Thrown when the static RPC does not exist. + + + + Tries to get the static RPC (Remote Procedure Call) invoke delegate based on its index. + + The index of the static RPC. + When this method returns, contains the static RPC invoke delegate if it exists; otherwise, default. + True if the static RPC invoke delegate exists; otherwise, false. + + + + Logs an error message indicating that the target of a Remote Procedure Call (RPC) payload is too large. + + Size of the payload. + The name of the RPC that was attempted. + + + + Logs an error message indicating that the target of a Remote Procedure Call (RPC) is not reachable. + + The player reference that is not reachable. + The name of the RPC that was attempted. + + + + Logs an error message indicating that a local simulation is not allowed to send a specific RPC. + + The name of the RPC that was attempted. + The network object on which the RPC was attempted. + The sources from which the RPC was attempted. + + + + Logs a warning message indicating that a local targeted RPC was culled. + + The player reference for which the RPC was culled. + The name of the method that was culled. + + + + Checks if the NetworkBehaviour object is initialized and throws an exception if it is not. + + The NetworkBehaviour object to check. + Thrown when the NetworkBehaviour object is not initialized. + + + + Logs a warning message indicating that a network wrap operation failed. + + The type of the value that failed to be wrapped. + The value that failed to be wrapped. + + + + Logs a warning message indicating that a network wrap operation failed for a specific wrapper type. + + The type of the value that failed to be wrapped. + The value that failed to be wrapped. + The type that was attempted to be used as a wrapper. + + + + Logs a warning message indicating that a network unwrap operation failed. + + + + + Initializes a NetworkArray with the values from a source array. + + The type of the elements in the NetworkArray and source array. + The NetworkArray to initialize. + The source array from which to copy the values. + The name of the NetworkArray for logging purposes. + + If the length of the source array is greater than the length of the NetworkArray, a warning is logged and only the first elements up to the length of the NetworkArray are copied. + + + + + Copies the values from a NetworkArray to a destination array. + + The type of the elements in the NetworkArray and destination array. + The NetworkArray from which to copy the values. + The destination array to which to copy the values. + + If the length of the destination array is not equal to the length of the NetworkArray, a new array of the correct length is created and assigned to the destination array. + + + + + Creates a new array that is a clone of the specified array. + + The type of the elements in the array. + The array to clone. + A new array that is a clone of the specified array. If the specified array is null, an empty array is returned. + + + + Initializes a NetworkLinkedList with the values from a source array. + + The type of the elements in the NetworkLinkedList and source array. Must be unmanaged. + The NetworkLinkedList to initialize. + The source array from which to copy the values. + The name of the NetworkLinkedList for logging purposes. + + If the length of the source array is greater than the capacity of the NetworkLinkedList, a warning is logged and only the first elements up to the capacity of the NetworkLinkedList are copied. + + + + + Copies the values from a NetworkLinkedList to a destination array. + + The type of the elements in the NetworkLinkedList and destination array. + The NetworkLinkedList from which to copy the values. + The destination array to which to copy the values. If the length of the destination array is not equal to the count of the NetworkLinkedList, a new array of the correct length is created and assigned to the destination array. + + + + This method is not meant to be called directly. Calls are injected by the Weaver. + + SimulationBehaviour object. + + + + This method is not meant to be called directly. Calls are injected by the Weaver. + + SimulationBehaviour object. + + + + This method is not meant to be called directly. Calls are injected by the Weaver. + + SimulationBehaviour object. + + + + Initializes a NetworkDictionary with the values from a source dictionary. + + The type of the source dictionary. Must implement IDictionary{K, V}. + The type of the keys in the NetworkDictionary and source dictionary. Must be unmanaged. + The type of the values in the NetworkDictionary and source dictionary. Must be unmanaged. + The NetworkDictionary to initialize. + The source dictionary from which to copy the values. + The name of the NetworkDictionary for logging purposes. + + If the count of the source dictionary is greater than the capacity of the NetworkDictionary, a warning is logged and only the first elements up to the capacity of the NetworkDictionary are copied. + + + + + Copies the values from a NetworkDictionary to a destination dictionary. + + The type of the destination dictionary. Must implement IDictionary{K, V} and have a parameterless constructor. + The type of the keys in the NetworkDictionary and destination dictionary. Must be unmanaged. + The type of the values in the NetworkDictionary and destination dictionary. Must be unmanaged. + The NetworkDictionary from which to copy the values. + The destination dictionary to which to copy the values. If the destination dictionary is null, a new dictionary of type D is created. + + + + Wraps a Dictionary in a SerializableDictionary. + + The type of the keys in the dictionary. Must be unmanaged. + The type of the values in the dictionary. Must be unmanaged. + The dictionary to wrap. + A SerializableDictionary that wraps the specified dictionary. + + + + A utility structure for initializing NetworkArray and NetworkLinkedList with inline initialization. + + The type of the elements in the NetworkArray and NetworkLinkedList. + + + + Implicitly converts an ArrayInitializer to a NetworkArray. + + The ArrayInitializer to convert. + A NetworkArray initialized with the values from the ArrayInitializer. + Thrown always as this method is meant to be used only for [Networked] properties inline initialization. + + + + Implicitly converts an ArrayInitializer to a NetworkLinkedList. + + The ArrayInitializer to convert. + A NetworkLinkedList initialized with the values from the ArrayInitializer. + Thrown always as this method is meant to be used only for [Networked] properties inline initialization. + + + + A utility structure for initializing NetworkDictionary with inline initialization. + + The type of the keys in the NetworkDictionary. + The type of the values in the NetworkDictionary. + + + + Implicitly converts a DictionaryInitializer to a NetworkDictionary. + + The DictionaryInitializer to convert. + A NetworkDictionary initialized with the values from the DictionaryInitializer. + Thrown always as this method is meant to be used only for [Networked] properties inline initialization. + + + + A component for synchronizing the Animator controller state from the State Authority to network proxies. + Requires a Unity Animator component, and a component. + NOTE: Animator Root Motion is not compatible with re-simulation and prediction. + + + + + Gets the dynamic word count for the NetworkMecanimAnimator. + + + The dynamic word count, which is the maximum of the current total words and the runtime counts, if the application is playing. + + Thrown when this property is accessed outside of playing. + + + + The Animator being synced. If unset, will attempt to find one on this GameObject. + + + + + The source of the State which is applied in Render. + + + + + Flags controlling which Mecanim data will be synced. + + + + + States found in the current AnimatorController, converted to hashes. + + + + + Triggers found in the current AnimatorController, converted to hashes. + + + + + The number of words allocated per snapshot for serialization of the Animator. (One Word is 32 bits, or 4 bytes.) + + + + + Animator data captured on the first invocation of EnsureInitialized(). + + + + + Have and been initialized? + + + + + To ensure triggers are synced consistently, 1 bit is used for true/false and 3 more are used + to differentiate the state (these are incremented with every change to the underlying bool). + + + + + Capture of Animator data. + + + + + + + + + + + Queues a SetTrigger() call for the associated Animator on the State Authority. Call this instead of Animator.SetTrigger() for the State Authority to ensure that triggers are captured. + On State Authority, this call will defer the SetTrigger() pass-through to the Animator until FixedUpdateNetwork() is called, + where all queued triggers will be executed (this is to ensure tick agreement between server and clients). + + Trigger hash to set + Will call Animator.SetTrigger() immediately on the InputAuthority. If false, SetTrigger() will not be called on the Input Authority at all + and Animator.SetTrigger() should be called explicitly as needed. + + + + + Queues a SetTrigger() call for the associated Animator on the State Authority. Call this instead of Animator.SetTrigger() for the State Authority to ensure that triggers are captured. + On State Authority, this call will defer the SetTrigger() pass-through to the Animator until FixedUpdateNetwork() is called, + where all queued triggers will be executed (this is to ensure tick agreement between server and clients). + + Trigger name to set + Will call Animator.SetTrigger() immediately on the InputAuthority. If false, SetTrigger() will not be called on the Input Authority at all + and Animator.SetTrigger() should be called explicitly as needed. + + + + + Base class for a Fusion aware Behaviour (derived from UnityEngine.MonoBehavour). + If a SimulationBehaviour is found on a game object during the runner initialisation, the SimulationBehaviour is automatically registered. + Objects derived from this object can be associated with a and using NetworkRunner.AddGlobal(). + + + + + Gets a value indicating whether this instance can receive render callbacks. + + + true if this instance can receive render callbacks; otherwise, false. + + + This property checks the current flags of the instance against various conditions to determine if it can receive render callbacks. + + + + + Gets a value indicating whether this instance can receive simulation callbacks. + + + true if this instance can receive simulation callbacks; otherwise, false. + + + This property checks the current flags of the instance against various conditions to determine if it can receive simulation callbacks. + + + + + The this component is associated with. + + + + + The this component is associated with. + Not applicable to s as they cannot be associated with a . + + + + + Fusion FixedUpdate timing callback. + + + + + Post simulation frame rendering callback. Runs after all simulations have finished. Use in place of Unity's Update when Fusion is handling Physics. + + + + + + This is intentionally private; sub classes can create their own overload + without worrying about matching visibility etc. + calls will be weaved in. + + + + + + + + + + + + + + + String representation of this object for debugging purposes. + + The builder to append to. + + + + Attribute for specifying which and this will execute in. + Can be used to limit execution to only Host, Server or Client peers, or to only execute on Resimulation or Forward ticks. + Usage: + + [SimulationBehaviour(Stages = SimulationStages.Forward, Modes = SimulationModes.Server | SimulationModes.Host)] + + + + + + Flag for which stages of the simulation loop this component will execute this script. + + + + + Flag for which indicated peers in will execute this script. + + + + + Flag for which topologies this script will execute in + + + + + Provides a scope for a SimulationBehaviourUpdater.BehaviourList, incrementing its lock count on creation and decrementing it on disposal. + If the lock count reaches zero on disposal, all pending removals in the list are performed. + + + + + Dispose unmanaged resources. + + + + + The default behaviour interfaces + + + + + Get all simulation behaviours registered. Simulation Behaviours only. + + + + + Add to any Transform, or its associated child Transforms to automatically synchronize + TRSP (Position/Rotation/Scale/Parent). + + + + + Enables synchronization of LocalScale. + + + + + Enables synchronization of transform.parent. + NOTE: Parent GameObjects must have a derived component to be a valid parent, + parent must belong to a different than this Object. + + + + + Determines if parent changes should automatically call , + and assign the parent as the override. Default is true, as you typically will want + player interest in this object to reflect player interest in the nested parent object. For example, + if a player is carrying an nested Object, players should only see that carried Object if they see the player. + Additionally, AOI works in world space, and NetworkTransform operates in local space, so any AOI position values of + nested Objects will ALWAYS be invalid, so nested Objects should always have their AOI Override set to a non-nested Object. + + + + + Automatically sets the Area Of Interest Override for this NetworkObject to the parent NetworkObject. + + + + + Disable interpolation on State Authority in Shared Mode. You should disable interpolation if your controller code moves an object inside of Update() + rather than FixedUpdateNetwork(). + + + + + Set the transform position and rotation to the indicated values, and network the Teleport event. + This will suspend interpolation between the previous tick state and the current tick state in Render(), + on this peer and all remote peers. + + + + + Manually set the used as the AreaOfInterestOverride. + + NetworkObject to use as the AreaOfInterestOverride. + + + + + + + + + + Implement this interface on a implementation to indicate it can be teleported. + + + + + Teleports to the indicated values, and network the Teleport event. + + + + + Base class for spatial (Position/Rotation/Scale/Parent) synchronization component, such as . + Provides the base logic for render interpolation, parenting synchronization, and teleport, that can be used in components derived from this class. + + + + + The main is at the root of the + and it will be used for area of interest operations and parenting of the . + + + + + The networked data of this . + + + + + A reference to the networked data of this . + + + + + Specify the delta used to smoothly correct the render error when state authority changes. + When the state authority changes, the render position and rotation will have an error on the previous authority due to the discarded prediction. + This error will be smoothed out over time by this delta. + Keep it 0 to disable it. + + + + The tick on which this was last re-enabled (right after Spawned doesn't count). + Used to override interpolation alpha for the tick after being re-enabled. + + + + Manually set the used as the AreaOfInterestOverride. + + NetworkObject to use as the AreaOfInterestOverride. + + + + The default Teleport implementation for derived classes. + + + + + Default handling for setting a 's parent using a NetworkBehaviourId value. + + + + + Recursively attempts to find nested parent NetworkObject, and if found assigns that NetworkObject as the AreaOfInterestOverride. + + Only pass a NetworkTRSP derived class that is on the same Transform as its associated , + as AreaOfInterestOverride is only applicable when is true.. + The direct parent of the + + + + + + + + + Default Render handling for derived classes. + + + + + Data structure storing spatial (Position/Rotation/Scale/Parent) synchronization data for spatial synchronization components, and its subclass . + + + + + Special NetworkBehaviourId value, used as a flag to tell the parent is a non-networked object + + + + + Networked properties word count for the base + + + + + The actual size for the networked properties in bytes + + + + + Offset to point at the position values on the data buffer + + + + + Id of a NetworkBehaviour on the parent of the component's transform. + + + + + Position relevant for the spatial synchronization component (can be used to either store a local position or a world position, depending on the component) + + + + + Rotation relevant for the spatial synchronization component (can be used to either store a local rotation or a world rotation, depending on the component) + + + + + Scale relevant for the spatial synchronization component + + + + + Key used to differentiate between several teleports + + + + + Id of a behaviour used as the reference point for this component during area of interest operations + The behaviour should be a derived class, that is on the same Transform as its associated + + + + + Tools to replace GetComponent variants that respects nested objects. + These are used to find components of a NetworkedObjects without also finding components that belong to parent or child NetworkedObjects. + + + + + Ensures that a component of type T exists on the root object of the provided transform. + If a component of type T does not exist, it is added. + The search for the root object stops if a component of type TStopOn is found. + + The type of component to ensure exists on the root object. + The type of component that stops the search for the root object. + The transform to start the search from. + The component of type T on the root object. Returns null if no root object is found. + + + + Find T on supplied transform or any parent. Unlike GetComponentInParent, GameObjects do not need to be active to be found. + + + + + Returns all T found between the child transform and its root. Order in List from child to parent, with the root/parent most being last. + + + + + Finds the first component of type T in the children of the provided transform, stopping the search if a component of type TStopOn is found. + + The type of component to find. + The type of component that stops the search. + The transform to start the search from. + Whether to include inactive game objects in the search. + The first component of type T found. Returns null if no component is found. + + + + Same as GetComponentInParent, but will always include inactive objects in search. + Will also stop recursing up the hierarchy when the StopOnT is found. + + + + + Finds the first component of type T in the parents of the provided transform, stopping the search if a component of type TStopOn is found. + + The type of component to find. + The type of component that stops the search. + The transform to start the search from. + The first component of type T found in the parents. Returns null if no component is found or a component of type TStopOn is found. + + + + Finds components of type T on supplied transform, and every parent above that node, inclusively stopping on node StopT component. + + + + + Same as GetComponentsInChildren, but will not recurse into children with component of the StopT type. + + + + + Same as GetComponentsInChildren, but will not recurse into children with any component of the types in the stopOn array. + + + + + Same as GetComponentsInChildren, but will not recurse into children with component of the StopT type. + + Cast found components to this type. Typically Component, but any other class/interface will work as long as they are assignable from SearchT. + Find components of this class or interface type. + When this component is found, no further recursing will be performed on that node. + + + + Find All instances of Component type in a scene. Attempts to respect the hierarchy of the scene objects to produce a more deterministic order. + This is a slower operation, and does produce garbage collection. + + + + + Find All instances of Component type in a scene. Attempts to respect the hierarchy of the scene objects to produce a more deterministic order. + This is a slower operation which should not be run every update. + + The type being searched for. + Scene to search. + Supplied list that will be populated by this find. + Whether results should include inactive components. + + + + Find All instances of Component type in a scene. Attempts to respect the hierarchy of the scene objects to produce a more deterministic order. + This is a slow operation, and does produce garbage collection. + + The type being searched for. + Casts all found objects to this type, and returns collection of this type. Objects that fail cast are excluded. + Scene to search. + Whether results should include inactive components. + + + + Find All instances of Component type in a scene. Attempts to respect the hierarchy of the scene objects to produce a more deterministic order. + This is a slower operation and should not be run every update. + + Type being searched for. + Type to cast found objects to. + Scene to search. + Supplied list that will be filled with found objects. + Whether results should include inactive components. + + + + A dynamic heap for allocating and tracking unmanaged objects. + + + + + Mark an object with + + Pointer Object to mark + Type of object + Pointer to object + + + + Free up an object + + Heap to free from + Pointer to object + Thrown if is not a tracked object + + + Collect garbage delegate + + + + Collect garbage + + Dynamic heap to collect from + Dynamic roots + Dynamic roots length + + + + Ignore this field when scanning for pointers. + + + + + Dynamic heap instance. + + + + + Create a dynamic heap instance. + + Types to allocate. + + + + Finalizes an instance of the class. + + + + + Free a pointer. + + Pointer to free. + + + + Allocate a pointer. + + Size to allocate. + Pointer to allocated memory. + + + + Allocate a pointer array. + + Length of array. + Type of array. + Pointer to allocated memory. + + + + Allocate an array of pointers. + + Length of array. + Type of array. + Pointer to allocated memory. + + + + Allocate a tracked pointer. + + Signal if the pointer is a root. + Type of pointer. + Pointer to allocated memory. + + + + Allocate a tracked pointer array. + + Length of array. + Signal if the pointer is a root. + Type of array. + Pointer to allocated memory. + + + + Allocate a tracked array of pointers. + + Length of array. + Signal if the pointer is a root. + Type of array. + Pointer to allocated memory. + + + + Behaviour statistics manager will provide access to the behaviour statistics snapshots. + + + + + The completed snapshot of statistics related to Fusion Behaviour type execution from the previous update. + + + + + Represents a snapshot of statistics related to Fusion Behaviour type execution. + + + + + Gets the count of FixedUpdateNetwork executions. + + + + + Gets the count of Render executions. + + + + + Gets the total execution time of FixedUpdateNetwork in milliseconds. + + + + + Gets the total execution time of Render in milliseconds. + + + + + Represents a fusion statistics manager. + + + + + Provides access to a complete snapshot of the fusion statistics. + + + This property behaves as a read-only property and provides the collected snapshot of the fusion statistics. + + + + + Manages the network object statistics. + + + + + Represents a snapshot of Fusion statistics. + + + + + Gets or sets the number of resimulations. + + + + + Gets or sets the number of forward ticks. + + + + + Gets or sets the number of incoming packets. + + + + + Gets or sets the number of outgoing packets. + + + + + Gets or sets the in-bandwidth value. + + + + + Gets or sets the outbandwith property. + + + + + Gets or sets the round trip time (RTT) in seconds. + + + + + Gets the input in bandwidth. + + + + + Gets the input out bandwidth. + + + + + The number of received objects updates per packet. + + + + + The number of sent objects updates per packet. + + + + + The number of used segments allocated for objects on the simulation. + + + + + The number of used segments allocated for general purposes on the simulation. + + + + + The number of free segments allocated for objects on the simulation. + + + + + The number of free segments allocated for general purposes on the simulation. + + + + + The number of words that were written. + + + + + The number of words that were read. + + + + + The total size in bytes of all written words. + + + + + The total size in bytes of all read words. + + + + + Gets the Input Receive Delta. + + + + + Gets the Time Resets count. + + + + + Gets the State Receive Delta. + + + + + Gets the Simulation Time Offset. + + + + + Gets the Simulation Speed. + + + + + Gets the Interpolation Offset. + + + + + Gets the Interpolation Speed. + + + + + Represents a snapshot of lag compensation statistics. + + + + + Represents the total time taken to advance, add and update all hitboxes on the Lag Compensation system. + + + + + Represents the deeper level reached by the BVH. + + + + + Represents total nodes count on the BVH. + + + + + Represents the total number of HitBoxCollider on the HitBoxSnapshot colliders buffer, including both used and free ones. + + + + + Represents the amount of time taken to register new hitboxes on the HitboxBuffer. + + + + + Represents the amount of time taken to register new hitboxes on the BVH. + + + + + Represents the amount of time taken to update the BVH. + + + + + Represents the amount of time taken to update the HitboxBuffer. + + + + + Represents the amount of time taken to advance the hitbox buffer and copy previous snapshot information. + + + + + Represents the amount of time taken to refit the BVH bounds after the nodes update. + + + + + Clear the snapshot + + + + + Represents a snapshot of specific memory statistics. For basic total allocated memory and total free memory check the Fusion Statistics. + + + + + Enum representing the target allocator for memory statistics. + + + + + The number of buckets used in the allocator. + + + + + Represents the total number of free blocks in the allocator. + + + + + Represents the number of full blocks in each bucket of the allocator. + + + + + Represents the number of used blocks in each bucket of the allocator. + + + + + Represents the number of free blocks in each bucket of the allocator. + + + + + Manages network object statistics for monitored network objects. + + + + + Starts or stops monitoring the statistics of a network object. + + The identifier of the network object to monitor. + True to start monitoring, false to stop monitoring. + + + + Clears the list of monitored network objects. + + + + + Retrieves the network object statistics for a given network ID. + + + Returns true if the object is being monitored and its statistics are successfully retrieved; otherwise, returns false. + + + + + Collects statistics and resets the completed snapshots for further use. + + + + + Represents a snapshot of network object statistics. + + + + + Gets or sets the number of incoming packets. + + + + + Gets or sets the number of outgoing packets. + + + + + Gets or sets the in-bandwidth value. + + + + + Gets or sets the outbandwith property. + + + + + Call this to batch-optimize any object-changes notified through + ssBVHNode.refit_ObjectChanged(..). For example, in a game-loop, + call this once per frame. + + + + + Call this when you wish to update an object. This does not update straight away, but marks it for update when Optimize() is called + + + + + initializes a BVH with a given nodeAdaptor, and object list. + + + + + SplitNode is called when a node has too many objects in it, and needs to be split into two nodes. + + BVH + The list of objects in the node + + + + Details regarding a shape intersection. + It does not carry information about the intersection happening or not. + + + + + Contact point. + + + + + Vector that described the plane of smallest penetration between the shapes. + + + + + Penetration along the normal plane. + + + + + Checks if a sphere in the local space of an AABB intersects with it. + + The extents of the AABB in the positive direction of each axis. + The center of the sphere in the local space of the AABB. + The radius of the sphere. + True if there is intersection. False otherwise. + + + + Checks if a sphere in the local space of an AABB intersects with it and outputs contact data (also in local space). + + The extents of the AABB in the positive direction of each axis. + The center of the sphere in the local space of the AABB. + The radius of the sphere. + Contact data in the local space of the AABB. Default if there is no intersection. + True if there is intersection. False otherwise. + + + + Container class to provide the necessary info to draw nodes from the BVH + + + + + Get the node Bounds + + + + + Get the node depth on the BVH + + + + + Get the BVH max depth + + + + + Container class to provide the necessary information to draw a hitbox collider + + + + + The of the collider. + + + + + The box extends of the collider + Used on of types: Box + + + + + The offset of the collider. + + + + + The radius of the collider. + Used on of types: Sphere and Capsule. + + + + + The height for capsule colliders. + + + + + + Represents the top center position of the capsule collider. + + + + + Represents the bottom center position of the capsule collider. + + + + + The local to world matrix of the collider. + + + + + Provide access to iterate over the lag compensation system components and give the necessary information to draw them. + + + + + Iterate over to get the hitbox snapshots draw data. Iterate the received hitbox snapshot draw data to get all the colliders draw info for that snapshot. + + + + + Iterate over to get the BVH node draw data. + + + + + Method to draw capsules out of simple shapes. + + The top capsule end position + The bottom capsule end posistion + The capsule radius + + + + Provide a way to iterate over the and + return the container for each snapshot on the buffer. + + + + + Returns an enumerator that iterates through the . + + + + + Provide a way to iterate over the and return + the for each collider on the snapshot. + + + + + Returns an enumerator that iterates through the of this container. + + + + + Provide a way to iterate over BVH and return a for each node. + + + + + Returns an enumerator that iterates through the . + + + + + Queries can hit either fusion's custom or Unity's standard Physx/Box2D colliders. + + + + + Used when a raycast does not hit anything. Not used on overlaps. + + + + + is a Fusion . + + + + + is a Unity PhysX Collider. + + + + + is a Unity Box2D Collider. + + + + + LagCompensated Extension methods + + + + + Sorts all in ascending order of distance + from to the point. + + List containing hits to be sorted. + Used as reference point to compute distance from hit points. + If are null. + + + + Sorts all in ascending order of . + + List containing hits to be sorted. + If are null. + + + + HitboxBuffer will store all snapshots of the colliders into a circular buffer + + + + + HitboxContainer represents 1 snapshot of all containers in a given Tick + + + + + Pre-processing delegate for queries. + + The query to be performed. + The root candidates to be used for the query. + The indices of the colliders that have been processed. + + + + Represents an Axis-Aligned Bounding Box (AABB). + + + + + Represents the center of the AABB. + + + + + Represents the extents (half-widths) of the AABB. + + + + + Represents the minimum point (lower corner) of the AABB. + + + + + Represents the maximum point (upper corner) of the AABB. + + + + + Constructs an AABB from a Unity Bounds object. + + The Unity Bounds object to construct the AABB from. + + + + Constructs an AABB from a center point and extents. + + The center point of the AABB. + The extents (half-widths) of the AABB. + + + + Constructs an AABB from a center point that encapsulate two other points. + + + + + Query parameters for position rotation query + + + + + Represents the base parameters for the query. + + + + + Represents the hitbox to be queried. + + + + + Create a new . + + Parameters to be used + The hitbox to be queried + + + + Class that represents a box overlap query. Used to query against the API. + + + + + The box query center. + + + + + The box query extents. + + + + + The box query rotation. + + + + + Create a new with the given . + + The parameters to be used when creating the query. + + + + Create a new with the given . The result colliders arrays + can be provided to avoid allocation. + + The parameters to be used when creating the query. + Array to write the results of the PhysX query if used. + Array to write the results of the Box2D query if used. + + + + Check if the given overlaps with this query. + + The bounds to check. + True if the bounds overlaps with this query, false otherwise. + + + + Base parameters needed to execute a box overlap query + + + + + Represents the base parameters for the query. + + + + + Represents the center of the box for the overlap query. + + + + + Represents the extents of the box for the overlap query. + + + + + Represents the rotation of the box for the overlap query. + + + + + Represents the capacity for the cached PhysX and Box2D static hits. + + + + + Create a new + + Parameters to be used + The query center + The query extents + The query rotation + Capacity for the cached PhysX and Box2D static hits. + + + + Base class for all Lag Compensation queries + + + + + Represents the interaction type of the query with triggers. + + + + + Represents the options for the hit detection of the query. + + + + + Represents the layer mask to selectively ignore colliders when performing the query. + + + + + Represents the player who initiated the query. + + + + + Represents the simulation tick at which the query was initiated. + + + + + Represents the user arguments for the query. + + + + + Represents the interpolation factor between the current and next simulation tick. + + + + + Represents the simulation tick to which the query is performed. + + + + + Represents the delegate to be called for pre-processing before the query is performed. + + + + + Initializes a new instance of the Query class using the provided QueryParams. + + The QueryParams to use for initializing the Query. + + + + Checks if the provided bounds should be included in the query. + + The bounds to check. + True if the bounds should be included in the query, false otherwise. + + + + Base parameters needed to execute a query. + + + + + Represents the options for the hit detection of the query. + + + + + Represents the interaction type of the query with triggers. + + + + + Represents the layer mask to selectively ignore colliders when performing the query. + + + + + Represents the player who initiated the query. + + + + + Represents the simulation tick at which the query was initiated. + + + + + Represents the simulation tick to which the query is performed. + + + + + Represents the interpolation factor between the current and next simulation tick. + + + + + Represents the delegate to be called for pre-processing before the query is performed. + + + + + Represents the user arguments for the query. + + + + + Class that represents a raycast all query. Used to query against the API. + + + + + Create a new with the given . + + The parameters to be used when creating the query. + + + + Create a new with the given . The result colliders arrays + can be provided to avoid allocation. + + The parameters to be used when creating the query. + Array to write the results of the PhysX query if used. + Array to write the results of the Box2D query if used. + + + + Class that represents a raycast query. Used to query against the API. + + + + + Represents the direction of the raycast for the query. + + + + + Represents the origin point of the raycast for the query. + + + + + Represents the maximum length of the raycast for the query. + + + + + Create a new with the given + + The parameters to be used when creating the query. + + + + Check if the provided bounds should be included in the query. + + The bounds to check. + True if the bounds should be included in the query, false otherwise. + + + + Base parameters needed to execute a raycast query + + + + + Represents the base parameters for the raycast query. + + + + + Represents the origin point of the raycast for the query. + + + + + Represents the direction of the raycast for the query. + + + + + Represents the maximum length of the raycast for the query. + + + + + Represents the capacity for the cached PhysX and Box2D static hits. + + + + + Create a new + + Parameters to be used + The raycast origin + The raycast direction + The raycast max length + Capacity for the cached PhysX and Box2D static hits. + + + + Class that represents a sphere overlap query. Used to query against the API. + + + + + Represents the center of the sphere for the overlap query. + + + + + Represents the radius of the sphere for the overlap query. + + + + + Create a new with the given . + + The parameters to be used when creating the query. + + + + Create a new with the given . + + The parameters to be used when creating the query. + Array to write the results of the PhysX query if used. + Array to write the results of the Box2D query if used. + + + + Check if the given intersects with the sphere overlap query. + + The bounds to check against. + True if the bounds intersects with the sphere overlap query, false otherwise. + + + + Base parameters needed to execute a sphere overlap query + + + + + Represents the base parameters for the sphere overlap query. + + + + + Represents the center of the sphere for the overlap query. + + + + + Represents the radius of the sphere for the overlap query. + + + + + Represents the capacity for the cached PhysX and Box2D static hits. + + + + + Create a new . + + Parameters to be used + The query center + The query radius + Capacity for the cached PhysX and Box2D static hits. + + + + Defines the collision geometry type of a . + + + + + [Future Use] to represent a disabled . + + + + + Geometry is a box, fill in Extents and (optional) Offset. + + + + + Geometry is a sphere, fill in Radius and (optional) Offset. + + + + + Geometry is a capsule, fill in capsule Radius, capsule Height and (optional) Offset. + + + + + Represents a single lag-compensated collider. + Multiple component instances can be added anywhere in the hierarchy of a which includes a . + + + + + The collision geometry type for this . + + + + + When is set to , this defines the local-space geometry for narrow-phase checks. + + + + + When is set to , this defines the local-space geometry for narrow-phase checks. + + + + + When is set to , this defines the local-space geometry for narrow-phase checks. + + + + + When is set to , this defines the local-space geometry for narrow-phase checks. + + + + + This 's local-space offset from its GameObject position. + + + + + Reference to the top-level component for this . + + + + + The index of this hitbox in the array on . + The value is set by the root when initializing the nested hitboxes with . + + + + + Mask to access the state of this hitbox on the root. + + + + + Get or set the state of this Hitbox. + If a hitbox or its HitboxRoot are not active, it will not be hit by lag-compensated queries. + + + + + Index assigned to the collider of this hitbox on the lag-compensated snapshots. + + + + + World-space position (includes Offset) of this . + + + + + Color used when drawing gizmos for this hitbox. + + + + + Set a layer mask for this gameobject. This method caches the layer mask. + + + + + + Draws this hitbox gizmo on Unity editor. + + + + + Draw the gizmos. + + + + + Entry point for lag compensated queries, which + maintains a history buffer, and provides lag compensated raycast and overlap methods. + Singleton instance is accessible through the property Runner.LagCompensation. + Usage - Call any of the following methods: + HitboxManager.Raycast()

+ HitboxManager.RaycastAll()

+ HitboxManager.PositionRotation()

+ HitboxManager.OverlapSphere()
+ These methods use the history buffer to perform a query against a state consistent with how the indicated perceived them locally. +
+
+ + + Debug data from Broadphase BVH (tree depth). + + + + + Debug data from Broadphase BVH (total nodes count). + + + + + Debug data from lag compensation history (registered count). + + + + + Debug data used to draw the BVH nodes and the lag compensation history. + + + + + Performs a lag-compensated raycast query against all registered hitboxes. + If the or flag is indicated, query will also include static colliders, + Unity colliders are recommended for static geometry, rather than Hitboxes. + + Raycast origin, in world-space + Raycast direction, in world-space + Raycast length + Player who "owns" this raycast. Used by the server to find the exact hitbox snapshots to check against. + Raycast results will be filled in here. + Only objects with matching layers will be checked against. + Opt-in flags to compute with sub-tick accuracy () and/or to include PhysX () or Box2D (). + Trigger interaction behavior when also querying PhysX. + + Delegate to pre-process HitboxRoots found in the broad-phase resolution of the query. + Roots removed from the list will not be processed any further. + Roots that remain on the candidates collection will be normally processed and fitting colliders will be evaluated in + the query narrow-phase resolution. + Hitbox collider indices added to the processed set will be evaluated in the narrow-phase regardless of further root + processing steps (e.g. layer mask match). + + True if something is hit + + + + Performs a lag-compensated raycast query against all registered hitboxes. + If the or flag is indicated, query will also include static colliders, + Unity colliders are recommended for static geometry, rather than Hitboxes. + + Raycast origin, in world-space + Raycast direction, in world-space + Raycast length + Simulation tick number to use as the time reference for the lag compensation (use this for server AI, and similar). + + Simulation tick number to use as the time reference for the lag compensation. + If provided, must be combined with the parameter for interpolation between and . + If is included on , this query will be resolved against hitbox colliders interpolated between the specified ticks. + Otherwise, only one of the two ticks will be considered, according to the rounded value of . + + + Interpolation value when querying between and . + If is included on , this query will be resolved against hitbox colliders interpolated between the specified ticks. + Otherwise, only one of the two ticks will be considered, according to the rounded alpha value. + + Raycast results will be filled in here. + Only objects with matching layers will be checked against. + Opt-in flags to compute with sub-tick accuracy () and/or to include PhysX () or Box2D (). + Trigger interaction behavior when also querying PhysX. + + Delegate to pre-process HitboxRoots found in the broad-phase resolution of the query. + Roots removed from the list will not be processed any further. + Roots that remain on the candidates collection will be normally processed and fitting colliders will be evaluated in + the query narrow-phase resolution. + Hitbox collider indices added to the processed set will be evaluated in the narrow-phase regardless of further root + processing steps (e.g. layer mask match). + + True if something is hit + + + + Performs a lag-compensated raycast query against all registered hitboxes. + If the or flag is indicated, query will also include static colliders, + Unity colliders are recommended for static geometry, rather than Hitboxes. + Important: results are NOT sorted by distance. + + Raycast origin, in world-space + Raycast direction, in world-space + Raycast length + Player who "owns" this raycast. Used by the server to find the exact hitbox snapshots to check against. + List to be filled with hits (both hitboxes and/or static colliders, if included). + Only objects with matching layers will be checked against. + Opt-in flags to compute with sub-tick accuracy () and/or to include PhysX () or Box2D (). + Clear list of hits before filling with new ones (defaults to true). + Trigger interaction behavior when also querying PhysX. + + Delegate to pre-process HitboxRoots found in the broad-phase resolution of the query. + Roots removed from the list will not be processed any further. + Roots that remain on the candidates collection will be normally processed and fitting colliders will be evaluated in + the query narrow-phase resolution. + Hitbox collider indices added to the processed set will be evaluated in the narrow-phase regardless of further root + processing steps (e.g. layer mask match). + + total number of hits + + + + Performs a lag-compensated raycast query against all registered hitboxes. + If the or flag is indicated, query will also include static colliders, + Unity colliders are recommended for static geometry, rather than Hitboxes. + Important: results are NOT sorted by distance. + + Raycast origin, in world-space + Raycast direction, in world-space + Raycast length + Simulation tick number to use as the time reference for the lag compensation (use this for server AI, and similar). + + Simulation tick number to use as the time reference for the lag compensation. + If provided, must be combined with the parameter for interpolation between and . + If is included on , this query will be resolved against hitbox colliders interpolated between the specified ticks. + Otherwise, only one of the two ticks will be considered, according to the rounded value of . + + + Interpolation value when querying between and . + If is included on , this query will be resolved against hitbox colliders interpolated between the specified ticks. + Otherwise, only one of the two ticks will be considered, according to the rounded alpha value. + + List to be filled with hits (both hitboxes and/or static colliders, if included). + Only objects with matching layers will be checked against. + Opt-in flags to compute with sub-tick accuracy () and/or to include PhysX () or Box2D (). + Clear list of hits before filling with new ones (defaults to true). + Trigger interaction behavior when also querying PhysX. + + Delegate to pre-process HitboxRoots found in the broad-phase resolution of the query. + Roots removed from the list will not be processed any further. + Roots that remain on the candidates collection will be normally processed and fitting colliders will be evaluated in + the query narrow-phase resolution. + Hitbox collider indices added to the processed set will be evaluated in the narrow-phase regardless of further root + processing steps (e.g. layer mask match). + + total number of hits + + + + Performs a lag-compensated overlap sphere query against all registered hitboxes. + If the or flag is indicated, query will also include static colliders, + Unity colliders are recommended for static geometry, rather than Hitboxes. + + Sphere center, in world-space + Sphere radius + Player who "owns" this overlap. Used by the server to find the exact hitbox snapshots to check against. + List to be filled with hits (both hitboxes and/or static colliders, if included). + Only objects with matching layers will be checked against. + Opt-in flags to compute with sub-tick accuracy () and/or to include PhysX () or Box2D (). + Clear list of hits before filling with new ones (defaults to true). + Trigger interaction behavior when also querying PhysX. + + Delegate to pre-process HitboxRoots found in the broad-phase resolution of the query. + Roots removed from the list will not be processed any further. + Roots that remain on the candidates collection will be normally processed and fitting colliders will be evaluated in + the query narrow-phase resolution. + Hitbox collider indices added to the processed set will be evaluated in the narrow-phase regardless of further root + processing steps (e.g. layer mask match). + + total number of hits + + + + Performs a lag-compensated overlap sphere query against all registered hitboxes. + If the or flag is indicated, query will also include static colliders, + Unity colliders are recommended for static geometry, rather than Hitboxes. + + Sphere center, in world-space + Sphere radius + The tick to be queried + + Simulation tick number to use as the time reference for the lag compensation. + If provided, must be combined with the parameter for interpolation between and . + If is included on , this query will be resolved against hitbox colliders interpolated between the specified ticks. + Otherwise, only one of the two ticks will be considered, according to the rounded value of . + + + Interpolation value when querying between and . + If is included on , this query will be resolved against hitbox colliders interpolated between the specified ticks. + Otherwise, only one of the two ticks will be considered, according to the rounded alpha value. + + List to be filled with hits (both hitboxes and/or static colliders, if included). + Only objects with matching layers will be checked against. + Opt-in flags to compute with sub-tick accuracy () and/or to include PhysX () or Box2D (). + Clear list of hits before filling with new ones (defaults to true). + Trigger interaction behavior when also querying PhysX. + + Delegate to pre-process HitboxRoots found in the broad-phase resolution of the query. + Roots removed from the list will not be processed any further. + Roots that remain on the candidates collection will be normally processed and fitting colliders will be evaluated in + the query narrow-phase resolution. + Hitbox collider indices added to the processed set will be evaluated in the narrow-phase regardless of further root + processing steps (e.g. layer mask match). + + total number of hits + + + + Performs a lag-compensated box overlap query against all registered hitboxes. + If the or flag is indicated, query will also include static colliders, + Unity colliders are recommended for static geometry, rather than Hitboxes. + + Center of the box in world space. + Half of the size of the box in each dimension. + Rotation of the box. + Player who "owns" this overlap. Used by the server to find the exact hitbox snapshots to check against. + List to be filled with hits (both hitboxes and/or static colliders, if included). + Only objects with matching layers will be checked against. + Opt-in flags to compute with sub-tick accuracy () and/or to include PhysX () or Box2D (). + Clear list of hits before filling with new ones (defaults to true). + Trigger interaction behavior when also querying PhysX. + + Delegate to pre-process HitboxRoots found in the broad-phase resolution of the query. + Roots removed from the list will not be processed any further. + Roots that remain on the candidates collection will be normally processed and fitting colliders will be evaluated in + the query narrow-phase resolution. + Hitbox collider indices added to the processed set will be evaluated in the narrow-phase regardless of further root + processing steps (e.g. layer mask match). + + The total number of hits found. + + + + Performs a lag-compensated box overlap query against all registered hitboxes. + If the or flag is indicated, query will also include static colliders, + Unity colliders are recommended for static geometry, rather than Hitboxes. + + Center of the box in world space. + Half of the size of the box in each dimension. + Rotation of the box. + The exact tick to be queried + + Simulation tick number to use as the time reference for the lag compensation. + If provided, must be combined with the parameter for interpolation between and . + If is included on , this query will be resolved against hitbox colliders interpolated between the specified ticks. + Otherwise, only one of the two ticks will be considered, according to the rounded value of . + + + Interpolation value when querying between and . + If is included on , this query will be resolved against hitbox colliders interpolated between the specified ticks. + Otherwise, only one of the two ticks will be considered, according to the rounded alpha value. + + List to be filled with hits (both hitboxes and/or static colliders, if included). + Only objects with matching layers will be checked against. + Opt-in flags to compute with sub-tick accuracy () and/or to include PhysX () or Box2D (). + Clear list of hits before filling with new ones (defaults to true). + Trigger interaction behavior when also querying PhysX. + + Delegate to pre-process HitboxRoots found in the broad-phase resolution of the query. + Roots removed from the list will not be processed any further. + Roots that remain on the candidates collection will be normally processed and fitting colliders will be evaluated in + the query narrow-phase resolution. + Hitbox collider indices added to the processed set will be evaluated in the narrow-phase regardless of further root + processing steps (e.g. layer mask match). + + The total number of hits found. + + + + Performs a lag-compensated query for a specific Hitbox position and rotation. + + The target hitbox to be queried in the past + The tick to be queried + + Simulation tick number to use as the time reference for the lag compensation. + If provided, must be combined with the parameter for interpolation between and . + If is requested, the query will return the hitbox state interpolated between the specified ticks. + Otherwise, only one of the two ticks will be considered, according to the rounded value of . + + + Interpolation value when querying between and . + If is requested, the query will return the hitbox state interpolated between the specified ticks. + Otherwise, only one of the two ticks will be considered, according to the rounded alpha value. + + Will be filled with the hitbox position at the time of the tick + Will be filled with the hitbox rotation at the time of the tick + If the query should interpolate between ticks to reflect exactly what was seen on the client. + + + + Performs a lag-compensated query for a specific Hitbox position and rotation. + + The target hitbox to be queried in the past + Player who "owns" this overlap. Used by the server to find the exact hitbox snapshots to check against. + Will be filled with the hitbox position at the time of the tick + Will be filled with the hitbox rotation at the time of the tick + If the query should interpolate between ticks to reflect exactly what was seen on the client. + + + + Get the closest hit from a list of . + + The closest found. + + + + Performs a lag-compensated raycast query against all registered hitboxes. + If the or flag is indicated, query will also include static colliders, + Unity colliders are recommended for static geometry, rather than Hitboxes. + + The query containing all necessary information. + Raycast results will be filled in here. + The total number of hits found. + + + + Performs a lag-compensated raycast query against all registered hitboxes. + If the or flag is indicated, query will also include static colliders, + Unity colliders are recommended for static geometry, rather than Hitboxes. + + The query containing all necessary information. + List to be filled with hits (both hitboxes and/or static colliders, if included). + Clear list of hits before filling with new ones (defaults to true). + The total number of hits found. + + + + Performs a lag-compensated sphere overlap query against all registered hitboxes. + If the or flag is indicated, query will also include static colliders, + Unity colliders are recommended for static geometry, rather than Hitboxes. + + The query containing all necessary information. + List to be filled with hits (both hitboxes and/or static colliders, if included). + Clear list of hits before filling with new ones (defaults to true). + The total number of hits found. + + + + Performs a lag-compensated box overlap query against all registered hitboxes. + If the or flag is indicated, query will also include static colliders, + Unity colliders are recommended for static geometry, rather than Hitboxes. + + The query containing all necessary information. + List to be filled with hits (both hitboxes and/or static colliders, if included). + Clear list of hits before filling with new ones (defaults to true). + The total number of hits found. + + + + Gets the tick and alpha interpolate values for a player. + + The player reference. + The tick value from which to interpolate. + The tick value to which to interpolate. + The interpolation alpha value. + + + + Gets a snapshot of the lag compensation statistics. + + The lag compensation statistics snapshot. + + + + Internal use. Inserts (new ones) and updates all registered hitboxes into lag compensation history. + + + + + Register new hitboxes and updates existing ones on the Server. + + + + + + + Per-query options for lag compensation (both raycast and overlap). + + + + + Default, no extra options. + + + + + Add this to include checks against PhysX colliders. + + + + + Add this to include checks against Box2D colliders. If PhysX flag is set, it will be used instead. + + + + + Subtick accuracy query (exactly like seen by player). + + + + + If the objects which the player performing the query + (if specified) has input authority over should be ignored by the query. + + + + + Settings for lag compensation history. + + + + + Indicates if a instance should be added when the is initialized. + + + + + Hitbox snapshot history length in milliseconds. + + + + + Hitbox capacity per snapshot. + + + + + The size of the cached static colliders (PhysX or Box2D) array of the default Lag Compensation Queries. + + + + + Broadphase BVH node expansion factor (default 20%) for leaf nodes, so updates are not too frequent. + + + + + Optional: tries to optimize broadphase BVH every update. May be removed in the future. + + + + + Defines a lag compensated query hit result. + + + + + Hit object source (PhysX or Fusion Hitboxes). + + + + + The Unity Game Object that was hit. Its data is not lag compensated. + This is either the 's or the 's gameObject, + depending on the object hit being a lag-compensated Hitbox or a regular Unity collider, respectively. + + + + + Surface normal (if requested) of the hit, at the lag compensated time. + + + + + Point of impact of the hit, at the lag compensated time. + + + + + The hitbox collider position on the snapshot. + + + + + The hitbox collider rotation on the snapshot. + + + + + Distance (if requested) to hit, at the lag compensated time. + + + + + Fusion's . Null in case the hit was on PhysX or Box2D. + + + + + PhysX collider hit. Null in case hit is a Fusion or a Box2D hit. + + + + + Box2D collider hit. Null in case hit is a Fusion or a PhysX hit. + + + + + Auxiliary field used when sorting hits in a collection. + + + + + Creates a structure from the information on a Unity . + + The used as source. + The built LagCompensatedHit structure. + + + + Creates a structure from the information on a Unity . + + The used as source. + The built LagCompensatedHit structure. + + + + Creates a structure from the information on a . + + The used as source. + The built LagCompensatedHit structure. + + + + Root group container. Manages registering/unregistering hitboxes with the group, and defines the broadphase geometry for the group. + Broadphase is the initial rough query used by raycasts/overlaps/etc to find potential hit candidates, + which are then used in the final narrowphase query. + + + + + Set of configuration options for a Hitbox Root behaviour. + + + + + If the collection of hitboxes under a given root should be re-initialized before the Root is registered + in a hitbox snapshot. If disabled, the hitboxes will be used as configured in edit-time. + + + + + If Hitboxes on inactive Game Objects should be registered under this root upon initialization. + + + + + Set of configuration flags that replicate the behaviour as it was before the flag options were added. + + + + + Ser of configuration flags with the default behaviour, suitable for most use-cases. + + + + + Get or set the state of this HitboxRoot. + For a hitbox to be hit by lag-compensated queries, both it and its HitboxRoot must be active. + + + + + The max number of hitboxes allowed under the same root. + + + + + Set of configuration options for this Hitbox Root behaviour. + Check the API documentation for more details on what each flag represents. + + + + + The radius of the broadphase bounding sphere for this group. + Used by to insert/update lag compensated NetworkObjects into its BVH (bounding volume hierarchy) data structure. + Be sure this radius encompasses all children components (including their full ranges of animation motion). + We plan to offer an option to dynamically compute the bounding volume, but the performance trade of will still favor a hand-crafted radius. + Broadphase is the initial rough query used by raycasts/overlaps/etc to find potential hit candidates, + which are then used in the final narrowphase query. + + + + + Local-space offset of the broadphase bounding sphere from its transform position. + Adjust the and until the sphere gizmo (shown in the Unity Scene window) + encompasses all children components (including their full ranges of animation motion). + Broadphase is the initial rough query used by raycasts/overlaps/etc to find potential hit candidates, + which are then used in the final narrowphase query. + + + + + Color used when drawing gizmos for this hitbox. + + + + + All Hitbox instances in hierarchy. Auto-filled at Spawned. + + + + + Reference to associated hitbox manager (from which lag compensated queries can be performed). + + + + + If this is in interest for the local player. + + + + + on draw gizmos. + + + + + Draws the gizmos for the + + + + + Finds child components, and adds them to the collection. + + + + + Sets to a rough value which encompasses all in their current positions. + + + + + Sets the state of a Hitbox instance under this root. + Both the hitbox and its root must be active in order for it to be hit by lag-compensated queries. + + A hitbox instance under the hierarchy of this root. + If the hitbox should be activated or deactivated. + If the of the is outside the valid range. + In Debug configuration, if the is not part of this root. + + + + Checks the state of a Hitbox instance under this root. + Both the hitbox and its root must be active in order for it to be hit by lag-compensated queries. + + A hitbox instance under the hierarchy of this root. + True if the is part of this root and is active. + If the of the is outside the valid range. + In Debug configuration, if the is not part of this root. + + + + + + + A Networked fusion type for degrees. This can be used with the , in RPCs, or in structs. + + + + Size of this struct in bytes. + + + + Clamps the current value to the supplied min-max range. + + + + + Returns the smaller of two supplied angles. + + + + + Returns the larger of two supplied angles. + + + + + Lerps between two angle values. + + + + + Returns a the value, clamped to the min-max range. + + + + + Less than operator for Angle struct. + + First Angle instance. + Second Angle instance. + True if the value of the first Angle instance is less than the value of the second Angle instance, otherwise false. + + + + Less than or equal to operator for Angle struct. + + First Angle instance. + Second Angle instance. + True if the value of the first Angle instance is less than or equal to the value of the second Angle instance, otherwise false. + + + + Greater than operator for Angle struct. + + First Angle instance. + Second Angle instance. + True if the value of the first Angle instance is greater than the value of the second Angle instance, otherwise false. + + + + Greater than or equal to operator for Angle struct. + + First Angle instance. + Second Angle instance. + True if the value of the first Angle instance is greater than or equal to the value of the second Angle instance, otherwise false. + + + + Equality operator for Angle struct. + + First Angle instance. + Second Angle instance. + True if the value of the first Angle instance is equal to the value of the second Angle instance, otherwise false. + + + + Inequality operator for Angle struct. + + First Angle instance. + Second Angle instance. + True if the value of the first Angle instance is not equal to the value of the second Angle instance, otherwise false. + + + + Checks equality with another Angle. + + Other Angle. + Equality result. + + + + Checks equality with an object. + + Object to compare. + Equality result. + + + + Gets the hash code. + + Hash code. + + + + Addition operator for Angle struct. + + First Angle instance. + Second Angle instance. + A new Angle instance that is the sum of the first and second Angle instances, wrapped around at 360 degrees if necessary. + + + + Subtraction operator for Angle struct. + + First Angle instance. + Second Angle instance. + A new Angle instance that is the difference of the first and second Angle instances, wrapped around at 360 degrees if necessary. + + + + Converts Angle to float. + + Angle instance. + Float representation of the Angle. + + + + Converts Angle to double. + + Angle instance. + Double representation of the Angle. + + + + Converts double to Angle. + + Double value. + Angle instance with the value of the double. + + + + Converts float to Angle. + + Float value. + Angle instance with the value of the float. + + + + Converts int to Angle. + + Integer value. + Angle instance with the value of the integer. + + + + String representation of the Angle. + + + + + Base class for all Fusion assets. + + + + + Alternative base class to Unity's MonoBehaviour. + This allows for components that work both in Unity, as well as the Photon relays. + + + + + Wrapper for Unity's GameObject.AddComponent() + + + + + Wrapper for Unity's GameObject.TryGetComponent() + + + + + Wrapper for Unity's GameObject.GetComponentInChildren() + + + + + Wrapper for Unity's GameObject.Destroy() + + + + + Get the string to dump to the log. + + The string builder to append to. + + + + + + + + + + + + + + + + + + + + + Returns boxed static instance. + + + + + + + + + + + + + + + + + + + + + Returns boxed static instance. + + + + + + + + + + + + + + + + + + + + + Returns boxed static instance. + + + + + + + + + + + + + + + + + + + + + Returns boxed static instance. + + + + + + + + + + + + + + + + + + + + + Returns boxed static instance. + + + + + + + + + + + + + + + + + + + + + Returns boxed static instance. + + + + + + + + + + + + + + + + + + + + + Returns boxed static instance. + + + + + + + + + + + + + + + + + + + + + Returns boxed static instance. + + + + + + + + + + + + + + + + + + + + + Returns boxed static instance. + + + + + + + + + + + + + + + + + + + + + Returns boxed static instance. + + + + + + + + + + + + + + + + + + + + + Returns boxed static instance. + + + + + + + + + + + + + + + + + + + + + Returns boxed static instance. + + + + + + + + + + + + + + + + + + + + + Returns boxed static instance. + + + + + + + + + + + + + + + + + + + + + Returns boxed static instance. + + + + + + + + + + + + + + + + + + + + + Returns boxed static instance. + + + + + + + + + + + + + + + + + + + + + Returns boxed static instance. + + + + + + + + + + + + + + + + + + + + + Returns boxed static instance. + + + + + + + + + + + + + + + + + + + + + Returns boxed static instance. + + + + Represents a compressed float value for network transmission. + + + + Encoded value of the float. + + + + Implicit conversion from float to FloatCompressed. + + The float value to convert. + A new FloatCompressed instance with the compressed value of the float. + + + + Implicit conversion from FloatCompressed to float. + + The FloatCompressed instance to convert. + The decompressed float value of the FloatCompressed instance. + + + + Checks if the current FloatCompressed instance is equal to the other FloatCompressed instance. + + The other FloatCompressed instance to compare with the current FloatCompressed instance. + True if the values of both FloatCompressed instances are equal, otherwise false. + + + + Checks if the provided object is a FloatCompressed instance and if it's equal to the current FloatCompressed instance. + + The object to compare with the current FloatCompressed instance. + True if the provided object is a FloatCompressed instance and it's equal to the current FloatCompressed instance, otherwise false. + + + + Returns the hash code for the current FloatCompressed instance. + + A hash code for the current FloatCompressed instance. + + + + Equality operator for FloatCompressed struct. + + First FloatCompressed instance. + Second FloatCompressed instance. + True if the value of the first FloatCompressed instance is equal to the value of the second FloatCompressed instance, otherwise false. + + + + Inequality operator for FloatCompressed struct. + + First FloatCompressed instance. + Second FloatCompressed instance. + True if the value of the first FloatCompressed instance is not equal to the value of the second FloatCompressed instance, otherwise false. + + + + Represents a compressed Vector2 value for network transmission. + + + + Encoded value of the x component. + + + Encoded value of the y component. + + + Gets or sets the x component. + + + Gets or sets the y component. + + + + Implicit conversion from Vector2 to Vector2Compressed. + + The Vector2 value to convert. + A new Vector2Compressed instance with the compressed value of the Vector2. + + + + Implicit conversion from Vector2Compressed to Vector2. + + The Vector2Compressed instance to convert. + The decompressed Vector2 value of the Vector2Compressed instance. + + + + Checks if the current Vector2Compressed instance is equal to the other Vector2Compressed instance. + + The other Vector2Compressed instance to compare with the current Vector2Compressed instance. + True if the values of both Vector2Compressed instances are equal, otherwise false. + + + + Checks if the provided object is a Vector2Compressed instance and if it's equal to the current Vector2Compressed instance. + + The object to compare with the current Vector2Compressed instance. + True if the provided object is a Vector2Compressed instance and it's equal to the current Vector2Compressed instance, otherwise false. + + + + Returns the hash code for the current Vector2Compressed instance. + + A hash code for the current Vector2Compressed instance. + + + + Equality operator for Vector2Compressed struct. + + First Vector2Compressed instance. + Second Vector2Compressed instance. + True if the value of the first Vector2Compressed instance is equal to the value of the second Vector2Compressed instance, otherwise false. + + + + Inequality operator for Vector2Compressed struct. + + First Vector2Compressed instance. + Second Vector2Compressed instance. + True if the value of the first Vector2Compressed instance is not equal to the value of the second Vector2Compressed instance, otherwise false. + + + + Represents a compressed Vector3 value for network transmission. + + + + Encoded value of the x component. + + + Encoded value of the y component. + + + Encoded value of the z component. + + + Gets or sets the x component. + + + Gets or sets the y component. + + + Gets or sets the z component. + + + + Implicit conversion from Vector3 to Vector3Compressed. + + The Vector3 value to convert. + A new Vector3Compressed instance with the compressed value of the Vector3. + + + + Implicit conversion from Vector3Compressed to Vector3. + + The Vector3Compressed instance to convert. + The decompressed Vector3 value of the Vector3Compressed instance. + + + + Implicit conversion from Vector2 to Vector3Compressed. + + The Vector2 value to convert. + A new Vector3Compressed instance with the compressed value of the Vector2. + + + + Implicit conversion from Vector3Compressed to Vector2. + + The Vector3Compressed instance to convert. + The decompressed Vector2 value of the Vector3Compressed instance. + + + + Checks if the current Vector3Compressed instance is equal to the other Vector3Compressed instance. + + The other Vector3Compressed instance to compare with the current Vector3Compressed instance. + True if the values of both Vector3Compressed instances are equal, otherwise false. + + + + Checks if the provided object is a Vector3Compressed instance and if it's equal to the current Vector3Compressed instance. + + The object to compare with the current Vector3Compressed instance. + True if the provided object is a Vector3Compressed instance and it's equal to the current Vector3Compressed instance, otherwise false. + + + + Returns the hash code for the current Vector3Compressed instance. + + A hash code for the current Vector3Compressed instance. + + + + Equality operator for Vector3Compressed struct. + + First Vector3Compressed instance. + Second Vector3Compressed instance. + True if the value of the first Vector3Compressed instance is equal to the value of the second Vector3Compressed instance, otherwise false. + + + + Inequality operator for Vector3Compressed struct. + + First Vector3Compressed instance. + Second Vector3Compressed instance. + True if the value of the first Vector3Compressed instance is not equal to the value of the second Vector3Compressed instance, otherwise false. + + + + Represents a compressed Vector4 value for network transmission. + + + + Encoded value of the x component. + + + Encoded value of the y component. + + + Encoded value of the z component. + + + Encoded value of the w component. + + + Gets or sets the x component. + + + Gets or sets the y component. + + + Gets or sets the z component. + + + Gets or sets the w component. + + + + Implicit conversion from Vector4 to Vector4Compressed. + + The Vector4 value to convert. + A new Vector4Compressed instance with the compressed value of the Vector4. + + + + Implicit conversion from Vector4Compressed to Vector4. + + The Vector4Compressed instance to convert. + The decompressed Vector4 value of the Vector4Compressed instance. + + + + Checks if the current Vector4Compressed instance is equal to the other Vector4Compressed instance. + + The other Vector4Compressed instance to compare with the current Vector4Compressed instance. + True if the values of both Vector4Compressed instances are equal, otherwise false. + + + + Checks if the provided object is a Vector4Compressed instance and if it's equal to the current Vector4Compressed instance. + + The object to compare with the current Vector4Compressed instance. + True if the provided object is a Vector4Compressed instance and it's equal to the current Vector4Compressed instance, otherwise false. + + + + Returns the hash code for the current Vector4Compressed instance. + + A hash code for the current Vector4Compressed instance. + + + + Equality operator for Vector4Compressed struct. + + First Vector4Compressed instance. + Second Vector4Compressed instance. + True if the value of the first Vector4Compressed instance is equal to the value of the second Vector4Compressed instance, otherwise false. + + + + Inequality operator for Vector4Compressed struct. + + First Vector4Compressed instance. + Second Vector4Compressed instance. + True if the value of the first Vector4Compressed instance is not equal to the value of the second Vector4Compressed instance, otherwise false. + + + + Represents a compressed Quaternion value for network transmission. + + + + Encoded value of the x component. + + + Encoded value of the y component. + + + Encoded value of the z component. + + + Encoded value of the w component. + + + Gets or sets the x component. + + + Gets or sets the y component. + + + Gets or sets the z component. + + + Gets or sets the w component. + + + + Implicit conversion from Quaternion to QuaternionCompressed. + + The Quaternion value to convert. + A new QuaternionCompressed instance with the compressed value of the Quaternion. + + + + Implicit conversion from QuaternionCompressed to Quaternion. + + The QuaternionCompressed instance to convert. + The decompressed Quaternion value of the QuaternionCompressed instance. + + + + Checks if the current QuaternionCompressed instance is equal to the other QuaternionCompressed instance. + + The other QuaternionCompressed instance to compare with the current QuaternionCompressed instance. + True if the values of both QuaternionCompressed instances are equal, otherwise false. + + + + Checks if the provided object is a QuaternionCompressed instance and if it's equal to the current QuaternionCompressed instance. + + The object to compare with the current QuaternionCompressed instance. + True if the provided object is a QuaternionCompressed instance and it's equal to the current QuaternionCompressed instance, otherwise false. + + + + Returns the hash code for the current QuaternionCompressed instance. + + A hash code for the current QuaternionCompressed instance. + + + + Equality operator for QuaternionCompressed struct. + + First QuaternionCompressed instance. + Second QuaternionCompressed instance. + True if the value of the first QuaternionCompressed instance is equal to the value of the second QuaternionCompressed instance, otherwise false. + + + + Inequality operator for QuaternionCompressed struct. + + First QuaternionCompressed instance. + Second QuaternionCompressed instance. + True if the value of the first QuaternionCompressed instance is not equal to the value of the second QuaternionCompressed instance, otherwise false. + + + + Defines the type of the current connection with the Remote Peer, + either the Server or a Client + + + + + No connection is currently active + + + + + Connection was accomplished using the Photon Relay Services + + + + + Connection was accomplished directly with the remote peer + + + + + A fixed size array that can be used in structs. + + + + + + Enumerator for the FixedArray struct. + + + + + Gets the current element in the collection. + + + + + Initializes a new instance of the Enumerator struct. + + The FixedArray instance to enumerate. + + + + Advances the enumerator to the next element of the collection. + + True if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection. + + + + Sets the enumerator to its initial position, which is before the first element in the collection. + + + + + Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + + + + + The fixed size of the array. + + + + + Indexer of array elements. + + + + + NetworkArray constructor. + + + + + Allocates a new array and copies values from this array. For a non-alloc alternative use . + + + + + Adds each value to the supplied List. This does not clear the list, so values will be appended to the existing list. + + + + + Copies values to the supplied array. + + The array to copy values to. Must not be null. + If true, this method will throw an error if the supplied array is smaller than this . If false, will only copy as many elements as the target array can hold. + + + + Returns a string that represents the current object. + + + + + Returns an enumerator that iterates through the FixedArray. + + + + + Sets all elements in the array to their default value. + + + + + Copies a range of elements from a source array into the FixedArray. + + The source array from which to copy elements. Must not be null. + The zero-based index in the source array at which copying begins. + The number of elements to copy from the source array. + Thrown when the provided source array is null. + Thrown when the number of elements to copy is greater than the length of the FixedArray. + Thrown when the sum of sourceOffset and sourceCount is greater than the length of the source array. + + + + Copies a range of elements from a source list into the FixedArray. + + The source list from which to copy elements. Must not be null. + The zero-based index in the source list at which copying begins. + The number of elements to copy from the source list. + Thrown when the provided source list is null. + Thrown when the number of elements to copy is greater than the length of the FixedArray. + Thrown when the sum of sourceOffset and sourceCount is greater than the length of the source list. + + + + Returns the elements of this array as a string, with value separated by \n characters. Specifically for use in the Unity inspector. + This is private and only is found by NetworkBehaviourEditor using reflection, so do not rename this method. + + + + + Helper methods for FixedArray. + + + + + Creates a FixedArray from a sequence of fields. + + Reference to the first field in the sequence. + Reference to the last field in the sequence. + A new FixedArray instance with the values from the sequence of fields. + + + + Creates a FixedArray with a specified length. + + Reference to the first field in the array. + The length of the array. + A new FixedArray instance with the specified length. + + + + Creates a FixedArray with a specified length and adapts the type of the elements. + + Reference to the first field in the array. + The length of the array. + A new FixedArray instance with the specified length and adapted type of elements. + + + + Returns the index of the first occurrence of a value in the FixedArray. + + The FixedArray to search. + The value to locate in the FixedArray. + The zero-based index of the first occurrence of elem within the entire FixedArray, if found; otherwise, -1. + + + + Interface for fixed storage types. + + + + + Provides utility methods for fixed storage types. + + + + + Gets the word count of a fixed storage type. + + The type of the fixed storage. + The word count of the fixed storage type. + + + + A FixedStorage that can hold up to 2 words. + + + + + The size of the FixedStorage in bytes. + + + + + The data of the FixedStorage. + + + + + A FixedStorage that can hold up to 4 words. + + + + + The size of the FixedStorage in bytes. + + + + + The data of the FixedStorage. + + + + + A FixedStorage that can hold up to 8 words. + + + + + The size of the FixedStorage in bytes. + + + + + The data of the FixedStorage. + + + + + A FixedStorage that can hold up to 16 words. + + + + + The size of the FixedStorage in bytes. + + + + + The data of the FixedStorage. + + + + + A FixedStorage that can hold up to 32 words. + + + + + The size of the FixedStorage in bytes. + + + + + The data of the FixedStorage. + + + + + A FixedStorage that can hold up to 64 words. + + + + + The size of the FixedStorage in bytes. + + + + + The data of the FixedStorage. + + + + + A FixedStorage that can hold up to 128 words. + + + + + The size of the FixedStorage in bytes. + + + + + The data of the FixedStorage. + + + + + A FixedStorage that can hold up to 256 words. + + + + + The size of the FixedStorage in bytes. + + + + + The data of the FixedStorage. + + + + + A FixedStorage that can hold up to 512 words. + + + + + The size of the FixedStorage in bytes. + + + + + The data of the FixedStorage. + + + + + Provides utility methods for compressing and decompressing float values. + + + + + Default accuracy for float compression and decompression. + + + + + Compresses a float value. + + The float value to compress. + The accuracy to use for compression. Defaults to DEFAULT_ACCURACY. + The compressed float value. + + + + Decompresses a compressed float value. + + The compressed float value to decompress. + The accuracy to use for decompression. Defaults to DEFAULT_ACCURACY. + The decompressed float value. + + + + This may only be deterministic on 64 bit systems. + + String to hash + Initial hash value + Hash code + + + + Defines an asynchronous operation. + + + + Gets a value indicating whether the operation is done. + + + Occurs when the operation is completed. + + + Gets the exception information if an error occurred during the operation. + + + + Defines a coroutine. + + + + + Defines the interface for reading and writing elements in a byte array. + + The type of the elements. + + + + Reads an element from the specified index in the byte array. + + The byte array. + The index of the element. + The element at the specified index. + + + + Writes an element to the specified index in the byte array. + + The byte array. + The index at which to write the element. + The element to write. + + + + Gets the word count of an element. + + The word count of an element. + + + + Reads a reference to an element from the specified index in the byte array. + + The byte array. + The index of the element. + A reference to the element at the specified index. + + + + Calculate the hash code of an element. + + + + + + + Flag interface for custom NetworkInput structs. + + + + + A set of parameters that tune the interpolated correction of prediction error on transform data. + + + + + + A factor with dimension of 1/s (Hz) that works as a lower limit for how much + of the accumulated prediction error is corrected every frame. + This factor affects both the position and the rotation correction. + Suggested values are greater than zero and smaller than MaxRate. + + + E.g.: MinRate = 3, rendering delta time = (1/60)s: at least 5% (3 * 1/60) of the accumulated error + will be corrected on this rendered frame. + + + This threshold might not be respected if the resultant correction magnitude is + below the PosMinCorrection + or above the PosTeleportDistance, for the position error, + or above the RotTeleportRadians, for the rotation error. + + + + + + + A factor with dimension of 1/s (Hz) that works as a upper limit for how much + of the accumulated prediction error is corrected every frame. + This factor affects both the position and the rotation correction. + Suggested values are greater than MinRate + and smaller than half of a target rendering rate. + + + E.g.: MaxRate = 15, rendering delta time = (1/60)s: at maximum 25% (15 * 1/60) of the accumulated error + will be corrected on this rendered frame. + + + This threshold might not be respected if the resultant correction magnitude is + below the PosMinCorrection or + above the PosTeleportDistance, for the position error, + or above the RotTeleportRadians, for the rotation error. + + + + + + + The reference for the magnitude of the accumulated position error, in meters, + at which the position error will be corrected at the MinRate. + Suggested values are greater than PosMinCorrection + and smaller than PosBlendEnd. + + + In other words, if the magnitude of the accumulated error is equal to or smaller than this threshold, + it will be corrected at the MinRate. + If, instead, the magnitude is between this threshold and PosBlendEnd, + the error is corrected at a rate between MinRate + and MaxRate, proportionally. + If it is equal to or greater than PosBlendEnd, + it will be corrected at the MaxRate. + + + Note: as the factor is expressed in distance units (meters), it might need to be scaled + proportionally to the overall scale of objects in the scene and speeds at which they move, + which are factors that affect the expected magnitude of prediction errors. + + + + + + + The reference for the magnitude of the accumulated position error, in meters, + at which the position error will be corrected at the MaxRate. + Suggested values are greater than PosBlendStart + and smaller than PosTeleportDistance. + + + In other words, if the magnitude of the accumulated error is equal to or greater than this threshold, + it will be corrected at the MaxRate. + If, instead, the magnitude is between PosBlendStart and this threshold, + the error is corrected at a rate between MinRate + and MaxRate, proportionally. + If it is equal to or smaller than PosBlendStart, + it will be corrected at the MinRate. + + + Note: as the factor is expressed in distance units (meters), it might need to be scaled + proportionally to the overall scale of objects in the scene and speeds at which they move, + which are factors that affect the expected magnitude of prediction errors. + + + + + + + The value, in meters, that represents the minimum magnitude of the accumulated position error + that will be corrected in a single frame, until it is fully corrected. + + + This setting has priority over the resultant correction rate, i.e. the restriction + will be respected even if it makes the effective correction rate be different than + the one computed according to the min/max rates and start/end blend values. + Suggested values are greater than zero and smaller than PosBlendStart. + + + Note: as the factor is expressed in distance units (meters), it might need to be scaled + proportionally to the overall scale of objects in the scene and speeds at which they move, + which are factors that affect the expected magnitude of prediction errors. + + + + + + + The value, in meters, that represents the magnitude of the accumulated + position error above which the error will be instantaneously corrected, + effectively teleporting the rendered object to its correct position. + Suggested values are greater than PosBlendEnd. + + + This setting has priority over the resultant correction rate, i.e. the restriction + will be respected even if it makes the effective correction rate be different than + the one computed according to the min/max rates and start/end blend values. + + + Note: as the factor is expressed in distance units (meters), it might need to be scaled + proportionally to the overall scale of objects in the scene and speeds at which they move, + which are factors that affect the expected magnitude of prediction errors. + + + + + + + The reference for the magnitude of the accumulated rotation error, in radians, + at which the rotation error will be corrected at the MinRate. + Suggested values are smaller than RotBlendEnd. + + + In other words, if the magnitude of the accumulated error is equal to or smaller than this threshold, + it will be corrected at the MinRate. + If, instead, the magnitude is between this threshold and RotBlendEnd, + the error is corrected at a rate between MinRate + and MaxRate, proportionally. + If it is equal to or greater than RotBlendEnd, + it will be corrected at the MaxRate. + + + + + + + The reference for the magnitude of the accumulated rotation error, in radians, + at which the rotation error will be corrected at the MaxRate. + Suggested values are greater than RotBlendStart + and smaller than RotTeleportRadians. + + + In other words, if the magnitude of the accumulated error is equal to or greater than this threshold, + it will be corrected at the MaxRate. + If, instead, the magnitude is between RotBlendStart and this threshold, + the error is corrected at a rate between MinRate + and MaxRate, proportionally. + If it is equal to or smaller than RotBlendStart, + it will be corrected at the MinRate. + + + + + + + The value, in radians, that represents the magnitude of the accumulated + rotation error above which the error will be instantaneously corrected, + effectively teleporting the rendered object to its correct orientation. + Suggested values are greater than RotBlendEnd. + + + This setting has priority over the resultant correction rate, i.e. the restriction + will be respected even if it makes the effective correction rate be different than + the one computed according to the min/max rates and start/end blend values. + + + + + + Fusion type for networking arrays. + Maximum capacity is fixed, and is set with the .



+ Typical Usage: + + [Networked, Capacity(4)]

+ NetworkArray<float> syncedArray => default; +
+ Optional usage (for NetworkBehaviours ONLY - this is not legal in INetworkStructs): + + [Networked, Capacity(4)]

+ NetworkArray<int> syncedArray { get; } = MakeInitializer(new int[] { 1, 2, 3, 4 });

+
+ Usage for modifying data: + + array.Set(123); + array[0] = 456; + +
+ T can be a primitive, or an INetworkStruct. +
+ + + Enumerator for NetworkArray. + + + + + Gets the current element in the collection. + + + + Gets the current element in the collection. + + + + Initializes a new instance of the Enumerator with the specified NetworkArray. + + The NetworkArray to enumerate. + + + + Advances the enumerator to the next element of the collection. + + true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection. + + + + Sets the enumerator to its initial position, which is before the first element in the collection. + + + + + Releases all resources used by the Enumerator. + + + + + The fixed size of the array. + + + + + Indexer of array elements. + + + + + NetworkArray constructor. + + + + + Returns a NetworkArrayReadOnly view of this array. + + + + + Returns the array value at supplied index. + + + + + Sets the array value at the supplied index. + + + + + This method is exposed with an extension method to filter + types to unmanaged ones only. This is not exhaustive, + as wrapped types are likely to pass this. + + Index to get. + Value at index. + + + + + Allocates a new array and copies values from this array. For a non-alloc alternative use . + + + + + Adds each value to the supplied List. This does not clear the list, so values will be appended to the existing list. + + + + + Copies values to the supplied array. + + NetworkArray to copy to. + Thrown if the supplied array is smaller than this . + + + + Copies values to the supplied array. + + Array to copy to. + If true, this method will throw an error if the supplied array is smaller than this . If false, will only copy as many elements as the target array can hold. + + + + Returns a string that represents the current object. + + + + + Returns an enumerator that iterates through the collection. + + + + + Clears the array, setting all values to default. + + + + + Copies a range of elements from a source array into the NetworkArray. + + The source array from which to copy elements. Must not be null. + The zero-based index in the source array at which copying begins. + The number of elements to copy from the source array. + Thrown when the provided source array is null. + Thrown when the number of elements to copy is greater than the length of the NetworkArray. + Thrown when the sum of sourceOffset and sourceCount is greater than the length of the source array. + + + + Copies a range of values from a supplied source list into the NetworkArray. + + The source list from which to copy elements. + The zero-based index in the source list at which copying begins. + The number of elements to copy from the source list. + Thrown when the provided source list is null. + Thrown when the number of elements to copy is greater than the length of the NetworkArray. + Thrown when the sum of sourceOffset and sourceCount is greater than the length of the source list. + + + + Returns the elements of this array as a string, with value separated by \n characters. Specifically for use in the Unity inspector. + This is private and only is found by NetworkBehaviourEditor using reflection, so do not rename this method. + + + + + Returns a NetworkArrayReadOnly view of this array. + + NetworkArray to convert. + NetworkArrayReadOnly view of this array. + + + + Provides extension methods for the NetworkArray class. + + + + + Finds the index of the first occurrence of a specified element in the NetworkArray. + + The NetworkArray to search. + The element to locate in the NetworkArray. + The zero-based index of the first occurrence of elem within the entire NetworkArray, if found; otherwise, -1. + + + + Returns a reference to the element at a specified position in the NetworkArray. + + The NetworkArray to search. + The zero-based index of the element to get a reference for. + A reference to the element at the specified position in the NetworkArray. + + + + Defines the interface for a networked array. + + + + + Gets or sets the element at the specified index. + + The zero-based index of the element to get or set. + + + + Attribute applied to an array property by the weaver. + + + + + Array capacity. + + + + + Word count of each element. + + + + + ReaderWriter type for the element. + + + + + + + + Provides a read-only view of a network array. + + The type of the elements. + + + + The fixed size of the array. + + + + + Indexer of array elements. + + + + + NetworkArrayReadOnly constructor. + + + + + Represents a boolean value that can be networked. + + + + The size of the NetworkBool structure in bytes. + + + + Initializes a new instance of the NetworkBool struct with the specified value. + + The boolean value. + + + + Determines whether the specified NetworkBool is equal to the current NetworkBool. + + The NetworkBool to compare with the current NetworkBool. + true if the specified NetworkBool is equal to the current NetworkBool; otherwise, false. + + + + Returns a string that represents the current NetworkBool. + + A string that represents the current NetworkBool. + + + + Determines whether the specified object is equal to the current NetworkBool. + + The object to compare with the current NetworkBool. + true if the specified object is equal to the current NetworkBool; otherwise, false. + + + + Serves as the default hash function. + + A hash code for the current NetworkBool. + + + + Defines an implicit conversion of a NetworkBool to a bool. + + The NetworkBool to convert. + + + + Defines an implicit conversion of a bool to a NetworkBool. + + The bool to convert. + + + + Represents a set of buttons that can be networked. + + + + + Gets the bits representing the state of the buttons. + + + + + Initializes a new instance of the NetworkButtons struct with the specified buttons state. + + The integer representing the state of the buttons. + + + + Checks if the button at the specified index is set. + + The index of the button to check. + true if the button is set; otherwise, false. + + + + Sets the button at the specified index to down. + + The index of the button to set to down. + + + + Sets the button at the specified index to up. + + The index of the button to set to up. + + + + Sets the button at the specified index to the specified state. + + The index of the button to set. + The state to set the button to. + + + + Sets all buttons to up. + + + + + Sets all buttons to down. + + + + + Checks if the button of the specified enum type is set. + + The enum type of the button. + The button of type T to check. + true if the button is set; otherwise, false. + + + + Sets the button of the specified enum type to down. + + The enum type of the button. + The button of type T to set to down. + + + + Sets the button of the specified enum type to up. + + The enum type of the button. + The button of type T to set to up. + + + + Sets the button of the specified enum type to the specified state. + + The enum type of the button. + The button of type T to set. + The state to set the button to. + + + + Gets the buttons that were pressed or released since the previous state. + + The previous state of the buttons. + A tuple containing the buttons that were pressed and the buttons that were released. + + + + Gets the buttons that were pressed since the previous state. + + The previous state of the buttons. + The buttons that were pressed. + + + + Gets the buttons that were released since the previous state. + + The previous state of the buttons. + The buttons that were released. + + + + Checks if the button at the specified index was pressed since the previous state. + + The previous state of the buttons. + The index of the button to check. + true if the button was pressed; otherwise, false. + + + + Checks if the button at the specified index was released since the previous state. + + The previous state of the buttons. + The index of the button to check. + true if the button was released; otherwise, false. + + + + Checks if the button of the specified enum type was pressed since the previous state. + + The enum type of the button. + The previous state of the buttons. + The button of type T to check. + true if the button was pressed; otherwise, false. + + + + Checks if the button of the specified enum type was released since the previous state. + + The enum type of the button. + The previous state of the buttons. + The button of type T to check. + true if the button was released; otherwise, false. + + + + Determines whether the specified NetworkButtons is equal to the current NetworkButtons. + + The NetworkButtons to compare with the current NetworkButtons. + true if the specified NetworkButtons is equal to the current NetworkButtons; otherwise, false. + + + + Determines whether the specified object is equal to the current NetworkButtons. + + The object to compare with the current NetworkButtons. + true if the specified object is equal to the current NetworkButtons; otherwise, false. + + + + Serves as the default hash function. + + A hash code for the current NetworkButtons. + + + + Fusion type for networking Dictionaries. + Maximum capacity is fixed, and is set with the .



+ Typical Usage: + + [Networked, Capacity(10)]

+ NetworkDictionary<int, float> syncedDict => default;

+
+ Usage for modifying data: + + var dict = syncedDict; + dict.Add(5, 123); + dict[5] = 456; + dict.Remove(5); + +
+ Key can be a primitive, or an INetworkStruct. + Value can be a primitive, or an INetworkStruct. +
+ + Meta word count for NetworkDictionary. + + + + Enumerator for NetworkDictionary. + + + + + Move to next entry in dictionary. + + Returns true if there is a next entry. + + + + Reset enumerator. + + + + + Current key/value pair. + + Thrown if enumerator is not valid. + + + + Dispose enumerator. + + + + + Current number of key/value entries in the Dictionary. + + + + + The maximum number of entries this dictionary may contain. + + + + + Key indexer. Gets/Sets value for specified key. + + + + + Initializes a new instance of the NetworkDictionary struct with the specified data, capacity, and reader/writers. + + The pointer to the data of the dictionary. + The capacity of the dictionary. + The reader/writer for the keys of the dictionary. + The reader/writer for the values of the dictionary. + + + + Converts the current NetworkDictionary to a read-only version. + + A new instance of NetworkDictionaryReadOnly with the same data, capacity, and reader/writers as the current NetworkDictionary. + + + + Remove all entries from the Dictionary, and clear backing memory. + + + + + Returns true if the Dictionary contains an entry for the given key. + + + + + Returns true if the Dictionary contains an entry value which compares as equal to given value. + + The value to compare against. + Specify custom IEqualityComparer to be used for compare. + + + + Returns the value for the given key. Will throw an error if the key is not found. + + + + + Sets the value for the given key. Will add a new key if the key does not already exist. + + + + + Adds a new key value pair to the Dictionary. If the key already exists, will return false. + + + + + Attempts to get the value for a given key. If found, returns true. + + The key to remove. + Returns value of removed item. Returns default value if key did not exist. + Returns true if key was found. + + + + Remove entry from Dictionary. + + The key to remove. + Returns true if key was found. + + + + Removes entry from Dictionary. If successful (key existed), returns true and the value of removed item. + + The key to remove. + Returns value of removed item. Returns default value if key did not exist. + Returns true if key was found. + + + + Returns an enumerator that iterates through the NetworkDictionary. + + + + + Converts the current NetworkDictionary to a read-only version. + + The NetworkDictionary to convert. + A new instance of NetworkDictionaryReadOnly with the same data, capacity, and reader/writers as the current NetworkDictionary. + + + + Defines the interface for a networked dictionary. + + + + + Adds an item to the networked dictionary. + + The item to add to the dictionary. + + + + Attribute applied to a dictionary property by the weaver. + + + + + + + + Capacity of the dictionary. + + + + + Word count for the key. + + + + + Word count for the value. + + + + + + + + + + + A read-only version of . + + The type of the key. + The type of the value. + + + + Current number of key/value entries in the Dictionary. + + + + + The maximum number of entries this dictionary may contain. + + + + + Returns the value for the given key. Will throw an error if the key is not found. + + + + + Attempts to get the value for a given key. If found, returns true. + + The key to remove. + Returns value of removed item. Returns default value if key did not exist. + Returns true if key was found. + + + + Fusion type for networking LinkedLists. + Maximum capacity is fixed, and is set with the .



+ Typical Usage: + + [Networked, Capacity(10)]

+ NetworkLinkedList<int> syncedLinkedList => default; +
+ Optional usage (for NetworkBehaviours ONLY - this is not legal in INetworkStructs): + + [Networked, Capacity(4)]

+ NetworkLinkedList<int> syncedLinkedList { get; } = MakeInitializer(new int[] { 1, 2, 3, 4 });

+
+ Usage for modifying data: + + var list = syncedLinkedList; + list.Add(123); + list[0] = 456; + list.Remove(0); + +
+ T can be a primitive, or an INetworkStruct. +
+ + Returns the number of words required to store a single element. + + + Returns the number of words required to store the list metadata. + + + + Enumerator for . + + + + + Advances the enumerator to the next element of the . + + Returns true if the enumerator advanced to the next element. + + + + Resets the enumerator to its initial position, which is before the first element in the collection. + + + + + Gets the current element in the collection. + + The current element in the collection. + Thrown when the enumerator is positioned before the first element or after the last element. + + + + Releases all resources used by the . + + + + + Returns the current element count. + + + + + Returns the max element count. + + + + + Element indexer. + + + + + Initializes a new instance of the NetworkLinkedList struct with the specified data, capacity, and reader/writer. + + The pointer to the data of the list. + The capacity of the list. + The reader/writer for the elements of the list. + + + + Remaps the current NetworkLinkedList to a new memory location. + + The pointer to the new memory location. + A new instance of NetworkLinkedList with the same capacity and reader/writer, but mapped to the new memory location. + + + + Removes and clears all list elements. + + + + + Returns true if the value already exists in the list. + + + + + Returns true if the value already exists in the list. + + + + + Sets the value at supplied index. + + + + + Returns the value at supplied index. + + + + + Returns the index with this value. Returns -1 if not found. + + + + + Returns the index of the first occurrence of a value in the NetworkLinkedList. + + The value to locate in the NetworkLinkedList. The value can be null for reference types. + An equality comparer to compare values. Must not be null. + The zero-based index of the first occurrence of value within the entire NetworkLinkedList, if found; otherwise, -1. + + This method performs a linear search; therefore, this method is an O(n) operation, where n is Count. + + + + + Removes the first found element with indicated value. + + + + + Removes the first found element with indicated value. + + + + + Adds a value to the end of the list. + + Value to add. + + + + Get the enumerator for the list. + + + + + Defines the interface for a networked linked list. + + + + + Adds an item to the networked linked list. + + The item to add to the linked list. + + + + Attribute applied to a list property by the weaver. + + + + + List capacity. + + + + + Word count of each element. + + + + + ReaderWriter type for the element. + + + + + + + + Read-only version of . + + Custom struct type. + + + Returns the number of words required to store a single element. + + + Returns the number of words required to store the list metadata. + + + + Returns the current element count. + + + + + Returns the max element count. + + + + + Element indexer. + + + + + Returns true if the value already exists in the list. + + + + + Returns true if the value already exists in the list. + + + + + Returns the value at supplied index. + + + + + Returns the index with this value. Returns -1 if not found. + + + + + Returns the index of the first occurrence of a value in the FixedArray. + + The value to locate in the FixedArray. The value can be null for reference types. + An equality comparer to compare values. Must not be null. + The zero-based index of the first occurrence of value within the entire FixedArray, if found; otherwise, -1. + + This method performs a linear search; therefore, this method is an O(n) operation, where n is Capacity. + + + + + PCG32 random generator, 16 bytes in size. + http://www.pcg-random.org + + + + + 0x1.00000001p-32 aka 0x3df0000000100000 + + + + + 0x1p-32 aka 0x3df0000000000000 + + + + + 0x1.000002p-24 aka 0x33800001 + + + + + 0x1p-24 aka 0x33800000 + + + + + Returns the same RNG instance. + + + + + Generates a random double value within the inclusive range [0, 1]. + + A random double value between 0 and 1, inclusive. + + + + Generates a random double value within the exclusive range [0, 1). + + A random double value between 0 (inclusive) and 1 (exclusive). + + + + Generates a random float value within the inclusive range [0, 1]. + + A random float value between 0 and 1, inclusive. + + + + Generates a random float value within the exclusive range [0, 1). + + A random float value between 0 (inclusive) and 1 (exclusive). + + + + Generates a random integer value within the range of int.MinValue to int.MaxValue. + + A random integer value between int.MinValue and int.MaxValue, inclusive. + + + + Generates a random unsigned integer value within the range of 0 to uint.MaxValue. + + A random unsigned integer value between 0 and uint.MaxValue, inclusive. + + + Size of the struct in bytes. + + + Maximum allowed value + + + + Creates a new instance of with a random seed. + + Seed value. + + + + String representation of the RNG state. + + + + + Returns a random Double within [minInclusive, maxInclusive] (range is inclusive). + If minInclusive is greater than maxInclusive, then the numbers are automatically swapped. + + + + + Returns a random Single within [minInclusive, maxInclusive] (range is inclusive). + If minInclusive is greater than maxInclusive, then the numbers are automatically swapped. + + + + + Returns a random Int32 within [minInclusive, maxExclusive) (range is exclusive). + If minInclusive and maxExclusive are equal, then the "exclusive rule" is ignored and minInclusive will be returned. + If minInclusive is greater than maxExclusive, then the numbers are automatically swapped. + + + + + Returns a random Int32 within [minInclusive, maxInclusive] (range is inclusive). + If minInclusive is greater than maxInclusive, then the numbers are automatically swapped. + + + + + Returns a random UInt32 within [minInclusive, maxExclusive) (range is exclusive). + If minInclusive and maxExclusive are equal, then the "exclusive rule" is ignored and minInclusive will be returned. + If minInclusive is greater than maxExclusive, then the numbers are automatically swapped. + + + + + Returns a random UInt32 within [minInclusive, maxInclusive] (range is inclusive). + If minInclusive is greater than maxInclusive, then the numbers are automatically swapped. + + + + + Fixed-size UTF32 string. All operations are alloc-free, except for converting to . + + + + + + Creates a new instance of with the given value. + + String value. + + + + Maximum UTF32 string length. + + + + + Converts to/from regular UTF16 string. Setter is alloc-free. Use + to get possibly alloc-free conversion. + + + + + Number of UTF32 scalars. It is equal or less than or the length + of , because those use UTF16 encoding, which needs two characters to encode + some values. + + + + + Returns UTF32 scalar at position. To iterate over characters, + use . + + Index to get. + UTF32 scalar at position. + + + + Defines an implicit conversion of a string to a NetworkString. + + The string to convert. + A new instance of NetworkString with the same value as the string. + + + + Defines an explicit conversion of a NetworkString to a string. + + The NetworkString to convert. + The string value of the NetworkString. + + + + Defines an inequality operator for NetworkString. + + The first NetworkString to compare. + The second NetworkString to compare. + true if the NetworkStrings are not equal; otherwise, false. + + + + Defines an inequality operator for a string and a NetworkString. + + The string to compare. + The NetworkString to compare. + true if the string and the NetworkString are not equal; otherwise, false. + + + + Defines an inequality operator for a NetworkString and a string. + + The NetworkString to compare. + The string to compare. + true if the NetworkString and the string are not equal; otherwise, false. + + + + Defines an equality operator for NetworkString. + + The first NetworkString to compare. + The second NetworkString to compare. + true if the NetworkStrings are equal; otherwise, false. + + + + Defines an equality operator for a string and a NetworkString. + + The string to compare. + The NetworkString to compare. + true if the string and the NetworkString are equal; otherwise, false. + + + + Defines an equality operator for a NetworkString and a string. + + The NetworkString to compare. + The string to compare. + true if the NetworkString and the string are equal; otherwise, false. + + + + Checks if is equivalent and if not converts to UTF16 and + stores the result in . + + The string to convert. + False if no conversion was performed, true otherwise. + + + + Converts to UTF32 string and stores it internally. + + The string to set. + False if was too long to fit and had to be trimmed. + + + + Returns the index of the first occurrence of a specified character in this instance. + + The Unicode character to seek. + The search starting position. + The zero-based index position of value if that character is found, or -1 if it is not. + + + + Returns the index of the first occurrence of a specified character in this instance. + + The Unicode character to seek. + The search starting position. + The number of character positions to examine. + The zero-based index position of value if that character is found, or -1 if it is not. + + + + Returns the index of the first occurrence of a specified Unicode code point in this instance. + + The Unicode code point to seek. + The search starting position. + The zero-based index position of value if that Unicode code point is found, or -1 if it is not. + + + + Returns the index of the first occurrence of a specified Unicode code point in this instance. + + The Unicode code point to seek. + The search starting position. + The number of character positions to examine. + The zero-based index position of value if that Unicode code point is found, or -1 if it is not. + Thrown when the start index is less than zero or greater than the safe length of the string, or when the count is less than zero or the sum of the start index and count is greater than the safe length of the string. + + + + Returns the index of the first occurrence of a specified string in this instance. + + The string to seek. + The search starting position. + The zero-based index position of value if that string is found, or -1 if it is not. + + + + Returns the index of the first occurrence of a specified string in this instance. + + The string to seek. + The search starting position. + The number of character positions to examine. + The zero-based index position of value if that string is found, or -1 if it is not. + Thrown when the string is null. + Thrown when the start index is less than zero or greater than the safe length of the string, or when the count is less than zero or the sum of the start index and count is greater than the safe length of the string. + + + + Returns the index of the first occurrence of a specified NetworkString in this instance. + + The size of the other NetworkString. + The NetworkString to seek. + The search starting position. + The zero-based index position of value if that NetworkString is found, or -1 if it is not. + + + + Returns the index of the first occurrence of a specified NetworkString in this instance. + + The size of the other NetworkString. + The NetworkString to seek. + The search starting position. + The number of character positions to examine. + The zero-based index position of value if that NetworkString is found, or -1 if it is not. + + + + Returns the index of the first occurrence of a specified NetworkString in this instance. + + The size of the other NetworkString. + The NetworkString to seek. + The search starting position. + The zero-based index position of value if that NetworkString is found, or -1 if it is not. + + + + Returns the index of the first occurrence of a specified NetworkString in this instance. + + The size of the other NetworkString. + The NetworkString to seek. + The search starting position. + The number of character positions to examine. + The zero-based index position of value if that NetworkString is found, or -1 if it is not. + Thrown when the start index is less than zero or greater than the safe length of the string, or when the count is less than zero or the sum of the start index and count is greater than the safe length of the string. + + + + Determines whether a specified character is in this instance. + + The Unicode character to seek. + true if the value parameter occurs within this string, or if value is the empty string (""); otherwise, false. + + + + Determines whether a specified Unicode code point is in this instance. + + The Unicode code point to seek. + true if the value parameter occurs within this string, or if value is the empty string (""); otherwise, false. + + + + Determines whether a specified string is in this instance. + + The string to seek. + true if the value parameter occurs within this string, or if value is the empty string (""); otherwise, false. + + + + Determines whether a specified NetworkString is in this instance. + + The size of the other NetworkString. + The NetworkString to seek. + true if the value parameter occurs within this string, or if value is the empty string (""); otherwise, false. + + + + Determines whether a specified NetworkString is in this instance. + + The size of the other NetworkString. + The NetworkString to seek. + true if the value parameter occurs within this string, or if value is the empty string (""); otherwise, false. + + + + Returns a substring from this instance. The substring starts at a specified character position. + + The zero-based starting character position of a substring in this instance. + A new NetworkString that is equivalent to the substring that begins at startIndex in this instance, or NetworkString.Empty if startIndex is equal to the length of this instance. + startIndex is less than zero or greater than the length of this instance. + + + + Returns a substring from this instance. The substring starts at a specified character position and has a specified length. + + The zero-based starting character position of a substring in this instance. + The number of characters in the substring. + A new NetworkString that is equivalent to the substring of length length that begins at startIndex in this instance, or NetworkString.Empty if startIndex is equal to the length of this instance and length is zero. + startIndex plus length indicates a position not within this instance, or startIndex or length is less than zero. + + + + Converts all the characters in this NetworkString to lowercase. + + A new NetworkString in which all characters in this NetworkString are converted to lowercase. + + + + Converts all the characters in this NetworkString to uppercase. + + A new NetworkString in which all characters in this NetworkString are converted to uppercase. + + + + Calculates the length of the equivalent UTF16 string. + + The length of the equivalent UTF16 string. + + + + Compares this instance with a specified string. + + The string to compare. + A 32-bit signed integer that indicates the comparison result. + + + + Compares this instance with a specified NetworkString. + + The NetworkString to compare. + A 32-bit signed integer that indicates the comparison result. + + + + Compares this instance with a specified NetworkString. + + The NetworkString to compare. + A 32-bit signed integer that indicates the comparison result. + + + + Compares this instance with a specified NetworkString of a different size. + + The size of the other NetworkString. + The NetworkString to compare. + A 32-bit signed integer that indicates the comparison result. + + + + Compares this instance with a specified NetworkString of a different size. + + The size of the other NetworkString. + The NetworkString to compare. + A 32-bit signed integer that indicates the comparison result. + + + + Determines whether the current NetworkString is equal to a specified string. + + The string to compare with the current NetworkString. + true if the specified string is equal to the current NetworkString; otherwise, false. + + + + Determines whether the current NetworkString is equal to a specified object. + + The object to compare with the current NetworkString. + true if the specified object is equal to the current NetworkString; otherwise, false. + + + + Determines whether the current NetworkString is equal to a specified NetworkString. + + The NetworkString to compare with the current NetworkString. + true if the specified NetworkString is equal to the current NetworkString; otherwise, false. + + + + Determines whether the current NetworkString is equal to a specified NetworkString. + + The NetworkString to compare with the current NetworkString. + true if the specified NetworkString is equal to the current NetworkString; otherwise, false. + + + + Determines whether the current NetworkString is equal to a specified NetworkString of a different size. + + The size of the other NetworkString. + The NetworkString to compare with the current NetworkString. + true if the specified NetworkString is equal to the current NetworkString; otherwise, false. + + + + Determines whether the current NetworkString is equal to a specified NetworkString of a different size. + + The size of the other NetworkString. + The NetworkString to compare with the current NetworkString. + true if the specified NetworkString is equal to the current NetworkString; otherwise, false. + + + + Assign a new value to this NetworkString. + + String value. + + + + Checks if the current NetworkString starts with a specified string. + + The string to check. + true if the current NetworkString starts with the specified string; otherwise, false. + Thrown when the string is null. + + + + Checks if the current NetworkString starts with a specified NetworkString of a different size. + + The size of the other NetworkString. + The NetworkString to check. + true if the current NetworkString starts with the specified NetworkString; otherwise, false. + + + + Checks if the current NetworkString ends with a specified NetworkString of a different size. + + The size of the other NetworkString. + The NetworkString to check. + true if the current NetworkString ends with the specified NetworkString; otherwise, false. + + + + Checks if the current NetworkString ends with a specified string. + + The string to check. + true if the current NetworkString ends with the specified string; otherwise, false. + Thrown when the string is null. + + + + Returns the hash code for this NetworkString. + + A 32-bit signed integer hash code. + + + + Converts the value of this NetworkString to its equivalent string representation. + + A string representation of the value of this NetworkString. + + + + Returns an enumerator that iterates through the NetworkString. + + A UTF32Tools.CharEnumerator for the NetworkString. + + + + Provides static methods for NetworkString operations. + + + + + Gets the capacity of a NetworkString of a specified size. + + The size of the NetworkString. + The capacity of a NetworkString of the specified size. + + + + Represents a Fusion player. + + The PlayerRef, in contrast to the player index, is 1-based. The reason is that default(PlayerRef) will return a "null/invalid" player ref struct for convenience. There are automatic cast operators that can cast an int into a PlayerRef. + + default(PlayerRef), internally a 0, means NOBODY + PlayerRef, internally 1, is the same as player index 0 + PlayerRef, internally 2, is the same as player index 1 + + + + + Gets an equality comparer that can be used to compare two PlayerRef instances. + + + + + The size of the PlayerRef structure in bytes. + + + + + A constant representing the raw index value for the master client. + + + + + Invalid player ref. Used to differentiate no player ref (None) from an invalid one. + + + + + None player + + + + + Special master client player ref value of -1 + + + + + If this player ref is a valid unique player index + + + + + Returns true if the index value equals -1 (internal raw value of 0), indicating no player. + + + + + Returns true if this PlayerRef indicates the MasterClient rather than a specific Player by Index, + This is a special flag value which has the encoded index value of -2 (internal raw backing value of -1). + This is not a valid PlayerRef value in itself, and no Runner will ever be assigned this value as its LocalPlayer. + It is used by properties like Object.StateAuthority to indicate that the MasterClient has authority + (which ever player that currently is), rather than a specific Player. + + + + + Returns the index backing value without modification. + Unlike which returns the backing value - 1. + 0=None -1=MasterClient >0=PlayerId + + + + + Returns the PlayerRef int as an integer Id value. + -1=None -2=MasterClient >=0=PlayerId + + + + + Returns the PlayerRef as an integer Id value. + -1=None -2=MasterClient + + + + + Determines whether the specified object is equal to the current object. + + The object to compare with the current object. + true if the specified object is equal to the current object; otherwise, false. + + + + Serves as the default hash function. + + + + + Returns a string that represents the current object. + + + + + Creates a new PlayerRef from the given encoded value. + + The encoded value to create the PlayerRef from. + A new PlayerRef that represents the encoded value. + + + + Creates a new PlayerRef from the given index. + + The index to create the PlayerRef from. + A new PlayerRef that represents the index. + + + + Determines whether two PlayerRef instances are equal. + + The first PlayerRef to compare. + The second PlayerRef to compare. + true if the PlayerRefs are equal; otherwise, false. + + + + Determines whether two PlayerRef instances are not equal. + + The first PlayerRef to compare. + The second PlayerRef to compare. + true if the PlayerRefs are not equal; otherwise, false. + + + + Writes the PlayerRef to the provided NetBitBuffer. + + The buffer to write to. + The PlayerRef to write. + + + + Writes the PlayerRef to the provided buffer. + + The type of the buffer. Must be unmanaged and implement INetBitWriteStream. + The buffer to write to. + The PlayerRef to write. + + + + Reads a PlayerRef from the provided NetBitBuffer. + + The buffer to read from. + The PlayerRef read from the buffer. + + + + Determines whether the specified PlayerRef is equal to the current PlayerRef. + + The PlayerRef to compare with the current PlayerRef. + true if the specified PlayerRef is equal to the current PlayerRef; otherwise, false. + + + + Provides utility methods for reading and writing data. + + + + Accuracy of floating point values when serialized. + + + + Writes a float value to the provided memory location. + + The memory location to write to. + The float value to write. + + + + Reads a float value from the provided memory location. + + The memory location to read from. + The float value read from the memory location. + + + + Writes a Vector2 value to the provided memory location. + + The memory location to write to. + The Vector2 value to write. + + + + Reads a Vector2 value from the provided memory location. + + The memory location to read from. + The Vector2 value read from the memory location. + + + + Writes a Vector3 value to the provided memory location. + + The memory location to write to. + The Vector3 value to write. + + + + Reads a Vector3 value from the provided memory location. + + The memory location to read from. + The Vector3 value read from the memory location. + + + + Writes a Vector4 value to the provided memory location. + + The memory location to write to. + The Vector4 value to write. + + + + Reads a Vector4 value from the provided memory location. + + The memory location to read from. + The Vector4 value read from the memory location. + + + + Writes a Quaternion value to the provided memory location. + + The memory location to write to. + The Quaternion value to write. + + + + Reads a Quaternion value from the provided memory location. + + The memory location to read from. + The Quaternion value read from the memory location. + + + + Provides utility methods for reading and writing data. + + + + Accuracy of floating point values when serialized. + + + + Reads a boolean value from the provided memory location. + + The memory location to read from. + The boolean value read from the memory location. + + + + Writes a boolean value to the provided memory location. + + The memory location to write to. + The boolean value to write. + + + + + + + + + + + + Writes a string to the provided memory location in UTF8 format without a hash. + + The memory location to write to. + The string to write. + The number of bytes written. + + + + Reads a string from the provided memory location in UTF8 format without a hash. + + The memory location to read from. + The string read from the memory location. + The number of bytes read. + + + + Gets the byte count of a string in UTF8 format without a hash. + + The string to get the byte count of. + The byte count of the string in UTF8 format. + + + + + + + + + + + + Writes a string to the provided memory location in UTF32 format without a hash. + + The memory location to write to. + The maximum length of the string. + The string to write. + The number of bytes written. + + + + Reads a string from the provided memory location in UTF32 format without a hash. + + The memory location to read from. + The maximum length of the string. + The string read from the memory location. + The number of bytes read. + + + + Writes a string to the provided memory location in UTF32 format with a hash. + + The memory location to write to. + The maximum length of the string. + The string to write. + A reference to a cache string. This will be updated with the trimmed value of the input string. + The number of bytes written. + + + + Reads a string from the provided memory location in UTF32 format with a hash. + + The memory location to read from. + The maximum length of the string. + A reference to a cache string. This will be updated with the read string if it matches the cached hashcode. + The number of bytes read. + + + + Gets the word count of a string with optional caching. + + The capacity of the string. + Indicates whether caching is used. + The word count of the string. + + + + Verifies the byte count of a network unwrapped object. + Throws an exception if the actual byte count exceeds the maximum allowed byte count. + + The type of the network unwrapped object. + The actual byte count. + The maximum allowed byte count. + The actual byte count if it does not exceed the maximum allowed byte count. + + + + Verifies the byte count of a network wrapped object. + Throws an exception if the actual byte count exceeds the maximum allowed byte count. + + The type of the network wrapped object. + The actual byte count. + The maximum allowed byte count. + The actual byte count if it does not exceed the maximum allowed byte count. + + + + Provides utility methods for reflection. + + + + + Retrieves a custom attribute of type T from the provided member. + + The type of the attribute to retrieve. Must be a subclass of Attribute. + The member to retrieve the attribute from. + Specifies whether to search this member's inheritance chain to find the attributes. + The custom attribute of type T. + Thrown when the provided member does not have an attribute of type T. + Thrown when the provided member has more than one attribute of type T. + + + + Gets the NetworkBehaviourWeavedAttribute for the specified type. + Throws an InvalidOperationException if the type has not been weaved. + + The type to get the NetworkBehaviourWeavedAttribute for. + The NetworkBehaviourWeavedAttribute for the specified type. + Thrown when the type has not been weaved. + + + + Gets all assemblies that have been weaved. + + An IEnumerable of all weaved assemblies. + + + + Gets all types that are assignable from SimulationBehaviour from all assemblies. + + An IEnumerable of all types that are assignable from SimulationBehaviour. + + + + Gets all types that are assignable from SimulationBehaviour from all weaved assemblies. + + An IEnumerable of all types that are assignable from SimulationBehaviour in weaved assemblies. + + + + Gets all types that are assignable from NetworkBehaviour from all assemblies. + + An IEnumerable of all types that are assignable from NetworkBehaviour. + + + + Gets all types that are assignable from NetworkBehaviour from all weaved assemblies. + + An IEnumerable of all types that are assignable from NetworkBehaviour in weaved assemblies. + + + + Gets all types that have the WeaverGeneratedAttribute from all weaved assemblies. + + An IEnumerable of all types that have the WeaverGeneratedAttribute in weaved assemblies. + + + + + A circular buffer. + + + Normally, you push to the back and pop from the front. + When it's full, PushBack will remove from the front and PushFront will remove from the back. + + + + + + Returns a new instance. + + + + + Returns a new instance. + + + + + The number of items in the buffer. + + + + + The maximum number of items that can be in the buffer. + + + + + True if the buffer contains no items. + + + + + True if the buffer contains the maximum number of items. + + + + + Indexed access to items in the buffer. + Indexes follow insertion order, i.e. this[0] returns the front item and this[Count - 1] returns the rear item. + + + + + + Returns a read-only reference to the front item in the buffer. + + + + + Returns a reference to the front item in the buffer. + + + + + Returns a read-only reference to the item in the buffer at index. + Indexes follow insertion order, i.e. this[0] returns the front item and this[Count - 1] returns the back item. + + + + + Returns a reference to the item in the buffer at index. + Indexes follow insertion order, i.e. this[0] returns the front item and this[Count - 1] returns the back item. + + + + + Returns a read-only reference to the back item in the buffer. + + + + + Returns a reference to the back item in the buffer. + + + + + Inserts an item at the back of the buffer. + + + + + Inserts an item at the front of the buffer. + + + + + Removes and returns the item at the back of the buffer. + + + + + Removes and returns the item at the front of the buffer. + + + + + Removes all items from the buffer. + + + + + Returns an ArraySegment pair, where both segments and the items within them follow insertion order. + Does not copy. + + + + + Returns a new array with the buffer's items in insertion order. + + + + + Returns an enumerator that can iterate the buffer. + + + + + Converts index into the corresponding index in _buffer. + + + + + Increments the provided index variable, wrapping around if necessary. + + + + + Decrements the provided index variable, wrapping around if necessary. + + + + + Scene reference struct. + Can be used to reference a scene by index or by path. + + + + The size of the SceneRef structure in bytes. + + + A constant representing the flag for addressable scenes. + + + The raw value of the SceneRef. This can represent either an index or a path hash, depending on the flag. + + + + None scene + + + + + If this scene index is valid + + + + + Returns true if this scene ref is an index. + + + + + Returns lower 32 bits as an index. + + + + + Gets the path hash of the SceneRef. + + Thrown when the SceneRef is an index, not a path. + + + + Checks if the SceneRef corresponds to a specific path. + + The path to check. + true if the SceneRef corresponds to the path; otherwise, false. + + + + Creates a SceneRef from an index. + + The index to create the SceneRef from. + A SceneRef that represents the index. + Thrown when the index is less than 0 or equal to int.MaxValue. + + + + Creates a scene ref from a path. The most common use case for this method is when using Unity's addressable scenes. + The path is hashed (31 bit), so on rare occasion there may be a hash collision. In such case + consider renaming a scene or construct your own hash and use . + To check if a scene ref is was created for a specific path, use . + + The path to create the SceneRef from. + A SceneRef that represents the path. + + + + Creates a SceneRef from a raw value. + + The raw value to create the SceneRef from. + A SceneRef that represents the raw value. + + + + Determines whether the specified object is equal to the current SceneRef. + + The object to compare with the current SceneRef. + true if the specified object is equal to the current SceneRef; otherwise, false. + + + + Determines whether the specified SceneRef is equal to the current SceneRef. + + The SceneRef to compare with the current SceneRef. + true if the specified SceneRef is equal to the current SceneRef; otherwise, false. + + + + Serves as the default hash function. + + A hash code for the current SceneRef. + + + + Returns a string that represents the current SceneRef. + + A string that represents the current SceneRef. + + + + Returns a string that represents the current SceneRef, with optional formatting. + + If true, the string will be enclosed in brackets. + If true, the string will be prefixed with "Scene:". + A string that represents the current SceneRef, formatted according to the provided parameters. + + + + Parses a SceneRef from a string. + + + + + + + Returns true if the values are equal. + + SceneRef a + SceneRef b + true if the values are equal; otherwise, false. + + + + Returns true if the values are not equal. + + SceneRef a + SceneRef b + true if the values are not equal; otherwise, false. + + + + A serializable dictionary. + + + + + Creates a new serializable dictionary. + + The type of the dictionary key. + The type of the dictionary value. + A new serializable dictionary. + + + + A serializable dictionary. + + The type of the dictionary key. + The type of the dictionary value. + + This class is not thread-safe. + + + + + The property path for the items in the SerializableDictionary. + + + + + The property path for the key in the Entry structure. + + + + + Wraps an existing Dictionary into a SerializableDictionary. + + The Dictionary to be wrapped. + A new SerializableDictionary that wraps the provided Dictionary. + + + + Gets or sets the value associated with the specified key. + + The key of the value to get or set. + The value associated with the specified key. If the specified key is not found, a get operation throws a KeyNotFoundException, and a set operation creates a new element with the specified key. + + + + Gets the number of key/value pairs contained in the SerializableDictionary. + + + + + Gets a value indicating whether the SerializableDictionary is read-only. This value is always false. + + + + + Adds the specified key and value to the SerializableDictionary. + + The key of the element to add. + The value of the element to add. + + + + Removes all keys and values from the SerializableDictionary. + + + + + Determines whether the SerializableDictionary contains the specified key. + + The key to locate in the SerializableDictionary. + true if the SerializableDictionary contains an element with the specified key; otherwise, false. + + + + Removes the value with the specified key from the SerializableDictionary. + + The key of the element to remove. + true if the element is successfully found and removed; otherwise, false. This method returns false if key is not found in the SerializableDictionary. + + + + Gets the value associated with the specified key. + + The key of the value to get. + When this method returns, contains the value associated with the specified key, if the key is found; otherwise, the default value for the type of the value parameter. This parameter is passed uninitialized. + true if the SerializableDictionary contains an element with the specified key; otherwise, false. + + + + Gets a collection containing the keys in the SerializableDictionary. + + + + + Gets a collection containing the values in the SerializableDictionary. + + + + + Returns an enumerator that iterates through the SerializableDictionary. + + A Dictionary{TKey,TValue}.Enumerator structure for the SerializableDictionary. + + + + Resets the SerializableDictionary, clearing its internal dictionary. + + + + + Stores the SerializableDictionary's data into an array for serialization. + This includes handling duplicates and null keys. + + + + + A tick is a 32-bit integer that represents a frame number. + + + + + Provides a mechanism for comparing two Tick objects. + + + + + Compares two Tick objects and returns a value indicating whether one is less than, equal to, or greater than the other. + + The first Tick object to compare. + The second Tick object to compare. + A signed integer that indicates the relative values of x and y. + + + + Provides a mechanism for comparing two Tick objects for equality. + + + + + Determines whether the specified Tick objects are equal. + + The first Tick object to compare. + The second Tick object to compare. + true if the specified Tick objects are equal; otherwise, false. + + + + Serves as a hash function for a Tick object. + + The Tick object for which to get a hash code. + A hash code for the specified Tick object. + + + + The size of the Tick structure in bytes. + + + + + The alignment of the Tick structure in bytes. + + + + + The raw value of the Tick. This represents a frame number. + + + + + Returns the next Tick, incremented by a specified value. + + The value to increment the Tick by. + A new Tick that represents the current Tick incremented by the specified value. + + + + Determines whether the specified Tick is equal to the current Tick. + + The Tick to compare with the current Tick. + true if the specified Tick is equal to the current Tick; otherwise, false. + + + + Compares the current Tick with another Tick. + + A Tick to compare with this Tick. + A value that indicates the relative order of the objects being compared. + + + + Determines whether the specified object is equal to the current Tick. + + The object to compare with the current Tick. + true if the specified object is equal to the current Tick; otherwise, false. + + + + Serves as the default hash function. + + A hash code for the current Tick. + + + + String representation of the Tick. + + + + + Determines whether the first specified Tick is greater than the second specified Tick. + + + + + Determines whether the first specified Tick is greater than or equal to the second specified Tick. + + + + + Determines whether the first specified Tick is less than the second specified Tick. + + + + + Determines whether the first specified Tick is less than or equal to the second specified Tick. + + + + + Determines whether the first specified Tick is equal to the second specified Tick. + + + + + Determines whether the first specified Tick is not equal to the second specified Tick. + + + + + Converts an integer to a Tick. + + The integer to convert. + A Tick that represents the specified integer. If the integer is less than 0, the Tick's raw value is set to 0. + + + + Converts a Tick to an integer. + + The Tick to convert. + An integer that represents the raw value of the specified Tick. + + + + Converts a Tick to a boolean. + + The Tick to convert. + true if the Tick's raw value is greater than 0; otherwise, false. + + + + A tick accumulator. + + + + + Gets the number of pending ticks in the TickAccumulator. + + + + + Gets the remaining time in the TickAccumulator. + + + + + Gets a value indicating whether the TickAccumulator is running. + + + + + Gets or sets the time scale of the TickAccumulator. + + Thrown when the value is less than or equal to 0. + + + + Calculates the alpha value based on the given step. + + The step value used to calculate the alpha. + The calculated alpha value. + + + + Adds a specified number of ticks to the TickAccumulator. + + The number of ticks to add. + + + + Adds a specified amount of time to the TickAccumulator, incrementing the tick count as necessary. + + The amount of time to add. + The time step value. + The maximum number of ticks to add. If this value is reached, the remaining time is set to 0. + + + + Stops the TickAccumulator from accumulating ticks. + + + + + Starts the TickAccumulator, allowing it to accumulate ticks. + + + + + Consumes a tick from the TickAccumulator. + + When this method returns, contains a boolean indicating whether the consumed tick was the last one. + true if a tick was successfully consumed; otherwise, false. + + + + Starts a new TickAccumulator. + + A new TickAccumulator. + + + + A tick rate is a collection of tick rates. + + + + + Represents a selection of tick rates for client and server. + + + + + The tick rate for the client. + + + + + The index of the server's tick rate in the available tick rates. + + + + + The index of the client's send tick rate in the available tick rates. + + + + + The index of the server's send tick rate in the available tick rates. + + + + + Represents a resolved tick rate. + + + The tick rate is resolved by the client and server tick rates. + + + + The size of the Resolved structure in bytes. + + + The size of the Resolved structure in words. + + + The tick rate for the client. + + + The send tick rate for the client. + + + The tick rate for the server. + + + The send tick rate for the server. + + + + Gets the delta time for the server tick rate. + + + + + Gets the delta time for the server send rate. + + + + + Gets the stride of the server tick rate relative to the client tick rate. + + + + + Gets the delta time for the client tick rate. + + + + + Gets the delta time for the client send rate. + + + + + Gets the stride of the client tick rate. This is always 1. + + + + + + + + Enumerates the possible results of validating a tick rate selection. + + + + The tick rate selection is valid. + + + An error occurred during validation. + + + The tick rate selection was not found. + + + The tick rate selection is invalid. + + + The server index in the tick rate selection is out of range. + + + The client send index in the tick rate selection is out of range. + + + The server send index in the tick rate selection is out of range. + + + The server send rate in the tick rate selection is larger than the tick rate. + + + + Gets the tick rate for the client. + + + + + Gets the count of tick rates in the TickRate. + + + + + Gets the tick rate at the specified index. + + The index of the tick rate to get. + The tick rate at the specified index. + + + + Gets the divisor for the tick rate at the specified index. + + The index of the tick rate to get the divisor for. + The divisor for the tick rate at the specified index. + Thrown when the index is out of range. + Thrown when the client tick rate is not divisible by the tick rate at the specified index. + + + + Gets the tick rate at the specified index. + + The index of the tick rate to get. + The tick rate at the specified index. + Thrown when the index is out of range. + + + + Converts the tick rates to an array. + + An array containing the tick rates. + + + + Validates the tick rates in the TickRate. + + true if the tick rates are valid; otherwise, false. + + The tick rates are valid if: + - There is at least one tick rate. + - There are no more than four tick rates. + - The first tick rate is greater than 0. + - Each tick rate is a divisor of the first tick rate. + + + + + Clamps the indices in the specified Selection to valid ranges. + + The Selection to clamp. + A new Selection with clamped indices. If the TickRate is invalid, returns a default Selection. + + + + Validates the tick rates in the specified Selection. + + The Selection to validate. + A ValidateResult that indicates the result of the validation. + + The Selection is valid if: + - The TickRate is valid. + - The client tick rate in the Selection matches the client tick rate in the TickRate. + - The server index in the Selection is within the range of available tick rates. + - The server send index in the Selection is within the range of available tick rates. + - The client send index in the Selection is within the range of available tick rates. + - The server send rate in the Selection is not larger than the server tick rate. + + + + + Initializes the TickRate class by setting up the valid tick rates and their lookup dictionary. + + + + + Checks if the provided TickRate is valid. + + The TickRate to validate. + true if the TickRate is valid; otherwise, false. + + + + Checks if the provided tick rate is valid. + + The tick rate to validate. + true if the tick rate is valid; otherwise, false. + + + + Retrieves the TickRate associated with the specified tick rate. + + The tick rate to retrieve the TickRate for. + The TickRate associated with the specified tick rate. + Thrown when the tick rate is invalid. + + + + Resolves the specified Selection into a Resolved structure. + + The Selection to resolve. + A Resolved structure that represents the resolved tick rates. + + + + Gets a read-only list of all available TickRates. + + + This property ensures that the TickRate class is initialized before returning the list. + + + + + A timer that is based on ticks instead of seconds. + + + + + Gets a TickTimer that is not running. + + + + + Gets a value indicating whether the TickTimer is running. + + + true if the TickTimer is running; otherwise, false. + + + + + Gets the target tick of the TickTimer. + + + The target tick if the TickTimer is running; otherwise, null. + + + + + Checks if the TickTimer has expired. + + The NetworkRunner associated with the TickTimer. + true if the TickTimer is alive, the runner is running, and the target tick has been reached or passed; otherwise, false. + + + + Checks if the TickTimer has expired or is not running. + + The NetworkRunner associated with the TickTimer. + true if the TickTimer is not running, the runner is not running, or the TickTimer has expired; otherwise, false. + + + + Gets the number of remaining ticks until the TickTimer expires. + + The NetworkRunner associated with the TickTimer. + The number of remaining ticks if the TickTimer is alive and running; otherwise, null. + + + + Gets the remaining time in seconds until the TickTimer expires. + + The NetworkRunner associated with the TickTimer. + The remaining time in seconds if there are remaining ticks; otherwise, null. + + + + Creates a TickTimer from a specified delay in seconds. + + The NetworkRunner associated with the TickTimer. + The delay in seconds to set the TickTimer to. + A TickTimer that will expire after the specified delay in seconds. If the NetworkRunner is not alive or not running, returns a default TickTimer. + + + + Creates a TickTimer from a specified number of ticks. + + The NetworkRunner associated with the TickTimer. + The number of ticks to set the TickTimer to. + A TickTimer that will expire after the specified number of ticks. If the NetworkRunner is not alive or not running, returns a default TickTimer. + + + + Returns a string that represents the current TickTimer. + + A string that represents the current TickTimer. + + + + The number of available samples. + + + + + The maximum number samples that can fit in this series. + + + + + True if the series contains zero samples. + + + + + True if the series contains the maximum number of samples. + + + + + The most recent sample. + + + + + The arithmetic mean of the samples in the series. + + + + + The variance of the samples in the series. + + + + + The standard deviation of the samples in the series. + + + + + The smallest value in the series. + + + + + The largest value in the series. + + + + + The median value in the series. + + + + + The mean absolute deviation from the mean. + + + + + The median absolute deviation from the median. + + + + + Returns the exponentially-weighted average of the series (with smoothing factor alpha). + + + + + Adds a new sample to the time series. If the series is already full, the oldest sample will be removed. + + The new sample value to be added to the series. + Thrown when the provided value is NaN or Infinity. + + + + Removes all samples and resets statistical values. + + + + + A base class for Unity array surrogates. + + Unmanaged type of the array elements. + Unmanaged type of the reader/writer for the array elements. + + + + Gets or sets the data array for the UnityArraySurrogate. + + + + + Reads data into the UnityArraySurrogate from a specified memory location. + + The memory location to read from. + The number of elements to read. + + + + Writes data from the UnityArraySurrogate to a specified memory location. + + The memory location to write to. + The number of elements to write. + + + + Initializes the UnityArraySurrogate with a specified capacity. + + The capacity to initialize the UnityArraySurrogate with. + + + + A surrogate for serializing a dictionary. + + The type of the key. + The type of the key reader writer. + The type of the value. + The type of the value reader writer. + + + + + Gets or sets the data property. + + + + + Reads data into the UnityDictionarySurrogate from a specified memory location. + + The memory location to read from. + The number of elements to read. + + + + Writes data from the UnityDictionarySurrogate to a specified memory location. + + The memory location to write to. + The number of elements to write. + + + + Initializes the UnityDictionarySurrogate with a specified capacity. + + The capacity to initialize the UnityDictionarySurrogate with. + + + + A surrogate for serializing a linked list. + + The type of the elements in the linked list. + The type of the reader writer for the elements in the linked list. + + + Gets or sets the data property. + + + + Reads data into the UnityLinkedListSurrogate from a specified memory location. + + The memory location to read from. + The number of elements to read. + + + + Writes data from the UnityLinkedListSurrogate to a specified memory location. + + The memory location to write to. + The number of elements to write. + + + + Initializes the UnityLinkedListSurrogate with a specified capacity. + + The capacity to initialize the UnityLinkedListSurrogate with. + + + + Represents a base class for Unity surrogates. + This class is serializable and provides abstract methods for reading, writing, and initializing data. + + + + + Reads data from a specified memory location into the UnitySurrogateBase. + + The memory location to read from. + The number of elements to read. + + + + Writes data from the UnitySurrogateBase to a specified memory location. + + The memory location to write to. + The number of elements to write. + + + + Initializes the UnitySurrogateBase with a specified capacity. + + The capacity to initialize the UnitySurrogateBase with. + + + + Represents an interface for Unity surrogates. + This interface provides methods for reading and writing data. + + + + + Reads data from a specified memory location. + + The memory location to read from. + The number of elements to read. + + + + Writes data to a specified memory location. + + The memory location to write to. + The number of elements to write. + + + + Represents a base class for Unity value surrogates. + This class is serializable and provides methods for reading, writing, and initializing data. + + The type of the data. + The type of the reader/writer for the data. Must be unmanaged and implement IElementReaderWriter{T}. + + + + Gets or sets the data for the UnityValueSurrogate. + + + + + Reads data into the UnityValueSurrogate from a specified memory location. + + The memory location to read from. + The number of elements to read. + + + + Writes data from the UnityValueSurrogate to a specified memory location. + + The memory location to write to. + The number of elements to write. + + + + Initializes the UnityValueSurrogate with a specified capacity. + + The capacity to initialize the UnityValueSurrogate with. + + + + Represents an interface for Unity value surrogates. + This interface provides a property for accessing and modifying the data. + + The type of the data. + + + + Gets or sets the data for the Unity value surrogate. + + + + + Enum representing the possible modes for adding a system to the Unity player loop. + + + + Add the system as the first child of the parent system. + + + Add the system as the last child of the parent system. + + + Add the system before the parent system in the player loop. + + + Add the system after the parent system in the player loop. + + + + The unique identifier for a network entity. + + + + + The size of the network id block in bytes. + + + + + IEqualityComparer interface for NetworkId objects. + + + + + Determines whether the specified NetworkId objects are equal. + + + + + Returns a hash code for the specified NetworkId object. + + + + + The size of the network id in bytes. + + + + + The alignment of the network id in bytes. + + + + + The IEqualityComparer for NetworkId objects. + + + + + The raw value of the network id. + + + + + Signal if the network id is valid. + + + + + Signal if the network id is reserved. + + + + + Determines whether the current NetworkId object is equal to another NetworkId object. + + A NetworkId object to compare with this object. + true if the current object is equal to the other parameter; otherwise, false. + + + + Compares the current NetworkId object with another NetworkId object. + + A NetworkId object to compare with this object. + A value that indicates the relative order of the objects being compared. + + + + Determines whether the specified object is equal to the current NetworkId object. + + The object to compare with the current object. + true if the specified object is equal to the current object; otherwise, false. + + + + Determines whether two NetworkId objects are equal. + + + + + Determines whether two NetworkId objects are not equal. + + + + + Converts the NetworkId object to a boolean value. + + + + + Writes the NetworkId to the provided NetBitBuffer. + + The buffer to write the NetworkId to. + The NetworkId to write. + + + + Reads a NetworkId from the provided NetBitBuffer. + + The buffer to read the NetworkId from. + The NetworkId read from the buffer. + + + + Writes this NetworkId to the provided NetBitBuffer. + + The buffer to write this NetworkId to. + + + + Get the hash code for this NetworkId. + + + + + String representation of the NetworkId. + + + + + String conversion specifically for use in prefixing names of GameObjects. + + + + + Enum representing the priority levels for network objects + + + + + Priority level assigned to player-related network objects. + + + + + High priority level, typically assigned to network objects that require frequent updates. + + + + + Medium priority level, typically assigned to network objects that require regular updates. + + + + + Low priority level, typically assigned to network objects that require less frequent updates. + + + + + Lowest priority level, typically assigned to network objects that require minimal updates. + + + + + The primary Fusion component for networked GameObject entities. + This stores the object's network identity and manages the object's state and input authority. + + + + + Delegate for determining if a network object should be replicated to a specific player. + + The network object in question. + The player to potentially replicate to. + True if the object should be replicated to the player, false otherwise. + + + + Delegate for determining the priority level of a network object for a specific player. + + The network object in question. + The player for whom the priority level is being determined. + The priority level of the network object for the player. + + + + The unique identifier for this network entity. + + + + + Signal that this comes from a Resume Spawn + + + + + The this entity is associated with. + + + + + Used for whenever objects need to be sorted in a deterministic order, like + when registering scene objects. + + + + + Delegate callback used to override if an object should be replicate to a client or not + + + + + Delegate callback used to override priority value for a specific object-player pair + + + + + How Object Interest is determined for this object. + + + + + Last tick this object received an update. + + + + + Flags used for network object prefabs and similar + + + + + The type ID for this prefab or scene object, set when adding to the prefab table and registering scene objects, respectively. + All spawned instances of this object will retain this value. Use for the unique ID of network entries. + + + + + Array of initial child nested entities, that are children of this Object. + + + + + Array of all s associated with this network entity. + + + + + The ID + Unity GameObject name for this entity. + + + + + Returns true while a NetworkObject is valid (after it is spawned until it is despawned). + While this property is true, Networked Properties and RPCs on the NetworkObject can be used. + Internally, the property validates whether a NetworkObject is associated with its , and that runner is not null. + + + + + If this object is inserted into the simulation + + + + + Returns if is the designated Input Source for this network entity. + + + + + Returns if is the designated State Source for this network entity. + + + + + Returns if is neither the Input nor State Source for this network entity. + + + + + Returns true if this object is nested in a prefab instance. + + + + + Returns root of this prefab instance, if this object is nested. + + + + + Returns the for this instance, + indicating what snapshot data will be used to render it. + + + + + Force the used to + + + + + Returns the for this instance, + indicating how snapshot data will be used to render it. + + + + + Returns the current interpolation time for this object + + + + + Returns the that has Input Authority over this network entity. + PlayerRefs are assigned in order from 0 to MaxPlayers-1 and are re-used as players join and leave. + The only caveat is that the server player (if one exists), always gets the last index no matter how many clients are connected. + + + + + Returns the that has State Authority over this network entity. + PlayerRefs are assigned in order from 0 to MaxPlayers-1 and are re-used as players join and leave. + The only caveat is that the server player (if one exists), always gets the last index no matter how many clients are connected. + + + + + Toggles if this NetworkObject is included in the , which will include the prefab in builds as a Spawnable object. + + + + + Awake is called when the script instance is being loaded. + + + + + OnDestroy is called when the script instance is being destroyed. + + + + + Calculates the total word count for a given NetworkObject. + + The NetworkObject for which the word count is to be calculated. + The total word count of the NetworkObject. Returns 0 if the NetworkObject is not alive. + Thrown when a NetworkBehaviour reference is missing in the NetworkBehaviour array of the NetworkObject. + + + + Gets a bitmask of flags, representing the current local authority over this . + + + + + Sets which has Input Authority for this Object. + + + + + Request state authority over this on shared mode. + + + + + Release the state authority over this on shared mode. + + + + + Removes input authority from whichever player has it for this object. Only valid when called on a Host or Server peer. + + + + + Converts the Network Object to it's NetworkId. + + The object to convert + The of the object. Default if the object is not alive (null or destroyed) + + + + Add or remove specific player interest in this NetworkObject. Only the NetworkObject State Authority can set interest. + + The player to set interest for + If the player should always be interested in this object + + + + Copies the entire State from another + + to copy the State from + + + + Copies the entire State from another based on the + + to copy the state from + + + + Return the . + + The that is assigned to + The to get the ID from + The of the object. Default if the object is not alive (null or destroyed) + + + + Return the . + + The to get the ID from + The of the object. Default if the object is not alive (null or destroyed) + + + + Return the reference on that matches the provided + + The that will be used to try to find a with ID equals to + The to be searched + The found . null if the provided is not valid + + + + Returns a string representation of the object. + + The StringBuilder to append to. + + + + Enum representing the flags for network objects in the Fusion system. + This enum is marked with the Flags attribute, allowing it to be treated as a bit field. + + + + + Represents a state where no flags are set. + + + + + Mask for isolating the version part of the flags. + + + + + Represents the first version of the network object flags. + + + + + Flag indicating that the network object should be ignored. + + + + + Shared Mode only. The current Master client always has State Authority for this object. + If the Master client leaves the new Master client automatically becomes the State Authority + for it. + + + + + Flag indicating that the network object should be destroyed when the state authority leaves. + + + + + Flag indicating that the network object allows state authority override. + + + + + Extension methods for the NetworkObjectFlags enum. + + + + + Returns the version of the flags. + + The flags to get the version of. + The version of the flags. + + + + Check if the flags are the current version. + + The flags to check. + True if the flags are the current version, false otherwise. + + + + Sets the flags to the current version. + + The flags to set. + The flags with the version set to the current version. + + + + Check if the flags are ignored. + + The flags to check. + True if the flags are ignored, false otherwise. + + + + Sets the ignored flag on the flags. + + Flags to set the ignored flag on. + Ignored flag value. + The flags with the ignored flag set to the given value. + + + + Sets the flags with the provided mask and value. + + The original flags. + The value to be set. + The mask to isolate the part of the flags to be set. + The flags after setting the value with the mask. + + This method first clears the part of the flags specified by the mask, then sets that part to the provided value. + + + + + NetworkObjectGuid + + + + + EqualityComparer for NetworkObjectGuid + + + + + Check if two NetworkObjectGuid are equals + + + + + Get the hashcode for a NetworkObjectGuid + + + + The Size of the NetworkObjectGuid in bytes + + + The alignment of the NetworkObjectGuid + + + The Raw Guid Value of the NetworkObjectGuid + + + The default value of a NetworkObjectGuid + + + + Create a new NetworkObjectGuid + + The guid to use + + + + Create a new NetworkObjectGuid + + Data0 of the Guid + Data1 of the Guid + + + + Create a NetworkObjectGuid from a byte array + + The byte array to create the NetworkObjectGuid from + + + + Create a NetworkObjectGuid from a byte* + + The byte* to create the NetworkObjectGuid from + + + + Signal if the NetworkObjectGuid is valid. + + + + + Implicit conversion from Guid to NetworkObjectGuid + + Guid to convert from + NetworkObjectGuid + + + + Implicit conversion from NetworkObjectGuid to Guid + + NetworkObjectGuid to convert from + Guid + + + + Try to parse a string into a NetworkObjectGuid + + String to parse + Parsed NetworkObjectGuid + True if the string was parsed successfully, false otherwise + + + + Parse a NetworkObjectGuid from a string. + + The string to parse. + The parsed NetworkObjectGuid. + + + + Compare two NetworkObjectGuid + + True if the NetworkObjectGuid are equal, false otherwise + + + + Compare two NetworkObjectGuid + + True if the NetworkObjectGuid are not equal, false otherwise + + + + Check if the NetworkObjectGuid is equal to another NetworkObjectGuid + + The other NetworkObjectGuid to check against + True if the NetworkObjectGuids are equal, false otherwise + + + + Check if the NetworkObjectGuid is equal to another object + + The other object to check against + True if the objects are equal, false otherwise + + + + Get the hashcode for a NetworkObjectGuid + + + + + Returns a string representation of the NetworkObjectGuid. + + + + + Returns a string representation of the NetworkObjectGuid. + + + + + Returns a string representation of the NetworkObjectGuid. + + + + + Compare the NetworkObjectGuid to another NetworkObjectGuid + + The other NetworkObjectGuid to compare against + 0 if the NetworkObjectGuid are equal, -1 if this NetworkObjectGuid is less than the other, 1 if this NetworkObjectGuid is greater than the other + + + + Explicit conversion from NetworkObjectGuid to NetworkPrefabRef + + NetworkObjectGuid to convert from + NetworkPrefabRef + + + + NetworkPrefabRef + + + A decoupled prefab reference. Internally stored as a GUID. + + + + + EqualityComparer for NetworkPrefabRef + + + + + Check if two NetworkPrefabRef are equals + + + + + Get the hashcode for a NetworkPrefabRef + + + + The Size of the NetworkPrefabRef in bytes + + + The alignment of the NetworkPrefabRef + + + The Raw Guid Value of the NetworkPrefabRef + + + The default value of a NetworkPrefabRef + + + + Create a new NetworkPrefabRef + + The guid to use + + + + Create a new NetworkPrefabRef + + Data0 of the Guid + Data1 of the Guid + + + + Create a NetworkPrefabRef from a byte array + + The byte array to create the NetworkPrefabRef from + + + + Create a NetworkPrefabRef from a byte* + + The byte* to create the NetworkPrefabRef from + + + + Signal if the NetworkPrefabRef is valid. + + + + + Implicit conversion from Guid to NetworkPrefabRef + + Guid to convert from + NetworkPrefabRef + + + + Implicit conversion from NetworkPrefabRef to Guid + + NetworkPrefabRef to convert from + Guid + + + + Try to parse a string into a NetworkPrefabRef + + String to parse + Parsed NetworkPrefabRef + True if the string was parsed successfully, false otherwise + + + + Parse a NetworkPrefabRef from a string. + + The string to parse. + The parsed NetworkPrefabRef. + + + + Compare two NetworkPrefabRef + + True if the NetworkPrefabRef are equal, false otherwise + + + + Compare two NetworkPrefabRef + + True if the NetworkPrefabRef are not equal, false otherwise + + + + Check if the NetworkPrefabRef is equal to another NetworkPrefabRef + + The other NetworkPrefabRef to check against + True if the NetworkPrefabRefs are equal, false otherwise + + + + Check if the NetworkPrefabRef is equal to another object + + The other object to check against + True if the objects are equal, false otherwise + + + + Get the hashcode for a NetworkPrefabRef + + + + + Returns a string representation of the NetworkPrefabRef. + + + + + Returns a string representation of the NetworkPrefabRef. + + + + + Returns a string representation of the NetworkPrefabRef. + + + + + Compare the NetworkPrefabRef to another NetworkPrefabRef + + The other NetworkPrefabRef to compare against + 0 if the NetworkPrefabRef are equal, -1 if this NetworkPrefabRef is less than the other, 1 if this NetworkPrefabRef is greater than the other + + + + Explicit conversion from NetworkPrefabRef to NetworkObjectGuid + + NetworkPrefabRef to convert from + NetworkObjectGuid + + + + Enum representing various flags for a network object header. + Each flag represents a different state or property of a network object. + + + + + Flag indicating that the object is of global interest. + + + + + Flag indicating that the object should be destroyed when the state authority leaves. + + + + + Flag indicating that the object was spawned by a client. + + + + + Flag indicating that the state authority override is allowed. + + + + + Flag indicating that the object has no prefab. (Mainly internal data). + + + + + Flag indicating that the object is an array of structs. + + + + + Flag indicating that the object should not be destroyed on load. + + + + + Flag indicating that the object has a main network TRSP. + + + + + Flag indicating that the object is in an area of interest. + + + + + Represents a pointer to a NetworkObjectHeader. + This struct is unsafe because it uses pointers. + + + + + Represents a pointer to a NetworkObjectHeader. + This struct is unsafe because it uses pointers. + + + + + Pointer to a NetworkObjectHeader. + + + + + Gets the Type of the NetworkObjectHeader this struct points to. + + + + + Gets the Id of the NetworkObjectHeader this struct points to. + + + + + + + + + + Network object header information for a . + + + + + Network object header information for a . + + + + + The size of the NetworkObjectHeader in bytes. + + + + + The size of the NetworkObjectHeader in words. + + + + + The word index of the player data in the NetworkObjectHeader. + + + + + The unique identifier of the network object. + + + + + The size of the network object's data in words. + + + + + The number of behaviours in the network object. + + + + + The type identifier of the network object. + + + + + The unique identifier of the root network object in the nesting hierarchy. + + + + + The nesting key of the network object. + + + + + The flags indicating various states or properties of the network object. + + + + + The player reference who has input authority over the network object. + + + + + The player reference who has state authority over the network object. + + + + + The unique data for each player. + + + + + Reserved space for future use. + + + + + The size of the network object's data in bytes. + + + + + Returns a pointer to the data of a NetworkObjectHeader. + + Pointer to the NetworkObjectHeader. + Pointer to the data of the NetworkObjectHeader. + + + + Returns the count of data words in a NetworkObjectHeader. + + Pointer to the NetworkObjectHeader. + The count of data words in the NetworkObjectHeader. + + + + Returns a pointer to the array of behaviour change ticks in a NetworkObjectHeader. + + Pointer to the NetworkObjectHeader. + Pointer to the array of behaviour change ticks in the NetworkObjectHeader. + + + + Checks if a NetworkObjectHeader has a main network TRSP. + + Pointer to the NetworkObjectHeader. + True if the NetworkObjectHeader has a main network TRSP, false otherwise. + + + + Returns a pointer to the main network TRSP data of a NetworkObjectHeader, if it exists. + + Pointer to the NetworkObjectHeader. + Pointer to the main network TRSP data of the NetworkObjectHeader if it exists, null otherwise. + + + + The string representation of the NetworkObjectHeader. + + + + + Checks if the current instance of NetworkObjectHeader is equal to another instance of the same type. + + + + + Checks if the current instance of NetworkObjectHeader is equal to another object. + + + + + Generates a hash code for the current instance of NetworkObjectHeader. + + + + + Determines if two instances of NetworkObjectHeader are equal. + + True if the instances are equal; otherwise, false. + + + + Determines if two instances of NetworkObjectHeader are not equal. + + True if the instances are not equal; otherwise, false. + + + + Compute the CRC of this Object Snapshot + + + + + Interface for initializing network objects. + + + + + Initializes the network object. + + The network object to initialize. + + + + Initializes network objects for Unity. + + + + + Initializes the network object. + + The network object to initialize. + + + + Meta information about a network object. + + + + The flags indicating various states or properties of the network object. + + + Get the NetworkObjectTypeId of this object. + + + Get the NetworkId of this object. + + + Get the NetworkId of the root object in the nesting hierarchy. + + + The nesting key of the network object. + + + Get the Player that has state authority over this object. + + + Get the Player that has input authority over this object. + + + + A key used to identify a network object nesting. + + + + + Implements the IEqualityComparer interface. + + + + + Determines whether two NetworkObjectNestingKey objects are equal. + + + + + Returns a hash code for the specified NetworkObjectNestingKey. + + + + The size of the NetworkObjectNestingKey in bytes. + + + The alignment of the NetworkObjectNestingKey in bytes. + + + The value of the NetworkObjectNestingKey. + + + + Checks if the NetworkObjectNestingKey is none. + + True if the value of the NetworkObjectNestingKey is 0; otherwise, false. + + + + Checks if the NetworkObjectNestingKey is valid. + + True if the value of the NetworkObjectNestingKey is greater than 0; otherwise, false. + + + + Initializes a new instance of the NetworkObjectNestingKey struct with a specified value. + + The value of the NetworkObjectNestingKey. + + + + Checks if the current instance of NetworkObjectNestingKey is equal to another instance of the same type. + + An instance of NetworkObjectNestingKey to compare with the current instance. + True if the current instance is equal to the other parameter; otherwise, false. + + + + Checks if the current instance of NetworkObjectNestingKey is equal to another object. + + An object to compare with the current instance. + True if the current instance is equal to the obj parameter; otherwise, false. + + + + Serves as the default hash function. + + A hash code for the current object. + + + + Returns a string that represents the current object. + + A string that represents the current object. + + + + Interface which defines the handlers for Spawn() and Despawn() actions. + Passing an instance of this interface to + as the argument value will assign that instance + as the handler for runner Spawn() and Despawn() actions. + By default (if == null) actions will use Instantiate(), and Despawn() actions will use Destroy(). + + + + + Acquires an instance of a prefab for a network object. + + The NetworkRunner that manages the network objects. + The context that provides information for acquiring the prefab instance. + The acquired NetworkObject instance. + A NetworkObjectAcquireResult indicating the result of the operation. + + + + Releases an instance of a network object. + + The NetworkRunner that manages the network objects. + The context that provides information for releasing the network object instance. + + + + Translates guid into prefab id. + + + + + + + + + + + + + + + + + + + + Represents the context for acquiring a prefab instance for a network object. + This struct is unsafe because it uses pointers. + + + + + The identifier of the prefab. + + + + + The metadata of the network object. + + + + + Indicates whether the operation is synchronous. + + + + + Indicates whether the network object should not be destroyed on load. + + + + + Initializes a new instance of the NetworkPrefabAcquireContext struct with the specified parameters. + + The identifier of the prefab. + The metadata of the network object. + Indicates whether the operation is synchronous. + Indicates whether the network object should not be destroyed on load. + + + + Checks if the Header is not null. + + True if the Header is not null; otherwise, false. + + + + Gets the data pointer to the first word of this NetworkObject's data block. + + Data pointer to the first word of this NetworkObject's data block or null, if data is not yet allocated. + + + + Represents the context for releasing a network object. + This struct is unsafe because it uses pointers. + + + + + The network object to be released. + + + + + The type identifier of the network object. + + + + + Indicates whether the network object is being destroyed. + + + + + Indicates whether the network object is a nested object. + + + + + Initializes a new instance of the NetworkObjectReleaseContext struct with the specified parameters. + + The network object to be released. + The type identifier of the network object. + Indicates whether the network object is being destroyed. + Indicates whether the network object is a nested object. + + + + Returns a string that represents the current object. + + A string that represents the current object. + + + + Enum representing the possible results of acquiring a prefab instance for a network object. + + + + Indicates that the prefab instance was successfully acquired. + + + Indicates that the acquisition of the prefab instance failed. + + + Indicates that the acquisition of the prefab instance should be retried. + + + Indicates that the acquisition of the prefab instance should be ignored. + + + + A dummy implementation of the INetworkObjectProvider interface. + This class is used for testing purposes and throws a NotImplementedException for all its methods. + + + + + + + + + + + + + + This class is used to compare two NetworkObject instances based on their SortKey. + It implements the IComparer interface. + + + + + An instance of the NetworkObjectSortKeyComparer class. + + + + + Compares two NetworkObject instances based on their SortKey. + + The first NetworkObject to compare. + The second NetworkObject to compare. + + A signed integer that indicates the relative values of x and y. + + + + + Enum representing the type of a NetworkObject. + + + + + Represents a NetworkObject that is a Prefab. + + + + + Represents a NetworkObject that is a Custom type. + + + + + Represents a NetworkObject that is an InternalStruct. + + + + + Represents a NetworkObject that is a SceneObject. + + + + + Represents an Invalid NetworkObject type. + + + + + ID for a Prefab which has been cataloged in a . + + + + + Comparer + + + + + Checks if two NetworkObjectTypeId instances are equal. + + + + + Gets the hash code of a NetworkObjectTypeId instance. + + The NetworkObjectTypeId instance. + + + Represents the size of a NetworkObjectTypeId in bytes. + + + Represents the alignment of a NetworkObjectTypeId in memory. + + + Represents the maximum number of SceneObjects that can be represented by a NetworkObjectTypeId. + + + An instance of the NetworkObjectTypeId EqualityComparer class. + + + Represents a NetworkObjectTypeId for the PlayerData. + + + Gets the kind of the NetworkObjectTypeId. + + + + Creates a NetworkObjectTypeId from a SceneRef, an object index, and an optional NetworkSceneLoadId. + + The SceneRef to use. + The object index to use. + The NetworkSceneLoadId to use. Defaults to default(NetworkSceneLoadId). + + A NetworkObjectTypeId that represents a SceneObject with the given SceneRef, object index, and NetworkSceneLoadId. + + Thrown when the provided SceneRef is not valid. + Thrown when the provided object index is out of range. + + + + Creates a NetworkObjectTypeId from a NetworkSceneObjectId. + + + + + + + Gets the NetworkSceneObjectId representation of the NetworkObjectTypeId assuming it is a SceneObject. + + + The NetworkSceneObjectId representation of the NetworkObjectTypeId. + + Thrown when the NetworkObjectTypeId is not a SceneObject. + + + + Creates a NetworkObjectTypeId from a NetworkPrefabId. + + The NetworkPrefabId to use. + + A NetworkObjectTypeId that represents a Prefab with the given NetworkPrefabId. + + Thrown when the provided NetworkPrefabId is not valid. + + + + Gets the NetworkPrefabId representation of the NetworkObjectTypeId assuming it is a Prefab. + + + The NetworkPrefabId representation of the NetworkObjectTypeId. + + Thrown when the NetworkObjectTypeId is not a Prefab. + + + + Creates a NetworkObjectTypeId from a raw uint value representing a Custom type. + + The raw uint value to use. + + A NetworkObjectTypeId that represents a Custom type with the given raw value. + + + + + Gets the raw uint value representation of the NetworkObjectTypeId assuming it is a Custom type. + + + The raw uint value representation of the NetworkObjectTypeId. + + Thrown when the NetworkObjectTypeId is not a Custom type. + + + + Creates a NetworkObjectTypeId from a ushort value representing an InternalStruct type. + + The ushort value to use. + + A NetworkObjectTypeId that represents an InternalStruct type with the given ushort value. + + + + + Gets the ushort value representation of the NetworkObjectTypeId assuming it is an InternalStruct type. + + + The ushort value representation of the NetworkObjectTypeId. + + Thrown when the NetworkObjectTypeId is not an InternalStruct type. + + + Represents the first part of the value of a NetworkObjectTypeId. + + + Represents the second part of the value of a NetworkObjectTypeId. + + + + Checks if the NetworkObjectTypeId is invalid. + + + + + Checks if the NetworkObjectTypeId is valid. + + + + + Checks if the NetworkObjectTypeId is a SceneObject. + + + + + Checks if the NetworkObjectTypeId is a Prefab. + + + + + Checks if the NetworkObjectTypeId is an InternalStruct. + + + + + Checks if the NetworkObjectTypeId is a Custom type. + + + + + Checks if the current NetworkObjectTypeId instance is equal to another NetworkObjectTypeId instance. + + The other NetworkObjectTypeId instance to compare with the current instance. + + + + Generates a hash code for the current NetworkObjectTypeId instance. + + + + + Determines whether the specified object is equal to the current NetworkObjectTypeId instance. + + The object to compare with the current NetworkObjectTypeId instance. + + + + Returns a string that represents the current NetworkObjectTypeId instance. + + + + Determines whether two NetworkObjectTypeId instances are equal. + + + Determines whether two NetworkObjectTypeId instances are not equal. + + + + Converts a NetworkPrefabId instance to a NetworkObjectTypeId instance. + + The NetworkPrefabId instance to convert. + + A NetworkObjectTypeId instance that represents a Prefab with the given NetworkPrefabId. + + + + + Interface for a network asset source. + + Type of the network asset. + + + + Acquires the network asset. + + If true, the acquisition is done synchronously. + + + + Releases the network asset. + + + + + Waits for the result of the network asset acquisition. + + The network asset. + + + + Checks if the network asset acquisition is completed. + + + + + Gets the description of the network asset. + + + + + Interface for a network prefab source. + + + + + Gets the GUID of the network object asset. + + + + + This class represents the data for a network object prefab. + + + + + The unique identifier for the network object. + + + + + ID for a Prefab which has been cataloged in a . + + + + + Equality comparer for NetworkPrefabId. + + + + + Checks if two NetworkPrefabId are equal. + + + + + Gets the hash code of a NetworkPrefabId. + + + + + The size of a NetworkPrefabId. + + + + + The alignment of a NetworkPrefabId. + + + + + The maximum index value of a NetworkPrefabId. + + + + + The raw value of the NetworkPrefabId. + + + + + Checks if the NetworkPrefabId is none. + + + + + Checks if the NetworkPrefabId is valid. + + + + + Converts the NetworkPrefabId to an index. + + + + + Creates a NetworkPrefabId from an index. + + + + + Creates a NetworkPrefabId from a raw value. + + + + + Checks if the NetworkPrefabId is equal to another NetworkPrefabId. + + + + + Checks if the NetworkPrefabId is equal to another object. + + + + + Gets the hash code of the NetworkPrefabId. + + + + + Converts the NetworkPrefabId to a string. + + + + + Compares the NetworkPrefabId to another object. + + + + + Converts the NetworkPrefabId to a string with optional brackets and prefix. + + + + + Checks if two NetworkPrefabId are equal. + + + + + Checks if two NetworkPrefabId are not equal. + + + + + Compares the NetworkPrefabId to another NetworkPrefabId. + + + + + Enum representing the possible results of attempting to get a prefab from the NetworkPrefabTable. + + + + + The prefab was successfully retrieved. + + + + + The retrieval of the prefab is in progress. + + + + + The prefab was not found in the NetworkPrefabTable. + + + + + There was an error in loading the prefab. + + + + + Class representing a table of network prefabs. + + + + + Options for the NetworkPrefabTable. + + + + + All prefab sources. + + + + + Acquired prefabs mask. + + + + + Data about acquired prefabs. Only indices matched by are valid. + + + + + Translates guid to index in . + + + + + Incremented every time a change occurs. + + + + + All prefab sources. + + + + + Prefab table version. Incremented every time a change occurs. + + + + + Returns all entries in the table. + + + + + Adds a prefab source to the table. + + Prefab source to add. + Thrown if a prefab source with the same guid already exists. + + + + Tries to add a prefab source to the table. + + Prefab source to add. + Id of the prefab source. + True if the prefab source was added, false otherwise. + Thrown if is null. + + + + Gets a prefab source by guid. + + Guid of the prefab source. + The prefab source, or default if not found. + + + + Gets a prefab source by id. + + Id of the prefab source. + The prefab source, or default if not found. + + + + Gets a prefab id by guid. + + Guid of the prefab source. + The prefab id, or default if not found. + + + + Gets a prefab guid by id. + + Id of the prefab source. + The prefab guid, or default if not found. + + + + Get the instance count of a prefab id. + + Id of the prefab. + The instance count, or 0 if not found. + + + + Add an instance of a prefab id. + + Id of the prefab. + The new instance count, or 0 if not found. + + + + Remove an instance of a prefab id. + + Id of the prefab. + The new instance count, or 0 if not found. + + + + Returns true if the prefab table contains a prefab with the given id. + + Id of the prefab. + True if the prefab table contains a prefab with the given id. + + + + Signal if a prefab id has been acquired. + + Id of the prefab. + True if the prefab id has been acquired. + + + + Load a prefab by id. + + Id of the prefab. + If true, the load will be synchronous. + The loaded prefab, or null if not found. + + + + Unload a prefab by id. + + Id of the prefab. + True if the prefab was unloaded, false otherwise. + + + + Unload all unreferenced prefabs. + + If true, incomplete loads will be unloaded as well. + The number of prefabs unloaded. + + + + Unload all prefabs. + + + + + Clear the prefab table. + + + + + Options for the NetworkPrefabTable. + + + + + If true, prefabs will be unloaded when the last instance is released. + + + + + If true, all prefabs will be unloaded on shutdown. + + + + + Default options. + + + + + Provides constants and methods for managing authority masks. + + + + + Constant representing the state authority mask. + + + + + Constant representing the input authority mask. + + + + + Constant representing the proxy authority mask. + + + + + Constant representing no authority. + + + + + Constant representing all authorities. + + + + + Creates an authority mask based on the provided state and input flags. + + If true, the state authority is included in the mask. + If true, the input authority is included in the mask. + An integer representing the created authority mask. + + + + Flags a method as being a networked Remote Procedure Call. + Only usable in a NetworkBehaviour. + Calls to this method (from the indicated allowed ) will generate a network message, + which will execute the method remotely on the indicated . + The RPC method can include an empty argument, that will include meta information about the RPC on the receiving peer. + Example: + + | [Rpc(RpcSources.All, RpcTargets.All, InvokeLocal = false, Channel = RpcChannel.Reliable, TickAligned = true)]

+ | public void RPC_Configure(NetworkObject no, string name, Color color, RpcInfo info = default) { } +
+ To target a specific Player, use the : + + | [Rpc] + | public void RpcFoo([RpcTarget] PlayerRef targetPlayer) {} + + Use as a return value to access meta information about the RPC send attempt, such as failure to send reasons, message size, etc. + + Non-static RPCs are only valid on a . + Static RPCs can be implemented on s, and do not require a instance. + Static RPC require the first argument to be NetworkRunner. + + Static RPC Example: + + | [Rpc] + | public static void RPC_Configure(NetworkRunner runner) { } + +
+
+ + + The legal types that can trigger this Rpc. Cast to int. + Default value is (int). + + + + + The types that will receive and invoke this method. Cast to int. + Default value is (int). + + + + + Indicates if the method should be called locally (on the RPC caller). This happens immediately. + Default value is true. + + + + + Specifies which RpcChannel to use. + Default value is + + + + + Indicates if this RPC's execution will be postponed until the local simulation catches up with the sender's Tick number. + Even if set to false, the order of Rpcs is always preserved. Rpcs are deferred until all preceding Rpcs have + executed. + Default value is true. + + + + + Options for when the game is run in mode and RPC is invoked by the host. + + + + + Constructor for RpcAttributes. + + + + + Constructor for RpcAttributes. + + The legal types that can trigger this Rpc. Default is + The types that will receive and invoke this method. Default is + + + + Maximum allowed size for the payload of the RPC message. + + + + + Flags for the RPC channel. + + + + + Rpc order preserved, delivery verified, resend in case of a failed delivery. + + + + + Rpc order preserved, delivery not verified, no resend attempts. + + + + + Header for RPC messages. + + + + + The size of the RpcHeader structure in bytes. + + + + + The NetworkId of the object associated with the RPC message. + + + + + The behaviour associated with the RPC message. + + + + + The method associated with the RPC message. + + + + + Writes the RpcHeader to the provided byte pointer. + + The RpcHeader to write. + The byte pointer to write the RpcHeader to. + Returns the size of the RpcHeader structure in bytes. + + + + Reads the size of the RpcHeader from the provided byte pointer. + + The byte pointer to read the RpcHeader size from. + Returns the size of the RpcHeader structure in bytes. + + + + Reads the RpcHeader from the provided byte pointer. + + The byte pointer to read the RpcHeader from. + The size of the RpcHeader structure in bytes. + Returns the RpcHeader read from the byte pointer. + + + + Creates a new RpcHeader with the provided NetworkId, behaviour, and method. + + The NetworkId of the object associated with the RPC message. + The behaviour associated with the RPC message. + The method associated with the RPC message. + Returns a new RpcHeader with the provided parameters. + + + + Creates a new RpcHeader with the provided staticRpcKey. + + The staticRpcKey associated with the RPC message. + Returns a new RpcHeader with the provided staticRpcKey. + + + + Returns a string that represents the current RpcHeader. + + Returns a string that represents the current RpcHeader. + + + + Options for when the game is run in mode and RPC is invoked by the host. + + + + + If host invokes RPC will be set to (default). + + + + + If host invokes RPC will be set to the host's local player. + + + + + RpcInfo is a struct that contains information about the RPC message. + + + + + Represents the tick at which the RPC message was sent. + + + + + Represents the player who sent the RPC message. + + + + + Represents the channel through which the RPC message was sent. + + + + + Indicates whether the RPC message is invoked locally. + + + + + Creates a new RpcInfo instance for a local RPC message. + + The NetworkRunner associated with the RPC message. + The RpcChannel through which the RPC message was sent. + The RpcHostMode of the RPC message. + Returns a new RpcInfo instance with the provided parameters. + + + + Creates a new RpcInfo instance from a SimulationMessage. + + The NetworkRunner associated with the RPC message. + The SimulationMessage from which to create the RpcInfo instance. + The RpcHostMode of the RPC message. + Returns a new RpcInfo instance with the provided parameters. + + + + Returns a string that represents the current RpcInfo. + + Returns a string that represents the current RpcInfo. + + + + Represents the data required to invoke an RPC message. + + + + Represents the key associated with the RPC message. + + + Represents the sources of the RPC message. + + + Represents the targets of the RPC message. + + + Represents the delegate to be invoked for the RPC message. + + + + Returns a string that represents the current RpcInvokeData. + + Returns a string that represents the current RpcInvokeData. + + + + Represents a delegate that can be invoked by an RPC message. + + The NetworkBehaviour associated with the RPC message. + The SimulationMessage associated with the RPC message. + + The RpcInvokeDelegate is used to invoke an RPC message. The delegate is invoked by the RpcSystem + when an RPC message is received. The delegate is invoked with the NetworkBehaviour associated with + the RPC message and the SimulationMessage associated with the RPC message. + + + + + May be used as an optional return value. Contains meta data about the RPC send, such as failure to send reasons, culling, message size, etc. + Example: + + | [Rpc] + | public RpcInvokeInfo RpcFoo(int value) { + | return default; + | } + | + | public override void FixedUpdateNetwork() { + | var info = RpcFoo(); + | Debug.Log(info); + | } + + + + + Represents the result of the local RPC invocation. + + + Represents the result of the RPC message send operation. + + + Contains detailed information about the RPC send operation result. + + + + Returns a string that represents the current RpcInvokeInfo. + + + + + Results for the local RPC Invocation of the RPC method. + + + + + RPC has been invoked locally. + + + + + Not invoked locally because is false. + + + + + Not invoked locally because simulation stage is + + + + + Not invoked because source current authority does not match flags set in + + + + + Not invoked because target player is local and this current authority does not match flags set in + + + + + Not invoked because target player is not local. + + + + + RPC is too large. See for the maximum allowed size. + + + + + + + + + + Results for the RPC message send operation. Note: Some individual targets may be culled even if the send operation succeeds. + Information about culled targets can be found in . + + + + + RPC has been sent. Check for details. + + + + + Send culled because is false. + + + + + Send culled because source current authority does not match flags set in + + + + + Send culled because there are no active connections. + + + + + Send culled because target player does not exist. + + + + + Send culled because target player is local and is false. + + + + + RPC message size is too large to be sent. See for the maximum allowed size. + + + + + RPC send operation result information. + + + + + Result flags for the RPC send operation. + + + + + The size of the RPC message. + + + + + Returns a string that represents the current RpcSendResult. + + + + + Enum representing the sources of an RPC message. + + + + + Represents the state authority source of an RPC message. + + + + + Represents the input authority source of an RPC message. + + + + + Represents the proxy source of an RPC message. + + + + + Represents all possible sources of an RPC message. + + + + + Represents a delegate that can be invoked by an RPC message. + + The NetworkRunner associated with the RPC message. + The SimulationMessage associated with the RPC message. + + The RpcInvokeDelegate is used to invoke an RPC message. The delegate is invoked by the RpcSystem + when an RPC message is received. The delegate is invoked with the NetworkRunner associated with + the RPC message and the SimulationMessage associated with the RPC message. + + + + + RPC attribute used to indicate a specific target player for an RPC when sending from one player to another. + RPC is sent to the server, and then is forwarded to the specified player. + Usage: + + | [Rpc] + | public void RpcFoo([RpcTarget] PlayerRef targetPlayer) { } + + + + + RPC Attribute constructor. + + + + Enum representing the targets of an RPC message. + + + + + Represents the state authority target of an RPC message. + + + + + Represents the input authority target of an RPC message. + + + + + Represents the proxy target of an RPC message. + + + + + Represents all possible targets of an RPC message. + + + + + Enum representing the status of an RPC target. + + + + + Represents an unreachable RPC target. + + + + + Represents the RPC target as self. + + + + + Represents a remote RPC target. + + + + + Project configuration settings specific to how the Host Migration behaves. + + + + + Enabled the Host Migration feature + + + + + Delay between Host Migration Snapshot updates + + + + + Transitory Holder with all necessary information to restart the Fusion Runner + after the Host Migration has completed + + + + + New GameMode the local peer will assume after the Host Migration + + + + + Represents a Server or Client Simulation. IMPORTANT: You can only use a NetworkRunner once. Once that NetworkRunner + disconnects from a game session or fails to connect it should be destroyed, and a new Network Runner instance should + be created to start any new game sessions. For more information see the + Online Manual. + + + + + if this instance is a resume (host migration) + + + + + Compute and send a Host Migration Snapshot to the Photon Cloud + + Task with the result of the operation. True if it was successful, false otherwise. + + + + Iterate over the old NetworkObjects from the Resume Snapshot + + Iterable list of + + + + Iterate over the Scene NetworkObjects from the Resume Snapshot while giving the reference of the old + Snapshot data associated with that particular Scene Object + + Iterable list of Scene and Scene Object Header + + + + Temporary Host Migration Snapshot data buffer + + + + + Temporary Host Migration Snapshot Data buffer size + + + + + Gets a temporary representation of a from the old Server Snapshot + + + + + Setup Host Migration information + + Host Migration info + + + + Start the Host Migration process + + Base Snapshot to be used when restarting the Host + + + + Signal the Host Migration process has started + + + + + Try to send a Host Snapshot to Plugin + + + + + Get the Snapshot Host Migration Data + + True if the snapshot was properly captured + + + + Enumeration of Fusion.Runtime.dll options. + + + + + Use the Debug version of the Fusion.Runntime.dll. + + + + + Use the Debug version of the Fusion.Runntime.dll. + + + + + Initialization stages of Fusion + + + + + Runner is about to start + + + + + Runner is running + + + + + Runner is shutdown + + + + + Get Fusion.Runtime.dll build type. + + + + + Delegate type for on before spawned callback + + + + + Delegate type for object callback + + + + + Delegate type for event. + + + + + Event for object acquired + + + + + Event raised when [Networked] property of or has a valid underlying , but + the simulation is unable to find an instance, e.g. due to AoI. In such case, a property will return null. + + + + + Stores the Shutdown parameters used when requesting a Shutdown from inside a Runner Callback + + + + + Is the runner updating the simulation. + + + + + Completion Source for the startup Photon Cloud Operations + + + + + Check if the Runner was properly Initialized + + + + + Signal if the OnGameStarted was invoked already + + + + + A queue of simulation behaviours that were attached to the runner by AddGlobal and implement the ISpawned interface. + But the spawned call is delayed to when the RuntimeConfig is ready. + + + + + Indicates if this is collecting . + + + + + The current topology used + + + + + Returns the for this . + + + + + Returns the flags for The type of network peer the associated represents. + + + + + Returns the current stage of this . + + + + + Returns the fixed tick time interval. Derived from the . + + + + + The time the current State represents (the most recent FixedUpdateNetwork simulation). + Use as an equivalent to Unity's Time.fixedTime. + Time is relative to Tick 0 (which represents Time 0f). + + + + + The current time (current State.Time + Simulation.DeltaTime) for predicted objects (objects in the local time frame). + Use as an equivalent to Unity's Time.time. + Time is relative to Tick 0 (which represents Time 0f). + + + + + The current time (current State.Time + Simulation.DeltaTime) for non-predicted objects (objects in a remote time frame). + Use as an equivalent to Unity's Time.time. + Time is relative to Tick 0 (which represents Time 0f). + + + + + Returns if this is valid and running. + + + + + If the runner is shutdown + + + + + Has the shutdown been deferred. + + + + + Are we dealing with a regular, planned shutdown. + + + + + Get the local time alpha value + + + + + Get the latest confirmed tick of the server we are aware of + + + + + If the runner is pending to start + + + + + Returns if this represents a Client connection. + + + + + Returns if this Client is currently connected to a Remote Server + + + + + Returns if this represents a Server connection. + + + + + Returns true if this runner represents a Client or Host. Dedicated servers have no local player and will return false. + + + + + Returns true if this runner was started as single player (Started as with = 1). + + + + + If this is the last tick that is being executed this update + + + + + If this is the first tick that executes this update or re-simulation + + + + + If this is not a re-simulation but a new forward tick + + + + + If we are currently executing a client side prediction re-simulation. + + + + + + + + + + The current state of the runner, if it's Starting, Running, Shutdown + + + + + Returns a for the local simulation. For a dedicated server will equal false. + PlayerRefs are assigned in order from 0 to MaxPlayers-1 and are re-used as players join and leave. + The only caveat is that the server player (if one exists), always gets the last index no matter how many clients are connected. + + + + + The tick associated with the current state of networked objects, or the current simulation tick being processed (when evaluated during FixedUpdateNetwork). + + + + + Returns the reference. + + + + + Reference to the . + + + + + Returns how many ticks we executed last update. + + + + + Returns the collection of objects for this NetworkRunner's . + + + + + Returns the collection of objects for this NetworkRunner's . + Unlike it will not contain players that have just left the room and are pending + deletion. + + + + + Is the Fusion player in the room and not pending deletion? + + + + + Returns the instance. + + + + + + + + Bound Address of the internal socket used for direct connection in Server/Host Mode. + On the Host/Server this address contains the address/port that was passed into StartGame. + When forwarding ports on the router/ firewall this port should be used as the internal port for the forwarding. The external port can either be the same value or any port. + Note: This is not the public address of the server or the address / port that was discovered via NAT Punchthrough. + + + + + Returns the instance. + + + + + Global CancellationTokenSource for this NetworkRunner Instance + Used to control all async operations along the life cycle of the Runner + Should be used in conjunction with + + + + + Global copy of the Cancellation Token for this NetworkRunner Instance + + + + + Returns the global instance of a lag compensation buffer . + + + + + Remote prefabs are instantiated after an entire packet is read; this is to ensure + references are resolved correctly immediately. This is a buffer for all the instantiated instances + that await their spawned callbacks. + + + + + Queue of NetworkIds that have been received and wait for NOs to be created. + + + + + Queue of nested NetworkIds that have been received and wait for NOs to be created. + + + + + Queue of NetworkIds that have been received and wait for NOs to be destroyed. + + + + + Disconnects a player from the server. + + The player to disconnect. Must be a valid PlayerRef. + Optional byte array. If provided, it will be used as the disconnection token. + + This method can only be called from the server. If called from a client, an error message will be logged. + + + + + Disconnect a client based on its NetAddress + + NetAddress of the client + + + + Connect this as a client to a Server. + + + + + Initiates a . + + + + + Called by the simulation when the RuntimeConfig is first acknowledged. + + + + + OnGameStarted Callback + + + + + Invoke OnGameStarted callback + + + + + Starts using the supplied arguments. + + + + + Pauses the game in single player + + + + + Continues a paused game in single player + + + + + Sets the paused state in a single player + + + + + Get the number of interfaces of the desired type that are registered on the behaviour updater. + + The interface type + The number of interfaces + + + + Get the interface list head. + + The interface type + The desired index on the list of behaviourList + The head reference + A disposable to be used on an `using` scope + + + + Get the previous behaviour + + The reference behaviour to get the previous one + Gives the previous behaviour + + + + Get the next behaviour + + The reference behaviour to get the next one + Gives the next behaviour + + + + Starts an operation to retrieves the list of available regions. + + Optional App ID. If not provided, the method will use the global App ID from the PhotonAppSettings. + Optional CancellationToken parameter that can be used to cancel the operation. + Returns a list with information about all the available regions. + + + + Gets Player's Actor Number (ID). + + If used in Shared Mode, every client can get this information. + If used in Client Server Mode, only the Server is able to get this information. + + PlayerRef to get the Actor Number (ID) + Actor Number associated with the PlayerRef, otherwise null. + + + + Gets Player's UserID. + + If used in Shared Mode, every client can get this information. + If used in Client Server Mode, only the Server is able to get this information. + + PlayerRef to get the UserID. If no PlayerRef is passed, the UserID of the local client is returned instead. + UserID if valid player found, otherwise null. + + + + Sets the network object associated with this player + + PlayerRef to set the network object + Network object to associate with the player + + + + Gets the network object associated with a specific player + + PlayerRef to get the network object + Network object if one is associated with the player + + + + Try to gets the associated with a specific player + + PlayerRef to get the network object + Network object if one is associated with the player + Signals if it was able to get a for the player provided + + + + Get a list with all behaviours of the desired type that are registered on the . + + type + The result list + + + + Retrieves a list of all network objects in the simulation. + + A list of NetworkObject instances. + + + + Populate a list with all network objects in the simulation. + + The list to which the network objects will be added. + + + + Add on the list all behaviours of the desired type that are registered on the . + Note: The list will not be cleared before adding the results. + + The list to add the behaviours + type + + + + Returns the player round trip time (ping) in seconds + + The player you want the round trip time for + + + + Sends RPC message. Not meant to be used directly, ILWeaver calls this. + + SimulationMessage to send + + + + Sends RPC message. Not meant to be used directly, ILWeaver calls this. + + SimulationMessage to send + RpcSendResult + + + + Checks if the provided player is valid in the current simulation. + + The player reference to be validated. + Returns true if the player is valid, false otherwise. + + + + Returns a copy of the Connection Token used by a Player when connecting to this Server. + Only available on Server. It will return null if running on a Client or the Connection token is missing + + PlayerRef to check for a Connection Token + Copy of the Connection Token + + + + Return the with a Remote . + Valid only when invoked from a Server () + + Remote Player to check the + with a + + + + Returns an array of all instances registered with this . + + The type of the behaviours to be returned. + An array of instances of the specified type. + + + + Register an instance for callbacks from this . + + Callbacks to register + + + + Unregister an instance for callbacks from this . + + Callbacks to unregister + + + + Gets a memory snapshot of the simulation. + + The target allocator for the memory statistics. Must be a valid value from the MemoryStatisticsSnapshot.TargetAllocator enum. + A reference to the MemoryStatisticsSnapshot struct that will store the memory snapshot. + + + + This method is meant to be called by . + + + + + Promote a player to be the new master client. Only the master client is able to call this method + + The player to be promoted to master client + + + + This method is meant to be called by . + + + + + Search on the runner object for instances of and register them for callbacks on this . + Disabled components are not registered. + + + + + Sends a reliable data buffer to a target player. + + The player who should receive the buffer. + The key associated with the reliable data. + The data buffer to be sent. + + + + Sends a reliable data buffer to the server. + + The key associated with the reliable data. + The data buffer to be sent. + + If the runner is a client, the data is sent to the server (connection index 0) with the player's index. + If the runner is a server, the data is sent via the simulation callbacks. + + + + + Flags this player as always interested in this object. Means it does not have to be in a players area of interest to be replicated. Only the NetworkObject State Authority can set interest. + + The player + The object + If he's always interested, or not. + + + + Returns the data from player, converted to the indicated . + + + + + Returns the unconverted unsafe for the indicated player. + + + + + Requests state authority for a specified on shared mode. + + The NetworkId for which state authority is being requested. + + + + Releases state authority for a given on shared mode. + + The NetworkId for which state authority needs to be released. + + + + Outputs the from player, translated to the indicated . + + + + + Get the instance for this from a . + + NetworkID to look forward + null if object cannot be found. + + + + Get the instance for this from a . + + Object NetworkID to look forward + NetworkObject reference, if found + True if object was found. + + + + Get the instance for this from a . + + NetworkBehaviourId to look forward + NetworkBehaviour reference, if found + True if object was found. + + + + Try to find a with the provided NetworkBehaviourId. + + The NetworkBehaviourId to search for + The behaviour found + A type + Returns true if the behaviour was found and it is alive. False otherwise + + + + Tries to return the first instance of T found on the root of a . + + The type of the component to search for + NetworkId of the to search for + Returns the found component. Null if the cannot be found, or if T cannot be found on the GameObject. + + + + Tries to return the behaviour . + + NetworkBehaviour to get the from + Returns the of the provided behaviour. Returns default if the behaviour is not alive or the that has this behaviour is not valid. + + + + Tries to return a for the provided. + + NetworkBehaviour to get the from + Returns a to the provided behaviour. Returns default if the behaviour is not alive or the that has this behaviour is not valid. + + + + Sets the simulation state for this object, if it takes part in the NetworkFixedUpdate, etc. + In shared mode a client cannot change the simulation state of s it does not have state authority over. + + the object to change state for + true if it should be simulated, false if otherwise + true if the state of the object changed, false otherwise + + + + Set the area of interest grid dimensions + + X dimension + Y dimension + Z dimension + Can't change grid size in shared mode + + + + Set the area of interest cell size + + Size of the cell + Can't change cell size in shared mode + + + + Retrieves a list of network object IDs that are in the area of interest for the specified player. Server only. + + The player for whom the area of interest is being queried. + A list of network object IDs in the area of interest for the player. + + + + Populates the provided list with data about the current Area of Interest (AOI) cells. + Each element in the list represents one AOI cell. + + The list to be populated with AOI cell data. Each tuple in the list contains the center of the AOI cell, its size, the count of players in the cell, and the count of objects in the cell. + + + + Tries to get the FusionStatisticsManager from the simulation. + + The FusionStatisticsManager returned by the method + True if the FusionStatisticsManager is successfully retrieved, otherwise false + + + + Tries to get the statistics snapshot for a specified behaviour type. + + The type of the behaviour for which to get the statistics snapshot. + When this method returns, contains the statistics snapshot for the specified behaviour type, if found; otherwise, the default value. + true if the statistics snapshot for the specified behaviour type is found; otherwise, false. + + + + Returns if the contains a reference to a in the current State. + + + + + Returns if the contains a with given in the current State. + + + + + Destroys a NetworkObject. + + The NetworkObject to be destroyed. + + This method checks if the local simulation has state authority over the NetworkObject. + If it does, it checks if the NetworkObject exists and has state authority. + If these conditions are met, it destroys the NetworkObject. + + Thrown when the NetworkObject does not belong to this runner. + + + + Ensures that a specific component exists on this gameobject. + + + + + Returns if a given is present in this gameobject. + + Returns true if the was found + + + + Removes a specific from this gameobject, if it exists. + + + + + Add and register a to this . + Note: It should NOT be a + + + + + Removes a specific from this gameobject, if it exists. + + + + + Unregister a instance from the callbacks. + Invalid if NetworkRunner has not been started and initialized. + + + + + Attaches a user-created network object to the network. + + The network object to attach. Must not be null and must have a valid NetworkTypeId. + Optional PlayerRef. If assigned, it will be the default input authority for this object. + Optional boolean. If true, the object will be allocated in memory and attached to the scene object. Default is true. + Optional boolean. If provided, it will override the master client object setting. Default is null. + Thrown when the provided network object is null. + Thrown when the provided network object has an invalid NetworkTypeId. + + + + Call this every FixedUpdateNetwork to add an area of interest for a player. + Should only be called from the Host/Server in Server client mode. + Should only be called for the local player in shared mode. + + + + + Clears the area of interest for a player. This can only be called from the server/host + + + + + Test if a player has Interest in a . + + Returns null if interest cannot be determined (clients without State Authority are not aware of other client's Object Interest) + + + + Controls if a specific network behaviours state is replicated to all players or not + + The behaviour to change replication status for + true = replicate, false = don't replicate + + + + Controls if a specific network behaviours state is replicated to a player or not + + The behaviour to change replication status for + The player to change replication status for + true = replicate, false = don't replicate + + + + Attach and assign to this the provided. + Used internally from the default implementation of to register scene objects. + + + + + Registers scene objects to the network. + + The scene reference. Must be valid. + Array of NetworkObject instances to be registered. Must not be null. + Optional NetworkSceneLoadId. Default value is used if not provided. + The number of objects registered. + Thrown when the provided scene is not valid. + Thrown when the provided objects array is null. + + + + Return the for a specific player. + + + + + + + + Get enumerator for the collection of all s. Allows + to enumerate alloc-free. + + + + + A list of all s. + + + + + Set the value for simulating physics scenes when using multi-peer. + NOTE: The physics scenes will only be simulated if AutoSimulate is enabled for Physics3D and SimulationMode is set to FixedUpdate for Physics2D in the project settings. + + The value to set. True to simulate physics scenes, false otherwise. + + + + Try to get the physics info. + + Network physics info + True if the physics info exists, otherwise false. + + + + Try to set the physics info. + + Network physics info + True if the physics info was set, otherwise false. + Thrown if the runner does not have the scene authority. + + + + The initial scene info that was used to start the runner. It is consumed before the first tick by the scene authority. + + The way this works is a bit complex. The order of things is as follows: + first update -> BeforeFirstTick -> OnServerStart / OnClientStart (master) -> ConsumeInitialSceneInfo + + Only then the initial scene info is consumed and is invoked. Any load/unload calls + before that get queued and executed after the initial scene info is consumed. + + + + + What caused the scene change. This is either set in or when object is updated remotely. + + + + + To pretend the scene info is available before the first tick (important for shared mode), we store the copy of initial scene info + here and modify it/return it instead of the state. After scene info has been consumed, this is no longer needed. + + Last used scene info. If is set this is compared + to the current scene info and scenes are loaded/unloaded accordingly. + + + + + Completed and nullified once the initial / first remote scene info is consumed. Loads/Unloads that happen before the first tick + depend on the result of this task. + + + + + Is this runner responsible for scene management. + + + + + Signals if the instance assigned to this is busy + with any scene loading operation. + + + + + Tries to get the of this . + + The result + Returns true if it was able to get the scene info + + + + + + + + + + Moves a GameObject to a specific scene + + Game Object to move + Scene to move the object to + True if the object was moved, false otherwise + + + + Moves a GameObject to the same scene as another GameObject + + Game Object to move + Game Object to move to the same scene as + True if the object was moved, false otherwise + + + + Loads a scene + + Name of the scene to load + Parameters to use when loading the scene + Should the scene be set as active when loaded + Scene Load operation + + + + Loads a scene + + Name of the scene to load + Scene load mode + Scene physics mode + Should the scene be set as active when loaded + Scene Load operation + + + + Loads a scene + + Reference to the scene to load + Scene load mode + Scene physics mode + Should the scene be set as active when loaded + Scene Load operation + + + + Unloads a scene + + Name of the scene to unload + Scene Unload operation + + + + + + + + + + Invoke on all implementations + + + + + Invoke on all implementations + + + + + The main scene of the or default if not running. + + + + + Get the a GameObject instance belongs to. + + GameObject to check for a + reference, or null if not found + + + + Get the from a specific Scene + + Scene to check for a + reference, or null if not found + + + + Get the 3D Physics scene being used by this Runner. + + + + + Get the 2D Physics scene being used by this Runner. + + + + + Instantiates an object in the scene of this runner + + + + + Instantiates an object in the scene of this runner + + + + + Instantiates an object in the scene of this runner + + + + + Instantiates an object in the scene of this runner + + + + + Ensures the scene of this runner is active and returns the previous active scene + + Previous active scene + True if the scene was changed, false otherwise + + + + Moves an object to the scene of this runner + + + Component of object to move + + + + Moves an object to the scene of this runner + + Object to move + Target scene to move the object to + + + + Mark an object as `DontDestroyOnLoad`. + + Object to mark + + + + Signal if the Network Runner can spawn a + + + + + Attempts to network instantiate a using a + Note: position and rotation values are only used locally for the instantiation of the object, and are not inherently networked. + Use (or any derived classes) to replicate the initial transform state. + + reference, or null if it was not able to spawn the object + + + + Attempts to network instantiate a using a Component type that is part of a + + Must be a Type derived from + used to spawn the + T reference, or null if it was not able to spawn the object + Spawn Position + Spawn Rotation + Player Input Authority + reference + Spawn flags + + + + Attempts to network instantiate a using a GameObject. The supplied GameObject must have a component. + + A GameObject with a + Spawn Position + Spawn Rotation + Player Input Authority + reference + Spawn flags + reference, or null if it was not able to spawn the object + + + + Attempts to network instantiate a using a prefab. + Note: position and rotation values are only used locally for the instantiation of the object, and are not inherently networked. + Use (or any custom class derived from NetworkTRSP) to replicate the initial transform state. + + Prefab used to spawn the + Spawn Position + Spawn Rotation + Player Input Authority + reference + Spawn flags + reference, or null if it was not able to spawn the object + + + + Attempts to network instantiate a using a . + Note: position and rotation values are only used locally for the instantiation of the object, and are not inherently networked. + Use (or any custom class derived from NetworkTRSP) to replicate the initial transform state. + + Prefab Ref used to spawn the + Spawn Position + Spawn Rotation + Player Input Authority + reference + Spawn flags + reference, or null if it was not able to spawn the object + + + + Attempts to network instantiate a using a + Note: position and rotation values are only used locally for the instantiation of the object, and are not inherently networked. + Use (or any custom class derived from NetworkTRSP) to replicate the initial transform state. + + Object Guid used to spawn the + Spawn Position + Spawn Rotation + Player Input Authority + reference + Spawn flags + reference, or null if it was not able to spawn the object + + + + Attempts to network instantiate a using a + Note: position and rotation values are only used locally for the instantiation of the object, and are not inherently networked. + Use (or any custom class derived from NetworkTRSP) to replicate the initial transform state. + + Prefab ID used to spawn the + Spawn Position + Spawn Rotation + Player Input Authority + reference + Spawn flags + reference, or null if it was not able to spawn the object + + + + Attempts to network instantiate a using a Component type that is part of a + + Must be a Type derived from + used to spawn the + T reference, or null if it was not able to spawn the object + Spawned reference + Spawn Position + Spawn Rotation + Player Input Authority + reference + Spawn flags + + + + Attempts to network instantiate a using a GameObject. The supplied GameObject must have a component. + + A GameObject with a + Spawn Position + Spawn Rotation + Player Input Authority + reference + Spawn flags + Spawned reference + reference, or null if it was not able to spawn the object + + + + Attempts to network instantiate a using a prefab. + Note: position and rotation values are only used locally for the instantiation of the object, and are not inherently networked. + Use (or any custom class derived from NetworkTRSP) to replicate the initial transform state. + + Prefab used to spawn the + Spawn Position + Spawn Rotation + Player Input Authority + reference + Spawn flags + Spawned reference + reference, or null if it was not able to spawn the object + + + + Attempts to network instantiate a using a . + Note: position and rotation values are only used locally for the instantiation of the object, and are not inherently networked. + Use (or any custom class derived from NetworkTRSP) to replicate the initial transform state. + + Prefab Ref used to spawn the + Spawn Position + Spawn Rotation + Player Input Authority + reference + Spawn flags + Spawned reference + reference, or null if it was not able to spawn the object + + + + Attempts to network instantiate a using a + Note: position and rotation values are only used locally for the instantiation of the object, and are not inherently networked. + Use (or any custom class derived from NetworkTRSP) to replicate the initial transform state. + + Object Guid used to spawn the + Spawn Position + Spawn Rotation + Player Input Authority + reference + Spawn flags + Spawned reference + reference, or null if it was not able to spawn the object + + + + Attempts to network instantiate a using a + Note: position and rotation values are only used locally for the instantiation of the object, and are not inherently networked. + Use (or any custom class derived from NetworkTRSP) to replicate the initial transform state. + + Prefab ID used to spawn the + Spawn Position + Spawn Rotation + Player Input Authority + reference + Spawn flags + Spawned reference + reference, or null if it was not able to spawn the object + + + + Attempts to network instantiate a using a Component type that is part of a + + Must be a Type derived from + used to spawn the + T reference, or null if it was not able to spawn the object + A callback to fire once the spawn is done. + Spawn Position + Spawn Rotation + Player Input Authority + reference + Spawn flags + + + + Attempts to network instantiate a using a GameObject. The supplied GameObject must have a component. + + A GameObject with a + Spawn Position + Spawn Rotation + Player Input Authority + reference + Spawn flags, + A callback to fire once the spawn is done. + + + + Attempts to network instantiate a using a prefab. + Note: position and rotation values are only used locally for the instantiation of the object, and are not inherently networked. + Use (or any custom class derived from NetworkTRSP) to replicate the initial transform state. + + Prefab used to spawn the + Spawn Position + Spawn Rotation + Player Input Authority + reference + Spawn flags, + A callback to fire once the spawn is done. + + + + Attempts to network instantiate a using a . + Note: position and rotation values are only used locally for the instantiation of the object, and are not inherently networked. + Use (or any custom class derived from NetworkTRSP) to replicate the initial transform state. + + Prefab Ref used to spawn the + Spawn Position + Spawn Rotation + Player Input Authority + reference + Spawn flags, + A callback to fire once the spawn is done. + + + + Attempts to network instantiate a using a + Note: position and rotation values are only used locally for the instantiation of the object, and are not inherently networked. + Use (or any custom class derived from NetworkTRSP) to replicate the initial transform state. + + Object Guid used to spawn the + Spawn Position + Spawn Rotation + Player Input Authority + reference + Spawn flags, + A callback to fire once the spawn is done. + + + + Attempts to network instantiate a using a + Note: position and rotation values are only used locally for the instantiation of the object, and are not inherently networked. + Use (or any custom class derived from NetworkTRSP) to replicate the initial transform state. + + Prefab ID used to spawn the + Spawn Position + Spawn Rotation + Player Input Authority + reference + Spawn flags, + A callback to fire once the spawn is done. + + + + If this callback is implemented, the default behavior of disconnecting + when the cloud connection is lost in server/client or host/client mode + is disabled. This callback will be invoked instead, leaving the decision + up to the user. It is called both on the server/host and on the client. + + NetworkRunner instance + Shutdown reason for the connection loss + Flag to signal if the peer is attempting to reconnect to the Photon Cloud + + + + + + + Signal if the Local Peer is connected to Photon Cloud and is able to Create/Join Room but also receive Lobby Updates + + + + + + + + Photon Client UserID + + Returns null if Peer is not connected to Photon Cloud + + + + used by this Runner to Authenticate the local peer. + + + + + Current Game Mode active on the Fusion Simulation + + + + + Stores information about the current running session + + + + + Signal if the local peer is already inside a Lobby + + + + + Signal if the runner was already initialized. Used to avoid recycling of NetworkRunners. + + + + + Check the current Connection Type with the Remote Server + + + + + Exposes the current NAT Type from the local Peer + + + + + Signal if the Local Peer is in a Room and is the Room Master Client + + + + + + + + Completion Source for the startup Photon Cloud Operations + + + + + Responsible to manage the Photon Cloud related Services + + + + + The cached region summary for the local player. Initially empty but populated after the first connection. + + + + + Join the Peer to a specific Lobby, either a prebuild or a custom one. + + More about matchmaking: + https://doc.photonengine.com/en-us/fusion/current/manual/matchmaking + + Lobby Type to Join + Lobby ID + Authentication Values used to authenticate this peer + Custom Photon Application Settings + Signal if the LoadBalancingClient should use the Default or Alternative Ports + Optional Cancellation Token + Signal if the cached regions ping should be used to speed up connection + Async Task to Join a Session Lobby. Can be used to wait for the process to be finished. + + + + Starts the local Fusion Runner and takes care of all major setup necessary + + More about matchmaking: + https://doc.photonengine.com/en-us/fusion/current/manual/matchmaking + + Custom arguments used to setup the Fusion Simulation + Task that can be awaited to chain actions + + + + Connect the local peer to Photon Cloud using an async process. + + Authentication Values used to authenticate this peer + Custom Photon Application Settings + External Communicator that will be reused on restart + External CancellationToken + Signal if the LoadBalancingClient should use the Default or Alternative Ports + Signal if the cached regions ping should be used to speed up connection + Async Task of the connect to Photon Cloud process. Can be used to wait for the process to be finished. + + + + Disconnect the Peer from Photon Cloud + + If the Cloud Services were not initialized, it just returns immediately + + Async Task of the disconnect from Photon Cloud process. Can be used to wait for the process to be finished. + + + + Start Fusion in Single Player Mode + + Initialization Arguments + A running Task of the initialization process + + + + Start Fusion in one of the Cloud Game Modes + + Initialization Arguments + A running Task of the initialization process + + + + Shutdown the Fusion Runner based on a arbitrary Exception + + Exception used as base for the Shutdown procedure + Awaitable Task of the Shutdown procedure + + + + Signal an update on the list of + + New Session Info + + + + Signal an update on the list of + + Custom Authentication Response + + + + Interface which defines the handlers for Updates. An implementation + is responsible for calling and + periodically. + + An instance of this interface can be passed to + as the . By default (if == null) + Fusion will use , which invokes before + script's Update and before LateUpdate. + + + + + Called when the is started. + + The instance. + + + + Called when the is stopped. + + The instance. + + + + Network Runner Callbacks Delegates + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Companion component for . Exposes as UnityEvents, + which can be wired up to other components in the inspector. + + + + + + UnityEvent for NetworkInput + + + + + UnityEvent for NetworkInput with PlayerRef + + + + + UnityEvent for ConnectRequest + + + + + UnityEvent for ConnectFailed + + + + + UnityEvent for DisconnectFromServer + + + + + UnityEvent for Shutdown + + + + + UnityEvent for PlayerRef + + + + + UnityEvent for NetworkRunner + + + + + UnityEvent for SimulationMessage + + + + + UnityEvent for SessionInfo List + + + + + UnityEvent for Custom Authentication + + + + + UnityEvent for HostMigration + + + + + UnityEvent for Reliable Data + + + + + UnityEvent for Reliable Data Progress + + + + + UnityEvent for NetworkObject + + + + + UnityEvent for NetworkObject with PlayerRef + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The core Fusion config file that is shared with all peers at startup. + + + + + Options for running one or multiple peers in one Unity instance. + Multiple is useful for testing multiple players/clients inside of the Unity editor without needing to build executables. + Each peer is assigned its own independent physics scene and instance. + + + + + This is the normal use case, where every build and the editor run a single server, host or client peer. + + + + + This is the optional use case, which allows running multiple peers in the Unity editor, or in a build. + + + + + Eventual Consistency state replication options. + Scheduling enables automatic prioritization of objects when culling occurs + (when Object's are not replicated due to exceeding per tick data limits, they increase in priority on the following Tick). + Interest Management enables NetworkObject Area Of Interest and Explicit Interest features. + + + + + No special replication handling. This setting is ideal if your project never exceeds per tick data limits during gameplay. + + + + + When changed Network Objects are not replicated by the server to a client due to culling (data per tick limit was reached) + the server increases the priority of that Network Object for the next outgoing Tick update to that client. + + + + + In addition to scheduling, Interest Management features are also enabled (Area Of Interest and Explicit Interest). + + + + + Default file name for the asset + + + + + Reference for the default . By default, loads a resource named "NetworkProjectConfig". This behaviour + can be changed with an attribute . + + + + + Unloads , if already loaded. If loading has faulted, resets the state + and next call to the accessor will attempt to load the config again. + + + + + Current Type ID + + + + + Current version + + + + + Current version + + + + + Current Type ID + + + + + Setting for whether multiple peers can run per Unity instance (typically to allow easy testing of multiple peers inside of the editor). + + + + + Advanced lag compensation buffer settings. + + + + + This flag changes the behaviour of to return null (instead of throwing an exception) and ) to return + if Fusion was unable to load a prefab synchronously (e.g. because it was Addressable). Fusion will enqueue the spawn and attempt to perform it the next frame, until successful. + Useful for transition from Fusion 1.x. + + + + + Signal if the callbacks should be invoked in Batch Mode. + + + + + Signal if the of the should be included on the name of the GameObject + + + + + Inactive need special handling in case they get destroyed without ever being activated. This is achieved + with adding a nested GameObject called "NetworkObjectInactivityGuard" that tracks the OnDestroy message. + can be used to control whether these guards are visible in the hierarchy or not. + + + + + Allow the use of Client-Server modes in WebGL builds. This is not recommended, as it can lead to degraded performance. + Client-Server modes are not supported in WebGL builds by default, this setting allows its usage despite the limitations. + Read more about the limitations of WebGL builds in the Fusion documentation at https://doc.photonengine.com/fusion/v2/fusion-intro. + + + + + When this setting is enabled, each client will record its frame and packet timing information to a local file (one file per session). + Timing issues depend on many external factors, which makes them difficult to intentionally reproduce. But if an issue can be captured once, + its trace can be used to simulate and troubleshoot it more effectively. + + + + + Reference to the instance for this . + + + + + Reference to settings for this . + + + + + this can be used to override the time synchronization from code + + + + + Reference to settings for this . + + + + + Reference to settings for this + + + + + Reference to settings for this + + + + + Settings for simulating network conditions of latency and loss. + + + + + Heap Settings + + + + + Names of assemblies Fusion is going to weave. Not case sensitive. + + + + + Use Fusion.SerializableDictionary to store [Networked] dictionary properties initial value. If unchecked, + the weaver will emit System.Generic.Dictionary instead - a type that's not Unity-serializable, but custom + serializers (e.g. Odin) may support it. + + + + + If set, the weaver will add a check to all [Networked] properties on each + to verify if owing has been attached to. + + + + + If set, the weaver will check if is used in types that do not support it. This requires + all types to be scanned and can increase weaving duration. + + + + + If set, the weaver will check if properties getters and setters are empty. + + + + + Get the execution order for a given type. If the type is registered, returns null. + + Type to check for the execution order. + Execution order for the type, or null if not registered. + + + + Make a copy of the . + + + + + ToString() implementation. + + + + + Get the version information for the Fusion.Runntime.dll. + + + + + Serialize a into a JSON string. + + NetworkProjectConfig reference + JSON String + + + + De-serialize a from a JSON string (typically sent by the Room's Creator). + + JSON string of a serialized NetworkProjectConfig + NetworkProjectConfig reference de-serialized from JSON string + + + + Remove unnecessary data from serialized version of . + + + + + Manages and references the current instance of + + + + + The current instance. + + + + + The current instance. + + + + + Try to get the current instance. + + instance if it exists, otherwise null. + True if the instance exists, otherwise false. + + + + True if the instance exists, otherwise false. + + + + + Unload the current instance. + + + + + An auto-generated list containing source information (e.g. Resource path, address, static reference) for all the prefabs that can be spawned, i.e. the ones with + component and enabled. +
+ Additional prefabs can registered at runtime with . +
+
+ + + Options for the . + + + + + An auto-generated list containing meta information about all the s in the project, e.g. execution order. + + + + The type of the . + + + The execution order of the . + + + + An auto-generated list containing meta information about all the s in the project, e.g. execution order. + + + + + Unloads all prefabs. + + + + + Network Spawn Status + + + + Spawn is queued and will be spawned when the prefab is loaded. + + + Spawned successfully. + + + Failed to Load Prefab Synchronously. + + + Failed to create instance. + + + Failed to spawn because the client can't spawn. + + + Failed to spawn because the local player is not yet set. + + + + Network Spawn Flags + + + + + Object get spawned as DontDestroyOnLoad on all clients. + + + + + In shared mode, override the state authority to , ignoring "Is Master Client Object" inspector setting. + If used by a non-master client, object will be spawned with local player authority and an error message will be logged. + + + + + In shared mode, override the state authority to local player, ignoring "Is Master Client Object" inspector setting. + + + + + Spawn Operation + + + + Network Runner Reference + + + Get the spawned Network Object + + + Get the Spawn Operation Status + + + Returns true if the object has been spawned. + + + Returns true if the object is still queued for spawning. + + + Returns true if the object has failed to spawn. + + + Get this Spawn Operation + + + + Awaiter for + + + + + Awaiter Constructor + + Spawn Operation + + + Returns true if the Spawn Operation is completed + + + + Get the result of the Spawn Operation + + Spawned Network Object + Thrown if the Spawn Operation failed + + + + Awaiter OnCompleted Callback + + Continuation Action + Thrown if the Spawn Operation is not supported + + + + Network Object Spawn Delegate + + + + + Network Object Spawn Exception + + + + + Network Object Spawn Exception Constructor + + Network Spawn Status + Network Object Type Id + + + Network Object Type Id + + + Network Spawn Status + + + Exception Message + + + + Stores data types used on the interface + + + + + Data holder of a Connection Request from a remote client + + + + + Address of the remote client + + + + + Accepts the Request + + + + + Refuses the Request + + + + + Refuses the Request + + + + + Describes a list of Reason why the Fusion Runner was Shutdown + + + + + OK Reason means Fusion was Shutdown by request + + + + + Shutdown was caused by some internal error + + + + + Raised when the peer tries to Join a Room with a mismatching type between ClientServer Mode and Shared Mode. + + + + + Raised when the local peer started as a Server and tried to join a Room that already has a Server peer. + + + + + Raised when the Peer is disconnected or kicked by a Plugin Logic. + + + + + Raised when the Game the Peer is trying to Join is Closed + + + + + Raised when the Game the Peer is trying to Join does not exist + + + + + Raised when all CCU available for the Photon Application are in use + + + + + Raised when the peer is trying to connect to an unavailable or non-existent Region + + + + + Raised when a Session with the same name was already created + + + + + Raised when a peer is trying to join a Room with already the max capacity of players + + + + + Raised when the Authentication Values are invalid + + + + + Raised when the Custom Authentication has failed for some other reason + + + + + Raised when the Authentication Ticket has expired + + + + + Timeout on the Connection with the Photon Cloud + + + + + Raised when Fusion is already running and the StartGame is invoked again + + + + + Raised when any of the StartGame arguments does not meet the requirements + + + + + Signal this Runner is shutting down because of a Host Migration is about to happen + + + + + Connection with a remote server failed by timeout + + + + + Connection with a remote server failed because it was refused + + + + + The current operation has timed out + + + + + The current operation was canceled + + + + + Interface for callbacks. + Register a class/struct instance which implements this interface with . + + + + + Callback from a when a new has exit the Area of Interest + + NetworkRunner reference + NetworkObject reference + PlayerRef reference + + + + Callback from a when a new has entered the Area of Interest + + NetworkRunner reference + NetworkObject reference + PlayerRef reference + + + + Callback from a when a new player has joined. + + + + + Callback from a when a player has disconnected. + + + + + Called when the runner is shutdown + + The runner being shutdown + Describes the reason Fusion was Shutdown + + + + Callback when disconnects from a server or host. + + + + + Callback when receives a Connection Request from a Remote Client + + Local NetworkRunner + Request information + Request Token + + + + Callback when fails to connect to a server or host. + + + + + This callback is invoked when a manually dispatched simulation message is received from a remote peer + + The runner this message is for + The message pointer + + + + Callback is invoked when a Reliable Data Stream has been received + + NetworkRunner reference + Which PlayerRef the stream was sent from + ReliableKey reference that identifies the data stream + Data received + + + + Callback is invoked when a Reliable Data Stream is being received, reporting its progress + + NetworkRunner reference + Which PlayerRef the stream is being sent from + ReliableKey reference that identifies the data stream + Progress of the stream + + + + Callback from that polls for user inputs. + The that is supplied expects: + + input.Set(new CustomINetworkInput() { /* your values */ }); + + + + + + Callback from when an input is missing. + + NetworkRunner reference + PlayerRef reference which the input is missing from + NetworkInput reference which is missing + + + + Callback when successfully connects to a server or host. + + + + + This callback is invoked when a new List of Sessions is received from Photon Cloud + + The runner this object exists on + Updated list of Session + + + + Callback is invoked when the Authentication procedure returns a response from the Authentication Server + + The runner this object exists on + Custom Authentication Reply Values + + + + Callback is invoked when the Host Migration process has started + + The runner this object exists on + Migration Token that stores all necessary information to restart the Fusion Runner + + + + Callback is invoked when a Scene Load has finished + + NetworkRunner reference + + + + Callback is invoked when a Scene Load has started + + NetworkRunner reference + + + + Represents the initialization arguments for the NetworkRunner. + + + + + Gets or sets the network scene information. + + + + + Gets or sets the network address. + + + + + Gets or sets the public network address. + + + + + Gets or sets the player count. + + + + + Gets or sets the simulation mode. + + + + + Gets or sets the input word count. + + + + + Gets or sets the scene information word count. + + + + + Gets or sets the network project configuration. + + + + + Gets or sets the action to be called when the game starts. + + + + + Gets or sets the network object provider. + + + + + Gets or sets the network scene manager. + + + + + Gets or sets the network runner updater. + + + + + Gets or sets the network object initializer. + + + + + Gets or sets the custom callback interfaces. + + + + + Gets or sets the connection token. + + + + + Gets a value indicating whether the game is in single-player mode. + + + + + Gets or sets the resume network ID for host migration. + + + + + Gets or sets the resume tick for host migration. + + + + + Gets or sets the resume state for host migration. + + + + + Gets or sets the action to be called when host migration resumes. + + + + + Default implementation of that uses the Unity PlayerLoop. + + + + + Default settings for the NetworkRunner Update Loop. + + + + + Default settings for the NetworkRunner Render Loop. + + + + + Registers in the PlayerLoop. + + Update settings. + Render settings. + True if registered, false if already registered with the same settings. + + + + Unregisters from the PlayerLoop. + + True if unregistered, false if not registered. + + + + Invokes for all the runners + and stores them for upcoming . + + + + + Invokes for all the runners + that have been updated with the last . + + + + + Used to invoke in the PlayerLoop. + + + + + Used to invoke in the PlayerLoop. + + + + + Settings for the . + + + + + Reference to the PlayerLoopSystem to add the NetworkRunner to. + + + + + Add mode for the PlayerLoopSystem. + + + + + Checks if the settings are equal. + + Settings to check for equality. + True if equal, false otherwise. + + + + Checks if the settings are equal. + + Settings to check for equality. + True if equal, false otherwise. + + + + Gets the hash code of the settings. + + Hash code. + + + + Returns a string representation of the settings. + + String representation. + + + + Checks if the settings are equal. + + Settings to check for equality. + Settings to check for equality. + True if equal, false otherwise. + + + + Checks if the settings are not equal. + + Settings to check for equality. + Settings to check for equality. + True if not equal, false otherwise. + + + + Fusion Game Mode. + + Used to select how the local simulation will act. + + + + + Single Player Mode: it works very similar to Mode, but don't accept any connections. + + + + + Shared Mode: starts a Game Client, which will connect to a Game Server running in the Photon Cloud using the Fusion Plugin. + + + + + Server Mode: starts a Dedicated Game Server with no local player. + + + + + Host Mode: starts a Game Server and allows a local player. + + + + + Client Mode: starts a Game Client, which will connect to a peer in either or Modes. + + + + + Automatically start as Host or Client. The first peer to connect to a room will be started as a Host, all others will connect as clients. + + + + + Fusion Start Arguments, used to configure the simulation mode and other settings + + More about matchmaking: + https://doc.photonengine.com/en-us/fusion/current/manual/matchmaking + + + + + in which this peer will start + + + + + Photon Cloud Session Name used either to Create or Join a Session. + + Default: null (random session matching) + + + + + Used to generate a new Session Name when creating a Session. + + Default: null (a random session name is generated based on a GUID) + + + + + Peer Binding Address + + Default: + + + + + Custom Public Reflexive Address + + Default: null + + + + + Object pool to use + + Default: null + + + + + See . + + Default: null + + More about Scene Loading: + https://doc.photonengine.com/en-us/fusion/current/manual/scene-loading + + + + + See + + + + + See + + + + + Custom used to start the simulation + + Default: Global NetworkProjectConfig + + More about NetworkProjectConfig: + https://doc.photonengine.com/en-us/fusion/current/manual/network-project-config + + + + + Number of players allowed to connect to the session. Overrides the value set in the NetworkProjectConfig. The 1-255 range is purely a limit in the NetworkProjectConfig inspector the player count can be set to any positive int value via code. + + Default: DefaultPlayers from the Global NetworkProjectConfig + + + + + Scene that will be set as the starting Scene. + + Default: null (no scene set) + + + + + Callback that is invoked when the Fusion has fully started + + Default: null + + + + + Flag to disable the NAT Punchthrough implementation and connect only via Relay + + Default: false + + + + + User defined callback interfaces we will provide O(1) constant time lookup for + + Default: null + + + + + Connection token sent by client to server. Not used in shared mode. + + Default: null (empty connection token) + + + + + Custom Session Properties. + This dictionary can be used to either setup the initial Session Properties when creating a Session + but also to set the matchmaking filters when joining a Random Session. + + Default: null (empty custom properties) + + + + + Session should be created Open or Closed to accept joins + + Default: true + + + + + Session should be Visible or not in the Session Lobby list + + Default: true + + + + + Session Join Matchmaking Mode when joining a Session. + For more information, check + + Default: + + + + + Signal if the internal Realtime Client should use the Default Photon ports to connect to the Photon Cloud. + By default, Fusion uses ports: 27000, 27001 and 27002. + Set this to True to use ports: 5058, 5055 and 5056. + + + Default: false (uses ports 27000, 27001 and 27002) + + + + + Session Custom Lobby to be published in + + Default: null (default Lobby for each Session Type, LobbyClientServer or LobbyShared) + + + + + Specify a Custom STUN Server used to Resolve the peer Reflexive Addresses + It can be used to specify multiple STUN Servers separated by semicolon (;) + + Default: null (no custom STUN Server) + + + + + Custom Authentication Data + + Default: null (default authentication values) + + + + + Custom Photon Application Settings + + Default: null (Global PhotonAppSettings) + + + + + Enables the Session creation when starting a Client with an specific Session Name + + Default: false (clients *can not* create new Sessions) + + + + + Host Migration Token used when restarting the Fusion Simulation + + Default: null + + + + + Callback invoked when the new Host is migrating from the old Host state + + Default: null + + + + + Optional CancellationToken used to cancel the NetworkRunner start up process and shutdown + + Defaults: null + + + + + Enables the usage of the previous cached regions pings. This speeds up the region ping process and the runner startup process. + + Defaults: true + + + + + StartGameArgs ToString() + + + + + Describe an Exception that Occurred while starting the Fusion Simulation + + + + + ShutdownReason that caused this exception + + + + + StartGameException to String + + + + + Represents the result of starting the Fusion Simulation + + + + + Signal if the Start was OK + + + + + Start Game Shutdown Reason + + + + + Custom Error Message filled with data about the Shutdown. + Usually used to store custom data when the StartGame fails. + + + + + Optional Exception StackTrace + + + + + String representation of the StartGameResult + + + + + Convert arbitrary Exceptions into a StartGameException to public use + + Exception to be converted + Reference to a StartGameException holding a ShutdownReason + + + + Interface for a scene manager. + A scene manager is responsible for loading and unloading scenes + + + + + Callback for initialization + + + + + Callback for shutdown and clean up + + + + + Signals if the instance is busy with any scene loading operations + + + + + The main scene of the . Mostly used for Multipeer logic + + + + + Signals if the given scene is the main runner scene. Mostly used for Multipeer logic + + + + + Tries to get the physics scene 2D. + + Returns true if the operation was successfully + + + + Tries to get the physics scene 3D. + + Returns true if the operation was successfully + + + + Mark an object as `DontDestroyOnLoad`. + + + + + Move a to a desired scene. + + Return true if the operation was successfully + + + + Loads a given scene with the specified parameters. + + Returns a that can be waited + + + + Unloads a given scene. + + Returns a that can be waited + + + + Gets a for the scene that the given belongs to. + + + + + Gets a for the given scene name or path. + + + + + Implement this method and return true if you want to handle scene info changes manually. Return false if + the default scene info change handling should be done by the instead. + + Return true if a custom handling is provided, false otherwise to use the default one + + + + A wrapper for async scene operations. + + + + The scene reference of the operation + + + Signals if the operation is valid + + + Signals if the operation is done + + + + Attached error to the operation + + + + + Creates a from a + + Scene reference + Async operation reference + Returns a instance + Thrown if is null + + + + Creates a from a + + Scene reference + Coroutine reference + Returns a instance + Thrown if is null + + + + Creates a from a + + Scene reference + Task reference + Returns a instance + Thrown if is null + + + + Creates a from a + + Scene reference + Exception reference + Returns a instance + Thrown if is null + + + + Creates a completed + + Scene reference + Returns a instance + + + + Adds a callback to be called when the operation is completed + + The callback to be called + + + + Gets the awaiter for the operation + + + + + Awaiter for + + + + + Creates a new instance + + The operation to await + + + + Signals if the operation is completed + + + + + Gets the result of the operation + + + + + Adds a callback to be called when the operation is completed + + The callback to be called + + + + A unique identifier for a scene load operation. + + + + + The value of the id + + + + + Creates a new with the given value + + The value of the id + + + + Compares two for equality + + The other + Returns true if the two are equal + + + + Compares two for equality + + The other object to check + Returns true if the two are equal + + + + Returns the hash code of the + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Parameters for loading a scene + + + + + The unique id of the scene load operation + + + + + The to use when loading the scene + + + + + The to use when loading the scene + + + + + The to use when loading the scene + + + + + Signals if the scene should be active on load + + + + + Signals if the scene should be single loaded + + + + + Signals if the scene should have local 2D physics + + + + + Signals if the scene should have local 3D physics + + + + + Compares two for equality + + The other + Returns true if the two are equal + + + + Compares two for equality + + The other + Returns true if the two are equal + + + + Returns the hash code of the + + + + + Compares two for equality + + Left + Right + Returns true if the two are equal + + + + Compares two for inequality + + Left + Right + Returns true if the two are not equal + + + + Returns a string representation of the + + + + + Network Scene Info Default Flags + + + + The scene count mask + + + The counter mask + + + + Can store up to 8 active scenes and allows for duplicates. + Each write increases which can be used to generate unique scene objects ids + for when a scene is supposed to be reloaded. + + + + The size of the struct in words + + + The size of the struct in bytes + + + Max number of scenes that can be stored + + + The scenes list + + + The scenes load parameters list + + + + Gets the index of the given scene + + SceneRef to look for + Scene parameters to look for + Returns the index of the scene or -1 if not found + + + + + + + Total Scene Count + + + + + Version number + + + + + Adds a scene to the list + + Scene to add + Load scene mode + Local physics mode + Signals if the scene should be active on load + Returns the index of the scene or -1 if the scene could not be added + + + + Removes a scene from the list + + Scene to remove + Returns true if the scene was removed + + + + String representation of the + + + + + Compares two for equality + + The other + Returns true if the two are equal + + + + Compares two for equality + + The other + Returns true if the two are equal + + + + Get the hash code of the + + Hast code of the + + + + Implicit conversion to + + SceneRef to convert + Returns a instance + + + + What has contributed to the observed change in the scene info. + + + + + No change. + + + + + The initial local scene has changed. + + + + + The remove scene has changed. + + + + + A unique identifier for a scene object. + + + + + Identifies the scene in which the object is located. + + + + + Index of the object in the scene or any other form of unique identifier. + + + + + Use + + + + + Unique identifier of a specific scene load. Needs to be used when loading multiple scenes with the same or reloading a scene. + For example, increments its internal LoadId every time a new scene is added. + + + + + TODO + + + + + + + + Signal if the is valid. + + + + + String representation of the . + + + + + Check if two are equal. + + Another to check for equality + Returns true if the two are equal + + + + Check if two are equal. + + Another to check for equality + Returns true if the two are equal + + + + Get the hash code of the . + + + + + Indicates which point in time (or "timeframe") networked properties should be rendered in. + + + + + The default timeframe for owned and predicted objects. + + + + + The default timeframe for proxied objects. + + + + + Indicates how available snapshot data should be used to render networked properties + (in the chosen ). + + + + + The rendered value will come from interpolating the values at + and to the desired point in time. + + + + + The rendered value will come from the nearest available snapshot at or before the + point in time being rendered. + + + + + The rendered value will come from the nearest available snapshot ahead of the + point in time being rendered. + + + + + The rendered value will come from the latest snapshot. + + + + + Can be used to acquire interpolated data for different points in time. + + + + + Get the render data for the given . + + Network behaviour to get render data for. + Render data for the previous point in time. + Render data for the next point in time. + Interpolation alpha. + + + + Information used to observe an object in remote time. + + + + + Main simulation class + + + + + Clears the provided list and populates it with data about the current Area of Interest (AOI) cells. + Each tuple in the list represents one AOI cell, containing its center, size, player count, and object count. + + The list to be populated with AOI cell data. + + + + Retrieves a list of network object IDs that are in the area of interest for the specified player. + + The player for whom the area of interest is being queried. + A list of network object IDs in the area of interest for the player. + + + + Used by RunnerAOIGizmos component. Supplies data about current active AOI cells. + + + + + Area of Interest Definition + + + + + Size of each cell in the AOI grid. + + + + + Get the size of the AOI grid. + + The size of the AOI grid. + + + + Get the size of each cell in the AOI grid. + + The size of each cell in the AOI grid. + + + + Convert a sphere into a set of AOI cells. + + Sphere center + Sphere radius. Max allowed radius is + Resulting set of cells + + + + Convert a position into the respective cell coordinate. + + Position + Cell coordinate + + + + Converts a cell index into its corresponding cell coordinates. + + The index of the cell to be converted. + A tuple containing the x, y, and z coordinates of the cell. + + + + Convert a cell index into the respective cell center position. + + Cell index + Cell center position + + + + Convert a position into the respective cell index. + + Position + Cell index + + + + Convert a cell coordinate into the respective cell index. + + X coordinate + Y coordinate + Z coordinate + Cell index + + + + Clamp cell coordinates to the valid range. + + X coordinate + Y coordinate + Z coordinate + Clamped cell coordinates + + + + latest tick on server we are aware of + + + + + latest tick on server we are aware of + + + + + How large the ticks the current simulation takes are + + + + + The current tick rate of the simulation + + + + + The delta time of each tick as a double + + + + + The delta time of each tick as a float + + + + + The packet send rate + + + + + The packet send delta time + + + + + Gets the fixed tick time interval. Derived from the . + + + + + + + + + + + + + + + Use inside of FixedUpdateNetwork to determine if the tick currently being simulated has previously been simulated locally. + Resimulation occurs in client prediction when new states arrive from the StateAuthority. + Networked objects are set to the most current authority state tick, and simulations are repeated from that tick to the local current tick. + + + + + Use in conjunction with IsResimulation/IsForward inside of FixedUpdateNetwork to determine if the current tick being simulated + is the last tick of the resimulation or forward phase of the simulation loop. + + 'Resimulation' describes simulating a tick that has been previously been simulated.

+ 'Forward' describes simulating a tick that is being simulated for the first time locally.

+ 'Prediction' describes simulating ticks higher than the most current known StateAuthority snapshot tick. +
+
+
+ + + Use in conjunction with IsResimulation/IsForward inside of FixedUpdateNetwork to determine if the current tick being simulated + is the first tick of the resimulation or forward phase of the simulation loop. + + 'Resimulation' describes simulating a tick that has been previously been simulated.

+ 'Forward' describes simulating a tick that is being simulated for the first time locally.

+ 'Prediction' describes simulating ticks higher than the most current known StateAuthority snapshot tick. +
+
+
+ + + Use inside of FixedUpdateNetwork to determine if the tick currently being simulated has NOT previously been simulated locally. + + + + + True if the current stage of the simulation loop is Forward. False during resimulations. + + 'Resimulation' describes simulating a tick that has been previously been simulated.

+ 'Forward' describes simulating a tick that is being simulated for the first time locally.

+ 'Prediction' describes simulating ticks higher than the most current known StateAuthority snapshot tick. +
+
+
+ + + The tick associated with the current state of networked objects, or the current simulation tick being processed (when evaluated during FixedUpdateNetwork). + + + + + The previous tick + + + + + The current simulation time in seconds + + + + + The current input collection size + + + + + Indicates if a Server/Client or Shared Mode (relay server) topology is being used. + + + + + Gets the flags for The type of network peer this simulation represents. + + + + + Gets the current value. + + + + + The file used by this . + + + + + The file used by this . + + + + + Remote Interpolation Alpha + + + + + Remote previous Tick + + + + + Remote Tick + + + + + If this peer is a client. True for client peers in Server/Client topologies, and true for all peers in Shared Mode. + + + + + If this peer is the server. True for the Server or Host peer in Server/Client topologies, and always false for all peers in Shared Mode (the relay is the server). + + + + + True for any peer that represents a human player. This is true for all peers except a dedicated server. + + + + + Indicates that this simulation is operating in Single Player mode, which is a Host that accepts no connections. + + + + + Only valid in Shared Mode. Indicates if this peer is flagged as the MasterClient, which means it is default StateAuthority + + + + + List of Active players in the Simulation + + + + + Signal if the Simulation is currently running + + + + + In shared mode, scene info is only reliable after the initial tick. Before that + initial scene info data needs to be used. + + + + + Bound Address of the internal socket + + + + + Current + + + + + Get LocalPlayer PlayerRef + + + + + Callback invoked After the Simulation Update + + + + + Callback invoked on the Connected + + Connection that was connected + + + + Callback invoked on the Disconnected + + Connection that was disconnected + Reason for the disconnection + + + + Callback invoked when the Network Receive is completed + + + + + Callback invoked when there is no simulation + + + + + Callback invoked before the Simulation Loop + + Total number of re-simulations + + + + Callback invoked before the First Tick + + + + + Add or remove specific player interest in a NetworkObject. + + + + + Try to get the Host Player + + Host Player + True if the Host Player was found, false otherwise + + + + Check if a NetworkObjectMeta is interested by a specific Player + + NetworkObjectMeta to check + Player to check + True if the Player is interested in the NetworkObjectMeta, false otherwise + + + + Check if a Player is the Input Authority over an NetworkObjectMeta + + NetworkObjectMeta to check + Player to check + True if the Player is the Input Authority over the NetworkObjectMeta, false otherwise + + + + Check if a Player is the Input Authority over an NetworkObjectMeta + + NetworkObjectMeta to check + Player to check + True if the Player is the Input Authority over the NetworkObjectMeta, false otherwise + + + + Check if a Player is the State Authority in relation to another Player + + State Source Player + Player to check + True if the Player is the State Authority, false otherwise + + + + Check if a Player is the State Authority over a NetworkObjectMeta + + NetworkObjectMeta to check + Player to check + True if the Player is the State Authority, false otherwise + + + + Check if the Local Player is the Input Authority + + Object's input authority + True if the Player is the Input Authority, false otherwise + + + + Check if the Local Player is the State Authority + + Object's input authority + True if the Player is the State Authority, false otherwise + + + + Get the State Authority PlayerRef for a NetworkObjectMeta + + Player to check + PlayerRef of the State Authority for the NetworkObject + + + + Return a copy if a Player's Connection Token + + Player to check for the Connection Token + Connection Token Copy + + + + Return the Address of a remote Player + + Player to check for the NetAddress + NetAddress of a specific player + + + + Return the UniqueId as a long from a specific Player + + Player to check for the UniqueId + UniqueId of a specific Player + + + + Get the Simulation Input for a specific Player + + Player to check for the Simulation Input + Simulation Input for a specific Player + + + + Signal if the Server has any Active Connection with any number of Clients. + + True, if at least one connection is active, false otherwise. + + + + Callback invoked before the Simulation Update + + + + + Callback invoked after the Simulation Update + + + + + Forwards the Simulation based on the Delta Time + + Delta Time used to forward the simulation + How many Ticks executed on this Update + + + + Set the rate at which reliable data is sent in hz. + Minimum value is 1hz, maximum value is the same as the + Increasing this value increases the throughput when sending reliable data such as with . + + + + Returns the number of objects in the simulation. + + + Returns a map of all objects in the simulation. + + + + Gets a bitmask of flags, representing the current local authority over this . + + + + + Enqueue a on the Out Message List for a specific + + Message to enqueue + Target Connection + + + + Disconnect a client based on it's NetAddress + + Client Adress to Disconnect + + + + Dispose Host Migration data + + + + + Process the serialized data from the bytebuffer into a organized Dictionary of NetIDs=NO Snapshots + + Data buffer to read the data from + Target dictionary that will be filled with NetworkObjects data + Allocator used to create new + + + + Result flags for the RPC message send operation. + + + + + Invalid result. + + + + + Client sent to the server, server will send to the target client. + + + + + Server sent to a specific client (a targeted message). + + + + + Server attempted to send to all the clients and at least one succeeded. + + + + + Target object not confirmed on the client. + + + + + Target object not in client's interest. Likely due to being outside of player's AOI region, or needs to be explicitly set as always interested. + + + + + Target client not connected (a targeted message). + + + + + Server attempted to send to all the clients, but none was connected. + + + + + Server attempted to send to all the clients, but the target object is not confirmed/not in Object Interest for all target clients. + + + + Mask for sent messages. + + + Mask for not sent messages. + + + Mask for broadcast messages. + + + Mask for culled messages. + + + + + + + + + Classic server and client model + + + + + Relay based shared world model + + + + + Project configuration settings specific to how the Simulation class behaves. + + + + + + + + + + Send delta compressed and redundant input, used for most games + + + + + Send redundant input, use for games with small input structs (like fps games) and high player count + + + + + Only send latest input state, useful for VR, etc. + + + + + + + + + + When a 's data changes, the server will send all properties + whose changes have not been acknowledged. + + + + This option consumes more bandwidth, but guarantees that each + has consistent state. + + + + + + + When a 's data changes, the server will only send the newly + changed properties. + + + + This option consumes less bandwidth, but a may have + inconsistent state at times (some properties up-to-date but not others). + + + + + + The time mode that the uses to calculate the delta time for the simulation update. + + + + + Use Time.UnscaledDeltaTime. Time is not affected by timescale and keeps running when the editor is paused. + + + + + Use Time.DeltaTime. Only for . Can be used to allow for timescale changes in gameplay or to pause the game and continue without interruption when stopping at a debug breakpoint. + + + + Input Data Word Count + + + Scheduling is the default. + + + Signal if scheduling is enabled + + + Signal if AOI is enabled + + + Signal if scheduling is running without AOI + + + + The way which input is transferred + + + + + How the server chooses to send updates to balance consistency and bandwidth consumption. + + + + + The time mode that the Runner uses to update the simulation. + - + + + + The default number of players allowed to join a game instance. Can also be changed in code when starting Fusion. + The 1-255 range is purely a limit in the inspector the player count can be set to any positive int value via code by changing the value of the + or by passing a player count override via . + + + + + The default tick rate to use. Can also be changed in code when starting Fusion. + + + + + The topology used + + + + + If, in host mode, we should allow host migration if the current host leaves. + + + + + The amount of object destroys Fusion sends per packet. + Increasing this value means Fusion prioritizes sending more destroys before regular state changes. + Setting this value to 255, can help with problems when unloading scenes with many runtime spawned objects (such as Spawned/Despawned being called on those objects multiple times). + + + + + If set to true, makes sure Fusion packets fit into UDP MTU on retries, thus avoiding fragmentation. This can help in poor network conditions, when + larger Fusion packets are rejected due to UDP packets arriving out of order or being lost, at a cost of more packets being sent overall, especially + when using large state objects. + + + + + If set to true, makes sure Fusion packets fit into UDP MTU on retries, thus avoiding fragmentation. This can help in poor network conditions, when + larger Fusion packets are rejected due to UDP packets arriving out of order or being lost, at a cost of more packets being sent overall, especially + when using large state objects. + + + + + + + + + + Enable Serializers + + + + + + + + + + Memory Heap Settings + + + + + Default size of each Heap Page + + + + + Default number of Heap Pages + + + + + Heap Global Size + + + + + Initializes and creates a new based on the Global Size + + + + + ToString + + + + + Main network configuration class. + + + + + Flag for allowed Reliable Data transfer modes. + + + + + Allow Client to Server. + + + + + Allow Client to Client using Server as Proxy. + + + + + Size in Kilobytes of the underlying socket send buffer. + + + + + Size in Kilobytes of the underlying socket receive buffer. + + + + + Max number of connection attempts that a Client will run when trying to connect to a remote Server. + + + + + Interval in seconds between each connection attempt from a Client. + + + + + Default assumed RTT in seconds for new connections (before actual RTT has been determined). The real RTT is calculated over time once the connection is established. + + + + + Max allowed time in seconds that the local peer can run without receiving any update from a remote peer. + If a client does not receive any update from the server within this period, it will disconnect itself. + If a server does not receive any update from a remote client within this period, it will disconnect that particular client. + + + + + Interval in seconds between PING messages sent to a remote connection, in order to keep that connection alive. + + Currently unused. + + + + Default delay between connection changes status to Shutdown (disconnected/invalid), and it actually being released (freeing all references to that particular connection). + + + + + Current mode. + + + + + Initializes and creates a copy of this . + + A copy of this . + + + + Convert this into a using the as reference. + + + + + Configuration for network conditions simulation (induced latency and loss). + + + + + If adverse network conditions are being simulated. + + + + + The pattern used to oscillate between and values. + + + + + The lowest packet delay value returned from the oscillator. + + + + + The highest packet delay value returned from the oscillator. + + + + + The period of the oscillator (the rate at which delay oscillates in seconds). + + + + + The oscillates between 0 and 1. Values below this threshold are reduced to zero, resulting in a value equal to . + + + + + After the delay value from the oscillator is determined, random 0 to this value of additional seconds be added to the packet latency. + + + + + The pattern used to oscillate between and values. + + + + + The lowest loss chance value the oscillator will produce. 0 = 0% chance of being lost. 1 = 100% chance of being lost. + + + + + The highest loss chance value the oscillator will produce. 0 = 0% chance of being lost. 1 = 100% chance of being lost. + + + + + The wave oscillates between 0 and 1. Values below this threshold are reduced to zero, resulting in a value equal to . + + + + + The period of the oscillator (the rate at which delay oscillates between and ). + + + + + After the oscillation loss chance is calculated, an additional random value of 0 to this (normalized) percentage of loss chance is added. + + + + Creates a copy of this . + + + + Creates a new based on the current configuration. + + A new based on the current configuration. + + + + Time Synchronization Configuration + + + + + How big of a window the client looks at to evaluate the latest network conditions. + Increasing this makes the client slower to adapt to changes. + + + + + The maximum percentage of inputs that should ever arrive late because of jitter. + Decreasing this increases the client's simulation offset. + + + + + The maximum percentage of snapshots that should ever arrive late because of jitter. + Decreasing this increases the client's interpolation delay. + + + + + The number of consecutive, additional chances each input should have to reach the server before it's needed. + Increasing this increases the client's simulation offset. + + + + + The number of consecutive snapshots a client should be able to miss without disrupting their interpolation. + Increasing this increases the client's interpolation delay. + + + + + + The maximum percentage the client's simulation can speed up or slow down + to maintain its target simulation offset. + + + + + + + The maximum percentage the client's interpolation can speed up or slow down + to maintain its target interpolation delay. + + + + + + Simulation Input + + + + + Buffer for s. + + + + Number of inputs in the buffer. + + + Whether the buffer is full. + + + + Creates a new . + + Network project configuration. + + + Clears the buffer. + + + + Copies the buffer to an array and sorts it. + + Array to copy to. + Number of elements copied. + + + + Whether the buffer contains an input for . + + Tick to check. + Whether the buffer contains an input for . + + + + Removes an input for from the buffer. + + Tick to remove. + Removed input. + Whether an input was removed. + + + + Gets the insert time for . + + Tick to get insert time for. + Insert time for . + + + + Gets the input for . + + Tick to get input for. + Input for . + + + + Retrieves the last used input header data. + + The last used input header data. + + + + Adds an input to the buffer. + + Input to add. + Insert time for . + Whether the input was added. + + + + Player that owns this input. + + + + + Header for this input. + + + + + Data for this input. + + + + + Simulation input sent count. + + + + + Clear a total of words from this input. + + Word count to clear. + + + + Copy words from to this input. + + Input to copy from. + Word count to copy. + + + + Simulation Input Header + + + + Word count of the header. + + + Size of the header. + + + Tick of the input. + + + Interpolation alpha of the input. + + + Interpolation from tick of the input. + + + Interpolation to tick of the input. + + + + Simulation Message + + + + SimulationMessage size in bytes. + + + Max user message size in bytes. + + + Flag for user messages. + + + Flag for remote messages. + + + Flag for static messages. + + + Flag for unreliable messages. + + + Flag for targeted messages to a player. + + + Flag for targeted messages to the server. + + + Flag for internal messages. + + + Flag for messages that are not tick aligned. + + + Flag for dummy messages. + + + Flag for user flags. + + + Flag for reserved flags. + + + Flag for reserved bits. + + + + Tick of this + + + + + Source Player of this + + + + + Capacity in Bits of this + + + + + Current offset in Bits + + + + + Reference Count + + + + + Flags + + + + + Target Player of this + + + + + Add a reference to this + + + + + Subtract a reference from this + + True if the reference count is now 0 + + + + Set the player target of this + + Target Player + + + + Set this as Static + + + + + Set this as Unreliable + + + + + Set this as Not Tick Aligned + + + + + Set this as Dummy + + + + + Get if a flag is set on this + + Flag to check + True if the flag is set + + + + Signal if this is Targeted + + True if this is Targeted + + + + Signal if this is Unreliable + + + + + Create a copy of a + + to allocate from + to copy + Copy of the + + + + Get the byte pointer content of a + + SimulationMessage to get the byte pointer of + Byte pointer of the + + + + Get the byte pointer content of a + + SimulationMessage to get the byte pointer of + Byte pointer of the + + + + Allocate a new + + Simulation to get the Memory from + Size in bytes of the new + Pointer to the new + + + + Checks if a message with given size can be allocated. + + + + Simulation Message ToString + + + Simulation Message ToString + + + + Simulation Message Pointer + + + + + Pointer to the message. + + + + + Flags for The type of network peer a simulation represents. + + + + + Simulation represents a server peer, with no local player. + + + + + Simulation represents a server peer, with a local player. + + + + + Simulation represents a client peer, with a local player. + + + + + Stores the runtime configuration of the simulation + + + + + Current tick rates and send rates for server and client + + + + + Current Simulation Mode + + + + + Current player count + + + + + Current master client (in shared mode) + + + + + Current master client (in shared mode) + + + + + Current master client (in shared mode) + + + + + Flags for which stage the simulation currently running. Forward is when a tick is being simulated for the first time. + Resimulate is when a tick is being simulated again with corrections. + + + + + Currently simulating a tick for the first time. + + + + + Currently simulating a previously simulated tick again, with state corrections. + + + + + Reduces errors at a constant rate using an on-off controller. + + + + + The clock used to send inputs to the server. Later than Local by input delay. Currently input delay is always 0. + + + + + Local (or "simulation") time is the clock used to drive FixedUpdateNetwork. It is the tick FixedNetworkUpdate + should simulate up to. + + + + + Remote (or "interpolation") time is the clock used to select pairs of snapshots for interpolating remote + (proxy) objects. + + + + + Reduces errors at a variable rate using a PID controller. + + + + + NetworkInput Struct + + + + + Number of Words for the + + + + + Data pointer of the + + + + + Signal if the is valid or not + + + + + Get the Type associated with this + + + + + Tries to export data as the indicated T struct. + + + + + Tries to import data from a struct. + + + + + Gets the content of this as another type + + + + + Sets the content of this to another type + + + + + Converts the Type of this to another type + + + + + + + + Checks if this is of a certain type + + + + + Network Physics + + + + + Word Count + + + + + Total Size + + + + + Time Scale + + + + + Base interface for all Fusion Network Structs + + + + + Utility methods for + + + + + Get the max word count from all registered types + + + + + Get Type Word Count if it is of type + + Type to check for word count + Number of words for the + + + + Get the Key associate with the argument Type + + Type to check for the key + Associated Type Key, or an exception if not found + + + + Get the Type based on its associate Key + + Key associated with a Type + Type associated with the Key, or null otherwise + + + + Utility methods for + + + + + Get Word Count + + type reference + Number of Words necessary for this specific + + + + + + + + + + + + + + + + + + + + + + + + Returns boxed static instance. + + + diff --git a/Assets/Photon/Fusion/Assemblies/Fusion.Runtime.xml.meta b/Assets/Photon/Fusion/Assemblies/Fusion.Runtime.xml.meta new file mode 100644 index 00000000..3da8c818 --- /dev/null +++ b/Assets/Photon/Fusion/Assemblies/Fusion.Runtime.xml.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: d461b7cc07b37504f94b4a901b69e494 +labels: +- FusionCodeDoc +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Assemblies/Fusion.Sockets.dll b/Assets/Photon/Fusion/Assemblies/Fusion.Sockets.dll new file mode 100644 index 00000000..622c04e6 Binary files /dev/null and b/Assets/Photon/Fusion/Assemblies/Fusion.Sockets.dll differ diff --git a/Assets/Photon/Fusion/Assemblies/Fusion.Sockets.dll.meta b/Assets/Photon/Fusion/Assemblies/Fusion.Sockets.dll.meta new file mode 100644 index 00000000..3aea0d00 --- /dev/null +++ b/Assets/Photon/Fusion/Assemblies/Fusion.Sockets.dll.meta @@ -0,0 +1,33 @@ +fileFormatVersion: 2 +guid: 850f5b8096bb4c64abfef35065cde6b1 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Any: + second: + enabled: 1 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + Windows Store Apps: WindowsStoreApps + second: + enabled: 0 + settings: + CPU: AnyCPU + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Assemblies/Fusion.Sockets.xml b/Assets/Photon/Fusion/Assemblies/Fusion.Sockets.xml new file mode 100644 index 00000000..8a55d2a9 --- /dev/null +++ b/Assets/Photon/Fusion/Assemblies/Fusion.Sockets.xml @@ -0,0 +1,4479 @@ + + + + Fusion.Sockets + + + + + Responsible for encrypting and decrypting data buffers + + + + + + + + + + + Encrypts the data in the provided buffer. + + The buffer containing the data to be encrypted. + The length of the data in the buffer. + The total capacity of the buffer. + Returns true if the encryption was successful, false otherwise. + Thrown when the encryption provider is not initialized. + Thrown when the original buffer cannot hold the encrypted data. + + + + + + + + + + + + + Build a new AES Implementation + + + + + Build a new HMACSHA256 Implementation + + + + + Clear and return the IN Encrypt Buffer + + + + + Clear and return the OUT Encrypt Buffer + + + + + Dispose of the DataEncryptor + + + + + Interface for classes that manage the encryption/decryption of byte arrays + + + + + Setup the encryption implementation with the right key + + + + + Generate the key used used by the encryption implementation + + Key used to setup the encryption implementation + + + + Encrypt data in place and update it's length. + + Data to encrypt + Length of the data to encrypt + Buffer total capacity + True if the encryption was completed, false otherwise + + + + Decrypt data in place and update it's length. + + Data to decrypt + Length of the data to decrypt + Buffer total capacity + True if the decryption was completed, false otherwise + + + + Compute the Buffer hash and append it to the buffer itself + + Data to compute the hash + Length of the data to hash + Buffer total capacity + True if the hash was properly computed, false otherwise + + + + Verify the buffer hash that was appended to the buffer + + Buffer to check the hash + Length of the data to hash + Buffer total capacity + True if the hash was properly verified, false otherwise + + + + The EncryptionManager class manages encryption keys for different handles. + + A type that implements the IDataEncryption interface. + A type that implements the IEquatable interface. + + This class is unsafe because it uses pointers in its methods. + + + + + A dictionary that maps handle IDs to their corresponding encryption objects. + + + + + Disposes all encryption objects in the _cyphers dictionary. + + + This method is called when the EncryptionManager object is being disposed. + + + + + Registers a new encryption key for a specific handle. + + The handle ID for which the encryption key is to be registered. + The encryption key to be registered. + + + + Deletes the encryption key for a specific handle. + + The handle ID for which the encryption key is to be deleted. + + + + Check if a handle has encryption setup + + Handle ID + True if the handle has encryption setup, false otherwise + + + + Wraps the provided buffer with encryption and computes a hash for it. + + The handle ID for which the packet is to be encrypted and hashed. + The data buffer array containing the packet to be encrypted and hashed. + The length of the buffer array. + The total capacity of the buffer array. + Returns true if both the encryption and hash computation were successful, false otherwise. + + + + Unwraps the provided buffer by verifying its hash and decrypting it. + + The handle ID for which the packet is to be verified and decrypted. + The data buffer array containing the packet to be verified and decrypted. + The length of the buffer array. + The total capacity of the buffer array. + Returns true if both the hash verification and decryption were successful, false otherwise. + + + + Generates a new encryption key. + + A byte array representing the generated encryption key. + + + + Computes the hash for the data in the buffer for a specific handle. + + The handle for which to compute the hash. + The buffer containing the data to hash. + The length of the data in the buffer. + The total capacity of the buffer. + True if the hash was computed successfully, false otherwise. + + + + Verifies the hash for the data in the buffer for a specific handle. + + The handle for which to verify the hash. + The buffer containing the data to verify the hash. + The length of the data in the buffer. + The total capacity of the buffer. + True if the hash was verified successfully, false otherwise. + + + + Encrypts a packet for a specific handle. + + The handle ID for which the packet is to be encrypted. + The data buffer array containing the packet to be encrypted. + The length of the buffer array. + The total capacity of the buffer array. + Returns true if the encryption was successful, false otherwise. + + + + Decrypts a packet from a specific handle. + + The handle ID from which the packet is to be decrypted. + The data buffer array containing the packet to be decrypted. + The length of the buffer array. + The total capacity of the buffer array. + Returns true if the decryption was successful, false otherwise. + + + + Represents an encryption token. + + + + + Configuration for the Encryption Feature + + + + + Enabled the Encryption Feature + + + + + Interface for writing bits to a stream. + + + + + Gets the current offset in bits. + + + + + Writes a 32-bit signed integer to the stream. + + The 32-bit signed integer value to write. + The number of bits to write. Default is 32. + + + + Writes a 32-bit signed integer to the stream with variable length encoding. + + The 32-bit signed integer value to write. + + + + Writes a 32-bit signed integer to the stream with variable length encoding and a specified block size. + + The 32-bit signed integer value to write. + The block size for variable length encoding. + + + + Writes a 64-bit unsigned integer to the stream with variable length encoding and a specified block size. + + The 64-bit unsigned integer value to write. + The block size for variable length encoding. + + + + Writes a boolean value to the stream. + + The boolean value to write. + The boolean value that was written. + + + + Writes a sequence of bytes to the stream, aligned to byte boundaries. + + A pointer to the buffer containing the bytes to write. + The number of bytes to write. + + + + Writes a sequence of bytes to the stream, aligned to byte boundaries. + + A pointer to the buffer containing the bytes to write. + + + + Represents a Network Address, which includes a IP and Port + This can contains either a IPv4 or a IPv6 address + + + + + Retrieves the Remote Actor ID which this Represents + + + + + Signal if the is a Relayed connection + + + + + Signal if the represents an IPv6 Address + + + + + Signal if the represents an IPv4 Address + + + + + Signal if this is not default/empty + + + + + Signal if this has a valid IP Address + + + + + Build a new based on an ActorId + + ActorId used to build the NetAddress + Relay NetAddress that references the ActorId + ActorId must be 0 or greater + + + + Build 64Bit Hash + + Address to build the Hash + 64bit Hash + + + + Create a new NetAddress on the LocalHost address with the desired Port + + Port used to build the NetAddress + New NetAddress reference + + + + Create a new on the LocalHost IPv6 Address with the desired Port + + Port used to build the NetAddress + New NetAddress reference + + + + Create a new NetAddress using the "Any" IPv4 Address representation (0.0.0.0) + with the Port passed as argument + + Port used to build the NetAddress + New NetAddress reference + + + + Create a new NetAddress using the "Any" IPv6 Address representation (::) + with the Port passed as argument + + Port used to build the NetAddress + New NetAddress reference + + + + Create a new NetAddress based on the IP and Port passed as argument + + String representation of an IP, either IPv4 or IPv6 + Port used to build the NetAddress + New NetAddress reference + If IP is empty/null or an invalid IP, or port < 0 + If unable to parse IP + + + + Determines whether the specified instances are equal. + + + + + Determines whether the specified is equal to the current . + + + + + Returns a hash code for this instance. + + + + + Provides a string representation of the + + + + + Provides methods to compare two instances for equality. + + + + + Determines whether the specified instances are equal. + + + + + Returns a hash code for the specified . + + + + + IPv4 Subnet Mask Utilities + + + + + List of IPv4 Subnet Masks + + + + + Check if two IPv4 seems to be in the same Subnet. + + The addresses are checked against all subnet masks in . + + EndPoint A + EndPoint B + True if both addresses seems to be in the same subnet + + + + Extracts the Network Address of an IPv4 EndPoint based on a particular Subnet Mask + + EndPoint + Subnet Mask + Network Address based on the Subnet Mask + + + + Represents a buffer for reading and writing bits. + + + + + The address of the buffer. + + + + + Gets or sets a pointer to the data in the buffer. + + + + + Gets the length of the buffer in bits. + + + + + Gets the number of bytes remaining in the buffer. + + + + + Gets or sets the length of the buffer in bytes. + + + + + Gets or sets the current offset in bits. + + + + + Gets a value indicating whether the buffer has been fully read. + + + + + Gets a value indicating whether the buffer has overflowed. + + + + + Gets a value indicating whether the buffer has overflowed or has less than one byte remaining. + + + + + Gets or sets the current offset in bits without any safety checks. + + + + + Gets a value indicating whether the buffer is done or has overflowed. + + + + + Gets a value indicating whether there is more data to read in the buffer. + + + + + Gets or sets the packet type stored in the buffer. + + + + + Replaces the current data block with a temporary block of the specified size. + + The size of the temporary block in bytes. + + + + Represents an offset within a NetBitBuffer. + + + + + Initializes a new instance of the Offset struct with the specified buffer. + + The buffer to initialize the offset from. + + + + Gets the length in bits from the stored offset to the current offset of the buffer. + + The buffer to calculate the length from. + The length in bits. + + + + Gets the current offset of the specified buffer. + + The buffer to get the offset from. + An Offset struct representing the current offset. + + + + Allocates a new NetBitBuffer with the specified group and size. + + The group identifier for the buffer. + The size of the buffer in bytes. + A pointer to the allocated NetBitBuffer. + + + + Releases the reference to the specified buffer and sets it to null. + + A reference to the buffer to release. + + + + Releases the specified buffer. + + The buffer to release. + + + + Sets the buffer length in bytes and updates the internal length in bits. + + A pointer to the buffer. + The length of the buffer in bytes. + + + + Clears the buffer by setting all bits to zero. + + + + + Writes a boolean value to the buffer. + + The boolean value to write. + The boolean value that was written. + + + + Reads a boolean value from the buffer. + + The boolean value read from the buffer. + + + + Peeks at the next boolean value in the buffer without advancing the read position. + + The boolean value peeked from the buffer. + + + + Writes a byte value to the buffer. + + The byte value to write. + The number of bits to write. Default is 8. + + + + Reads a byte value from the buffer. + + The number of bits to read. Default is 8. + The byte value read from the buffer. + + + + Writes a 16-bit signed integer value to the buffer. + + The 16-bit signed integer value to write. + The number of bits to write. Default is 16. + + + + Reads a 16-bit signed integer value from the buffer. + + The number of bits to read. Default is 16. + The 16-bit signed integer value read from the buffer. + + + + Writes a 16-bit unsigned integer value to the buffer. + + The 16-bit unsigned integer value to write. + The number of bits to write. Default is 16. + + + + Reads a 16-bit unsigned integer value from the buffer. + + The number of bits to read. Default is 16. + The 16-bit unsigned integer value read from the buffer. + + + + Writes a 32-bit signed integer value to the buffer. + + The 32-bit signed integer value to write. + The number of bits to write. Default is 32. + + + + Reads a 32-bit signed integer value from the buffer. + + The number of bits to read. Default is 32. + The 32-bit signed integer value read from the buffer. + + + + Writes a 32-bit unsigned integer value to the buffer. + + The 32-bit unsigned integer value to write. + The number of bits to write. Default is 32. + + + + Writes a string to the buffer using UTF-8 encoding. + + The string to write. + + + + Writes a string to the buffer using the specified encoding. + + The string to write. + The encoding to use. + + + + Reads a string from the buffer using UTF-8 encoding. + + The string read from the buffer. + + + + Reads a string from the buffer using the specified encoding. + + The encoding to use. + The string read from the buffer. + + + + Checks if the specified number of bits can be written to the buffer. + + The number of bits to check. + true if the specified number of bits can be written; otherwise, false. + + + + Checks if the specified number of bits can be read from the buffer. + + The number of bits to check. + true if the specified number of bits can be read; otherwise, false. + + + + Gets a value indicating whether the buffer is aligned to an even byte boundary. + + + + + Gets the current offset in bytes. + + + + + Pads the buffer to the next byte boundary by writing zero bits if necessary. + + + + + Gets a pointer to the current data position in the buffer. + + A pointer to the current data position in the buffer. + + + + Pads the buffer to the next byte boundary and returns a pointer to the current data position. + + A pointer to the current data position in the buffer after padding to the byte boundary. + + + + Checks if the specified number of bits can be read or written without exceeding the buffer length. + + The number of bits to check. + true if the specified number of bits can be read or written; otherwise, false. + + + + Advances the buffer offset to the next byte boundary. + + + + + Writes an array of bytes to the buffer, ensuring byte alignment. + + The array of bytes to write. + The number of bytes to write. + + + + Writes a block of bytes to the buffer, ensuring byte alignment. + + A pointer to the block of bytes to write. + The number of bytes to write. + + + + Writes a block of bytes to the buffer, ensuring byte alignment. + + A pointer to the block of bytes to write. + + + + Reads an array of bytes from the buffer, ensuring byte alignment. + + The array to store the read bytes. + The number of bytes to read. + + + + Reads a block of bytes from the buffer, ensuring byte alignment. + + A pointer to the block of bytes to store the read data. + + + + Reads a block of bytes from the buffer, ensuring byte alignment. + + A pointer to the block of bytes to store the read data. + The number of bytes to read. + + + + Writes a 64-bit signed integer value with variable length to the buffer. + + The 64-bit signed integer value to write. + The block size in bits. + + + + Writes a 32-bit signed integer value with variable length to the buffer. + + The 32-bit signed integer value to write. + + + + Writes a 32-bit signed integer value with variable length to the buffer. + + The 32-bit signed integer value to write. + The block size in bits. + + + + Reads a 32-bit signed integer value with variable length from the buffer. + + The 32-bit signed integer value read from the buffer. + + + + Reads a 64-bit signed integer value with variable length from the buffer. + + The block size in bits. + The 64-bit signed integer value read from the buffer. + + + + Reads a 32-bit signed integer value with variable length from the buffer. + + The block size in bits. + The 32-bit signed integer value read from the buffer. + + + + Reads a 32-bit unsigned integer value with variable length from the buffer. + + The block size in bits. + The 32-bit unsigned integer value read from the buffer. + + + + Reads a 64-bit unsigned integer value with variable length from the buffer. + + The block size in bits. + The 64-bit unsigned integer value read from the buffer. + + + + Writes a 32-bit unsigned integer value with variable length to the buffer. + + The 32-bit unsigned integer value to write. + The block size in bits. + + + + Writes a 64-bit unsigned integer value with variable length to the buffer. + + The 64-bit unsigned integer value to write. + The block size in bits. + + + + Writes a 32-bit unsigned integer value with variable length to the buffer. + + The 32-bit unsigned integer value to write. + + + + Reads a 32-bit unsigned integer value with variable length from the buffer. + + The 32-bit unsigned integer value read from the buffer. + + + + Reads a 32-bit unsigned integer value from the buffer. + + The number of bits to read. Default is 32. + The 32-bit unsigned integer value read from the buffer. + + + + Writes a 64-bit signed integer value to the buffer. + + The 64-bit signed integer value to write. + The number of bits to write. Default is 64. + + + + Reads a 64-bit signed integer value from the buffer. + + The number of bits to read. Default is 64. + The 64-bit signed integer value read from the buffer. + + + + Writes a 64-bit unsigned integer value to the buffer. + + The 64-bit unsigned integer value to write. + The number of bits to write. Default is 64. + + + + Reads a 64-bit unsigned integer value from the buffer. + + The number of bits to read. Default is 64. + The 64-bit unsigned integer value read from the buffer. + + + + Writes a single-precision floating-point value to the buffer. + + The single-precision floating-point value to write. + + + + Reads a single-precision floating-point value from the buffer. + + The single-precision floating-point value read from the buffer. + + + + Writes a double-precision floating-point value to the buffer. + + The double-precision floating-point value to write. + + + + Reads a double-precision floating-point value from the buffer. + + The double-precision floating-point value read from the buffer. + + + + Writes a 32-bit integer value at a specified offset in the buffer. + + The 32-bit integer value to write. + The offset in bits where the value should be written. + The number of bits to write. + + + + Writes a 64-bit unsigned integer value at a specified offset in the buffer. + + The 64-bit unsigned integer value to write. + The offset in bits where the value should be written. + The number of bits to write. + + + + Writes a value to the buffer with a specified number of bits. + + The value to write. + The number of bits to write. + + + + Writes a value to the buffer with a specified number of bits, handling cases where the value spans multiple words. + + The value to write. + The number of bits to write. + + + + Represents a linked list of + + + + + Add a at the beginning of the List + + NetBitBuffer to add to the list + + + + Add a at the end of the list. + + NetBitBuffer to add to the list + + + + Removes the first element of the list + + NetBitBuffer reference + + + + Remove a specific from the list + + NetBitBuffer to remove + + + + Check if a specific is in the list + + NetBitBuffer to check + True if the list contains the item, false otherwise + + + + Represents a null bit buffer for writing data. + + + + + Gets or sets the offset in bits. + + The current offset in bits. + + + + Pads the buffer to the next byte boundary by writing zero bits if necessary. + + + + + Writes a byte value to the buffer and increments the offset by the specified number of bits. + + The byte value to write. + The number of bits to write. Default is 8. + + + + Writes a 32-bit integer value to the buffer and increments the offset by the specified number of bits. + + The 32-bit integer value to write. + The number of bits to write. Default is 32. + + + + Writes a 32-bit integer value with variable length encoding. + + The 32-bit integer value to write. + + + + Writes a 32-bit integer value with variable length encoding and a specified block size. + + The 32-bit integer value to write. + The block size for encoding. + + + + Writes a 32-bit unsigned integer value with variable length encoding and a specified block size. + + The 32-bit unsigned integer value to write. + The block size for encoding. + + + + Writes a 64-bit unsigned integer value with variable length encoding and a specified block size. + + The 64-bit unsigned integer value to write. + The block size for encoding. + + + + Writes a 32-bit unsigned integer value with variable length encoding. + + The 32-bit unsigned integer value to write. + + + + Writes a boolean value to the buffer and increments the offset by 1 bit. + + The boolean value to write. + The boolean value that was written. + + + + Writes a specified number of bytes from a buffer to the bit buffer, ensuring byte alignment. + + A pointer to the buffer containing the bytes to write. + The number of bytes to write. + + + + Writes a specified number of bytes from a buffer to the bit buffer, ensuring byte alignment. + + A pointer to the buffer containing the bytes to write. + + + + Represents a serializer for reading and writing data to a . + + + + + Gets a value indicating whether the serializer is in write mode. + + + + + Gets a value indicating whether the serializer is in read mode. + + + + + Gets the buffer associated with the serializer. + + + + + Creates a new instance of for writing to the buffer. + + The buffer to write to. + A new instance of configured for writing. + + + + Creates a new instance of for reading from the buffer. + + The buffer to read from. + A new instance of configured for reading. + + + + Checks the boolean value and writes or reads it from the buffer based on the serializer mode. + + The boolean value to check. + The boolean value read from the buffer if in read mode, otherwise the value written to the buffer. + + + + Serializes a float value by writing or reading it from the buffer based on the serializer mode. + + The float value to serialize. + + + + Serializes a byte value by writing or reading it from the buffer based on the serializer mode. + + The byte value to serialize. + + + + Serializes a boolean value by writing or reading it from the buffer based on the serializer mode. + + The boolean value to serialize. + + + + Serializes an integer value by writing or reading it from the buffer based on the serializer mode. + + The integer value to serialize. + + + + Serializes an unsigned integer value by writing or reading it from the buffer based on the serializer mode. + + The unsigned integer value to serialize. + + + + Serializes an unsigned long value by writing or reading it from the buffer based on the serializer mode. + + The unsigned long value to serialize. + + + + Serializes a string value by writing or reading it from the buffer based on the serializer mode. + + The string value to serialize. + + + + Describe the Type of a Command Packet + + + + + Network Command Header + Describe its type and usual settings for all commands + + + + + Create a new NetCommandHeader based on a type + + Type of Command that should be created + New NetCommandHeader reference based on the Command Type + + + + Connect Command used to signal a remote server that a client is trying to connect to it + + + + + Accepted Command, sent by the server when a remote client connection is accepted + + + + + Refuse Command, sent by the server when the connection was refused. + This happens when the server has reached its max connection capacity. + + + + + Disconnect Command, it can be used by either side of the connection + + + + + General configuration used to drive the behavior of the Socket library + + + + + Pre-allocated number of data buffers used to send data + + + + + Number of Connection Groups supported by the local instance + + + + + Max Number of Connections supported by the local instance + + + + + Max number of Connection per Group based on the and + + + + + Size of the internal Socket send buffer + + + + + + Size of the internal Socket receive buffer + + + + + + UDP Packet Size in Bytes + + + + + UDP Packet Size in Bits based on + + + + + Number of Connection Attempts tried by the peer before cancel the connection + + + + + Interval in Seconds between attempts to connect to a remote server + + + + + Max Allowed time for the Send and Receive operations, in milliseconds + + + + + Initial RTT + + + + + Connection Timeout in seconds + + + + + Interval in Seconds between ping being sent to a remote end + + + + + Timeout in Seconds to allow a disconnected Connection to be released from the Group Mapping + + + + + + + + + Network Address used to bind the internal Socket + + + + + Package acknowledgment system configuration + + + + + Network simulation system configuration + + + + + Builds a with the default values + + + + + Represents the configuration for network notifications. + + + + + The number of bytes used for acknowledgment masks. + + + + + The count of forced acknowledgments. + + + + + The timeout for forced acknowledgments. + + + + + The size of the window. + + + + + The number of bytes used for sequences. + + + + + Gets the sequence bounds, calculated as WindowSize * 16. + + + + + Gets the number of bits used for acknowledgment masks. + + + + + Gets the default configuration for network notifications. + + + + + Represents the configuration for network simulation. + + + + + Pointer to the sequences used for loss notifications. + + + + + The length of the loss notification sequences. + + + + + The oscillator used for simulating delay. + + + + + The oscillator used for simulating loss. + + + + + The chance of a duplicate packet. + + + + + Gets the default configuration for network simulation. + + + + + Creates a configuration with specified loss notification sequences. + + The sequences to be used for loss notifications. + A new instance of with the specified sequences. + + + + Represents an oscillator configuration for network simulation. + + + + + Defines the shape of the wave. + + + + + Random noise wave shape. + + + + + Sine wave shape. + + + + + Square wave shape. + + + + + Triangle wave shape. + + + + + Sawtooth wave shape. + + + + + Reverse sawtooth wave shape. + + + + + The shape of the wave. + + + + + The minimum value of the wave. + + + + + The maximum value of the wave. + + + + + The period of the wave in seconds. + + + + + The threshold value for the wave. + + + + + Additional noise to be added to the wave. + + + + + Calculates the value of the wave at a given time. + + The random number generator. + The elapsed time in seconds. + The calculated wave value. + + + + The reason a connection with a remote server has failed + + + + + Server is not responding. + + + + + Server has accepted the max allowed Players + + + + + Server refused the connection + + + + + Network connection + + + + + Client Unique ID size in bytes + + + + + Gets a value indicating whether the connection is active. + + + + + Gets the round-trip time (RTT) for the connection. + + + + + Gets the remote address of the connection. + + + + + Gets the current status of the connection. + + + + + Gets the local connection ID. + + + + + Gets the remote connection ID. + + + + + Returns a string that represents the current NetConnection. + + + + + Represents a network connection ID. + + + + + An equality comparer for NetConnectionId instances. + + + + + Compares two NetConnectionId instances for equality. + + + + + Get the hash code for the NetConnectionId. + + + + The group of the connection. + + + The index of the connection within the group. + + + + Compares this NetConnectionId to another for equality. + + + + + Compares this NetConnectionId to another for equality. + + + + + Get the hash code for this NetConnectionId. + + + + + Compares two NetConnectionId instances for equality. + + + + + Compares two NetConnectionId instances for inequality. + + + + + NetConnectionId ToString + + + + + Represents a network connection map. + + + + + Represents the state of an entry in the `NetConnectionMap`. + + + + + The entry is not in use. + + + + + The entry is free and available for use. + + + + + The entry is currently in use. + + + + + Iterator for traversing the connections in a `NetConnectionMap`. + + + + + Gets the current connection in the iteration. + + + + + Initializes a new instance of the `Iterator` struct. + + The `NetConnectionMap` to iterate over. + + + + Gets a value indicating whether the current index is valid. + + + + + Advances the iterator to the next connection. + + True if the iterator was successfully advanced to the next connection, otherwise false. + + + + Disposes of the specified `NetConnectionMap` and its associated resources. + + A pointer to the `NetConnectionMap` to dispose. + The callbacks to invoke during disposal. + + + + Allocates and initializes a new `NetConnectionMap` with the specified capacity and group index. + + The number of connections the map can hold. + The group index associated with the connections. + The configuration settings for the network connections. + A pointer to the newly allocated `NetConnectionMap`. + + + + Gets the count of used connections minus the free connections. + + + + + Gets the count of used connections. + + + + + Gets the buffer of connections. + + + + + Gets a value indicating whether the connection map is full. + + + + + Remaps a connection from an old address to a new address. + + The old address of the connection. + The new address of the connection. + A pointer to the remapped connection, or null if the remapping failed. + + + + Removes a connection from the network peer group by its address. + + The address of the connection to remove. + True if the connection was successfully removed, otherwise false. + + + + Inserts a new connection into the network peer group using the specified address and unique ID. + + The address of the connection to insert. + The unique ID of the connection to insert. + A pointer to the newly inserted connection, or null if the connection is already in use or the capacity is full. + + + + Gets a connection by its index in the network peer group. + + The index of the connection to retrieve. + A pointer to the connection if found, otherwise throws an IndexOutOfRangeException. + Thrown when the index is out of the usable capacity range. + + + + Tries to get a connection by its index in the network peer group. + + The index of the connection to retrieve. + Output parameter that will point to the connection if found. + True if the connection was found, otherwise false. + + + + Finds a connection by its ID in the network peer group. + + The ID of the connection to find. + A pointer to the connection if found, otherwise null. + + + + Finds a connection by its address in the network peer group. + + The address of the connection to find. + A pointer to the connection if found, otherwise null. + + + + Represents the status of a network connection. + + + + + The connection has been created. + + + + + The connection is in the process of connecting. + + + + + The connection is established. + + + + + The connection has been disconnected. + + + + + The connection has been shut down. + + + + + Disconnect Reason Flag + + + Disconnect Reason Flag + + + + + The reason for disconnection is unknown. + + + + + The connection timed out. + + + + + The disconnection was requested. + + + + + The sequence is out of bounds. + + + + + The send window is full. + + + + + The disconnection was initiated by the remote party. + + + + + Describe the type of a Networked Packet + + + + + Network Peer + + + + + IPv6 header: 40 bytes, UDP header: 8 bytes, Realtime Header: 96 bytes + + + + + MaximumTransferUnit total bytes. + + The maximum size of bytes that can be sent in a single UDP packet. + + + + + MaximumTransferUnit payload bytes. + + The maximum number of bytes available for data(payload) in a single UDP packet. + + + + + MaximumTransferUnit bits. (ipv6 header: 40 bytes, udp header: 8 bytes) + + Same as but in bits. + + + + + Max packet payload size in bytes. (Considering the NetNotifyHeader size for each fragment) + + The maximum number of bytes available for data that can be fragmented into multiple packets of size . + + + + + Max packet total size in bytes. + + The maximum number of bytes that can be fragmented into multiple packets of size . + + + + + Maximum number of fragments. + + + + + Flag for the last fragment. + + + + + Gets the network address of the peer. + + + + + Gets the configuration settings for the peer. + + + + + Gets the number of connection groups. + + + + + Gets a value indicating whether the peer is shut down. + + + + + Gets a pointer to the configuration settings of the specified NetPeer. + + A pointer to the NetPeer instance. + A pointer to the NetConfig if the peer is not shut down; otherwise, null. + + + + Gets a pointer to the NetPeerGroup at the specified index. + + A pointer to the NetPeer instance. + The index of the NetPeerGroup. + A pointer to the NetPeerGroup if the peer is not shut down; otherwise, null. + + + + Updates the state of the specified NetPeer. + + A pointer to the NetPeer instance. + The socket used for network communication. + A random number generator. + + + + Updates the state of the specified NetPeer and indicates whether work was done. + + A pointer to the NetPeer instance. + The socket used for network communication. + A pointer to a boolean indicating whether work was done. + A random number generator. + + + + Receives data for the specified NetPeer. + + A pointer to the NetPeer instance. + The socket used for network communication. + A random number generator. + + + + Receives data for the specified NetPeer and indicates whether work was done. + + A pointer to the NetPeer instance. + The socket used for network communication. + A pointer to a boolean indicating whether work was done. + A random number generator. + + + + Remaps the network address of the specified NetPeer. + + A pointer to the NetPeer instance. + The old network address. + The new network address. + + + + Sends data for the specified NetPeer. + + A pointer to the NetPeer instance. + The socket used for network communication. + /// The amount of fragments sent + + + + Sends data for the specified NetPeer and indicates whether work was done. + + A pointer to the NetPeer instance. + The socket used for network communication. + A pointer to a boolean indicating whether work was done. + The amount of fragments sent + + + + Initializes a new instance of the NetPeer structure with the specified configuration and socket. + + The configuration settings for the NetPeer. + The socket used for network communication. + A pointer to the newly initialized NetPeer instance. + + + + Initializes the specified NetPeer instance with the given configuration and socket. + + A pointer to the NetPeer instance. + The configuration settings for the NetPeer. + The socket used for network communication. + + + + Destroys the specified NetPeer instance and releases associated resources. + + A pointer to the NetPeer instance. + The socket used for network communication. + The callbacks for the NetPeerGroup. + + + The amount of fragments sent + + + The amount of fragments sent + + + + Network Peer Group. + + + + + Gets the elapsed time in seconds. + + + + + Gets the group index. + + + + + Gets the number of active connections. + + + + + Gets a connection by its ID in the network peer group. + + Pointer to the network peer group. + The ID of the connection to retrieve. + A pointer to the connection if found, otherwise null. + + + + Gets a connection by its index in the network peer group. + + Pointer to the network peer group. + The index of the connection to retrieve. + A pointer to the connection if found, otherwise null. + + + + Tries to get a connection by its index in the network peer group. + + Pointer to the network peer group. + The index of the connection to retrieve. + Output parameter that will point to the connection if found. + True if the connection was found, otherwise false. + + + + Gets an iterator for the connections in the network peer group. + + Pointer to the network peer group. + An iterator for the connections in the network peer group. + + + + Connects to a specified address with an optional unique ID and token. + + Pointer to the network peer group. + The address to connect to. + The token used for the connection. + Optional unique ID for the connection. + + + + Connects to a specified IP address and port with an optional unique ID and token. + + Pointer to the network peer group. + The IP address to connect to. + The port to connect to. + The token used for the connection. + Optional unique ID for the connection. + + + + Disconnects a given connection from the network peer group with an optional token. + + Pointer to the network peer group. + Pointer to the connection to be disconnected. + Optional token used for the disconnection. + + + + Updates the network peer group by processing received data, handling timeouts, and managing connection retries. + + Pointer to the network peer group. + Callback interface for network peer group events. + Amount of packets received + + + + Sends reliable data for a given connection in the network peer group. + + Pointer to the network peer group. + Pointer to the connection. + Reliable ID for the data being sent. + Pointer to the data to be sent. + The length of the data to be sent. + + + + Changes the address of a connection during the connecting phase. + + Pointer to the network peer group. + Pointer to the connection. + The new address to be assigned to the connection. + + + + Sends unconnected data to a specified address. + + Pointer to the network peer group. + The address to send the data to. + Pointer to the data to be sent. + The length of the data to be sent. + True if the data was successfully sent, otherwise false. + + + + Gets an unreliable data buffer for a given connection in the network peer group. + + Pointer to the network peer group. + Pointer to the connection. + Output parameter that will point to the bit buffer containing the data to be sent. + True if the buffer was successfully acquired, otherwise false. + + + + Sends an unreliable data buffer for a given connection in the network peer group. + + Pointer to the network peer group. + Pointer to the connection. + Pointer to the bit buffer containing the data to be sent. + True if the buffer was successfully sent, otherwise false. + + + + Gets a notify data buffer for a given connection in the network peer group. + + Pointer to the network peer group. + Pointer to the connection. + Output parameter that will point to the bit buffer containing the data to be sent. + True if the buffer was successfully acquired, otherwise false. + + + + Sends a notify data buffer for a given connection in the network peer group. + + Pointer to the network peer group. + Pointer to the connection. + Pointer to the bit buffer containing the data to be sent. + Pointer to user data associated with the buffer. + True if the buffer was successfully sent, otherwise false. + + + Amount of packets received + + + + Gets the idle time of a connection. + + The network peer group. + The connection whose idle time is to be calculated. + The idle time of the connection in seconds. + + + + Represents the possible replies to a connection request. + + + + + The connection request is accepted. + + + + + The connection request is refused. + + + + + The connection request is waiting for a decision. + + + + + Defines the callbacks for network peer group events. + + + + + Called when a connection is established. + + The connection that was established. + + + + Called when a connection is disconnected. + + The connection that was disconnected. + The reason for disconnection. + + + + Called when unreliable data is received. + + The connection from which the data was received. + The buffer containing the data. + + + + Called when unconnected data is received. + + The buffer containing the data. + + + + Called when notify data is received. + + The connection from which the data was received. + The buffer containing the data. + + + + Called when notify data is lost. + + The connection from which the data was lost. + The envelope containing the lost data. + + + + Called when notify data is delivered. + + The connection to which the data was delivered. + The envelope containing the delivered data. + + + + Called when a notify envelope is disposed. + + The envelope that was disposed. + + + + Called when reliable data is received. + + The connection from which the data was received. + The identifier of the reliable data. + The data received. + + + + Called when a connection request is received. + + The address of the remote peer. + The token associated with the connection request. + The unique identifier for the connection request. + The reply to the connection request. + + + + Called when a connection attempt fails. + + The address of the remote peer. + The reason for the connection failure. + + + + Called when a connection attempt is made. + + The connection being attempted. + The number of attempts made. + The total number of connection attempts. + + + + Represents an envelope for sending network packets in the Fusion.Sockets namespace. + + + + + Gets or sets the user data associated with the envelope. + + + + + Gets or sets the time the packet was sent. + + + + + Gets or sets the sequence number of the packet. + + + + + Gets or sets the type of the network packet. + + + + + Takes the user data associated with the envelope and sets the UserData to null. + + The type of the user data. + The user data cast to the specified type. + Thrown if UserData is null. + + + + Calculates the distance between two sequence numbers, taking into account the circular nature of the sequence. + + The sequence number to start from. + The sequence number to calculate the distance to. + + The distance between the two sequence numbers. + If 'from' is larger than 'to', the result is positive. + If 'from' is lesser than 'to', the result is negative. + If they are the same, the result is zero. + + Thrown when the calculated distance is outside the range of an integer. + + + + Represents a buffer for reliable data transmission. + + + + + The number of bytes used for the sequence. + + + + + The sequencer used for generating sequence numbers. + + + + + The list of received reliable data. + + + + + The current receive sequence number. + + + + + Gets the number of bits used for the sequence. + + + + + Creates a new instance of the ReliableBuffer structure. + + A new ReliableBuffer instance. + + + + Gets the next sequence number for sending data. + + The next sequence number. + + + + Disposes of the ReliableBuffer, freeing any allocated memory. + + + + + Attempts to receive data from the reliable buffer. + + The pointer to the memory that needs to be freed later. + The reliable identifier associated with the received data. + The pointer to the received data. + + true if the data is exactly what is expected; otherwise, false. + + + + + Frees the memory associated with the specified root pointer. + + The pointer to the memory to be freed. + + + + Receives data from the specified buffer and processes it. + + The buffer containing the data to be received. + The reliable identifier associated with the received data. + + true if the data is exactly what is expected; otherwise, false. + + + + + Represents a reliable key structure used in the Fusion.Sockets namespace. + + + + + The size of the ReliableKey structure in bytes. + + + + + Fixed byte array to store the key data. + + + + + Gets the key data as integers. + + The first integer key. + The second integer key. + The third integer key. + The fourth integer key. + + + + Gets the key data as unsigned long integers. + + The first unsigned long key. + The second unsigned long key. + + + + Creates a ReliableKey from integer values. + + The first integer key. + The second integer key. + The third integer key. + The fourth integer key. + A new ReliableKey instance. + + + + Creates a ReliableKey from unsigned long values. + + The first unsigned long key. + The second unsigned long key. + A new ReliableKey instance. + + + + Returns if the is the same. + + + + + Returns if obj is a ReliableKey and the is the same. + + + + + Returns if the is the same. + + + + + Returns if the is not the same. + + + + + Returns a hash based on the . + + + + + + Represents a reliable identifier used in the Fusion.Sockets namespace. + + + + + The size of the ReliableId structure in bytes. + + + + + The sequence number associated with this ReliableId. + + + + + The length of the slice. + + + + + The total length. + + + + + The source identifier. + + + + + The source send identifier. + + + + + The target identifier. + + + + + The reliable key associated with this ReliableId. + + + + + Padding to align the structure. + + + + + Gets the combined source identifier. + + + + + Represents a reliable header structure used in the Fusion.Sockets namespace. + + + + + The size of the ReliableHeader structure in bytes. + + + + + Pointer to the next ReliableHeader in the list. + + + + + Pointer to the previous ReliableHeader in the list. + + + + + The ReliableId associated with this ReliableHeader. + + + + + Gets the data associated with the specified ReliableHeader. + + The ReliableHeader to get data from. + A pointer to the data associated with the specified ReliableHeader. + + + + Represents a list of reliable headers. + + + + + Gets or sets the number of items in the list. + + + + + Gets or sets the head of the list. + + + + + Gets or sets the tail of the list. + + + + + Adds the specified item to the beginning of the list. + + The item to add to the beginning of the list. + + + + Adds the specified item to the end of the list. + + The item to add to the end of the list. + + + + Adds the specified item before another specified item in the list. + + The item before which the new item will be added. + The item to add to the list. + + + + Adds the specified item after another specified item in the list. + + The item after which the new item will be added. + The item to add to the list. + + + + Removes and returns the head element of the list. + + The head element of the list. + + + + Removes the specified item from the list. + + The item to remove. + + + Dispose + + + + Represents a network socket with a handle and a native socket. + + + + + The handle of the socket. + + + + + The native socket. + + + + + Gets a value indicating whether the socket is created. + + + + + Defines the interface for network socket operations. + + + + + Gets a value indicating whether the socket supports multi-threading. + + + + + Initializes the socket with the specified configuration. + + The configuration for the socket. + + + + Creates a new socket with the specified configuration. + + The configuration for the socket. + The created socket. + + + + Binds the socket to the specified address and configuration. + + The socket to bind. + The configuration for the socket. + The bound address. + + + + Determines whether the specified address can be fragmented. + + The address to check. + True if the address can be fragmented, otherwise false. + + + + Polls the socket for incoming data with the specified timeout. + + The socket to poll. + The timeout in milliseconds. + True if data is available, otherwise false. + + + + Receives data from the socket into the specified buffer. + + The socket to receive data from. + The address of the sender. + The buffer to store the received data. + The length of the buffer. + The number of bytes received. + + + + Sends data from the specified buffer to the socket. + + The socket to send data to. + The address of the recipient. + The buffer containing the data to send. + The length of the buffer. + Send reliable or not + The number of bytes sent. + + + + Destroys the specified socket. + + The socket to destroy. + + + + Deletes the encryption key for the specified address. + + The address to delete the encryption key for. + + + + Sets up encryption with the specified key and encrypted key. + + The encryption key. + The encrypted key. + + + Encryption System + + Sets up the encryption system by initializing the encryption manager, + registering the encryption key, and creating the encryption token and buffer. + + The encryption key. + The encrypted key. + + If is null, it indicates that the peer is a server. + If is not null, the peer is a client. + + + + + Handles the outgoing encryption process for the given address and buffer. + + The address to which the data is being sent. + The buffer containing the data to be sent. + The length of the data to be sent. + Returns true if the encryption process is successful, false otherwise. + + + + Handles the incoming encryption process for the given address and buffer. + + The address from which the data is received. + The buffer containing the received data. + The length of the received data. + The number of bytes received. + Returns true if the decryption process is successful, false otherwise. + + + + Resets the encryption system by disposing the encryption manager and resetting the session key. + + + + + Deletes the encryption key associated with the given address. + + The address whose associated encryption key is to be deleted. + + + + Random ID of this socket + + + + + Reference to Current Communicator + + + + + Local Peer Address is based on the current Player Actor Number inside the Room + + + + + Specifies UDP network type. + + + + + Invalid NAT Type + + + + + UDP is always blocked. + + + + + No NAT, public IP, no firewall. + + + + + A full cone NAT is one where all requests from the same internal IP address and port are + mapped to the same external IP address and port. Furthermore, any external host can send + a packet to the internal host, by sending a packet to the mapped external address. + + + + + A symmetric NAT is one where all requests from the same internal IP address and port, + to a specific destination IP address and port, are mapped to the same external IP address and + port. If the same host sends a packet with the same source address and port, but to + a different destination, a different mapping is used. Furthermore, only the external host that + receives a packet can send a UDP packet back to the internal host. + + + + + This class implements STUN Client. Defined in RFC 8489 + + Session Traversal Utilities for NAT (STUN) + Traversal Using Relays around NAT (TURN): Relay Extensions to Session Traversal Utilities for NAT(STUN) + Happy Eyeballs Version 2: Better Connectivity Using Concurrency + State of Peer-to-Peer (P2P) Communication across Network Address Translators(NATs) + + + + + + + + + + List of public DNS Servers + + + + + Reset the state of the StunClient + + + + + Run the STUN Service to retrieve the current Reflexive Addresses of the local peer + + The bound local address to be used for the STUN service. + A function delegate that takes a byte array and a NetAddress as parameters and returns a boolean value. This function is used to send data via a socket. + An optional parameter that represents a custom public address. If provided, this address is used instead of discovering one. + An optional parameter that represents a custom STUN server. If provided, this server is used instead of the default one. + A boolean value indicating whether to extend the query time. If true, the query time is extended; otherwise, it is not. + A function delegate that takes no parameters and returns a boolean value. This function is used to determine whether the STUN service should keep running. + Reflexive Info data as a object. + + + + Tries to parse a buffer data into a + + Remove origin of the buffer data + Buffer data to be parsed + Lenght of the buffer data to be parsed + True if the buffer contains a , false otherwise + + + + Query the LAN Address + + Bound Local Address + Output the Address Family found + Output the LAN Address + True if a valid LAN Address was found, false otherwise + + + + Run a STUN Binding Request against the Public STUN Server in order to discover peer reflexive addresses + + Custom method to send data to remote Address + Original Address Family + Request ID to be used when sending the STUN Binding Request + Signal to skip the NAT Type discovery + Running Task of the STUN Query Procedure + + + + Retrieve the Local IP Endpoint currently active + + True if a valid Local IP Address was found, false otherwise + + + + This class implements STUN ERROR-CODE. Defined in RFC RFC 5389 15.6 + + + + + Gets or sets error code. + + + + + Gets reason text. + + + + + Default constructor. + + Error code. + Reason text. + + + + Implements STUN message. Defined in RFC 3489. + + + + + Cache Stun Message Types + + + + + STUN Message Type + + + + + STUN Attribute Type + + + + + Global Stun Related defined values + + + + + IP Address Family + + + + + STUN Message Type + + + + + STUN Transaction ID + + + + + Gets transaction ID. + + + + + Gets or sets IP end point what was actually connected to STUN server. Returns null if not specified. + + + + + Gets or sets user name. Value null means not specified. + + + + + Gets or sets error info. Returns null if not specified. + + + + + Default constructor. + + + + + Parses STUN message from raw data packet. + + Raw STUN message. + STUN Message length + + + + Converts this to raw STUN packet. + + Returns raw STUN packet. + + + + Parses attribute from data. + + SIP message data. + Offset in data. + + + + Pasrses IP endpoint attribute. + + STUN message data. + Offset in data. + Returns parsed IP end point. + + + + Stores ip end point attribute to buffer. + + Attribute type. + IP end point. + Buffer where to store. + Offset in buffer. + + + + This class holds the result of a STUN Query + + + + + Current NAT Type of the peer + + + + + Signal if Result is valid + + + + + Gets public IP end point. + + + + + Gets private IP end point. + + + + + Invalid StunResult Reference + + + + + Initializes a new instance of the class. + + The public IP endpoint. Defaults to an unspecified IPv4 address. + The private IP endpoint. Defaults to an unspecified IPv4 address. + + + + List of public STUN Servers + + + + + Stores Addresses of a STUN Server + + + + + Build a list of STUN Servers + + Filter STUN Servers with IPv6 Support + List of valid STUN Servers + + + + Pre-Build the list of all STUN Servers + + Optional Custom STUN Server to ping + + + + Build the STUN Server Info by checking its valid IPs + + + STUN Server Address + reference + + + + BitStream serialization methods. + + + + + Total size in BITS of the buffer + + + + + Current read/write position in BITS inside the Buffer + + + + + Size of written buffer in BYTES + Ammount of bytes required considering the total of written bytes + + + + + Gets a value indicating whether the current position is at an even byte boundary. + + + + + Total Size in BYTES of the Buffer + + + + + Signal if the buffer was completely written + + + + + Signal if the buffer is overflowing + + + + + Signal if the Buffer is in Write Mode + + + + + Signal if the Buffer is in Read Mode + + + + + Internal Byte Array + + + + + Initializes a new instance of the class with an empty byte array. + + + + + Initializes a new instance of the class with a specified size in bytes. + + The size of the byte array in bytes. + + + + Initializes a new instance of the class with the specified byte array. + + The byte array to use as the internal buffer. + + + + Initializes a new instance of the class with the specified byte array and size. + + The byte array to use as the internal buffer. + The size of the byte array in bytes. + + + + Sets the internal buffer to the specified byte array. + + The byte array to use as the internal buffer. + + + + Sets the internal buffer to the specified byte array and size. + + The byte array to use as the internal buffer. + The size of the byte array in bytes. + + + + Rounds the current position to the nearest byte boundary by filling with zero bits if necessary. + + The number of bytes required to store the current position. + + + + Doubles the capacity of the internal buffer. + + + + + Checks if at least one bit can be written to the stream. + + True if at least one bit can be written, otherwise false. + + + + Checks if at least one bit can be read from the stream. + + True if at least one bit can be read, otherwise false. + + + + Checks if the specified number of bits can be written to the stream. + + The number of bits to check. + True if the specified number of bits can be written, otherwise false. + + + + Checks if the specified number of bits can be read from the stream. + + The number of bits to check. + True if the specified number of bits can be read, otherwise false. + + + + Copies data from the specified byte array into the internal buffer. + + The byte array to copy from. + + + + Resets the internal buffer to its initial state. + + + + + Resets the internal buffer to the specified size. + + The size in bytes to reset the buffer to. + + + + Resets the internal buffer to the specified size without clearing the data. + + The size in bytes to reset the buffer to. + + + + Converts the internal buffer to a byte array. + + A byte array containing the data in the internal buffer. + + + + Writes a boolean value to the stream. + + The boolean value to write. + The boolean value that was written. + + + + Writes a boolean value to the stream. + + The boolean value to write. + The boolean value that was written. + + + + Reads a boolean value from the stream. + + The boolean value that was read. + + + + Reads a boolean value from the stream. + + The boolean value that was read. + + + + Writes a byte value to the stream with a specified number of bits. + + The byte value to write. + The number of bits to write. + + + + Reads a byte value from the stream with a specified number of bits. + + The number of bits to read. + The byte value that was read. + + + + Writes a byte value to the stream. + + The byte value to write. + + + + Reads a byte value from the stream. + + The byte value that was read. + + + + Reads a signed byte value from the stream. + + The signed byte value that was read. + + + + Writes a signed byte value to the stream. + + The signed byte value to write. + + + + Writes an unsigned short value to the stream with a specified number of bits. + + The unsigned short value to write. + The number of bits to write. + + + + Reads an unsigned short value from the stream with a specified number of bits. + + The number of bits to read. + The unsigned short value that was read. + + + + Writes an unsigned short value to the stream. + + The unsigned short value to write. + + + + Reads an unsigned short value from the stream. + + The unsigned short value that was read. + + + + Writes a short value to the stream with a specified number of bits. + + The short value to write. + The number of bits to write. + + + + Reads a short value from the stream with a specified number of bits. + + The number of bits to read. + The short value that was read. + + + + Writes a short value to the stream. + + The short value to write. + + + + Reads a short value from the stream. + + The short value that was read. + + + + Writes a character value to the stream. + + The character value to write. + + + + Reads a character value from the stream. + + The character value that was read. + + + + Writes an unsigned 32-bit integer value to the stream with a specified number of bits. + + The unsigned 32-bit integer value to write. + The number of bits to write. + + + + Reads an unsigned 32-bit integer value from the stream with a specified number of bits. + + The number of bits to read. + The unsigned 32-bit integer value that was read. + + + + Writes an unsigned 32-bit integer value to the stream. + + The unsigned 32-bit integer value to write. + + + + Reads an unsigned 32-bit integer value from the stream. + + The unsigned 32-bit integer value that was read. + + + + Writes a shifted 32-bit integer value to the stream with a specified number of bits. + + The 32-bit integer value to write. + The number of bits to write. + The number of bits to shift. + + + + Reads a shifted 32-bit integer value from the stream with a specified number of bits. + + The number of bits to read. + The number of bits to shift. + The 32-bit integer value that was read. + + + + Writes a 32-bit integer value to the stream with a specified number of bits. + + The 32-bit integer value to write. + The number of bits to write. + + + + Reads a 32-bit integer value from the stream with a specified number of bits. + + The number of bits to read. + The 32-bit integer value that was read. + + + + Writes a 32-bit integer value to the stream. + + The 32-bit integer value to write. + + + + Reads a 32-bit integer value from the stream. + + The 32-bit integer value that was read. + + + + Writes an unsigned 64-bit integer value to the stream with a specified number of bits. + + The unsigned 64-bit integer value to write. + The number of bits to write. + + + + Reads an unsigned 64-bit integer value from the stream with a specified number of bits. + + The number of bits to read. + The unsigned 64-bit integer value that was read. + + + + Writes an unsigned 64-bit integer value to the stream. + + The unsigned 64-bit integer value to write. + + + + Reads an unsigned 64-bit integer value from the stream. + + The unsigned 64-bit integer value that was read. + + + + Writes a signed 64-bit integer value to the stream with a specified number of bits. + + The signed 64-bit integer value to write. + The number of bits to write. + + + + Reads a signed 64-bit integer value from the stream with a specified number of bits. + + The number of bits to read. + The signed 64-bit integer value that was read. + + + + Writes a signed 64-bit integer value to the stream. + + The signed 64-bit integer value to write. + + + + Reads a signed 64-bit integer value from the stream. + + The signed 64-bit integer value that was read. + + + + Writes a 32-bit floating point value to the stream. + + The 32-bit floating point value to write. + + + + Reads a 32-bit floating point value from the stream. + + The 32-bit floating point value that was read. + + + + Writes a 64-bit floating point value to the stream. + + The 64-bit floating point value to write. + + + + Reads a 64-bit floating point value from the stream. + + The 64-bit floating point value that was read. + + + + Writes a byte array to the stream. + + The byte array to write. + + + + Writes a specified number of bytes from a byte array to the stream. + + The byte array to write from. + The number of bytes to write. + + + + Writes a specified number of bytes from a byte array to the stream starting at a given offset. + + The byte array to write from. + The starting offset in the byte array. + The number of bytes to write. + + + + Reads a specified number of bytes from the stream into a new byte array. + + The number of bytes to read. + A byte array containing the read bytes. + + + + Reads bytes from the stream into the specified byte array. + + The byte array to read into. + + + + Reads a specified number of bytes from the stream into the specified byte array. + + The byte array to read into. + The number of bytes to read. + + + + Reads a specified number of bytes from the stream into the specified byte array starting at a given offset. + + The byte array to read into. + The starting offset in the byte array. + The number of bytes to read. + + + + Writes a byte array to the stream with a length prefix. + + The byte array to write. + + + + Writes a byte array to the stream with a length prefix and a specified maximum length. + + The byte array to write. + The maximum length of the byte array to write. + + + + Reads a byte array from the stream with a length prefix. + + A byte array containing the read bytes, or null if the length prefix indicates no data. + + + + Writes a string to the stream using the specified encoding. + + The string to write. + The encoding to use for the string. + + + + Writes a string to the stream using UTF-8 encoding. + + The string to write. + + + + Reads a string from the stream using the specified encoding. + + The encoding to use for the string. + The string that was read, or null if the length prefix indicates no data. + + + + Reads a string from the stream using UTF-8 encoding. + + The string that was read, or null if the length prefix indicates no data. + + + + Writes a GUID to the stream. + + The GUID to write. + + + + Reads a GUID from the stream. + + The GUID that was read. + + + + Writes a byte value at a specified bit position in a byte array. + + The byte array to write to. + The bit position to start writing at. + The number of bits to write. + The byte value to write. + + + + Serialize a value. + + The value type. + + + + Evaluates a condition and serializes it if in write mode, or deserializes it if in read mode. + + The condition to evaluate and serialize/deserialize. + The evaluated condition. + + + + Serializes or deserializes a string value. + + The string value to serialize/deserialize. + + + + Serializes or deserializes a boolean value. + + The boolean value to serialize/deserialize. + + + + Serializes or deserializes a float value. + + The float value to serialize/deserialize. + + + + Serializes or deserializes a double value. + + The double value to serialize/deserialize. + + + + Serializes or deserializes a 64-bit integer value. + + The 64-bit integer value to serialize/deserialize. + + + + Serializes or deserializes a 64-bit unsigned integer value. + + The 64-bit unsigned integer value to serialize/deserialize. + + + + Serializes or deserializes a byte value. + + The byte value to serialize/deserialize. + + + + Serializes or deserializes a 32-bit unsigned integer value. + + The 32-bit unsigned integer value to serialize/deserialize. + + + + Serializes or deserializes a 32-bit unsigned integer value with a specified number of bits. + + The 32-bit unsigned integer value to serialize/deserialize. + The number of bits to use for serialization/deserialization. + + + + Serializes or deserializes a 64-bit unsigned integer value with a specified number of bits. + + The 64-bit unsigned integer value to serialize/deserialize. + The number of bits to use for serialization/deserialization. + + + + Serializes or deserializes a 32-bit integer value. + + The 32-bit integer value to serialize/deserialize. + + + + Serializes or deserializes a 32-bit integer value with a specified number of bits. + + The 32-bit integer value to serialize/deserialize. + The number of bits to use for serialization/deserialization. + + + + Serializes or deserializes an array of 32-bit integers. + + The array of 32-bit integers to serialize/deserialize. + + + + Serializes or deserializes a byte array with a length prefix. + + The byte array to serialize/deserialize. + + + + Serializes or deserializes a byte array with a specified length. + + The byte array to serialize/deserialize. + The length of the byte array. + + + + Serializes or deserializes a byte array with a fixed size. + + The byte array to serialize/deserialize. + The fixed size of the byte array. + + + + Serializes or deserializes a byte array with a specified length and fixed size. + + The byte array to serialize/deserialize. + The length of the byte array. + The fixed size of the byte array. + + + + Serializes or deserializes the length of an array. + + The type of the array elements. + The array whose length to serialize/deserialize. + + + + Serializes or deserializes an array of elements using a specified serializer. + + The type of the array elements. + The array to serialize/deserialize. + The serializer to use for each element. + + + + Serializes or deserializes a byte value. + + The byte value to serialize/deserialize. + + + + Serializes or deserializes a signed byte value. + + The signed byte value to serialize/deserialize. + + + + Serializes or deserializes a short value. + + The short value to serialize/deserialize. + + + + Serializes or deserializes an unsigned short value. + + The unsigned short value to serialize/deserialize. + + + + Serializes or deserializes an integer value. + + The integer value to serialize/deserialize. + + + + Serializes or deserializes an unsigned 32-bit integer value. + + The unsigned 32-bit integer value to serialize/deserialize. + + + + Serializes or deserializes a 64-bit integer value. + + The 64-bit integer value to serialize/deserialize. + + + + Serializes or deserializes an unsigned 64-bit integer value. + + The unsigned 64-bit integer value to serialize/deserialize. + + + + Serializes or deserializes an unsigned 32-bit integer value with a specified number of bits. + + The unsigned 32-bit integer value to serialize/deserialize. + The number of bits to use for serialization/deserialization. + + + + Serializes or deserializes a 32-bit integer value with a specified number of bits. + + The 32-bit integer value to serialize/deserialize. + The number of bits to use for serialization/deserialization. + + + + Serializes or deserializes a buffer of byte values. + + The buffer of byte values to serialize/deserialize. + The length of the buffer. + + + + Serializes or deserializes a buffer of signed byte values. + + The buffer of signed byte values to serialize/deserialize. + The length of the buffer. + + + + Serializes or deserializes a buffer of short values. + + The buffer of short values to serialize/deserialize. + The length of the buffer. + + + + Serializes or deserializes a buffer of unsigned short values. + + The buffer of unsigned short values to serialize/deserialize. + The length of the buffer. + + + + Serializes or deserializes a buffer of 32-bit integer values. + + The buffer of 32-bit integer values to serialize/deserialize. + The length of the buffer. + + + + Serializes or deserializes a buffer of unsigned 32-bit integer values. + + The buffer of unsigned 32-bit integer values to serialize/deserialize. + The length of the buffer. + + + + Serializes or deserializes a buffer of 64-bit integer values. + + The buffer of 64-bit integer values to serialize/deserialize. + The length of the buffer. + + + + Serializes or deserializes a buffer of unsigned 64-bit integer values. + + The buffer of unsigned 64-bit integer values to serialize/deserialize. + The length of the buffer. + + + + Handles Protocol Msgs sent by the Fusion Plugin + + It converts the byte buffer into usable Protocol Msgs + + Sender Actor of the Protocol Msg, generally the Plugin + Object that stores the buffer to be converted + + + + Convert the Data object into a usable Byte Buffer. + How the conversion happens depends on the the Type of Communicator + + + + + Change master client request Message + Used to signal that Fusion Simulation should start + + + + + Player Unique Ref + + + + + Base Protocol Message. + + This concentrates the basics for serialization and cloning + + + + + Max Length of the Custom Data + + + + + Stores the Current Protocol Message version + + + + + Stores the Current Fusion Serialization Version + + + + + Signal if this Message is Valid or not + + + + + Signal if this Message has a valid Protocol Version and Fusion Serialization Version + + + + + Custom data send along side any Protocol Message + + + + + Creates a copy of this Message + + + + + Created a new Message with a certain version + + Protocol Message Version + Fusion Serialization Version + + + + Serialize this Message into or from a . + + Buffer to read from or write into the data of the Message + + + + Used by the specialized versions of Message to serialize its data + + Buffer to read from or write into the data of the Message + + + + Check if this Message is compatible with target versions + + Target Protocol Message Version + Plugin Version + Target Fusion Serialization Version + True if message is compatible with versions + + + + Disconnect Protocol Message. + + Used to signal a peer that it will be disconnected from Photon Cloud + + + + + Disconnect Reason + + + + + Disconnect Protocol Message + + + + + Disconnect Protocol Message + + The reason for the disconnection. + The version of the protocol to be used. Defaults to the latest protocol version. + The version of the serialization to be used. Defaults to null. + + + + List all Disconnect reason used by the Plugin to remove an Actor from the Room + + + + + No reason + + + + + Abstract disconnect reason + + + + + Used when an event with other code other then the treated ones is received by the plugin + + + + + When the Join Message is not of the Request Type + + + + + When the Join Message does not contain a valid Game Mode + + + + + When any of the major settings of a message does not align with the current settings, + like GameMode, Protocol Version, Serialization Version and Peer Mode + + + + + When there is already a Server running on the current Room + + + + + An error occured on the Plugin + + + + + Dummy Traffic Send Interval + + + + + Dummy Traffic Size + + + + + Flag to signal if the Dummy Traffic is valid + + + + + Local Peer Mode + + + + + message Type + + + + + Sent by Peer to Request to Join on the Plugin + + + + + Sent by the Plugin to confirm the Join of a Peer + + + + + Type of Peer which the Peer is starting as + + + + + No Mode Selected, means Invalid + + + + + Server Mode + + + + + Client Mode + + + + + Join Requests sent by the Plugin to request data from the Peer + + + + + No request in the Join Message + + + + + Request the Network Config + + + + + Request for Reflexive Information + + + + + Request to Disable NAT Punch + + + + + Join Message + + It is used to join a Fusion Room Session with extra information about the Peer. + This is unrelated to the Join Operation into a Photon Room. + + + + + Join Message Type + + + + + Requested Plugin Game Mode + + + + + Local Peer Mode + + + + + Requests sent from Plugin + + + + + Peer Unique ID + + + + + Player Unique Ref + + + + + Peer Encryption Key + + + + + Peer Encryption Key + + + + + Network Config Msg Type + + + + + Request Network Config + + + + + Response to a Request + + + + + Override Signal for the Network Config + + + + + NetworkConfig Protocol Msgs + It is used to serialize the Fusion NetworkConfig and send to Photon Cloud Plugin + + + + + Network Config Type + + + + + JSON Serialized NetworkConfig + + + + + Reflexive Info Msgs + + Used to transport information about the Reflexive Addresses of a Peer + + + + + Actor ID to which this info is related + + + + + Peer Public Address + + + + + Peer Private Address + + + + + Peer NAT Type + + + + + Signal if this Reflexive Info is Valid or not + + + + + Peer Unique ID + + + + + Snapshot Message Type + + + + + Invalid/Empty Type + + + + + Base Snapshot + + + + + Confirmation sent by the Plugin + + + + + State Snapshot Protocol Msgs + + Used to sync the current Server Game State with the Photon Cloud Plugin + in order to perform an eventual Host Migration + + + + + Tick to which this Snapshot represents + + + + + Last NetworkID from the Server + + + + + Snapshot Type + + + + + Snapshot Total number of bytes stored or expected to be stored on the Snapshot + + + + + Check if the Snapshot has a valid Data based on the expected CRC + + True if the Snapshot has a valid data + + + + Get Snapshot internal Data Buffer + + Internal Data Buffer + + + + CRC Hash based on the content of the internal data or the expected CRC after all fragments are computed + + + + + Computes the CRC64 of the current Buffer Data stored on the Snapshot + + + + + Create a clone with this Snapshot and reset reference + + + + + Start Message Requests + + + + + No Requests + + + + + Peer should connect to Shared Server + + + + + Peer should wait for the Server Reflexive Info + + + + + Start Protocol Msgs + Used to signal that Fusion Simulation should start + + + + + Actor ID of the Remote Server + + + + + Start Requests + + + + + Defines the Mode the Plugin should run + + + + + No Game Mode set + + + + + Client Server Game Mode + + The Plugin will act just as a relay, exchanging data between the peers. + + + + + Shared Game Mode + + The Plugin will act as a Fusion Server and will accept remote connections + + + + + Photon Event Codes used by the Fusion to communicate with the Photon Cloud + + + + + Protocol Event Code + + + + + Data Event Code + + + + + Dummy Event Code + + + + + Zero (0) means: if it should be the room itself (authorative event). + + + + + + + Default CustomData Key of Realtime Events + + + + + + + Protocol Messages Serializer + + + + + Serialize a Protocol Message into a BitStream + + Protocol Message to be serialized + BitStream containing the Protocol Message + True if the Protocol Message was serialized + + + + Deserialize a Protocol Message from a BitStream + + Stream containing a Protocol Message + Deserialized Protocol Message + True if a Protocol Message was deserialized + + + + Invalid Version + + + + + Initial Version + + + + + Added Support to Fusion Serialization Version + + + + + Added Custom Data to all Protocol Messages + + + + + Added NAT Type to Reflexive Info + + + + + Added Host Migration Support + + + + + Added Peer Unique Key + + + + + Added Join Message PlayerRef + + + + + Rework Host Migration + + + + + Added Encryption Support + + + + + Added Dummy Traffic Support + + + + + Always points to the Latest version + + + + diff --git a/Assets/Photon/Fusion/Assemblies/Fusion.Sockets.xml.meta b/Assets/Photon/Fusion/Assemblies/Fusion.Sockets.xml.meta new file mode 100644 index 00000000..bda64c5f --- /dev/null +++ b/Assets/Photon/Fusion/Assemblies/Fusion.Sockets.xml.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: d77bef94dfcb61b44b1b809af11e53ed +labels: +- FusionCodeDoc +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Assemblies/Release.meta b/Assets/Photon/Fusion/Assemblies/Release.meta new file mode 100644 index 00000000..470622f4 --- /dev/null +++ b/Assets/Photon/Fusion/Assemblies/Release.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ad677f39f9d15f746a4df26e67a7b402 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Assemblies/Release/Fusion.Common.dll.release b/Assets/Photon/Fusion/Assemblies/Release/Fusion.Common.dll.release new file mode 100644 index 00000000..97a922ae Binary files /dev/null and b/Assets/Photon/Fusion/Assemblies/Release/Fusion.Common.dll.release differ diff --git a/Assets/Photon/Fusion/Assemblies/Release/Fusion.Common.dll.release.meta b/Assets/Photon/Fusion/Assemblies/Release/Fusion.Common.dll.release.meta new file mode 100644 index 00000000..b3c92633 --- /dev/null +++ b/Assets/Photon/Fusion/Assemblies/Release/Fusion.Common.dll.release.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: e88931f866d341247bdd75e9663acab7 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Assemblies/Release/Fusion.Log.dll.release b/Assets/Photon/Fusion/Assemblies/Release/Fusion.Log.dll.release new file mode 100644 index 00000000..e0cdc000 Binary files /dev/null and b/Assets/Photon/Fusion/Assemblies/Release/Fusion.Log.dll.release differ diff --git a/Assets/Photon/Fusion/Assemblies/Release/Fusion.Log.dll.release.meta b/Assets/Photon/Fusion/Assemblies/Release/Fusion.Log.dll.release.meta new file mode 100644 index 00000000..e8cbe451 --- /dev/null +++ b/Assets/Photon/Fusion/Assemblies/Release/Fusion.Log.dll.release.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 5824a7f6c259369499f815fa20f73256 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Assemblies/Release/Fusion.Realtime.dll.release b/Assets/Photon/Fusion/Assemblies/Release/Fusion.Realtime.dll.release new file mode 100644 index 00000000..4711653d Binary files /dev/null and b/Assets/Photon/Fusion/Assemblies/Release/Fusion.Realtime.dll.release differ diff --git a/Assets/Photon/Fusion/Assemblies/Release/Fusion.Realtime.dll.release.meta b/Assets/Photon/Fusion/Assemblies/Release/Fusion.Realtime.dll.release.meta new file mode 100644 index 00000000..5c8570b8 --- /dev/null +++ b/Assets/Photon/Fusion/Assemblies/Release/Fusion.Realtime.dll.release.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: f9b150e50c4049e469c1f2177288065c +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Assemblies/Release/Fusion.Runtime.dll.release b/Assets/Photon/Fusion/Assemblies/Release/Fusion.Runtime.dll.release new file mode 100644 index 00000000..48882db0 Binary files /dev/null and b/Assets/Photon/Fusion/Assemblies/Release/Fusion.Runtime.dll.release differ diff --git a/Assets/Photon/Fusion/Assemblies/Release/Fusion.Runtime.dll.release.meta b/Assets/Photon/Fusion/Assemblies/Release/Fusion.Runtime.dll.release.meta new file mode 100644 index 00000000..9b7c45a8 --- /dev/null +++ b/Assets/Photon/Fusion/Assemblies/Release/Fusion.Runtime.dll.release.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 7ae4310552d54ab458f82d7ae21ca757 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Assemblies/Release/Fusion.Sockets.dll.release b/Assets/Photon/Fusion/Assemblies/Release/Fusion.Sockets.dll.release new file mode 100644 index 00000000..6f027074 Binary files /dev/null and b/Assets/Photon/Fusion/Assemblies/Release/Fusion.Sockets.dll.release differ diff --git a/Assets/Photon/Fusion/Assemblies/Release/Fusion.Sockets.dll.release.meta b/Assets/Photon/Fusion/Assemblies/Release/Fusion.Sockets.dll.release.meta new file mode 100644 index 00000000..8c011903 --- /dev/null +++ b/Assets/Photon/Fusion/Assemblies/Release/Fusion.Sockets.dll.release.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 9a2ec6b90eeba1c47b380c9776d70a18 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/CodeGen.meta b/Assets/Photon/Fusion/CodeGen.meta new file mode 100644 index 00000000..4759b1dc --- /dev/null +++ b/Assets/Photon/Fusion/CodeGen.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: aac8de19e8d82d844b4a99a89c3b26a2 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/CodeGen/Fusion.CodeGen.Trigger.fusionweavertrigger b/Assets/Photon/Fusion/CodeGen/Fusion.CodeGen.Trigger.fusionweavertrigger new file mode 100644 index 00000000..e69de29b diff --git a/Assets/Photon/Fusion/CodeGen/Fusion.CodeGen.Trigger.fusionweavertrigger.meta b/Assets/Photon/Fusion/CodeGen/Fusion.CodeGen.Trigger.fusionweavertrigger.meta new file mode 100644 index 00000000..9dec7f5f --- /dev/null +++ b/Assets/Photon/Fusion/CodeGen/Fusion.CodeGen.Trigger.fusionweavertrigger.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f4bd6c24648e05c4fa838da70f847a76 +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 11500000, guid: ad51894a651725c42acc54ccd6a81282, type: 3} + RunWeaverOnConfigChanges: 1 diff --git a/Assets/Photon/Fusion/CodeGen/Fusion.CodeGen.User.cs b/Assets/Photon/Fusion/CodeGen/Fusion.CodeGen.User.cs new file mode 100644 index 00000000..cf40aefa --- /dev/null +++ b/Assets/Photon/Fusion/CodeGen/Fusion.CodeGen.User.cs @@ -0,0 +1,8 @@ +#if FUSION_WEAVER && FUSION_WEAVER_ILPOSTPROCESSOR +namespace Fusion.CodeGen { + partial class ILWeaverSettings { + static partial void OverrideNetworkProjectConfigPath(ref string path) { + } + } +} +#endif \ No newline at end of file diff --git a/Assets/Photon/Fusion/CodeGen/Fusion.CodeGen.User.cs.meta b/Assets/Photon/Fusion/CodeGen/Fusion.CodeGen.User.cs.meta new file mode 100644 index 00000000..b6f752ef --- /dev/null +++ b/Assets/Photon/Fusion/CodeGen/Fusion.CodeGen.User.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5f9faa3ffc014c941995a81138385040 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/CodeGen/Fusion.CodeGen.asmdef b/Assets/Photon/Fusion/CodeGen/Fusion.CodeGen.asmdef new file mode 100644 index 00000000..adb4f645 --- /dev/null +++ b/Assets/Photon/Fusion/CodeGen/Fusion.CodeGen.asmdef @@ -0,0 +1,48 @@ +{ + "name": "Unity.Fusion.CodeGen", + "rootNamespace": "Fusion.CodeGen", + "references": [], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": true, + "overrideReferences": true, + "precompiledReferences": [ + "Mono.Cecil.dll", + "Mono.Cecil.Rocks.dll", + "Mono.Cecil.Pdb.dll", + "Fusion.Runtime.dll", + "Fusion.Common.dll", + "Fusion.Realtime.dll", + "Fusion.Sockets.dll", + "Fusion.Log.dll" + ], + "autoReferenced": false, + "defineConstraints": [ + "FUSION_WEAVER" + ], + "versionDefines": [ + { + "name": "Unity", + "expression": "0", + "define": "FUSION_WEAVER_ILPOSTPROCESSOR" + }, + { + "name": "com.unity.nuget.mono-cecil", + "expression": "1", + "define": "FUSION_HAS_MONO_CECIL" + }, + { + "name": "nuget.mono-cecil", + "expression": "0.1", + "define": "FUSION_HAS_MONO_CECIL" + }, + { + "name": "com.unity.nuget.mono-cecil", + "expression": "1.11", + "define": "FUSION_CECIL_1_11_OR_NEWER" + } + ], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Assets/Photon/Fusion/CodeGen/Fusion.CodeGen.asmdef.meta b/Assets/Photon/Fusion/CodeGen/Fusion.CodeGen.asmdef.meta new file mode 100644 index 00000000..c09f525e --- /dev/null +++ b/Assets/Photon/Fusion/CodeGen/Fusion.CodeGen.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: d9d8635f8670c0c44ba7a8140f7ac7b9 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/CodeGen/Fusion.CodeGen.cs b/Assets/Photon/Fusion/CodeGen/Fusion.CodeGen.cs new file mode 100644 index 00000000..49b30206 --- /dev/null +++ b/Assets/Photon/Fusion/CodeGen/Fusion.CodeGen.cs @@ -0,0 +1,7422 @@ +#if !FUSION_DEV + +#region Assets/Photon/Fusion/CodeGen/AssemblyInfo.cs + +[assembly: Fusion.NetworkAssemblyIgnore] + +#endregion + + +#region Assets/Photon/Fusion/CodeGen/ForLoopMacro.cs + +#if FUSION_WEAVER && FUSION_HAS_MONO_CECIL +namespace Fusion.CodeGen { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + using Mono.Cecil; + using Mono.Cecil.Cil; + using Mono.Cecil.Rocks; + using static ILWeaverOpCodes; + using MethodBody = Mono.Cecil.Cil.MethodBody; + + public readonly struct NewArrayWithLengthEqualToOtherArrayOrZero : ILProcessorMacro { + public readonly Action GetArray; + public readonly TypeReference ArrayElementType; + + public NewArrayWithLengthEqualToOtherArrayOrZero(TypeReference arrayElementType, Action getArray) { + GetArray = getArray; + ArrayElementType = arrayElementType; + } + + public void Emit(ILProcessor il) { + var brNotNul = Nop(); + var brNewArr = Nop(); + + il.Append(Dup()); + il.Append(Brtrue_S(brNotNul)); + il.Append(Pop()); + il.Append(Ldc_I4(0)); + il.Append(Br_S(brNewArr)); + + il.Append(brNotNul); + il.Append(Ldlen()); + il.Append(Conv_I4()); + + il.Append(brNewArr); + il.Append(Newarr(ArrayElementType)); + } + } + + public readonly struct GetCollectionCountOrZero : ILProcessorMacro { + public readonly TypeReference CollectionType; + + public GetCollectionCountOrZero(TypeReference collectionType) { + CollectionType = collectionType; + } + + public void Emit(ILProcessor il) { + + var brNotNul = Nop(); + var done = Nop(); + + il.Append(Dup()); + il.Append(Brtrue_S(brNotNul)); + il.Append(Pop()); + il.Append(Ldc_I4(0)); + il.Append(Br_S(done)); + + il.Append(brNotNul); + il.Append(Call(new MethodReference("get_Count", il.Body.Method.Module.TypeSystem.Int32, CollectionType) { HasThis = true })); + + il.Append(done); + } + } + + public readonly struct ForLoopMacro : ILProcessorMacro { + public readonly MethodBody Body; + public readonly Action Generator; + public readonly Action Start; + public readonly Action Stop; + + public ForLoopMacro(MethodBody body, Action generator, Action start, Action stop) { + Body = body; + Generator = generator; + Start = start; + Stop = stop; + } + + public void Emit(ILProcessor il) { + var varId = Body.Variables.Count; + var indexVariable = new VariableDefinition(Body.Method.Module.TypeSystem.Int32); + Body.Variables.Add(indexVariable); + + Start(il); + il.Append(Stloc(Body, varId)); + + var loopConditionStart = Ldloc(Body, varId); + il.Append(Br_S(loopConditionStart)); + { + var loopBodyBegin = il.AppendReturn(Nop()); + Generator(il, indexVariable); + + il.Append(Ldloc(Body, varId)); + il.Append(Ldc_I4(1)); + il.Append(Add()); + il.Append(Stloc(Body, varId)); + + il.Append(loopConditionStart); + Stop(il); + il.Append(Blt_S(loopBodyBegin)); + } + } + } + + public readonly struct DictionaryForEachMacro : ILProcessorMacro { + public readonly MethodBody Body; + public readonly Action Generator; + public readonly TypeReference EnumerableType; + public readonly ModuleDefinition Module; + + public DictionaryForEachMacro(ModuleDefinition module, MethodBody body, Action generator, TypeReference enumerableType) { + Module = module; + Body = body; + Generator = generator; + EnumerableType = enumerableType; + } + + (TypeReference variableType, TypeReference depententType) GetDependentType(Type type, TypeReference provider) { + var enumeratorDef = Module.ImportReference(type).Resolve(); + + var parameterTypeReference0 = new Mono.Cecil.GenericParameter($"!0", provider); + parameterTypeReference0.SetPosition(0); + var parameterTypeReference1 = new Mono.Cecil.GenericParameter($"!1", provider); + parameterTypeReference1.SetPosition(1); + + var returnRef = new GenericInstanceType(Module.ImportReference(enumeratorDef)) { + GenericArguments = { + parameterTypeReference0, + parameterTypeReference1 + } + }; + + var variableTypeRef = Module.ImportReference(enumeratorDef) + .MakeGenericInstanceType(((GenericInstanceType)EnumerableType).GenericArguments.ToArray()); + + return (variableTypeRef, returnRef); + } + + public void Emit(ILProcessor il) { + + var enumeratorType = GetDependentType(typeof(Dictionary<,>.Enumerator), EnumerableType); + var enumeratorVariable = new VariableDefinition(enumeratorType.variableType); + Body.Variables.Add(enumeratorVariable); + + var keyValueType = GetDependentType(typeof(KeyValuePair<,>), enumeratorType.variableType); + var keyValueVariable = new VariableDefinition(keyValueType.variableType); + Body.Variables.Add(keyValueVariable); + + il.Append(Callvirt(new MethodReference("GetEnumerator", enumeratorType.depententType, EnumerableType) { HasThis = true })); + il.Append(Stloc(enumeratorVariable)); + + var moveNextStart = Ldloca(enumeratorVariable); + var getCurrentStart = Ldloca(enumeratorVariable); + + il.Append(Br(moveNextStart)); + + il.Append(getCurrentStart); + il.Append(Callvirt(new MethodReference("get_Current", keyValueType.depententType, enumeratorType.variableType) { HasThis = true })); + il.Append(Stloc(keyValueVariable)); + Generator(il, keyValueVariable); + + il.Append(moveNextStart); + il.Append(Callvirt(new MethodReference("MoveNext", Module.TypeSystem.Boolean, enumeratorType.variableType) { HasThis = true })); + il.Append(Brtrue_S(getCurrentStart)); + + } + } +} +#endif + +#endregion + + +#region Assets/Photon/Fusion/CodeGen/ILMacroStruct.cs + +#if FUSION_WEAVER && FUSION_HAS_MONO_CECIL + +namespace Fusion.CodeGen { + using System; + using Mono.Cecil.Cil; + + internal struct ILMacroStruct : ILProcessorMacro { + Action generator; + Instruction[] instructions; + public static implicit operator ILMacroStruct(Instruction[] instructions) { + if (instructions == null) { + throw new ArgumentNullException(nameof(instructions)); + } + return new ILMacroStruct() { + instructions = instructions + }; + } + + public static implicit operator ILMacroStruct(Action generator) { + if (generator == null) { + throw new ArgumentNullException(nameof(generator)); + } + return new ILMacroStruct() { + generator = generator + }; + } + + public void Emit(ILProcessor il) { + if (generator != null) { + generator(il); + } else { + foreach (var instruction in instructions) { + il.Append(instruction); + } + } + } + } +} + +#endif + +#endregion + + +#region Assets/Photon/Fusion/CodeGen/ILWeaver.Cache.cs + +#if FUSION_WEAVER && FUSION_HAS_MONO_CECIL +namespace Fusion.CodeGen { + + using System; + using System.Linq; + using System.Runtime.CompilerServices; + using Mono.Cecil; + using Mono.Cecil.Cil; + using Mono.Cecil.Rocks; + using static Fusion.CodeGen.ILWeaverOpCodes; + + partial class ILWeaver { + + private TypeReference MakeFixedBuffer(ILWeaverAssembly asm, int wordCount) { + + FieldDefinition CreateFixedBufferField (TypeDefinition type, string fieldName, TypeReference elementType, int elementCount) { + var fixedBufferFieldType = new TypeDefinition("", $"<{fieldName}>e__FixedBuffer", TypeAttributes.SequentialLayout | TypeAttributes.AnsiClass | TypeAttributes.Sealed | TypeAttributes.BeforeFieldInit | TypeAttributes.NestedPublic) { + BaseType = asm.Import(typeof(ValueType)), + PackingSize = 0, + ClassSize = elementCount * elementType.GetPrimitiveSize(), + }; + fixedBufferFieldType.AddAttribute(asm); + fixedBufferFieldType.AddAttribute(asm); + fixedBufferFieldType.AddTo(type); + + var elementField = new FieldDefinition("FixedElementField", FieldAttributes.Public, elementType); + elementField.AddTo(fixedBufferFieldType); + + var field = new FieldDefinition(fieldName, FieldAttributes.Public, fixedBufferFieldType); + field.AddAttribute(asm, elementType, elementCount); + field.AddTo(type); + + return field; + } + + string typeName = $"FixedStorage@{wordCount}"; + var fixedBufferType = asm.CecilAssembly.MainModule.GetType("Fusion.CodeGen", typeName); + if (fixedBufferType == null) { + // fixed buffers could be included directly in structs, but then again it would be impossible to provide a custom drawer; + // that's why there's this proxy struct + var storageType = new TypeDefinition("Fusion.CodeGen", typeName, + TypeAttributes.ExplicitLayout | TypeAttributes.AnsiClass | TypeAttributes.Sealed | TypeAttributes.BeforeFieldInit | TypeAttributes.Serializable, + asm.ValueType); + + storageType.AddTo(asm.CecilAssembly); + storageType.AddInterface(asm); + storageType.AddAttribute(asm, wordCount); + + FieldDefinition bufferField; + if (Allocator.REPLICATE_WORD_SIZE == sizeof(int)) { + bufferField = CreateFixedBufferField(storageType, $"Data", asm.Import(typeof(int)), wordCount); + bufferField.Offset = 0; + + // Unity debugger seems to copy only the first element of a buffer, + // the rest is garbage when inspected; let's add some additional + // fields to help it + for (int i = 1; i < wordCount; ++i) { + var unityDebuggerWorkaroundField = new FieldDefinition($"_{i}", FieldAttributes.Private | FieldAttributes.NotSerialized, asm.Import()); + unityDebuggerWorkaroundField.Offset = Allocator.REPLICATE_WORD_SIZE * i; + unityDebuggerWorkaroundField.AddTo(storageType); + } + + } + + fixedBufferType = storageType; + } + return fixedBufferType; + } + + private string TypeNameToIdentifier(TypeReference type, string prefix) { + string result = type.FullName; + result = result.Replace("`1", ""); + result = result.Replace("`2", ""); + result = result.Replace("`3", ""); + result = result.Replace(".", "_"); + result = prefix + result; + return result; + } + + private TypeDefinition MakeUnitySurrogate(ILWeaverAssembly asm, PropertyDefinition property) { + var type = property.PropertyType; + + GenericInstanceType baseType; + string surrogateName; + + TypeReference dataType; + Instruction initCall = null; + + if (type.IsNetworkDictionary(out var keyType, out var valueType)) { + keyType = asm.Import(keyType); + valueType = asm.Import(valueType); + var keyReaderWriterType = GetOrMakeElementReaderWriter(asm, property.DeclaringType, property, keyType); + var valueReaderWriterType = GetOrMakeElementReaderWriter(asm, property.DeclaringType, property, valueType); + baseType = asm.Import(typeof(Fusion.Internal.UnityDictionarySurrogate<,,,>)).MakeGenericInstanceType(keyType, keyReaderWriterType, valueType, valueReaderWriterType); + surrogateName = "UnityDictionarySurrogate@" + keyReaderWriterType.Name + "@" + valueReaderWriterType.Name; + dataType = TypeReferenceRocks.MakeGenericInstanceType(asm.Import(typeof(SerializableDictionary<,>)), keyType, valueType); + initCall = Call(new GenericInstanceMethod(asm.Import(asm.Import(typeof(SerializableDictionary)).Resolve().GetMethodOrThrow("Create"))) { + GenericArguments = { keyType, valueType } + }); + } else if (type.IsNetworkArray(out var elementType)) { + elementType = asm.Import(elementType); + var readerWriterType = GetOrMakeElementReaderWriter(asm, property.DeclaringType, property, elementType); + baseType = asm.Import(typeof(Fusion.Internal.UnityArraySurrogate<,>)).MakeGenericInstanceType(elementType, readerWriterType); + surrogateName = "UnityArraySurrogate@" + readerWriterType.Name; + dataType = elementType.MakeArrayType(); + initCall = Call(new GenericInstanceMethod(asm.Import(asm.Import(typeof(Array)).Resolve().GetMethodOrThrow("Empty"))) { + GenericArguments = { elementType } + }); + } else if (type.IsNetworkList(out elementType)) { + elementType = asm.Import(elementType); + var readerWriterType = GetOrMakeElementReaderWriter(asm, property.DeclaringType, property, elementType); + baseType = asm.Import(typeof(Fusion.Internal.UnityLinkedListSurrogate<,>)).MakeGenericInstanceType(elementType, readerWriterType); + surrogateName = "UnityLinkedListSurrogate@" + readerWriterType.Name; + dataType = elementType.MakeArrayType(); + initCall = Call(new GenericInstanceMethod(asm.Import(asm.Import(typeof(Array)).Resolve().GetMethodOrThrow("Empty"))) { + GenericArguments = { elementType } + }); + } else { + var readerWriterType = GetOrMakeElementReaderWriter(asm, property.DeclaringType, property, property.PropertyType); + baseType = asm.Import(typeof(Fusion.Internal.UnityValueSurrogate<,>)).MakeGenericInstanceType(property.PropertyType, readerWriterType); + surrogateName = "UnityValueSurrogate@" + readerWriterType.Name; + dataType = property.PropertyType; + } + + int attributesHash = HashCodeUtilities.InitialHash; + VisitPropertyMovableAttributes(property, (ctor, blob) => { + attributesHash = HashCodeUtilities.GetHashDeterministic(ctor.FullName, attributesHash); + attributesHash = HashCodeUtilities.GetHashCodeDeterministic(blob, attributesHash); + }); + if (attributesHash != HashCodeUtilities.InitialHash) { + surrogateName += $"@Attributes_0x{attributesHash:X8}"; + } + + var surrogateType = asm.CecilAssembly.MainModule.GetType("Fusion.CodeGen", surrogateName); + if (surrogateType == null) { + surrogateType = new TypeDefinition("Fusion.CodeGen", surrogateName, + TypeAttributes.NotPublic | TypeAttributes.AnsiClass | TypeAttributes.Serializable | TypeAttributes.BeforeFieldInit, + baseType); + + surrogateType.AddTo(asm.CecilAssembly); + + + var dataProp = new PropertyDefinition("DataProperty", PropertyAttributes.None, dataType); + dataProp.AddTo(surrogateType); + + var dataField = new FieldDefinition("Data", FieldAttributes.Public, dataType); + dataField.AddTo(surrogateType); + + var getMethod = new MethodDefinition($"get_{dataProp.Name}", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.Virtual, dataType); + { + var il = getMethod.Body.GetILProcessor(); + il.Append(Ldarg_0()); + il.Append(Ldfld(dataField)); + il.Append(Ret()); + } + getMethod.AddTo(surrogateType); + + var setMethod = new MethodDefinition($"set_{dataProp.Name}", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.Virtual, asm.Void); + setMethod.Parameters.Add(new ParameterDefinition("value", ParameterAttributes.None, dataType)); + { + var il = setMethod.Body.GetILProcessor(); + il.Append(Ldarg_0()); + il.Append(Ldarg_1()); + il.Append(Stfld(dataField)); + il.Append(Ret()); + } + setMethod.AddTo(surrogateType); + + dataProp.GetMethod = getMethod; + dataProp.SetMethod = setMethod; + + MovePropertyAttributesToBackingField(asm, property, dataField, addSerializeField: false); + + surrogateType.AddDefaultConstructor(il => { + if (initCall != null) { + il.Append(Ldarg_0()); + il.Append(initCall); + il.Append(Stfld(dataField)); + } + }); + } + return surrogateType; + } + + public static TypeReference GetExistingElementReaderWriter(TypeDefinition declaringType, ICustomAttributeProvider member, NetworkTypeInfo typeInfo) { + var result = TryGetExistingElementReaderWriter(declaringType, member, typeInfo); + if (result == null) { + throw new ILWeaverException($"No reader-writer found for {typeInfo.TypeRef}"); + } + return result; + } + + public static TypeReference TryGetExistingElementReaderWriter(TypeReference declaringType, ICustomAttributeProvider member, NetworkTypeInfo typeInfo) { + var module = declaringType.Module; + if (!typeInfo.CanBeUsedInStructs) { + if (!declaringType.Is()) { + throw new ILWeaverException($"{typeInfo.TypeRef} needs wrapping - such types are only supported as NetworkBehaviour properties."); + } + + var interfaceType = module.ImportReference(typeof(IElementReaderWriter<>)).MakeGenericInstanceType(typeInfo.TypeRef); + + // check if the behaviour implements the interface + var currentType = declaringType.Resolve(); + while (currentType != null) { + if (currentType.Interfaces.Any(x => x.InterfaceType.FullName == interfaceType.FullName)) { + // already implemented + return declaringType; + } + currentType = currentType.BaseType?.Resolve(); + } + + return null; + } else { + + switch (typeInfo.TypeRef.FullName) { + case "System.Byte" : return module.ImportReference(typeof(ElementReaderWriterByte)); + case "System.SByte" : return module.ImportReference(typeof(ElementReaderWriterSByte)); + case "System.Int16" : return module.ImportReference(typeof(ElementReaderWriterInt16)); + case "System.UInt16": return module.ImportReference(typeof(ElementReaderWriterUInt16)); + case "System.Int32" : return module.ImportReference(typeof(ElementReaderWriterInt32)); + case "System.UInt32": return module.ImportReference(typeof(ElementReaderWriterUInt32)); + case "System.Int64" : return module.ImportReference(typeof(ElementReaderWriterInt64)); + case "System.UInt64": return module.ImportReference(typeof(ElementReaderWriterUInt64)); + case "System.Single": return module.ImportReference(typeof(ElementReaderWriterSingle)); + case "System.Double": return module.ImportReference(typeof(ElementReaderWriterDouble)); + case "System.Boolean": return module.ImportReference(typeof(ElementReaderWriterBoolean)); + //case "System.String": return Import(); + case "System.Char": return module.ImportReference(typeof(ElementReaderWriterChar)); + case "UnityEngine.Vector2": return module.ImportReference(typeof(ElementReaderWriterVector2)); + case "UnityEngine.Vector3": return module.ImportReference(typeof(ElementReaderWriterVector3)); + case "UnityEngine.Vector4": return module.ImportReference(typeof(ElementReaderWriterVector4)); + + case "Fusion.NetworkBool": return module.ImportReference(typeof(ElementReaderWriterNetworkBool)); + case "Fusion.PlayerRef": return module.ImportReference(typeof(ElementReaderWriterPlayerRef)); + case "Fusion.NetworkId": return module.ImportReference(typeof(ElementReaderWriterNetworkId)); + case "Fusion.NetworkBehaviourId": return module.ImportReference(typeof(ElementReaderWriterNetworkBehaviourId)); + } + + var readerWriterName = "ReaderWriter@" + typeInfo.TypeRef.FullName.Replace(".", "_").Replace("/", "__"); + + if (typeInfo.TryGetCapacity(member, out int capacity)) { + readerWriterName += $"@Capacity_{capacity}"; + } + + return module.GetType("Fusion.CodeGen", readerWriterName); + } + } + + private TypeReference GetOrMakeElementReaderWriter(ILWeaverAssembly asm, TypeReference declaringType, ICustomAttributeProvider member, TypeReference elementType) { + + elementType = asm.Import(elementType); + var typeInfo = TypeRegistry.GetInfo(elementType); + + var existing = TryGetExistingElementReaderWriter(declaringType, member, typeInfo); + + if (existing != null) { + return existing; + } + + + var interfaceType = asm.Import(typeof(IElementReaderWriter<>)).MakeGenericInstanceType(elementType); + + void AddIElementReaderWriterImplementation(TypeDefinition readerWriterType, int elementWordCount, bool isExplicit = false) { + + var dataType = asm.Import(typeof(byte*)); + var indexType = asm.Import(typeof(int)); + + readerWriterType.Interfaces.Add(new InterfaceImplementation(interfaceType)); + + var visibility = isExplicit ? MethodAttributes.Private : MethodAttributes.Public; + var namePrefix = isExplicit ? $"CodeGen@ElementReaderWriter<{elementType.FullName}>." : ""; + + var readMethod = new MethodDefinition($"{namePrefix}Read", + visibility | MethodAttributes.Final | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual, + elementType); + + readMethod.Parameters.Add(new ParameterDefinition("data", ParameterAttributes.None, dataType)); + readMethod.Parameters.Add(new ParameterDefinition("index", ParameterAttributes.None, indexType)); + readMethod.AddAttribute(asm, MethodImplOptions.AggressiveInlining); + readMethod.AddTo(readerWriterType); + + var readRefMethod = new MethodDefinition($"{namePrefix}ReadRef", + visibility | MethodAttributes.Final | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual, + elementType.MakeByReferenceType()); + + readRefMethod.Parameters.Add(new ParameterDefinition("data", ParameterAttributes.None, dataType)); + readRefMethod.Parameters.Add(new ParameterDefinition("index", ParameterAttributes.None, indexType)); + readRefMethod.AddAttribute(asm, MethodImplOptions.AggressiveInlining); + readRefMethod.AddTo(readerWriterType); + + var writeMethod = new MethodDefinition($"{namePrefix}Write", + visibility | MethodAttributes.Final | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual, + asm.Void); + + writeMethod.Parameters.Add(new ParameterDefinition("data", ParameterAttributes.None, dataType)); + writeMethod.Parameters.Add(new ParameterDefinition("index", ParameterAttributes.None, indexType)); + writeMethod.Parameters.Add(new ParameterDefinition("val", ParameterAttributes.None, elementType)); + writeMethod.AddAttribute(asm, MethodImplOptions.AggressiveInlining); + writeMethod.AddTo(readerWriterType); + + var getElementWordCountMethod = new MethodDefinition($"{namePrefix}GetElementWordCount", + visibility | MethodAttributes.Final | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual, + asm.Import(typeof(int))); + + getElementWordCountMethod.AddAttribute(asm, MethodImplOptions.AggressiveInlining); + getElementWordCountMethod.AddTo(readerWriterType); + + var getElementHashCodeMethod = new MethodDefinition($"{namePrefix}GetElementHashCode", + visibility | MethodAttributes.Final | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual, + asm.Import(typeof(int))); + + getElementHashCodeMethod.Parameters.Add(new ParameterDefinition("val", ParameterAttributes.None, elementType)); + getElementHashCodeMethod.AddAttribute(asm, MethodImplOptions.AggressiveInlining); + getElementHashCodeMethod.AddTo(readerWriterType); + + if (isExplicit) { + readMethod.Overrides.Add(interfaceType.GetGenericInstanceMethodOrThrow(nameof(IElementReaderWriter.Read))); + readRefMethod.Overrides.Add(interfaceType.GetGenericInstanceMethodOrThrow(nameof(IElementReaderWriter.ReadRef))); + writeMethod.Overrides.Add(interfaceType.GetGenericInstanceMethodOrThrow(nameof(IElementReaderWriter.Write))); + getElementWordCountMethod.Overrides.Add(interfaceType.GetGenericInstanceMethodOrThrow(nameof(IElementReaderWriter.GetElementWordCount))); + getElementHashCodeMethod.Overrides.Add(interfaceType.GetGenericInstanceMethodOrThrow(nameof(IElementReaderWriter.GetElementHashCode))); + } + + Action addressGetter = il => { + il.Append(Instruction.Create(OpCodes.Ldarg_1)); + il.Append(Instruction.Create(OpCodes.Ldarg_2)); + il.Append(Instruction.Create(OpCodes.Ldc_I4, elementWordCount * Allocator.REPLICATE_WORD_SIZE)); + il.Append(Instruction.Create(OpCodes.Mul)); + il.Append(Instruction.Create(OpCodes.Add)); + }; + + + EmitRead(asm, readMethod.Body.GetILProcessor(), elementType, readerWriterType, member, addressGetter, emitRet: true); + EmitRead(asm, readRefMethod.Body.GetILProcessor(), readRefMethod.ReturnType, readerWriterType, member, addressGetter, emitRet: true, throwForNonUnmanagedRefs: true); + EmitWrite(asm, writeMethod.Body.GetILProcessor(), elementType, readerWriterType, member, addressGetter, OpCodes.Ldarg_3, emitRet: true); + EmitGetHashCode(asm, getElementHashCodeMethod.Body.GetILProcessor(), elementType, readerWriterType, member, addressGetter, + valueGetter: il => { + il.Append(Ldarg_1()); + }, + valueAddrGetter: il => { + il.Append(Ldarga_S(getElementHashCodeMethod.Parameters[0])); + }, + emitRet: true); + + { + var il = getElementWordCountMethod.Body.GetILProcessor(); + il.Append(Ldc_I4(elementWordCount)); + il.Append(Ret()); + } + } + + if (!typeInfo.CanBeUsedInStructs) { + if (!declaringType.Is()) { + throw new ILWeaverException($"{elementType} needs wrapping - such types are only supported as NetworkBehaviour properties."); + } + + // let's add an interface! + var behaviour = declaringType.Resolve(); + var wordCount = typeInfo.GetMemberWordCount(member, behaviour); + + Log.Debug($"Adding interface {behaviour} {interfaceType}"); + AddIElementReaderWriterImplementation(behaviour, wordCount, isExplicit: true); + return behaviour; + } else { + // make there is a built-in reader-writer for this type? + + var readerWriterName = "ReaderWriter@" + elementType.FullName.Replace(".", "_").Replace("/", "__"); + + if (typeInfo.TryGetCapacity(member, out int capacity)) { + readerWriterName += $"@Capacity_{capacity}"; + } + + const string GetInstanceMethodName = "GetInstance"; + + var readerWriterTypeDef = new TypeDefinition("Fusion.CodeGen", readerWriterName, + TypeAttributes.AnsiClass | TypeAttributes.Sealed | TypeAttributes.SequentialLayout | TypeAttributes.BeforeFieldInit, asm.ValueType); + + // without this, VS debugger will crash + readerWriterTypeDef.PackingSize = 0; + readerWriterTypeDef.ClassSize = 1; + + readerWriterTypeDef.AddTo(asm.CecilAssembly); + + var wordCount = typeInfo.GetMemberWordCount(member, readerWriterTypeDef); + AddIElementReaderWriterImplementation(readerWriterTypeDef, wordCount); + + var instanceField = new FieldDefinition("Instance", FieldAttributes.Public | FieldAttributes.Static, interfaceType); + instanceField.AddTo(readerWriterTypeDef); + + var initializeMethod = new MethodDefinition(GetInstanceMethodName, MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig, interfaceType); + initializeMethod.AddAttribute(asm, MethodImplOptions.AggressiveInlining); + initializeMethod.AddTo(readerWriterTypeDef); + + { + var il = initializeMethod.Body.GetILProcessor(); + var loadFld = Ldsfld(instanceField); + + var tmpVar = new VariableDefinition(readerWriterTypeDef); + il.Body.Variables.Add(tmpVar); + + il.Append(Ldsfld(instanceField)); + il.Append(Brtrue_S(loadFld)); + + il.Append(Ldloca_S(tmpVar)); + il.Append(Initobj(readerWriterTypeDef)); + il.Append(Ldloc_0()); + il.Append(Box(readerWriterTypeDef)); + il.Append(Stsfld(instanceField)); + + il.Append(loadFld); + il.Append(Ret()); + + } + + return readerWriterTypeDef; + } + } + + private void EmitElementReaderWriterLoad(ILWeaverAssembly asm, ILProcessor il, TypeReference readerWriterType) { + if (readerWriterType.Is()) { + il.Append(Ldarg_0()); + } else { + var getInstanceMethod = readerWriterType.Resolve().GetMethodOrThrow("GetInstance"); + il.Append(Call(asm.Import(getInstanceMethod))); + } + } + + } +} +#endif + +#endregion + + +#region Assets/Photon/Fusion/CodeGen/ILWeaver.cs + +#if FUSION_WEAVER && FUSION_HAS_MONO_CECIL + +namespace Fusion.CodeGen { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Runtime.CompilerServices; + using Mono.Cecil; + using Mono.Cecil.Cil; + using Mono.Cecil.Rocks; + using Mono.Collections.Generic; + using static Fusion.CodeGen.ILWeaverOpCodes; + using ICustomAttributeProvider = Mono.Cecil.ICustomAttributeProvider; + using MethodAttributes = Mono.Cecil.MethodAttributes; + using ParameterAttributes = Mono.Cecil.ParameterAttributes; + + public unsafe partial class ILWeaver { + + Dictionary _rpcCount = new Dictionary(new MemberReferenceFullNameComparer()); + + + internal readonly ILWeaverLog Log; + internal readonly ILWeaverSettings Settings; + + public ILWeaver(ILWeaverSettings settings, ILWeaverLog log) { + if (log == null) { + throw new ArgumentNullException(nameof(log)); + } + Log = log; + Settings = settings; + } + + public ILWeaver(ILWeaverSettings settings, ILWeaverLogger logger) : this(settings, new ILWeaverLog(logger)) { + } + + private void EnsureTypeRegistry(ILWeaverAssembly asm) { + if (TypeRegistry == null) { + TypeRegistry = new NetworkTypeInfoRegistry(asm.CecilAssembly.MainModule, Settings, Log.Logger, typeRef => CalculateStructWordCount(typeRef)); + } + } + + void InjectPtrNullCheck(ILWeaverAssembly asm, ILProcessor il, PropertyDefinition property) { + if (Settings.NullChecksForNetworkedProperties) { + var nop = Instruction.Create(OpCodes.Nop); + + il.Append(Instruction.Create(OpCodes.Ldarg_0)); + var ptrGetter = asm.NetworkedBehaviour.GetFieldOrThrow(nameof(NetworkBehaviour.Ptr)); + il.Append(Instruction.Create(OpCodes.Ldfld, ptrGetter)); + //var ptrField = asm.NetworkedBehaviour.GetField(nameof(NetworkBehaviour.Ptr)); + //il.Append(Instruction.Create(OpCodes.Ldfld, ptrField)); + il.Append(Instruction.Create(OpCodes.Ldc_I4_0)); + il.Append(Instruction.Create(OpCodes.Conv_U)); + il.Append(Instruction.Create(OpCodes.Ceq)); + il.Append(Instruction.Create(OpCodes.Brfalse, nop)); + + var ctor = typeof(InvalidOperationException).GetConstructors().First(x => x.GetParameters().Length == 1); + var exnCtor = asm.Import(ctor); + + il.Append(Instruction.Create(OpCodes.Ldstr, $"Error when accessing {property.DeclaringType.Name}.{property.Name}. Networked properties can only be accessed when Spawned() has been called.")); + il.Append(Instruction.Create(OpCodes.Newobj, exnCtor)); + il.Append(Instruction.Create(OpCodes.Throw)); + il.Append(nop); + } + } + + void EmitRead(ILWeaverAssembly asm, ILProcessor il, PropertyDefinition property, Action addressGetter) { + EmitRead(asm, il, property.PropertyType, property.DeclaringType, property, addressGetter, true); + } + + void EmitRead(ILWeaverAssembly asm, ILProcessor il, TypeReference type, TypeReference declaringType, ICustomAttributeProvider member, Action addressGetter, bool emitRet = false, bool throwForNonUnmanagedRefs = false) { + // for pointer types we can simply just return the address we loaded on the stack + if (type.IsPointer || type.IsByReference) { + // load address + if (throwForNonUnmanagedRefs == false || TypeRegistry.GetInfo(type.GetElementTypeWithGenerics()).IsTriviallyCopyable) { + addressGetter(il); + } else { + il.Append(Ldstr($"Only supported for trivially copyable types. {type.GetElementTypeWithGenerics()} is not trivially copyable.")); + il.Append(Newobj(asm.Import(typeof(NotSupportedException).GetConstructor(new[] { typeof(string) })))); + il.Append(Throw()); + return; + } + } else { + using (var ctx = new MethodContext(asm, il.Body.Method, addressGetter: addressGetter)) { + ctx.LoadElementReaderWriterImpl = (il, type, member) => { + EmitElementReaderWriterLoad(asm, il, GetOrMakeElementReaderWriter(asm, declaringType, member, type)); + }; + + TypeRegistry.EmitRead(type, il, ctx, member); + } + } + + if (emitRet) { + il.Append(Ret()); + } + } + + void EmitWrite(ILWeaverAssembly asm, ILProcessor il, PropertyDefinition property, Action addressGetter, OpCode valueOpCode) { + EmitWrite(asm, il, property.PropertyType, property.DeclaringType, property, addressGetter, valueOpCode, true); + } + + void EmitWrite(ILWeaverAssembly asm, ILProcessor il, TypeReference type, TypeReference declaringType, ICustomAttributeProvider member, Action addressGetter, OpCode valueOpCode, bool emitRet = false) { + + if (type.IsPointer || type.IsByReference) { + throw new ILWeaverException($"Pointer and reference members are read-only"); + } + + using (var ctx = new MethodContext(asm, il.Body.Method, addressGetter: addressGetter, valueGetter: (il) => il.Append(Instruction.Create(valueOpCode)))) { + ctx.LoadElementReaderWriterImpl = (il, type, member) => { + EmitElementReaderWriterLoad(asm, il, GetOrMakeElementReaderWriter(asm, declaringType, member, type)); + }; + TypeRegistry.EmitWrite(type, il, ctx, member); + if (emitRet) { + il.Append(Ret()); + } + } + } + + void EmitGetHashCode(ILWeaverAssembly asm, ILProcessor il, TypeReference type, TypeReference declaringType, ICustomAttributeProvider member, Action addressGetter, Action valueGetter, Action valueAddrGetter, bool emitRet = false) { + + using (var ctx = new MethodContext(asm, il.Body.Method, addressGetter: null, valueGetter: valueGetter, valueAddrGetter: valueAddrGetter)) { + ctx.LoadElementReaderWriterImpl = (il, type, member) => { + EmitElementReaderWriterLoad(asm, il, GetOrMakeElementReaderWriter(asm, declaringType, member, type)); + }; + TypeRegistry.EmitGetHashCode(type, il, ctx, member); + if (emitRet) { + il.Append(Ret()); + } + } + } + + void ThrowIfPropertyNotEmptyOrCompilerGenerated(PropertyDefinition property) { + Collection instructions; + int idx; + + var getter = property.GetMethod; + var setter = property.SetMethod; + + void ExpectNext(params OpCode[] opCodes) { + foreach (var opCode in opCodes) { + // skip nops + for (; idx < instructions.Count && instructions[idx].OpCode.Equals(OpCodes.Nop); ++idx) { + } + + if (idx >= instructions.Count) { + throw new InvalidOperationException($"Expected {opCode}, but run out of instructions"); + } else if (!instructions[idx].OpCode.Equals(opCode)) { + throw new InvalidOperationException($"Expected {opCode}, got {instructions[idx].OpCode} at {idx}. Full IL: {string.Join(", ", instructions)}"); + } + ++idx; + } + } + + if (getter != null && !getter.TryGetAttribute(out _)) { + instructions = getter.Body.Instructions; + idx = 0; + + bool expectLocalVariable = false; + var returnType = getter.ReturnType; + + switch (returnType.MetadataType) { + case MetadataType.SByte: + case MetadataType.Byte: + case MetadataType.Int16: + case MetadataType.UInt16: + case MetadataType.Int32: + case MetadataType.UInt32: + case MetadataType.Boolean: + case MetadataType.Char: + ExpectNext(OpCodes.Ldc_I4_0); + break; + case MetadataType.Int64: + case MetadataType.UInt64: + ExpectNext(OpCodes.Ldc_I4_0, OpCodes.Conv_I8); + break; + case MetadataType.Single: + ExpectNext(OpCodes.Ldc_R4); + break; + case MetadataType.Double: + ExpectNext(OpCodes.Ldc_R8); + break; + case MetadataType.String: + case MetadataType.Object: + ExpectNext(OpCodes.Ldnull); + break; + default: + expectLocalVariable = true; + ExpectNext(OpCodes.Ldloca_S, OpCodes.Initobj, OpCodes.Ldloc_0); + break; + } + + if (getter.Body.Variables.Count > (expectLocalVariable ? 1 : 0)) { + if (expectLocalVariable) { + ExpectNext(OpCodes.Stloc_1, OpCodes.Br_S, OpCodes.Ldloc_1); + } else { + ExpectNext(OpCodes.Stloc_0, OpCodes.Br_S, OpCodes.Ldloc_0); + } + } + + ExpectNext(OpCodes.Ret); + } + + if (setter != null && !setter.TryGetAttribute(out _)) { + instructions = setter.Body.Instructions; + idx = 0; + ExpectNext(OpCodes.Ret); + } + } + + (MethodDefinition getter, MethodDefinition setter) PreparePropertyForWeaving(PropertyDefinition property) { + + var getter = property.GetMethod; + var setter = property.SetMethod; + + // clear getter + getter.CustomAttributes.Clear(); + getter.Body.Instructions.Clear(); + + // clear setter if it exists + setter?.CustomAttributes?.Clear(); + setter?.Body?.Instructions?.Clear(); + + return (getter, setter); + } + + struct WeavablePropertyMeta { + public string DefaultFieldName; + public FieldDefinition BackingField; + public bool ReatainIL; + public string OnChanged; + } + + bool IsWeavableProperty(PropertyDefinition property, out WeavablePropertyMeta meta) { + if (property.TryGetAttribute(out var attr) == false) { + meta = default; + return false; + } + + // check getter ... it has to exist + var getter = property.GetMethod; + if (getter == null) { + meta = default; + return false; + } + + // check setter ... + var setter = property.SetMethod; + if (setter == null) { + // if it doesn't exist we allow either array or pointer + if (property.PropertyType.IsByReference == false && property.PropertyType.IsPointer == false && !property.PropertyType.IsNetworkCollection()) { + throw new ILWeaverException($"Simple properties need a setter."); + } + } + + if (getter.IsStatic) { + throw new ILWeaverException($"Networked properties can't be static."); + } + + + + // check for backing field ... + if (property.TryGetBackingField(out var backing)) { + var il = attr.Properties.FirstOrDefault(x => x.Name == "RetainIL"); + + if (il.Argument.Value is bool retainIL && retainIL) { + meta = new WeavablePropertyMeta() { + ReatainIL = true + }; + return true; + } + } + + meta = new WeavablePropertyMeta() { + BackingField = backing, + ReatainIL = false, + }; + + attr.TryGetAttributeProperty(nameof(NetworkedAttribute.Default), out meta.DefaultFieldName); + + + return true; + } + + + + void ThrowIfNotRpcCompatible(TypeReference type) { + NetworkTypeInfo typeInfo; + typeInfo = TypeRegistry.GetInfo(type); + if (!typeInfo.CanBeUsedInRpc) { + throw new ArgumentException($"Can't be used in RPC"); + } + } + + MethodReference GetBaseMethodReference(ILWeaverAssembly asm, MethodDefinition overridingDefinition, TypeReference baseType) { + + var baseMethod = new MethodReference(overridingDefinition.Name, overridingDefinition.ReturnType, baseType) { + HasThis = overridingDefinition.HasThis, + ExplicitThis = overridingDefinition.ExplicitThis, + CallingConvention = overridingDefinition.CallingConvention, + }; + + foreach (var parameter in overridingDefinition.Parameters) { + baseMethod.Parameters.Add(new ParameterDefinition(parameter.ParameterType)); + } + + if (baseMethod.DeclaringType is GenericInstanceType genericTypeRef) { + baseMethod = baseMethod.GetCallable(genericTypeRef); + } + + return asm.Import(baseMethod); + } + + string InvokerMethodName(string method, Dictionary nameCache) { + nameCache.TryGetValue(method, out var count); + nameCache[method] = ++count; + return $"{method}@Invoker{(count == 1 ? "" : count.ToString())}"; + } + + bool HasRpcPrefixOrSuffix(MethodDefinition def) { + return def.Name.StartsWith("rpc", StringComparison.OrdinalIgnoreCase) || def.Name.EndsWith("rpc", StringComparison.OrdinalIgnoreCase); + } + + void WeaveRpcs(ILWeaverAssembly asm, TypeDefinition type, bool allowInstanceRpcs = true) { + // rpc list + var rpcs = new List<(MethodDefinition, CustomAttribute)>(); + + + bool hasStaticRpc = false; + + foreach (var rpc in type.Methods) { + if (rpc.TryGetAttribute(out var attr)) { + + if (HasRpcPrefixOrSuffix(rpc) == false) { + throw new ILWeaverException($"{rpc}: name needs to start or end with the \"Rpc\" prefix or suffix"); + } + + if (rpc.IsStatic && rpc.Parameters.FirstOrDefault()?.ParameterType.FullName != asm.NetworkRunner.Reference.FullName) { + throw new ILWeaverException($"{rpc}: Static RPC needs {nameof(NetworkRunner)} as the first parameter"); + } + + hasStaticRpc |= rpc.IsStatic; + + if (!allowInstanceRpcs && !rpc.IsStatic) { + throw new ILWeaverException($"{rpc}: Instance RPCs not allowed for this type"); + } + + foreach (var parameter in rpc.Parameters) { + if (rpc.IsStatic && parameter == rpc.Parameters[0]) { + continue; + } + + if (IsInvokeOnlyParameter(parameter)) { + continue; + } + + var parameterType = parameter.ParameterType.IsArray ? parameter.ParameterType.GetElementTypeWithGenerics() : parameter.ParameterType; + + try { + ThrowIfNotRpcCompatible(parameterType); + } catch (Exception ex) { + throw new ILWeaverException($"{rpc}: parameter {parameter.Name} is not Rpc-compatible.", ex); + } + } + + if (!rpc.ReturnType.Is(asm.RpcInvokeInfo) && !rpc.ReturnType.IsVoid()) { + throw new ILWeaverException($"{rpc}: RPCs can't return a value."); + } + + rpcs.Add((rpc, attr)); + } + } + + if (!rpcs.Any()) { + return; + } + + int instanceRpcKeys = GetInstanceRpcCount(type.BaseType); + + Dictionary invokerNameCounter = new Dictionary(); + + foreach (var (rpc, attr) in rpcs) { + int sources; + int targets; + + if (attr.ConstructorArguments.Count == 2) { + sources = attr.GetAttributeArgument(0); + targets = attr.GetAttributeArgument(1); + } else { + sources = AuthorityMasks.ALL; + targets = AuthorityMasks.ALL; + } + + ParameterDefinition rpcTargetParameter = rpc.Parameters.SingleOrDefault(x => x.HasAttribute()); + if (rpcTargetParameter != null && !rpcTargetParameter.ParameterType.Is()) { + throw new ILWeaverException($"{rpcTargetParameter}: {nameof(RpcTargetAttribute)} can only be used for {nameof(PlayerRef)} type argument"); + } + + attr.TryGetAttributeProperty(nameof(RpcAttribute.InvokeLocal), out var invokeLocal, defaultValue: true); + attr.TryGetAttributeProperty(nameof(RpcAttribute.Channel), out var channel); + attr.TryGetAttributeProperty(nameof(RpcAttribute.TickAligned), out var tickAligned, defaultValue: true); + attr.TryGetAttributeProperty(nameof(RpcAttribute.HostMode), out var hostMode); + + // rpc key + int instanceRpcKey = -1; + var returnsRpcInvokeInfo = rpc.ReturnType.Is(asm.RpcInvokeInfo); + + + using (var ctx = new RpcMethodContext(asm, rpc, rpc.IsStatic)) { + + // local variables + ctx.DataVariable = new VariableDefinition(asm.Import(typeof(byte)).MakePointerType()); + ctx.OffsetVariable = new VariableDefinition(asm.Import(typeof(int))); + var message = new VariableDefinition(asm.SimulationMessage.Reference.MakePointerType()); + VariableDefinition localAuthorityMask = null; + + rpc.Body.Variables.Add(ctx.DataVariable); + rpc.Body.Variables.Add(ctx.OffsetVariable); + rpc.Body.Variables.Add(message); + rpc.Body.InitLocals = true; + + // get il processes and our jump instruction + var il = rpc.Body.GetILProcessor(); + var jmp = Nop(); + var inv = Nop(); + var prepareInv = Nop(); + + Instruction targetedInvokeLocal = null; + + + // instructions for our branch + var ins = new List(); + + if (returnsRpcInvokeInfo) { + // find local variable that's used for return(default); + ctx.RpcInvokeInfoVariable = new VariableDefinition(asm.RpcInvokeInfo); + rpc.Body.Variables.Add(ctx.RpcInvokeInfoVariable); + ins.Add(Ldloca(ctx.RpcInvokeInfoVariable)); + ins.Add(Initobj(ctx.RpcInvokeInfoVariable.VariableType)); + + // fix each ret + var returns = il.Body.Instructions.Where(x => x.OpCode == OpCodes.Ret).ToList(); + foreach (var retInstruction in returns) { + // need to pop the original value and load our new one + il.InsertBefore(retInstruction, Pop()); + il.InsertBefore(retInstruction, Ldloc(ctx.RpcInvokeInfoVariable)); + } + } + + if (rpc.IsStatic) { + ins.Add(Ldsfld(asm.NetworkBehaviourUtils.GetFieldOrThrow(nameof(NetworkBehaviourUtils.InvokeRpc)))); + ins.Add(Brfalse(jmp)); + ins.Add(Ldc_I4(0)); + ins.Add(Stsfld(asm.NetworkBehaviourUtils.GetFieldOrThrow(nameof(NetworkBehaviourUtils.InvokeRpc)))); + } else { + ins.Add(Ldarg_0()); + ins.Add(Ldfld(asm.NetworkedBehaviour.GetFieldOrThrow(nameof(NetworkBehaviour.InvokeRpc)))); + ins.Add(Brfalse(jmp)); + ins.Add(Ldarg_0()); + ins.Add(Ldc_I4(0)); + ins.Add(Stfld(asm.NetworkedBehaviour.GetFieldOrThrow(nameof(NetworkBehaviour.InvokeRpc)))); + } + ins.Add(inv); + + + // insert instruction into method body + var prev = rpc.Body.Instructions[0]; //.OpCode == OpCodes.Nop ? rpc.Body.Instructions[1] : rpc.Body.Instructions[0]; + + for (int i = ins.Count - 1; i >= 0; --i) { + il.InsertBefore(prev, ins[i]); + prev = ins[i]; + } + + // jump target + il.Append(jmp); + + + + var returnInstructions = returnsRpcInvokeInfo + ? new[] { Ldloc(ctx.RpcInvokeInfoVariable), Ret() } + : new[] { Ret() }; + + var ret = returnInstructions.First(); + + // check if runner's ok + if (rpc.IsStatic) { + il.AppendMacro(ctx.LoadRunner()); + var checkDone = Nop(); + il.Append(Brtrue_S(checkDone)); + il.Append(Ldstr(rpc.Parameters[0].Name)); + il.Append(Newobj(typeof(ArgumentNullException).GetConstructor(asm, 1))); + il.Append(Throw()); + il.Append(checkDone); + } else { + il.Append(Ldarg_0()); + il.Append(Call(asm.NetworkBehaviourUtils.GetMethod(nameof(NetworkBehaviourUtils.ThrowIfBehaviourNotInitialized)))); + } + + il.AppendMacro(ctx.SetRpcInvokeInfoStatus(!invokeLocal, RpcLocalInvokeResult.NotInvokableLocally)); + + // if we shouldn't invoke during resim + { + var checkDone = Nop(); + + il.AppendMacro(ctx.LoadRunner()); + + il.Append(Call(asm.NetworkRunner.GetGetterOrThrow("Stage"))); + il.Append(Ldc_I4((int)SimulationStages.Resimulate)); + il.Append(Bne_Un_S(checkDone)); + + il.AppendMacro(ctx.SetRpcInvokeInfoStatus(invokeLocal, RpcLocalInvokeResult.NotInvokableDuringResim)); + il.AppendMacro(ctx.SetRpcInvokeInfoStatus(RpcSendCullResult.NotInvokableDuringResim)); + il.Append(Br(ret)); + + il.Append(checkDone); + } + + if (!rpc.IsStatic) { + localAuthorityMask = new VariableDefinition(asm.Import(typeof(int))); + rpc.Body.Variables.Add(localAuthorityMask); + il.Append(Ldarg_0()); + il.Append(Call(asm.NetworkedBehaviour.GetGetterOrThrow(nameof(NetworkBehaviour.Object)))); + il.Append(Call(asm.NetworkedObject.GetMethod(nameof(NetworkObject.GetLocalAuthorityMask)))); + il.Append(Stloc(localAuthorityMask)); + } + + // check if target is reachable or not + if (rpcTargetParameter != null) { + il.AppendMacro(ctx.LoadRunner()); + + il.Append(Ldarg(rpcTargetParameter)); + il.Append(Call(asm.NetworkRunner.GetMethod(nameof(NetworkRunner.GetRpcTargetStatus)))); + il.Append(Dup()); + + // check for being unreachable + { + var done = Nop(); + il.Append(Ldc_I4((int)RpcTargetStatus.Unreachable)); + il.Append(Bne_Un_S(done)); + + if (!returnsRpcInvokeInfo) { + il.Append(Ldarg(rpcTargetParameter)); + il.Append(Ldstr(rpc.ToString())); + il.Append(Call(asm.NetworkBehaviourUtils.GetMethod(nameof(NetworkBehaviourUtils.NotifyRpcTargetUnreachable)))); + } + + il.Append(Pop()); // pop the GetRpcTargetStatus + + il.AppendMacro(ctx.SetRpcInvokeInfoStatus(invokeLocal, RpcLocalInvokeResult.TargetPlayerIsNotLocal)); + il.AppendMacro(ctx.SetRpcInvokeInfoStatus(RpcSendCullResult.TargetPlayerUnreachable)); + il.Append(Br(ret)); + + il.Append(done); + } + + // check for self + { + il.Append(Ldc_I4((int)RpcTargetStatus.Self)); + if (invokeLocal) { + // straight to the invoke; this will prohibit any sending + Log.Assert(targetedInvokeLocal == null); + targetedInvokeLocal = Nop(); + il.Append(Beq(targetedInvokeLocal)); + il.AppendMacro(ctx.SetRpcInvokeInfoStatus(true, RpcLocalInvokeResult.TargetPlayerIsNotLocal)); + } else { + // will never get called + var checkDone = Nop(); + il.Append(Bne_Un_S(checkDone)); + + if (!returnsRpcInvokeInfo && NetworkRunner.BuildType == NetworkRunner.BuildTypes.Debug) { + il.Append(Ldarg(rpcTargetParameter)); + il.Append(Ldstr(rpc.ToString())); + il.Append(Call(asm.NetworkBehaviourUtils.GetMethod(nameof(NetworkBehaviourUtils.NotifyLocalTargetedRpcCulled)))); + } + + il.AppendMacro(ctx.SetRpcInvokeInfoStatus(RpcSendCullResult.TargetPlayerIsLocalButRpcIsNotInvokableLocally)); + il.Append(Br(ret)); + + il.Append(checkDone); + } + } + } + + // check if sender flags make sense + if (!rpc.IsStatic) { + var checkDone = Nop(); + + il.Append(Ldloc(localAuthorityMask)); + il.Append(Ldc_I4(sources)); + il.Append(And()); + il.Append(Brtrue_S(checkDone)); + + if (!returnsRpcInvokeInfo) { + // source is not valid, notify + il.Append(Ldstr(rpc.ToString())); + il.Append(Ldarg_0()); + il.Append(Call(asm.NetworkedBehaviour.GetGetterOrThrow(nameof(NetworkBehaviour.Object)))); + il.Append(Ldc_I4(sources)); + il.Append(Call(asm.NetworkBehaviourUtils.GetMethod(nameof(NetworkBehaviourUtils.NotifyLocalSimulationNotAllowedToSendRpc)))); + } + + il.AppendMacro(ctx.SetRpcInvokeInfoStatus(invokeLocal, RpcLocalInvokeResult.InsufficientSourceAuthority)); + il.AppendMacro(ctx.SetRpcInvokeInfoStatus(RpcSendCullResult.InsufficientSourceAuthority)); + + il.Append(Br(ret)); + + il.Append(checkDone); + + if (invokeLocal) { + // how about the target? does it match only the local client? + if (targets != 0 && (targets & AuthorityMasks.PROXY) == 0) { + il.Append(Ldloc(localAuthorityMask)); + il.Append(Ldc_I4(targets)); + il.Append(And()); + il.Append(Ldc_I4(targets)); + il.Append(Beq(prepareInv)); + } + } + } + + var messageSizeVar = ctx.CreateVariable(asm.Import()); + { + il.Append(Ldc_I4(RpcHeader.SIZE)); + il.Append(Stloc(messageSizeVar)); + + + for (int i = 0; i < rpc.Parameters.Count; ++i) { + var para = rpc.Parameters[i]; + + if (rpc.IsStatic && i == 0) { + Log.Assert(para.ParameterType.IsSame()); + continue; + } + + if (IsInvokeOnlyParameter(para)) { + continue; + } + if (para == rpcTargetParameter) { + continue; + } + + il.Append(Ldloc(messageSizeVar)); + + using (ctx.ValueGetter(il => il.Append(Ldarg(para)))) { + if (para.ParameterType.IsArray) { + // do nothing + EmitRpcArrayByteSize(il, ctx, para, para.ParameterType.GetElementTypeWithGenerics()); + } else { + TypeRegistry.EmitRpcByteCount(para.ParameterType, il, ctx, para, wordAligned: true); + } + } + + il.Append(Add()); + il.Append(Stloc(messageSizeVar)); + } + } + + // check the size + var sizeOk = Nop(); + il.Append(Ldloc(messageSizeVar)); + il.Append(Call(asm.SimulationMessage.GetMethod(nameof(SimulationMessage.CanAllocateUserPayload)))); + il.Append(Brtrue_S(sizeOk)); + il.AppendMacro(ctx.SetRpcInvokeInfoStatus(invokeLocal, RpcLocalInvokeResult.PayloadSizeExceeded)); + il.AppendMacro(ctx.SetRpcInvokeInfoStatus(RpcSendCullResult.PayloadSizeExceeded)); + if (!returnsRpcInvokeInfo) { + il.Append(Ldstr(rpc.ToString())); + il.Append(Ldloc(messageSizeVar)); + il.Append(Call(asm.NetworkBehaviourUtils.GetMethod(nameof(NetworkBehaviourUtils.NotifyRpcPayloadSizeExceeded)))); + } + il.Append(Br(ret)); + il.Append(sizeOk); + + // check if sending makes sense at all + var afterSend = Nop(); + + // if not targeted (already handled earlier) check if it can be sent at all + if (rpcTargetParameter == null) { + var checkDone = Nop(); + il.AppendMacro(ctx.LoadRunner()); + il.Append(Call(asm.NetworkRunner.GetMethod(nameof(NetworkRunner.HasAnyActiveConnections)))); + il.Append(Brtrue(checkDone)); + il.AppendMacro(ctx.SetRpcInvokeInfoStatus(RpcSendCullResult.NoActiveConnections)); + il.Append(Br(afterSend)); + il.Append(checkDone); + } + + // create simulation message + il.AppendMacro(ctx.LoadRunner()); + il.Append(Call(asm.NetworkRunner.GetGetterOrThrow(nameof(NetworkRunner.Simulation)))); + il.Append(Ldloc(messageSizeVar)); + + il.Append(Call(asm.SimulationMessage.GetMethod(nameof(SimulationMessage.Allocate), 2))); + il.Append(Stloc(message)); + + // get data for messages + il.Append(Ldloc(message)); + il.Append(Ldc_I4(SimulationMessage.SIZE)); + il.Append(Add()); + il.Append(Stloc(ctx.DataVariable)); + + // create RpcHeader + il.Append(Ldloc(ctx.DataVariable)); + + if (rpc.IsStatic) { + il.Append(Ldstr(rpc.ToString())); + il.Append(Call(asm.Import(typeof(NetworkBehaviourUtils).GetMethod(nameof(NetworkBehaviourUtils.GetRpcStaticIndexOrThrow))))); + il.Append(Call(asm.RpcHeader.GetMethod(nameof(RpcHeader.Create), 1))); + } else { + il.Append(Ldarg_0()); + il.Append(Call(asm.NetworkedBehaviour.GetGetterOrThrow(nameof(NetworkBehaviour.Object)))); + il.Append(Call(asm.NetworkedObject.GetGetterOrThrow(nameof(NetworkObject.Id)))); + + il.Append(Ldarg_0()); + il.Append(Ldfld(asm.NetworkedBehaviour.GetFieldOrThrow(nameof(NetworkBehaviour.ObjectIndex)))); + + instanceRpcKey = ++instanceRpcKeys; + il.Append(Ldc_I4(instanceRpcKey)); + il.Append(Call(asm.RpcHeader.GetMethod(nameof(RpcHeader.Create), 3))); + } + + il.Append(Stobj(asm.RpcHeader.Reference)); + il.Append(Ldc_I4(RpcHeader.SIZE)); + il.Append(Stloc(ctx.OffsetVariable)); + + // write parameters + for (int i = 0; i < rpc.Parameters.Count; ++i) { + var para = rpc.Parameters[i]; + + if (rpc.IsStatic && i == 0) { + continue; + } + if (IsInvokeOnlyParameter(para)) { + continue; + } + if (para == rpcTargetParameter) { + continue; + } + + using (ctx.ValueGetter(il => il.Append(Ldarg(para)))) { + if (para.ParameterType.IsArray) { + //WeaveRpcArrayInput(asm, ctx, il, para); + EmitRpcWriteArray(il, ctx, para, para.ParameterType.GetElementTypeWithGenerics()); + } else { + TypeRegistry.EmitWrite(para.ParameterType, il, ctx, para); + } + } + } + + // update message offset + il.Append(Ldloc(message)); + il.Append(Ldflda(asm.SimulationMessage.GetFieldOrThrow(nameof(SimulationMessage.Offset)))); + il.Append(Ldloc(ctx.OffsetVariable)); + il.Append(Ldc_I4(8)); + il.Append(Mul()); + il.Append(Stind_I4()); + + // send message + + il.AppendMacro(ctx.LoadRunner()); + + if (rpcTargetParameter != null) { + il.Append(Ldloc(message)); + il.Append(Ldarg(rpcTargetParameter)); + il.Append(Call(asm.SimulationMessage.GetMethod(nameof(SimulationMessage.SetTarget)))); + } + + if (channel == RpcChannel.Unreliable) { + il.Append(Ldloc(message)); + il.Append(Call(asm.SimulationMessage.GetMethod(nameof(SimulationMessage.SetUnreliable)))); + } + + if (!tickAligned) { + il.Append(Ldloc(message)); + il.Append(Call(asm.SimulationMessage.GetMethod(nameof(SimulationMessage.SetNotTickAligned)))); + } + + if (rpc.IsStatic) { + il.Append(Ldloc(message)); + il.Append(Call(asm.SimulationMessage.GetMethod(nameof(SimulationMessage.SetStatic)))); + } + + // send the rpc + il.Append(Ldloc(message)); + + if (ctx.RpcInvokeInfoVariable != null) { + il.Append(Ldloca(ctx.RpcInvokeInfoVariable)); + il.Append(Ldflda(asm.RpcInvokeInfo.GetFieldOrThrow(nameof(RpcInvokeInfo.SendResult)))); + il.Append(Call(asm.NetworkRunner.GetMethod(nameof(NetworkRunner.SendRpc), 2))); + } else { + il.Append(Call(asm.NetworkRunner.GetMethod(nameof(NetworkRunner.SendRpc), 1))); + } + + il.AppendMacro(ctx.SetRpcInvokeInfoStatus(RpcSendCullResult.NotCulled)); + + il.Append(afterSend); + + // .. hmm + if (invokeLocal) { + + if (targetedInvokeLocal != null) { + il.Append(Br(ret)); + il.Append(targetedInvokeLocal); + } + + if (!rpc.IsStatic) { + var checkDone = Nop(); + il.Append(Ldloc(localAuthorityMask)); + il.Append(Ldc_I4(targets)); + il.Append(And()); + il.Append(Brtrue_S(checkDone)); + + il.AppendMacro(ctx.SetRpcInvokeInfoStatus(true, RpcLocalInvokeResult.InsufficientTargetAuthority)); + + il.Append(Br(ret)); + + il.Append(checkDone); + } + + il.Append(prepareInv); + + foreach (var param in rpc.Parameters) { + if (param.ParameterType.IsSame()) { + // need to fill it now + il.AppendMacro(ctx.LoadRunner()); + il.Append(Ldc_I4((int)channel)); + il.Append(Ldc_I4((int)hostMode)); + il.Append(Call(asm.RpcInfo.GetMethod(nameof(RpcInfo.FromLocal)))); + il.Append(Starg_S(param)); + } + } + + il.AppendMacro(ctx.SetRpcInvokeInfoStatus(true, RpcLocalInvokeResult.Invoked)); + + // invoke + il.Append(Br(inv)); + } + + foreach (var instruction in returnInstructions) { + il.Append(instruction); + } + } + + var invoker = new MethodDefinition(InvokerMethodName(rpc.Name, invokerNameCounter), MethodAttributes.Family | MethodAttributes.Static, asm.Import(typeof(void))); + using (var ctx = new RpcMethodContext(asm, invoker, rpc.IsStatic)) { + + // create invoker delegate + if (rpc.IsStatic) { + var runner = new ParameterDefinition("runner", ParameterAttributes.None, asm.NetworkRunner.Reference); + invoker.Parameters.Add(runner); + } else { + var behaviour = new ParameterDefinition("behaviour", ParameterAttributes.None, asm.NetworkedBehaviour.Reference); + invoker.Parameters.Add(behaviour); + } + var message = new ParameterDefinition("message", ParameterAttributes.None, asm.SimulationMessage.Reference.MakePointerType()); + invoker.Parameters.Add(message); + + // add attribute + if (rpc.IsStatic) { + Log.Assert(instanceRpcKey < 0); + invoker.AddAttribute(asm, rpc.ToString()); + } else { + Log.Assert(instanceRpcKey >= 0); + invoker.AddAttribute(asm, instanceRpcKey, sources, targets); + } + +#if UNITY_EDITOR + invoker.AddAttribute(asm); +#endif + + // put on type + invoker.AddTo(type); + + // local variables + ctx.DataVariable = new VariableDefinition(asm.Import(typeof(byte)).MakePointerType()); + ctx.OffsetVariable = new VariableDefinition(asm.Import(typeof(int))); + var parameters = new VariableDefinition[rpc.Parameters.Count]; + + for (int i = 0; i < parameters.Length; ++i) { + invoker.Body.Variables.Add(parameters[i] = new VariableDefinition(rpc.Parameters[i].ParameterType)); + } + + invoker.Body.Variables.Add(ctx.DataVariable); + invoker.Body.Variables.Add(ctx.OffsetVariable); + invoker.Body.InitLocals = true; + + var il = invoker.Body.GetILProcessor(); + + // grab data from message and store in local + il.Append(Ldarg_1()); + il.Append(Ldc_I4(SimulationMessage.SIZE)); + il.Append(Add()); + il.Append(Stloc(ctx.DataVariable)); + + il.Append(Ldc_I4(RpcHeader.SIZE)); + il.Append(Stloc(ctx.OffsetVariable)); + + for (int i = 0; i < parameters.Length; ++i) { + var para = parameters[i]; + + if (rpc.IsStatic && i == 0) { + il.Append(Ldarg_0()); + il.Append(Stloc(para)); + continue; + } + + if (rpcTargetParameter == rpc.Parameters[i]) { + il.Append(Ldarg_1()); + il.Append(Ldfld(asm.SimulationMessage.GetFieldOrThrow(nameof(SimulationMessage.Target)))); + il.Append(Stloc(para)); + } else if (para.VariableType.IsSame()) { + il.AppendMacro(ctx.LoadRunner()); + il.Append(Ldarg_1()); + il.Append(Ldc_I4((int)hostMode)); + il.Append(Call(asm.RpcInfo.GetMethod(nameof(RpcInfo.FromMessage)))); + il.Append(Stloc(para)); + } else if (para.VariableType.IsArray) { + EmitRpcReadArray(il, ctx, rpc.Parameters[i], para.VariableType.GetElementTypeWithGenerics(), para); + } else { + using (ctx.TargetVariableAddr(para)) { + TypeRegistry.EmitRead(para.VariableType, il, ctx, rpc.Parameters[i]); + if (!ctx.TargetAddrUsed) { + il.Append(Stloc(para)); + } + } + } + } + + if (rpc.IsStatic) { + il.Append(Ldc_I4(1)); + il.Append(Stsfld(asm.NetworkBehaviourUtils.GetFieldOrThrow(nameof(NetworkBehaviour.InvokeRpc)))); + } else { + il.Append(Ldarg_0()); + il.Append(Ldc_I4(1)); + il.Append(Stfld(asm.NetworkedBehaviour.GetFieldOrThrow(nameof(NetworkBehaviour.InvokeRpc)))); + } + + var callableRpc = rpc.GetCallable(); + if (!rpc.IsStatic) { + il.Append(Ldarg_0()); + il.Append(Instruction.Create(OpCodes.Castclass, callableRpc.DeclaringType)); + } + + for (int i = 0; i < parameters.Length; ++i) { + il.Append(Ldloc(parameters[i])); + } + il.Append(Call(callableRpc)); + if (returnsRpcInvokeInfo) { + il.Append(Pop()); + } + il.Append(Ret()); + } + } + + { + Log.Assert(_rpcCount.TryGetValue(type, out int count) == false || count == instanceRpcKeys); + _rpcCount[type] = instanceRpcKeys; + } + } + + private int GetInstanceRpcCount(TypeReference type) { + if (_rpcCount.TryGetValue(type, out int result)) { + return result; + } + + result = 0; + + var typeDef = type.Resolve(); + + if (typeDef.BaseType != null) { + result += GetInstanceRpcCount(typeDef.BaseType); + } + + result += typeDef.GetMethods() + .Where(x => !x.IsStatic) + .Where(x => x.HasAttribute()) + .Count(); + + _rpcCount.Add(type, result); + return result; + } + + private bool IsInvokeOnlyParameter(ParameterDefinition para) { + if (para.ParameterType.IsSame()) { + return true; + } + return false; + } + + void EmitRpcWriteArray(ILProcessor il, MethodContext context, ICustomAttributeProvider member, TypeReference elementType) { + // store array length + il.AppendMacro(context.LoadAddress()); + il.AppendMacro(context.LoadValue()); + il.Append(Ldlen()); + il.Append(Conv_I4()); + il.Append(Stind_I4()); + il.AppendMacro(context.AddOffset(sizeof(int))); + + if (TypeRegistry.GetInfo(elementType).IsTriviallyCopyable) { + il.AppendMacro(context.LoadAddress()); + il.AppendMacro(context.LoadValue()); + var memCpy = new GenericInstanceMethod(context.Assembly.Native.GetMethod(nameof(Native.CopyFromArray), 2)); + memCpy.GenericArguments.Add(elementType); + il.Append(Call(memCpy)); + il.AppendMacro(context.AddOffset()); + } else { + il.AppendMacro( + context.For( + start: il => il.Append(Ldc_I4(0)), + stop: il => { + il.AppendMacro(context.LoadValue()); + il.Append(Ldlen()); + il.Append(Conv_I4()); + }, + body: (il, i) => { + using (context.ValueGetter((il, old) => { + old(il); + il.Append(Ldloc(i)); + il.Append(Ldelem(elementType)); + })) { + TypeRegistry.EmitWrite(elementType, il, context, member); + } + } + ) + ); + } + } + + void EmitRpcReadArray(ILProcessor il, MethodContext context, ICustomAttributeProvider member, TypeReference elementType, VariableDefinition arrayVar) { + // alloc array + il.AppendMacro(context.LoadAddress()); + il.Append(Ldind_I4()); + il.Append(Instruction.Create(OpCodes.Newarr, elementType)); + il.Append(Stloc(arrayVar)); + il.AppendMacro(context.AddOffset(sizeof(int))); + + if (TypeRegistry.GetInfo(elementType).IsTriviallyCopyable) { + il.Append(Ldloc(arrayVar)); + il.AppendMacro(context.LoadAddress()); + + var memCpy = new GenericInstanceMethod(context.Assembly.Native.GetMethod(nameof(Native.CopyToArray), 2)); + memCpy.GenericArguments.Add(elementType); + il.Append(Call(memCpy)); + il.AppendMacro(context.AddOffset()); + + } else { + il.AppendMacro( + context.For( + start: il => il.Append(Ldc_I4(0)), + stop: il => { + il.Append(Ldloc(arrayVar)); + il.Append(Ldlen()); + il.Append(Conv_I4()); + }, + body: (il, i) => { + var placeholder = Nop(); + il.Append(placeholder); + + using (context.TargetVariableAddr(arrayVar, i, elementType)) { + TypeRegistry.EmitRead(elementType, il, context, member); + if (!context.TargetAddrUsed) { + il.InsertAfter(placeholder, Ldloc(i)); + il.InsertAfter(placeholder, Ldloc(arrayVar)); + il.Append(Stelem(elementType)); + } + } + il.Remove(placeholder); + + } + ) + ); + } + } + + void EmitRpcArrayByteSize(ILProcessor il, MethodContext context, ICustomAttributeProvider member, TypeReference elementType) { + var elementTypeData = TypeRegistry.GetInfo(elementType); + + if (elementTypeData.HasDynamicRpcSize) { + var totalSize = context.CreateVariable(context.Assembly.Import()); + + il.Append(Ldc_I4(sizeof(Int32))); + il.Append(Stloc(totalSize)); + + il.AppendMacro( + context.For( + start: il => il.Append(Ldc_I4(0)), + stop: il => + { + il.AppendMacro(context.LoadValue()); + il.Append(Ldlen()); + il.Append(Conv_I4()); + }, + (il, counter) => { + il.Append(Ldloc(totalSize)); + + using (context.ValueGetter((il, old) => { + old(il); + il.Append(Ldloc(counter)); + il.Append(Ldelem(elementType)); + })) { + TypeRegistry.EmitRpcByteCount(elementType, il, context, member, wordAligned: true); + } + + il.Append(Add()); + il.Append(Stloc(totalSize)); + } + ) + ); + + il.Append(Ldloc(totalSize)); + + } else { + // array length + il.AppendMacro(context.LoadValue()); + il.Append(Ldlen()); + il.Append(Conv_I4()); + TypeRegistry.EmitRpcByteCount(elementType, il, context, member, wordAligned: false); + il.Append(Mul()); + + // store length as well + il.Append(Ldc_I4(sizeof(Int32))); + il.Append(Add()); + + // align + il.AppendMacro(context.AlignToWordSize()); + } + } + + public void WeaveSimulation(ILWeaverAssembly asm, TypeDefinition type) { + EnsureTypeRegistry(asm); + WeaveRpcs(asm, type, allowInstanceRpcs: false); + WeaveUnityMessages(asm, type); + } + + public static bool IsFieldOperand(Instruction instruction, FieldDefinition field, TypeReference declaringType) { + var storeField = instruction.Operand as FieldReference; + if (storeField == null) { + return false; + } + if (storeField == field) { + return true; + } + if (storeField.Name == field.Name && storeField.DeclaringType.Is(declaringType)) { + return true; + } + return false; + } + + private Instruction[] GetInlineFieldInit(MethodDefinition constructor, FieldDefinition field, TypeDefinition declaringType) { + if (field == null) { + throw new ArgumentNullException(nameof(field)); + } + if (constructor == null) { + throw new ArgumentNullException(nameof(constructor)); + } + + var instructions = constructor.Body.Instructions; + + int ldarg0Index = 0; + for (int i = 0; i < instructions.Count; ++i) { + var instruction = instructions[i]; + if (instruction.OpCode == OpCodes.Ldarg_0) { + ldarg0Index = i; + } else if (instruction.OpCode == OpCodes.Stfld && IsFieldOperand(instruction, field, declaringType)) { + // regular init + return instructions.Skip(ldarg0Index).Take(i - ldarg0Index + 1).ToArray(); + } else if (instruction.OpCode == OpCodes.Initobj && instruction.Previous?.OpCode == OpCodes.Ldflda && IsFieldOperand(instruction.Previous, field, declaringType)) { + // init with default constructor + return instructions.Skip(ldarg0Index).Take(i - ldarg0Index + 1).ToArray(); + } else if (instruction.IsBaseConstructorCall(constructor.DeclaringType)) { + // base constructor init + break; + } + } + return Array.Empty(); + } + + private Instruction[] RemoveInlineFieldInit(TypeDefinition type, FieldDefinition field) { + var constructors = type.GetConstructors().Where(x => !x.IsStatic); + if (!constructors.Any()) { + return Array.Empty(); + } + + var firstConstructor = constructors.First(); + var firstInlineInit = GetInlineFieldInit(firstConstructor, field, type).ToArray(); + if (firstInlineInit.Length != 0) { + Log.Debug($"Found {field} inline init: {(string.Join("; ", firstInlineInit.Cast()))}"); + } + + foreach (var constructor in constructors.Skip(1)) { + var otherInlineInit = GetInlineFieldInit(constructor, field, type); + if (!firstInlineInit.SequenceEqual(otherInlineInit, new InstructionEqualityComparer())) { + throw new ILWeaverException($"Expect inline init of {field} to be the same in all constructors," + + $" but there's a difference between {firstConstructor} and {constructor}"); + } + } + + foreach (var constructor in constructors) { + Log.Debug($"Removing inline init of {field} from {constructor}"); + var il = constructor.Body.GetILProcessor(); + var otherInlineInit = GetInlineFieldInit(constructor, field, type); + foreach (var instruction in otherInlineInit.Reverse()) { + Log.Debug($"Removing {instruction}"); + il.Remove(instruction); + } + } + + return firstInlineInit; + } + + private static bool IsMakeInitializerCall(Instruction instruction) { + if (instruction.OpCode == OpCodes.Call && instruction.Operand is MethodReference method) { + if (method.DeclaringType.IsSame() && method.Name == nameof(NetworkBehaviour.MakeInitializer)) { + return true; + } + } + return false; + } + + private static bool IsMakeRefOrMakePtrCall(Instruction instruction) { + if (instruction.OpCode == OpCodes.Call && instruction.Operand is MethodReference method) { + if (method.DeclaringType.IsSame() && ( + method.Name == nameof(NetworkBehaviour.MakeRef) || method.Name == nameof(NetworkBehaviour.MakePtr) + )) { + return true; + } + } + return false; + } + + private void CheckIfMakeInitializerImplicitCast(Instruction instruction) { + if (instruction.OpCode == OpCodes.Call && (instruction.Operand as MethodReference)?.Name == "op_Implicit") { + // all good + } else { + throw new ILWeaverException($"Expected an implicit cast, got {instruction}"); + } + } + + private void ReplaceBackingFieldInInlineInit(ILWeaverAssembly asm, FieldDefinition backingField, FieldReference field, ILProcessor il, Instruction[] instructions) { + bool nextImplicitCast = false; + foreach (var instruction in instructions) { + if (nextImplicitCast) { + CheckIfMakeInitializerImplicitCast(instruction); + nextImplicitCast = false; + il.Remove(instruction); + } else if (IsFieldOperand(instruction, backingField, field.DeclaringType)) { + instruction.Operand = field; + } else if (IsMakeInitializerCall(instruction)) { + // dictionaries need one extra step, if using SerializableDictionary :( + if (Settings.UseSerializableDictionary && backingField.FieldType.IsNetworkDictionary(out var keyType, out var valueType)) { + var m = new GenericInstanceMethod(asm.NetworkBehaviourUtils.GetMethod(nameof(NetworkBehaviourUtils.MakeSerializableDictionary))); + m.GenericArguments.Add(keyType); + m.GenericArguments.Add(valueType); + Log.Debug($"Inline init for {field}, replacing {instruction.Operand} with {m}"); + instruction.Operand = m; + } else { + // remove the op, it will be fine + Log.Debug($"Inline init for {field}, removing {instruction}"); + il.Remove(instruction); + } + nextImplicitCast = true; + } + } + } + + static IEnumerable AllTypeDefs(TypeDefinition definitions) { + yield return definitions; + + if (definitions.HasNestedTypes) { + foreach (var nested in definitions.NestedTypes.SelectMany(AllTypeDefs)) { + yield return nested; + } + } + } + + static IEnumerable AllTypeDefs(Collection definitions) { + return definitions.SelectMany(AllTypeDefs); + } + + public bool Weave(ILWeaverAssembly asm) { + // if we don't have the weaved assembly attribute, we need to do weaving and insert the attribute + if (asm.CecilAssembly.HasAttribute() != false) { + return false; + } + + using (Log.ScopeAssembly(asm.CecilAssembly)) { + // grab main module .. this contains all the types we need + var module = asm.CecilAssembly.MainModule; + var moduleAllTypes = AllTypeDefs(module.Types).ToArray(); + + // go through all types and check for network behaviours + foreach (var t in moduleAllTypes) { + if (t.IsValueType && t.Is()) { + try { + WeaveStruct(asm, t); + } catch (Exception ex) { + throw new ILWeaverException($"Failed to weave struct {t}", ex); + } + } + } + + foreach (var t in moduleAllTypes) { + if (t.IsValueType && t.Is()) { + try { + WeaveInput(asm, t); + } catch (Exception ex) { + throw new ILWeaverException($"Failed to weave input {t}", ex); + } + } + } + + foreach (var t in moduleAllTypes) { + if (t.IsSubclassOf()) { + try { + WeaveBehaviour(asm, t); + } catch (Exception ex) { + throw new ILWeaverException($"Failed to weave behaviour {t}", ex); + } + } else if (t.IsSubclassOf()) { + try { + WeaveSimulation(asm, t); + } catch (Exception ex) { + throw new ILWeaverException($"Failed to weave behaviour {t}", ex); + } + } + } + + if (Settings.CheckRpcAttributeUsage) { + using (Log.Scope("Checking RpcAttribute usage")) { + foreach (var t in moduleAllTypes) { + if (t.IsSubclassOf()) { + continue; + } + foreach (var method in t.Methods) { + if (method.TryGetAttribute(out _)) { + Log.Warn(method, $"Incorrect {nameof(RpcAttribute)} usage on {method}: only types derived from {nameof(SimulationBehaviour)} and {nameof(NetworkBehaviour)} are supported"); + } + } + } + } + } + + // only if it was modified + if (asm.Modified) { + // add weaved assembly attribute to this assembly + asm.CecilAssembly.CustomAttributes.Add(new CustomAttribute(typeof(NetworkAssemblyWeavedAttribute).GetConstructor(asm))); + } + + return asm.Modified; + } + } + } +} +#endif + + +#endregion + + +#region Assets/Photon/Fusion/CodeGen/ILWeaver.INetworkedStruct.cs + +#if FUSION_WEAVER && FUSION_HAS_MONO_CECIL +namespace Fusion.CodeGen { + using System; + using System.Collections.Generic; + using System.Runtime.CompilerServices; + using Mono.Cecil; + using Mono.Cecil.Cil; + using static Fusion.CodeGen.ILWeaverOpCodes; + using FieldAttributes = Mono.Cecil.FieldAttributes; + + unsafe partial class ILWeaver { + + const int WordSize = Allocator.REPLICATE_WORD_SIZE; + NetworkTypeInfoRegistry TypeRegistry; + + public int CalculateStructWordCount(TypeReference typeRef) { + var type = typeRef.Resolve(); + Log.Assert(type.TryGetAttribute(out _) == false); + + var wordCount = 0; + + foreach (var property in type.Properties) { + if (!IsWeavableProperty(property, out var propertyInfo)) { + continue; + } + + try { + wordCount += TypeRegistry.GetPropertyWordCount(property); + } catch (Exception ex) { + throw new ILWeaverException($"Failed to get word count of property {property}", ex); + } + } + + // figure out word counts for everything + foreach (var field in type.Fields) { + // skip statics + if (field.IsStatic) { + continue; + } + + try { + // increase block count + wordCount += TypeRegistry.GetTypeWordCount(field.FieldType); + } catch (Exception ex) { + throw new ILWeaverException($"Failed to get word count of field {field}", ex); + } + } + + return wordCount; + } + + public bool WeaveInput(ILWeaverAssembly asm, TypeReference typeRef) { + ILWeaverException.DebugThrowIf(!typeRef.Is(), $"Not a {nameof(INetworkInput)}"); + + + if (typeRef.Module != asm.CecilAssembly.MainModule) { + throw new ILWeaverException($"Type {typeRef} is not in the main module of assembly {asm.CecilAssembly}"); + } + + var type = typeRef.Resolve(); + if (type.TryGetAttribute(out _)) { + return false; + } + + EnsureTypeRegistry(asm); + using (Log.ScopeInput(type)) { + int wordCount = WeaveStructInner(asm, type); + // add new attribute + type.AddAttribute(asm, wordCount); + return true; + } + } + + public bool WeaveStruct(ILWeaverAssembly asm, TypeReference typeRef) { + ILWeaverException.DebugThrowIf(!typeRef.Is(), $"Not a {nameof(INetworkStruct)}"); + + if (typeRef.Module != asm.CecilAssembly.MainModule) { + throw new ILWeaverException($"Type {typeRef} is not in the main module of assembly {asm.CecilAssembly}"); + } + + var type = typeRef.Resolve(); + if (type.TryGetAttribute(out _)) { + return false; + } + + EnsureTypeRegistry(asm); + + using (Log.ScopeStruct(type)) { + int wordCount = WeaveStructInner(asm, type); + // add new attribute + type.AddAttribute(asm, wordCount); + return true; + } + } + + int WeaveStructInner(ILWeaverAssembly asm, TypeDefinition type) { + + // flag asm as modified + asm.Modified = true; + + // set as explicit layout + type.IsExplicitLayout = true; + + Log.Assert(type.IsValueType); + + if (!type.Is() && !type.Is()) { + throw new ILWeaverException($"Structs need to implement either {nameof(INetworkStruct)} or {nameof(INetworkInput)}"); + } + + Dictionary fieldToProperty = new(); + + // clear all backing fields + foreach (var property in type.Properties) { + if (!IsWeavableProperty(property, out var propertyInfo)) { + continue; + } + + try { + if (TypeRegistry.GetInfo(property.PropertyType).IsTriviallyCopyable) { + Log.Warn(property, $"Networked property {property} should be replaced with a regular field. For structs, " + + $"[Networked] attribute should to be applied only on collections and booleans."); + } + + int fieldIndex = type.Fields.Count; + + if (propertyInfo.BackingField != null) { + if (!propertyInfo.BackingField.FieldType.IsValueType) { + Log.Warn(property, $"Networked property {property} has a backing field that is not a value type. To keep unmanaged status," + + $" the accessor should follow \"{{ get => default; set {{}} }}\" pattern"); + } + + fieldIndex = type.Fields.IndexOf(propertyInfo.BackingField); + if (fieldIndex >= 0) { + type.Fields.RemoveAt(fieldIndex); + } + } + + if (Settings.CheckNetworkedPropertiesBeingEmpty) { + try { + ThrowIfPropertyNotEmptyOrCompilerGenerated(property); + } catch (Exception ex) { + Log.Warn(property, $"{property} is not compiler-generated or empty: {ex.Message}"); + } + } + + var propertyWordCount = TypeRegistry.GetPropertyWordCount(property); + + property.GetMethod?.RemoveAttribute(asm); + property.SetMethod?.RemoveAttribute(asm); + + + var getIL = property.GetMethod.Body.GetILProcessor(); + getIL.Clear(); + getIL.Body.Variables.Clear(); + + var setIL = property.SetMethod?.Body.GetILProcessor(); + if (setIL != null) { + setIL.Clear(); + setIL.Body.Variables.Clear(); + } + + var backingFieldName = $"_{property.Name}"; + var fixedBufferInfo = MakeFixedBuffer(asm, propertyWordCount); + var surrogateType = MakeUnitySurrogate(asm, property); + var storageField = new FieldDefinition($"_{property.Name}", FieldAttributes.Private, fixedBufferInfo); + + var typeInfo = TypeRegistry.GetInfo(property.PropertyType); + typeInfo.TryGetCapacity(property, out var capacity); + +#if UNITY_EDITOR + var fixedBufferAttribute = storageField.AddAttribute(asm, property.PropertyType, surrogateType, capacity); + + // this attribute should always be used first; depending on Unity version this means different order + fixedBufferAttribute.Properties.Add(new CustomAttributeNamedArgument(nameof(PropertyAttribute.order), new CustomAttributeArgument(asm.Import(), +#if UNITY_2021_1_OR_NEWER + -int.MaxValue +#else + int.MaxValue +#endif + ))); +#endif + + + storageField.InsertTo(type, fieldIndex); + + // move field attributes, if any + if (propertyInfo.BackingField != null) { + MoveBackingFieldAttributes(asm, propertyInfo.BackingField, storageField); + } + MovePropertyAttributesToBackingField(asm, property, storageField); + + Action addressGetter = il => { + var m = new GenericInstanceMethod(asm.Native.GetMethod(nameof(Native.ReferenceToPointer))); + m.GenericArguments.Add(storageField.FieldType); + + il.Append(Ldarg_0()); + il.Append(Ldflda(storageField)); + + il.Append(Call(m)); + }; + + EmitRead(asm, getIL, property, addressGetter); + if (setIL != null) { + EmitWrite(asm, setIL, property, addressGetter, OpCodes.Ldarg_1); + } + + fieldToProperty.Add(storageField, property); + typeInfo.AddCustomAttributes(property); + + } catch (Exception ex) { + throw new ILWeaverException($"Failed to weave property {property}", ex); + } + } + + // figure out word counts for everything + var wordCount = 0; + + foreach (var field in type.Fields) { + + // skip statics + if (field.IsStatic) { + continue; + } + + // set offset + field.Offset = wordCount * Allocator.REPLICATE_WORD_SIZE; + + try { + // increase block count + var fieldWordCount = TypeRegistry.GetTypeWordCount(field.FieldType); + + if (fieldToProperty.TryGetValue(field, out var property)) { + property.AddAttribute(asm, wordCount, fieldWordCount); + } + + wordCount += fieldWordCount; + } catch (Exception ex) { + throw new ILWeaverException($"Failed to get word count of field {field}", ex); + } + } + + type.PackingSize = 0; + type.ClassSize = wordCount * Allocator.REPLICATE_WORD_SIZE; + return wordCount; + + } + } +} +#endif + + +#endregion + + +#region Assets/Photon/Fusion/CodeGen/ILWeaver.NetworkBehaviour.cs + +#if FUSION_WEAVER && FUSION_HAS_MONO_CECIL +namespace Fusion.CodeGen { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Runtime.CompilerServices; + using static Fusion.CodeGen.ILWeaverOpCodes; + using Mono.Cecil; + using Mono.Cecil.Cil; + using Mono.Cecil.Rocks; + using FieldAttributes = Mono.Cecil.FieldAttributes; + using MethodAttributes = Mono.Cecil.MethodAttributes; + +#if UNITY_EDITOR + using UnityEngine; + using UnityEngine.Scripting; +#endif + + unsafe partial class ILWeaver { + + FieldDefinition AddNetworkBehaviourBackingField(PropertyDefinition property) { + + var fieldType = TypeRegistry.GetInfo(property.PropertyType).GetUnityBackingFieldType(true); + var field = new FieldDefinition($"_{property.Name}", FieldAttributes.Private, fieldType); + return field; + } + + private void MoveBackingFieldAttributes(ILWeaverAssembly asm, FieldDefinition backingField, FieldDefinition storageField) { + if (backingField.IsNotSerialized) { + storageField.IsNotSerialized = true; + } + + foreach (var attrib in backingField.CustomAttributes) { + if (attrib.AttributeType.Is() || + attrib.AttributeType.Is()) { + continue; + } + storageField.CustomAttributes.Add(attrib); + } + } + + private void VisitPropertyMovableAttributes(PropertyDefinition property, Action onAttribute) { + foreach (var attribute in property.CustomAttributes) { + if (attribute.AttributeType.IsSame() || + attribute.AttributeType.IsSame() || + attribute.AttributeType.IsSame()) { + continue; + } + + var attribDef = attribute.AttributeType.Resolve(); + + if (attribDef.TryGetAttribute(out var proxy)) { +#if UNITY_EDITOR + var attribTypeRef = proxy.GetAttributeArgument(0); + var attribTypeDef = attribTypeRef.Resolve(); + + if (attribTypeDef.TryGetMatchingConstructor(attribute.Constructor.Resolve(), out var constructor)) { + onAttribute(constructor, attribute.GetBlob()); + } else { + Log.Warn(property, $"Failed to find matching constructor of {attribTypeDef} for {attribute.Constructor} (property {property})"); + } +#endif + continue; + } + + if (attribDef.TryGetAttribute(out var usage)) { + var targets = usage.GetAttributeArgument(0); + if ((targets & AttributeTargets.Field) != AttributeTargets.Field) { + Log.Debug($"Attribute {attribute.AttributeType} can't be applied on a field, skipping."); + continue; + } + } + onAttribute(attribute.Constructor, attribute.GetBlob()); + } + } + + private void MovePropertyAttributesToBackingField(ILWeaverAssembly asm, PropertyDefinition property, FieldDefinition field, bool addSerializeField = true) { + bool hasNonSerialized = false; + + VisitPropertyMovableAttributes(property, (ctor, blob) => { + Log.Debug($"Adding {ctor.DeclaringType} to {field}"); + field.CustomAttributes.Add(new CustomAttribute(asm.Import(ctor), blob)); + if (ctor.DeclaringType.IsSame()) { + Log.Debug($"{field} marked as NonSerialized, SerializeField will not be applied"); + hasNonSerialized = true; + } + }); + +#if UNITY_EDITOR + if (addSerializeField) { + if (!hasNonSerialized && property.GetMethod.IsPublic) { + if (field.IsNotSerialized) { + // prohibited + } else if (field.HasAttribute()) { + // already added + } else { + field.AddAttribute(asm); + } + } + } +#endif + } + + + // bool TryGetNetworkBehaviourTGenericArgument(ILWeaverAssembly asm, TypeReference type, out TypeReference genericArgument) { + // var outerType = type; + // while (!type.IsSame()) { + // + // var typeDef = type.Resolve(); + // if (typeDef.IsSame(asm.NetworkedBehaviourT)) { + // var genericInstance = (GenericInstanceType)type; + // genericArgument = genericInstance.GenericArguments[0]; + // if (genericArgument is GenericParameter gp && gp.TryResolve(outerType, out var resolved)) { + // genericArgument = resolved; + // } + // return true; + // } + // + // type = typeDef.BaseType; + // } + // + // genericArgument = null; + // return false; + // } + + public int GetBehaviourWordCount(ILWeaverAssembly asm, TypeReference type) { + int wordCount = 0; + var outerType = type; + + while (!type.IsSame()) { + + var typeDef = type.Resolve(); + + if (typeDef.TryGetAttribute(out var weavedAttribute)) { + var result = weavedAttribute.GetAttributeArgument(0); + if (result > 0) { + wordCount += result; + } + + // else if (TryGetNetworkBehaviourTGenericArgument(asm, outerType, out var genericArgument)) { + // if (genericArgument is GenericParameter) { + // Log.Assert(wordCount == 0); + // return -1; + // } else { + // var genericTypeDef = genericArgument.Resolve(); + // if (genericTypeDef == null) { + // throw new ILWeaverException($"Failed to resolve generic argument {genericArgument} of {outerType}"); + // } + // + // wordCount += TypeRegistry.GetTypeWordCount(genericTypeDef); + // } + // } + break; + } + + foreach (var property in typeDef.Properties) { + if (!IsWeavableProperty(property, out var propertyInfo)) { + continue; + } + + wordCount += TypeRegistry.GetPropertyWordCount(property); + } + + type = typeDef.BaseType; + } + + return wordCount; + } + + public int WeaveBehaviour(ILWeaverAssembly asm, TypeDefinition type) { + + if (type.IsSame()) { + return 0; + } + + if (!type.IsSubclassOf()) { + throw new ILWeaverException($"Not a {nameof(NetworkBehaviour)}"); + } + + if (type.Module != asm.CecilAssembly.MainModule) { + throw new ILWeaverException($"Type {type} is not in the main module of assembly {asm.CecilAssembly}"); + } + + if (type.TryGetAttribute(out var weavedAttribute)) { + return weavedAttribute.GetAttributeArgument(0); + } + + bool isOpenGenericNB = false; + + // if (TryGetNetworkBehaviourTGenericArgument(asm, type, out var genericArgument)) { + // if (genericArgument is GenericParameter) { + // isOpenGenericNB = true; + // } + // } + + var baseType = type.BaseType.Resolve(); + + if (baseType.Module == type.Module) { + //Log.Warn($"Weaving base ASAP {baseType} {type}"); + WeaveBehaviour(asm, baseType); + } + + EnsureTypeRegistry(asm); + + // flag as modified + asm.Modified = true; + + + using (Log.ScopeBehaviour(type)) { + // get block count of parent as starting point for ourselves + var wordCount = GetBehaviourWordCount(asm, type.BaseType); + + // this is the data field which holds this behaviours root pointer + //var ptrField = asm.NetworkedBehaviour.GetField(nameof(NetworkBehaviour.Ptr)); + var ptrGetter = asm.NetworkedBehaviour.GetFieldOrThrow(nameof(NetworkBehaviour.Ptr)); + + var setDefaults = CreateOverride(asm, type, nameof(NetworkBehaviour.CopyBackingFieldsToState)); + var getDefaults = CreateOverride(asm, type, nameof(NetworkBehaviour.CopyStateToBackingFields)); + + FieldDefinition lastAddedFieldWithKnownPosition = null; + List fieldsWithUncertainPosition = new List(); + + foreach (var property in type.Properties) { + if (!IsWeavableProperty(property, out var propertyInfo)) { + continue; + } + + if (isOpenGenericNB) { + throw new ILWeaverException($"Open generic {nameof(NetworkBehaviour)} can't have [Networked] properties."); + } + + if (!string.IsNullOrEmpty(propertyInfo.OnChanged)) { + WeaveChangedHandler(asm, property, propertyInfo.OnChanged); + } + + if (property.PropertyType.IsPointer || property.PropertyType.IsByReference) { + var elementType = property.PropertyType.GetElementTypeWithGenerics(); + if (!TypeRegistry.GetInfo(elementType).IsTriviallyCopyable) { + throw new ILWeaverException($"{property}: type {elementType} can't be used in pointer/reference properties."); + } + } + + + try { + // try to maintain fields order + int backingFieldIndex = type.Fields.Count; + if (propertyInfo.BackingField != null) { + backingFieldIndex = type.Fields.IndexOf(propertyInfo.BackingField); + if (backingFieldIndex >= 0) { + type.Fields.RemoveAt(backingFieldIndex); + } else { + Log.Warn(property, $"Unable to find backing field for {property}"); + backingFieldIndex = type.Fields.Count; + } + } + + var readOnlyInit = GetReadOnlyPropertyInitializer(property); + + // prepare getter/setter methods + if (readOnlyInit == null && Settings.CheckNetworkedPropertiesBeingEmpty) { + try { + ThrowIfPropertyNotEmptyOrCompilerGenerated(property); + } catch (Exception ex){ + Log.Warn(property, $"{property} is not compiler-generated or empty: {ex.Message}"); + } + } + + var (getter, setter) = PreparePropertyForWeaving(property); + var getterRef = getter.GetCallable(); + var setterRef = setter?.GetCallable(); + + // capture word count in case we re-use the lambda that is created later on ... + var wordOffset = wordCount; + + var getIL = getter.Body.GetILProcessor(); + var setIL = setter?.Body?.GetILProcessor(); + + Action addressGetter = il => { + il.Append(Ldarg_0()); + //il.Append(Ldfld(ptrField)); + il.Append(Ldfld(ptrGetter)); + il.Append(Ldc_I4(wordOffset * Allocator.REPLICATE_WORD_SIZE)); + il.Append(Add()); + }; + + // emit accessors + InjectPtrNullCheck(asm, getIL, property); + EmitRead(asm, getIL, property, addressGetter); + + if (setIL != null) { + InjectPtrNullCheck(asm, setIL, property); + EmitWrite(asm, setIL, property, addressGetter, OpCodes.Ldarg_1); + } + + var propertyWordCount = TypeRegistry.GetPropertyWordCount(property); + + // step up wordcount + Log.Assert(wordCount >= 0); + wordCount += propertyWordCount; + + var typeInfo = TypeRegistry.GetInfo(property.PropertyType); + + // inject attribute to poll weaver data during runtime + weavedAttribute = property.AddAttribute(asm, wordOffset, propertyWordCount); + typeInfo.AddCustomAttributes(property); + + //if (typeInfo.ElementWordCount) + + if (property.HasAttribute() || propertyInfo.BackingField?.IsNotSerialized == true) { + // so the property is not serialized, so there will be no backing field. + + Instruction[] fieldInit = null; + VariableDefinition[] fieldInitLocalVariables = null; + + if (readOnlyInit?.Instructions.Length > 0) { + fieldInit = readOnlyInit.Value.Instructions; + fieldInitLocalVariables = readOnlyInit.Value.Variables; + } else if (propertyInfo.BackingField != null) { + fieldInit = RemoveInlineFieldInit(type, propertyInfo.BackingField); + fieldInitLocalVariables = Array.Empty(); + } + + if (fieldInit?.Any() == true) { + + var storeIndex = Array.FindIndex(fieldInit, x => IsMakeInitializerCall(x) || + x.OpCode == OpCodes.Stfld && IsFieldOperand(x, propertyInfo.BackingField, type)); + + if (storeIndex >= 0) { + Log.Assert(fieldInit[0].OpCode == OpCodes.Ldarg_0); + fieldInit = fieldInit.Skip(1).Take(storeIndex - 1).ToArray(); + } else { + // keep as it is + } + + // create initializer method + var initializeMethod = new MethodDefinition($"FusionCodeGen@Initialize@{property.Name}", MethodAttributes.Private, typeInfo.GetUnityBackingFieldType(false)); + initializeMethod.AddTo(type); + initializeMethod.AddAttribute(asm, property.Name, wordOffset, propertyWordCount); + + { + var (initClone, _) = MonoCecilExtensions.CloneAndFixUp(initializeMethod.Body, fieldInit, fieldInitLocalVariables); + var il = initializeMethod.Body.GetILProcessor(); + foreach (var instruction in initClone) { + il.Append(instruction); + } + + il.Append(Ret()); + } + + { + // need to patch defaults with this, but only during the initial set + var il = setDefaults.Item1.Body.GetILProcessor(); + var postInit = Nop(); + il.Append(Ldarg_1()); + il.Append(Brfalse(postInit)); + + typeInfo.EmitUnityInit(property, il, null, il => { ; + il.Append(Ldarg_0()); + il.Append(Call(initializeMethod)); + }); + + il.Append(postInit); + } + } + } else { + + FieldReference defaultField = null; + { + FieldDefinition defaultFieldDef; + + if (string.IsNullOrEmpty(propertyInfo.DefaultFieldName)) { + + defaultFieldDef = AddNetworkBehaviourBackingField(property); + if (propertyInfo.BackingField != null) { + defaultFieldDef.InsertTo(type, backingFieldIndex); + MoveBackingFieldAttributes(asm, propertyInfo.BackingField, defaultFieldDef); + + if (lastAddedFieldWithKnownPosition == null) { + // fixup fields that have been added without knowing their index + foreach (var f in fieldsWithUncertainPosition) { + type.Fields.Remove(f); + } + + var index = type.Fields.IndexOf(defaultFieldDef); + fieldsWithUncertainPosition.Reverse(); + foreach (var f in fieldsWithUncertainPosition) { + f.InsertTo(type, index); + } + } + + lastAddedFieldWithKnownPosition = defaultFieldDef; + + } else { + if (lastAddedFieldWithKnownPosition == null) { + // not sure where to put this... append + defaultFieldDef.AddTo(type); + fieldsWithUncertainPosition.Add(defaultFieldDef); + } else { + // add after the previous field + var index = type.Fields.IndexOf(lastAddedFieldWithKnownPosition); + Log.Assert(index >= 0); + + defaultFieldDef.InsertTo(type, index + 1); + lastAddedFieldWithKnownPosition = defaultFieldDef; + } + } + MovePropertyAttributesToBackingField(asm, property, defaultFieldDef); + } else { + defaultFieldDef = property.DeclaringType.GetFieldOrThrow(propertyInfo.DefaultFieldName); + } + + defaultFieldDef.AddAttribute(asm, property.Name, wordOffset, propertyWordCount); + defaultFieldDef.AddAttribute(asm, nameof(NetworkBehaviour.IsEditorWritable), true, CompareOperator.Equal, DrawIfMode.ReadOnly); + defaultField = defaultFieldDef.GetLoadable(); + } + + + // in each constructor, replace inline init, if present + foreach (var constructor in type.GetConstructors()) { + + if (readOnlyInit?.Instructions.Length > 0) { + var il = constructor.Body.GetILProcessor(); + + Instruction before = il.Body.Instructions[0]; + { + // find where to plug in; after last stfld, but before base constructor call + for (int i = 0; i < il.Body.Instructions.Count; ++i) { + var instruction = il.Body.Instructions[i]; + if (instruction.IsBaseConstructorCall(type)) { + break; + } else if (instruction.OpCode == OpCodes.Stfld) { + before = il.Body.Instructions[i + 1]; + } + } + } + + // clone variables + var (instructions, variables) = CloneInstructions(readOnlyInit.Value.Instructions, readOnlyInit.Value.Variables); + + foreach (var variable in variables) { + il.Body.Variables.Add(variable); + } + + il.InsertBefore(before, Ldarg_0()); + foreach (var instruction in instructions) { + il.InsertBefore(before, instruction); + } + il.InsertBefore(before, Stfld(defaultField)); + } else if (propertyInfo.BackingField != null) { + // remove the inline init, if present + var init = GetInlineFieldInit(constructor, propertyInfo.BackingField, type); + if (init.Length > 0) { + ReplaceBackingFieldInInlineInit(asm, propertyInfo.BackingField, defaultField, constructor.Body.GetILProcessor(), init); + } + } + } + + typeInfo.EmitUnityInit(property, setDefaults.Item1.Body.GetILProcessor(), defaultField.FieldType, il => { + il.Append(Ldarg_0()); + il.Append(Ldfld(defaultField)); + }); + + typeInfo.EmitUnityStore(property, getDefaults.Item1.Body.GetILProcessor(), defaultField); + } + + } catch (Exception ex) { + throw new ILWeaverException($"Failed to weave property {property}", ex); + } + } + + { + var (method, instruction) = setDefaults; + method.Body.GetILProcessor().Append(instruction); + } + + { + var (method, instruction) = getDefaults; + method.Body.GetILProcessor().Append(instruction); + } + + if (wordCount != GetBehaviourWordCount(asm, type)) { + throw new ILWeaverException($"Failed to weave {type} - word count mismatch {wordCount} vs {GetBehaviourWordCount(asm, type)}"); + } + + // add meta attribute + type.AddAttribute(asm, wordCount); + + WeaveRpcs(asm, type); + WeaveUnityMessages(asm, type); + return wordCount; + } + } + + private void WeaveUnityMessages(ILWeaverAssembly asm, TypeDefinition type) { + + WeaveUnityMessage("OnDestroy", asm.NetworkBehaviourUtils.GetMethod(nameof(NetworkBehaviourUtils.InternalOnDestroy))); + WeaveUnityMessage("OnEnable", asm.NetworkBehaviourUtils.GetMethod(nameof(NetworkBehaviourUtils.InternalOnEnable))); + WeaveUnityMessage("OnDisable", asm.NetworkBehaviourUtils.GetMethod(nameof(NetworkBehaviourUtils.InternalOnDisable))); + + void WeaveUnityMessage(string methodName, MethodReference internalMethod) { + if (!type.TryGetMethod(methodName, out var method)) { + // all good, nothing to do + return; + } + + var instructions = method.Body.Instructions; + bool hasCleanUp = instructions + .Where(x => x.OpCode == OpCodes.Call) + .Select(x => (MethodReference)x.Operand) + .Any(m => m.Name == nameof(internalMethod.Name) && m.DeclaringType.Is(internalMethod.DeclaringType)); + + if (hasCleanUp) { + Log.Debug($"Type {type} has a custom {methodName} method that calls {internalMethod.Name}, skipping"); + return; + } + + asm.Modified = true; + + Log.Debug($"Type {type} has a custom {methodName} method, weaving it"); + instructions.Insert(0, Ldarg_0()); + instructions.Insert(1, Call(internalMethod)); + } + } + + private void WeaveChangedHandler(ILWeaverAssembly asm, PropertyDefinition property, string handlerName) { + + // find the handler + { + foreach (var declaringType in property.DeclaringType.GetHierarchy()) { + var candidates = declaringType.GetMethods() + .Where(x => x.IsStatic) + .Where(x => x.Name == handlerName) + .Where(x => x.HasParameters && x.Parameters.Count == 1) + .Where(x => x.Parameters[0].ParameterType.IsSubclassOf()) + .ToList(); + + if (candidates.Count > 1) { + throw new ILWeaverException($"Ambiguous match for OnChanged handler for {property}: {string.Join("; ", candidates)}"); + } + + if (candidates.Count == 1) { + var handler = candidates[0]; + + Log.Debug($"OnChanged handler for {property}: {handler}"); + +#if UNITY_EDITOR + // add preserve attribute, if not added already + if (!handler.TryGetAttribute(out _)) { + handler.AddAttribute(asm); + Log.Debug($"Added {nameof(PreserveAttribute)} to {handler}"); + } +#endif + return; + } + } + } + + throw new ILWeaverException($"No match found for OnChanged handler for {property}"); + } + + struct ReadOnlyInitializer { + public Instruction[] Instructions; + public VariableDefinition[] Variables; + } + + private ReadOnlyInitializer? GetReadOnlyPropertyInitializer(PropertyDefinition property) { + if (property.PropertyType.IsPointer || property.PropertyType.IsByReference) { + // need to check if there's MakeRef/Ptr before getter gets obliterated + var instructions = property.GetMethod.Body.Instructions; + + + for (int i = 0; i < instructions.Count; ++i) { + var instr = instructions[i]; + if (IsMakeRefOrMakePtrCall(instr)) { + // found it! + return new ReadOnlyInitializer() { + Instructions = instructions.Take(i).ToArray(), + Variables = property.GetMethod.Body.Variables.ToArray() + }; + } + } + } + return null; + } + + private (Instruction[], VariableDefinition[]) CloneInstructions(Instruction[] source, VariableDefinition[] sourceVariables) { + + var constructor = typeof(Instruction).GetConstructor( + System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance, null, + new[] { typeof(OpCode), typeof(object) }, null); + + // shallow copy + var result = source.Select(x => (Instruction)constructor.Invoke(new[] { x.OpCode, x.Operand })) + .ToArray(); + + var variableMapping = new Dictionary(); + + // now need to resolve local variables and jump targets + foreach (var instruction in result) { + + if (instruction.IsLdlocWithIndex(out var locIndex) || instruction.IsStlocWithIndex(out locIndex)) { + var variable = sourceVariables[locIndex]; + if (!variableMapping.TryGetValue(variable, out var replacement)) { + replacement = new VariableDefinition(variable.VariableType); + variableMapping.Add(variable, replacement); + } + if (instruction.IsLdlocWithIndex(out _)) { + instruction.OpCode = OpCodes.Ldloc; + } else { + instruction.OpCode = OpCodes.Stloc; + } + instruction.Operand = replacement; + } else if (instruction.Operand is VariableDefinition variable) { + if (!variableMapping.TryGetValue(variable, out var replacement)) { + replacement = new VariableDefinition(variable.VariableType); + variableMapping.Add(variable, replacement); + } + instruction.Operand = replacement; + } else if (instruction.Operand is Instruction target) { + var targetIndex = Array.IndexOf(source, target); + Log.Assert(targetIndex >= 0); + instruction.Operand = result[targetIndex]; + } else if (instruction.Operand is Instruction[] targets) { + instruction.Operand = targets.Select(x => { + var targetIndex = Array.IndexOf(source, x); + Log.Assert(targetIndex >= 0); + return result[targetIndex]; + }); + } else if (instruction.Operand is ParameterDefinition) { + throw new NotSupportedException(); + } + } + + return (result, variableMapping.Values.ToArray()); + } + + private (MethodDefinition method, Instruction returnInstruction) CreateOverride(ILWeaverAssembly asm, TypeDefinition type, string name) { + var rootMethod = asm.NetworkedBehaviour.GetMethod(name); + + var result = type.Methods.FirstOrDefault(x => x.Name == name); + if (result != null) { + // need to find the placeholder method + var placeholderMethodName = asm.NetworkedBehaviour.GetMethod(nameof(NetworkBehaviour.InvokeWeavedCode)).FullName; + var placeholders = result.Body.Instructions + .Where(x => x.OpCode == OpCodes.Call && (x.Operand as MethodReference)?.FullName == placeholderMethodName) + .ToList(); + + if (placeholders.Count != 1) { + throw new ILWeaverException($"When overriding {name} in a type with [Networked] properties, make sure to call {placeholderMethodName} exactly once somewhere."); + } + + var baseCalls = result.Body.Instructions + .Where(x => x.OpCode == OpCodes.Call && (x.Operand as MethodReference)?.Name == name) + .ToList(); + + if (baseCalls.Count == 0 && !type.BaseType.IsSame()) { + throw new ILWeaverException($"When overriding {name} in a type derived from a type derived from {nameof(NetworkBehaviour)}, invoke the base method."); + } + + foreach (var baseCall in baseCalls) { + var baseMethod = (MethodReference)baseCall.Operand; + if (!baseMethod.DeclaringType.IsSame(type.BaseType)) { + Log.Debug($"Changing base declaring type from {baseMethod.DeclaringType} to {type.BaseType}"); + baseCall.Operand = GetBaseMethodReference(asm, result, type.BaseType); + } + } + + var placeholder = placeholders[0]; + var il = result.Body.GetILProcessor(); + + var jumpTarget = Nop(); + var returnTarget = Nop(); + + // this is where to jump after weaved code's done + il.InsertAfter(placeholder, returnTarget); + il.InsertAfter(placeholder, Br(jumpTarget)); + + il.Append(jumpTarget); + return (result, Br(returnTarget)); + } + + result = new MethodDefinition(name, MethodAttributes.Public, rootMethod.ReturnType) { + IsVirtual = true, + IsHideBySig = true, + IsReuseSlot = true + }; + + foreach (var parameter in rootMethod.Parameters) { + result.Parameters.Add(new ParameterDefinition(parameter.ParameterType)); + } + + result.AddTo(type); + + // call base method if it exists, unless it is a NB + var baseType = type.BaseType; + while (!baseType.IsSame()) { + + var baseMethod = GetBaseMethodReference(asm, result, baseType); + var bodyIL = result.Body.GetILProcessor(); + + bodyIL.Append(Instruction.Create(OpCodes.Ldarg_0)); + + foreach (var parameter in result.Parameters) { + bodyIL.Append(Ldarg(parameter)); + } + + bodyIL.Append(Call(baseMethod)); + break; + } + + return (result, Ret()); + } + + private Lazy MakeLazy(Func func) { + return new Lazy(func); + } + } +} +#endif + + +#endregion + + +#region Assets/Photon/Fusion/CodeGen/ILWeaverAssembly.cs + +#if FUSION_WEAVER && FUSION_HAS_MONO_CECIL +namespace Fusion.CodeGen { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + + using Mono.Cecil; + + public class ILWeaverImportedType { + public Type ClrType; + public ILWeaverAssembly Assembly; + public List BaseDefinitions; + public TypeReference Reference; + + Dictionary _fields = new Dictionary(); + Dictionary<(string, int?), MethodReference> _methods = new Dictionary<(string, int?), MethodReference>(); + Dictionary _propertiesGet = new Dictionary(); + Dictionary _propertiesSet = new Dictionary(); + + public static implicit operator TypeReference(ILWeaverImportedType type) => type.Reference; + + public ILWeaverImportedType(ILWeaverAssembly asm, Type type) { + ClrType = type; + Assembly = asm; + + Type baseType = type; + BaseDefinitions = new List(); + // Store the type, and each of its base types - so we can later find fields/properties/methods in the base class. + while (baseType != null) { + BaseDefinitions.Add(asm.CecilAssembly.MainModule.ImportReference(baseType).Resolve()); + baseType = baseType.BaseType; + } + + Reference = asm.CecilAssembly.MainModule.ImportReference(BaseDefinitions[0]); + } + + public FieldReference GetFieldOrThrow(string name) { + bool found = _fields.TryGetValue(name, out var fieldRef); + if (found == false) { + for (int i = 0; i < BaseDefinitions.Count; ++i) { + FieldDefinition typeDef = BaseDefinitions[i].Fields.FirstOrDefault(x => x.Name == name); + if (typeDef != null) { + fieldRef = Assembly.CecilAssembly.MainModule.ImportReference(typeDef); + _fields.Add(name, fieldRef); + return fieldRef; + } + } + throw new Exception($"Field {name} not found on type {ClrType}"); + } + + return fieldRef; + } + + public MethodReference GetGetterOrThrow(string name) { + bool found = _propertiesGet.TryGetValue(name, out var methRef); + if (found == false) { + for (int i = 0; i < BaseDefinitions.Count; ++i) { + PropertyDefinition typeDef = BaseDefinitions[i].Properties.FirstOrDefault(x => x.Name == name); + if (typeDef != null) { + methRef = Assembly.CecilAssembly.MainModule.ImportReference(typeDef.GetMethod); + _propertiesGet.Add(name, methRef); + return methRef; + } + } + throw new Exception($"Property {name} not found on type {ClrType}"); + } + + return methRef; + } + + public MethodReference GetMethod(string name, int? argsCount = null, int? genericArgsCount = null) { + if (!TryGetMethod(name, out var methRef, argsCount, genericArgsCount)) { + throw new InvalidOperationException($"Not found: {name}"); + } + return methRef; + } + + public bool TryGetMethod(string name, out MethodReference method, int? argsCount = null, int? genericArgsCount = null) { + if (_methods.TryGetValue((name, argsCount), out method)) { + return method != null; + } + + foreach (var t in BaseDefinitions) { + var typeDef = t.Methods.FirstOrDefault( + x => x.Name == name && + (argsCount.HasValue == false || x.Parameters.Count == argsCount.Value) && + (genericArgsCount == null || x.GenericParameters.Count == genericArgsCount.Value)); + + if (typeDef != null) { + method = Assembly.CecilAssembly.MainModule.ImportReference(typeDef); + _methods.Add((name, argsCount), method); + return true; + } + } + + _methods.Add((name, argsCount), null); + return false; + } + + + public GenericInstanceMethod GetGenericMethod(string name, int? argsCount = null, params TypeReference[] types) { + var method = GetMethod(name, argsCount); + var generic = new GenericInstanceMethod(method); + + foreach (var t in types) { + generic.GenericArguments.Add(t); + } + + return generic; + } + } + + public class ILWeaverAssembly { + public bool Modified; + public List Errors = new List(); + + public AssemblyDefinition CecilAssembly; + + ILWeaverImportedType _networkRunner; + ILWeaverImportedType _readWriteUtils; + ILWeaverImportedType _nativeUtils; + ILWeaverImportedType _rpcInfo; + ILWeaverImportedType _rpcInvokeInfo; + ILWeaverImportedType _rpcHeader; + ILWeaverImportedType _networkBehaviourUtils; + + ILWeaverImportedType _simulation; + ILWeaverImportedType _networkedObject; + ILWeaverImportedType _networkedObjectId; + ILWeaverImportedType _networkedBehaviour; + ILWeaverImportedType _networkedBehaviourT; + ILWeaverImportedType _networkedBehaviourId; + ILWeaverImportedType _simulationBehaviour; + ILWeaverImportedType _simulationMessage; + + ILWeaverImportedType _object; + ILWeaverImportedType _valueType; + ILWeaverImportedType _void; + ILWeaverImportedType _int; + ILWeaverImportedType _float; + + Dictionary _types = new Dictionary(); + + private ILWeaverImportedType MakeImportedType(ref ILWeaverImportedType field) { + return MakeImportedType(ref field, typeof(T)); + } + + private ILWeaverImportedType MakeImportedType(ref ILWeaverImportedType field, Type type) { + if (field == null) { + field = new ILWeaverImportedType(this, type); + } + return field; + } + + public ILWeaverImportedType WordSizedPrimitive => MakeImportedType(ref _int); + + public ILWeaverImportedType Void => MakeImportedType(ref _void, typeof(void)); + + public ILWeaverImportedType Object => MakeImportedType(ref _object); + + public ILWeaverImportedType ValueType => MakeImportedType(ref _valueType); + + public ILWeaverImportedType Float => MakeImportedType(ref _float); + + public ILWeaverImportedType NetworkedObject => MakeImportedType(ref _networkedObject); + + public ILWeaverImportedType Simulation => MakeImportedType(ref _simulation); + + public ILWeaverImportedType SimulationMessage => MakeImportedType(ref _simulationMessage); + + public ILWeaverImportedType NetworkedBehaviour => MakeImportedType(ref _networkedBehaviour); + + public ILWeaverImportedType SimulationBehaviour => MakeImportedType(ref _simulationBehaviour); + + public ILWeaverImportedType NetworkId => MakeImportedType(ref _networkedObjectId); + + public ILWeaverImportedType NetworkedBehaviourId => MakeImportedType(ref _networkedBehaviourId); + + public ILWeaverImportedType NetworkRunner => MakeImportedType(ref _networkRunner); + + public ILWeaverImportedType ReadWriteUtils => MakeImportedType(ref _readWriteUtils, typeof(ReadWriteUtilsForWeaver)); + + public ILWeaverImportedType Native => MakeImportedType(ref _nativeUtils, typeof(Native)); + + public ILWeaverImportedType NetworkBehaviourUtils => MakeImportedType(ref _networkBehaviourUtils, typeof(NetworkBehaviourUtils)); + + public ILWeaverImportedType RpcHeader => MakeImportedType(ref _rpcHeader); + + public ILWeaverImportedType RpcInfo => MakeImportedType(ref _rpcInfo); + + public ILWeaverImportedType RpcInvokeInfo => MakeImportedType(ref _rpcInvokeInfo); + + public TypeReference Import(TypeReference type) { + return CecilAssembly.MainModule.ImportReference(type); + } + + public MethodReference Import(MethodInfo method) { + return CecilAssembly.MainModule.ImportReference(method); + } + + public MethodReference Import(MethodReference method) { + return CecilAssembly.MainModule.ImportReference(method); + } + + public MethodReference Import(ConstructorInfo method) { + return CecilAssembly.MainModule.ImportReference(method); + } + + public TypeReference Import(Type type) { + if (_types.TryGetValue(type, out var reference) == false) { + _types.Add(type, reference = CecilAssembly.MainModule.ImportReference(type)); + } + + return reference; + } + + public void Dispose() { + CecilAssembly?.Dispose(); + + Modified = false; + Errors.Clear(); + CecilAssembly = null; + } + + public TypeReference Import() { + return Import(typeof(T)); + } + } +} +#endif + +#endregion + + +#region Assets/Photon/Fusion/CodeGen/ILWeaverAssemblyResolver.ILPostProcessor.cs + +#if FUSION_WEAVER && FUSION_WEAVER_ILPOSTPROCESSOR && FUSION_HAS_MONO_CECIL + +namespace Fusion.CodeGen { + using System.Collections.Generic; + using System.IO; + using System.Linq; + using Mono.Cecil; + + internal class ILWeaverAssemblyResolver : IAssemblyResolver { + private List _lookInDirectories; + private Dictionary _assemblyNameToPath; + private Dictionary _resolvedAssemblies = new Dictionary(); + private string _compiledAssemblyName; + private ILWeaverLog _log; + + public AssemblyDefinition WeavedAssembly; + + public ILWeaverAssemblyResolver(ILWeaverLog log, string compiledAssemblyName, string[] references, string[] weavedAssemblies) { + _log = log; + _compiledAssemblyName = compiledAssemblyName; + _assemblyNameToPath = new Dictionary(); + + foreach (var referencePath in references) { + var assemblyName = Path.GetFileNameWithoutExtension(referencePath); + if (_assemblyNameToPath.TryGetValue(assemblyName, out var existingPath)) { + _log.Warn($"Assembly {assemblyName} (full path: {referencePath}) already referenced by {compiledAssemblyName} at {existingPath}"); + } else { + _log.Debug($"Adding {assemblyName}->{referencePath}"); + _assemblyNameToPath.Add(assemblyName, referencePath); + } + } + + _lookInDirectories = references.Select(x => Path.GetDirectoryName(x)).Distinct().ToList(); + } + + public void Dispose() { + } + + public AssemblyDefinition Resolve(AssemblyNameReference name) { + return Resolve(name, new ReaderParameters(ReadingMode.Deferred)); + } + + public AssemblyDefinition Resolve(AssemblyNameReference name, ReaderParameters parameters) { + { + if (name.Name == _compiledAssemblyName) + return WeavedAssembly; + + var path = GetAssemblyPath(name); + if (string.IsNullOrEmpty(path)) + return null; + + if (_resolvedAssemblies.TryGetValue(path, out var result)) + return result; + + parameters.AssemblyResolver = this; + + var pdb = path + ".pdb"; + if (File.Exists(pdb)) { + parameters.SymbolStream = CreateAssemblyStream(pdb); + } + + var assemblyDefinition = AssemblyDefinition.ReadAssembly(CreateAssemblyStream(path), parameters); + _resolvedAssemblies.Add(path, assemblyDefinition); + return assemblyDefinition; + } + } + + private string GetAssemblyPath(AssemblyNameReference name) { + if (_assemblyNameToPath.TryGetValue(name.Name, out var path)) { + return path; + } + + // fallback for second-order references + foreach (var parentDir in _lookInDirectories) { + var fullPath = Path.Combine(parentDir, name.Name + ".dll"); + if (File.Exists(fullPath)) { + _assemblyNameToPath.Add(name.Name, fullPath); + return fullPath; + } + } + + return null; + } + + private static MemoryStream CreateAssemblyStream(string fileName) { + var bytes = File.ReadAllBytes(fileName); + return new MemoryStream(bytes); + } + } +} + +#endif + +#endregion + + +#region Assets/Photon/Fusion/CodeGen/ILWeaverAssemblyResolver.UnityEditor.cs + +#if FUSION_WEAVER && !FUSION_WEAVER_ILPOSTPROCESSOR && FUSION_HAS_MONO_CECIL + +namespace Fusion.CodeGen { + using System; + using System.Collections.Generic; + using Mono.Cecil; + using CompilerAssembly = UnityEditor.Compilation.Assembly; + + class ILWeaverAssemblyResolver : BaseAssemblyResolver { + Dictionary _assemblies; + Dictionary _assembliesByPath; + + public IEnumerable Assemblies => _assemblies.Values; + + public ILWeaverAssemblyResolver() { + _assemblies = new Dictionary(StringComparer.Ordinal); + _assembliesByPath = new Dictionary(); + } + + public sealed override AssemblyDefinition Resolve(AssemblyNameReference name) { + if (_assemblies.TryGetValue(name.FullName, out var asm) == false) { + asm = new ILWeaverAssembly(); + asm.CecilAssembly = base.Resolve(name, ReaderParameters(false, false)); + + _assemblies.Add(name.FullName, asm); + } + + return asm.CecilAssembly; + } + + public void Clear() { + _assemblies.Clear(); + } + + public bool Contains(CompilerAssembly compilerAssembly) { + return _assembliesByPath.ContainsKey(compilerAssembly.outputPath); + } + + public ILWeaverAssembly AddAssembly(string path, bool readWrite = true, bool readSymbols = true) { + return AddAssembly(AssemblyDefinition.ReadAssembly(path, ReaderParameters(readWrite, readSymbols)), null); + } + + public ILWeaverAssembly AddAssembly(CompilerAssembly compilerAssembly, bool readWrite = true, bool readSymbols = true) { + return AddAssembly(AssemblyDefinition.ReadAssembly(compilerAssembly.outputPath, ReaderParameters(readWrite, readSymbols)), compilerAssembly); + } + + public ILWeaverAssembly AddAssembly(AssemblyDefinition assembly, CompilerAssembly compilerAssembly) { + if (assembly == null) { + throw new ArgumentNullException(nameof(assembly)); + } + + if (_assemblies.TryGetValue(assembly.Name.FullName, out var asm) == false) { + asm = new ILWeaverAssembly(); + asm.CecilAssembly = assembly; + + _assemblies.Add(assembly.Name.FullName, asm); + + if (compilerAssembly != null) { + Assert.Always(_assembliesByPath.ContainsKey(compilerAssembly.outputPath) == false); + _assembliesByPath.Add(compilerAssembly.outputPath, asm); + } + } + + return asm; + } + + protected override void Dispose(bool disposing) { + foreach (var asm in _assemblies.Values) { + asm.CecilAssembly?.Dispose(); + } + + _assemblies.Clear(); + + base.Dispose(disposing); + } + + ReaderParameters ReaderParameters(bool readWrite, bool readSymbols) { + ReaderParameters p; + p = new ReaderParameters(ReadingMode.Immediate); + p.ReadWrite = readWrite; + p.ReadSymbols = readSymbols; + p.AssemblyResolver = this; + return p; + } + } +} + +#endif + +#endregion + + +#region Assets/Photon/Fusion/CodeGen/ILWeaverBindings.ILPostProcessor.cs + +#if FUSION_WEAVER_ILPOSTPROCESSOR +namespace Fusion.CodeGen { + using System; + using Unity.CompilationPipeline.Common.ILPostProcessing; + +#if FUSION_WEAVER + using System.Collections.Generic; + using Unity.CompilationPipeline.Common.Diagnostics; +#if FUSION_HAS_MONO_CECIL + using System.IO; + using System.Linq; + using Mono.Cecil; + using Mono.Cecil.Cil; + using System.Reflection; + + using System.Runtime.Serialization.Json; + using System.Xml.Linq; + + class ILWeaverBindings : ILPostProcessor { + + const string ConfigPathCachePath = "Temp/FusionILWeaverConfigPath.txt"; + const string MainAssemblyName = "Assembly-CSharp"; + const string OverrideMethodName = nameof(ILWeaverSettings) + ".OverrideNetworkProjectConfigPath"; + const string UserFileName = "Fusion.CodeGen.User.cs"; + + enum ConfigPathSource { + User, + PathFile, + Find + } + + Lazy<(string, ConfigPathSource)> _configPath; + Lazy _config; + + public ILWeaverBindings() { + _configPath = new Lazy<(string, ConfigPathSource)>(() => { + + // try the user-provided path + var defaultPath = ILWeaverSettings.DefaultConfigPath; + if (!string.IsNullOrEmpty(defaultPath) && File.Exists(defaultPath)) { + return (defaultPath, ConfigPathSource.User); + } + + // try the editor-provided path + if (File.Exists(ConfigPathCachePath)) { + var path = File.ReadAllText(ConfigPathCachePath); + if (File.Exists(path)) { + return (path, ConfigPathSource.PathFile); + } + } + + // last resort: grep + string[] paths = Directory.GetFiles("Assets", "*.fusion", SearchOption.AllDirectories); + if (paths.Length == 0) { + throw new InvalidOperationException($"No {nameof(NetworkProjectConfig)} file found (.fusion extension) in {Path.GetFullPath("Assets")}"); + } + if (paths.Length > 1) { + throw new InvalidOperationException($"Multiple config files found: {string.Join(", ", paths)}"); + } + return (paths[0], ConfigPathSource.Find); + }); + + _config = new Lazy(() => { + string configPath = _configPath.Value.Item1; + using (var stream = File.OpenRead(configPath)) { + var jsonReader = JsonReaderWriterFactory.CreateJsonReader(stream, new System.Xml.XmlDictionaryReaderQuotas()); + return XDocument.Load(jsonReader); + } + }); + } + + public override ILPostProcessor GetInstance() { + return this; + } + + public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly) { + // try to load the config + ILWeaverSettings settings; + try { + settings = ReadSettings(_config.Value); + } catch (Exception ex) { + + string message; + DiagnosticType messageType = DiagnosticType.Error; + try { + var (configPath, source) = _configPath.Value; + message = $"Failed to load config from \"{configPath}\". "; + if (source == ConfigPathSource.User) { + message += $"This is path comes from the default location ({ILWeaverSettings.DefaultConfigPath}). " + + $"Implement {OverrideMethodName} in {UserFileName} to override. "; + } else if (source == ConfigPathSource.PathFile) { + message += $"The path comes from {ConfigPathCachePath} file that is generated by editor scripts each time compilation starts. " + + $"This method is used if the default config ({ILWeaverSettings.DefaultConfigPath}) does not exist. " + + $"Implement {OverrideMethodName} in {UserFileName} to override. "; + } else if (source == ConfigPathSource.Find) { + message += $"The path comes searching Assets directory for *.fusion files. " + + $"This method is used if the default config ({ILWeaverSettings.DefaultConfigPath}) does not exist and " + + $"{ConfigPathCachePath} was not properly generated by editor scripts. "; + } + message += $"Details: {ex}"; + } catch (Exception configPathEx) { + message = + $"Failed to locate a valid config. " + + $"The weaver first checks the default location - {ILWeaverSettings.DefaultConfigPath} (implement {OverrideMethodName} in {UserFileName} to override). " + + $"If the file does not exist, editor-generated {ConfigPathCachePath} is checked (there might be a scenario where weaving is triggered without the editor scripts being compiled yet). " + + $"If that fails, the weaver searches for *.fusion files in Assets directory. "; + message += $"Details: {configPathEx}"; + messageType = DiagnosticType.Warning; + } + return new ILPostProcessResult(null, new List() { + { + new DiagnosticMessage() { + MessageData = message, + DiagnosticType = messageType, + } + } + }); + } + + InMemoryAssembly resultAssembly = null; + var logger = new ILWeaverLoggerDiagnosticMessages(); + var log = new ILWeaverLog(logger); + + using (log.Scope($"Process {compiledAssembly.Name}")) { + + { + var (configPath, configSource) = _configPath.Value; + log.Debug($"Using config at {configPath} (from {configSource})"); + if (compiledAssembly.Name == MainAssemblyName && configSource == ConfigPathSource.Find) { + log.Warn( + $"The weaver had to use Directory.GetFiles to locate the config {configPath}. " + + $"This is potentially slow and might happen if you moved config to a non-standard location and editor scripts did not get the chance to run yet. " + + $"If you see this message while running in a batch mode, implement {OverrideMethodName} in {UserFileName}."); + } + } + + try { + ILWeaverAssembly asm; + ILWeaver weaver; + + using (log.Scope("Resolving")) { + asm = CreateWeaverAssembly(settings, log, compiledAssembly); + } + + using (log.Scope("Init")) { + weaver = new ILWeaver(settings, log); + } + + weaver.Weave(asm); + + if (asm.Modified) { + var pe = new MemoryStream(); + var pdb = new MemoryStream(); + var writerParameters = new WriterParameters { + SymbolWriterProvider = new PortablePdbWriterProvider(), + SymbolStream = pdb, + WriteSymbols = true + }; + + using (log.Scope("Writing")) { + asm.CecilAssembly.Write(pe, writerParameters); + resultAssembly = new InMemoryAssembly(pe.ToArray(), pdb.ToArray()); + } + } + } catch (Exception ex) { + log.Error($"Exception thrown when weaving {compiledAssembly.Name}"); + log.Exception(ex); + } + } + + logger.FixNewLinesInMessages(); + return new ILPostProcessResult(resultAssembly, logger.Messages); + } + + public override bool WillProcess(ICompiledAssembly compiledAssembly) { + + string[] assembliesToWeave; + + try { + assembliesToWeave = ReadSettings(_config.Value, full: false).AssembliesToWeave; + } catch { + // need to go to the next stage for some assembly, main is good enough + return compiledAssembly.Name == MainAssemblyName; + } + + if (!ILWeaverSettings.IsAssemblyWeavable(assembliesToWeave, compiledAssembly.Name)) { + return false; + } + + if (!ILWeaverSettings.ContainsRequiredReferences(compiledAssembly.References)) { + return false; + } + + return true; + } + + + static ILWeaverAssembly CreateWeaverAssembly(ILWeaverSettings settings, ILWeaverLog log, ICompiledAssembly compiledAssembly) { + var resolver = new ILWeaverAssemblyResolver(log, compiledAssembly.Name, compiledAssembly.References, settings.AssembliesToWeave); + + var readerParameters = new ReaderParameters { + SymbolStream = new MemoryStream(compiledAssembly.InMemoryAssembly.PdbData.ToArray()), + SymbolReaderProvider = new PortablePdbReaderProvider(), + AssemblyResolver = resolver, + ReadingMode = ReadingMode.Immediate, + ReadWrite = true, + ReadSymbols = true, + ReflectionImporterProvider = new ReflectionImporterProvider(log) + }; + + var peStream = new MemoryStream(compiledAssembly.InMemoryAssembly.PeData.ToArray()); + var assemblyDefinition = AssemblyDefinition.ReadAssembly(peStream, readerParameters); + + resolver.WeavedAssembly = assemblyDefinition; + + return new ILWeaverAssembly() { + CecilAssembly = assemblyDefinition, + }; + } + + static ILWeaverSettings ReadSettings(XDocument config, bool full = true) { + + void SetIfExists(ref bool field, string name) { + var b = (bool?)config.Root.Element(name); + if (b != null) { + field = b.Value; + } + } + + var result = new ILWeaverSettings(); + + if (full) { + SetIfExists(ref result.NullChecksForNetworkedProperties, nameof(NetworkProjectConfig.NullChecksForNetworkedProperties)); + SetIfExists(ref result.UseSerializableDictionary, nameof(NetworkProjectConfig.UseSerializableDictionary)); + SetIfExists(ref result.CheckRpcAttributeUsage, nameof(NetworkProjectConfig.CheckRpcAttributeUsage)); + SetIfExists(ref result.CheckNetworkedPropertiesBeingEmpty, nameof(NetworkProjectConfig.CheckNetworkedPropertiesBeingEmpty)); + } + + result.AssembliesToWeave = config.Root.Element(nameof(NetworkProjectConfig.AssembliesToWeave))? + .Elements() + .Select(x => x.Value) + .ToArray() ?? Array.Empty(); + + return result; + } + + class ReflectionImporterProvider : IReflectionImporterProvider { + private ILWeaverLog _log; + + public ReflectionImporterProvider(ILWeaverLog log) { + _log = log; + } + + public IReflectionImporter GetReflectionImporter(ModuleDefinition module) { + return new ReflectionImporter(_log, module); + } + } + + class ReflectionImporter : DefaultReflectionImporter { + private ILWeaverLog _log; + + public ReflectionImporter(ILWeaverLog log, ModuleDefinition module) : base(module) { + _log = log; + } + + public override AssemblyNameReference ImportReference(AssemblyName name) { + if (name.Name == "System.Private.CoreLib") { + // seems weaver is run with .net core, but we need to stick to .net framework + var candidates = module.AssemblyReferences + .Where(x => x.Name == "mscorlib" || x.Name == "netstandard") + .OrderBy(x => x.Name) + .ThenByDescending(x => x.Version) + .ToList(); + + // in Unity 2020.1 and .NET 4.x mode when building with IL2CPP apparently both mscrolib and netstandard can be loaded + if (candidates.Count > 0) { + return candidates[0]; + } + + throw new ILWeaverException("Could not locate mscrolib or netstandard assemblies"); + } + + return base.ImportReference(name); + } + } + + + } +#else + class ILWeaverBindings : ILPostProcessor { + public override ILPostProcessor GetInstance() { + return this; + } + + public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly) { + return new ILPostProcessResult(null, new List() { + new DiagnosticMessage() { + DiagnosticType = DiagnosticType.Warning, + MessageData = "Mono.Cecil not found, Fusion IL weaving is disabled. Make sure package com.unity.nuget.mono-cecil is installed." + } + }); + } + + public override bool WillProcess(ICompiledAssembly compiledAssembly) { + return compiledAssembly.Name == "Assembly-CSharp"; + } + } +#endif +#else + class ILWeaverBindings : ILPostProcessor { + public override ILPostProcessor GetInstance() { + return this; + } + + public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly) { + throw new NotImplementedException(); + } + + public override bool WillProcess(ICompiledAssembly compiledAssembly) { + return false; + } + } +#endif +} +#endif + + +#endregion + + +#region Assets/Photon/Fusion/CodeGen/ILWeaverBindings.UnityEditor.cs + +#if FUSION_WEAVER && !FUSION_WEAVER_ILPOSTPROCESSOR + +namespace Fusion.CodeGen { +#if FUSION_HAS_MONO_CECIL + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + using System.Linq; + using Mono.Cecil; + using UnityEditor; + using UnityEditor.Build; + using UnityEditor.Build.Reporting; + using UnityEditor.Compilation; + using CompilerAssembly = UnityEditor.Compilation.Assembly; + + class ILWeaverBindings { + + public static bool IsEditorAssemblyPath(string path) { + return path.Contains("-Editor") || path.Contains(".Editor"); + } + + [UnityEditor.InitializeOnLoadMethod] + public static void InitializeOnLoad() { + CompilationPipeline.assemblyCompilationFinished += OnCompilationFinished; + EditorApplication.playModeStateChanged += OnPlayModeStateChanged; + } + + static void OnPlayModeStateChanged(PlayModeStateChange state) { + var projectConfig = NetworkProjectConfig.Global; + + // exit edit mode means play mode is about to start ... + if (state == PlayModeStateChange.ExitingEditMode) { + foreach (var assembly in CompilationPipeline.GetAssemblies()) { + var name = Path.GetFileNameWithoutExtension(assembly.outputPath); + if (ILWeaverSettings.IsAssemblyWeavable(projectConfig.AssembliesToWeave, name)) { + OnCompilationFinished(assembly.outputPath, new CompilerMessage[0]); + } + } + } + } + + static void OnCompilationFinished(string path, CompilerMessage[] messages) { +#if FUSION_DEV + Stopwatch sw = Stopwatch.StartNew(); + Log.Debug($"OnCompilationFinished({path})"); +#endif + + // never modify editor assemblies + if (IsEditorAssemblyPath(path)) { + return; + } + + var projectConfig = NetworkProjectConfig.Global; + if (projectConfig != null) { + // name of assembly on disk + var name = Path.GetFileNameWithoutExtension(path); + if (!ILWeaverSettings.IsAssemblyWeavable(projectConfig.AssembliesToWeave, name)) { + return; + } + } + + // errors means we should exit + if (messages.Any(x => x.type == CompilerMessageType.Error)) { +#if FUSION_DEV + Log.Error($"Can't execute ILWeaver on {path}, compilation errors exist."); +#endif + return; + } + + // grab compiler pipe assembly + var asm = CompilationPipeline.GetAssemblies().First(x => x.outputPath == path); + + // needs to reference phoenix runtime + if (ILWeaverSettings.ContainsRequiredReferences(asm.allReferences) == false) { + return; + } + + // perform weaving + try { + var settings = new ILWeaverSettings() { + AccuracyDefaults = projectConfig.AccuracyDefaults, + CheckNetworkedPropertiesBeingEmpty = projectConfig.CheckNetworkedPropertiesBeingEmpty, + CheckRpcAttributeUsage = projectConfig.CheckRpcAttributeUsage, + NullChecksForNetworkedProperties = projectConfig.NullChecksForNetworkedProperties, + UseSerializableDictionary = projectConfig.UseSerializableDictionary, + AssembliesToWeave = projectConfig.AssembliesToWeave, + }; + + var weaver = new ILWeaver(settings, new ILWeaverLoggerUnityDebug()); + Weave(weaver, asm); + } catch (Exception ex) { + UnityEngine.Debug.LogError(ex); + } + +#if FUSION_DEV + UnityEngine.Debug.Log($"OnCompilationFinished took: {sw.Elapsed}"); +#endif + } + + + static void Weave(ILWeaver weaver, Assembly compilerAssembly) { + using (weaver.Log.Scope("Processing")) { + + using (var resolver = new ILWeaverAssemblyResolver()) { + // if we're already weaving this don't do anything + if (resolver.Contains(compilerAssembly)) { + return; + } + + // make sure we can load all dlls + foreach (string path in compilerAssembly.allReferences) { + resolver.AddSearchDirectory(Path.GetDirectoryName(path)); + } + + // make sure we have the runtime dll loaded + if (!ILWeaverSettings.ContainsRequiredReferences(compilerAssembly.allReferences)) { + throw new InvalidOperationException($"Weaving: Could not find required assembly references"); + } + + ILWeaverAssembly asm; + + using (weaver.Log.Scope("Resolving")) { + asm = resolver.AddAssembly(compilerAssembly); + } + + if (weaver.Weave(asm)) { + + using (weaver.Log.Scope("Writing")) { + // write asm to disk + asm.CecilAssembly.Write(new WriterParameters { + WriteSymbols = true + }); + } + } + } + } + } + } +#else + class ILWeaverBindings { + [UnityEditor.InitializeOnLoadMethod] + public static void InitializeOnLoad() { + UnityEngine.Debug.LogError("Mono.Cecil not found, Fusion IL weaving is disabled. Make sure package com.unity.nuget.mono-cecil is installed."); + } + } +#endif +} +#endif + +#endregion + + +#region Assets/Photon/Fusion/CodeGen/ILWeaverException.cs + +namespace Fusion.CodeGen { + using System; + using System.Diagnostics; + + public class ILWeaverException : Exception { + public ILWeaverException(string error) : base(error) { + } + + public ILWeaverException(string error, Exception innerException) : base(error, innerException) { + } + + + [Conditional("UNITY_EDITOR")] + public static void DebugThrowIf(bool condition, string message) { + if (condition) { + throw new ILWeaverException(message); + } + } + } +} + +#endregion + + +#region Assets/Photon/Fusion/CodeGen/ILWeaverExtensions.cs + +#if FUSION_WEAVER && FUSION_HAS_MONO_CECIL +namespace Fusion.CodeGen { + + using System; + using System.Collections.Generic; + using System.Linq; + using Mono.Cecil; + using Mono.Cecil.Cil; + using BindingFlags = System.Reflection.BindingFlags; + + public static class ILWeaverExtensions { + + public static bool IsIntegral(this TypeReference type) { + switch (type.MetadataType) { + case MetadataType.Byte: + case MetadataType.SByte: + case MetadataType.UInt16: + case MetadataType.Int16: + case MetadataType.UInt32: + case MetadataType.Int32: + case MetadataType.UInt64: + case MetadataType.Int64: + return true; + default: + return false; + } + } + + public static int GetPrimitiveSize(this TypeReference type) { + switch (type.MetadataType) { + case MetadataType.Byte: + case MetadataType.SByte: + return sizeof(byte); + case MetadataType.UInt16: + case MetadataType.Int16: + return sizeof(short); + case MetadataType.UInt32: + case MetadataType.Int32: + return sizeof(int); + case MetadataType.UInt64: + case MetadataType.Int64: + return sizeof(long); + case MetadataType.Single: + return sizeof(float); + case MetadataType.Double: + return sizeof(double); + default: + throw new ArgumentException($"Unknown primitive type: {type}", nameof(type)); + } + } + + public static Type GetPrimitiveType(this TypeReference type) { + switch (type.MetadataType) { + case MetadataType.Byte: + return typeof(byte); + case MetadataType.SByte: + return typeof(sbyte); + case MetadataType.UInt16: + return typeof(ushort); + case MetadataType.Int16: + return typeof(short); + case MetadataType.UInt32: + return typeof(uint); + case MetadataType.Int32: + return typeof(int); + case MetadataType.UInt64: + return typeof(ulong); + case MetadataType.Int64: + return typeof(long); + case MetadataType.Single: + return typeof(float); + case MetadataType.Double: + return typeof(double); + case MetadataType.Boolean: + return typeof(bool); + case MetadataType.Char: + return typeof(char); + default: + throw new ArgumentException($"Unknown primitive type: {type}", nameof(type)); + } + } + + public static bool IsString(this TypeReference type) { + return type.MetadataType == MetadataType.String; + } + + public static bool IsFloat(this TypeReference type) { + return type.MetadataType == MetadataType.Single; + } + + public static bool IsBool(this TypeReference type) { + return type.MetadataType == MetadataType.Boolean; + } + + public static bool IsVector2(this TypeReference type) { + return type.FullName == "UnityEngine.Vector2"; + } + + public static bool IsVector3(this TypeReference type) { + return type.FullName == "UnityEngine.Vector3"; + } + + public static bool IsVector4(this TypeReference type) { + return type.FullName == "UnityEngine.Vector4"; + } + + public static bool IsQuaternion(this TypeReference type) { + return type.FullName == "UnityEngine.Quaternion"; + } + + public static bool IsVoid(this TypeReference type) { + return type.MetadataType == MetadataType.Void; + } + + public static TypeReference GetElementTypeWithGenerics(this TypeReference type) { + if (type.IsPointer) { + return ((Mono.Cecil.PointerType)type).ElementType; + } else if (type.IsByReference) { + return ((Mono.Cecil.ByReferenceType)type).ElementType; + } else if (type.IsArray) { + return ((Mono.Cecil.ArrayType)type).ElementType; + } else { + return type.GetElementType(); + } + } + + public static bool IsSubclassOf(this TypeReference type) { + return !IsSame(type) && Is(type); + } + + public static bool IsNetworkCollection(this TypeReference type) { + return type.IsNetworkArray(out _) || type.IsNetworkList(out _) || type.IsNetworkDictionary(out _, out _); + } + + public static bool IsNetworkList(this TypeReference type, out TypeReference elementType) { + if (!type.IsGenericInstance || type.GetElementTypeWithGenerics().FullName != typeof(NetworkLinkedList<>).FullName) { + elementType = default; + return false; + } + + var git = (GenericInstanceType)type; + elementType = git.GenericArguments[0]; + return true; + } + + + public static bool IsNetworkArray(this TypeReference type, out TypeReference elementType) { + if (!type.IsGenericInstance || type.GetElementTypeWithGenerics().FullName != typeof(NetworkArray<>).FullName) { + elementType = default; + return false; + } + + var git = (GenericInstanceType)type; + elementType = git.GenericArguments[0]; + return true; + } + + public static bool IsNetworkDictionary(this TypeReference type, out TypeReference keyType, out TypeReference valueType) { + if (!type.IsGenericInstance || type.GetElementTypeWithGenerics().FullName != typeof(NetworkDictionary<,>).FullName) { + keyType = default; + valueType = default; + return false; + } + + var git = (GenericInstanceType)type; + keyType = git.GenericArguments[0]; + valueType = git.GenericArguments[1]; + return true; + } + + public static bool Is(this TypeReference type) { + return Is(type, typeof(T)); + } + + public static bool Is(this TypeReference type, Type t) { + if (IsSame(type, t)) { + return true; + } + + var resolvedType = type.Resolve(); + if (resolvedType == null) { + throw new InvalidOperationException($"Failed to resolve {type}"); + } + + if (t.IsInterface) { + + foreach (var interf in resolvedType.Interfaces) { + if (interf.InterfaceType.IsSame(t)) { + return true; + } + } + return false; + } else { + if (resolvedType.BaseType == null) { + return false; + } + return Is(resolvedType.BaseType, t); + } + } + + public static bool Is(this TypeReference type, TypeReference t) { + + if (IsSame(type, t)) { + return true; + } + + var resolvedType = type.Resolve(); + if (IsSame(resolvedType, t)) { + return true; + } + + if (t is GenericParameter genericParameter) { + foreach (var constraint in genericParameter.Constraints) { +#if FUSION_CECIL_1_11_OR_NEWER + if (!Is(type, constraint.ConstraintType)) { +#else + if (!Is(type, constraint)) { +#endif + return false; + } + } + return true; + } + + if (t.Resolve().IsInterface == true) { + if (resolvedType == null) { + return false; + } + + foreach (var interf in resolvedType.Interfaces) { + if (interf.InterfaceType.IsSame(t)) { + return true; + } + } + return false; + } else { + if (resolvedType.BaseType == null) { + return false; + } + return Is(resolvedType.BaseType, t); + } + } + + public static bool IsSame(this TypeReference type) { + return IsSame(type, typeof(T)); + } + + public static bool IsSame(this TypeReference type, Type t) { + if (type == null) { + throw new ArgumentNullException(nameof(type)); + } + if (type.IsByReference) { + type = type.GetElementTypeWithGenerics(); + } + if (type.IsVoid() && t == typeof(void)) { + return true; + } + if (type.IsValueType != t.IsValueType) { + return false; + } + if (type.IsNested != t.IsNested) { + return false; + } + + if (t.IsNested) { + if (type.Name != t.Name || !IsSame(type.DeclaringType, t.DeclaringType)) { + return false; + } + } else { + if (type.FullName != t.FullName) { + return false; + } + } + return true; + } + + public static bool IsSame(this TypeReference type, TypeOrTypeRef t) { + if (t.Type != null) { + return IsSame(type, t.Type); + } else if (t.TypeReference != null) { + return IsSame(type, t.TypeReference); + } else { + throw new InvalidOperationException(); + } + } + + public static bool IsSame(this TypeReference type, TypeReference t) { + if (type == null) { + throw new ArgumentNullException(nameof(type)); + } + if (type.IsByReference) { + type = type.GetElementTypeWithGenerics(); + } + if (type.IsValueType != t.IsValueType) { + return false; + } + if (type.FullName != t.FullName) { + return false; + } + return true; + } + + + public static IEnumerable GetHierarchy(this TypeDefinition type, TypeReference stopAtBaseType = null) { + if (stopAtBaseType?.IsSame(type) == true) { + yield break; + } + + for (; ; ) { + yield return type; + if (type.BaseType == null || stopAtBaseType?.IsSame(type.BaseType) == true) { + break; + } + type = type.BaseType.Resolve(); + } + } + + public static bool Remove(this FieldDefinition field) { + return field.DeclaringType.Fields.Remove(field); + } + + public static FieldDefinition GetFieldOrThrow(this TypeDefinition type, string fieldName) { + foreach (var field in type.Fields) { + if ( field.Name == fieldName ) { + return field; + } + } + throw new ArgumentOutOfRangeException(nameof(fieldName), $"Field {fieldName} not found in {type}"); + } + + public static MethodReference GetGenericInstanceMethodOrThrow(this GenericInstanceType type, string name) { + var methodRef = type.Resolve().GetMethodOrThrow(name); + + var newMethodRef = new MethodReference(methodRef.Name, methodRef.ReturnType) { + HasThis = methodRef.HasThis, + ExplicitThis = methodRef.ExplicitThis, + DeclaringType = type, + CallingConvention = methodRef.CallingConvention, + }; + + foreach (var parameter in methodRef.Parameters) { + newMethodRef.Parameters.Add(new ParameterDefinition(parameter.Name, parameter.Attributes, parameter.ParameterType)); + } + + foreach (var genericParameter in methodRef.GenericParameters) { + newMethodRef.GenericParameters.Add(new GenericParameter(genericParameter.Name, newMethodRef)); + } + + return newMethodRef; + } + + public static MethodReference GetCallable(this MethodReference methodRef, GenericInstanceType declaringType = null) { + + if (declaringType == null) { + if (!methodRef.DeclaringType.HasGenericParameters) { + return methodRef; + } + + declaringType = new GenericInstanceType(methodRef.DeclaringType); + foreach (var parameter in methodRef.DeclaringType.GenericParameters) { + declaringType.GenericArguments.Add(parameter); + } + } + + var newMethodRef = new MethodReference(methodRef.Name, methodRef.ReturnType, declaringType) { + HasThis = methodRef.HasThis, + ExplicitThis = methodRef.ExplicitThis, + CallingConvention = methodRef.CallingConvention + }; + + foreach (var parameter in methodRef.Parameters) { + newMethodRef.Parameters.Add(new ParameterDefinition(parameter.Name, parameter.Attributes, parameter.ParameterType)); + } + + foreach (var genericParameter in methodRef.GenericParameters) { + newMethodRef.GenericParameters.Add(new GenericParameter(genericParameter.Name, newMethodRef)); + } + + return newMethodRef; + } + + public static FieldReference GetLoadable(this FieldDefinition field) { + + if (!field.DeclaringType.HasGenericParameters) { + return field; + } + + var declaringType = new GenericInstanceType(field.DeclaringType); + foreach (var parameter in field.DeclaringType.GenericParameters) { + declaringType.GenericArguments.Add(parameter); + } + return new FieldReference(field.Name, field.FieldType, declaringType); + } + + public static void AddInterface(this TypeDefinition type, ILWeaverAssembly asm) { + type.Interfaces.Add(new InterfaceImplementation(asm.Import(typeof(T)))); + } + + public static bool RemoveAttribute(this IMemberDefinition member, ILWeaverAssembly asm) where T : Attribute { + for (int i = 0; i < member.CustomAttributes.Count; ++i) { + var attr = member.CustomAttributes[i]; + if ( attr.AttributeType.Is() ) { + member.CustomAttributes.RemoveAt(i); + return true; + } + } + return false; + } + + public static CustomAttribute AddAttribute(this IMemberDefinition member, ILWeaverAssembly asm) where T : Attribute { + CustomAttribute attr; + attr = new CustomAttribute(typeof(T).GetConstructor(asm)); + member.CustomAttributes.Add(attr); + return attr; + } + + public static CustomAttribute AddAttribute(this IMemberDefinition member, ModuleDefinition module = null) where T : Attribute { + CustomAttribute attr; + attr = new CustomAttribute(typeof(T).GetConstructor(module ?? member.DeclaringType.Module)); + member.CustomAttributes.Add(attr); + return attr; + } + + public static CustomAttribute AddAttribute(this IMemberDefinition member, ILWeaverAssembly asm, A0 arg0) where T : Attribute { + CustomAttribute attr; + attr = new CustomAttribute(typeof(T).GetConstructor(asm, 1)); + attr.ConstructorArguments.Add(new CustomAttributeArgument(asm.ImportAttributeType(), arg0)); + member.CustomAttributes.Add(attr); + return attr; + } + + public static CustomAttribute AddAttribute(this IMemberDefinition member, ILWeaverAssembly asm, A0 arg0, A1 arg1) where T : Attribute { + CustomAttribute attr; + attr = new CustomAttribute(typeof(T).GetConstructor(asm, 2)); + attr.ConstructorArguments.Add(new CustomAttributeArgument(asm.ImportAttributeType(), arg0)); + attr.ConstructorArguments.Add(new CustomAttributeArgument(asm.ImportAttributeType(), arg1)); + member.CustomAttributes.Add(attr); + return attr; + } + + public static CustomAttribute AddAttribute(this IMemberDefinition member, ILWeaverAssembly asm, A0 arg0, A1 arg1, A2 arg2) where T : Attribute { + CustomAttribute attr; + attr = new CustomAttribute(typeof(T).GetConstructor(asm, 3)); + attr.ConstructorArguments.Add(new CustomAttributeArgument(asm.ImportAttributeType(), arg0)); + attr.ConstructorArguments.Add(new CustomAttributeArgument(asm.ImportAttributeType(), arg1)); + attr.ConstructorArguments.Add(new CustomAttributeArgument(asm.ImportAttributeType(), arg2)); + member.CustomAttributes.Add(attr); + return attr; + } + + public static CustomAttribute AddAttribute(this IMemberDefinition member, ModuleDefinition module, A0 arg0) where T : Attribute { + CustomAttribute attr; + attr = new CustomAttribute(typeof(T).GetConstructor(module, 1)); + attr.ConstructorArguments.Add(new CustomAttributeArgument(module.ImportAttributeType(), arg0)); + member.CustomAttributes.Add(attr); + return attr; + } + + public static CustomAttribute AddAttribute(this IMemberDefinition member, ModuleDefinition module, A0 arg0, A1 arg1) where T : Attribute { + CustomAttribute attr; + attr = new CustomAttribute(typeof(T).GetConstructor(module, 2)); + attr.ConstructorArguments.Add(new CustomAttributeArgument(module.ImportAttributeType(), arg0)); + attr.ConstructorArguments.Add(new CustomAttributeArgument(module.ImportAttributeType(), arg1)); + member.CustomAttributes.Add(attr); + return attr; + } + + public static CustomAttribute AddAttribute(this IMemberDefinition member, ModuleDefinition module, A0 arg0, A1 arg1, A2 arg2) where T : Attribute { + CustomAttribute attr; + attr = new CustomAttribute(typeof(T).GetConstructor(module, 3)); + attr.ConstructorArguments.Add(new CustomAttributeArgument(module.ImportAttributeType(), arg0)); + attr.ConstructorArguments.Add(new CustomAttributeArgument(module.ImportAttributeType(), arg1)); + attr.ConstructorArguments.Add(new CustomAttributeArgument(module.ImportAttributeType(), arg2)); + member.CustomAttributes.Add(attr); + return attr; + } + + public static CustomAttribute AddAttribute(this IMemberDefinition member, ModuleDefinition module, A0 arg0, A1 arg1, A2 arg2, A3 arg3) where T : Attribute { + CustomAttribute attr; + attr = new CustomAttribute(typeof(T).GetConstructor(module, 4)); + attr.ConstructorArguments.Add(new CustomAttributeArgument(module.ImportAttributeType(), arg0)); + attr.ConstructorArguments.Add(new CustomAttributeArgument(module.ImportAttributeType(), arg1)); + attr.ConstructorArguments.Add(new CustomAttributeArgument(module.ImportAttributeType(), arg2)); + attr.ConstructorArguments.Add(new CustomAttributeArgument(module.ImportAttributeType(), arg3)); + member.CustomAttributes.Add(attr); + return attr; + } + + public static CustomAttribute AddAttribute(this IMemberDefinition member, ModuleDefinition module, A0 arg0, A1 arg1, A2 arg2, A3 arg3, A4 arg4) where T : Attribute { + CustomAttribute attr; + attr = new CustomAttribute(typeof(T).GetConstructor(module, 5)); + attr.ConstructorArguments.Add(new CustomAttributeArgument(module.ImportAttributeType(), arg0)); + attr.ConstructorArguments.Add(new CustomAttributeArgument(module.ImportAttributeType(), arg1)); + attr.ConstructorArguments.Add(new CustomAttributeArgument(module.ImportAttributeType(), arg2)); + attr.ConstructorArguments.Add(new CustomAttributeArgument(module.ImportAttributeType(), arg3)); + attr.ConstructorArguments.Add(new CustomAttributeArgument(module.ImportAttributeType(), arg4)); + member.CustomAttributes.Add(attr); + return attr; + } + + public static CustomAttribute AddAttribute(this IMemberDefinition member, ILWeaverAssembly asm, A0 arg0, A1 arg1, A2 arg2, A3 arg3) where T : Attribute { + CustomAttribute attr; + + // TODO: this is inconsistent with other AddAttribute, but needed for DrawIfAttribute to work + var constructor = typeof(T).GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, new Type[] { typeof(A0), typeof(A1), typeof(A2), typeof(A3) }, null); + attr = new CustomAttribute(asm.CecilAssembly.MainModule.ImportReference(constructor)); + attr.ConstructorArguments.Add(new CustomAttributeArgument(asm.ImportAttributeType(), arg0)); + attr.ConstructorArguments.Add(new CustomAttributeArgument(asm.ImportAttributeType(), arg1)); + attr.ConstructorArguments.Add(new CustomAttributeArgument(asm.ImportAttributeType(), arg2)); + attr.ConstructorArguments.Add(new CustomAttributeArgument(asm.ImportAttributeType(), arg3)); + member.CustomAttributes.Add(attr); + return attr; + } + + private static TypeReference ImportAttributeType(this ILWeaverAssembly asm) { + if (typeof(T) == typeof(TypeReference)) { + return asm.Import(); + } else { + return asm.Import(); + } + } + + private static TypeReference ImportAttributeType(this ModuleDefinition module) { + if (typeof(T) == typeof(TypeReference)) { + return module.ImportReference(typeof(Type)); + } else { + return module.ImportReference(typeof(T)); + } + } + + public static MethodReference GetConstructor(this Type type, ILWeaverAssembly asm, int argCount = 0) { + foreach (var ctor in type.GetConstructors()) { + if (ctor.GetParameters().Length == argCount) { + return asm.CecilAssembly.MainModule.ImportReference(ctor); + } + } + + throw new ILWeaverException($"Could not find constructor with {argCount} arguments on {type.Name}"); + } + + public static MethodReference GetConstructor(this Type type, ModuleDefinition module, int argCount = 0) { + foreach (var ctor in type.GetConstructors()) { + if (ctor.GetParameters().Length == argCount) { + return module.ImportReference(ctor); + } + } + + throw new ILWeaverException($"Could not find constructor with {argCount} arguments on {type.Name}"); + } + + public static void AddTo(this MethodDefinition method, TypeDefinition type) { + type.Methods.Add(method); + method.AddAttribute(); + } + + public static void AddTo(this PropertyDefinition property, TypeDefinition type) { + type.Properties.Add(property); + property.AddAttribute(); + } + + public static FieldDefinition AddTo(this FieldDefinition field, TypeDefinition type) { + type.Fields.Add(field); + field.AddAttribute(); + return field; + } + + public static void InsertTo(this FieldDefinition field, TypeDefinition type, int index) { + type.Fields.Insert(index, field); + field.AddAttribute(); + } + + public static void AddTo(this TypeDefinition type, AssemblyDefinition assembly) { + assembly.MainModule.Types.Add(type); + type.AddAttribute(assembly.MainModule); + } + + public static void AddTo(this TypeDefinition type, TypeDefinition parentType) { + parentType.NestedTypes.Add(type); + type.AddAttribute(); + } + + public static Instruction AppendReturn(this ILProcessor il, Instruction instruction) { + il.Append(instruction); + return instruction; + } + + public static void Clear(this ILProcessor il) { + var instructions = il.Body.Instructions; + foreach (var instruction in instructions.Reverse()) { + il.Remove(instruction); + } + } + + public static void AppendMacro(this ILProcessor il, in T macro) where T : struct, ILProcessorMacro { + macro.Emit(il); + } + + public static bool GetSingleOrDefaultMethodWithAttribute(this TypeDefinition type, out CustomAttribute attribute, out MethodDefinition method) where T : Attribute { + + MethodDefinition resultMethod = null; + CustomAttribute resultAttribute = null; + + foreach (var m in type.Methods) { + if (m.TryGetAttribute(out var attr)) { + if (resultMethod != null) { + throw new ILWeaverException($"Only one method with attribute {typeof(T)} allowed per class: {type}"); + } else { + resultMethod = m; + resultAttribute = attr; + } + } + } + + method = resultMethod; + attribute = resultAttribute; + return method != null; + } + + + public static PropertyDefinition ThrowIfStatic(this PropertyDefinition property) { + if (property.GetMethod?.IsStatic == true || + property.SetMethod?.IsStatic == true) { + throw new ILWeaverException($"Property is static: {property.FullName}"); + } + return property; + } + + public static PropertyDefinition ThrowIfNoGetter(this PropertyDefinition property) { + if (property.GetMethod == null) { + throw new ILWeaverException($"Property does not have a getter: {property.FullName}"); + } + return property; + } + + public static PropertyDefinition ThrowIfNoSetter(this PropertyDefinition property) { + if (property.GetMethod == null) { + throw new ILWeaverException($"Property does not have a getter: {property.FullName}"); + } + return property; + } + + + public static MethodDefinition ThrowIfStatic(this MethodDefinition method) { + if (method.IsStatic) { + throw new ILWeaverException($"Method is static: {method.FullName}"); + } + return method; + } + + public static MethodDefinition ThrowIfNotStatic(this MethodDefinition method) { + if (!method.IsStatic) { + throw new ILWeaverException($"Method is not static: {method}"); + } + return method; + } + + public static MethodDefinition ThrowIfNotPublic(this MethodDefinition method) { + if (!method.IsPublic) { + throw new ILWeaverException($"Method is not public: {method}"); + } + return method; + } + + public static MethodDefinition ThrowIfReturnType(this MethodDefinition method, TypeOrTypeRef type) { + if (!method.ReturnType.IsSame(type)) { + + throw new ILWeaverException($"Method has an invalid return type (expected {type}): {method}"); + } + return method; + } + + public static MethodDefinition ThrowIfParameterCount(this MethodDefinition method, int count) { + if (method.Parameters.Count != count) { + throw new ILWeaverException($"Method has invalid parameter count (expected {count}): {method}"); + } + return method; + } + + public static MethodDefinition ThrowIfParameterCountLessThan(this MethodDefinition method, int count) { + if (method.Parameters.Count < count) { + throw new ILWeaverException($"Method has invalid parameter count (expected at leaset {count}): {method}"); + } + return method; + } + + public static MethodDefinition ThrowIfParameter(this MethodDefinition method, int index, TypeOrTypeRef type = null, bool isByReference = false, bool ignore = false) { + if (ignore) { + return method; + } + var p = method.Parameters[index]; + if (type != null && !p.ParameterType.IsSame(type)) { + throw new ILWeaverException($"Parameter {p} ({index}) has an invalid type (expected {type}): {method}"); + } + if (p.ParameterType.IsByReference != isByReference) { + if (p.IsOut) { + throw new ILWeaverException($"Parameter {p} ({index}) is a ref parameter: {method}"); + } else { + throw new ILWeaverException($"Parameter {p} ({index}) is not a ref parameter: {method}"); + } + } + return method; + } + + public static bool IsBaseConstructorCall(this Instruction instruction, TypeDefinition type) { + if (instruction.OpCode == OpCodes.Call) { + var m = ((MethodReference)instruction.Operand).Resolve(); + if (m.IsConstructor && m.DeclaringType.IsSame(type.BaseType)) { + // base constructor init + return true; + } + } + return false; + } + + public static bool IsLdloca(this Instruction instruction, out VariableDefinition variable, out bool isShort) { + if (instruction.OpCode == OpCodes.Ldloca) { + variable = (VariableDefinition)instruction.Operand; + isShort = false; + return true; + } + if (instruction.OpCode == OpCodes.Ldloca_S) { + variable = (VariableDefinition)instruction.Operand; + isShort = true; + return true; + } + + variable = default; + isShort = default; + return false; + } + + public static bool IsLdlocWithIndex(this Instruction instruction, out int index) { + if (instruction.OpCode == OpCodes.Ldloc_0) { + index = 0; + return true; + } + if (instruction.OpCode == OpCodes.Ldloc_1) { + index = 1; + return true; + } + if (instruction.OpCode == OpCodes.Ldloc_2) { + index = 2; + return true; + } + if (instruction.OpCode == OpCodes.Ldloc_3) { + index = 3; + return true; + } + index = -1; + return false; + } + + public static bool IsStlocWithIndex(this Instruction instruction, out int index) { + if (instruction.OpCode == OpCodes.Stloc_0) { + index = 0; + return true; + } + if (instruction.OpCode == OpCodes.Stloc_1) { + index = 1; + return true; + } + if (instruction.OpCode == OpCodes.Stloc_2) { + index = 2; + return true; + } + if (instruction.OpCode == OpCodes.Stloc_3) { + index = 3; + return true; + } + index = -1; + return false; + } + } + + public class TypeOrTypeRef { + + public Type Type { get; } + public TypeReference TypeReference { get; } + + + public TypeOrTypeRef(Type type, bool isOut = false) { + Type = type; + } + + public TypeOrTypeRef(TypeReference type, bool isOut = false) { + TypeReference = type; + } + + public static implicit operator TypeOrTypeRef(Type type) { + return new TypeOrTypeRef(type); + } + + public static implicit operator TypeOrTypeRef(TypeReference type) { + return new TypeOrTypeRef(type); + } + + public override string ToString() { + if (Type != null) { + return Type.FullName; + } else if (TypeReference != null) { + return TypeReference.ToString(); + } else { + return "AnyType"; + } + } + } + + public interface ILProcessorMacro { + void Emit(ILProcessor il); + } +} +#endif + + +#endregion + + +#region Assets/Photon/Fusion/CodeGen/ILWeaverLog.cs + +#if FUSION_WEAVER && FUSION_HAS_MONO_CECIL + +namespace Fusion.CodeGen { + + using System; + using System.Diagnostics; + using System.Runtime.CompilerServices; + using Mono.Cecil; + + public interface ILWeaverLogger { + void Log(LogLevel logType, string message, string filePath, int lineNumber); + void Log(Exception ex); + } + + + public sealed class ILWeaverLog { + + private ILWeaverLogger _logger; + + public ILWeaverLogger Logger => _logger; + + public ILWeaverLog(ILWeaverLogger logger) { + if (logger == null) { + throw new ArgumentNullException(nameof(logger)); + } + _logger = logger; + } + + public void AssertMessage(bool condition, string message, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = default) { + if (!condition) { + _logger.Log(LogLevel.Error, $"Assert failed: {message}", filePath, lineNumber); + throw new AssertException($"{message}{(string.IsNullOrEmpty(filePath) ? "" : $" at {filePath}:{lineNumber}")}"); + } + } + + public void Assert(bool condition, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = default) { + if (!condition) { + _logger.Log(LogLevel.Error, $"Assert failed", filePath, lineNumber); + throw new AssertException($"Assert failed{(string.IsNullOrEmpty(filePath) ? "" : $" at {filePath}:{lineNumber}")}"); + } + } + + [Conditional("FUSION_WEAVER_DEBUG")] + public void Debug(string message, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = default) { + _logger.Log(LogLevel.Debug, message, filePath, lineNumber); + } + + + + public void Warn(MethodDefinition method, string message, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = default) { + TryOverrideLocation(method, ref filePath, ref lineNumber); + _logger.Log(LogLevel.Warn, message, filePath, lineNumber); + } + + public void Warn(PropertyDefinition property, string message, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = default) { + TryOverrideLocation(property.GetMethod, ref filePath, ref lineNumber); + _logger.Log(LogLevel.Warn, message, filePath, lineNumber); + } + + public void Warn(string message, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = default) { + _logger.Log(LogLevel.Warn, message, filePath, lineNumber); + } + + public void Error(string message, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = default) { + _logger.Log(LogLevel.Error, message, filePath, lineNumber); + } + + public void Exception(Exception ex) { + _logger.Log(ex); + } + +#if !FUSION_WEAVER_DEBUG + public struct LogScope : IDisposable { + public void Dispose() { + } + } + + public LogScope Scope(string name) { + return default; + } + + public LogScope ScopeAssembly(AssemblyDefinition cecilAssembly) { + return default; + } + + public LogScope ScopeBehaviour(TypeDefinition type) { + return default; + } + + public LogScope ScopeInput(TypeDefinition type) { + return default; + } + + public LogScope ScopeStruct(TypeDefinition type) { + return default; + } + +#else + public LogScope Scope(string name, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = default) { + return new LogScope(this, name, filePath, lineNumber); + } + + public LogScope ScopeAssembly(AssemblyDefinition cecilAssembly, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = default) { + return new LogScope(this, $"Assembly: {cecilAssembly.FullName}", filePath, lineNumber); + } + + public LogScope ScopeBehaviour(TypeDefinition type, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = default) { + return new LogScope(this, $"Behaviour: {type.FullName}", filePath, lineNumber); + } + + public LogScope ScopeInput(TypeDefinition type, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = default) { + return new LogScope(this, $"Input: {type.FullName}", filePath, lineNumber); + } + + public LogScope ScopeStruct(TypeDefinition type, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = default) { + return new LogScope(this, $"Struct: {type.FullName}", filePath, lineNumber); + } + +#pragma warning disable CS0282 // There is no defined ordering between fields in multiple declarations of partial struct + public partial struct LogScope : IDisposable { +#pragma warning restore CS0282 // There is no defined ordering between fields in multiple declarations of partial struct + public string Message; + + public TimeSpan Elapsed => _stopwatch.Elapsed; + + private ILWeaverLog _log; + private Stopwatch _stopwatch; + + public int LineNumber; + public string FilePath; + + public LogScope(ILWeaverLog log, string message, [CallerFilePath] string filePath = null, [CallerLineNumber] int lineNumber = default) : this() { + _log = log; + _stopwatch = Stopwatch.StartNew(); + Message = message; + LineNumber = lineNumber; + FilePath = filePath; + _log.Debug($"{Message} start", FilePath, LineNumber); + } + + public void Dispose() { + _stopwatch.Stop(); + _log.Debug($"{Message} end {Elapsed}", FilePath, LineNumber); + } + } +#endif + + private static bool TryOverrideLocation(MethodDefinition method, ref string filePath, ref int lineNumber) { + var debugInformation = method?.DebugInformation; + if (debugInformation?.HasSequencePoints == true) { + var sequencePoint = debugInformation.SequencePoints[0]; + filePath = sequencePoint.Document.Url; + lineNumber = sequencePoint.StartLine; + return true; + } + + return false; + } + + } +} + +#endif + +#endregion + + +#region Assets/Photon/Fusion/CodeGen/ILWeaverLoggerDiagnosticMessages.cs + +#if FUSION_WEAVER && FUSION_WEAVER_ILPOSTPROCESSOR && FUSION_HAS_MONO_CECIL + +namespace Fusion.CodeGen { + using System; + using System.Collections.Generic; + using Unity.CompilationPipeline.Common.Diagnostics; + + class ILWeaverLoggerDiagnosticMessages : ILWeaverLogger { + + public List Messages { get; } = new List(); + + public void Log(LogLevel logType, string message, string filePath, int lineNumber) { + + DiagnosticType diagnosticType; + + if (logType == LogLevel.Debug) { + // there are no debug diagnostic messages, make pretend warnings + message = $"DEBUG: {message}"; + diagnosticType = DiagnosticType.Warning; + } else if (logType == LogLevel.Info) { + message = $"INFO: {message}"; + diagnosticType = DiagnosticType.Warning; + } else if (logType == LogLevel.Warn) { + diagnosticType = DiagnosticType.Warning; + } else { + diagnosticType = DiagnosticType.Error; + } + + // newlines in messagedata will need to be escaped, but let's not slow things down now + Messages.Add(new DiagnosticMessage() { + File = filePath, + Line = lineNumber, + DiagnosticType = diagnosticType, + MessageData = message + }); + } + + public void Log(Exception ex) { + var lines = ex.ToString().Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries); + foreach (var line in lines) { + Log(LogLevel.Error, line, null, 0); + } + } + + public void FixNewLinesInMessages() { + // fix the messages + foreach (var msg in Messages) { + msg.MessageData = msg.MessageData.Replace('\r', ';').Replace('\n', ';'); + } + } + } +} + +#endif + +#endregion + + +#region Assets/Photon/Fusion/CodeGen/ILWeaverLoggerUnityDebug.cs + +#if FUSION_WEAVER && FUSION_HAS_MONO_CECIL + +namespace Fusion.CodeGen { + + using System; + + public class ILWeaverLoggerUnityDebug : ILWeaverLogger { + + public void Log(LogLevel logType, string message, string filePath, int lineNumber) { + switch (logType) { + case LogLevel.Debug: + case LogLevel.Info: + UnityEngine.Debug.Log(message); + break; + case LogLevel.Warn: + UnityEngine.Debug.LogWarning(message); + break; + case LogLevel.Error: + UnityEngine.Debug.LogError(message); + break; + } + } + + public void Log(Exception ex) { + UnityEngine.Debug.unityLogger.LogException(ex); + } + } +} + +#endif + +#endregion + + +#region Assets/Photon/Fusion/CodeGen/ILWeaverMethodContext.cs + +#if FUSION_WEAVER && FUSION_HAS_MONO_CECIL + +namespace Fusion.CodeGen { + + using System; + using System.Collections.Generic; + using Mono.Cecil; + using Mono.Cecil.Cil; + using Mono.Cecil.Rocks; + using static Fusion.CodeGen.ILWeaverOpCodes; + + class MethodContext : IDisposable { + + public Action LoadElementReaderWriterImpl; + private Action _addressGetter; + private Dictionary<(string, string), VariableDefinition> _fields = new Dictionary<(string, string), VariableDefinition>(); + private bool _runnerIsLdarg0 = false; + protected Action _valueGetter; + protected Action _valueAddrGetter; + private TargetVariableAddrInfo _targetVariable; + + public MethodContext(ILWeaverAssembly assembly, MethodDefinition method, bool staticRunnerAccessor = false, + Action addressGetter = null, + Action valueGetter = null, + Action valueAddrGetter = null) { + if (assembly == null) { + throw new ArgumentNullException(nameof(assembly)); + } + if (method == null) { + throw new ArgumentNullException(nameof(method)); + } + + this.Assembly = assembly; + this.Method = method; + this._runnerIsLdarg0 = staticRunnerAccessor; + this._addressGetter = addressGetter; + this._valueGetter = valueGetter; + this._valueAddrGetter = valueAddrGetter; + } + + public ILWeaverAssembly Assembly { get; private set; } + public virtual bool HasOffset => false; + public virtual bool IsWriteCompact => false; + public MethodDefinition Method { get; private set; } + + public VariableDefinition AddVariable(TypeReference variableType) { + var variable = new VariableDefinition(variableType); + Method.Body.Variables.Add(variable); + return variable; + } + + public void Dispose() { + } + + public AddOffsetMacro AddOffset() => new AddOffsetMacro(this, null); + public AddOffsetMacro AddOffset(int value) => new AddOffsetMacro(this, Ldc_I4(value), isAligned: (value % Allocator.REPLICATE_WORD_SIZE) == 0); + + public ILMacroStruct AlignToWordSize() => new[] { + Ldc_I4(Allocator.REPLICATE_WORD_SIZE - 1), + Add(), + Ldc_I4(~(Allocator.REPLICATE_WORD_SIZE - 1)), + And() + }; + + public ForLoopMacro For(Action start, Action stop, Action body) => new ForLoopMacro(this, body, start, stop); + + public VariableDefinition GetOrCreateVariable(string id, TypeReference type, ILProcessor il = null) { + if (_fields.TryGetValue((id, type.FullName), out var val)) { + return val; + } + var result = CreateVariable(type, il); + _fields.Add((id, type.FullName), result); + return result; + } + + public VariableDefinition CreateVariable(TypeReference type, ILProcessor il = null, Instruction before = null) { + var result = new VariableDefinition(type); + Method.Body.Variables.Add(result); + if (il != null) { + if (before == null) { + if (type.IsValueType) { + il.Append(Ldloca(result)); + il.Append(Initobj(type)); + } else { + il.Append(Ldnull()); + il.Append(Stloc(result)); + } + } else { + if (type.IsValueType) { + il.InsertBefore(before, Ldloca(result)); + il.InsertBefore(before, Initobj(type)); + } else { + il.InsertBefore(before, Ldnull()); + il.InsertBefore(before, Stloc(result)); + } + } + } + return result; + } + + public virtual ILMacroStruct LoadAddress() => _addressGetter; + + public virtual ILMacroStruct LoadElementReaderWriter(TypeReference type, ICustomAttributeProvider member) => new Action(il => LoadElementReaderWriterImpl(il, type, member)); + + public ILMacroStruct LoadFixedBufferAddress(FieldDefinition fixedBufferField) => new Action(il => { + + var elementField = fixedBufferField.FieldType.Resolve().Fields[0]; + + int pointerLoc = il.Body.Variables.Count; + il.Body.Variables.Add(new VariableDefinition(elementField.FieldType.MakePointerType())); + int pinnedRefLoc = il.Body.Variables.Count; + il.Body.Variables.Add(new VariableDefinition(elementField.FieldType.MakeByReferenceType().MakePinnedType())); + + il.Append(Ldflda(fixedBufferField)); + il.Append(Ldflda(elementField)); + il.Append(Stloc(il.Body, pinnedRefLoc)); + il.Append(Ldloc(il.Body, pinnedRefLoc)); + il.Append(Conv_U()); + il.Append(Stloc(il.Body, pointerLoc)); + il.Append(Ldloc(il.Body, pointerLoc)); + }); + + public ILMacroStruct LoadRunner() { + return _runnerIsLdarg0 ? + new[] { Ldarg_0() } : + new[] { Ldarg_0(), Call(Assembly.SimulationBehaviour.GetGetterOrThrow(nameof(SimulationBehaviour.Runner))) }; + } + + public virtual ILMacroStruct LoadValue() => _valueGetter; + public virtual ILMacroStruct LoadValueAddr() => _valueAddrGetter; + public bool HasValueGetter => _valueGetter != null; + public bool HasValueAddrGetter => _valueAddrGetter != null; + + public ValueGetterScope ValueGetter(Action valueGetter) => new ValueGetterScope(this, valueGetter); + + public ValueGetterScope ValueGetter(Action> valueGetter) { + var current = _valueGetter; + return new ValueGetterScope(this, il => valueGetter(il, current)); + } + + public LoadVariableAddressMacro GetTargetVariableAddrOrTemp(TypeReference type, ILProcessor il, out VariableDefinition variable, Instruction before = null) { + if (_targetVariable.Variable == null || !_targetVariable.Type.IsSame(type)) { + variable = CreateVariable(type, il, before); + return new LoadVariableAddressMacro(variable, null, null); + } + + TargetAddrUsed = true; + variable = null; + + return new LoadVariableAddressMacro(_targetVariable.Variable, _targetVariable.IndexVariable, _targetVariable.Type); + } + + + public TargetVariableScope TargetVariableAddr(VariableDefinition variable) => new TargetVariableScope(this, new TargetVariableAddrInfo(variable)); + public TargetVariableScope TargetVariableAddr(VariableDefinition arrayVariable, VariableDefinition indexVariable, TypeReference type) => new TargetVariableScope(this, new TargetVariableAddrInfo(arrayVariable, indexVariable, type)); + + + public bool TargetAddrUsed { get; set; } + + + public ILMacroStruct VerifyRawNetworkUnwrap(TypeReference type, int maxByteCount) => new[] { + Ldc_I4(maxByteCount), + Call(new GenericInstanceMethod(Assembly.ReadWriteUtils.GetMethod(nameof(ReadWriteUtilsForWeaver.VerifyRawNetworkUnwrap), genericArgsCount: 1)) { + GenericArguments = { type } + }), + }; + + public ILMacroStruct VerifyRawNetworkWrap(TypeReference type, int maxByteCount) => new[] { + Ldc_I4(maxByteCount), + Call(new GenericInstanceMethod(Assembly.ReadWriteUtils.GetMethod(nameof(ReadWriteUtilsForWeaver.VerifyRawNetworkWrap), genericArgsCount: 1)) { + GenericArguments = { type } + }), + }; + + protected virtual void EmitAddOffsetAfter(ILProcessor il) { + } + + protected virtual void EmitAddOffsetBefore(ILProcessor il) { + } + public readonly struct AddOffsetMacro : ILProcessorMacro { + public readonly MethodContext Context; + public readonly Instruction Instruction; + public readonly bool IsAligned; + + + public AddOffsetMacro(MethodContext context, Instruction instruction = null, bool isAligned = false) { + Context = context; + Instruction = instruction; + IsAligned = isAligned; + } + + public void Emit(ILProcessor il) { + if (Context.HasOffset) { + if (Instruction == null) { + if (!IsAligned) { + il.AppendMacro(Context.AlignToWordSize()); + } + } + + Context.EmitAddOffsetBefore(il); + if (Instruction != null) { + il.Append(Instruction); + if (!IsAligned) { + il.AppendMacro(Context.AlignToWordSize()); + } + } + + Context.EmitAddOffsetAfter(il); + } else { + if (Instruction == null) { + // means variant with size already pushed has been used, pop it + il.Append(Pop()); + } + } + } + } + + public readonly struct TargetVariableAddrInfo { + public readonly VariableDefinition Variable; + public readonly VariableDefinition IndexVariable; + public readonly TypeReference Type; + + public TargetVariableAddrInfo(VariableDefinition variable) { + Variable = variable; + IndexVariable = null; + Type = variable.VariableType; + } + + public TargetVariableAddrInfo(VariableDefinition variable, VariableDefinition indexVariable, TypeReference elementType) { + Variable = variable; + IndexVariable = indexVariable; + Type = elementType; + } + } + + public readonly struct ForLoopMacro : ILProcessorMacro { + public readonly MethodContext Context; + public readonly Action Generator; + public readonly Action Start; + public readonly Action Stop; + + public ForLoopMacro(MethodContext context, Action generator, Action start, Action stop) { + Context = context; + Generator = generator; + Start = start; + Stop = stop; + } + + public void Emit(ILProcessor il) { + var body = Context.Method.Body; + var varId = body.Variables.Count; + var indexVariable = new VariableDefinition(Context.Assembly.Import(typeof(int))); + body.Variables.Add(indexVariable); + + Start(il); + il.Append(Stloc(body, varId)); + + var loopConditionStart = Ldloc(body, varId); + il.Append(Br_S(loopConditionStart)); + { + var loopBodyBegin = il.AppendReturn(Nop()); + Generator(il, indexVariable); + + il.Append(Ldloc(body, varId)); + il.Append(Ldc_I4(1)); + il.Append(Add()); + il.Append(Stloc(body, varId)); + + il.Append(loopConditionStart); + Stop(il); + il.Append(Blt_S(loopBodyBegin)); + } + } + } + + public struct ValueGetterScope : IDisposable { + MethodContext _context; + Action _oldValueGetter; + + public ValueGetterScope(MethodContext context, Action valueGetter) { + _context = context; + _oldValueGetter = context._valueGetter; + context._valueGetter = valueGetter; + } + + public void Dispose() { + _context._valueGetter = _oldValueGetter; + } + } + + public struct TargetVariableScope : IDisposable { + MethodContext _context; + TargetVariableAddrInfo _oldTargetVariable; + bool _wasUsed; + + public TargetVariableScope(MethodContext context, TargetVariableAddrInfo variable) { + _context = context; + _oldTargetVariable = context._targetVariable; + _wasUsed = context.TargetAddrUsed; + context._targetVariable = variable; + context.TargetAddrUsed = false; + } + + public void Dispose() { + _context._targetVariable = _oldTargetVariable; + _context.TargetAddrUsed = _wasUsed; + } + } + + public struct LoadVariableAddressMacro : ILProcessorMacro { + VariableDefinition _variable; + VariableDefinition _index; + TypeReference _elemType; + + public LoadVariableAddressMacro(VariableDefinition variable, VariableDefinition index, TypeReference elemType) { + _variable = variable; + _index = index; + _elemType = elemType; + } + + void ILProcessorMacro.Emit(ILProcessor il) { + if (_index == null) { + il.Append(Ldloca(_variable)); + } else { + il.Append(Ldloc(_variable)); + il.Append(Ldloc(_index)); + il.Append(Ldelema(_elemType)); + } + } + } + } + + class RpcMethodContext : MethodContext { + public VariableDefinition DataVariable; + public VariableDefinition OffsetVariable; + public VariableDefinition RpcInvokeInfoVariable; + + public RpcMethodContext(ILWeaverAssembly asm, MethodDefinition definition, bool staticRunnerAccessor) + : base(asm, definition, staticRunnerAccessor) { + } + + public override bool HasOffset => true; + public override bool IsWriteCompact => true; + + public override ILMacroStruct LoadAddress() => new[] { + Ldloc(DataVariable), + Ldloc(OffsetVariable), + Add(), + }; + + public ILMacroStruct SetRpcInvokeInfoStatus(bool emitIf, RpcLocalInvokeResult reason) => RpcInvokeInfoVariable == null || !emitIf ? new Instruction[0] : + new[] { + Ldloca(RpcInvokeInfoVariable), + Ldc_I4((int)reason), + Stfld(Assembly.RpcInvokeInfo.GetFieldOrThrow(nameof(RpcInvokeInfo.LocalInvokeResult))) + }; + + public ILMacroStruct SetRpcInvokeInfoStatus(RpcSendCullResult reason) => RpcInvokeInfoVariable == null ? new Instruction[0] : + new[] { + Ldloca(RpcInvokeInfoVariable), + Ldc_I4((int)reason), + Stfld(Assembly.RpcInvokeInfo.GetFieldOrThrow(nameof(RpcInvokeInfo.SendCullResult))) + }; + + protected override void EmitAddOffsetAfter(ILProcessor il) { + il.Append(Add()); + il.Append(Stloc(OffsetVariable)); + } + + protected override void EmitAddOffsetBefore(ILProcessor il) { + il.Append(Ldloc(OffsetVariable)); + } + } +} + +#endif + +#endregion + + +#region Assets/Photon/Fusion/CodeGen/ILWeaverOpCodes.cs + +#if FUSION_WEAVER && FUSION_HAS_MONO_CECIL +namespace Fusion.CodeGen { + using System.Reflection; + using Mono.Cecil; + using Mono.Cecil.Cil; + using MethodBody = Mono.Cecil.Cil.MethodBody; + + static class ILWeaverOpCodes { + // utils + public static Instruction Nop() => Instruction.Create(OpCodes.Nop); + public static Instruction Ret() => Instruction.Create(OpCodes.Ret); + public static Instruction Dup() => Instruction.Create(OpCodes.Dup); + public static Instruction Pop() => Instruction.Create(OpCodes.Pop); + public static Instruction Ldnull() => Instruction.Create(OpCodes.Ldnull); + public static Instruction Throw() => Instruction.Create(OpCodes.Throw); + + public static Instruction Cast(TypeReference type) => Instruction.Create(OpCodes.Castclass, type); + + // breaks + public static Instruction Brfalse(Instruction target) => Instruction.Create(OpCodes.Brfalse, target); + public static Instruction Brtrue(Instruction target) => Instruction.Create(OpCodes.Brtrue, target); + public static Instruction Brfalse_S(Instruction target) => Instruction.Create(OpCodes.Brfalse_S, target); + public static Instruction Brtrue_S(Instruction target) => Instruction.Create(OpCodes.Brtrue_S, target); + public static Instruction Br_S(Instruction target) => Instruction.Create(OpCodes.Br_S, target); + public static Instruction Br(Instruction target) => Instruction.Create(OpCodes.Br, target); + public static Instruction Blt_S(Instruction target) => Instruction.Create(OpCodes.Blt_S, target); + public static Instruction Ble_S(Instruction target) => Instruction.Create(OpCodes.Ble_S, target); + public static Instruction Beq(Instruction target) => Instruction.Create(OpCodes.Beq, target); + public static Instruction Bne_Un_S(Instruction target) => Instruction.Create(OpCodes.Bne_Un_S, target); + public static Instruction Beq_S(Instruction target) => Instruction.Create(OpCodes.Beq_S, target); + + // math + public static Instruction Add() => Instruction.Create(OpCodes.Add); + public static Instruction Sub() => Instruction.Create(OpCodes.Sub); + public static Instruction Mul() => Instruction.Create(OpCodes.Mul); + public static Instruction Div() => Instruction.Create(OpCodes.Div); + public static Instruction And() => Instruction.Create(OpCodes.And); + + // obj + public static Instruction Ldobj(TypeReference type) => Instruction.Create(OpCodes.Ldobj, type); + public static Instruction Stobj(TypeReference type) => Instruction.Create(OpCodes.Stobj, type); + + public static Instruction Newobj(MethodReference constructor) => Instruction.Create(OpCodes.Newobj, constructor); + public static Instruction Newarr(TypeReference type) => Instruction.Create(OpCodes.Newarr, type); + + public static Instruction Initobj(TypeReference type) => Instruction.Create(OpCodes.Initobj, type); + + public static Instruction Box(TypeReference type) => Instruction.Create(OpCodes.Box, type); + + + // fields + public static Instruction Ldflda(FieldReference field) => Instruction.Create(OpCodes.Ldflda, field); + + public static Instruction Ldfld(FieldReference field) => Instruction.Create(OpCodes.Ldfld, field); + public static Instruction Stfld(FieldReference field) => Instruction.Create(OpCodes.Stfld, field); + + public static Instruction Ldsfld(FieldReference field) => Instruction.Create(OpCodes.Ldsfld, field); + public static Instruction Stsfld(FieldReference field) => Instruction.Create(OpCodes.Stsfld, field); + + // locals + + public static Instruction Ldloc_or_const(VariableDefinition var, int val) => var != null ? Ldloc(var) : Ldc_I4(val); + + public static Instruction Ldloc(VariableDefinition var, MethodDefinition method) => Ldloc(method.Body, method.Body.Variables.IndexOf(var)); + + public static Instruction Ldloc(VariableDefinition var) => Instruction.Create(OpCodes.Ldloc, var); + public static Instruction Ldloca(VariableDefinition var) => Instruction.Create(OpCodes.Ldloca, var); + public static Instruction Ldloca_S(VariableDefinition var) => Instruction.Create(OpCodes.Ldloca_S, var); + public static Instruction Stloc(VariableDefinition var) => Instruction.Create(OpCodes.Stloc, var); + + public static Instruction Stloc_0() => Instruction.Create(OpCodes.Stloc_0); + public static Instruction Stloc_1() => Instruction.Create(OpCodes.Stloc_1); + public static Instruction Stloc_2() => Instruction.Create(OpCodes.Stloc_2); + public static Instruction Stloc_3() => Instruction.Create(OpCodes.Stloc_3); + + public static Instruction Ldloc_0() => Instruction.Create(OpCodes.Ldloc_0); + public static Instruction Ldloc_1() => Instruction.Create(OpCodes.Ldloc_1); + public static Instruction Ldloc_2() => Instruction.Create(OpCodes.Ldloc_2); + public static Instruction Ldloc_3() => Instruction.Create(OpCodes.Ldloc_3); + + public static Instruction Stloc(MethodBody body, int index) { + switch (index) { + case 0: + return Stloc_0(); + case 1: + return Stloc_1(); + case 2: + return Stloc_2(); + case 3: + return Stloc_3(); + default: + return Stloc(body.Variables[index]); + } + } + + public static Instruction Ldloc(MethodBody body, int index) { + switch (index) { + case 0: + return Ldloc_0(); + case 1: + return Ldloc_1(); + case 2: + return Ldloc_2(); + case 3: + return Ldloc_3(); + default: + return Ldloc(body.Variables[index]); + } + } + + + // ldarg + public static Instruction Ldarg(ParameterDefinition arg) => Instruction.Create(OpCodes.Ldarg, arg); + public static Instruction Ldarg_0() => Instruction.Create(OpCodes.Ldarg_0); + public static Instruction Ldarg_1() => Instruction.Create(OpCodes.Ldarg_1); + public static Instruction Ldarg_2() => Instruction.Create(OpCodes.Ldarg_2); + public static Instruction Ldarg_3() => Instruction.Create(OpCodes.Ldarg_3); + + public static Instruction Ldarga_S(ParameterDefinition p) => Instruction.Create(OpCodes.Ldarga_S, p); + + // starg + + public static Instruction Starg_S(ParameterDefinition arg) => Instruction.Create(OpCodes.Starg_S, arg); + + // array + public static Instruction Ldlen() => Instruction.Create(OpCodes.Ldlen); + + public static Instruction Ldelem(TypeReference type) { + switch (type.MetadataType) { + case MetadataType.Byte: + return Instruction.Create(OpCodes.Ldelem_U1); + case MetadataType.SByte: + return Instruction.Create(OpCodes.Ldelem_I1); + case MetadataType.UInt16: + return Instruction.Create(OpCodes.Ldelem_U2); + case MetadataType.Int16: + return Instruction.Create(OpCodes.Ldelem_I2); + case MetadataType.UInt32: + return Instruction.Create(OpCodes.Ldelem_U4); + case MetadataType.Int32: + return Instruction.Create(OpCodes.Ldelem_I4); + case MetadataType.UInt64: + return Instruction.Create(OpCodes.Ldelem_I8); + case MetadataType.Int64: + return Instruction.Create(OpCodes.Ldelem_I8); + case MetadataType.Single: + return Instruction.Create(OpCodes.Ldelem_R4); + case MetadataType.Double: + return Instruction.Create(OpCodes.Ldelem_R8); + + default: + if (type.IsValueType) { + return Instruction.Create(OpCodes.Ldelem_Any, type); + } else { + return Instruction.Create(OpCodes.Ldelem_Ref); + } + } + } + + public static Instruction Stelem(TypeReference type) { + switch (type.MetadataType) { + case MetadataType.Byte: + case MetadataType.SByte: + return Instruction.Create(OpCodes.Stelem_I1); + case MetadataType.UInt16: + case MetadataType.Int16: + return Instruction.Create(OpCodes.Stelem_I2); + case MetadataType.UInt32: + case MetadataType.Int32: + return Instruction.Create(OpCodes.Stelem_I4); + case MetadataType.UInt64: + case MetadataType.Int64: + return Instruction.Create(OpCodes.Stelem_I8); + case MetadataType.Single: + return Instruction.Create(OpCodes.Stelem_R4); + case MetadataType.Double: + return Instruction.Create(OpCodes.Stelem_R8); + default: + if (type.IsValueType) { + return Instruction.Create(OpCodes.Stelem_Any, type); + } else { + return Instruction.Create(OpCodes.Stelem_Ref); + } + } + } + + public static Instruction Ldelema(TypeReference arg) => Instruction.Create(OpCodes.Ldelema, arg); + + // conversions + public static Instruction Conv_R4() => Instruction.Create(OpCodes.Conv_R4); + public static Instruction Conv_I4() => Instruction.Create(OpCodes.Conv_I4); + public static Instruction Conv_U() => Instruction.Create(OpCodes.Conv_U); + + // functions + public static Instruction Call(MethodReference method) => Instruction.Create(OpCodes.Call, method); + public static Instruction Callvirt(MethodReference method) => Instruction.Create(OpCodes.Callvirt, method); + public static Instruction Ldftn(MethodReference method) => Instruction.Create(OpCodes.Ldftn, method); + + // constants + + public static Instruction Ldstr(string value) => Instruction.Create(OpCodes.Ldstr, value); + public static Instruction Ldc_R4(float value) => Instruction.Create(OpCodes.Ldc_R4, value); + public static Instruction Ldc_R8(float value) => Instruction.Create(OpCodes.Ldc_R8, value); + + public static Instruction Ldc_I4(int value) { + switch (value) { + case 0: return Instruction.Create(OpCodes.Ldc_I4_0); + case 1: return Instruction.Create(OpCodes.Ldc_I4_1); + case 2: return Instruction.Create(OpCodes.Ldc_I4_2); + case 3: return Instruction.Create(OpCodes.Ldc_I4_3); + case 4: return Instruction.Create(OpCodes.Ldc_I4_4); + case 5: return Instruction.Create(OpCodes.Ldc_I4_5); + case 6: return Instruction.Create(OpCodes.Ldc_I4_6); + case 7: return Instruction.Create(OpCodes.Ldc_I4_7); + case 8: return Instruction.Create(OpCodes.Ldc_I4_8); + default: + return Instruction.Create(OpCodes.Ldc_I4, value); + } + } + + public static Instruction Stind_I4() => Instruction.Create(OpCodes.Stind_I4); + public static Instruction Ldind_I4() => Instruction.Create(OpCodes.Ldind_I4); + + public static Instruction Stind_R4() => Instruction.Create(OpCodes.Stind_R4); + public static Instruction Ldind_R4() => Instruction.Create(OpCodes.Ldind_R4); + + + + public static Instruction Stind_or_Stobj(TypeReference type) { + if (type.IsPrimitive) { + return Stind(type); + } else { + return Stobj(type); + } + } + + public static Instruction Ldind_or_Ldobj(TypeReference type) { + if (type.IsPrimitive) { + return Ldind(type); + } else { + return Ldobj(type); + } + } + + public static Instruction Stind(TypeReference type) { + switch (type.MetadataType) { + case MetadataType.Byte: + case MetadataType.SByte: + return Instruction.Create(OpCodes.Stind_I1); + case MetadataType.UInt16: + case MetadataType.Int16: + return Instruction.Create(OpCodes.Stind_I2); + case MetadataType.UInt32: + case MetadataType.Int32: + return Instruction.Create(OpCodes.Stind_I4); + case MetadataType.UInt64: + case MetadataType.Int64: + return Instruction.Create(OpCodes.Stind_I8); + case MetadataType.Single: + return Instruction.Create(OpCodes.Stind_R4); + case MetadataType.Double: + return Instruction.Create(OpCodes.Stind_R8); + default: + throw new ILWeaverException($"Unknown primitive type {type.FullName}"); + } + } + + public static Instruction Ldind(TypeReference type) { + switch (type.MetadataType) { + case MetadataType.Byte: + return Instruction.Create(OpCodes.Ldind_U1); + case MetadataType.SByte: + return Instruction.Create(OpCodes.Ldind_I1); + case MetadataType.UInt16: + return Instruction.Create(OpCodes.Ldind_U2); + case MetadataType.Int16: + return Instruction.Create(OpCodes.Ldind_I2); + case MetadataType.UInt32: + return Instruction.Create(OpCodes.Ldind_U4); + case MetadataType.Int32: + return Instruction.Create(OpCodes.Ldind_I4); + case MetadataType.UInt64: + return Instruction.Create(OpCodes.Ldind_I8); + case MetadataType.Int64: + return Instruction.Create(OpCodes.Ldind_I8); + case MetadataType.Single: + return Instruction.Create(OpCodes.Ldind_R4); + case MetadataType.Double: + return Instruction.Create(OpCodes.Ldind_R8); + default: + throw new ILWeaverException($"Unknown primitive type {type.FullName}"); + } + } + } +} +#endif + +#endregion + + +#region Assets/Photon/Fusion/CodeGen/ILWeaverSettings.cs + +#if FUSION_WEAVER +namespace Fusion.CodeGen { + using System; + + public partial class ILWeaverSettings { + + public static string DefaultConfigPath { + get { + string result = "Assets/Photon/Fusion/Resources/NetworkProjectConfig.fusion"; + OverrideNetworkProjectConfigPath(ref result); + return result; + } + } + + static partial void OverrideNetworkProjectConfigPath(ref string path); + static partial void OverrideIsAssemblyWeavable(string assemblyName, ref bool result); + + public static bool IsAssemblyWeavable(string[] assembliesToWeave, string assemblyName) { + bool result = Array.FindIndex(assembliesToWeave, x => assemblyName.Equals(x, StringComparison.OrdinalIgnoreCase)) >= 0; + OverrideIsAssemblyWeavable(assemblyName, ref result); + return result; + } + + public static bool ContainsRequiredReferences(string[] references) { + return Array.FindIndex(references, x => x.Contains("Fusion.Runtime")) >= 0; + } + + public bool NullChecksForNetworkedProperties; + public bool UseSerializableDictionary; + public bool CheckRpcAttributeUsage; + public bool CheckNetworkedPropertiesBeingEmpty; + public string[] AssembliesToWeave; + } +} +#endif + +#endregion + + +#region Assets/Photon/Fusion/CodeGen/InstructionEqualityComparer.cs + +#if FUSION_WEAVER && FUSION_HAS_MONO_CECIL +namespace Fusion.CodeGen { + using System.Collections.Generic; + using Mono.Cecil.Cil; + + internal class InstructionEqualityComparer : IEqualityComparer { + public bool Equals(Instruction x, Instruction y) { + if (x.OpCode != y.OpCode) { + return false; + } + + if (x.Operand != y.Operand) { + if (x.Operand?.GetType() != y?.Operand.GetType()) { + return false; + } + // there needs to be a better way to do this + if (x.Operand.ToString() != y.Operand.ToString()) { + return false; + } + + } + + return true; + } + + public int GetHashCode(Instruction obj) { + return obj.GetHashCode(); + } + } +} +#endif + +#endregion + + +#region Assets/Photon/Fusion/CodeGen/MemberReferenceFullNameComparer.cs + +#if FUSION_WEAVER && FUSION_HAS_MONO_CECIL +namespace Fusion.CodeGen { + using System.Collections.Generic; + using Mono.Cecil; + + class MemberReferenceFullNameComparer : IEqualityComparer { + bool IEqualityComparer.Equals(MemberReference x, MemberReference y) { + if ( x == y ) { + return true; + } + if ( x == null || y == null ) { + return false; + } + + return GetFullName(x).Equals(GetFullName(y)); + } + + int IEqualityComparer.GetHashCode(MemberReference obj) { + if ( obj == null ) { + return 0; + } + return GetFullName(obj).GetHashCode(); + } + + string GetFullName(MemberReference member) { + if (member is TypeReference type) { + if (type.IsGenericParameter) { + return $"{type.FullName} of {GetFullName(type.DeclaringType)}"; + } + } + return member.FullName; + } + } +} +#endif + +#endregion + + +#region Assets/Photon/Fusion/CodeGen/MonoCecilExtensions.cs + +#if FUSION_WEAVER && FUSION_HAS_MONO_CECIL +namespace Fusion.CodeGen { + + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using System.Linq.Expressions; + using System.Reflection; + using System.Runtime.CompilerServices; + using Mono.Cecil; + using Mono.Cecil.Cil; + using Mono.Cecil.Rocks; + using UnityEditor; + using ICustomAttributeProvider = Mono.Cecil.ICustomAttributeProvider; + using MethodAttributes = Mono.Cecil.MethodAttributes; + using MethodBody = Mono.Cecil.Cil.MethodBody; + + public static class MonoCecilExtensions { + public static MethodDefinition AddDefaultConstructor(this TypeDefinition type, Action initializers = null) { + var methodAttributes = MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName; + var method = new MethodDefinition(".ctor", methodAttributes, type.Module.ImportReference(typeof(void))); + method.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_0)); + + MethodReference baseConstructor = new MethodReference(method.Name, method.ReturnType) { + HasThis = method.HasThis, + ExplicitThis = method.ExplicitThis, + DeclaringType = type.BaseType, + CallingConvention = method.CallingConvention, + }; + + if (type.BaseType.IsGenericInstance) { + var gi = (GenericInstanceType)type.BaseType; + foreach (var genericParameter in gi.GenericParameters) { + baseConstructor.GenericParameters.Add(new GenericParameter(genericParameter.Name, baseConstructor)); + } + + } + method.AddTo(type); + + var il = method.Body.GetILProcessor(); + + if (initializers != null) { + initializers(il); + } + + il.Append(Instruction.Create(OpCodes.Call, type.Module.ImportReference(baseConstructor))); + il.Append(Instruction.Create(OpCodes.Ret)); + + return method; + } + + public static T GetAttributeArgument(this CustomAttribute attr, int index) { + if (TryGetAttributeArgument(attr, index, out var result)) { + return result; + } else { + throw new ArgumentOutOfRangeException($"Argument {index} not found in {attr}"); + } + } + + public static MethodDefinition GetMethodOrThrow(this TypeDefinition type, string methodName, int? argCount = null) { + var query = type.Methods.Where(x => x.Name == methodName); + if (argCount != null) { + query = query.Where(x => x.Parameters.Count == argCount.Value); + } + + var results = query.ToList(); + if (results.Count == 0) { + throw new ArgumentOutOfRangeException(nameof(methodName), $"Method {methodName} not found in {type}"); + } else if (results.Count > 1) { + throw new ArgumentException(nameof(methodName), $"Method {methodName} has multiple matches in {type}"); + } else { + return results[0]; + } + } + + public static bool TryGetMethod(this TypeDefinition type, string methodName, out MethodDefinition method, int? argCount = null) { + var query = type.Methods.Where(x => x.Name == methodName); + if (argCount != null) { + query = query.Where(x => x.Parameters.Count == argCount.Value); + } + + var results = query.ToList(); + if (results.Count == 0) { + method = default; + return false; + } else if (results.Count > 1) { + throw new ArgumentException(nameof(methodName), $"Method {methodName} has multiple matches in {type}"); + } else { + method = results[0]; + return true; + } + } + + public static bool HasAttribute(this ICustomAttributeProvider type) where T : Attribute { + return TryGetAttribute(type, out _); + } + + public static bool TryGetBackingField(this PropertyDefinition property, out FieldDefinition field) { + const string Prefix = "<"; + const string Suffix = ">k__BackingField"; + + var fieldName = $"{Prefix}{property.Name}{Suffix}"; + + foreach (var f in property.DeclaringType.Fields) { + if (!f.IsPrivate) { + continue; + } + if (f.Name == fieldName) { + field = f; + return true; + } + } + + field = null; + return false; + } + + public static bool IsBackingField(this FieldDefinition field, out PropertyDefinition property) { + var fieldName = field.Name; + + const string Prefix = "<"; + const string Suffix = ">k__BackingField"; + + if (!fieldName.StartsWith(Prefix) || !fieldName.EndsWith(Suffix)) { + property = default; + return false; + } + + var propertyName = fieldName.Substring(Prefix.Length, fieldName.Length - Prefix.Length - Suffix.Length); + foreach (var prop in field.DeclaringType.Properties) { + if (prop.Name == propertyName) { + property = prop; + return true; + } + } + + throw new InvalidOperationException($"Field {field} matches backing field name, but property {propertyName} is not found"); + } + + public static bool IsEnumType(this TypeReference type, out TypeReference valueType) { + var typeDef = type.Resolve(); + + if (!typeDef.IsEnum) { + valueType = default; + return false; + } + + foreach (var field in typeDef.Fields) { + if (field.Name == "value__" && field.IsSpecialName && field.IsRuntimeSpecialName && !field.IsStatic) { + valueType = field.FieldType; + return true; + } + } + + throw new InvalidOperationException($"Matching value__ field not found on {type}"); + } + + public static bool IsFixedBuffer(this TypeReference type, out int size) { + size = default; + if (!type.IsValueType) { + return false; + } + + if (!type.Name.EndsWith("e__FixedBuffer")) { + return false; + } + + var definition = type.Resolve(); + + // this is a bit of a guesswork + if (HasAttribute(definition) && + HasAttribute(definition) && + definition.ClassSize > 0) { + size = definition.ClassSize; + return true; + } + + return false; + } + + public static bool TryGetAttribute(this ICustomAttributeProvider type, out CustomAttribute attribute) where T : Attribute { + for (int i = 0; i < type.CustomAttributes.Count; ++i) { + var attr = type.CustomAttributes[i]; + if (attr.AttributeType.Is(typeof(T))) { + attribute = attr; + return true; + } + } + + attribute = null; + return false; + } + + public static bool TryGetAttributeArgument(this CustomAttribute attr, int index, out T value, T defaultValue = default) { + if (index < attr.ConstructorArguments.Count) { + var val = attr.ConstructorArguments[index].Value; + if (val is T t) { + value = t; + return true; + } else if ( typeof(T).IsEnum && val.GetType().IsPrimitive ) { + value = (T)Enum.ToObject(typeof(T), val); + return true; + } + } + + value = defaultValue; + return false; + } + + public static bool TryGetAttributeProperty(this CustomAttribute attr, string name, out T value, T defaultValue = default) { + if (attr.HasProperties) { + var prop = attr.Properties.FirstOrDefault(x => x.Name == name); + + if (prop.Argument.Value != null) { + value = (T)prop.Argument.Value; + return true; + } + } + + value = defaultValue; + return false; + } + + public static bool TryGetMatchingConstructor(this TypeDefinition type, MethodDefinition constructor, out MethodDefinition matchingConstructor) { + return TryGetMatchingMethod(type.GetConstructors(), constructor.Parameters, out matchingConstructor); + } + + public static bool TryGetMethod(this TypeDefinition type, string methodName, IList parameters, out MethodDefinition method) { + var methods = type.Methods.Where(x => x.Name == methodName); + + if (TryGetMatchingMethod(methods, parameters, out method)) { + return true; + } + + //if (type.BaseType != null) { + // if (stopAtBaseType == null || !stopAtBaseType.IsSame(type.BaseType)) { + // return TryGetMethod(type.BaseType.Resolve(), methodName, parameters, out method, stopAtBaseType); + // } + //} + + method = null; + return false; + } + + public static VariableDefinition Clone(this VariableDefinition variable) { + return new VariableDefinition(variable.VariableType); + } + + public static Instruction Clone(this Instruction instruction) { + return (Instruction)Activator.CreateInstance(typeof(Instruction), BindingFlags.NonPublic | BindingFlags.Instance, null, new object[] { instruction.OpCode, instruction.Operand }, null); + } + + + public static (Instruction[], VariableDefinition[]) CloneAndFixUp(MethodBody targetBody, Instruction[] instructions, VariableDefinition[] localVariables) { + var resultInstructions = new Instruction[instructions.Length]; + for (int i = 0; i < instructions.Length; ++i) { + resultInstructions[i] = instructions[i].Clone(); + } + + var resultVariables = new VariableDefinition[localVariables.Length]; + for (int i = 0; i < localVariables.Length; ++i) { + resultVariables[i] = localVariables[i].Clone(); + targetBody.Variables.Add(resultVariables[i]); + } + + for (int i = 0; i < instructions.Length; ++i) { + if (instructions[i].Operand is Instruction referencedInstruction) { + var referencedIndex = Array.IndexOf(instructions, referencedInstruction); + if (referencedIndex >= 0) { + resultInstructions[i].Operand = resultInstructions[referencedIndex]; + } else { + throw new InvalidOperationException(); + } + } else if (instructions[i].Operand is VariableDefinition referencedVariable) { + var referencedIndex = Array.IndexOf(localVariables, referencedVariable); + if (referencedIndex >= 0) { + resultInstructions[i].Operand = resultVariables[referencedIndex]; + } else { + throw new InvalidOperationException(); + + } + } else { + var opCode = instructions[i].OpCode; + int index = -1; + if (opCode == OpCodes.Ldloc_0) { + index = 0; + } else if (opCode == OpCodes.Ldloc_1) { + index = 1; + } else if (opCode == OpCodes.Ldloc_2) { + index = 2; + } else if (opCode == OpCodes.Ldloc_3) { + index = 3; + } + + if (index >= 0) { + var varIndex = Array.FindIndex(localVariables, x => x.Index == index); + if (varIndex >= 0) { + var replacementOp = resultInstructions[i]; + replacementOp.OpCode = OpCodes.Ldloc; + replacementOp.Operand = resultVariables[varIndex]; + } else { + throw new InvalidOperationException($"Using ldloc with index {index} but no variable with that index exists"); + } + } + } + } + return (resultInstructions, resultVariables); + } + + private static bool TryGetMatchingMethod(IEnumerable methods, IList parameters, out MethodDefinition result) { + foreach (var c in methods) { + if (c.Parameters.Count != parameters.Count) { + continue; + } + int i; + for (i = 0; i < c.Parameters.Count; ++i) { + if (!c.Parameters[i].ParameterType.IsSame(parameters[i].ParameterType)) { + break; + } + } + + if (i == c.Parameters.Count) { + result = c; + return true; + } + } + + result = null; + return false; + } + + public static void SetPosition(this GenericParameter parameter, int position) { + var positionField = parameter.GetType().GetField("position", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + Debug.Assert(positionField != null, nameof(positionField) + " != null"); + positionField.SetValue(parameter, position); + } + + public static TypeReference GetGenericParameter(this TypeReference provider, int position) { + var parameterTypeReference0 = new Mono.Cecil.GenericParameter($"!{position}", provider); + parameterTypeReference0.SetPosition(position); + return parameterTypeReference0; + } + + public static MethodReference ImportGetter(this ModuleDefinition module, System.Linq.Expressions.Expression> methodExpression, TypeReference genericParameterProvider = null) { + var body = (System.Linq.Expressions.MemberExpression)methodExpression.Body; + var property = (PropertyInfo)body.Member; + var method = module.ImportReference(property.GetMethod); + MakeCallable(method, genericParameterProvider); + return method; + } + + public static FieldReference ImportField(this ModuleDefinition module, System.Linq.Expressions.Expression> fieldExpression, TypeReference genericParameterProvider = null) { + var body = (System.Linq.Expressions.MemberExpression)fieldExpression.Body; + var member = (FieldInfo)body.Member; + var result = module.ImportReference(member); + MakeCallable(result, genericParameterProvider); + return result; + } + + public static MethodReference ImportMethod(this ModuleDefinition module, System.Linq.Expressions.Expression> methodExpression, TypeReference genericParameterProvider = null) { + MethodInfo methodInfo; + switch (methodExpression.Body) { + case MethodCallExpression mce: + methodInfo = mce.Method; + break; + case UnaryExpression ue: + methodInfo = ue.Method; + break; + default: + throw new NotSupportedException($"{methodExpression.Body.GetType()} is not supported"); + } + + var method = module.ImportReference(methodInfo); + MakeCallable(method, genericParameterProvider); + return method; + } + + public static MethodReference ImportMethod(this ModuleDefinition module, System.Linq.Expressions.Expression> methodExpression, TypeReference genericParameterProvider = null) { + var body = (System.Linq.Expressions.MethodCallExpression)methodExpression.Body; + var method = module.ImportReference(body.Method); + MakeCallable(method, genericParameterProvider); + return method; + } + + public static MethodReference ImportMethod(this ModuleDefinition module, System.Linq.Expressions.Expression> methodExpression, TypeReference genericParameterProvider = null) { + var body = (System.Linq.Expressions.MethodCallExpression)methodExpression.Body; + var method = module.ImportReference(body.Method); + MakeCallable(method, genericParameterProvider); + return method; + } + + private static void MakeCallable(this MethodReference method, TypeReference genericParameterProvider) { + if (method.ReturnType.IsGenericParameter || method.Parameters.Any(x => x.ParameterType.IsGenericParameter)) { + // needs some bullshit processing + if (genericParameterProvider == null) { + throw new ArgumentException("Generic parameter provider must be specified when importing generic methods"); + } + + // this is iffy! + method.DeclaringType = genericParameterProvider; + + if (method.ReturnType.IsGenericParameter) { + method.ReturnType = genericParameterProvider.GetGenericParameter(((GenericParameter)method.ReturnType).Position); + } + + foreach (var parameter in method.Parameters) { + if (parameter.ParameterType.IsGenericParameter) { + parameter.ParameterType = genericParameterProvider.GetGenericParameter(((GenericParameter)parameter.ParameterType).Position); + } + } + + } else if (genericParameterProvider != null) { + // iffy + method.DeclaringType = genericParameterProvider; + } + } + + private static void MakeCallable(this FieldReference field, TypeReference genericParameterProvider) { + if (genericParameterProvider == null) { + throw new ArgumentNullException(nameof(genericParameterProvider)); + } + + if (field.FieldType.IsGenericParameter) { + field.FieldType = genericParameterProvider.GetGenericParameter(((GenericParameter)field.FieldType).Position); + } + + // iffy + field.DeclaringType = genericParameterProvider; + } + + public static TypeReference ImportType(this ModuleDefinition module) { + return module.ImportReference(typeof(T)); + } + + public static TypeReference MakeGenericInstance(this TypeReference self, params TypeReference[] arguments) { + if (self.GenericParameters.Count != arguments.Length) { + throw new ArgumentException(); + } + + var instance = new GenericInstanceType(self); + foreach (var argument in arguments) { + instance.GenericArguments.Add(argument); + } + + return instance; + } + + public static MethodReference MakeGenericInstance(this MethodReference self, params TypeReference[] arguments) { + var reference = new MethodReference(self.Name, self.ReturnType) { + DeclaringType = self.DeclaringType.MakeGenericInstance(arguments), + HasThis = self.HasThis, + ExplicitThis = self.ExplicitThis, + CallingConvention = self.CallingConvention, + }; + + foreach (var parameter in self.Parameters) { + reference.Parameters.Add(new ParameterDefinition(parameter.ParameterType)); + } + + foreach (var parameter in self.GenericParameters) { + reference.GenericParameters.Add(new GenericParameter(parameter.Name, reference)); + } + + return reference; + } + + public static bool TryResolve(this GenericParameter gp, TypeReference context, out TypeReference typeReference) { + var declaringType = gp.DeclaringType; + if (declaringType is TypeDefinition declaringTypeDef) { + if (!declaringTypeDef.HasGenericParameters) { + throw new ArgumentException("Generic parameter must be declared on a generic type"); + } + + var genericArgumentIndex = declaringTypeDef.GenericParameters.IndexOf(gp); + if (genericArgumentIndex < 0) { + throw new ArgumentException($"Generic parameter not found in declaring type {declaringTypeDef}"); + } + + // find type ref pointing to this + var type = context; + + while (!type.IsSame()) { + var typeDef = type.Resolve(); + if (typeDef.IsSame(declaringType)) { + if (type is TypeDefinition) { + // impossible to resolve + break; + } + + if (!type.IsGenericInstance) { + throw new NotSupportedException($"Expected generic instance of {declaringType}"); + } + + var genericInstance = (GenericInstanceType)type; + var genericArg = genericInstance.GenericArguments[genericArgumentIndex]; + if (genericArg is GenericParameter newGenericParameter) { + return TryResolve(newGenericParameter, context, out typeReference); + } else { + typeReference = genericArg; + return true; + } + } + + type = typeDef.BaseType; + } + + typeReference = null; + return false; + + } else { + throw new NotSupportedException(); + } + } + } + + public static class TmpVariable { + public static T variable; + } + +} +#endif + +#endregion + + +#region Assets/Photon/Fusion/CodeGen/NetworkTypeInfo.cs + +#if FUSION_WEAVER && FUSION_HAS_MONO_CECIL +namespace Fusion.CodeGen { + using System; + using System.Linq; + using Mono.Cecil; + using Mono.Cecil.Cil; + using static ILWeaverOpCodes; + + [Flags] + public enum NetworkTypeInfoFlags { + IsTriviallyCopyable = 1 << 0, + CantBeUsedInStructs = 1 << 1, + CantBeUsedInRpcs = 1 << 2, + HasDynamicRpcSize = 1 << 3, + } + + public class NetworkTypeInfo { + + internal delegate void EmitDelegate(ICustomAttributeProvider member, ILProcessor processor, MethodContext context); + internal delegate int GetMemberWordCountDelegate(ICustomAttributeProvider member, TypeReference declaringType); + internal delegate int GetCapacityDelegate(ICustomAttributeProvider member); + internal delegate void EmitInitDelegate(PropertyDefinition property, ILProcessor processor, TypeReference initType, Action emitArg); + internal delegate void EmitStoreDelegate(PropertyDefinition property, ILProcessor processor, FieldReference field); + internal delegate TypeReference GetUnitySerializableTypeDelegate(bool isSerializable); + + + internal static NetworkTypeInfo Create(TypeReference type, + GetUnitySerializableTypeDelegate unitySerializableType = null, + EmitDelegate read = null, + EmitDelegate write = null, + EmitDelegate compactByteCount = null, + EmitDelegate getHashCode = null, + GetMemberWordCountDelegate wordCount = null, + int typeByteSize = -1, + GetCapacityDelegate capacity = null, + EmitInitDelegate unityInit = null, + EmitStoreDelegate unityStore = null, + NetworkTypeInfoFlags flags = 0, + TypeReference wrapperType = null, + Action addAttributes = null) { + + if (type == null) { + throw new ArgumentNullException(nameof(type)); + } + + if (typeByteSize <= 0 && wordCount == null) { + throw new ArgumentNullException(nameof(wordCount)); + } + + return new NetworkTypeInfo(type, flags) { + _emitRead = read, + _emitWrite = write, + _emitGetHashCode = getHashCode, + _unitySerializableType = unitySerializableType, + _typeByteSize = typeByteSize, + _getMemberWordCount = wordCount, + _getCapacity = capacity, + _emitRpcByteCount = compactByteCount, + _emitUnityInit = unityInit, + _emitUnityStore = unityStore, + _wrapperType = wrapperType, + _addCustomAttributes = addAttributes, + }; + } + + protected NetworkTypeInfo(TypeReference type, NetworkTypeInfoFlags flags) { + TypeRef = type; + _flags = flags; + } + + public TypeReference TypeRef { get; private set; } + + public TypeReference WrapperType => _wrapperType; + + public int? ElementWordCount { get; private set; } + + public bool HasStaticSize => _typeByteSize > 0; + + public int StaticByteCount { + get { + if (_typeByteSize <= 0) { + throw new InvalidOperationException($"{TypeRef} does not have a static type size"); + } + return _typeByteSize; + } + } + + public int StaticWordCount => Native.WordCount(StaticByteCount, Allocator.REPLICATE_WORD_SIZE); + + public bool HasDynamicRpcSize => (_flags & NetworkTypeInfoFlags.HasDynamicRpcSize) != 0; + + public bool CanBeUsedInRpc => (_flags & NetworkTypeInfoFlags.CantBeUsedInRpcs) == 0; + + public bool CanBeUsedInStructs => (_flags & NetworkTypeInfoFlags.CantBeUsedInStructs) == 0; + + public bool IsTriviallyCopyable => (_flags & NetworkTypeInfoFlags.IsTriviallyCopyable) != 0; + + private NetworkTypeInfoFlags _flags; + private int _typeByteSize; + private EmitDelegate _emitWrite; + private EmitDelegate _emitRead; + private EmitDelegate _emitRpcByteCount; + private EmitDelegate _emitGetHashCode; + private GetMemberWordCountDelegate _getMemberWordCount; + private GetCapacityDelegate _getCapacity; + private GetUnitySerializableTypeDelegate _unitySerializableType; + private EmitInitDelegate _emitUnityInit; + private EmitStoreDelegate _emitUnityStore; + private TypeReference _wrapperType; + private Action _addCustomAttributes; + + + internal virtual void EmitUnityInit(PropertyDefinition property, ILProcessor il, TypeReference initType, Action emitArg) { + if (_emitUnityInit != null) { + _emitUnityInit(property, il, initType, emitArg); + } else { + var setterRef = property.SetMethod.GetCallable(); + il.Append(Ldarg_0()); + emitArg(il); + il.Append(Call(setterRef)); + } + } + + internal virtual void EmitUnityStore(PropertyDefinition property, ILProcessor il, FieldReference field) { + if (_emitUnityStore != null) { + _emitUnityStore(property, il, field); + } else { + il.Append(Ldarg_0()); + il.Append(Ldarg_0()); + il.Append(Call(property.GetMethod.GetCallable())); + il.Append(Stfld(field)); + } + } + + internal virtual int GetMemberWordCount(ICustomAttributeProvider member, TypeReference declaringType) { + int result; + if (_getMemberWordCount != null) { + if (member == null) { + throw new InvalidOperationException($"Member is needed to get word count"); + } + result = _getMemberWordCount(member, declaringType); + } else { + result = StaticWordCount; + } + + if (result <= 0) { + throw new InvalidOperationException($"Expected word count of {member} to be greater than 0"); + } + return result; + } + + internal virtual bool TryGetCapacity(ICustomAttributeProvider member, out int capacity) { + if (_getCapacity != null) { + capacity = _getCapacity(member); + return true; + } else { + capacity = 0; + return false; + } + } + + internal virtual void EmitRpcByteCount(ILProcessor il, MethodContext context, ICustomAttributeProvider member, bool wordAligned) { + if (_emitRpcByteCount != null) { + _emitRpcByteCount(member, il, context); + if (wordAligned) { + il.AppendMacro(context.AlignToWordSize()); + } + } else if (_getMemberWordCount != null) { + il.Append(Ldc_I4(_getMemberWordCount(member, context.Method.DeclaringType) * Allocator.REPLICATE_WORD_SIZE)); + } else { + if (wordAligned) { + il.Append(Ldc_I4(Native.RoundToAlignment(StaticByteCount, Allocator.REPLICATE_WORD_ALIGN))); + } else { + il.Append(Ldc_I4(StaticByteCount)); + } + } + } + + internal virtual void EmitWrite(ILProcessor il, MethodContext context, ICustomAttributeProvider member) { + if (_emitWrite != null) { + _emitWrite(member, il, context); + } else { + il.AppendMacro(context.LoadAddress()); + il.AppendMacro(context.LoadValue()); + il.Append(Stind_or_Stobj(TypeRef)); + il.AppendMacro(context.AddOffset(StaticByteCount)); + } + } + + internal virtual void EmitGetHashCode(ILProcessor il, MethodContext context, ICustomAttributeProvider member) { + if (_emitGetHashCode != null) { + _emitGetHashCode(member, il, context); + } else { + var tmp = context.AddVariable(TypeRef); + + if (context.HasValueAddrGetter) { + il.AppendMacro(context.LoadValueAddr()); + } else { + il.AppendMacro(context.LoadValue()); + il.Append(Stloc(tmp)); + il.Append(Ldloca(tmp)); + } + + if (TypeRef.IsPrimitive) { + var getHashCode = context.Assembly.Import(TypeRef.GetPrimitiveType().GetMethod(nameof(object.GetHashCode))); + il.Append(Call(getHashCode)); + } else { + if (!TypeRef.IsValueType) { + throw new InvalidOperationException($"Expected {TypeRef} to be a value type"); + } + var getHashCode = context.Assembly.Object.GetMethod(nameof(object.GetHashCode)); + if (TypeRef.IsValueType) { + il.Append(Instruction.Create(OpCodes.Constrained, TypeRef)); + } + il.Append(Callvirt(getHashCode)); + } + } + } + + internal virtual void EmitRead(ILProcessor il, MethodContext context, ICustomAttributeProvider member) { + if (_emitRead != null) { + _emitRead(member, il, context); + } else { + il.AppendMacro(context.LoadAddress()); + il.Append(Ldind_or_Ldobj(TypeRef)); + il.AppendMacro(context.AddOffset(StaticByteCount)); + } + } + + internal virtual TypeReference GetUnityBackingFieldType(bool isSerializable) { + return _unitySerializableType?.Invoke(isSerializable) ?? TypeRef; + } + + internal void AddCustomAttributes(PropertyDefinition property) { + _addCustomAttributes?.Invoke(property); + } + } +} +#endif + +#endregion + + +#region Assets/Photon/Fusion/CodeGen/NetworkTypeInfoRegistry.cs + +#if FUSION_WEAVER && FUSION_HAS_MONO_CECIL +namespace Fusion.CodeGen { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + using Mono.Cecil; + using Mono.Cecil.Cil; + using Mono.Cecil.Rocks; + +#if UNITY_EDITOR + using UnityEngine; +#endif + + using static ILWeaverOpCodes; + using Behaviour = Fusion.Behaviour; + using FieldAttributes = Mono.Cecil.FieldAttributes; + using ICustomAttributeProvider = Mono.Cecil.ICustomAttributeProvider; + + public class NetworkTypeInfoRegistry { + + public delegate int CalculateWordCountDelegate(TypeReference type); + + private Dictionary _types = new Dictionary(new MemberReferenceFullNameComparer()); + private ModuleDefinition _module; + private CalculateWordCountDelegate _calculateValueTypeWordCount; + private ILWeaverSettings _settings; + + internal ILWeaverLog Log { get; } + + public NetworkTypeInfoRegistry(ModuleDefinition module, ILWeaverSettings settings, ILWeaverLogger log, CalculateWordCountDelegate getWordCount) { + _module = module; + _settings = settings; + _calculateValueTypeWordCount = getWordCount; + Log = new ILWeaverLog(log); + AddBuiltInTypes(); + } + + public int GetTypeWordCount(TypeReference type) => GetInfo(type).StaticWordCount; + public int GetPropertyWordCount(PropertyDefinition property) => GetMemberWordCount(property.PropertyType, property, property.DeclaringType); + public int GetMemberWordCount(TypeReference type, ICustomAttributeProvider member, TypeReference declaringType) => GetInfo(type).GetMemberWordCount(member, declaringType); + + internal void EmitRead(TypeReference type, ILProcessor il, MethodContext context, ICustomAttributeProvider member) => GetInfo(type).EmitRead(il, context, member); + internal void EmitWrite(TypeReference type, ILProcessor il, MethodContext context, ICustomAttributeProvider member) => GetInfo(type).EmitWrite(il, context, member); + internal void EmitGetHashCode(TypeReference type, ILProcessor il, MethodContext context, ICustomAttributeProvider member) => GetInfo(type).EmitGetHashCode(il, context, member); + internal void EmitRpcByteCount(TypeReference type, ILProcessor il, MethodContext context, ICustomAttributeProvider member, bool wordAligned) => GetInfo(type).EmitRpcByteCount(il, context, member, wordAligned); + + + public NetworkTypeInfo GetInfo() => GetInfo(typeof(T)); + + public NetworkTypeInfo GetInfo(Type type) { + if (type == null) { + throw new ArgumentNullException(nameof(type)); + } + var t = _module.ImportReference(type); + if (t == null) { + throw new InvalidOperationException($"Failed to resolve: {type.FullName}"); + } + return GetInfo(t); + } + + + public NetworkTypeInfo GetInfo(TypeReference type) { + if (type == null) { + throw new ArgumentNullException(nameof(type)); + } + if (_types.TryGetValue(type, out var result)) { + return result; + } + + return AddType(type); + } + + const int DefaultArrayCapacity = 1; + + public const int DefaultContainerCapacity = 1; + public const int DefaultStringCapacity = 16; + + private NetworkTypeInfo AddType(TypeReference type) { + if (type == null) { + throw new ArgumentNullException(nameof(type)); + } + + if (_types.ContainsKey(type)) { + throw new InvalidOperationException($"Type {type} already added"); + } + + var meta = MakeTypeData(type); + + _types.Add(type, meta); + return meta; + } + + private NetworkTypeInfo MakeTypeData(TypeReference type) { + + var resolved = type.Resolve(); + var imported = _module.ImportReference(type); + + if (type is GenericParameter) { + throw new ArgumentException($"Generic parameters are not supported", nameof(type)); + } + + { + if (type is Mono.Cecil.PointerType ptrType) { + return CreatePointerOrRefMeta(imported, ptrType.ElementType); + } else if (type is ByReferenceType refType) { + return CreatePointerOrRefMeta(imported, refType.ElementType); + } else if (type is TypeSpecification && !(type is GenericInstanceType)) { + throw new ArgumentException($"Invalid TypeReference type: {type.FullName} ({type.GetType()})", nameof(type)); + } + } + + { + if (type.IsNetworkArray(out var elementType)) { + return CreateNetworkArrayOrNetworkLinkedListMeta(imported, elementType, false); + } + if (type.IsNetworkList(out elementType)) { + return CreateNetworkArrayOrNetworkLinkedListMeta(imported, elementType, true); + } + if (type.IsNetworkDictionary(out var keyType, out var valueType)) { + return CreateNetworkDictionaryMeta(imported, keyType, valueType); + } + } + + if (TryGetNetworkWrapperType(type, out var wrapInfo)) { + return CreateWrappedMeta(imported, wrapInfo); + } else if (resolved.IsValueType) { + if (resolved.IsFixedBuffer(out var byteCount)) { + return CreateUnmanagedTypeMeta(imported, byteCount); + } else if (resolved.IsEnumType(out var enumValueType)) { + return CreateUnmanagedTypeMeta(imported, GetInfo(enumValueType).StaticByteCount); + } else if (resolved.Is() || resolved.Is()) { + int wordCount; + try { + wordCount = GetUserValueTypeWordCount(resolved, type); + } catch (Exception ex) { + throw new ArgumentException($"Failed to get user type word count: {type.FullName}", nameof(type), ex); + } + return CreateUnmanagedTypeMeta(imported, Math.Max(1, wordCount) * Allocator.REPLICATE_WORD_SIZE); + } else { + // TODO: unmanaged portable structs + throw new ArgumentException($"Value types need to implement either {nameof(INetworkStruct)} or {nameof(INetworkInput)} interface (type: {type.FullName})", nameof(type)); + } + } else { + // check for wapper? + throw new ArgumentException($"Type {type.FullName} is a reference type but does not implement Wrap pattern.", nameof(type)); + } + } + + private int GetUserValueTypeWordCount(TypeDefinition type, TypeReference typeRef) { + int wordCount; + + // is the type already weaved? + if (type.TryGetAttribute(out var attribute)) { + + wordCount = attribute.GetAttributeArgument(0); + + // is this a generic composite type? + if (attribute.TryGetAttributeArgument(1, out bool value, false) && value) { + Log.Assert(typeRef.IsGenericInstance == true); + foreach (var gen in ((GenericInstanceType)typeRef).GenericArguments) { + if (gen.IsValueType && gen.Is()) { + wordCount += GetTypeWordCount(typeRef); + } + } + } + } else { + wordCount = _calculateValueTypeWordCount(type); + } + + return wordCount; + } + + private NetworkTypeInfo CreateUnmanagedTypeMeta(TypeReference type, int byteCount, NetworkTypeInfo.EmitDelegate read = null, NetworkTypeInfo.EmitDelegate write = null, bool isTriviallyCopyable = true) { + return NetworkTypeInfo.Create(type, + typeByteSize: byteCount, + flags: isTriviallyCopyable ? NetworkTypeInfoFlags.IsTriviallyCopyable : 0, + read: read, + write: write + ); + } + + private NetworkTypeInfo CreatePointerOrRefMeta(TypeReference type, TypeReference elementType) { + var elementInfo = GetInfo(elementType); + return NetworkTypeInfo.Create(type, + read: (member, il, context) => il.AppendMacro(context.LoadAddress()), + write: (member, il, context) => throw new NotSupportedException($"Pointers and references can't have setters"), + wordCount: (member, declaringType) => elementInfo.GetMemberWordCount(member, declaringType), + unitySerializableType: _ => elementType, + unityInit: (prop, il, _, emitArg) => { + il.Append(Ldarg_0()); + il.Append(Call(prop.GetMethod.GetCallable())); + emitArg(il); + il.Append(Stind_or_Stobj(prop.PropertyType.GetElementTypeWithGenerics())); + }, + unityStore: (prop, il, field) => { + il.Append(Ldarg_0()); + il.Append(Ldarg_0()); + il.Append(Call(prop.GetMethod.GetCallable())); + il.Append(Ldind_or_Ldobj(prop.PropertyType.GetElementType())); + il.Append(Stfld(field)); + }, + flags: NetworkTypeInfoFlags.CantBeUsedInRpcs + ); + } + + private NetworkTypeInfo CreateWrappedMeta(TypeReference type, NetworkTypeWrapInfo wrapInfo) { + if (wrapInfo == null) { + throw new ArgumentNullException(nameof(wrapInfo)); + } + + NetworkTypeInfoFlags flags = 0; + if (!wrapInfo.WrapperTypeInfo.CanBeUsedInRpc) { + flags |= NetworkTypeInfoFlags.CantBeUsedInRpcs; + } + if (wrapInfo.WrapNeedsRunner || wrapInfo.UnwrapNeedsRunner) { + flags |= NetworkTypeInfoFlags.CantBeUsedInStructs; + } + + return NetworkTypeInfo.Create(type, + typeByteSize: wrapInfo.WrapperTypeInfo.StaticByteCount, + wordCount: (member, declaringType) => { + return wrapInfo.WrapperTypeInfo.StaticWordCount; + }, + read: (member, il, context) => { + if (wrapInfo.UnwrapByRef) { + + var nop = il.AppendReturn(Nop()); + + if (wrapInfo.UnwrapNeedsRunner) { + il.AppendMacro(context.LoadRunner()); + } + + il.AppendMacro(context.LoadAddress()); + il.Append(Ldind_or_Ldobj(wrapInfo.WrapperType)); + il.AppendMacro(context.GetTargetVariableAddrOrTemp(wrapInfo.TargetType, il, out var variable, before: nop)); + il.Append(Call(wrapInfo.UnwrapMethod.GetCallable())); + il.AppendMacro(context.AddOffset(wrapInfo.WrapperTypeInfo.StaticByteCount)); + + if (variable != null) { + il.Append(Ldloc(variable)); + if (!wrapInfo.TargetType.Is(type)) { + il.Append(Cast(type)); + } + } + + il.Remove(nop); + + } else { + if (wrapInfo.UnwrapNeedsRunner) { + il.AppendMacro(context.LoadRunner()); + } + + il.AppendMacro(context.LoadAddress()); + il.Append(Ldind_or_Ldobj(wrapInfo.WrapperType)); + il.Append(Call(wrapInfo.UnwrapMethod.GetCallable())); + + if (!wrapInfo.TargetType.Is(type)) { + il.Append(Cast(type)); + } + + il.AppendMacro(context.AddOffset(wrapInfo.WrapperTypeInfo.StaticByteCount)); + } + }, + write: (member, il, context) => { + // this is to do the store later on + il.AppendMacro(context.LoadAddress()); + + // actual args start here + if (wrapInfo.WrapNeedsRunner) { + il.AppendMacro(context.LoadRunner()); + } + + il.AppendMacro(context.LoadValue()); + il.Append(Call(wrapInfo.WrapMethod.GetCallable())); + il.Append(Stind_or_Stobj(wrapInfo.WrapperType)); + il.AppendMacro(context.AddOffset(wrapInfo.WrapperTypeInfo.StaticByteCount)); + }, + getHashCode: (member, il, context) => { + using var nestedContext = new MethodContext(context.Assembly, context.Method, valueGetter: il => { + if (wrapInfo.WrapNeedsRunner) { + il.AppendMacro(context.LoadRunner()); + } + il.AppendMacro(context.LoadValue()); + il.Append(Call(wrapInfo.WrapMethod.GetCallable())); + }); + wrapInfo.WrapperTypeInfo.EmitGetHashCode(il, nestedContext, member); + }, + flags: flags, + wrapperType: wrapInfo.WrapperType + ); + } + + private NetworkTypeInfo CreateNetworkArrayOrNetworkLinkedListMeta(TypeReference type, TypeReference elementType, bool isList = false) { + var ctor = _module.ImportReference(type.Resolve().GetConstructors().Single(x => x.HasParameters)); + + ctor.DeclaringType = type; + + elementType = _module.ImportReference(elementType); + + var elementInfo = GetInfo(elementType); + var unitySerializableType = elementType.MakeArrayType(); + + TypeReference wrapperType = null; + if (elementInfo.WrapperType != null) { + var genericType = isList ? _module.ImportReference(typeof(NetworkLinkedList<>)) : _module.ImportReference(typeof(NetworkArray<>)); + wrapperType = TypeReferenceRocks.MakeGenericInstanceType(genericType, elementInfo.WrapperType); + } + + return NetworkTypeInfo.Create(type, + wordCount: (member, declaringType) => { + var capacity = GetCapacity(member, DefaultCollectionCapacity); + var elementWordCount = elementInfo.GetMemberWordCount(member, declaringType); + if (isList) { + return NetworkLinkedList.META_WORDS + capacity * (elementWordCount + NetworkLinkedList.ELEMENT_WORDS); + } else { + return capacity * elementWordCount; + } + }, + read: (member, il, context) => { + var capacity = GetCapacity(member, DefaultContainerCapacity); + il.AppendMacro(context.LoadAddress()); + il.Append(Ldc_I4(capacity)); + il.AppendMacro(context.LoadElementReaderWriter(elementType, member)); + il.Append(Newobj(ctor)); + }, + write: (member, il, context) => throw new NotSupportedException($"Collections can't have setters"), + capacity: (member) => GetCapacity(member, DefaultContainerCapacity), + unitySerializableType: _ => unitySerializableType, + unityInit: (prop, il, _, emitArg) => { + var baseMethod = _module.ImportReference(typeof(NetworkBehaviourUtils).GetMethod(isList ? nameof(NetworkBehaviourUtils.InitializeNetworkList) : nameof(NetworkBehaviourUtils.InitializeNetworkArray))); + var m = new GenericInstanceMethod(baseMethod) { GenericArguments = { elementType } }; + il.Append(Ldarg_0()); + il.Append(Call(prop.GetMethod.GetCallable())); + emitArg(il); + il.Append(Ldstr(prop.Name)); + il.Append(Call(m)); + }, + unityStore: (prop, il, field) => { + var baseMethod = _module.ImportReference(typeof(NetworkBehaviourUtils).GetMethod(isList ? nameof(NetworkBehaviourUtils.CopyFromNetworkList) : nameof(NetworkBehaviourUtils.CopyFromNetworkArray))); + var m = new GenericInstanceMethod(baseMethod) { GenericArguments = { elementType } }; + + il.Append(Ldarg_0()); + il.Append(Call(prop.GetMethod.GetCallable())); + il.Append(Ldarg_0()); + il.Append(Ldflda(field)); + il.Append(Call(m)); + }, + flags: NetworkTypeInfoFlags.CantBeUsedInRpcs, + wrapperType: wrapperType, + addAttributes: property => { + var capacity = GetCapacity(property, DefaultContainerCapacity); + var elementWordCount = elementInfo.GetMemberWordCount(property, property.DeclaringType); + var elementReaderWriter = ILWeaver.GetExistingElementReaderWriter(property.DeclaringType, property, elementInfo); + + if (isList) { + property.AddAttribute(_module, capacity, elementWordCount, elementReaderWriter); + } else { + property.AddAttribute(_module, capacity, elementWordCount, elementReaderWriter); + } + } + ); + } + + private NetworkTypeInfo CreateNetworkDictionaryMeta(TypeReference type, TypeReference keyType, TypeReference valueType) { + var ctor = _module.ImportReference(type.Resolve().GetConstructors().Single(x => x.HasParameters)); + ctor.DeclaringType = type; + + keyType = _module.ImportReference(keyType); + valueType = _module.ImportReference(valueType); + + var keyInfo = GetInfo(keyType); + var valueInfo = GetInfo(valueType); + + NetworkTypeInfo.GetUnitySerializableTypeDelegate unitySerializableType = isSerializable => { + if (isSerializable && _settings.UseSerializableDictionary) { + return TypeReferenceRocks.MakeGenericInstanceType(_module.ImportReference(typeof(SerializableDictionary<,>)), keyType, valueType); + } else { + return TypeReferenceRocks.MakeGenericInstanceType(_module.ImportReference(typeof(Dictionary<,>)), keyType, valueType); + } + }; + + TypeReference wrapperType = null; + if (keyInfo.WrapperType != null || valueInfo.WrapperType != null) { + var genericType = _module.ImportReference(typeof(NetworkDictionary<,>)); + wrapperType = TypeReferenceRocks.MakeGenericInstanceType(genericType, keyInfo.WrapperType ?? keyType, valueInfo.WrapperType ?? valueType); + } + + NetworkTypeInfo.GetCapacityDelegate getCapacity = member => Primes.GetNextPrime(Math.Max(1, GetCapacity(member, DefaultCollectionCapacity))); + return NetworkTypeInfo.Create(type, + wordCount: (member, declaringType) => { + var capacity = getCapacity(member); + return + // meta data (counts, etc) + NetworkDictionary.META_WORD_COUNT + + // buckets + (capacity) + + // entry + // next + (capacity) + + // key + (capacity * keyInfo.GetMemberWordCount(member, declaringType)) + + // value + (capacity * valueInfo.GetMemberWordCount(member, declaringType)); + }, + read: (member, il, context) => { + var capacity = getCapacity(member); + il.AppendMacro(context.LoadAddress()); + il.Append(Ldc_I4(capacity)); + il.AppendMacro(context.LoadElementReaderWriter(keyType, member)); + il.AppendMacro(context.LoadElementReaderWriter(valueType, member)); + il.Append(Newobj(ctor)); + }, + write: (member, il, context) => throw new NotSupportedException($"Collections can't have setters"), + capacity: getCapacity, + unitySerializableType: unitySerializableType, + unityInit: (prop, il, initType, emitArg) => { + if (initType == null) { + initType = unitySerializableType(false); + } + var baseMethod = _module.ImportReference(typeof(NetworkBehaviourUtils).GetMethod(nameof(NetworkBehaviourUtils.InitializeNetworkDictionary))); + var m = new GenericInstanceMethod(baseMethod) { + GenericArguments = { initType, keyType, valueType } + }; + il.Append(Ldarg_0()); + il.Append(Call(prop.GetMethod.GetCallable())); + emitArg(il); + il.Append(Ldstr(prop.Name)); + il.Append(Call(m)); + }, + unityStore: (prop, il, field) => { + var baseMethod = _module.ImportReference(typeof(NetworkBehaviourUtils).GetMethod(nameof(NetworkBehaviourUtils.CopyFromNetworkDictionary))); + var m = new GenericInstanceMethod(baseMethod) { + GenericArguments = { field.FieldType, keyType, valueType } + }; + + il.Append(Ldarg_0()); + il.Append(Call(prop.GetMethod.GetCallable())); + il.Append(Ldarg_0()); + il.Append(Ldflda(field)); + il.Append(Call(m)); + }, + flags: NetworkTypeInfoFlags.CantBeUsedInRpcs, + wrapperType: wrapperType, + addAttributes: property => { + var capacity = getCapacity(property); + var keyWordCount = keyInfo.GetMemberWordCount(property, property.DeclaringType); + var valueWordCount = valueInfo.GetMemberWordCount(property, property.DeclaringType); + var keyReaderWriter = ILWeaver.GetExistingElementReaderWriter(property.DeclaringType, property, keyInfo); + var valueReaderWriter = ILWeaver.GetExistingElementReaderWriter(property.DeclaringType, property, valueInfo); + property.AddAttribute(_module, capacity, keyWordCount, valueWordCount, keyReaderWriter, valueReaderWriter); + } + ); + } + + void AddBuiltInType(NetworkTypeInfo meta) { + _types.Add(meta.TypeRef, meta); + } + + unsafe void AddBuiltInType(MethodReference readMethod = null, MethodReference writeMethod = null, bool isTriviallyCopyable = true) where T : unmanaged { + + var imported = _module.ImportReference(typeof(T)); + + var size = sizeof(T); + int alignedByteCount = Native.WordCount(size, Allocator.REPLICATE_WORD_SIZE) * Allocator.REPLICATE_WORD_SIZE; + + if (readMethod != null) { + readMethod = _module.ImportReference(readMethod); + } + if (writeMethod != null) { + writeMethod = _module.ImportReference(writeMethod); + } + + AddBuiltInType(CreateUnmanagedTypeMeta(_module.ImportReference(typeof(T)), size, + read: readMethod != null ? (member, il, c) => { + il.AppendMacro(c.LoadAddress()); + il.Append(Call(readMethod)); + il.AppendMacro(c.AddOffset(alignedByteCount)); + } : null, + write: writeMethod != null ? (member, il, c) => { + il.AppendMacro(c.LoadAddress()); + il.AppendMacro(c.LoadValue()); + il.Append(Call(writeMethod)); + il.AppendMacro(c.AddOffset(alignedByteCount)); + } : null, + isTriviallyCopyable: isTriviallyCopyable + )); + } + + + unsafe void AddUnityType() where T : unmanaged { + AddBuiltInType(CreateUnmanagedTypeMeta(_module.ImportReference(typeof(T)), sizeof(T))); + } + + unsafe void AddBuiltInTypes() { + + var readWriteUtils = _module.ImportReference(typeof(ReadWriteUtilsForWeaver)).Resolve(); + + // safe primitive types + AddBuiltInType(); + AddBuiltInType(); + AddBuiltInType(); + AddBuiltInType(); + AddBuiltInType(); + AddBuiltInType(); + AddBuiltInType(); + AddBuiltInType(); + AddBuiltInType(); + AddBuiltInType(); + AddBuiltInType(); + + { + var imported = _module.ImportReference(typeof(bool)); + var readMethod = _module.ImportReference(readWriteUtils.GetMethodOrThrow(nameof(ReadWriteUtilsForWeaver.ReadBoolean), 1)); + var writeMethod = _module.ImportReference(readWriteUtils.GetMethodOrThrow(nameof(ReadWriteUtilsForWeaver.WriteBoolean), 2)); + + AddBuiltInType(NetworkTypeInfo.Create(imported, + read: (member, il, c) => { + il.AppendMacro(c.LoadAddress()); + il.Append(Call(readMethod)); + il.AppendMacro(c.AddOffset(sizeof(int))); + }, + write: (member, il, c) => { + il.AppendMacro(c.LoadAddress()); + il.AppendMacro(c.LoadValue()); + il.Append(Call(writeMethod)); + il.AppendMacro(c.AddOffset(sizeof(int))); + }, + typeByteSize: NetworkBool.SIZE, + wrapperType: _module.ImportReference(typeof(NetworkBool)) + )); + } + +#if UNITY_EDITOR + // Unity types + // TODO: restore AccuracyAttribute support for migrating from 1.1 + AddBuiltInType(); + AddBuiltInType(); + AddBuiltInType(); + AddBuiltInType(); + AddUnityType(); + AddUnityType(); + AddUnityType(); + AddUnityType(); + AddUnityType(); + AddUnityType(); + AddUnityType(); + AddUnityType(); + AddUnityType(); + AddUnityType(); +#endif + + { + AddBuiltInType> (); + AddBuiltInType> (); + AddBuiltInType> (); + AddBuiltInType> (); + AddBuiltInType> (); + AddBuiltInType> (); + AddBuiltInType>(); + AddBuiltInType>(); + AddBuiltInType>(); + } + + { + var getUtf8ByteCount = _module.ImportReference(readWriteUtils.GetMethodOrThrow(nameof(ReadWriteUtilsForWeaver.GetByteCountUtf8NoHash), 1)); + var writeUtf8 = _module.ImportReference(readWriteUtils.GetMethodOrThrow(nameof(ReadWriteUtilsForWeaver.WriteStringUtf8NoHash), 2)); + var readUtf8 = _module.ImportReference(readWriteUtils.GetMethodOrThrow(nameof(ReadWriteUtilsForWeaver.ReadStringUtf8NoHash), 2)); + var getHashCode = _module.ImportReference(readWriteUtils.GetMethodOrThrow(nameof(ReadWriteUtilsForWeaver.GetStringHashCode), 2)); + + var readNoHash = _module.ImportReference(readWriteUtils.GetMethodOrThrow(nameof(ReadWriteUtilsForWeaver.ReadStringUtf32NoHash), 3)); + var writeNoHash = _module.ImportReference(readWriteUtils.GetMethodOrThrow(nameof(ReadWriteUtilsForWeaver.WriteStringUtf32NoHash), 3)); + + var readWithHash = _module.ImportReference(readWriteUtils.GetMethodOrThrow(nameof(ReadWriteUtilsForWeaver.ReadStringUtf32WithHash), 3)); + var writeWithHash = _module.ImportReference(readWriteUtils.GetMethodOrThrow(nameof(ReadWriteUtilsForWeaver.WriteStringUtf32WithHash), 4)); + + var t = _module.ImportReference(typeof(string)); + + string GetCacheFieldName(PropertyDefinition prop) => $"cache_{prop.Name}"; + + FieldDefinition GetCacheField(PropertyDefinition prop) { + var name = GetCacheFieldName(prop); + var field = prop.DeclaringType.Fields.SingleOrDefault(x => x.Name == name && x.FieldType.IsSame(t)); + if (field == null) { + field = new FieldDefinition($"cache_{prop.Name}", FieldAttributes.Private, t); + field.AddTo(prop.DeclaringType); + } + return field; + } + + AddBuiltInType(NetworkTypeInfo.Create(t, + wordCount: (member, declaringType) => { + return GetCapacity(member, DefaultStringCapacity) + (declaringType.IsValueType ? 1 : 2); + }, + read: (member, il, context) => { + il.AppendMacro(context.LoadAddress()); + if (context.IsWriteCompact) { + il.AppendMacro(context.GetTargetVariableAddrOrTemp(t, il, out var variable)); + il.Append(Call(readUtf8)); + il.AppendMacro(context.AddOffset()); + if (variable != null) { + il.Append(Ldloc(variable)); + } + } else { + il.Append(Ldc_I4(GetCapacity(member, DefaultStringCapacity))); + if (context.Method.DeclaringType.IsValueType) { + il.AppendMacro(context.GetTargetVariableAddrOrTemp(t, il, out var variable)); + il.Append(Call(readNoHash)); + il.AppendMacro(context.AddOffset()); + if (variable != null) { + il.Append(Ldloc(variable)); + } + } else { + var field = GetCacheField((PropertyDefinition)member); + il.Append(Ldarg_0()); + il.Append(Ldflda(field)); + il.Append(Call(readWithHash)); + il.AppendMacro(context.AddOffset()); + il.Append(Ldarg_0()); + il.Append(Ldfld(field)); + } + + } + }, + write: (member, il, context) => { + + il.AppendMacro(context.LoadAddress()); + + if (context.IsWriteCompact) { + il.AppendMacro(context.LoadValue()); + il.Append(Call(writeUtf8)); + } else { + il.Append(Ldc_I4(GetCapacity(member, DefaultStringCapacity))); + + if (context.Method.DeclaringType.IsValueType) { + il.AppendMacro(context.LoadValue()); + il.Append(Call(writeNoHash)); + } else { + il.AppendMacro(context.LoadValue()); + il.Append(Ldarg_0()); + il.Append(Ldflda(GetCacheField((PropertyDefinition)member))); + il.Append(Call(writeWithHash)); + } + } + + il.AppendMacro(context.AddOffset()); + }, + getHashCode: (member, il, context) => { + il.AppendMacro(context.LoadValue()); + il.Append(Ldc_I4(GetCapacity(member, DefaultStringCapacity))); + il.Append(Call(getHashCode)); + }, + compactByteCount: (member, il, context) => { + il.AppendMacro(context.LoadValue()); + il.Append(Call(getUtf8ByteCount)); + }, + capacity: member => GetCapacity(member, DefaultStringCapacity), + flags: NetworkTypeInfoFlags.HasDynamicRpcSize, + addAttributes: property => { + property.AddAttribute(_module, + GetCapacity(property, DefaultStringCapacity), + property.DeclaringType.IsValueType ? "" : GetCacheFieldName(property)); + } + )); + } + + // System types + AddBuiltInType(); + + + + + // reference types + foreach (var t in new [] { typeof(NetworkObject), typeof(NetworkBehaviour) }) { + var typeRef = _module.ImportReference(t); + TryGetNetworkWrapperType(typeRef, out var wrapInfo); + AddBuiltInType(CreateWrappedMeta(typeRef, wrapInfo)); + } + } + + const int DefaultCollectionCapacity = 1; + + + bool TryGetNetworkWrapperType(TypeReference type, out NetworkTypeWrapInfo result) { + if (type == null) { + throw new ArgumentNullException(nameof(type)); + } + + var definition = type.Resolve(); + + bool wrapNeedsRunner = false; + + if (definition.GetSingleOrDefaultMethodWithAttribute(out var wrapAttribute, out var wrapMethod)) { + int argsStart = 0; + + try { + if (wrapMethod.ThrowIfParameterCountLessThan(1).Parameters[0].ParameterType.Is()) { + wrapNeedsRunner = true; + argsStart = 1; + } + + wrapMethod.ThrowIfNotStatic() + .ThrowIfNotPublic() + .ThrowIfParameterCount(argsStart + 1) + .ThrowIfParameter(argsStart + 0, type); + } catch (Exception ex) { + throw new ILWeaverException($"Method marked with {nameof(NetworkSerializeMethodAttribute)} has an invalid signature", ex); + } + } + + bool unwrapByRef = false; + bool unwrapNeedsRunner = false; + + if (definition.GetSingleOrDefaultMethodWithAttribute(out var unwrapAttribute, out var unwrapMethod)) { + if (wrapMethod == null) { + throw new ILWeaverException($"Method marked with {nameof(NetworkDeserializeMethodAttribute)}, but there is no method marked with {nameof(NetworkSerializeMethodAttribute)}: {unwrapMethod}"); + } + + int argsStart = 0; + + try { + if (unwrapMethod.ThrowIfParameterCountLessThan(1).Parameters[0].ParameterType.Is()) { + unwrapNeedsRunner = true; + argsStart = 1; + } + + if (wrapNeedsRunner) { + unwrapMethod.ThrowIfParameterCountLessThan(1) + .ThrowIfParameter(0, typeof(NetworkRunner)); + } + + unwrapMethod.ThrowIfNotStatic() + .ThrowIfNotPublic() + .ThrowIfParameter(argsStart + 0, wrapMethod.ReturnType); + + if (unwrapMethod.Parameters.Count == 2 + argsStart) { + unwrapMethod.ThrowIfReturnType(typeof(void)) + .ThrowIfParameter(argsStart + 1, type, isByReference: true); + unwrapByRef = true; + } else { + unwrapMethod.ThrowIfParameterCount(argsStart + 1); + unwrapMethod.ThrowIfReturnType(type); + } + + } catch (Exception ex) { + throw new ILWeaverException($"Method marked with {nameof(NetworkDeserializeMethodAttribute)} has an invalid signature", ex); + } + } else if (wrapMethod != null) { + throw new ILWeaverException($"Method marked with {nameof(NetworkSerializeMethodAttribute)}, but there is no method marked with {nameof(NetworkDeserializeMethodAttribute)}: {wrapMethod}"); + } + + + if (wrapMethod != null && unwrapMethod != null) { + result = new NetworkTypeWrapInfo() { + WrapNeedsRunner = wrapNeedsRunner, + UnwrapNeedsRunner = unwrapNeedsRunner, + UnwrapMethod = _module.ImportReference(unwrapMethod), + WrapMethod = _module.ImportReference(wrapMethod), + UnwrapByRef = unwrapByRef, + WrapperType = _module.ImportReference(wrapMethod.ReturnType), + WrapperTypeInfo = GetInfo(wrapMethod.ReturnType), + TargetType = _module.ImportReference(definition), + }; + return true; + } + + if (definition.BaseType == null) { + result = default; + return false; + } else { + return TryGetNetworkWrapperType(definition.BaseType, out result); + } + } + + public static int GetCapacity(ICustomAttributeProvider member, int defaultCapacity) { + if (member.TryGetAttribute(out var attr)) { + if (attr.TryGetAttributeArgument(0, out var result)) { + return result; + } + } + return defaultCapacity; + } + } +} +#endif + + +#endregion + + +#region Assets/Photon/Fusion/CodeGen/NetworkTypeWrapInfo.cs + +#if FUSION_WEAVER && FUSION_HAS_MONO_CECIL +namespace Fusion.CodeGen { + using Mono.Cecil; + + public class NetworkTypeWrapInfo { + public NetworkTypeInfo WrapperTypeInfo; + public TypeReference WrapperType; + public TypeReference TargetType; + public MethodReference WrapMethod; + public MethodReference UnwrapMethod; + public bool WrapNeedsRunner; + public bool UnwrapNeedsRunner; + public bool UnwrapByRef; + } +} +#endif + +#endregion + +#endif diff --git a/Assets/Photon/Fusion/CodeGen/Fusion.CodeGen.cs.meta b/Assets/Photon/Fusion/CodeGen/Fusion.CodeGen.cs.meta new file mode 100644 index 00000000..58b59577 --- /dev/null +++ b/Assets/Photon/Fusion/CodeGen/Fusion.CodeGen.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 800c2efef414be248b2a8c6cf25bf27f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Editor.meta b/Assets/Photon/Fusion/Editor.meta new file mode 100644 index 00000000..659b550d --- /dev/null +++ b/Assets/Photon/Fusion/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 505520cfe3e106e4fbf591aace73bdc5 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Editor/EditorResources.meta b/Assets/Photon/Fusion/Editor/EditorResources.meta new file mode 100644 index 00000000..c973433c --- /dev/null +++ b/Assets/Photon/Fusion/Editor/EditorResources.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 68c2afe3bc0582f439533eb3b5178491 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Editor/EditorResources/Fonts.meta b/Assets/Photon/Fusion/Editor/EditorResources/Fonts.meta new file mode 100644 index 00000000..ccba53e6 --- /dev/null +++ b/Assets/Photon/Fusion/Editor/EditorResources/Fonts.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7e075107f5c7e6246bc185e8afc7f951 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Editor/EditorResources/Fonts/OFL.txt b/Assets/Photon/Fusion/Editor/EditorResources/Fonts/OFL.txt new file mode 100644 index 00000000..6485417d --- /dev/null +++ b/Assets/Photon/Fusion/Editor/EditorResources/Fonts/OFL.txt @@ -0,0 +1,93 @@ +Copyright 2016 The Oswald Project Authors (https://github.com/googlefonts/OswaldFont) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/Assets/Photon/Fusion/Editor/EditorResources/Fonts/OFL.txt.meta b/Assets/Photon/Fusion/Editor/EditorResources/Fonts/OFL.txt.meta new file mode 100644 index 00000000..f9955a49 --- /dev/null +++ b/Assets/Photon/Fusion/Editor/EditorResources/Fonts/OFL.txt.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: db2794fb134f41649947dbdbcac93dee +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Editor/EditorResources/Fonts/Oswald-Header.ttf b/Assets/Photon/Fusion/Editor/EditorResources/Fonts/Oswald-Header.ttf new file mode 100644 index 00000000..7601ddd2 Binary files /dev/null and b/Assets/Photon/Fusion/Editor/EditorResources/Fonts/Oswald-Header.ttf differ diff --git a/Assets/Photon/Fusion/Editor/EditorResources/Fonts/Oswald-Header.ttf.meta b/Assets/Photon/Fusion/Editor/EditorResources/Fonts/Oswald-Header.ttf.meta new file mode 100644 index 00000000..398a6d17 --- /dev/null +++ b/Assets/Photon/Fusion/Editor/EditorResources/Fonts/Oswald-Header.ttf.meta @@ -0,0 +1,22 @@ +fileFormatVersion: 2 +guid: d9d6a85f7c05f99439d73c334199cb58 +TrueTypeFontImporter: + externalObjects: {} + serializedVersion: 4 + fontSize: 17 + forceTextureCase: -2 + characterSpacing: 0 + characterPadding: 1 + includeFontData: 1 + fontNames: + - Oswald + fallbackFontReferences: + - {fileID: 12800000, guid: a30f933bed2f6504bae572c0ef6aaeb7, type: 3} + customCharacters: + fontRenderingMode: 0 + ascentCalculationMode: 1 + useLegacyBoundsCalculation: 0 + shouldRoundAdvanceValue: 1 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Editor/EditorResources/Fusion-dark-help-button-off.png b/Assets/Photon/Fusion/Editor/EditorResources/Fusion-dark-help-button-off.png new file mode 100644 index 00000000..25175c6b Binary files /dev/null and b/Assets/Photon/Fusion/Editor/EditorResources/Fusion-dark-help-button-off.png differ diff --git a/Assets/Photon/Fusion/Editor/EditorResources/Fusion-dark-help-button-off.png.meta b/Assets/Photon/Fusion/Editor/EditorResources/Fusion-dark-help-button-off.png.meta new file mode 100644 index 00000000..196045d5 --- /dev/null +++ b/Assets/Photon/Fusion/Editor/EditorResources/Fusion-dark-help-button-off.png.meta @@ -0,0 +1,123 @@ +fileFormatVersion: 2 +guid: 1cb1b1bf7bfc6574a95f9a2bc7421c60 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 12 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMasterTextureLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + cookieLightType: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Server + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + nameFileIdTable: {} + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Editor/EditorResources/Fusion-dark-help-button-on.png b/Assets/Photon/Fusion/Editor/EditorResources/Fusion-dark-help-button-on.png new file mode 100644 index 00000000..ca719772 Binary files /dev/null and b/Assets/Photon/Fusion/Editor/EditorResources/Fusion-dark-help-button-on.png differ diff --git a/Assets/Photon/Fusion/Editor/EditorResources/Fusion-dark-help-button-on.png.meta b/Assets/Photon/Fusion/Editor/EditorResources/Fusion-dark-help-button-on.png.meta new file mode 100644 index 00000000..903cccf5 --- /dev/null +++ b/Assets/Photon/Fusion/Editor/EditorResources/Fusion-dark-help-button-on.png.meta @@ -0,0 +1,123 @@ +fileFormatVersion: 2 +guid: f8b3194f397d8934697497c4c07cf47e +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 12 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMasterTextureLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + cookieLightType: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Server + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + nameFileIdTable: {} + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Editor/EditorResources/Fusion-inline-box-2x.png b/Assets/Photon/Fusion/Editor/EditorResources/Fusion-inline-box-2x.png new file mode 100644 index 00000000..53b21e36 Binary files /dev/null and b/Assets/Photon/Fusion/Editor/EditorResources/Fusion-inline-box-2x.png differ diff --git a/Assets/Photon/Fusion/Editor/EditorResources/Fusion-inline-box-2x.png.meta b/Assets/Photon/Fusion/Editor/EditorResources/Fusion-inline-box-2x.png.meta new file mode 100644 index 00000000..60d159ca --- /dev/null +++ b/Assets/Photon/Fusion/Editor/EditorResources/Fusion-inline-box-2x.png.meta @@ -0,0 +1,123 @@ +fileFormatVersion: 2 +guid: 80273e5c843674a3eae0b3c38a19a511 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 12 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMasterTextureLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 0 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + cookieLightType: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 64 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Server + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + nameFileIdTable: {} + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Editor/EditorResources/Fusion-inline-box-no-vertical-borders.png b/Assets/Photon/Fusion/Editor/EditorResources/Fusion-inline-box-no-vertical-borders.png new file mode 100644 index 00000000..c55e3fe4 Binary files /dev/null and b/Assets/Photon/Fusion/Editor/EditorResources/Fusion-inline-box-no-vertical-borders.png differ diff --git a/Assets/Photon/Fusion/Editor/EditorResources/Fusion-inline-box-no-vertical-borders.png.meta b/Assets/Photon/Fusion/Editor/EditorResources/Fusion-inline-box-no-vertical-borders.png.meta new file mode 100644 index 00000000..1169ca6f --- /dev/null +++ b/Assets/Photon/Fusion/Editor/EditorResources/Fusion-inline-box-no-vertical-borders.png.meta @@ -0,0 +1,123 @@ +fileFormatVersion: 2 +guid: 6f21527b75fde934381920063e9666ce +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 12 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMasterTextureLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 0 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + cookieLightType: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Server + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + nameFileIdTable: {} + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Editor/EditorResources/Fusion-inline-box.png b/Assets/Photon/Fusion/Editor/EditorResources/Fusion-inline-box.png new file mode 100644 index 00000000..619132c7 Binary files /dev/null and b/Assets/Photon/Fusion/Editor/EditorResources/Fusion-inline-box.png differ diff --git a/Assets/Photon/Fusion/Editor/EditorResources/Fusion-inline-box.png.meta b/Assets/Photon/Fusion/Editor/EditorResources/Fusion-inline-box.png.meta new file mode 100644 index 00000000..a8d01faf --- /dev/null +++ b/Assets/Photon/Fusion/Editor/EditorResources/Fusion-inline-box.png.meta @@ -0,0 +1,123 @@ +fileFormatVersion: 2 +guid: 3f929bc2e87f64441ba0a9c7a42516ca +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 12 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMasterTextureLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 0 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + cookieLightType: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Server + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + nameFileIdTable: {} + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Editor/EditorResources/Fusion-inline-help-box.png b/Assets/Photon/Fusion/Editor/EditorResources/Fusion-inline-help-box.png new file mode 100644 index 00000000..1d80c4d5 Binary files /dev/null and b/Assets/Photon/Fusion/Editor/EditorResources/Fusion-inline-help-box.png differ diff --git a/Assets/Photon/Fusion/Editor/EditorResources/Fusion-inline-help-box.png.meta b/Assets/Photon/Fusion/Editor/EditorResources/Fusion-inline-help-box.png.meta new file mode 100644 index 00000000..70e90ff6 --- /dev/null +++ b/Assets/Photon/Fusion/Editor/EditorResources/Fusion-inline-help-box.png.meta @@ -0,0 +1,123 @@ +fileFormatVersion: 2 +guid: 7428079610f074ba484c046f3cc92512 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 12 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMasterTextureLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 0 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + cookieLightType: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Server + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + nameFileIdTable: {} + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Editor/EditorResources/Fusion-inline-selector.png b/Assets/Photon/Fusion/Editor/EditorResources/Fusion-inline-selector.png new file mode 100644 index 00000000..0ea0ceb8 Binary files /dev/null and b/Assets/Photon/Fusion/Editor/EditorResources/Fusion-inline-selector.png differ diff --git a/Assets/Photon/Fusion/Editor/EditorResources/Fusion-inline-selector.png.meta b/Assets/Photon/Fusion/Editor/EditorResources/Fusion-inline-selector.png.meta new file mode 100644 index 00000000..8c56a997 --- /dev/null +++ b/Assets/Photon/Fusion/Editor/EditorResources/Fusion-inline-selector.png.meta @@ -0,0 +1,123 @@ +fileFormatVersion: 2 +guid: 95929fcf43db30546b27280e9f89777b +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 12 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMasterTextureLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 0 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + cookieLightType: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Server + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + nameFileIdTable: {} + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Editor/EditorResources/Fusion-light-help-button-off.png b/Assets/Photon/Fusion/Editor/EditorResources/Fusion-light-help-button-off.png new file mode 100644 index 00000000..0bffb171 Binary files /dev/null and b/Assets/Photon/Fusion/Editor/EditorResources/Fusion-light-help-button-off.png differ diff --git a/Assets/Photon/Fusion/Editor/EditorResources/Fusion-light-help-button-off.png.meta b/Assets/Photon/Fusion/Editor/EditorResources/Fusion-light-help-button-off.png.meta new file mode 100644 index 00000000..65aa522d --- /dev/null +++ b/Assets/Photon/Fusion/Editor/EditorResources/Fusion-light-help-button-off.png.meta @@ -0,0 +1,123 @@ +fileFormatVersion: 2 +guid: 2d73479cf571d21409b2656fd24f7737 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 12 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMasterTextureLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + cookieLightType: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Server + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + nameFileIdTable: {} + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Editor/EditorResources/Fusion-light-help-button-on.png b/Assets/Photon/Fusion/Editor/EditorResources/Fusion-light-help-button-on.png new file mode 100644 index 00000000..3fc47a80 Binary files /dev/null and b/Assets/Photon/Fusion/Editor/EditorResources/Fusion-light-help-button-on.png differ diff --git a/Assets/Photon/Fusion/Editor/EditorResources/Fusion-light-help-button-on.png.meta b/Assets/Photon/Fusion/Editor/EditorResources/Fusion-light-help-button-on.png.meta new file mode 100644 index 00000000..abb1465b --- /dev/null +++ b/Assets/Photon/Fusion/Editor/EditorResources/Fusion-light-help-button-on.png.meta @@ -0,0 +1,123 @@ +fileFormatVersion: 2 +guid: decc33e0428e74349941f54fe6299753 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 12 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMasterTextureLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + cookieLightType: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Server + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + nameFileIdTable: {} + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Editor/EditorResources/Fusion-logo-2x.png b/Assets/Photon/Fusion/Editor/EditorResources/Fusion-logo-2x.png new file mode 100644 index 00000000..2e294223 Binary files /dev/null and b/Assets/Photon/Fusion/Editor/EditorResources/Fusion-logo-2x.png differ diff --git a/Assets/Photon/Fusion/Editor/EditorResources/Fusion-logo-2x.png.meta b/Assets/Photon/Fusion/Editor/EditorResources/Fusion-logo-2x.png.meta new file mode 100644 index 00000000..33c82c8c --- /dev/null +++ b/Assets/Photon/Fusion/Editor/EditorResources/Fusion-logo-2x.png.meta @@ -0,0 +1,123 @@ +fileFormatVersion: 2 +guid: 662ae237300fee247a43823401881e39 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 12 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMasterTextureLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 0 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + cookieLightType: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 128 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Server + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + nameFileIdTable: {} + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Editor/EditorResources/Fusion-logo.png b/Assets/Photon/Fusion/Editor/EditorResources/Fusion-logo.png new file mode 100644 index 00000000..ad227e5e Binary files /dev/null and b/Assets/Photon/Fusion/Editor/EditorResources/Fusion-logo.png differ diff --git a/Assets/Photon/Fusion/Editor/EditorResources/Fusion-logo.png.meta b/Assets/Photon/Fusion/Editor/EditorResources/Fusion-logo.png.meta new file mode 100644 index 00000000..dee73142 --- /dev/null +++ b/Assets/Photon/Fusion/Editor/EditorResources/Fusion-logo.png.meta @@ -0,0 +1,123 @@ +fileFormatVersion: 2 +guid: 58f8d5c3c5df6ee419821a5c345ebed1 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 12 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMasterTextureLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 0 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + cookieLightType: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 64 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Server + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + nameFileIdTable: {} + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Editor/EditorResources/Fusion-outline-box.png b/Assets/Photon/Fusion/Editor/EditorResources/Fusion-outline-box.png new file mode 100644 index 00000000..9f6bcbf7 Binary files /dev/null and b/Assets/Photon/Fusion/Editor/EditorResources/Fusion-outline-box.png differ diff --git a/Assets/Photon/Fusion/Editor/EditorResources/Fusion-outline-box.png.meta b/Assets/Photon/Fusion/Editor/EditorResources/Fusion-outline-box.png.meta new file mode 100644 index 00000000..2cf67b74 --- /dev/null +++ b/Assets/Photon/Fusion/Editor/EditorResources/Fusion-outline-box.png.meta @@ -0,0 +1,123 @@ +fileFormatVersion: 2 +guid: 223390825bdf7f7488411f7b7ca1c776 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 12 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMasterTextureLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 0 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + cookieLightType: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Server + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + nameFileIdTable: {} + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Editor/EditorResources/Fusion-script-header-2x.png b/Assets/Photon/Fusion/Editor/EditorResources/Fusion-script-header-2x.png new file mode 100644 index 00000000..96c7121f Binary files /dev/null and b/Assets/Photon/Fusion/Editor/EditorResources/Fusion-script-header-2x.png differ diff --git a/Assets/Photon/Fusion/Editor/EditorResources/Fusion-script-header-2x.png.meta b/Assets/Photon/Fusion/Editor/EditorResources/Fusion-script-header-2x.png.meta new file mode 100644 index 00000000..8d516b65 --- /dev/null +++ b/Assets/Photon/Fusion/Editor/EditorResources/Fusion-script-header-2x.png.meta @@ -0,0 +1,123 @@ +fileFormatVersion: 2 +guid: 4379871c896cb47159ca30caed15cf45 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 12 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMasterTextureLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 0 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 0 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + cookieLightType: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 64 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Server + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 1537655665 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + nameFileIdTable: {} + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Editor/EditorResources/Fusion-script-header.png b/Assets/Photon/Fusion/Editor/EditorResources/Fusion-script-header.png new file mode 100644 index 00000000..9908d904 Binary files /dev/null and b/Assets/Photon/Fusion/Editor/EditorResources/Fusion-script-header.png differ diff --git a/Assets/Photon/Fusion/Editor/EditorResources/Fusion-script-header.png.meta b/Assets/Photon/Fusion/Editor/EditorResources/Fusion-script-header.png.meta new file mode 100644 index 00000000..1db17c0b --- /dev/null +++ b/Assets/Photon/Fusion/Editor/EditorResources/Fusion-script-header.png.meta @@ -0,0 +1,123 @@ +fileFormatVersion: 2 +guid: 2f112e6067c75b34f8c6233878db13b4 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 12 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMasterTextureLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 0 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + cookieLightType: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Server + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + nameFileIdTable: {} + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Editor/EditorResources/Fusion.Unity.xml b/Assets/Photon/Fusion/Editor/EditorResources/Fusion.Unity.xml new file mode 100644 index 00000000..7cca7961 --- /dev/null +++ b/Assets/Photon/Fusion/Editor/EditorResources/Fusion.Unity.xml @@ -0,0 +1,772 @@ + + + + Fusion.Unity + + + + + Types that fusion.runtime isn't aware of, which need to be found using names instead. + + + + + Find all component types that contribute to a scene rendering, and associate them with a component, + and add them to the runner's list of visibility nodes. + + + + + + + Reapplies a runner's IsVisibile setting to all of its registered visibility nodes. + + + + + + + Dictionary lookup for manually added visibility nodes (which indicates only one instance should be visible at a time), + which returns a list of nodes for a given LocalIdentifierInFile. + + + + + Gets the unique identifier of the network object. + + + The network object identifier. + + + + + The side to attach the statistics panel anchor. + + + + + Flags controlling which Mecanim data will be synced. + + + + + Gets a value indicating whether the statistics panel is active. + + + + + Sets the custom configuration for Fusion Statistics. + + The list of custom configurations for Fusion Statistics. + + + + Sets the anchor position of the Fusion Statistics canvas. + + The anchor position of the canvas (TopLeft or TopRight). + + + + Called from a custom editor script. + Will update any editor information into the fusion statistics. + + + + + Sets up the statistics panel for Fusion statistic tracking. + + + + + Sets the world anchor for Fusion Statistics. Set null to return to screen space overlay. + + The FusionStatsWorldAnchor component that defines the anchor object. Null to return to screen space overlay. + The scale of the statistics panel. + + + + Destroys the statistics panel. + + + + + List of all simulation stats able to render on a graph. + + + + + Incoming packets. + + + + + Outgoing packets. + + + + + Round Trip Time. + + + + + In Bandwidth in Bytes. + + + + + Out Bandwidth in Bytes. + + + + + Amount of re-simulation ticks executed. + + + + + Amount of forward ticks executed. + + + + + Average measured time between two input/state packets (from same client) received by the server. + + + + + Time sync abruptly reset count. + + + + + Average measured time between two state packets (from server) received by the client. + + + + + Average buffering for prediction. + + + + + How much the simulation is currently sped up / slowed down. + + + + + Average buffering for interpolation. + + + + + How much interpolation is currently sped up / slowed down. + + + + + Input in bandwidth. + + + + + Input out bandwidth. + + + + + Average size for received packet. + + + + + Average size for sent packet. + + + + + Amount of object updates received. + + + + + Amount of object updates sent. + + + + + Memory in use for the object allocator. + + + + + Memory in use for the general allocator. + + + + + Memory free for the object allocator. + + + + + Memory free for the general allocator. + + + + + Amount of written words. How many networked changes are being sent. + + + + + Size of all last written words in Bytes. + + + + + Amount of read words. How many networked changes are being received. + + + + + Size of all last read words in Bytes. + + + + + Component which automatically faces this GameObject toward the supplied Camera. If Camera == null, will face towards Camera.main. + + + + + Force a particular camera to billboard this object toward. Leave null to use Camera.main. + + + + + Automatically adds a for each indicated component. + These indicated components will be limited to no more than one enabled instance when running in Multi-Peer mode. + + + + + If more than one runner instance is visible, this indicates which peer's clone of this entity should be visible. + + + + + Collection of components that will be marked for Multi-Peer mode as objects that should only have one enabled instance. + + + + + Prefix for the GUIDs of components which are added at runtime. + + + + + At runtime startup, this adds a for each component reference to this GameObject. + + + + + Finds visual/audio components on this GameObject, and adds them to the Components collection. + + + + + Finds visual/audio nested components on this GameObject and its children, and adds them to the Components collection. + + + + + If true, all messages will be prefixed with [Fusion] tag + + + + + If true, some parts of messages will be enclosed with <color> tags. + + + + + If true, each log message that has a source parameter will be prefixed with a hash code of the source object. + + + + + Color of the global prefix (see ). + + + + + Min Random Color + + + + + Max Random Color + + + + + Server Color + + + + + When running in Multi-Peer mode, this component automatically will register the associated + with , + and will automatically attach loaded scene objects and spawned objects with the peers visibility handling. + + + + + Add this Component to the NetworkRunner Prefab or GameObject. If Interest Management is enabled in NetworkProjectConfig ReplicationFeatures, + gizmos will be shown that indicate active Area Of Interest cells. These gizmos are currently NOT applicable to Shared Mode and will only + render for the Server/Host peer. + + + + + Flag component which indicates a NetworkObject has already been factored into a Runner's VisibilityNode list. + + + + + The current status of MPPM. If the package is not enabled, this will always be . + + + + + MPPM is not installed. + + + + + This instance is the main instance. Can use to send commands. + + + + + This instance is a virtual instance. Will receive commands from the main instance. + + + + + Support for Multiplayer Play Mode (MPPM). It uses named pipes + to communicate between the main Unity instance and virtual instances. + + + + + The current status of MPPM. + + + + + If is , this static field can be used to send commands. + + + + + Sends a command to all virtual instances. Use as: + FusionMppm.MainEditor?.Send + + + + + + + The base class for all Fusion MPPM commands. + + + + + Execute the command on a virtual instance. + + + + + Does the main instance need to wait for an ack? + + + + + If the command is persistent (i.e. needs to be executed on each domain reload), this key is used to store it. + + + + + Companion component for . Automatically added as needed for rendering in-game networking IMGUI. + + + + + When enabled, the in-game user interface buttons can be activated with the keys H (Host), S (Server) and C (Client). + + + + + The GUISkin to use as the base for the scalable in-game UI. + + + + + Finds all components of type in the scene. + + + + + + + + + Finds all components of type in the scene. + + + + + + + + + + Finds all components of type in the scene. + + + + + + + + + + Finds the first instance of type in the scene. Returns null if no instance found. + + + + + + + + + If enabled and there is an already loaded scene that matches what the scene manager has intended to load, + that scene will be used instead and load will be avoided. + + + + + Should all scene load errors be logged into the console? If disabled, errors can still be retrieved via the + or . + + + + + All the scenes loaded by all the managers. Used when is enabled. + + + + + In multiple peer mode, each runner maintains its own scene where all the newly loaded scenes + are moved to. This is to make sure physics are properly sandboxed. + + + + + List of running coroutines. Only one is actually executed at a time. + + + + + For remote clients, this manager first unloads old scenes then loads the new ones. It might happen that all + the current scenes need to be unloaded and in such case a temp scene needs to be created to ensure at least one + scene loaded at all times. + + + + + Scene used when Multiple Peer mode is used. Each loaded scene is merged into this one, allowing + for multiple runners to have separate cross-scene physics. + + + + + Root for DontDestroyOnLoad objects. Instantiated on . + + + + + A label by which addressable scenes can be discovered. + + + + + Creates a task that resolves addressable scene paths. By default, this method locates all the addressable scenes with + label. Override this method to provide a custom implementation. For example, user + might want to have a pre-defined set of addressable scenes to avoid the wait: + + protected override GetAddressableScenesResult GetAddressableScenes() { + return Task.FromResult(new string[] { + "Assets/Scenes/AddressableScene1.unity", + "Assets/Scenes/AddressableScene2.unity", + }); + } + + + A task representing resolve operation and optionally a delegate to be invoked before the task is going to be + awaited synchronously + + + + Returns the timeout for addressable scene paths to be resolved. By default, this method returns 10 seconds. + + + + + + A Fusion prototyping class for starting up basic networking. Add this component to your startup scene, and supply a . + Can be set to automatically startup the network, display an in-game menu, or allow simplified start calls like . + + + + + Selection for how will behave at startup. + + + + + The current stage of connection or shutdown. + + + + + Supply a Prefab or a scene object which has the component on it, + as well as any runner dependent components which implement , + such as or your own custom INetworkInput implementations. + + + + + Select how network startup will be triggered. Automatically, by in-game menu selection, or exclusively by script. + + + + + When is set to , this option selects if the + will be started as a dedicated server, or as a host (which is a server with a local player). + + + + + will not render GUI elements while == . + + + + + The number of client instances that will be created if running in Mulit-Peer Mode. + When using the Select start mode, this number will be the default value for the additional clients option box. + + + + + How long to wait after starting a peer before starting the next one. + + + + + The port that server/host will use. + + + + + The default room name to use when connecting to photon cloud. + + + + + The Scene that will be loaded after network shutdown completes (all peers have disconnected). + If this field is null or invalid, will be set to the current scene when runs Awake(). + + + + + Indicates which step of the startup process is currently in. + + + + + Requires Multiplayer Play Mode (MPPM) to be installed. If enabled, will automatically join the virtual instance. + + + + + How much to wait before the main instance lets the virtual instances connect. + + + + + Indicates which step of the startup process is currently in. + + + + + The index number used for the last created peer. + + + + + The server mode that was used for initial startup. Used to inform UI which client modes should be available. + + + + + Start a single player instance. + + + + + Start a server instance. + + + + + Start a host instance. This is a server instance, with a local player. + + + + + Start a client instance. + + + + + Start a Fusion server instance, and the number of client instances indicated by . + InstanceMode must be set to Multi-Peer mode, as this requires multiple instances. + + + + + Start a Fusion host instance, and the number of client instances indicated by . + InstanceMode must be set to Multi-Peer mode, as this requires multiple instances. + + + + + Start a Fusion server instance, and the indicated number of client instances. + InstanceMode must be set to Multi-Peer mode, as this requires multiple instances. + + + + + Start a Fusion host instance (server with local player), and the indicated number of additional client instances. + InstanceMode must be set to Multi-Peer mode, as this requires multiple instances. + + + + + Start a Fusion host instance (server with local player), and the indicated number of additional client instances. + InstanceMode must be set to Multi-Peer mode, as this requires multiple instances. + + + + + Start as Room on the Photon cloud, and connects as one or more clients. + + + + + + Only show the GUI if the StartMode is set to UserInterface and not being run in a Virtual Instance (MPPM). + + + + + In-Game IMGUI style used for the interface. + + + + + Get the custom scalable skin, already resized to the current screen. Provides the height, width, padding and margin used. + + + + + + Modifies a skin to make it scale with screen height. + + + Returns (height, width, padding, top-margin, left-box-margin) values applied to the GuiSkin + + + + If enabled, the provider will delay acquiring a prefab instance if the scene manager is busy. + + + + + Flags a MonoBehaviour class as a RunnerVisibilityControl recognized type. + Will be included in runner visibility handling, and will be found by component finds. + + + + + Identifies visible/audible components (such as renderers, canvases, lights) that should be enabled/disabled by runner visibility handling. + Automatically added to scene objects and spawned objects during play if running in . + Additionally this component can be added manually at development time to identify specific Behaviours or Renderers you would like to restrict to one enabled copy at a time. + + + + + The peer runner that will be used if more than one runner is visible, and this node was manually added by developer (indicating only one instance should be visible at a time). + + + + + The first visible runner will be used. + + + + + The server peer/runner will be used if visible. + + + + + The first client peer/runner will be used if visible. + + + + + The components will only be enabled on the instance that has input authority over the NetworkObject. Unlike the other options, this expects a NetworkObject to work and it will search its children and parents for it. + + + + + If more than one runner instance is visible, this indicates which peer's clone of this entity should be visible. + + + + + The associated component with this node. This Behaviour or Renderer will be enabled/disabled when its NetworkRunner.IsVisible value is changed. + + + + + Guid is used for common objects (user flagged components that should only run in one instance), to identify matching clones. + + + + + Set to false to indicate that this object should remain disabled even when is set to true. + + + + + Sets the visibility state of this node. + + + + + diff --git a/Assets/Photon/Fusion/Editor/EditorResources/Fusion.Unity.xml.meta b/Assets/Photon/Fusion/Editor/EditorResources/Fusion.Unity.xml.meta new file mode 100644 index 00000000..009126ba --- /dev/null +++ b/Assets/Photon/Fusion/Editor/EditorResources/Fusion.Unity.xml.meta @@ -0,0 +1,4 @@ +fileFormatVersion: 2 +guid: b199a25aef303094f8d299987c726459 +labels: +- FusionCodeDoc diff --git a/Assets/Photon/Fusion/Editor/EditorResources/FusionEditorSkin.guiskin b/Assets/Photon/Fusion/Editor/EditorResources/FusionEditorSkin.guiskin new file mode 100644 index 00000000..6126de35 --- /dev/null +++ b/Assets/Photon/Fusion/Editor/EditorResources/FusionEditorSkin.guiskin @@ -0,0 +1,2089 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12001, guid: 0000000000000000e000000000000000, type: 0} + m_Name: FusionEditorSkin + m_EditorClassIdentifier: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_box: + m_Name: box + m_Normal: + m_Background: {fileID: 11001, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.79999995, g: 0.79999995, b: 0.79999995, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 6 + m_Right: 6 + m_Top: 6 + m_Bottom: 6 + m_Margin: + m_Left: 4 + m_Right: 4 + m_Top: 4 + m_Bottom: 4 + m_Padding: + m_Left: 4 + m_Right: 4 + m_Top: 4 + m_Bottom: 4 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 1 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_button: + m_Name: button + m_Normal: + m_Background: {fileID: 11006, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.9, g: 0.9, b: 0.9, a: 1} + m_Hover: + m_Background: {fileID: 11003, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Active: + m_Background: {fileID: 11002, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.9, g: 0.9, b: 0.9, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnNormal: + m_Background: {fileID: 11005, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.9019608, g: 0.9019608, b: 0.9019608, a: 1} + m_OnHover: + m_Background: {fileID: 11004, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnActive: + m_Background: {fileID: 11002, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.9, g: 0.9, b: 0.9, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 6 + m_Right: 6 + m_Top: 6 + m_Bottom: 4 + m_Margin: + m_Left: 4 + m_Right: 4 + m_Top: 4 + m_Bottom: 4 + m_Padding: + m_Left: 6 + m_Right: 6 + m_Top: 3 + m_Bottom: 3 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 4 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_toggle: + m_Name: toggle + m_Normal: + m_Background: {fileID: 11018, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.89112896, g: 0.89112896, b: 0.89112896, a: 1} + m_Hover: + m_Background: {fileID: 11014, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Active: + m_Background: {fileID: 11013, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 11016, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.8901961, g: 0.8901961, b: 0.8901961, a: 1} + m_OnHover: + m_Background: {fileID: 11015, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnActive: + m_Background: {fileID: 11017, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 14 + m_Right: 0 + m_Top: 14 + m_Bottom: 0 + m_Margin: + m_Left: 4 + m_Right: 4 + m_Top: 4 + m_Bottom: 4 + m_Padding: + m_Left: 15 + m_Right: 0 + m_Top: 3 + m_Bottom: 0 + m_Overflow: + m_Left: -1 + m_Right: 0 + m_Top: -4 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_label: + m_Name: label + m_Normal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.9, g: 0.9, b: 0.9, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.9, g: 0.9, b: 0.9, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.9, g: 0.9, b: 0.9, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.9, g: 0.9, b: 0.9, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.9, g: 0.9, b: 0.9, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.9, g: 0.9, b: 0.9, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.9, g: 0.9, b: 0.9, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.9, g: 0.9, b: 0.9, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 4 + m_Right: 4 + m_Top: 4 + m_Bottom: 4 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 3 + m_Bottom: 3 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 1 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_textField: + m_Name: textfield + m_Normal: + m_Background: {fileID: 11024, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.79999995, g: 0.79999995, b: 0.79999995, a: 1} + m_Hover: + m_Background: {fileID: 11026, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.9, g: 0.9, b: 0.9, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 11026, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnNormal: + m_Background: {fileID: 11025, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 4 + m_Right: 4 + m_Top: 4 + m_Bottom: 4 + m_Margin: + m_Left: 4 + m_Right: 4 + m_Top: 4 + m_Bottom: 4 + m_Padding: + m_Left: 3 + m_Right: 3 + m_Top: 3 + m_Bottom: 3 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 0 + m_TextClipping: 1 + m_ImagePosition: 3 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_textArea: + m_Name: textarea + m_Normal: + m_Background: {fileID: 11024, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.9019608, g: 0.9019608, b: 0.9019608, a: 1} + m_Hover: + m_Background: {fileID: 11026, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.79999995, g: 0.79999995, b: 0.79999995, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 11025, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 4 + m_Right: 4 + m_Top: 4 + m_Bottom: 4 + m_Margin: + m_Left: 4 + m_Right: 4 + m_Top: 4 + m_Bottom: 4 + m_Padding: + m_Left: 3 + m_Right: 3 + m_Top: 3 + m_Bottom: 3 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 1 + m_RichText: 0 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_window: + m_Name: window + m_Normal: + m_Background: {fileID: 11023, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 11022, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 8 + m_Right: 8 + m_Top: 18 + m_Bottom: 8 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 10 + m_Right: 10 + m_Top: 20 + m_Bottom: 10 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 1 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: -18} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_horizontalSlider: + m_Name: horizontalslider + m_Normal: + m_Background: {fileID: 11009, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 3 + m_Right: 3 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 4 + m_Right: 4 + m_Top: 4 + m_Bottom: 4 + m_Padding: + m_Left: -1 + m_Right: -1 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: -2 + m_Bottom: -3 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 2 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 12 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_horizontalSliderThumb: + m_Name: horizontalsliderthumb + m_Normal: + m_Background: {fileID: 11011, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 11012, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 11010, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 4 + m_Right: 4 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 7 + m_Right: 7 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: -1 + m_Right: -1 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 2 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 12 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_verticalSlider: + m_Name: verticalslider + m_Normal: + m_Background: {fileID: 11021, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 3 + m_Bottom: 3 + m_Margin: + m_Left: 4 + m_Right: 4 + m_Top: 4 + m_Bottom: 4 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: -1 + m_Bottom: -1 + m_Overflow: + m_Left: -2 + m_Right: -3 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 12 + m_FixedHeight: 0 + m_StretchWidth: 0 + m_StretchHeight: 1 + m_verticalSliderThumb: + m_Name: verticalsliderthumb + m_Normal: + m_Background: {fileID: 11011, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 11012, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 11010, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 7 + m_Bottom: 7 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: -1 + m_Bottom: -1 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 12 + m_FixedHeight: 0 + m_StretchWidth: 0 + m_StretchHeight: 1 + m_horizontalScrollbar: + m_Name: horizontalscrollbar + m_Normal: + m_Background: {fileID: 11008, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 9 + m_Right: 9 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 4 + m_Right: 4 + m_Top: 1 + m_Bottom: 4 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 2 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 15 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_horizontalScrollbarThumb: + m_Name: horizontalscrollbarthumb + m_Normal: + m_Background: {fileID: 11007, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 6 + m_Right: 6 + m_Top: 6 + m_Bottom: 6 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 6 + m_Right: 6 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: -1 + m_Bottom: 1 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 13 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_horizontalScrollbarLeftButton: + m_Name: horizontalscrollbarleftbutton + m_Normal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_horizontalScrollbarRightButton: + m_Name: horizontalscrollbarrightbutton + m_Normal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_verticalScrollbar: + m_Name: verticalscrollbar + m_Normal: + m_Background: {fileID: 11020, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 9 + m_Bottom: 9 + m_Margin: + m_Left: 1 + m_Right: 4 + m_Top: 4 + m_Bottom: 4 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 1 + m_Bottom: 1 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 15 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_verticalScrollbarThumb: + m_Name: verticalscrollbarthumb + m_Normal: + m_Background: {fileID: 11019, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 6 + m_Right: 6 + m_Top: 6 + m_Bottom: 6 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 6 + m_Bottom: 6 + m_Overflow: + m_Left: -1 + m_Right: -1 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 2 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 15 + m_FixedHeight: 0 + m_StretchWidth: 0 + m_StretchHeight: 1 + m_verticalScrollbarUpButton: + m_Name: verticalscrollbarupbutton + m_Normal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_verticalScrollbarDownButton: + m_Name: verticalscrollbardownbutton + m_Normal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_ScrollView: + m_Name: scrollview + m_Normal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_CustomStyles: + - m_Name: light-help-button + m_Normal: + m_Background: {fileID: 2800000, guid: 2d73479cf571d21409b2656fd24f7737, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_OnNormal: + m_Background: {fileID: 2800000, guid: decc33e0428e74349941f54fe6299753, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 0 + m_TextClipping: 0 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 16 + m_FixedHeight: 16 + m_StretchWidth: 0 + m_StretchHeight: 0 + - m_Name: dark-help-button + m_Normal: + m_Background: {fileID: 2800000, guid: 1cb1b1bf7bfc6574a95f9a2bc7421c60, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_OnNormal: + m_Background: {fileID: 2800000, guid: f8b3194f397d8934697497c4c07cf47e, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 0 + m_TextClipping: 0 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 16 + m_FixedHeight: 16 + m_StretchWidth: 0 + m_StretchHeight: 0 + - m_Name: outline-box + m_Normal: + m_Background: {fileID: 2800000, guid: 223390825bdf7f7488411f7b7ca1c776, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_OnNormal: + m_Background: {fileID: 2800000, guid: f8b3194f397d8934697497c4c07cf47e, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_Border: + m_Left: 10 + m_Right: 10 + m_Top: 10 + m_Bottom: 10 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 6 + m_Right: 6 + m_Top: 6 + m_Bottom: 6 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 3 + m_WordWrap: 1 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 0 + m_StretchHeight: 0 + - m_Name: inline-box-full-width-scope + m_Normal: + m_Background: {fileID: 2800000, guid: 6f21527b75fde934381920063e9666ce, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_OnNormal: + m_Background: {fileID: 2800000, guid: f8b3194f397d8934697497c4c07cf47e, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_Border: + m_Left: -200 + m_Right: -200 + m_Top: 1 + m_Bottom: 1 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 4 + m_Right: 4 + m_Top: 8 + m_Bottom: 8 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 3 + m_WordWrap: 1 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 0 + m_StretchHeight: 0 + - m_Name: inline-box-full-width + m_Normal: + m_Background: {fileID: 2800000, guid: 3f929bc2e87f64441ba0a9c7a42516ca, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_OnNormal: + m_Background: {fileID: 2800000, guid: f8b3194f397d8934697497c4c07cf47e, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_Border: + m_Left: 2 + m_Right: 2 + m_Top: 1 + m_Bottom: 1 + m_Margin: + m_Left: -1 + m_Right: 0 + m_Top: -2 + m_Bottom: -2 + m_Padding: + m_Left: 22 + m_Right: 16 + m_Top: 10 + m_Bottom: 12 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 11 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 1 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 0 + m_StretchHeight: 0 + - m_Name: inline-selector + m_Normal: + m_Background: {fileID: 2800000, guid: 95929fcf43db30546b27280e9f89777b, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_OnNormal: + m_Background: {fileID: 2800000, guid: f8b3194f397d8934697497c4c07cf47e, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_Border: + m_Left: 6 + m_Right: 0 + m_Top: 4 + m_Bottom: 1 + m_Margin: + m_Left: 0 + m_Right: 2 + m_Top: 0 + m_Bottom: -1 + m_Padding: + m_Left: 12 + m_Right: 12 + m_Top: 30 + m_Bottom: 20 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 11 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 1 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 0 + m_StretchHeight: 0 + - m_Name: light-rich-label + m_Normal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_OnNormal: + m_Background: {fileID: 2800000, guid: f8b3194f397d8934697497c4c07cf47e, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 11 + m_FontStyle: 0 + m_Alignment: 3 + m_WordWrap: 1 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 0 + m_StretchHeight: 0 + - m_Name: dark-rich-label + m_Normal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 11 + m_FontStyle: 0 + m_Alignment: 3 + m_WordWrap: 1 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 0 + m_StretchHeight: 0 + - m_Name: script-header-bg + m_Normal: + m_Background: {fileID: 2800000, guid: 2f112e6067c75b34f8c6233878db13b4, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_OnNormal: + m_Background: {fileID: 2800000, guid: f8b3194f397d8934697497c4c07cf47e, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_Border: + m_Left: 1 + m_Right: 2 + m_Top: 1 + m_Bottom: 1 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 3 + m_Bottom: 4 + m_Padding: + m_Left: 8 + m_Right: 16 + m_Top: 4 + m_Bottom: 4 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 11 + m_FontStyle: 0 + m_Alignment: 3 + m_WordWrap: 1 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 0 + m_StretchHeight: 0 + - m_Name: script-header-label + m_Normal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_OnNormal: + m_Background: {fileID: 2800000, guid: f8b3194f397d8934697497c4c07cf47e, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_Border: + m_Left: 60 + m_Right: 0 + m_Top: 21 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: -5 + m_Bottom: 0 + m_Padding: + m_Left: 2 + m_Right: 40 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 12800000, guid: d9d6a85f7c05f99439d73c334199cb58, type: 3} + m_FontSize: 16 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 0 + m_StretchHeight: 0 + - m_Name: script-header-icon + m_Normal: + m_Background: {fileID: 2800000, guid: 58f8d5c3c5df6ee419821a5c345ebed1, type: 3} + m_ScaledBackgrounds: + - {fileID: 2800000, guid: 662ae237300fee247a43823401881e39, type: 3} + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_OnNormal: + m_Background: {fileID: 2800000, guid: f8b3194f397d8934697497c4c07cf47e, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 0} + m_Border: + m_Left: 0 + m_Right: 34 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 2 + m_Top: -3 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 0 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 21 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_Settings: + m_DoubleClickSelectsWord: 1 + m_TripleClickSelectsLine: 1 + m_CursorColor: {r: 1, g: 1, b: 1, a: 1} + m_CursorFlashSpeed: -1 + m_SelectionColor: {r: 1, g: 0.38403907, b: 0, a: 0.7} diff --git a/Assets/Photon/Fusion/Editor/EditorResources/FusionEditorSkin.guiskin.meta b/Assets/Photon/Fusion/Editor/EditorResources/FusionEditorSkin.guiskin.meta new file mode 100644 index 00000000..4784ba0c --- /dev/null +++ b/Assets/Photon/Fusion/Editor/EditorResources/FusionEditorSkin.guiskin.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c809d493e97d33546a90fc00dcd9ba38 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Editor/EditorResources/FusionHub.meta b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub.meta new file mode 100644 index 00000000..ea524b21 --- /dev/null +++ b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 32233be8e24abce40b70c013c4298c14 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-community.png b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-community.png new file mode 100644 index 00000000..cedac710 Binary files /dev/null and b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-community.png differ diff --git a/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-community.png.meta b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-community.png.meta new file mode 100644 index 00000000..cfd0ba4e --- /dev/null +++ b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-community.png.meta @@ -0,0 +1,120 @@ +fileFormatVersion: 2 +guid: 80faca055f57d5b449f78efe31607c57 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-correct-icon.png b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-correct-icon.png new file mode 100644 index 00000000..c07250fd Binary files /dev/null and b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-correct-icon.png differ diff --git a/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-correct-icon.png.meta b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-correct-icon.png.meta new file mode 100644 index 00000000..b686e33f --- /dev/null +++ b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-correct-icon.png.meta @@ -0,0 +1,120 @@ +fileFormatVersion: 2 +guid: 8958b450dee843f41a091a734ed43ccd +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-documentation.png b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-documentation.png new file mode 100644 index 00000000..8dee3476 Binary files /dev/null and b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-documentation.png differ diff --git a/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-documentation.png.meta b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-documentation.png.meta new file mode 100644 index 00000000..e7e5e6df --- /dev/null +++ b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-documentation.png.meta @@ -0,0 +1,120 @@ +fileFormatVersion: 2 +guid: ac3bb1641dbebe441be81b7b3af3606c +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-icon.png b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-icon.png new file mode 100644 index 00000000..e3297080 Binary files /dev/null and b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-icon.png differ diff --git a/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-icon.png.meta b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-icon.png.meta new file mode 100644 index 00000000..0ea450b6 --- /dev/null +++ b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-icon.png.meta @@ -0,0 +1,120 @@ +fileFormatVersion: 2 +guid: ee9f419d0e0655a4882d707f86b99f2c +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-information.png b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-information.png new file mode 100644 index 00000000..458c1612 Binary files /dev/null and b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-information.png differ diff --git a/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-information.png.meta b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-information.png.meta new file mode 100644 index 00000000..f99bb16e --- /dev/null +++ b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-information.png.meta @@ -0,0 +1,120 @@ +fileFormatVersion: 2 +guid: 0a86c2478d62ef74bb46b7906cac7fb2 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-logo.png b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-logo.png new file mode 100644 index 00000000..7b36705c Binary files /dev/null and b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-logo.png differ diff --git a/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-logo.png.meta b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-logo.png.meta new file mode 100644 index 00000000..69436803 --- /dev/null +++ b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-logo.png.meta @@ -0,0 +1,120 @@ +fileFormatVersion: 2 +guid: 3ffdd609e3329cb46a9ac335d4539399 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-photon-cloud-32-dark.png b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-photon-cloud-32-dark.png new file mode 100644 index 00000000..6fffc1b3 Binary files /dev/null and b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-photon-cloud-32-dark.png differ diff --git a/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-photon-cloud-32-dark.png.meta b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-photon-cloud-32-dark.png.meta new file mode 100644 index 00000000..c9fede90 --- /dev/null +++ b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-photon-cloud-32-dark.png.meta @@ -0,0 +1,120 @@ +fileFormatVersion: 2 +guid: 43725de47547036498287ad504fe9f9d +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-samples.png b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-samples.png new file mode 100644 index 00000000..a01465d7 Binary files /dev/null and b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-samples.png differ diff --git a/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-samples.png.meta b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-samples.png.meta new file mode 100644 index 00000000..d1ff08da --- /dev/null +++ b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-samples.png.meta @@ -0,0 +1,120 @@ +fileFormatVersion: 2 +guid: 24a22d21fe312f44892796563f01c302 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-skin-box.png b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-skin-box.png new file mode 100644 index 00000000..e45ccac7 Binary files /dev/null and b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-skin-box.png differ diff --git a/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-skin-box.png.meta b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-skin-box.png.meta new file mode 100644 index 00000000..77bf0f7f --- /dev/null +++ b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-skin-box.png.meta @@ -0,0 +1,120 @@ +fileFormatVersion: 2 +guid: 5c322c17376df3f448fb66679ef2af5d +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-skin-button-active.png b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-skin-button-active.png new file mode 100644 index 00000000..4ac8abe2 Binary files /dev/null and b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-skin-button-active.png differ diff --git a/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-skin-button-active.png.meta b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-skin-button-active.png.meta new file mode 100644 index 00000000..0071a44c --- /dev/null +++ b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-skin-button-active.png.meta @@ -0,0 +1,120 @@ +fileFormatVersion: 2 +guid: d0575c14294b39f4aa7849bf1172ec95 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-skin-button-hover.png b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-skin-button-hover.png new file mode 100644 index 00000000..489d1924 Binary files /dev/null and b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-skin-button-hover.png differ diff --git a/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-skin-button-hover.png.meta b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-skin-button-hover.png.meta new file mode 100644 index 00000000..a8079c4d --- /dev/null +++ b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-skin-button-hover.png.meta @@ -0,0 +1,120 @@ +fileFormatVersion: 2 +guid: 28ab15b073656894582b34905fc3ef9b +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-skin-button.png b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-skin-button.png new file mode 100644 index 00000000..669e921b Binary files /dev/null and b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-skin-button.png differ diff --git a/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-skin-button.png.meta b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-skin-button.png.meta new file mode 100644 index 00000000..d6a5c991 --- /dev/null +++ b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-skin-button.png.meta @@ -0,0 +1,120 @@ +fileFormatVersion: 2 +guid: 7c5bca332d2e7d548a317592d570b483 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-skin-sand.png b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-skin-sand.png new file mode 100644 index 00000000..a8596804 Binary files /dev/null and b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-skin-sand.png differ diff --git a/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-skin-sand.png.meta b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-skin-sand.png.meta new file mode 100644 index 00000000..59ae4a6f --- /dev/null +++ b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-skin-sand.png.meta @@ -0,0 +1,120 @@ +fileFormatVersion: 2 +guid: 808bf90f5f24df3419fc47e88bc82168 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-skin-steel.png b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-skin-steel.png new file mode 100644 index 00000000..932ec099 Binary files /dev/null and b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-skin-steel.png differ diff --git a/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-skin-steel.png.meta b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-skin-steel.png.meta new file mode 100644 index 00000000..ebfa0370 --- /dev/null +++ b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-skin-steel.png.meta @@ -0,0 +1,120 @@ +fileFormatVersion: 2 +guid: 0566916d8e2825c4b8ca741167d4ddda +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-skin-window.png b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-skin-window.png new file mode 100644 index 00000000..4782375b Binary files /dev/null and b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-skin-window.png differ diff --git a/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-skin-window.png.meta b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-skin-window.png.meta new file mode 100644 index 00000000..3ef24b8f --- /dev/null +++ b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-skin-window.png.meta @@ -0,0 +1,120 @@ +fileFormatVersion: 2 +guid: 8acec593dd6702e4e872b506285ffc4d +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-tanknarok-logo.png b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-tanknarok-logo.png new file mode 100644 index 00000000..9dcea07d Binary files /dev/null and b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-tanknarok-logo.png differ diff --git a/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-tanknarok-logo.png.meta b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-tanknarok-logo.png.meta new file mode 100644 index 00000000..4b4be382 --- /dev/null +++ b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/Fusion-hub-tanknarok-logo.png.meta @@ -0,0 +1,120 @@ +fileFormatVersion: 2 +guid: 98358aa88aea883458fa24b6502d1da0 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 2 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/FusionHubSkin.guiskin b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/FusionHubSkin.guiskin new file mode 100644 index 00000000..18f93aba --- /dev/null +++ b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/FusionHubSkin.guiskin @@ -0,0 +1,1692 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12001, guid: 0000000000000000e000000000000000, type: 0} + m_Name: FusionHubSkin + m_EditorClassIdentifier: + m_Font: {fileID: 0} + m_box: + m_Name: box + m_Normal: + m_Background: {fileID: 2800000, guid: 5c322c17376df3f448fb66679ef2af5d, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.79999995, g: 0.79999995, b: 0.79999995, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 6 + m_Right: 6 + m_Top: 6 + m_Bottom: 6 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 12 + m_Right: 12 + m_Top: 12 + m_Bottom: 12 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 4 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_button: + m_Name: button + m_Normal: + m_Background: {fileID: 2800000, guid: 7c5bca332d2e7d548a317592d570b483, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.8584906, g: 0.8584906, b: 0.8584906, a: 1} + m_Hover: + m_Background: {fileID: 2800000, guid: 28ab15b073656894582b34905fc3ef9b, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.8773585, g: 0.8773585, b: 0.8773585, a: 1} + m_Active: + m_Background: {fileID: 2800000, guid: d0575c14294b39f4aa7849bf1172ec95, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.9, g: 0.9, b: 0.9, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.9019608, g: 0.9019608, b: 0.9019608, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.9, g: 0.9, b: 0.9, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 7 + m_Right: 7 + m_Top: 7 + m_Bottom: 7 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 12 + m_Right: 12 + m_Top: 12 + m_Bottom: 12 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 14 + m_FontStyle: 0 + m_Alignment: 3 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_toggle: + m_Name: toggle + m_Normal: + m_Background: {fileID: 11018, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.89112896, g: 0.89112896, b: 0.89112896, a: 1} + m_Hover: + m_Background: {fileID: 11014, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Active: + m_Background: {fileID: 11013, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 11016, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.8901961, g: 0.8901961, b: 0.8901961, a: 1} + m_OnHover: + m_Background: {fileID: 11015, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnActive: + m_Background: {fileID: 11017, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 14 + m_Right: 0 + m_Top: 14 + m_Bottom: 0 + m_Margin: + m_Left: 4 + m_Right: 4 + m_Top: 4 + m_Bottom: 4 + m_Padding: + m_Left: 15 + m_Right: 0 + m_Top: 3 + m_Bottom: 0 + m_Overflow: + m_Left: -1 + m_Right: 0 + m_Top: -4 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_label: + m_Name: label + m_Normal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.9, g: 0.9, b: 0.9, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 6 + m_Right: 6 + m_Top: 6 + m_Bottom: 6 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 12 + m_FontStyle: 0 + m_Alignment: 3 + m_WordWrap: 1 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_textField: + m_Name: textfield + m_Normal: + m_Background: {fileID: 11024, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.79999995, g: 0.79999995, b: 0.79999995, a: 1} + m_Hover: + m_Background: {fileID: 11026, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.9, g: 0.9, b: 0.9, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 11026, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnNormal: + m_Background: {fileID: 11025, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 4 + m_Right: 4 + m_Top: 4 + m_Bottom: 4 + m_Margin: + m_Left: 4 + m_Right: 4 + m_Top: 4 + m_Bottom: 4 + m_Padding: + m_Left: 3 + m_Right: 3 + m_Top: 3 + m_Bottom: 3 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 12 + m_FontStyle: 0 + m_Alignment: 4 + m_WordWrap: 0 + m_RichText: 0 + m_TextClipping: 1 + m_ImagePosition: 3 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_textArea: + m_Name: textarea + m_Normal: + m_Background: {fileID: 11024, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.9019608, g: 0.9019608, b: 0.9019608, a: 1} + m_Hover: + m_Background: {fileID: 11026, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.79999995, g: 0.79999995, b: 0.79999995, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 11025, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 4 + m_Right: 4 + m_Top: 4 + m_Bottom: 4 + m_Margin: + m_Left: 4 + m_Right: 4 + m_Top: 4 + m_Bottom: 4 + m_Padding: + m_Left: 3 + m_Right: 3 + m_Top: 3 + m_Bottom: 3 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 1 + m_RichText: 0 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_window: + m_Name: window + m_Normal: + m_Background: {fileID: 2800000, guid: 8acec593dd6702e4e872b506285ffc4d, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 11022, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 8 + m_Right: 8 + m_Top: 8 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 4 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 0 + m_StretchHeight: 0 + m_horizontalSlider: + m_Name: horizontalslider + m_Normal: + m_Background: {fileID: 11009, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 3 + m_Right: 3 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 4 + m_Right: 4 + m_Top: 4 + m_Bottom: 4 + m_Padding: + m_Left: -1 + m_Right: -1 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: -2 + m_Bottom: -3 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 2 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 12 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_horizontalSliderThumb: + m_Name: horizontalsliderthumb + m_Normal: + m_Background: {fileID: 11011, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 11012, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 11010, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 4 + m_Right: 4 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 7 + m_Right: 7 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: -1 + m_Right: -1 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 2 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 12 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_verticalSlider: + m_Name: verticalslider + m_Normal: + m_Background: {fileID: 11021, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 3 + m_Bottom: 3 + m_Margin: + m_Left: 4 + m_Right: 4 + m_Top: 4 + m_Bottom: 4 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: -1 + m_Bottom: -1 + m_Overflow: + m_Left: -2 + m_Right: -3 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 12 + m_FixedHeight: 0 + m_StretchWidth: 0 + m_StretchHeight: 1 + m_verticalSliderThumb: + m_Name: verticalsliderthumb + m_Normal: + m_Background: {fileID: 11011, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 11012, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 11010, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 7 + m_Bottom: 7 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: -1 + m_Bottom: -1 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 12 + m_FixedHeight: 0 + m_StretchWidth: 0 + m_StretchHeight: 1 + m_horizontalScrollbar: + m_Name: horizontalscrollbar + m_Normal: + m_Background: {fileID: 11008, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 9 + m_Right: 9 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 4 + m_Right: 4 + m_Top: 1 + m_Bottom: 4 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 2 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 15 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_horizontalScrollbarThumb: + m_Name: horizontalscrollbarthumb + m_Normal: + m_Background: {fileID: 11007, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 6 + m_Right: 6 + m_Top: 6 + m_Bottom: 6 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 6 + m_Right: 6 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: -1 + m_Bottom: 1 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 13 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_horizontalScrollbarLeftButton: + m_Name: horizontalscrollbarleftbutton + m_Normal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_horizontalScrollbarRightButton: + m_Name: horizontalscrollbarrightbutton + m_Normal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_verticalScrollbar: + m_Name: verticalscrollbar + m_Normal: + m_Background: {fileID: 11020, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 9 + m_Bottom: 9 + m_Margin: + m_Left: 1 + m_Right: 4 + m_Top: 4 + m_Bottom: 4 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 1 + m_Bottom: 1 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 15 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_verticalScrollbarThumb: + m_Name: verticalscrollbarthumb + m_Normal: + m_Background: {fileID: 11019, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 6 + m_Right: 6 + m_Top: 6 + m_Bottom: 6 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 6 + m_Bottom: 6 + m_Overflow: + m_Left: -1 + m_Right: -1 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 2 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 15 + m_FixedHeight: 0 + m_StretchWidth: 0 + m_StretchHeight: 1 + m_verticalScrollbarUpButton: + m_Name: verticalscrollbarupbutton + m_Normal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_verticalScrollbarDownButton: + m_Name: verticalscrollbardownbutton + m_Normal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_ScrollView: + m_Name: scrollview + m_Normal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_CustomStyles: + - m_Name: thumb + m_Normal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + - m_Name: leftbutton + m_Normal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + - m_Name: rightbutton + m_Normal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + - m_Name: SteelBox + m_Normal: + m_Background: {fileID: 2800000, guid: 0566916d8e2825c4b8ca741167d4ddda, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.8867924, g: 0.8867924, b: 0.8867924, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 8 + m_Right: 8 + m_Top: 8 + m_Bottom: 8 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 16 + m_Right: 16 + m_Top: 16 + m_Bottom: 16 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 3 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + - m_Name: HeaderLogo + m_Normal: + m_Background: {fileID: 2800000, guid: 3ffdd609e3329cb46a9ac335d4539399, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.8867924, g: 0.8867924, b: 0.8867924, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 8 + m_Right: 8 + m_Top: 8 + m_Bottom: 8 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 16 + m_Right: 16 + m_Top: 16 + m_Bottom: 16 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 14 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_Settings: + m_DoubleClickSelectsWord: 1 + m_TripleClickSelectsLine: 1 + m_CursorColor: {r: 1, g: 1, b: 1, a: 1} + m_CursorFlashSpeed: -1 + m_SelectionColor: {r: 1, g: 0.38403907, b: 0, a: 0.7} diff --git a/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/FusionHubSkin.guiskin.meta b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/FusionHubSkin.guiskin.meta new file mode 100644 index 00000000..2bbc1c7c --- /dev/null +++ b/Assets/Photon/Fusion/Editor/EditorResources/FusionHub/FusionHubSkin.guiskin.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: db84c499838991343a5a0a419616fcbe +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Editor/Fusion.Unity.Editor.AssemblyAttributes.cs b/Assets/Photon/Fusion/Editor/Fusion.Unity.Editor.AssemblyAttributes.cs new file mode 100644 index 00000000..9006ca20 --- /dev/null +++ b/Assets/Photon/Fusion/Editor/Fusion.Unity.Editor.AssemblyAttributes.cs @@ -0,0 +1,18 @@ +#if !FUSION_DEV + +#region Assets/Photon/Fusion/Editor/AssemblyAttributes/FusionEditorAssemblyAttributes.Common.cs + +// merged EditorAssemblyAttributes + +#region RegisterEditorLoader.cs + +// the default edit-mode loader +[assembly: Fusion.Editor.FusionGlobalScriptableObjectEditorAttribute(typeof(Fusion.FusionGlobalScriptableObject), AllowEditMode = true, Order = int.MaxValue)] + +#endregion + + + +#endregion + +#endif diff --git a/Assets/Photon/Fusion/Editor/Fusion.Unity.Editor.AssemblyAttributes.cs.meta b/Assets/Photon/Fusion/Editor/Fusion.Unity.Editor.AssemblyAttributes.cs.meta new file mode 100644 index 00000000..c790535a --- /dev/null +++ b/Assets/Photon/Fusion/Editor/Fusion.Unity.Editor.AssemblyAttributes.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5094317ddb6bdc049aac25f854861ab9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Editor/Fusion.Unity.Editor.asmdef b/Assets/Photon/Fusion/Editor/Fusion.Unity.Editor.asmdef new file mode 100644 index 00000000..01a3b2cd --- /dev/null +++ b/Assets/Photon/Fusion/Editor/Fusion.Unity.Editor.asmdef @@ -0,0 +1,73 @@ +{ + "name": "Fusion.Unity.Editor", + "rootNamespace": "Fusion", + "references": [ + "GUID:142cc54f3f73e4dabac1c30b19afb51d", + "GUID:19e39f4cea842594788e0c7be4c044c8", + "GUID:1baf1ffff1031e948b3b228ab3453251", + "GUID:9e24947de15b9834991c9d8411ea37cf", + "GUID:69448af7b92c7f342b298e06a37122aa", + "GUID:84651a3751eca9349aac36a66bba901b" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": true, + "overrideReferences": true, + "precompiledReferences": [ + "Mono.Cecil.dll", + "Mono.Cecil.Pdb.dll", + "Mono.Cecil.Mdb.dll", + "Mono.Cecil.Rocks.dll", + "Fusion.Common.dll", + "Fusion.Runtime.dll", + "Fusion.Realtime.dll", + "Fusion.Log.dll", + "Sirenix.OdinInspector.Editor.dll", + "Sirenix.Utilities.Editor.dll", + "Sirenix.Utilities.dll", + "Sirenix.OdinInspector.Attributes.dll", + "Newtonsoft.Json.dll" + ], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [ + { + "name": "com.unity.nuget.mono-cecil", + "expression": "1", + "define": "FUSION_HAS_MONO_CECIL" + }, + { + "name": "nuget.mono-cecil", + "expression": "0.1", + "define": "FUSION_HAS_MONO_CECIL" + }, + { + "name": "com.unity.nuget.mono-cecil", + "expression": "1.11", + "define": "FUSION_CECIL_1_11_OR_NEWER" + }, + { + "name": "com.unity.addressables", + "expression": "1.0", + "define": "FUSION_ENABLE_ADDRESSABLES" + }, + { + "name": "com.unity.ide.rider", + "expression": "1.0", + "define": "FUSION_HAS_JETBRAINS_ATTRIBUTES" + }, + { + "name": "com.unity.nuget.newtonsoft-json", + "expression": "1.0", + "define": "FUSION_HAS_NEWTONSOFT_JSON" + }, + { + "name": "com.unity.multiplayer.playmode", + "expression": "0.6", + "define": "FUSION_HAS_MPPM" + } + ], + "noEngineReferences": false +} diff --git a/Assets/Photon/Fusion/Editor/Fusion.Unity.Editor.asmdef.meta b/Assets/Photon/Fusion/Editor/Fusion.Unity.Editor.asmdef.meta new file mode 100644 index 00000000..42ebe571 --- /dev/null +++ b/Assets/Photon/Fusion/Editor/Fusion.Unity.Editor.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 4d449a4732e064d26a3069f2cfd88917 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Editor/Fusion.Unity.Editor.cs b/Assets/Photon/Fusion/Editor/Fusion.Unity.Editor.cs new file mode 100644 index 00000000..8ef000bf --- /dev/null +++ b/Assets/Photon/Fusion/Editor/Fusion.Unity.Editor.cs @@ -0,0 +1,15150 @@ +#if !FUSION_DEV + +#region Assets/Photon/Fusion/Editor/AssetObjectEditor.cs + +namespace Fusion.Editor { + using UnityEditor; + + [CustomEditor(typeof(AssetObject), true)] + public class AssetObjectEditor : UnityEditor.Editor { + public override void OnInspectorGUI() { + base.OnInspectorGUI(); + } + } +} + + +#endregion + + +#region Assets/Photon/Fusion/Editor/BehaviourEditor.cs + +namespace Fusion.Editor { + + using System; + using System.Collections.Generic; + using System.Linq; + using UnityEditor; + + [CustomEditor(typeof(Fusion.Behaviour), true)] + [CanEditMultipleObjects] + public partial class BehaviourEditor : FusionEditor { + } +} + + +#endregion + + +#region Assets/Photon/Fusion/Editor/ChangeDllManager.cs + +namespace Fusion.Editor { + using System; + using System.IO; + using System.Linq; + using UnityEditor; + using UnityEngine; + + /// + /// Provides methods to toggle between different DLL modes for the Fusion framework. + /// + public static class ChangeDllManager { + private const string FusionRuntimeDllGuid = "e725a070cec140c4caffb81624c8c787"; + + private static readonly string[] FileList = { "Fusion.Common.dll", "Fusion.Runtime.dll", "Fusion.Realtime.dll", "Fusion.Sockets.dll", "Fusion.Log.dll" }; + + /// + /// Changes the DLL mode to Debug. + /// + [MenuItem("Tools/Fusion/Change Dll Mode/Debug", false, 500)] + public static void ChangeDllModeToSharedDebug() { + ChangeDllMode(NetworkRunner.BuildTypes.Debug); + } + + /// + /// Changes the DLL mode to Release. + /// + [MenuItem("Tools/Fusion/Change Dll Mode/Release", false, 501)] + public static void ChangeDllModeToSharedRelease() { + ChangeDllMode(NetworkRunner.BuildTypes.Release); + } + + /// + /// Changes the DLL mode based on the specified build type and build mode. + /// + /// The build type (). + private static void ChangeDllMode(NetworkRunner.BuildTypes buildType) { + if (NetworkRunner.BuildType == buildType) { + Debug.Log($"Fusion Dll Mode is already {buildType}"); + return; + } + + Debug.Log($"Changing Fusion Dll Mode from {NetworkRunner.BuildType} to {buildType}"); + + var targetExtension = $"{GetBuildTypeExtension(buildType)}"; + var targetSubFolder = GetBuildTypeSubFolder(buildType); + + // find the root + var fusionRuntimeDllPath = AssetDatabase.GUIDToAssetPath(FusionRuntimeDllGuid); + if (string.IsNullOrEmpty(fusionRuntimeDllPath)) { + Debug.LogError($"Cannot locate Fusion assemblies directory"); + return; + } + + // Check if all dlls are present + var assembliesDir = PathUtils.Normalize(Path.GetDirectoryName(fusionRuntimeDllPath)); + var originalFileTemplate = $"{assembliesDir}/{{0}}"; + var targetFileTemplate = $"{assembliesDir}/{targetSubFolder}/{{0}}{targetExtension}"; + var currentDlls = FileList.All(f => File.Exists(string.Format(originalFileTemplate, f))); + var targetDlls = FileList.All(f => File.Exists(string.Format(targetFileTemplate, f))); + + if (currentDlls == false) { + Debug.LogError("Cannot find all Fusion dlls"); + return; + } + + if (targetDlls == false) { + Debug.LogError($"Cannot find all Fusion dlls marked with {targetExtension}"); + return; + } + + if (FileList.Any(f => new FileInfo(string.Format(targetFileTemplate, f)).Length == 0)) { + Debug.LogError("Targets dlls are not valid"); + return; + } + + // Move the files + try { + foreach (var f in FileList) { + var source = string.Format(targetFileTemplate, f); + var dest = string.Format(originalFileTemplate, f); + + Debug.Log($"Moving {source} to {dest}"); + FileUtil.ReplaceFile(source, dest); + } + + Debug.Log($"Activated Fusion {buildType} dlls"); + } catch (Exception e) { + Debug.LogAssertion(e); + Debug.LogError($"Failed to Change Fusion Dll Mode"); + } + + AssetDatabase.Refresh(); + + return; + + // Gets the file extension for the specified build type. + string GetBuildTypeExtension(NetworkRunner.BuildTypes referenceBuildType) => + referenceBuildType switch { + NetworkRunner.BuildTypes.Debug => ".debug", + NetworkRunner.BuildTypes.Release => ".release", + _ => throw new ArgumentOutOfRangeException() + }; + + // Gets the subfolder name for the specified build type. + string GetBuildTypeSubFolder(NetworkRunner.BuildTypes referenceBuildModes) => + referenceBuildModes switch { + NetworkRunner.BuildTypes.Debug => "Debug", + NetworkRunner.BuildTypes.Release => "Release", + _ => throw new ArgumentOutOfRangeException() + }; + } + } +} + +#endregion + + +#region Assets/Photon/Fusion/Editor/ChildLookupEditor.cs + +// removed July 12 2021 + + +#endregion + + +#region Assets/Photon/Fusion/Editor/CustomTypes/FixedBufferPropertyAttributeDrawer.cs + +namespace Fusion.Editor { + + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + using Fusion.Internal; + using Unity.Collections.LowLevel.Unsafe; + using UnityEditor; + using UnityEditor.Compilation; + using UnityEngine; + + [CustomPropertyDrawer(typeof(FixedBufferPropertyAttribute))] + unsafe class FixedBufferPropertyAttributeDrawer : PropertyDrawerWithErrorHandling { + public const string FixedBufferFieldName = "Data"; + public const string WrapperSurrogateDataPath = "Surrogate.Data"; + + private const float SpacingSubLabel = 2; + private static readonly int _multiFieldPrefixId = "MultiFieldPrefixId".GetHashCode(); + private static int[] _buffer = Array.Empty(); + + private static SurrogatePool _pool = new SurrogatePool(); + private static GUIContent[] _vectorProperties = new[] { + new GUIContent("X"), + new GUIContent("Y"), + new GUIContent("Z"), + new GUIContent("W"), + }; + + private Dictionary _needsSurrogateCache = new Dictionary(); + private Dictionary _optimisedReaderWriters = new Dictionary(); + + private Type ActualFieldType => ((FixedBufferPropertyAttribute)attribute).Type; + private int Capacity => ((FixedBufferPropertyAttribute)attribute).Capacity; + private Type SurrogateType => ((FixedBufferPropertyAttribute)attribute).SurrogateType; + + public override float GetPropertyHeight(SerializedProperty property, GUIContent label) { + if (SurrogateType == null) { + return EditorGUIUtility.singleLineHeight; + } + + if (NeedsSurrogate(property)) { + var fixedBufferProperty = GetFixedBufferProperty(property); + var firstElement = fixedBufferProperty.GetFixedBufferElementAtIndex(0); + if (!firstElement.IsArrayElement()) { + // it seems that with multiple seclection child elements are not accessible + Debug.Assert(property.serializedObject.targetObjects.Length > 1); + return EditorGUIUtility.singleLineHeight; + } + + var wrapper = _pool.Acquire(fieldInfo, Capacity, property, SurrogateType); + try { + return EditorGUI.GetPropertyHeight(wrapper.Property); + } catch (Exception ex) { + FusionEditorLog.ErrorInspector($"Error in GetPropertyHeight for {property.propertyPath}: {ex}"); + return EditorGUIUtility.singleLineHeight; + } + + } else { + int count = 1; + if (!EditorGUIUtility.wideMode) { + count++; + } + return count * (EditorGUIUtility.singleLineHeight) + (count - 1) * EditorGUIUtility.standardVerticalSpacing; + } + } + + protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) { + if (NeedsSurrogate(property)) { + if (SurrogateType == null) { + this.SetInfo($"[Networked] properties of type {ActualFieldType.FullName} in structs are not yet supported"); + EditorGUI.LabelField(position, label, GUIContent.none); + } else { + int capacity = Capacity; + var fixedBufferProperty = GetFixedBufferProperty(property); + + Array.Resize(ref _buffer, Math.Max(_buffer.Length, fixedBufferProperty.fixedBufferSize)); + + var firstElement = fixedBufferProperty.GetFixedBufferElementAtIndex(0); + if (!firstElement.IsArrayElement()) { + Debug.Assert(property.serializedObject.targetObjects.Length > 1); + SetInfo($"Type does not support multi-edit"); + EditorGUI.LabelField(position, label); + } else { + var wrapper = _pool.Acquire(fieldInfo, Capacity, property, SurrogateType); + + { + bool surrogateOutdated = false; + var targetObjects = property.serializedObject.targetObjects; + if (targetObjects.Length > 1) { + for (int i = 0; i < targetObjects.Length; ++i) { + using (var so = new SerializedObject(targetObjects[i])) { + using (var sp = so.FindPropertyOrThrow($"{property.propertyPath}.Data")) { + if (UpdateSurrogateFromFixedBuffer(sp, wrapper.Surrogates[i], false, _pool.Flush)) { + surrogateOutdated = true; + } + } + } + } + + if (surrogateOutdated) { + // it seems that a mere Update won't do here + wrapper.Property = new SerializedObject(wrapper.Wrappers).FindPropertyOrThrow(WrapperSurrogateDataPath); + } + } else { + // an optimised path, no alloc needed + Debug.Assert(wrapper.Surrogates.Length == 1); + if (UpdateSurrogateFromFixedBuffer(fixedBufferProperty, wrapper.Surrogates[0], false, _pool.Flush)) { + wrapper.Property.serializedObject.Update(); + } + } + } + + // check if there has been any chagnes + EditorGUI.BeginChangeCheck(); + EditorGUI.BeginProperty(position, label, property); + + try { + EditorGUI.PropertyField(position, wrapper.Property, label, true); + } catch (Exception ex) { + FusionEditorLog.ErrorInspector($"Error in OnGUIInternal for {property.propertyPath}: {ex}"); + } + + EditorGUI.EndProperty(); + if (EditorGUI.EndChangeCheck()) { + wrapper.Property.serializedObject.ApplyModifiedProperties(); + + // when not having multiple different values, just write the whole thing + if (UpdateSurrogateFromFixedBuffer(fixedBufferProperty, wrapper.Surrogates[0], true, !fixedBufferProperty.hasMultipleDifferentValues)) { + fixedBufferProperty.serializedObject.ApplyModifiedProperties(); + + // refresh? + wrapper.Property.serializedObject.Update(); + } + } + } + } + } else { + if (!_optimisedReaderWriters.TryGetValue(SurrogateType, out var surrogate)) { + surrogate = (UnitySurrogateBase)Activator.CreateInstance(SurrogateType); + _optimisedReaderWriters.Add(SurrogateType, surrogate); + } + + if (ActualFieldType == typeof(float)) { + DoFloatField(position, property, label, (IUnityValueSurrogate)surrogate); + } else if (ActualFieldType == typeof(Vector2)) { + DoFloatVectorProperty(position, property, label, 2, (IUnityValueSurrogate)surrogate); + } else if (ActualFieldType == typeof(Vector3)) { + DoFloatVectorProperty(position, property, label, 3, (IUnityValueSurrogate)surrogate); + } else if (ActualFieldType == typeof(Vector4)) { + DoFloatVectorProperty(position, property, label, 4, (IUnityValueSurrogate)surrogate); + } + } + } + + private void DoFloatField(Rect position, SerializedProperty property, GUIContent label, IUnityValueSurrogate surrogate) { + var fixedBuffer = GetFixedBufferProperty(property); + Debug.Assert(1 == fixedBuffer.fixedBufferSize); + + var valueProp = fixedBuffer.GetFixedBufferElementAtIndex(0); + int value = valueProp.intValue; + surrogate.Read(&value, 1); + + EditorGUI.BeginProperty(position, label, property); + EditorGUI.BeginChangeCheck(); + surrogate.DataProperty = EditorGUI.FloatField(position, label, surrogate.DataProperty); + if (EditorGUI.EndChangeCheck()) { + surrogate.Write(&value, 1); + valueProp.intValue = value; + property.serializedObject.ApplyModifiedProperties(); + } + EditorGUI.EndProperty(); + } + + private unsafe void DoFloatVectorProperty(Rect position, SerializedProperty property, GUIContent label, int count, IUnityValueSurrogate readerWriter) where T : unmanaged { + EditorGUI.BeginProperty(position, label, property); + try { + var fixedBuffer = GetFixedBufferProperty(property); + Debug.Assert(count == fixedBuffer.fixedBufferSize); + + int* raw = stackalloc int[count]; + for (int i = 0; i < count; ++i) { + raw[i] = fixedBuffer.GetFixedBufferElementAtIndex(i).intValue; + } + + readerWriter.Read(raw, 1); + + int changed = 0; + + var data = readerWriter.DataProperty; + float* pdata = (float*)&data; + + int id = GUIUtility.GetControlID(_multiFieldPrefixId, FocusType.Keyboard, position); + position = UnityInternal.EditorGUI.MultiFieldPrefixLabel(position, id, label, count); + if (position.width > 1) { + using (new EditorGUI.IndentLevelScope(-EditorGUI.indentLevel)) { + float w = (position.width - (count - 1) * SpacingSubLabel) / count; + var nestedPosition = new Rect(position) { width = w }; + + for (int i = 0; i < count; ++i) { + var propLabel = _vectorProperties[i]; + float prefixWidth = EditorStyles.label.CalcSize(propLabel).x; + using (new FusionEditorGUI.LabelWidthScope(prefixWidth)) { + EditorGUI.BeginChangeCheck(); + var newValue = propLabel == null ? EditorGUI.FloatField(nestedPosition, pdata[i]) : EditorGUI.FloatField(nestedPosition, propLabel, pdata[i]); + if (EditorGUI.EndChangeCheck()) { + changed |= (1 << i); + pdata[i] = newValue; + } + } + nestedPosition.x += w + SpacingSubLabel; + } + } + } + + if (changed != 0) { + readerWriter.DataProperty = data; + readerWriter.Write(raw, 1); + + for (int i = 0; i < count; ++i) { + if ((changed & (1 << i)) != 0) { + fixedBuffer.GetFixedBufferElementAtIndex(i).intValue = raw[i]; + } + } + property.serializedObject.ApplyModifiedProperties(); + } + } finally { + EditorGUI.EndProperty(); + } + } + + private SerializedProperty GetFixedBufferProperty(SerializedProperty prop) { + var result = prop.FindPropertyRelativeOrThrow(FixedBufferFieldName); + Debug.Assert(result.isFixedBuffer); + return result; + } + + private bool NeedsSurrogate(SerializedProperty property) { + if (_needsSurrogateCache.TryGetValue(property.propertyPath, out var result)) { + return result; + } + + result = true; + if (ActualFieldType == typeof(float) || ActualFieldType == typeof(Vector2) || ActualFieldType == typeof(Vector3) || ActualFieldType == typeof(Vector4)) { + var attributes = UnityInternal.ScriptAttributeUtility.GetFieldAttributes(fieldInfo); + if (attributes == null || attributes.Count == 0) { + // fast drawers do not support any additional attributes + result = false; + } + } + + _needsSurrogateCache.Add(property.propertyPath, result); + return result; + } + + private bool UpdateSurrogateFromFixedBuffer(SerializedProperty sp, UnitySurrogateBase surrogate, bool write, bool force) { + int count = sp.fixedBufferSize; + Array.Resize(ref _buffer, Math.Max(_buffer.Length, count)); + + // need to get to the first property... `GetFixedBufferElementAtIndex` is slow and allocs + + var element = sp.Copy(); + element.Next(true); // .Array + element.Next(true); // .Array.size + element.Next(true); // .Array.data[0] + + fixed (int* p = _buffer) { + UnsafeUtility.MemClear(p, count * sizeof(int)); + + try { + surrogate.Write(p, Capacity); + } catch (Exception ex) { + SetError($"Failed writing: {ex}"); + } + + int i = 0; + if (!force) { + // find first difference + for (; i < count; ++i, element.Next(true)) { + Debug.Assert(element.propertyType == SerializedPropertyType.Integer); + if (element.intValue != p[i]) { + break; + } + } + } + + if (i < count) { + // update data + if (write) { + for (; i < count; ++i, element.Next(true)) { + element.intValue = p[i]; + } + } else { + for (; i < count; ++i, element.Next(true)) { + p[i] = element.intValue; + } + } + // update surrogate + surrogate.Read(p, Capacity); + return true; + } else { + return false; + } + } + } + + private class SurrogatePool { + + private const int MaxTTL = 10; + + private FieldInfo _surrogateField = typeof(FusionUnitySurrogateBaseWrapper).GetField(nameof(FusionUnitySurrogateBaseWrapper.Surrogate)); + private Dictionary<(Type, string, int), PropertyEntry> _used = new Dictionary<(Type, string, int), PropertyEntry>(); + private Dictionary> _wrappersPool = new Dictionary>(); + + public SurrogatePool() { + Undo.undoRedoPerformed += () => Flush = true; + + EditorApplication.update += () => { + Flush = false; + if (!WasUsed) { + return; + } + WasUsed = false; + + var keysToRemove = new List<(Type, string, int)>(); + + foreach (var kv in _used) { + var entry = kv.Value; + if (--entry.TTL < 0) { + // return to pool + keysToRemove.Add(kv.Key); + foreach (var wrapper in entry.Wrappers) { + _wrappersPool[wrapper.Surrogate.GetType()].Push(wrapper); + } + } + } + + // make all the wrappers available again + foreach (var key in keysToRemove) { + FusionEditorLog.TraceInspector($"Cleaning up {key}"); + _used.Remove(key); + } + }; + + CompilationPipeline.compilationFinished += obj => { + // destroy SO's, we don't want them to hold on to the surrogates + + var wrappers = _wrappersPool.Values.SelectMany(x => x) + .Concat(_used.Values.SelectMany(x => x.Wrappers)); + + foreach (var wrapper in wrappers) { + UnityEngine.Object.DestroyImmediate(wrapper); + } + }; + } + + public bool Flush { get; private set; } + + public bool WasUsed { get; private set; } + + public PropertyEntry Acquire(FieldInfo field, int capacity, SerializedProperty property, Type type) { + WasUsed = true; + + bool hadNulls = false; + + var key = (type, property.propertyPath, property.serializedObject.targetObjects.Length); + if (_used.TryGetValue(key, out var entry)) { + var countValid = entry.Wrappers.Count(x => x); + if (countValid != entry.Wrappers.Length) { + // something destroyed wrappers + Debug.Assert(countValid == 0); + _used.Remove(key); + hadNulls = true; + } else { + entry.TTL = MaxTTL; + return entry; + } + } + + // acquire new entry + var wrappers = new FusionUnitySurrogateBaseWrapper[key.Item3]; + if (!_wrappersPool.TryGetValue(type, out var pool)) { + pool = new Stack(); + _wrappersPool.Add(type, pool); + } + + for (int i = 0; i < wrappers.Length; ++i) { + + // pop destroyed ones + while (pool.Count > 0 && !pool.Peek()) { + pool.Pop(); + hadNulls = true; + } + + if (pool.Count > 0) { + wrappers[i] = pool.Pop(); + } else { + FusionEditorLog.TraceInspector($"Allocating surrogate {type}"); + wrappers[i] = ScriptableObject.CreateInstance(); + } + + if (wrappers[i].SurrogateType != type) { + FusionEditorLog.TraceInspector($"Replacing type {wrappers[i].Surrogate?.GetType()} with {type}"); + wrappers[i].Surrogate = (UnitySurrogateBase)Activator.CreateInstance(type); + wrappers[i].Surrogate.Init(capacity); + wrappers[i].SurrogateType = type; + } + } + + FusionEditorLog.TraceInspector($"Created entry for {property.propertyPath}"); + + entry = new PropertyEntry() { + Property = new SerializedObject(wrappers).FindPropertyOrThrow(WrapperSurrogateDataPath), + Surrogates = wrappers.Select(x => x.Surrogate).ToArray(), + TTL = MaxTTL, + Wrappers = wrappers + }; + + _used.Add(key, entry); + + if (hadNulls) { + GUIUtility.ExitGUI(); + } + + return entry; + } + + public class PropertyEntry { + public SerializedProperty Property; + public UnitySurrogateBase[] Surrogates; + public int TTL; + public FusionUnitySurrogateBaseWrapper[] Wrappers; + } + } + } +} + +#endregion + + +#region Assets/Photon/Fusion/Editor/CustomTypes/INetworkPrefabSourceDrawer.cs + +namespace Fusion.Editor { + using System; + using UnityEditor; + using UnityEngine; + + [CustomPropertyDrawer(typeof(INetworkPrefabSource), true)] + class INetworkPrefabSourceDrawer : PropertyDrawerWithErrorHandling { + + const int ThumbnailWidth = 20; + + protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) { + + using (new FusionEditorGUI.PropertyScopeWithPrefixLabel(position, label, property, out position)) { + + EditorGUI.BeginChangeCheck(); + + var source = property.managedReferenceValue as INetworkPrefabSource; + position = DrawThumbnailPrefix(position, source); + source = DrawSourceObjectPicker(position, GUIContent.none, source); + + if (EditorGUI.EndChangeCheck()) { + // see how it can be loaded + property.managedReferenceValue = source; + property.serializedObject.ApplyModifiedProperties(); + } + } + } + + public static Rect DrawThumbnailPrefix(Rect position, INetworkPrefabSource source) { + if (source == null) { + return position; + } + + var pos = position; + pos.width = ThumbnailWidth; + FusionEditorGUI.DrawTypeThumbnail(pos, source.GetType(), "NetworkPrefabSource", source.Description); + position.xMin += ThumbnailWidth; + return position; + } + + public static void DrawThumbnail(Rect position, INetworkPrefabSource source) { + if (source == null) { + return; + } + var pos = position; + pos.x += (pos.width - ThumbnailWidth) / 2; + pos.width = ThumbnailWidth; + FusionEditorGUI.DrawTypeThumbnail(pos, source.GetType(), "NetworkPrefabSource", source.Description); + } + + public static INetworkPrefabSource DrawSourceObjectPicker(Rect position, GUIContent label, INetworkPrefabSource source) { + NetworkProjectConfigUtilities.TryGetPrefabEditorInstance(source?.AssetGuid ?? default, out var target); + + EditorGUI.BeginChangeCheck(); + target = NetworkPrefabRefDrawer.DrawNetworkPrefabPicker(position, label, target); + if (EditorGUI.EndChangeCheck()) { + if (target) { + var factory = new NetworkAssetSourceFactory(); + return factory.TryCreatePrefabSource(new NetworkAssetSourceFactoryContext(target)); + } else { + return null; + } + } else { + return source; + } + } + + public override float GetPropertyHeight(SerializedProperty property, GUIContent label) { + return EditorGUIUtility.singleLineHeight; + } + } +} + + +#endregion + + +#region Assets/Photon/Fusion/Editor/CustomTypes/NetworkBoolDrawer.cs + +namespace Fusion.Editor { + using System; + using System.Collections.Generic; + using System.Reflection; + using UnityEditor; + using UnityEngine; + + [CustomPropertyDrawer(typeof(NetworkBool))] + public class NetworkBoolDrawer : PropertyDrawer { + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { + using (new FusionEditorGUI.PropertyScope(position, label, property)) { + var valueProperty = property.FindPropertyRelativeOrThrow("_value"); + EditorGUI.BeginChangeCheck(); + bool isChecked = EditorGUI.Toggle(position, label, valueProperty.intValue > 0); + if (EditorGUI.EndChangeCheck()) { + valueProperty.intValue = isChecked ? 1 : 0; + valueProperty.serializedObject.ApplyModifiedProperties(); + } + } + } + } +} + +#endregion + + +#region Assets/Photon/Fusion/Editor/CustomTypes/NetworkObjectGuidDrawer.cs + +namespace Fusion.Editor { + using UnityEditor; + using UnityEngine; + + [CustomPropertyDrawer(typeof(NetworkObjectGuid))] + [FusionPropertyDrawerMeta(HasFoldout = false)] + class NetworkObjectGuidDrawer : PropertyDrawerWithErrorHandling { + + protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) { + var guid = GetValue(property); + + using (new FusionEditorGUI.PropertyScopeWithPrefixLabel(position, label, property, out position)) { + if (!GUI.enabled) { + GUI.enabled = true; + EditorGUI.SelectableLabel(position, $"{(System.Guid)guid}"); + GUI.enabled = false; + } else { + EditorGUI.BeginChangeCheck(); + + var text = EditorGUI.TextField(position, ((System.Guid)guid).ToString()); + ClearErrorIfLostFocus(); + + if (EditorGUI.EndChangeCheck()) { + if (NetworkObjectGuid.TryParse(text, out guid)) { + SetValue(property, guid); + property.serializedObject.ApplyModifiedProperties(); + } else { + SetError($"Unable to parse {text}"); + } + } + } + } + } + + public static unsafe NetworkObjectGuid GetValue(SerializedProperty property) { + var guid = new NetworkObjectGuid(); + var prop = property.FindPropertyRelativeOrThrow(nameof(NetworkObjectGuid.RawGuidValue)); + guid.RawGuidValue[0] = prop.GetFixedBufferElementAtIndex(0).longValue; + guid.RawGuidValue[1] = prop.GetFixedBufferElementAtIndex(1).longValue; + return guid; + } + + public static unsafe void SetValue(SerializedProperty property, NetworkObjectGuid guid) { + var prop = property.FindPropertyRelativeOrThrow(nameof(NetworkObjectGuid.RawGuidValue)); + prop.GetFixedBufferElementAtIndex(0).longValue = guid.RawGuidValue[0]; + prop.GetFixedBufferElementAtIndex(1).longValue = guid.RawGuidValue[1]; + } + } +} + +#endregion + + +#region Assets/Photon/Fusion/Editor/CustomTypes/NetworkPrefabAttributeDrawer.cs + +namespace Fusion.Editor { + using System; + using System.Collections.Generic; + using System.Reflection; + using UnityEditor; + using UnityEngine; + + [CustomPropertyDrawer(typeof(NetworkPrefabAttribute))] + [FusionPropertyDrawerMeta(HasFoldout = false)] + class NetworkPrefabAttributeDrawer : PropertyDrawerWithErrorHandling { + + protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) { + + var leafType = fieldInfo.FieldType.GetUnityLeafType(); + if (leafType != typeof(GameObject) && leafType != typeof(NetworkObject) && !leafType.IsSubclassOf(typeof(NetworkObject))) { + SetError($"{nameof(NetworkPrefabAttribute)} only works for {typeof(GameObject)} and {typeof(NetworkObject)} fields"); + return; + } + + using (new FusionEditorGUI.PropertyScopeWithPrefixLabel(position, label, property, out position)) { + + GameObject prefab; + if (leafType == typeof(GameObject)) { + prefab = (GameObject)property.objectReferenceValue; + } else { + var component = (NetworkObject)property.objectReferenceValue; + prefab = component != null ? component.gameObject : null; + } + + EditorGUI.BeginChangeCheck(); + + prefab = (GameObject)EditorGUI.ObjectField(position, prefab, typeof(GameObject), false); + + // ensure the results are filtered + if (UnityInternal.ObjectSelector.isVisible) { + var selector = UnityInternal.ObjectSelector.get; + if (UnityInternal.EditorGUIUtility.LastControlID == selector.objectSelectorID) { + var filter = selector.searchFilter; + if (!filter.Contains(NetworkProjectConfigImporter.FusionPrefabTagSearchTerm)) { + if (string.IsNullOrEmpty(filter)) { + filter = NetworkProjectConfigImporter.FusionPrefabTagSearchTerm; + } else { + filter = NetworkProjectConfigImporter.FusionPrefabTagSearchTerm + " " + filter; + } + selector.searchFilter = filter; + } + } + } + + if (EditorGUI.EndChangeCheck()) { + UnityEngine.Object result; + if (!prefab) { + result = null; + } else { + if (leafType == typeof(GameObject)) { + result = prefab; + } else { + result = prefab.GetComponent(leafType); + if (!result) { + SetError($"Prefab {prefab} does not have a {leafType} component"); + return; + } + } + } + + property.objectReferenceValue = prefab; + property.serializedObject.ApplyModifiedProperties(); + } + + if (prefab) { + var no = prefab.GetComponent(); + if (!no) { + SetError($"Prefab {prefab} does not have a {nameof(NetworkObject)} component"); + } + if (!AssetDatabaseUtils.HasLabel(prefab, NetworkProjectConfigImporter.FusionPrefabTag)) { + SetError($"Prefab {prefab} is not tagged as a Fusion prefab. Try reimporting."); + } + } + } + } + } +} + +#endregion + + +#region Assets/Photon/Fusion/Editor/CustomTypes/NetworkPrefabRefDrawer.cs + +namespace Fusion.Editor { + using System; + using System.Collections.Generic; + using System.Reflection; + using UnityEditor; + using UnityEngine; + + [CustomPropertyDrawer(typeof(NetworkPrefabRef))] + [FusionPropertyDrawerMeta(HasFoldout = false)] + class NetworkPrefabRefDrawer : PropertyDrawerWithErrorHandling { + + protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) { + + var prefabRef = NetworkObjectGuidDrawer.GetValue(property); + + using (new FusionEditorGUI.PropertyScopeWithPrefixLabel(position, label, property, out position)) { + NetworkObject prefab = null; + if (prefabRef.IsValid && !NetworkProjectConfigUtilities.TryGetPrefabEditorInstance(prefabRef, out prefab)) { + SetError($"Prefab with guid {prefabRef} not found."); + } + + EditorGUI.BeginChangeCheck(); + + prefab = DrawNetworkPrefabPicker(position, GUIContent.none, prefab); + + if (EditorGUI.EndChangeCheck()) { + if (prefab) { + prefabRef = NetworkObjectEditor.GetPrefabGuid(prefab); + } else { + prefabRef = default; + } + NetworkObjectGuidDrawer.SetValue(property, prefabRef); + property.serializedObject.ApplyModifiedProperties(); + } + + SetInfo($"{prefabRef}"); + + + if (prefab) { + var expectedPrefabRef = NetworkObjectEditor.GetPrefabGuid(prefab); + if (!prefabRef.Equals(expectedPrefabRef)) { + SetError($"Resolved {prefab} has a different guid ({expectedPrefabRef}) than expected ({prefabRef}). " + + $"This can happen if prefabs are incorrectly resolved, e.g. when there are multiple resources of the same name."); + } else if (!expectedPrefabRef.IsValid) { + SetError($"Prefab {prefab} needs to be reimported."); + } else if (!AssetDatabaseUtils.HasLabel(prefab, NetworkProjectConfigImporter.FusionPrefabTag)) { + SetError($"Prefab {prefab} is not tagged as a Fusion prefab. Try reimporting."); + } else { + // ClearError(); + } + } + } + } + + public static NetworkObject DrawNetworkPrefabPicker(Rect position, GUIContent label, NetworkObject prefab) { + var prefabGo = (GameObject)EditorGUI.ObjectField(position, label, prefab ? prefab.gameObject : null, typeof(GameObject), false); + + // ensure the results are filtered + if (UnityInternal.ObjectSelector.isVisible) { + var selector = UnityInternal.ObjectSelector.get; + if (UnityInternal.EditorGUIUtility.LastControlID == selector.objectSelectorID) { + var filter = selector.searchFilter; + if (!filter.Contains(NetworkProjectConfigImporter.FusionPrefabTagSearchTerm)) { + if (string.IsNullOrEmpty(filter)) { + filter = NetworkProjectConfigImporter.FusionPrefabTagSearchTerm; + } else { + filter = NetworkProjectConfigImporter.FusionPrefabTagSearchTerm + " " + filter; + } + + selector.searchFilter = filter; + } + } + } + + if (prefabGo) { + return prefabGo.GetComponent(); + } else { + return null; + } + } + } +} + +#endregion + + +#region Assets/Photon/Fusion/Editor/CustomTypes/NetworkStringDrawer.cs + +namespace Fusion.Editor { + using System; + using System.Collections.Generic; + using System.Reflection; + using System.Text; + using UnityEditor; + using UnityEngine; + + [CustomPropertyDrawer(typeof(NetworkString<>))] + [FusionPropertyDrawerMeta(HasFoldout = false)] + class NetworkStringDrawer : PropertyDrawerWithErrorHandling { + + private string _str = ""; + private Action _write; + private Action _read; + private int _expectedLength; + + public NetworkStringDrawer() { + _write = (buffer, count) => { + unsafe { + fixed (int* p = buffer) { + _str = new string((sbyte*)p, 0, Mathf.Clamp(_expectedLength, 0, count) * 4, Encoding.UTF32); + } + } + }; + + _read = (buffer, count) => { + unsafe { + fixed (int* p = buffer) { + var charCount = UTF32Tools.Convert(_str, (uint*)p, count).CharacterCount; + if (charCount < _str.Length) { + _str = _str.Substring(0, charCount); + } + } + } + }; + } + + protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) { + + var length = property.FindPropertyRelativeOrThrow(nameof(NetworkString<_2>._length)); + var data = property.FindPropertyRelativeOrThrow($"{nameof(NetworkString<_2>._data)}.Data"); + + _expectedLength = length.intValue; + data.UpdateFixedBuffer(_read, _write, false); + + EditorGUI.BeginChangeCheck(); + + using (new FusionEditorGUI.ShowMixedValueScope(data.hasMultipleDifferentValues)) { + _str = EditorGUI.TextField(position, label, _str); + } + + if (EditorGUI.EndChangeCheck()) { + _expectedLength = _str.Length; + if (data.UpdateFixedBuffer(_read, _write, true, data.hasMultipleDifferentValues)) { + length.intValue = Encoding.UTF32.GetByteCount(_str) / 4; + data.serializedObject.ApplyModifiedProperties(); + } + } + } + } +} + +#endregion + + +#region Assets/Photon/Fusion/Editor/CustomTypes/NormalizedRectAttributeDrawer.cs + + +namespace Fusion.Editor { + using System; + using UnityEditor; + using UnityEngine; + +#if UNITY_EDITOR + [CustomPropertyDrawer(typeof(NormalizedRectAttribute))] + public class NormalizedRectAttributeDrawer : PropertyDrawer { + + bool isDragNewRect; + bool isDragXMin, isDragXMax, isDragYMin, isDragYMax, isDragAll; + MouseCursor lockCursorStyle; + + Vector2 mouseDownStart; + static GUIStyle _compactLabelStyle; + static GUIStyle _compactValueStyle; + + const float EXPANDED_HEIGHT = 140; + const float COLLAPSE_HEIGHT = 48; + + public override float GetPropertyHeight(SerializedProperty property, GUIContent label) { + if (property.propertyType == SerializedPropertyType.Rect) { + return property.isExpanded ? EXPANDED_HEIGHT : COLLAPSE_HEIGHT; + } else { + return base.GetPropertyHeight(property, label); + } + } + + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { + + EditorGUI.BeginProperty(position, label, property); + + bool hasChanged = false; + + EditorGUI.LabelField(new Rect(position) { height = 17 }, label); + + var value = property.rectValue; + + if (property.propertyType == SerializedPropertyType.Rect) { + + var dragarea = new Rect(position) { + yMin = position.yMin + 16 + 3, + yMax = position.yMax - 2, + //xMin = position.xMin + 16, + //xMax = position.xMax - 4 + }; + + // lower foldout box + GUI.Box(dragarea, GUIContent.none, EditorStyles.helpBox); + + property.isExpanded = GUI.Toggle(new Rect(position) { xMin = dragarea.xMin + 2, yMin = dragarea.yMin + 2, width = 12, height = 16 }, property.isExpanded, GUIContent.none, EditorStyles.foldout); + bool isExpanded = property.isExpanded; + + float border = isExpanded ? 4 : 2; + dragarea.xMin += 18; + dragarea.yMin += border; + dragarea.xMax -= border; + dragarea.yMax -= border; + + // Reshape the inner box to the correct aspect ratio + if (isExpanded) { + var ratio = (attribute as NormalizedRectAttribute).AspectRatio; + if (ratio == 0) { + var currentRes = UnityEditor.Handles.GetMainGameViewSize(); + ratio = currentRes.x / currentRes.y; + } + + // Don't go any wider than the inspector box. + var width = (dragarea.height * ratio); + if (width < dragarea.width) { + var x = (dragarea.width - width) / 2; + dragarea.x = dragarea.xMin + (int)x; + dragarea.width = (int)(width); + } + } + + + // Simulated desktop rect + GUI.Box(dragarea, GUIContent.none, EditorStyles.helpBox); + + var invertY = (attribute as NormalizedRectAttribute).InvertY; + + Event e = Event.current; + + const int HANDLE_SIZE = 8; + + var normmin = new Vector2(value.xMin, invertY ? 1f - value.yMin : value.yMin); + var normmax = new Vector2(value.xMax, invertY ? 1f - value.yMax : value.yMax); + var minreal = Rect.NormalizedToPoint(dragarea, normmin); + var maxreal = Rect.NormalizedToPoint(dragarea, normmax); + var lowerleftrect = new Rect(minreal.x , minreal.y - (invertY ? HANDLE_SIZE : 0), HANDLE_SIZE, HANDLE_SIZE); + var upperrghtrect = new Rect(maxreal.x - HANDLE_SIZE, maxreal.y - (invertY ? 0 : HANDLE_SIZE), HANDLE_SIZE, HANDLE_SIZE); + var upperleftrect = new Rect(minreal.x , maxreal.y - (invertY ? 0 : HANDLE_SIZE), HANDLE_SIZE, HANDLE_SIZE); + var lowerrghtrect = new Rect(maxreal.x - HANDLE_SIZE, minreal.y - (invertY ? HANDLE_SIZE : 0), HANDLE_SIZE, HANDLE_SIZE); + + var currentrect = Rect.MinMaxRect(minreal.x, invertY ? maxreal.y : minreal.y, maxreal.x, invertY ? minreal.y : maxreal.y); + + if (lockCursorStyle == MouseCursor.Arrow) { + if (isExpanded) { + EditorGUIUtility.AddCursorRect(lowerleftrect, MouseCursor.Link); + EditorGUIUtility.AddCursorRect(upperrghtrect, MouseCursor.Link); + EditorGUIUtility.AddCursorRect(upperleftrect, MouseCursor.Link); + EditorGUIUtility.AddCursorRect(lowerrghtrect, MouseCursor.Link); + } + EditorGUIUtility.AddCursorRect(currentrect, MouseCursor.MoveArrow); + } else { + // Lock cursor to a style while dragging, otherwise the slow inspector update causes rapid mouse icon changes. + EditorGUIUtility.AddCursorRect(dragarea, lockCursorStyle); + } + + EditorGUI.DrawRect(lowerleftrect, Color.yellow); + EditorGUI.DrawRect(upperrghtrect, Color.yellow); + EditorGUI.DrawRect(upperleftrect, Color.yellow); + EditorGUI.DrawRect(lowerrghtrect, Color.yellow); + + var mousepos = e.mousePosition; + if (e.button == 0) { + if (e.type == EventType.MouseUp) { + isDragXMin = false; + isDragYMin = false; + isDragXMax = false; + isDragYMax = false; + isDragAll = false; + lockCursorStyle = MouseCursor.Arrow; + isDragNewRect = false; + + hasChanged = true; + } + + if (e.type == EventType.MouseDown ) { + if (isExpanded && lowerleftrect.Contains(mousepos)) { + isDragXMin = true; + isDragYMin = true; + lockCursorStyle = MouseCursor.Link; + } else if (isExpanded && upperrghtrect.Contains(mousepos)) { + isDragXMax = true; + isDragYMax = true; + lockCursorStyle = MouseCursor.Link; + } else if (isExpanded && upperleftrect.Contains(mousepos)) { + isDragXMin = true; + isDragYMax = true; + lockCursorStyle = MouseCursor.Link; + } else if (isExpanded && lowerrghtrect.Contains(mousepos)) { + isDragXMax = true; + isDragYMin = true; + lockCursorStyle = MouseCursor.Link; + } else if (currentrect.Contains(mousepos)) { + isDragAll = true; + // mouse start is stored as a normalized offset from the Min values. + mouseDownStart = Rect.PointToNormalized(dragarea, mousepos) - normmin; + lockCursorStyle = MouseCursor.MoveArrow; + } else if (isExpanded && dragarea.Contains(mousepos)) { + mouseDownStart = mousepos; + isDragNewRect = true; + } + } + } + + if (e.type == EventType.MouseDrag) { + + Rect rect; + if (isDragNewRect) { + var start = Rect.PointToNormalized(dragarea, mouseDownStart); + var end = Rect.PointToNormalized(dragarea, e.mousePosition); + + if (invertY) { + rect = Rect.MinMaxRect( + Math.Max(0f, Math.Min(start.x, end.x)), + Math.Max(0f, 1f - Math.Max(start.y, end.y)), + Math.Min(1f, Math.Max(start.x, end.x)), + Math.Min(1f, 1f - Math.Min(start.y, end.y)) + ); + } else { + rect = Rect.MinMaxRect( + Math.Max(0f, Math.Min(start.x, end.x)), + Math.Max(0f, Math.Min(start.y, end.y)), + Math.Min(1f, Math.Max(start.x, end.x)), + Math.Min(1f, Math.Max(start.y, end.y)) + ); + } + property.rectValue = rect; + hasChanged = true; + + + } else if (isDragAll){ + var normmouse = Rect.PointToNormalized(dragarea, e.mousePosition); + rect = new Rect(value) { + x = Math.Max(normmouse.x - mouseDownStart.x, 0), + y = Math.Max(invertY ? (1 - normmouse.y + mouseDownStart.y) : (normmouse.y - mouseDownStart.y), 0) + }; + + if (rect.xMax > 1) { + rect = new Rect(rect) { x = rect.x + (1f - rect.xMax)}; + } + if (rect.yMax > 1) { + rect = new Rect(rect) { y = rect.y + (1f - rect.yMax) }; + } + + property.rectValue = rect; + hasChanged = true; + + } else if (isDragXMin || isDragXMax || isDragYMin || isDragYMax) { + + const float VERT_HANDLE_MIN_DIST = .2f; + const float HORZ_HANDLE_MIN_DIST = .05f; + var normmouse = Rect.PointToNormalized(dragarea, e.mousePosition); + if (invertY) { + rect = Rect.MinMaxRect( + isDragXMin ? Math.Min( normmouse.x, value.xMax - HORZ_HANDLE_MIN_DIST) : value.xMin, + isDragYMin ? Math.Min(1f - normmouse.y, value.yMax - VERT_HANDLE_MIN_DIST) : value.yMin, + isDragXMax ? Math.Max( normmouse.x, value.xMin + HORZ_HANDLE_MIN_DIST) : value.xMax, + isDragYMax ? Math.Max(1f - normmouse.y, value.yMin + VERT_HANDLE_MIN_DIST) : value.yMax + ); + } else { + rect = Rect.MinMaxRect( + isDragXMin ? Math.Min(normmouse.x, value.xMax - HORZ_HANDLE_MIN_DIST) : value.xMin, + isDragYMin ? Math.Min(normmouse.y, value.yMax - VERT_HANDLE_MIN_DIST) : value.yMin, + isDragXMax ? Math.Max(normmouse.x, value.xMin + HORZ_HANDLE_MIN_DIST) : value.xMax, + isDragYMax ? Math.Max(normmouse.y, value.yMin + VERT_HANDLE_MIN_DIST) : value.yMax + ); + } + + property.rectValue = rect; + hasChanged = true; + } + } + + const float SPACING = 4f; + const int LABELS_WIDTH = 16; + const float COMPACT_THRESHOLD = 340f; + + bool useCompact = position.width < COMPACT_THRESHOLD; + + var labelwidth = EditorGUIUtility.labelWidth; + var fieldwidth = (position.width - labelwidth- 3 * SPACING) * 0.25f ; + var fieldbase = new Rect(position) { xMin = position.xMin + labelwidth, height = 16, width = fieldwidth - (useCompact ? 0 : LABELS_WIDTH) }; + + if (_compactValueStyle == null) { + _compactLabelStyle = new GUIStyle(EditorStyles.miniLabel) { fontSize = 9, alignment = TextAnchor.MiddleLeft, padding = new RectOffset(2, 0, 1, 0) }; + _compactValueStyle = new GUIStyle(EditorStyles.miniTextField) { fontSize = 9, alignment = TextAnchor.MiddleLeft, padding = new RectOffset(2, 0, 1, 0) }; + } + GUIStyle valueStyle = _compactValueStyle; + + //if (useCompact) { + // if (_compactStyle == null) { + // _compactStyle = new GUIStyle(EditorStyles.miniTextField) { fontSize = 9, alignment = TextAnchor.MiddleLeft, padding = new RectOffset(2, 0, 1, 0) }; + // } + // valueStyle = _compactStyle; + //} else { + // valueStyle = EditorStyles.textField; + //} + + // Only draw labels when not in compact + if (!useCompact) { + Rect l1 = new Rect(fieldbase) { x = fieldbase.xMin }; + Rect l2 = new Rect(fieldbase) { x = fieldbase.xMin + 1 * (fieldwidth + SPACING) }; + Rect l3 = new Rect(fieldbase) { x = fieldbase.xMin + 2 * (fieldwidth + SPACING) }; + Rect l4 = new Rect(fieldbase) { x = fieldbase.xMin + 3 * (fieldwidth + SPACING) }; + GUI.Label(l1, "L:", _compactLabelStyle); + GUI.Label(l2, "R:", _compactLabelStyle); + GUI.Label(l3, "T:", _compactLabelStyle); + GUI.Label(l4, "B:", _compactLabelStyle); + } + + // Draw value fields + Rect f1 = new Rect(fieldbase) { x = fieldbase.xMin + 0 * fieldwidth + (useCompact ? 0 : LABELS_WIDTH) }; + Rect f2 = new Rect(fieldbase) { x = fieldbase.xMin + 1 * fieldwidth + (useCompact ? 0 : LABELS_WIDTH) + 1 * SPACING }; + Rect f3 = new Rect(fieldbase) { x = fieldbase.xMin + 2 * fieldwidth + (useCompact ? 0 : LABELS_WIDTH) + 2 * SPACING }; + Rect f4 = new Rect(fieldbase) { x = fieldbase.xMin + 3 * fieldwidth + (useCompact ? 0 : LABELS_WIDTH) + 3 * SPACING }; + + using (var check = new EditorGUI.ChangeCheckScope()) { + float newxmin, newxmax, newymin, newymax; + if (invertY) { + newxmin = EditorGUI.DelayedFloatField(f1, (float)Math.Round(value.xMin, useCompact ? 2 : 3), valueStyle); + newxmax = EditorGUI.DelayedFloatField(f2, (float)Math.Round(value.xMax, useCompact ? 2 : 3), valueStyle); + newymax = EditorGUI.DelayedFloatField(f3, (float)Math.Round(value.yMax, useCompact ? 2 : 3), valueStyle); + newymin = EditorGUI.DelayedFloatField(f4, (float)Math.Round(value.yMin, useCompact ? 2 : 3), valueStyle); + } else { + newxmin = EditorGUI.DelayedFloatField(f1, (float)Math.Round(value.xMin, useCompact ? 2 : 3), valueStyle); + newxmax = EditorGUI.DelayedFloatField(f2, (float)Math.Round(value.xMax, useCompact ? 2 : 3), valueStyle); + newymin = EditorGUI.DelayedFloatField(f3, (float)Math.Round(value.yMin, useCompact ? 2 : 3), valueStyle); + newymax = EditorGUI.DelayedFloatField(f4, (float)Math.Round(value.yMax, useCompact ? 2 : 3), valueStyle); + } + + if (check.changed) { + if (newxmin != value.xMin) value.xMin = Math.Min(newxmin, value.xMax - .05f); + if (newxmax != value.xMax) value.xMax = Math.Max(newxmax, value.xMin + .05f); + if (newymax != value.yMax) value.yMax = Math.Max(newymax, value.yMin + .05f); + if (newymin != value.yMin) value.yMin = Math.Min(newymin, value.yMax - .05f); + property.rectValue = value; + property.serializedObject.ApplyModifiedProperties(); + } + } + + var nmins = new Vector2(value.xMin, invertY ? 1f - value.yMin : value.yMin); + var nmaxs = new Vector2(value.xMax, invertY ? 1f - value.yMax : value.yMax); + var mins = Rect.NormalizedToPoint(dragarea, nmins); + var maxs = Rect.NormalizedToPoint(dragarea, nmaxs); + var area = Rect.MinMaxRect(minreal.x, invertY ? maxreal.y : minreal.y, maxreal.x, invertY ? minreal.y : maxreal.y); + + EditorGUI.DrawRect(area, new Color(1f, 1f, 1f, .1f)); + //GUI.DrawTexture(area, GUIContent.none, EditorStyles.helpBox); + //GUI.Box(area, GUIContent.none, EditorStyles.helpBox); + + } else { + Debug.LogWarning($"{nameof(NormalizedRectAttribute)} only valid on UnityEngine.Rect fields. Will use default rendering for '{property.type} {property.name}' in class '{fieldInfo.DeclaringType}'."); + EditorGUI.PropertyField(position, property, label); + } + + if (hasChanged) { + GUI.changed = true; + property.serializedObject.ApplyModifiedProperties(); + } + + EditorGUI.EndProperty(); + } + } +#endif + +} + + +#endregion + + +#region Assets/Photon/Fusion/Editor/CustomTypes/SceneRefDrawer.cs + +namespace Fusion.Editor { + using System; + using System.Collections.Generic; + using System.Reflection; + using UnityEditor; + using UnityEngine; + + [CustomPropertyDrawer(typeof(SceneRef))] + public class SceneRefDrawer : PropertyDrawer { + + public const int CheckboxWidth = 16; + + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { + + using (new FusionEditorGUI.PropertyScopeWithPrefixLabel(position, label, property, out position)) { + var valueProperty = property.FindPropertyRelativeOrThrow(nameof(SceneRef.RawValue)); + long rawValue = valueProperty.longValue; + + var togglePos = position; + togglePos.width = CheckboxWidth; + bool hasValue = rawValue > 0; + + EditorGUI.BeginChangeCheck(); + + if (EditorGUI.Toggle(togglePos, hasValue) != hasValue) { + rawValue = valueProperty.longValue = hasValue ? 0 : 1; + valueProperty.serializedObject.ApplyModifiedProperties(); + } + + if (rawValue > 0) { + position.xMin += togglePos.width; + + rawValue = EditorGUI.LongField(position, rawValue - 1); + rawValue = Math.Max(0, rawValue) + 1; + + if (EditorGUI.EndChangeCheck()) { + valueProperty.longValue = rawValue; + valueProperty.serializedObject.ApplyModifiedProperties(); + } + } + } + } + } +} + +#endregion + + +#region Assets/Photon/Fusion/Editor/CustomTypes/SerializableDictionaryDrawer.cs + +namespace Fusion.Editor { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + using System.Text; + using System.Threading.Tasks; + using UnityEditor; + using UnityEditorInternal; + using UnityEngine; + + [CustomPropertyDrawer(typeof(SerializableDictionary), true)] + class SerializableDictionaryDrawer : PropertyDrawerWithErrorHandling { + const string ItemsPropertyPath = SerializableDictionary.ItemsPropertyPath; + const string EntryKeyPropertyPath = SerializableDictionary.EntryKeyPropertyPath; + + protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) { + var entries = property.FindPropertyRelativeOrThrow(ItemsPropertyPath); + entries.isExpanded = property.isExpanded; + using (new FusionEditorGUI.PropertyScope(position, label, property)) { + EditorGUI.PropertyField(position, entries, label, true); + property.isExpanded = entries.isExpanded; + + string error = VerifyDictionary(entries, EntryKeyPropertyPath); + if (error != null) { + SetError(error); + } else { + ClearError(); + } + } + } + public override float GetPropertyHeight(SerializedProperty property, GUIContent label) { + var entries = property.FindPropertyRelativeOrThrow(ItemsPropertyPath); + return EditorGUI.GetPropertyHeight(entries, label, true); + } + + private static HashSet _dictionaryKeyHash = new HashSet(new SerializedPropertyUtilities.SerializedPropertyEqualityComparer()); + + private static string VerifyDictionary(SerializedProperty prop, string keyPropertyName) { + Debug.Assert(prop.isArray); + try { + for (int i = 0; i < prop.arraySize; ++i) { + var keyProperty = prop.GetArrayElementAtIndex(i).FindPropertyRelativeOrThrow(keyPropertyName); + if (!_dictionaryKeyHash.Add(keyProperty)) { + + var groups = Enumerable.Range(0, prop.arraySize) + .GroupBy(x => prop.GetArrayElementAtIndex(x).FindPropertyRelative(keyPropertyName), x => x, _dictionaryKeyHash.Comparer) + .Where(x => x.Count() > 1) + .ToList(); + + // there are duplicates - take the slow and allocating path now + return string.Join("\n", groups.Select(x => $"Duplicate keys for elements: {string.Join(", ", x)}")); + } + } + + return null; + + } finally { + _dictionaryKeyHash.Clear(); + } + } + } +} + + +#endregion + + +#region Assets/Photon/Fusion/Editor/CustomTypes/TickRateDrawer.cs + +namespace Fusion.Editor { + using System; + using System.Reflection; + using UnityEditor; + using UnityEngine; + + [CustomPropertyDrawer(typeof(TickRate.Selection))] + [FusionPropertyDrawerMeta(HasFoldout = false)] + public class TickRateDrawer : PropertyDrawer { + private const int PAD = 0; + + // Cached pop items for client rate + private static GUIContent[] _clientRateOptions; + private static GUIContent[] ClientRateOptions { + get { + if (_clientRateOptions != null) { + return _clientRateOptions; + } + ExtractClientRates(); + return _clientRateOptions; + } + } + + // Cached pop items for client rate + private static int[] _clientRateValues; + private static int[] ClientRateValues { + get { + if (_clientRateValues != null) { + return _clientRateValues; + } + ExtractClientRates(); + return _clientRateValues; + } + } + // + // private static GUIContent[] _ratioOptions = new GUIContent[4]; + // private static int[] _ratioValues = new int[] { 0, 1, 2, 3 }; + + private static readonly GUIContent[][] _reusableRatioGUIArrays = new GUIContent[4][] { new GUIContent[1], new GUIContent[2], new GUIContent[3], new GUIContent[4] }; + private static readonly int[][] _reusableRatioIntArrays = new int[4][] { new int[1], new int[2], new int[3], new int[4] }; + + private static readonly GUIContent[][] _reusableServerGUIArrays = new GUIContent[4][] { new GUIContent[1], new GUIContent[2], new GUIContent[3], new GUIContent[4] }; + private static readonly int[][] _reusableServerIntArrays = new int[4][] { new int[1], new int[2], new int[3], new int[4] }; + + private static readonly LazyGUIStyle _buttonStyle = LazyGUIStyle.Create(_ => new GUIStyle(EditorStyles.miniButton) { + fontSize = 9, + alignment = TextAnchor.MiddleCenter + }); + + public override float GetPropertyHeight(SerializedProperty property, GUIContent label) { + return (base.GetPropertyHeight(property, label) + EditorGUIUtility.standardVerticalSpacing) * 4 + PAD * 2; + } + + private int DrawPopup(ref Rect labelRect, ref Rect fieldRect, float rowHeight, GUIContent guiContent, int[] sliderValues, int[] values, GUIContent[] options, int currentValue, int offset = 0) { + + EditorGUI.LabelField(labelRect, guiContent); + int indentHold = EditorGUI.indentLevel; + EditorGUI.indentLevel = 0; + int value; + Rect dropRect = fieldRect; + + if (fieldRect.width > 120) { + var slideRect = new Rect(fieldRect) { xMax = fieldRect.xMax - 64 }; + dropRect = new Rect(fieldRect) { xMin = fieldRect.xMax - 64 }; + + var sliderRange = Math.Max(3, sliderValues.Length - 1); + if (sliderRange == 3) { + var dividerRect = new Rect(slideRect); + // dividerRect.yMin += 2; + // dividerRect.yMax -= 2; + var quarter = slideRect.width * 1f /4; + + using (new EditorGUI.DisabledScope(!(options.Length + offset >= 4))) { + if (GUI.Toggle(new Rect(dividerRect) { width = quarter }, currentValue == 3, new GUIContent("1/8"), _buttonStyle )) { + currentValue = 3; + } + } + using (new EditorGUI.DisabledScope(!(options.Length + offset >= 3 && offset <= 2))) { + if (GUI.Toggle(new Rect(dividerRect) { width = quarter, x = dividerRect.x + quarter }, currentValue == 2, new GUIContent("1/4"), _buttonStyle)) { + currentValue = 2; + } + } + using (new EditorGUI.DisabledScope(!(options.Length + offset >= 2 && offset <= 1))) { + if (GUI.Toggle(new Rect(dividerRect) { width = quarter, x = dividerRect.x + quarter * 2 }, currentValue == 1, new GUIContent("1/2"), _buttonStyle)) { + currentValue = 1; + } + } + using (new EditorGUI.DisabledScope(!(options.Length + offset >= 1 && offset == 0))) { + if (GUI.Toggle(new Rect(dividerRect) { width = quarter, x = dividerRect.x + quarter * 3 }, currentValue == 0, new GUIContent("1:1"), _buttonStyle)) { + currentValue = 0; + } + } + EditorGUI.LabelField(dropRect, options[currentValue - offset], new GUIStyle(EditorStyles.label){padding = new RectOffset(4, 0, 0, 0)}); + value = values[currentValue - offset]; + + } else { + currentValue = (int)GUI.HorizontalSlider(slideRect, (float)currentValue, sliderRange, 0); + + // Clamp slider ranges into valid enum ranges + if (currentValue - offset < 0) { + currentValue = offset; + } + else if (currentValue - offset >= values.Length) { + currentValue = values.Length - 1 + offset; + } + value = values[EditorGUI.Popup(dropRect, GUIContent.none, currentValue - offset, options)]; + // value = values[EditorGUI.Popup(fieldRect, GUIContent.none, currentValue - offset, options)]; + + } + + } else { + // Handling for very narrow window. Falls back to just a basic popup for each value. + dropRect = fieldRect; + var index = EditorGUI.Popup(dropRect, GUIContent.none, currentValue - offset, options); + index = Math.Clamp(index, 0, values.Length-1); + value = values[index]; + } + + EditorGUI.indentLevel = indentHold; + labelRect.y += rowHeight + EditorGUIUtility.standardVerticalSpacing; + fieldRect.y += rowHeight + EditorGUIUtility.standardVerticalSpacing; + + return value; + } + + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { + + using (new FusionEditorGUI.PropertyScope(position, label, property)) { + + var rowHeight = base.GetPropertyHeight(property, label); + + // using (new FusionEditorGUI.PropertyScopeWithPrefixLabel(position, label, property, out position)) { + // EditorGUI.LabelField(new Rect(position){ yMin = position.yMin + rowHeight}, GUIContent.none, FusionGUIStyles.GroupBoxType.Gray.GetStyle()); + + position = new Rect(position) { + xMin = position.xMin + PAD, + xMax = position.xMax - PAD, + yMin = position.yMin + PAD, + yMax = position.yMax - PAD + }; + + var clientRateProperty = property.FindPropertyRelativeOrThrow(nameof(TickRate.Selection.Client)); + var serverRateProperty = property.FindPropertyRelativeOrThrow(nameof(TickRate.Selection.ServerIndex)); + var clientSendProperty = property.FindPropertyRelativeOrThrow(nameof(TickRate.Selection.ClientSendIndex)); + var serverSendProperty = property.FindPropertyRelativeOrThrow(nameof(TickRate.Selection.ServerSendIndex)); + + var selection = GetSelectionValue(property); + + var hold = selection; + var tickRate = TickRate.Get(TickRate.IsValid(selection.Client) ? selection.Client : TickRate.Default.Client); + var clientRateIndex = GetIndexForClientRate(tickRate.Client); + + var rect = new Rect(position) { height = base.GetPropertyHeight(property, label) }; + + //var fieldWidth = Math.Max(Math.Min(position.width * .33f, MAX_FIELD_WIDTH), MIN_FIELD_WIDTH); + var labelWidth = EditorGUIUtility.labelWidth; + + + var labelRect = new Rect(rect) { width = labelWidth}; // { xMax = rect.xMax - fieldWidth }}; + //var fieldRect = new Rect(rect) { xMin = rect.xMax -fieldWidth }; + var fieldRect = new Rect(rect) { xMin = rect.xMin + labelWidth}; + + // CLIENT SIM RATE + + selection.Client = DrawPopup(ref labelRect, ref fieldRect, rowHeight, new GUIContent("Client Tick Rate"), _clientRateValues,_clientRateValues, ClientRateOptions, clientRateIndex); + + // TODO: This validates every tick without checking for changes. May be good, may not. + selection = tickRate.ClampSelection(selection); + + // CLIENT SEND RATE + var ratioOptions = _reusableRatioGUIArrays[tickRate.Count - 1]; // _ratioOptions; + var ratioValues = _reusableRatioIntArrays[tickRate.Count - 1]; //_ratioValues; + for (var i = 0; i < tickRate.Count; ++i) { + ratioOptions[i] = new GUIContent(tickRate.GetTickRate(i).ToString()); + ratioValues[i] = i; + } + + selection.ClientSendIndex = DrawPopup(ref labelRect, ref fieldRect, rowHeight, new GUIContent("Client Send Rate"), ratioValues, ratioValues, ratioOptions, selection.ClientSendIndex); + + // SERVER SIM RATE - Force it to be 1:1 with the client tick rate - since different tick rates are not supported. + var srOptions = _reusableServerGUIArrays[0]; + var srValues = _reusableServerIntArrays[0]; + + srOptions[0] = ratioOptions[0]; + srValues[0] = 0; + + selection.ServerIndex = DrawPopup(ref labelRect, ref fieldRect, rowHeight, new GUIContent("Server Tick Rate"), ratioValues, srValues, srOptions, selection.ServerIndex); + + selection = tickRate.ClampSelection(selection); + + // SERVER SEND RATE - uses a subset of ratio - since it CANNOT be higher than Server Rate. + var sOffset = selection.ServerIndex; + var sLen = ratioOptions.Length - sOffset; + var sSendOptions = _reusableServerGUIArrays[sLen - 1]; // new GUIContent[sLen]; + var sSendValues = _reusableServerIntArrays[sLen - 1]; // new int[sLen]; + + for (var i = 0; i < sLen; ++i) { + sSendOptions[i] = ratioOptions[i + sOffset]; + sSendValues[i] = ratioValues[i + sOffset]; + } + + selection.ServerSendIndex = DrawPopup(ref labelRect, ref fieldRect, rowHeight, new GUIContent("Server Send Rate"), ratioValues, sSendValues, sSendOptions, selection.ServerSendIndex, sOffset); + + if (hold.Equals(selection) == false) { + selection = tickRate.ClampSelection(selection); + + // FIELD INFO SET VALUE ALTERNATIVE + // fieldInfo.SetValue(targetObject, selection); + + clientRateProperty.intValue = selection.Client; + clientSendProperty.intValue = selection.ClientSendIndex; + serverRateProperty.intValue = selection.ServerIndex; + serverSendProperty.intValue = selection.ServerSendIndex; + property.serializedObject.ApplyModifiedProperties(); + } + } + } + + private int GetIndexForClientRate(int clientRate) { + for (var i = ClientRateValues.Length - 1; i >= 0; --i) + if (_clientRateValues[i] == clientRate) { + return i; + } + return -1; + } + + + // Extract in reverse order so all the popups are consistent. + private static void ExtractClientRates() { + int cnt = TickRate.Available.Count; + + _clientRateOptions = new GUIContent[cnt]; + _clientRateValues = new int[cnt]; + for (int i = 0, reverse = cnt -1; i < cnt; ++i, --reverse) { + _clientRateOptions[i] = new GUIContent(TickRate.Available[reverse].Client.ToString()); + _clientRateValues[i] = TickRate.Available[reverse].Client; + } + } + + // Wacky reflection to locate the value + private static TickRate.Selection GetSelectionValue (SerializedProperty property) { + object obj = property.serializedObject.targetObject; + string path = property.propertyPath; + string[] parts = path.Split ('.'); + foreach (var t in parts) { + obj = GetValueFromFieldName (t, obj); + } + return (TickRate.Selection)obj; + } + + private static object GetValueFromFieldName(string name, object obj, BindingFlags bindings = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) { + FieldInfo field = obj.GetType().GetField(name, bindings); + if (field != null) { + return field.GetValue(obj); + } + + return TickRate.Default; + } + + } +} + +#endregion + + +#region Assets/Photon/Fusion/Editor/EditorRecompileHook.cs + +namespace Fusion.Editor { + using System; + using System.IO; + using UnityEditor; + using UnityEditor.Compilation; + using UnityEngine; + + [InitializeOnLoad] + public static class EditorRecompileHook { + static EditorRecompileHook() { + + EditorApplication.update += delegate { + if (PlayerSettings.allowUnsafeCode == false) { + PlayerSettings.allowUnsafeCode = true; + + // request re-compile + CompilationPipeline.RequestScriptCompilation(RequestScriptCompilationOptions.None); + } + }; + + AssemblyReloadEvents.beforeAssemblyReload += ShutdownRunners; + + CompilationPipeline.compilationStarted += _ => ShutdownRunners(); + CompilationPipeline.compilationStarted += _ => StoreConfigPath(); + } + + static void ShutdownRunners() { + var runners = NetworkRunner.GetInstancesEnumerator(); + + while (runners.MoveNext()) { + if (runners.Current) { + runners.Current.Shutdown(); + } + } + } + + static void StoreConfigPath() { + const string ConfigPathCachePath = "Temp/FusionILWeaverConfigPath.txt"; + + var configPath = NetworkProjectConfigUtilities.GetGlobalConfigPath(); + if (string.IsNullOrEmpty(configPath)) { + // delete + try { + File.Delete(ConfigPathCachePath); + } catch (FileNotFoundException) { + // ok + } catch (Exception ex) { + FusionEditorLog.ErrorConfig($"Error when clearing the config path file for the Weaver. Weaving results may be invalid: {ex}"); + } + } else { + try { + System.IO.File.WriteAllText(ConfigPathCachePath, configPath); + } catch (Exception ex) { + FusionEditorLog.ErrorConfig($"Error when writing the config path file for the Weaver. Weaving results may be invalid: {ex}"); + } + } + } + } +} + +#endregion + + +#region Assets/Photon/Fusion/Editor/FusionAssistants.cs + +namespace Fusion.Editor { + using UnityEngine; + using System; + + static class FusionAssistants { + public const int PRIORITY = 0; + public const int PRIORITY_LOW = 1000; + + /// + /// Ensure GameObject has component T. Will create as needed and return the found/created component. + /// + public static T EnsureComponentExists(this GameObject go) where T : Component { + if (go.TryGetComponent(out var t)) + return t; + + else + return go.AddComponent(); + } + + public static GameObject EnsureComponentsExistInScene(string preferredGameObjectName, params Type[] components) { + + GameObject go = null; + + foreach(var c in components) { + var found = UnityEngine.Object.FindFirstObjectByType(c); + if (found) + continue; + + if (go == null) + go = new GameObject(preferredGameObjectName); + + go.AddComponent(c); + } + + return go; + } + + public static T EnsureExistsInScene(string preferredGameObjectName = null, GameObject onThisObject = null, params Type[] otherRequiredComponents) where T : Component { + + if (preferredGameObjectName == null) + preferredGameObjectName = typeof(T).Name; + + T comp; + comp = UnityEngine.Object.FindFirstObjectByType(); + if (comp == null) { + // T was not found in scene, create a new gameobject and add T, as well as other required components + if (onThisObject == null) + onThisObject = new GameObject(preferredGameObjectName); + comp = onThisObject.AddComponent(); + foreach (var add in otherRequiredComponents) { + onThisObject.AddComponent(add); + } + } else { + // Make sure existing found T has the indicated extra components as well. + foreach (var add in otherRequiredComponents) { + if (comp.GetComponent(add) == false) + comp.gameObject.AddComponent(add); + } + } + return comp; + } + + /// + /// Create a scene object with all of the supplied arguments and parameters applied. + /// + public static GameObject CreatePrimitive( + PrimitiveType? primitive, + string name, + Vector3? position, + Quaternion? rotation, + Vector3? scale, + Transform parent, + Material material, + params Type[] addComponents) { + + GameObject go; + if (primitive.HasValue) { + go = GameObject.CreatePrimitive(primitive.Value); + + go.name = name; + + if (material != null) + go.GetComponent().material = material; + + foreach (var type in addComponents) { + go.AddComponent(type); + } + + } else { + go = new GameObject(name, addComponents); + } + + if (position.HasValue) + go.transform.position = position.Value; + + if (rotation.HasValue) + go.transform.rotation = rotation.Value; + + if (scale.HasValue) + go.transform.localScale = scale.Value; + + if (parent) + go.transform.parent = parent; + + return go; + } + + internal static EnableOnSingleRunner EnsureComponentHasVisibilityNode(this Component component) { + var allExistingNodes = component.GetComponents(); + foreach (var existingNodes in allExistingNodes) { + foreach (var comp in existingNodes.Components) { + if (comp == component) { + return existingNodes; + } + } + } + + // Component is not represented yet. If there is a VisNodes already, use it. Otherwise make one. + EnableOnSingleRunner targetNodes = component.GetComponent(); + if (targetNodes == null) { + targetNodes = component.gameObject.AddComponent(); + } + + // Add this component to the collection. + int newArrayPos = targetNodes.Components.Length; + Array.Resize(ref targetNodes.Components, newArrayPos + 1); + targetNodes.Components[newArrayPos] = component; + return targetNodes; + } + } +} + +#endregion + + +#region Assets/Photon/Fusion/Editor/FusionBackwardCompatibility.Common.cs + +// merged BackwardCompatibility + +#region HierarchyIteratorExtensions.cs + +namespace Fusion.Editor { + using UnityEditor; + using UnityEngine; + +#if !UNITY_6000_3_OR_NEWER + using HierarchyIterator = UnityEditor.HierarchyProperty; +#endif + + static class HierarchyIteratorExtensions { +#if UNITY_6000_3_OR_NEWER + public static EntityId GetObjectId(this HierarchyIterator iterator) { + return iterator.entityId; + } +#else + public static int GetObjectId(this HierarchyIterator iterator) { + return iterator.instanceID; + } +#endif + +#if UNITY_6000_2_OR_NEWER + public static GUID GetAssetGuid(this HierarchyIterator iterator) { + return iterator.assetGUID; + } +#else + public static GUID GetAssetGuid(this HierarchyIterator iterator) { + var guidStr = iterator.guid; + return string.IsNullOrEmpty(guidStr) ? default : new GUID(guidStr); + } +#endif + } +} + +#endregion + + +#region LazyLoadReferenceExtensions.cs + +namespace Fusion.Editor { + using UnityEditor; + using UnityEngine; + + static class LazyLoadReferenceExtensions { +#if UNITY_6000_3_OR_NEWER + public static EntityId GetObjectId(this LazyLoadReference obj) where T : Object { + return obj.entityId; + } +#else + public static int GetObjectId(this LazyLoadReference obj) where T : Object { + return obj.instanceID; + } +#endif + } +} + +#endregion + + +#region Object.cs + +namespace Fusion.Editor { + using UnityEditor; + using UnityEngine; + + static class ObjectExtensions { +#if UNITY_6000_3_OR_NEWER + public static EntityId GetObjectId(this UnityEngine.Object obj) { + return obj.GetEntityId(); + } +#else + public static int GetObjectId(this UnityEngine.Object obj) { + return obj.GetInstanceID(); + } +#endif + } +} + +#endregion + + + +#endregion + + +#region Assets/Photon/Fusion/Editor/FusionBootstrapEditor.cs + +namespace Fusion.Editor { + using System.Linq; + using UnityEditor; + using UnityEngine; + using UnityEngine.SceneManagement; + + [CustomEditor(typeof(FusionBootstrap))] + public class FusionBootstrapEditor : BehaviourEditor { + + public override void OnInspectorGUI() { + base.OnInspectorGUI(); + + if (Application.isPlaying) + return; + + var currentScene = SceneManager.GetActiveScene(); + if (!currentScene.IsAddedToBuildSettings()) { + using (new FusionEditorGUI.WarningScope("Current scene is not added to Build Settings list.")) { + if (GUILayout.Button("Add Scene To Build Settings")) { + if (currentScene.name == "") { + UnityEditor.SceneManagement.EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo(); + } + + if (currentScene.name != "") { + EditorBuildSettings.scenes = EditorBuildSettings.scenes + .Concat(new[] { new EditorBuildSettingsScene(currentScene.path, true) }) + .ToArray(); + } + } + } + } + } + } +} + +#endregion + + +#region Assets/Photon/Fusion/Editor/FusionBuildTriggers.cs + +namespace Fusion.Editor { + + using UnityEditor; + using UnityEditor.Build; + using UnityEditor.Build.Reporting; + + + public class FusionBuildTriggers : IPreprocessBuildWithReport { + + public const int CallbackOrder = 1000; + + public int callbackOrder => CallbackOrder; + + public void OnPreprocessBuild(BuildReport report) { + if (report.summary.platformGroup != BuildTargetGroup.Standalone) { + return; + } + + if (!PlayerSettings.runInBackground) { + FusionEditorLog.Warn($"Standalone builds should have {nameof(PlayerSettings)}.{nameof(PlayerSettings.runInBackground)} enabled. " + + $"Otherwise, loss of application focus may result in connection termination."); + } + } + } +} + + +#endregion + + +#region Assets/Photon/Fusion/Editor/FusionEditor.Common.cs + +// merged Editor + +#region INetworkAssetSourceFactory.cs + +namespace Fusion.Editor { + using UnityEditor; + +#if UNITY_6000_3_OR_NEWER + using ObjectIdType = UnityEngine.EntityId; + using HierarchyIteratorType = UnityEditor.HierarchyIterator; +#else + using ObjectIdType = System.Int32; + using HierarchyIteratorType = UnityEditor.HierarchyProperty; +#endif + + /// + /// A factory that creates asset source instances for a given asset. + /// + public partial interface INetworkAssetSourceFactory { + /// + /// The order in which this factory is executed. The lower the number, the earlier it is executed. + /// + int Order { get; } + } + + /// + /// A context object that is passed to instances to create an asset source instance. + /// + public readonly partial struct NetworkAssetSourceFactoryContext { + /// + /// Asset instance ID. + /// + public readonly ObjectIdType InstanceID; + /// + /// Asset Unity GUID; + /// + public readonly string AssetGuid; + /// + /// Asset name; + /// + public readonly string AssetName; + /// + /// Is this the main asset. + /// + public readonly bool IsMainAsset; + /// + /// Asset Unity path. + /// + public string AssetPath => AssetDatabaseUtils.GetAssetPathOrThrow(InstanceID); + + /// + /// The object pointed to be + /// + public UnityEngine.Object Object { + get => +#if UNITY_6000_3_OR_NEWER + EditorUtility.EntityIdToObject(InstanceID); +#else + EditorUtility.InstanceIDToObject(InstanceID); +#endif + } + + /// + /// Create a new instance of . + /// + public NetworkAssetSourceFactoryContext(string assetGuid, ObjectIdType instanceID, string assetName, bool isMainAsset) { + AssetGuid = assetGuid; + InstanceID = instanceID; + AssetName = assetName; + IsMainAsset = isMainAsset; + } + + /// + /// Create a new instance of . + /// + public NetworkAssetSourceFactoryContext(HierarchyIteratorType hierarchyProperty) { + AssetGuid = hierarchyProperty.guid; + InstanceID = hierarchyProperty.GetObjectId(); + AssetName = hierarchyProperty.name; + IsMainAsset = hierarchyProperty.isMainRepresentation; + } + + /// + /// Create a new instance of . + /// + public NetworkAssetSourceFactoryContext(UnityEngine.Object obj) { + if (!obj) { + throw new System.ArgumentNullException(nameof(obj)); + } + + (AssetGuid, _) = AssetDatabaseUtils.GetGUIDAndLocalFileIdentifierOrThrow(obj); + InstanceID = obj.GetInstanceID(); + AssetName = obj.name; + IsMainAsset = AssetDatabase.IsMainAsset(obj); + } + } +} + +#endregion + + +#region NetworkAssetSourceFactoryAddressable.cs + +#if (FUSION_ADDRESSABLES || FUSION_ENABLE_ADDRESSABLES) && !FUSION_DISABLE_ADDRESSABLES +namespace Fusion.Editor { + using UnityEditor.AddressableAssets; + + /// + /// A implementation that creates + /// if the asset is an Addressable. + /// + public partial class NetworkAssetSourceFactoryAddressable : INetworkAssetSourceFactory { + /// + public const int Order = 800; + + int INetworkAssetSourceFactory.Order => Order; + + /// + /// Creates a new instance. Checks if AddressableAssetSettings exists and logs a warning if it does not. + /// + public NetworkAssetSourceFactoryAddressable() { + if (!AddressableAssetSettingsDefaultObject.SettingsExists) { + FusionEditorLog.WarnImport($"AddressableAssetSettings does not exist, Fusion will not be able to use Addressables for asset sources."); + } + } + + /// + /// Creates if the asset is an Addressable. + /// + protected bool TryCreateInternal(in NetworkAssetSourceFactoryContext context, out TSource result) + where TSource : NetworkAssetSourceAddressable, new() + where TAsset : UnityEngine.Object { + + if (!AddressableAssetSettingsDefaultObject.SettingsExists) { + result = default; + return false; + } + + var assetsSettings = AddressableAssetSettingsDefaultObject.Settings; + if (assetsSettings == null) { + throw new System.InvalidOperationException("Unable to load Addressables settings. This may be due to an outdated Addressables version."); + } + + var addressableEntry = assetsSettings.FindAssetEntry(context.AssetGuid, true); + if (addressableEntry == null) { + result = default; + return false; + } + + result = new TSource() { + RuntimeKey = $"{addressableEntry.guid}{(context.IsMainAsset ? string.Empty : $"[{context.AssetName}]")}", + }; + return true; + } + } +} +#endif + +#endregion + + +#region NetworkAssetSourceFactoryResource.cs + +namespace Fusion.Editor { + /// + /// A implementation that creates + /// instances for assets in the Resources folder. + /// + public partial class NetworkAssetSourceFactoryResource : INetworkAssetSourceFactory { + /// + public const int Order = 1000; + + int INetworkAssetSourceFactory.Order => Order; + + /// + /// Creates if the asset is in the Resources folder. + /// + protected bool TryCreateInternal(in NetworkAssetSourceFactoryContext context, out TSource result) + where TSource : NetworkAssetSourceResource, new() + where TAsset : UnityEngine.Object { + if (!PathUtils.TryMakeRelativeToFolder(context.AssetPath, "/Resources/", out var resourcePath)) { + result = default; + return false; + } + + var withoutExtension = PathUtils.GetPathWithoutExtension(resourcePath); + result = new TSource() { + ResourcePath = withoutExtension, + SubObjectName = context.IsMainAsset ? string.Empty : context.AssetName, + }; + return true; + } + } +} + +#endregion + + +#region NetworkAssetSourceFactoryStatic.cs + +namespace Fusion.Editor { + using UnityEditor; + using UnityEngine; + + /// + /// A implementation that creates . + /// + public partial class NetworkAssetSourceFactoryStatic : INetworkAssetSourceFactory { + /// + public const int Order = int.MaxValue; + + int INetworkAssetSourceFactory.Order => Order; + + /// + /// Creates . + /// + protected bool TryCreateInternal(in NetworkAssetSourceFactoryContext context, out TSource result) + where TSource : NetworkAssetSourceStaticLazy, new() + where TAsset : UnityEngine.Object { + + if (typeof(TAsset).IsSubclassOf(typeof(Component))) { + var prefab = (GameObject)context.Object; + + result = new TSource() { + Object = prefab.GetComponent() + }; + + } else { + result = new TSource() { + Object = new(context.InstanceID) + }; + } + return true; + } + } +} + +#endregion + + +#region AssetDatabaseUtils.Addressables.cs + +#if (FUSION_ADDRESSABLES || FUSION_ENABLE_ADDRESSABLES) && !FUSION_DISABLE_ADDRESSABLES +namespace Fusion.Editor { + using System; + using System.Collections.Generic; + using System.Linq; + using UnityEditor; + using UnityEditor.AddressableAssets; + using UnityEditor.AddressableAssets.Settings; + using UnityEngine; + + partial class AssetDatabaseUtils { + /// + /// Register a handler that will be called when an addressable asset with a specific label is added or removed. + /// + public static void AddAddressableAssetsWithLabelMonitor(string label, Action handler) { + AddressableAssetSettings.OnModificationGlobal += (settings, modificationEvent, data) => { + switch (modificationEvent) { + case AddressableAssetSettings.ModificationEvent.EntryAdded: + case AddressableAssetSettings.ModificationEvent.EntryCreated: + case AddressableAssetSettings.ModificationEvent.EntryModified: + case AddressableAssetSettings.ModificationEvent.EntryMoved: + + IEnumerable entries; + if (data is AddressableAssetEntry singleEntry) { + entries = Enumerable.Repeat(singleEntry, 1); + } else { + entries = (IEnumerable)data; + } + + List allEntries = new List(); + foreach (var entry in entries) { + entry.GatherAllAssets(allEntries, true, true, true); + if (allEntries.Any(x => HasLabel(x.AssetPath, label))) { + handler(settings.currentHash); + break; + } + + allEntries.Clear(); + } + + break; + + case AddressableAssetSettings.ModificationEvent.EntryRemoved: + // TODO: check what has been removed + handler(settings.currentHash); + break; + } + }; + } + + internal static AddressableAssetEntry GetAddressableAssetEntry(UnityEngine.Object source) { + if (source == null || !AssetDatabase.Contains(source)) { + return null; + } + + return GetAddressableAssetEntry(GetAssetGuidOrThrow(source)); + } + + internal static AddressableAssetEntry GetAddressableAssetEntry(string guid) { + if (string.IsNullOrEmpty(guid)) { + return null; + } + + var addressableSettings = AddressableAssetSettingsDefaultObject.Settings; + return addressableSettings.FindAssetEntry(guid); + } + + internal static AddressableAssetEntry CreateOrMoveAddressableAssetEntry(UnityEngine.Object source, string groupName = null) { + if (source == null || !AssetDatabase.Contains(source)) + return null; + + return CreateOrMoveAddressableAssetEntry(GetAssetGuidOrThrow(source), groupName); + } + + internal static AddressableAssetEntry CreateOrMoveAddressableAssetEntry(string guid, string groupName = null) { + if (string.IsNullOrEmpty(guid)) { + return null; + } + + var addressableSettings = AddressableAssetSettingsDefaultObject.Settings; + + AddressableAssetGroup group; + if (string.IsNullOrEmpty(groupName)) { + group = addressableSettings.DefaultGroup; + } else { + group = addressableSettings.FindGroup(groupName); + } + + if (group == null) { + throw new ArgumentOutOfRangeException($"Group {groupName} not found"); + } + + var entry = addressableSettings.CreateOrMoveEntry(guid, group); + return entry; + } + + internal static bool RemoveMoveAddressableAssetEntry(UnityEngine.Object source) { + if (source == null || !AssetDatabase.Contains(source)) { + return false; + } + + return RemoveMoveAddressableAssetEntry(GetAssetGuidOrThrow(source)); + } + + internal static bool RemoveMoveAddressableAssetEntry(string guid) { + if (string.IsNullOrEmpty(guid)) { + return false; + } + + var addressableSettings = AddressableAssetSettingsDefaultObject.Settings; + return addressableSettings.RemoveAssetEntry(guid); + } + + [InitializeOnLoadMethod] + static void InitializeRuntimeCallbacks() { + FusionAddressablesUtils.SetLoadEditorInstanceHandler(LoadEditorInstance); + } + + private static UnityEngine.Object LoadEditorInstance(string runtimeKey) { + if (string.IsNullOrEmpty(runtimeKey)) { + return default; + } + + if (!FusionAddressablesUtils.TryParseAddress(runtimeKey, out var mainKey, out var subKey)) { + throw new ArgumentException($"Invalid address: {runtimeKey}", nameof(runtimeKey)); + } + + if (GUID.TryParse(mainKey, out _)) { + // a guid one, we can load it + if (string.IsNullOrEmpty(subKey)) { + var asset = AssetDatabase.LoadMainAssetAtPath(AssetDatabase.GUIDToAssetPath(mainKey)); + if (asset != null) { + return asset; + } + } else { + foreach (var subAsset in AssetDatabase.LoadAllAssetRepresentationsAtPath(AssetDatabase.GUIDToAssetPath(mainKey))) { + if (ReferenceEquals(subAsset, null)) { + continue; + } + if (subAsset.name == subKey) { + return subAsset; + } + } + + // not returning null here, as there might be a chance for a guid-like address + } + } + + // need to resort to addressable asset settings + // path... this sucks + if (!AddressableAssetSettingsDefaultObject.SettingsExists) { + FusionEditorLog.Error($"Unable to load asset: {runtimeKey}; AddressableAssetSettings does not exist"); + return default; + } + + var settings = AddressableAssetSettingsDefaultObject.Settings; + Assert.Check(settings != null); + + var list = new List(); + settings.GetAllAssets(list, true, entryFilter: x => { + if (x.IsFolder) { + return mainKey.StartsWith(x.address, StringComparison.OrdinalIgnoreCase); + } else { + return mainKey.Equals(x.address, StringComparison.OrdinalIgnoreCase); + } + }); + + // given the filtering above, the list will contain more than one if we + // check for a root asset that has nested assets + foreach (var entry in list) { + if (runtimeKey.Equals(entry.address, StringComparison.OrdinalIgnoreCase)) { + return entry.TargetAsset; + } + } + + return default; + } + } +} +#endif + +#endregion + + +#region AssetDatabaseUtils.cs + +namespace Fusion.Editor { + using System; + using System.Collections; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Runtime.InteropServices; + using UnityEditor; + using UnityEditor.Build; + using UnityEditor.PackageManager; + using UnityEngine; + using Object = UnityEngine.Object; + + +#if UNITY_6000_3_OR_NEWER + using ObjectIdType = UnityEngine.EntityId; + using HierarchyIteratorType = UnityEditor.HierarchyIterator; +#else + using ObjectIdType = System.Int32; + using HierarchyIteratorType = UnityEditor.HierarchyProperty; +#endif + + + /// + /// Utility methods for working with Unity's + /// + public static partial class AssetDatabaseUtils { + + /// + /// Sets the asset dirty and, if is a sub-asset, also sets the main asset dirty. + /// + /// + public static void SetAssetAndTheMainAssetDirty(UnityEngine.Object obj) { + EditorUtility.SetDirty(obj); + + var assetPath = AssetDatabase.GetAssetPath(obj); + if (string.IsNullOrEmpty(assetPath)) { + return; + } + var mainAsset = AssetDatabase.LoadMainAssetAtPath(assetPath); + if (!mainAsset || mainAsset == obj) { + return; + } + EditorUtility.SetDirty(mainAsset); + } + + /// + /// Returns the asset path for the given instance ID or throws an exception if the asset is not found. + /// + public static string GetAssetPathOrThrow(ObjectIdType instanceID) { + var result = AssetDatabase.GetAssetPath(instanceID); + if (string.IsNullOrEmpty(result)) { + throw new ArgumentException($"Asset with InstanceID {instanceID} not found"); + } + return result; + } + + /// + /// Returns the asset path for the given object or throws an exception if is + /// not an asset. + /// + public static string GetAssetPathOrThrow(UnityEngine.Object obj) { + var result = AssetDatabase.GetAssetPath(obj); + if (string.IsNullOrEmpty(result)) { + throw new ArgumentException($"Asset {obj} not found"); + } + return result; + } + + /// + /// Returns the asset path for the given asset GUID or throws an exception if the asset is not found. + /// + public static string GetAssetPathOrThrow(string assetGuid) { + var result = AssetDatabase.GUIDToAssetPath(assetGuid); + if (string.IsNullOrEmpty(result)) { + throw new ArgumentException($"Asset with Guid {assetGuid} not found"); + } + + return result; + } + + /// + /// Returns the asset GUID for the given asset path or throws an exception if the asset is not found. + /// + public static string GetAssetGuidOrThrow(string assetPath) { + var result = AssetDatabase.AssetPathToGUID(assetPath); + if (string.IsNullOrEmpty(result)) { + throw new ArgumentException($"Asset with path {assetPath} not found"); + } + + return result; + } + + /// + /// Returns the asset GUID for the given instance ID or throws an exception if the asset is not found. + /// + public static string GetAssetGuidOrThrow(ObjectIdType instanceId) { + var assetPath = GetAssetPathOrThrow(instanceId); + return GetAssetGuidOrThrow(assetPath); + } + + /// + /// Returns the asset GUID for the given object reference or throws an exception if the asset is not found. + /// + public static string GetAssetGuidOrThrow(UnityEngine.Object obj) { + var assetPath = GetAssetPathOrThrow(obj); + return GetAssetGuidOrThrow(assetPath); + } + + /// + /// Gets the GUID and local file identifier for the given object reference or throws an exception if the asset is not found. + /// + public static (string, long) GetGUIDAndLocalFileIdentifierOrThrow(LazyLoadReference reference) where T : UnityEngine.Object { + if (!AssetDatabase.TryGetGUIDAndLocalFileIdentifier(reference, out var guid, out long localId)) { + throw new ArgumentException($"Asset with instanceId {reference} not found"); + } + + return (guid, localId); + } + + /// + /// Gets the GUID and local file identifier for the given object reference or throws an exception if the asset is not found. + /// + public static (string, long) GetGUIDAndLocalFileIdentifierOrThrow(UnityEngine.Object obj) { + if (!AssetDatabase.TryGetGUIDAndLocalFileIdentifier(obj, out var guid, out long localId)) { + throw new ArgumentException(nameof(obj)); + } + + return (guid, localId); + } + + /// + /// Gets the GUID and local file identifier for the instance ID or throws an exception if the asset is not found. + /// + public static (string, long) GetGUIDAndLocalFileIdentifierOrThrow(ObjectIdType instanceId) { + if (!AssetDatabase.TryGetGUIDAndLocalFileIdentifier(instanceId, out var guid, out long localId)) { + throw new ArgumentException($"Asset with instanceId {instanceId} not found"); + } + + return (guid, localId); + } + + /// + /// Moves the asset at to or throws an exception if the move fails. + /// + public static void MoveAssetOrThrow(string source, string destination) { + var error = AssetDatabase.MoveAsset(source, destination); + if (!string.IsNullOrEmpty(error)) { + throw new ArgumentException($"Failed to move {source} to {destination}: {error}"); + } + } + + /// + /// Returns if the asset at has the given . + /// + public static bool HasLabel(string assetPath, string label) { + var guidStr = AssetDatabase.AssetPathToGUID(assetPath); + if (!GUID.TryParse(guidStr, out var guid)) { + return false; + } + + var labels = AssetDatabase.GetLabels(guid); + var index = Array.IndexOf(labels, label); + return index >= 0; + } + + /// + /// Returns if the asset has the given . + /// + public static bool HasLabel(UnityEngine.Object obj, string label) { + var labels = AssetDatabase.GetLabels(obj); + var index = Array.IndexOf(labels, label); + return index >= 0; + } + + /// + /// Returns if the asset has the given . + /// + public static bool HasLabel(GUID guid, string label) { + var labels = AssetDatabase.GetLabels(guid); + var index = Array.IndexOf(labels, label); + return index >= 0; + } + + /// + /// Returns if the asset at has any of the given . + /// + public static bool HasAnyLabel(string assetPath, params string[] labels) { + var guidStr = AssetDatabase.AssetPathToGUID(assetPath); + if (!GUID.TryParse(guidStr, out var guid)) { + return false; + } + + var assetLabels = AssetDatabase.GetLabels(guid); + foreach (var label in labels) { + if (Array.IndexOf(assetLabels, label) >= 0) { + return true; + } + } + + return false; + } + + /// + /// Returns if the has any of the given . + /// + public static bool HasAnyLabel(UnityEngine.Object asset, params string[] labels) { + var assetLabels = AssetDatabase.GetLabels(asset); + foreach (var label in labels) { + if (Array.IndexOf(assetLabels, label) >= 0) { + return true; + } + } + + return false; + } + + + /// + /// Sets or unsets label for the asset at , depending + /// on the value of . + /// + /// if there was a change to the labels. + public static bool SetLabel(string assetPath, string label, bool present) { + var guid = AssetDatabase.GUIDFromAssetPath(assetPath); + if (guid.Empty()) { + return false; + } + + var labels = AssetDatabase.GetLabels(guid); + var index = Array.IndexOf(labels, label); + if (present) { + if (index >= 0) { + return false; + } + ArrayUtility.Add(ref labels, label); + } else { + if (index < 0) { + return false; + } + ArrayUtility.RemoveAt(ref labels, index); + } + + var obj = AssetDatabase.LoadMainAssetAtPath(assetPath); + if (obj == null) { + return false; + } + + AssetDatabase.SetLabels(obj, labels); + return true; + } + + /// + /// Sets or unsets the label for the asset , depending + /// on the value of . + /// + /// if there was a change to the labels. + public static bool SetLabel(UnityEngine.Object obj, string label, bool present) { + var labels = AssetDatabase.GetLabels(obj); + var index = Array.IndexOf(labels, label); + if (present) { + if (index >= 0) { + return false; + } + ArrayUtility.Add(ref labels, label); + } else { + if (index < 0) { + return false; + } + ArrayUtility.RemoveAt(ref labels, index); + } + + AssetDatabase.SetLabels(obj, labels); + return true; + } + + /// + /// Sets all the labels for the asset at . + /// + /// if the asset was found + public static bool SetLabels(string assetPath, string[] labels) { + var obj = AssetDatabase.LoadMainAssetAtPath(assetPath); + if (obj == null) { + return false; + } + + AssetDatabase.SetLabels(obj, labels); + return true; + } + + /// + /// Checks if a scripting define is defined for . + /// + public static bool HasScriptingDefineSymbol(NamedBuildTarget target, string value) { + var defines = PlayerSettings.GetScriptingDefineSymbols(target).Split(';'); + return System.Array.IndexOf(defines, value) >= 0; + } + + /// + /// Checks if a scripting define is defined for . + /// + public static bool HasScriptingDefineSymbol(BuildTargetGroup group, string value) { + var defines = PlayerSettings.GetScriptingDefineSymbols(NamedBuildTarget.FromBuildTargetGroup(group)).Split(';'); + return System.Array.IndexOf(defines, value) >= 0; + } + + /// + public static T SetScriptableObjectType(ScriptableObject obj) where T : ScriptableObject { + return (T)SetScriptableObjectType(obj, typeof(T)); + } + + /// + /// Changes the type of scriptable object. + /// + /// The new instance with requested type + public static ScriptableObject SetScriptableObjectType(ScriptableObject obj, Type type) { + const string ScriptPropertyName = "m_Script"; + + if (!obj) { + throw new ArgumentNullException(nameof(obj)); + } + if (type == null) { + throw new ArgumentNullException(nameof(type)); + } + if (!type.IsSubclassOf(typeof(ScriptableObject))) { + throw new ArgumentException($"Type {type} is not a subclass of {nameof(ScriptableObject)}"); + } + + if (obj.GetType() == type) { + return obj; + } + + var tmp = ScriptableObject.CreateInstance(type); + try { + using (var dst = new SerializedObject(obj)) { + using (var src = new SerializedObject(tmp)) { + var scriptDst = dst.FindPropertyOrThrow(ScriptPropertyName); + var scriptSrc = src.FindPropertyOrThrow(ScriptPropertyName); + Debug.Assert(scriptDst.objectReferenceValue != scriptSrc.objectReferenceValue); + dst.CopyFromSerializedProperty(scriptSrc); + dst.ApplyModifiedPropertiesWithoutUndo(); + return (ScriptableObject)dst.targetObject; + } + } + } finally { + UnityEngine.Object.DestroyImmediate(tmp); + } + } + + private static bool IsEnumValueObsolete(string valueName) where T : System.Enum { + var fi = typeof(T).GetField(valueName); + var attributes = fi.GetCustomAttributes(typeof(System.ObsoleteAttribute), false); + return attributes?.Length > 0; + } + + internal static IEnumerable ValidBuildTargetGroups { + get { + foreach (var name in System.Enum.GetNames(typeof(BuildTargetGroup))) { + if (IsEnumValueObsolete(name)) + continue; + var group = (BuildTargetGroup)System.Enum.Parse(typeof(BuildTargetGroup), name); + if (group == BuildTargetGroup.Unknown) + continue; + + yield return group; + } + } + } + + /// + /// Checks if any and all have the given scripting define symbol. + /// + /// if all groups have the symbol, if none have it, if some have it and some don't + public static bool? HasScriptingDefineSymbol(string value) { + bool anyDefined = false; + bool anyUndefined = false; + foreach (BuildTargetGroup group in ValidBuildTargetGroups) { + if (HasScriptingDefineSymbol(group, value)) { + anyDefined = true; + } else { + anyUndefined = true; + } + } + + return (anyDefined && anyUndefined) ? (bool?)null : anyDefined; + } + + /// + /// Adds or removes scripting define symbol from , depending + /// on the value of + /// + public static void UpdateScriptingDefineSymbol(BuildTargetGroup group, string define, bool enable) { + UpdateScriptingDefineSymbolInternal(new[] { group }, + enable ? new[] { define } : null, + enable ? null : new[] { define }); + } + + /// + /// Adds or removes from all s, depending on the value of + /// + public static void UpdateScriptingDefineSymbol(string define, bool enable) { + UpdateScriptingDefineSymbolInternal(ValidBuildTargetGroups, + enable ? new[] { define } : null, + enable ? null : new[] { define }); + } + + internal static void UpdateScriptingDefineSymbol(BuildTargetGroup group, IEnumerable definesToAdd, IEnumerable definesToRemove) { + UpdateScriptingDefineSymbolInternal(new[] { group }, + definesToAdd, + definesToRemove); + } + + internal static void UpdateScriptingDefineSymbol(IEnumerable definesToAdd, IEnumerable definesToRemove) { + UpdateScriptingDefineSymbolInternal(ValidBuildTargetGroups, + definesToAdd, + definesToRemove); + } + + private static void UpdateScriptingDefineSymbolInternal(IEnumerable groups, IEnumerable definesToAdd, IEnumerable definesToRemove) { + EditorApplication.LockReloadAssemblies(); + try { + foreach (var group in groups) { + var originalDefines = PlayerSettings.GetScriptingDefineSymbols(NamedBuildTarget.FromBuildTargetGroup(group)); + var defines = originalDefines.Split(';').ToList(); + + if (definesToRemove != null) { + foreach (var d in definesToRemove) { + defines.Remove(d); + } + } + + if (definesToAdd != null) { + foreach (var d in definesToAdd) { + defines.Remove(d); + defines.Add(d); + } + } + + var newDefines = string.Join(";", defines); + PlayerSettings.SetScriptingDefineSymbols(NamedBuildTarget.FromBuildTargetGroup(group), newDefines); + } + } finally { + EditorApplication.UnlockReloadAssemblies(); + } + } + + /// + /// Iterates over all assets in the project that match the given search criteria, without + /// actually loading them. + /// + /// The optional root folder + /// The optional label + public static AssetEnumerable IterateAssets(string root = null, string label = null) where T : UnityEngine.Object { + return IterateAssets(root, label, typeof(T)); + } + + /// + /// Iterates over all assets in the project that match the given search criteria, without + /// actually loading them. + /// + /// The optional root folder + /// The optional label + /// The optional type + public static AssetEnumerable IterateAssets(string root = null, string label = null, Type type = null) { + return new AssetEnumerable(root, label, type); + } + + /// + /// Checks if given path is read only. This can happen e.g. for non-local and non-embedded packages. + /// + public static bool IsPathWritable(string path) { + if (string.IsNullOrEmpty(path)) { + return false; + } + + var directoryPath = Path.GetDirectoryName(path); + if (string.IsNullOrEmpty(directoryPath)) { + return true; + } + + if (UnityInternal.AssetDatabase.TryGetAssetFolderInfo(directoryPath, out _, out var immutable) && immutable) { + return false; + } + + return true; + } + + static Lazy s_rootFolders = new Lazy(() => new[] { "Assets" }.Concat(UnityEditor.PackageManager.PackageInfo.GetAllRegisteredPackages() + .Where(x => !IsPackageHidden(x)) +#if !FUSION_ENABLE_SEARCH_IN_UNITY_PACKAGES + .Where(x => !x.assetPath.StartsWith("Packages/com.unity.", StringComparison.Ordinal)) +#endif + .Select(x => x.assetPath)) + .ToArray()); + + private static bool IsPackageHidden(UnityEditor.PackageManager.PackageInfo info) => info.type == "module" || info.type == "feature" && info.source != PackageSource.Embedded; + + // ReSharper disable once InconsistentNaming + internal static Type GetMainAssetTypeFromGUID(GUID guid) { +#if UNITY_2022_3_OR_NEWER + return AssetDatabase.GetMainAssetTypeFromGUID(guid); +#else + var path = AssetDatabase.GUIDToAssetPath(guid); + if (string.IsNullOrEmpty(path)) { + return null; + } + + return AssetDatabase.GetMainAssetTypeAtPath(path); +#endif + } + + /// + /// Enumerates assets in the project that match the given search criteria using API. + /// Obtained with . + /// + public struct AssetEnumerator : IEnumerator { + + private HierarchyIteratorType _hierarchyProperty; + private int _rootFolderIndex; + private bool _skipFirstNext; + + private readonly string[] _rootFolders; + + /// + /// Creates a new instance. + /// + public AssetEnumerator(string root, string label, Type type) { + var searchFilter = MakeSearchFilter(label, type); + _rootFolderIndex = 0; + if (string.IsNullOrEmpty(root)) { + // search everywhere + _rootFolders = s_rootFolders.Value; + _hierarchyProperty = new HierarchyIteratorType(_rootFolders[0]); + } else { + _rootFolders = null; + _hierarchyProperty = new HierarchyIteratorType(root); + } + + _skipFirstNext = false; + + // are we already at the target asset + if (!_hierarchyProperty.isFolder) { + var guid = _hierarchyProperty.GetAssetGuid(); + // first, should we even consider this asset? + if (guid == default) { + // invalid path, nothing to do + } else if (!string.IsNullOrEmpty(label) && !HasLabel(guid, label)) { + // no label, ignore + } else if (type == null) { + // we accept any type, so we're good here + _skipFirstNext = true; + } else { + // we only accept a matching type + var mainAssetType = GetMainAssetTypeFromGUID(guid); + if (mainAssetType != null && (mainAssetType == type || mainAssetType.IsSubclassOf(type))) { + _skipFirstNext = true; + } + } + } + + _hierarchyProperty.SetSearchFilter(searchFilter, (int)SearchableEditorWindow.SearchMode.All); + } + + /// + /// Updates internal . + /// + /// + public bool MoveNext() { + if (_skipFirstNext) { + _skipFirstNext = false; + return true; + } + if (_hierarchyProperty.Next(null)) { + return true; + } + + if (_rootFolders == null || _rootFolderIndex + 1 >= _rootFolders.Length) { + return false; + } + + var newHierarchyProperty = new HierarchyIteratorType(_rootFolders[++_rootFolderIndex]); + UnityInternal.HierarchyIterator.CopySearchFilterFrom(newHierarchyProperty, _hierarchyProperty); + _hierarchyProperty = newHierarchyProperty; + + // try again + return MoveNext(); + } + + /// + /// Throws . + /// + /// + public void Reset() { + throw new System.NotImplementedException(); + } + + /// + /// Returns the internernal . Most of the time + /// this will be the same instance as returned the last time, so do not cache + /// the result - check its properties intestead. + /// + public HierarchyIteratorType Current => _hierarchyProperty; + + object IEnumerator.Current => Current; + + /// + public void Dispose() { + } + + private static string MakeSearchFilter(string label, Type type) { + string searchFilter; + + if (type == typeof(GameObject)) { + searchFilter = "t:prefab"; + } else if (type == typeof(SceneAsset)) { + searchFilter = "t:scene"; + } else if (type != null) { + searchFilter = "t:" + type.FullName; + } else { + searchFilter = ""; + } + + if (!string.IsNullOrEmpty(label)) { + if (searchFilter.Length > 0) { + searchFilter += " "; + } + + searchFilter += "l:" + label; + } + + return searchFilter; + } + } + + /// + /// Enumerable of assets in the project that match the given search criteria. + /// + /// + public struct AssetEnumerable : IEnumerable { + + private readonly string _root; + private readonly string _label; + private readonly Type _type; + + /// + /// Not intended to be called directly. Use instead. + /// + public AssetEnumerable(string root, string label, Type type) { + _type = type; + _root = root; + _label = label; + } + + /// + /// Not intended to be called directly. Use instead. + /// + public AssetEnumerator GetEnumerator() => new AssetEnumerator(_root, _label, _type); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } + + /// + /// Sends out command to virtual peers + /// before calling . + /// + public static void RegisterCustomDependencyWithMppmWorkaround(string customDependency, Hash128 hash) { + FusionMppm.MainEditor?.Send(new FusionMppmRegisterCustomDependencyCommand() { + DependencyName = customDependency, + Hash = hash.ToString(), + }); + AssetDatabase.RegisterCustomDependency(customDependency, hash); + } + + /// + /// Returns the address of an asset or an empty string, if either Addressables are disabled or the asset is not addressable. + /// + public static string GetAddress(UnityEngine.Object asset) { +#if (FUSION_ADDRESSABLES || FUSION_ENABLE_ADDRESSABLES) && !FUSION_DISABLE_ADDRESSABLES + var entry = GetAddressableAssetEntry(asset); + + if (entry != null) { + return entry.address; + } +#endif + return string.Empty; + } + + /// + /// Returns the address of an asset or an empty string, if either Addressables are disabled or the asset is not addressable. + /// + public static string GetAddress(string guid) { +#if (FUSION_ADDRESSABLES || FUSION_ENABLE_ADDRESSABLES) && !FUSION_DISABLE_ADDRESSABLES + var entry = GetAddressableAssetEntry(guid); + + if (entry != null) { + return entry.address; + } +#endif + return string.Empty; + } + } +} + +#endregion + + +#region EditorButtonDrawer.cs + +namespace Fusion.Editor { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + using UnityEditor; + using UnityEngine; + + struct EditorButtonDrawer { + + private struct ButtonEntry { + public MethodInfo Method; + public GUIContent Content; + public EditorButtonAttribute Attribute; + public (DoIfAttributeBase, Func)[] DoIfs; + } + + private Editor _lastEditor; + private List _buttons; + + public void Draw(Editor editor) { + var targets = editor.targets; + + if (_lastEditor != editor) { + _lastEditor = editor; + Refresh(editor); + } + + if (_buttons == null || targets == null || targets.Length == 0) { + return; + } + + foreach (var entry in _buttons) { + + if (entry.Attribute.Visibility == EditorButtonVisibility.PlayMode && !EditorApplication.isPlaying) { + continue; + } + + if (entry.Attribute.Visibility == EditorButtonVisibility.EditMode && EditorApplication.isPlaying) { + continue; + } + + if (!entry.Attribute.AllowMultipleTargets && editor.targets.Length > 1) { + continue; + } + + bool readOnly = false; + bool hidden = false; + string warningMessage = null; + bool warningAsBox = false; + + foreach (var (doIf, getter) in entry.DoIfs) { + + bool checkResult; + + if (getter == null) { + checkResult = DoIfAttributeDrawer.CheckDraw(doIf, editor.serializedObject); + } else { + var value = getter(targets[0]); + checkResult = DoIfAttributeDrawer.CheckCondition(doIf, value); + } + + if (!checkResult) { + if (doIf is DrawIfAttribute drawIf) { + if (drawIf.Hide) { + hidden = true; + break; + } else { + readOnly = true; + } + } else if (doIf is WarnIfAttribute warnIf) { + warningMessage = warnIf.Message; + warningAsBox = warnIf.AsBox; + } + } + } + + if (hidden) { + continue; + } + + using (warningMessage == null ? null : (IDisposable)new FusionEditorGUI.WarningScope(warningMessage)) { + var rect = FusionEditorGUI.LayoutHelpPrefix(editor, entry.Method); + using (new EditorGUI.DisabledScope(readOnly)) { + if (GUI.Button(rect, entry.Content)) { + EditorGUI.BeginChangeCheck(); + + if (entry.Method.IsStatic) { + entry.Method.Invoke(null, null); + } else { + foreach (var target in targets) { + entry.Method.Invoke(target, null); + if (entry.Attribute.DirtyObject) { + EditorUtility.SetDirty(target); + } + } + } + + if (EditorGUI.EndChangeCheck()) { + editor.serializedObject.Update(); + } + } + } + } + } + } + + private void Refresh(Editor editor) { + if (editor == null) { + throw new ArgumentNullException(nameof(editor)); + } + + var targetType = editor.target.GetType(); + + _buttons = targetType + .GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.FlattenHierarchy) + .Where(x => x.GetParameters().Length == 0 && x.IsDefined(typeof(EditorButtonAttribute))) + .Select(method => { + var attribute = method.GetCustomAttribute(); + var label = new GUIContent(attribute.Label ?? ObjectNames.NicifyVariableName(method.Name)); + var drawIfs = method.GetCustomAttributes() + .Select(x => { + var prop = editor.serializedObject.FindProperty(x.ConditionMember); + return prop != null ? (x, null) : (x, targetType.CreateGetter(x.ConditionMember)); + }) + .ToArray(); + return new ButtonEntry() { + Attribute = attribute, + Content = label, + Method = method, + DoIfs = drawIfs, + }; + }) + .OrderBy(x => x.Attribute.Priority) + .ToList(); + } + } +} + +#endregion + + +#region EnumDrawer.cs + +namespace Fusion.Editor { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + using UnityEditor; + using UnityEngine; + + struct EnumDrawer { + private Mask256[] _values; + private string[] _names; + private bool _isFlags; + private Type _enumType; + private Mask256 _allBitMask; + private FieldInfo[] _fields; + + [NonSerialized] + private List _selectedIndices; + + public Mask256[] Values => _values; + public string[] Names => _names; + public bool IsFlags => _isFlags; + public Type EnumType => _enumType; + public Mask256 BitMask => _allBitMask; + public FieldInfo[] Fields => _fields; + + public bool EnsureInitialized(Type enumType, bool includeFields) { + + if (enumType == null) { + throw new ArgumentNullException(nameof(enumType)); + } + + bool isEnum = enumType.IsEnum; + + if (!isEnum && !typeof(FieldsMask).IsAssignableFrom(enumType)) { + throw new ArgumentException("Type must be an enum or FieldsMask", nameof(enumType)); + } + + // Already initialized + if (_enumType == enumType) { + return false; + } + + if (isEnum) { + var enumUnderlyingType = Enum.GetUnderlyingType(enumType); + var rawValues = Enum.GetValues(enumType); + + _fields = includeFields ? new FieldInfo[rawValues.Length] : null; + _names = Enum.GetNames(enumType); + _values = new Mask256[rawValues.Length]; + _isFlags = enumType.GetCustomAttribute() != null; + _enumType = enumType; + + for (int i = 0; i < rawValues.Length; ++i) { + if (enumUnderlyingType == typeof(int) || + enumUnderlyingType == typeof(long) || + enumUnderlyingType == typeof(short) || + enumUnderlyingType == typeof(byte)) { + _values[i] = Convert.ToInt64(rawValues.GetValue(i)); + } else { + _values[i] = unchecked((long)Convert.ToUInt64(rawValues.GetValue(i))); + } + + _allBitMask[0] |= _values[i][0]; + if (includeFields) { + _fields[i] = enumType.GetField(_names[i], BindingFlags.Static | BindingFlags.Public); + } + } + + } else { + // Handling for FieldsMask + var tType = enumType.GenericTypeArguments[0]; + + _fields = tType.GetFields(); + _names = new string[_fields.Length]; + _values = new Mask256[_fields.Length]; + _isFlags = true; + _enumType = enumType; + + for (int i = 0; i < _values.Length; i++) { + long value = (long)1 << i; + _allBitMask.SetBit(i, true);; + _values[i].SetBit(i, true); // = (long)1 << i; + _names[i] = _fields[i].Name; + } + } + + for (int i = 0; i < _names.Length; ++i) { + _names[i] = ObjectNames.NicifyVariableName(_names[i]); + } + + return true; + } + + public void Draw(Rect position, SerializedProperty property, Type enumType, bool isEnum) { + + if (property == null) { + throw new ArgumentNullException(nameof(property)); + } + + EnsureInitialized(enumType, false); + Mask256 currentValue; + + if (isEnum) { + currentValue = new Mask256( + property.longValue + ); + } else { + currentValue = new Mask256( + property.GetFixedBufferElementAtIndex(0).longValue, + property.GetFixedBufferElementAtIndex(1).longValue, + property.GetFixedBufferElementAtIndex(2).longValue, + property.GetFixedBufferElementAtIndex(3).longValue + ); + } + + _selectedIndices ??= new List(); + _selectedIndices.Clear(); + + // find out what to show + for (int i = 0; i < _values.Length; ++i) { + var value = _values[i]; + if (_isFlags == false) { + if (currentValue[0]== value[0]) { + _selectedIndices.Add(i); + break; + } + } else if (Equals(currentValue & value, value)) { + _selectedIndices.Add(i); + } + } + + string labelValue; + if (_selectedIndices.Count == 0) { + if (_isFlags && currentValue.IsNothing()) { + labelValue = "Nothing"; + } else { + labelValue = ""; + } + } else if (_selectedIndices.Count == 1) { + labelValue = _names[_selectedIndices[0]]; + } else { + Debug.Assert(_isFlags); + if (_selectedIndices.Count == _values.Length) { + labelValue = "Everything"; + } else { + var names = _names; + labelValue = string.Join(", ", _selectedIndices.Select(x => names[x])); + } + } + + if (EditorGUI.DropdownButton(position, new GUIContent(labelValue), FocusType.Keyboard)) { + var values = _values; + var indices = _selectedIndices; + + if (_isFlags) { + var allOptions = new[] { "Nothing", "Everything" }.Concat(_names).ToArray(); + List allIndices = new List(); + if (_selectedIndices.Count == 0) { + allIndices.Add(0); // nothing + } + else if (_selectedIndices.Count == _values.Length) { + allIndices.Add(1); // everything + } + allIndices.AddRange(_selectedIndices.Select(x => x + 2)); + + UnityInternal.EditorUtility.DisplayCustomMenu(position, allOptions, allIndices.ToArray(), (userData, options, selected) => { + if (selected == 0) { + // Clicked None + if (isEnum) { + property.longValue = 0; + } + else { + property.GetFixedBufferElementAtIndex(0).longValue = 0; + property.GetFixedBufferElementAtIndex(1).longValue = 0; + property.GetFixedBufferElementAtIndex(2).longValue = 0; + property.GetFixedBufferElementAtIndex(3).longValue = 0; + } + } else if (selected == 1) { + // Selected Everything + if (isEnum) { + property.longValue = 0; + } else { + property.GetFixedBufferElementAtIndex(0).longValue = 0; + property.GetFixedBufferElementAtIndex(1).longValue = 0; + property.GetFixedBufferElementAtIndex(2).longValue = 0; + property.GetFixedBufferElementAtIndex(3).longValue = 0; + } + foreach (var value in values) { + if (isEnum) { + property.longValue |= value[0]; + } else{ + property.GetFixedBufferElementAtIndex(0).longValue |= value[0]; + property.GetFixedBufferElementAtIndex(1).longValue |= value[1]; + property.GetFixedBufferElementAtIndex(2).longValue |= value[2]; + property.GetFixedBufferElementAtIndex(3).longValue |= value[3]; + } + } + } else { + // Toggled a value + selected -= 2; + if (indices.Contains(selected)) { + if (isEnum) { + property.longValue &= ~values[selected][0]; + } else { + property.GetFixedBufferElementAtIndex(0).longValue &= ~values[selected][0]; + property.GetFixedBufferElementAtIndex(1).longValue &= ~values[selected][1]; + property.GetFixedBufferElementAtIndex(2).longValue &= ~values[selected][2]; + property.GetFixedBufferElementAtIndex(3).longValue &= ~values[selected][3]; + } + } else { + if (isEnum) { + property.longValue |= (long)values[selected][0]; + } else { + property.GetFixedBufferElementAtIndex(0).longValue |= (long)values[selected][0]; + property.GetFixedBufferElementAtIndex(1).longValue |= (long)values[selected][1]; + property.GetFixedBufferElementAtIndex(2).longValue |= (long)values[selected][2]; + property.GetFixedBufferElementAtIndex(3).longValue |= (long)values[selected][3]; + } + } + } + + property.serializedObject.ApplyModifiedProperties(); + }, null); + } else { + // non-flags enum + UnityInternal.EditorUtility.DisplayCustomMenu(position, _names, _selectedIndices.ToArray(), (userData, options, selected) => { + if (!indices.Contains(selected)) { + property.longValue = values[selected][0]; + property.serializedObject.ApplyModifiedProperties(); + } + }, null); + } + } + } + } +} + +#endregion + + +#region HashCodeUtilities.cs + +namespace Fusion.Editor { + internal static class HashCodeUtilities { + public const int InitialHash = (5381 << 16) + 5381; + + + /// + /// This may only be deterministic on 64 bit systems. + /// + /// + /// + /// + public static int GetHashDeterministic(this string str, int initialHash = InitialHash) { + unchecked { + var hash1 = initialHash; + var hash2 = initialHash; + + for (var i = 0; i < str.Length; i += 2) { + hash1 = ((hash1 << 5) + hash1) ^ str[i]; + if (i == str.Length - 1) { + break; + } + + hash2 = ((hash2 << 5) + hash2) ^ str[i + 1]; + } + + return hash1 + hash2 * 1566083941; + } + } + + public static int CombineHashCodes(int a, int b) { + return ((a << 5) + a) ^ b; + } + + public static int CombineHashCodes(int a, int b, int c) { + var t = ((a << 5) + a) ^ b; + return ((t << 5) + t) ^ c; + } + + public static unsafe int GetArrayHashCode(T* ptr, int length, int initialHash = InitialHash) where T : unmanaged { + var hash = initialHash; + for (var i = 0; i < length; ++i) { + hash = hash * 31 + ptr[i].GetHashCode(); + } + + return hash; + } + + public static int GetHashCodeDeterministic(byte[] data, int initialHash = 0) { + var hash = initialHash; + for (var i = 0; i < data.Length; ++i) { + hash = hash * 31 + data[i]; + } + + return hash; + } + + public static int GetHashCodeDeterministic(string data, int initialHash = 0) { + var hash = initialHash; + for (var i = 0; i < data.Length; ++i) { + hash = hash * 31 + data[i]; + } + + return hash; + } + + + public static unsafe int GetHashCodeDeterministic(T data, int initialHash = 0) where T : unmanaged { + return GetHashCodeDeterministic(&data, initialHash); + } + + public static unsafe int GetHashCodeDeterministic(T* data, int initialHash = 0) where T : unmanaged { + var hash = initialHash; + var ptr = (byte*)data; + for (var i = 0; i < sizeof(T); ++i) { + hash = hash * 31 + ptr[i]; + } + + return hash; + } + } +} + +#endregion + + +#region LazyAsset.cs + +namespace Fusion.Editor { + using System; + using System.Collections.Generic; + using UnityEngine; + using Object = UnityEngine.Object; + + internal class LazyAsset { + private T _value; + private Func _factory; + + public LazyAsset(Func factory) { + _factory = factory; + } + + public T Value { + get { + if (NeedsUpdate) { + lock (_factory) { + if (NeedsUpdate) { + _value = _factory(); + } + } + } + return _value; + } + } + + public static implicit operator T(LazyAsset lazyAsset) { + return lazyAsset.Value; + } + + public bool NeedsUpdate { + get { + if (_value is UnityEngine.Object obj) { + return !obj; + } else { + return _value == null; + } + } + } + } + + internal class LazyGUIStyle { + private Func, GUIStyle> _factory; + private GUIStyle _value; + private List _dependencies = new List(); + + public LazyGUIStyle(Func, GUIStyle> factory) { + _factory = factory; + } + + public static LazyGUIStyle Create(Func, GUIStyle> factory) { + return new LazyGUIStyle(factory); + } + + public static implicit operator GUIStyle(LazyGUIStyle lazyAsset) { + return lazyAsset.Value; + } + + public GUIStyle Value { + get { + if (NeedsUpdate) { + lock (_factory) { + if (NeedsUpdate) { + _dependencies.Clear(); + _value = _factory(_dependencies); + } + } + } + return _value; + } + } + + public bool NeedsUpdate { + get { + if (_value == null) { + return true; + } + foreach (var dependency in _dependencies) { + if (!dependency) { + return true; + } + } + + return false; + } + } + + public Vector2 CalcSize(GUIContent content) => Value.CalcSize(content); + public void Draw(Rect position, GUIContent content, bool isHover, bool isActive, bool on, bool hasKeyboardFocus) => Value.Draw(position, content, isHover, isActive, on, hasKeyboardFocus); + public void Draw(Rect position, bool isHover, bool isActive, bool on, bool hasKeyboardFocus) => Value.Draw(position, isHover, isActive, on, hasKeyboardFocus); + + public Font font => Value.font; + public FontStyle fontStyle => Value.fontStyle; + public bool richText => Value.richText; + public RectOffset margin => Value.margin; + public float fixedWidth => Value.fixedWidth; + public float fixedHeight => Value.fixedHeight; + public RectOffset padding => Value.padding; + public float CalcHeight(GUIContent content, float width) => Value.CalcHeight(content, width); + public GUIStyleState normal => Value.normal; + public GUIStyleState onNormal => Value.onNormal; + } + + internal class LazyGUIContent { + private Func, GUIContent> _factory; + private GUIContent _value; + private List _dependencies = new List(); + + public LazyGUIContent(Func, GUIContent> factory) { + _factory = factory; + } + + public static LazyGUIContent Create(Func, GUIContent> factory) { + return new LazyGUIContent(factory); + } + + public static implicit operator GUIContent(LazyGUIContent lazyAsset) { + return lazyAsset.Value; + } + + public GUIContent Value { + get { + if (NeedsUpdate) { + lock (_factory) { + if (NeedsUpdate) { + _dependencies.Clear(); + _value = _factory(_dependencies); + } + } + } + return _value; + } + } + + public bool NeedsUpdate { + get { + if (_value == null) { + return true; + } + foreach (var dependency in _dependencies) { + if (!dependency) { + return true; + } + } + + return false; + } + } + } + + internal static class LazyAsset { + public static LazyAsset Create(Func factory) { + return new LazyAsset(factory); + } + } +} + +#endregion + + +#region LogSettingsDrawer.cs + +namespace Fusion.Editor { + using System; + using System.Collections.Generic; + using System.Linq; + using UnityEditor; + using UnityEditor.Build; + using UnityEngine; + + + struct LogSettingsDrawer { + private static readonly Dictionary _logLevels = new Dictionary(StringComparer.Ordinal) { + { "FUSION_LOGLEVEL_DEBUG", LogLevel.Debug }, + { "FUSION_LOGLEVEL_INFO", LogLevel.Info }, + { "FUSION_LOGLEVEL_WARN", LogLevel.Warn }, + { "FUSION_LOGLEVEL_ERROR", LogLevel.Error }, + { "FUSION_LOGLEVEL_NONE", LogLevel.None }, + }; + + private static readonly Dictionary _enablingDefines = Enum.GetValues(typeof(TraceChannels)) + .Cast() + .ToDictionary(x => $"FUSION_TRACE_{x.ToString().ToUpperInvariant()}", x => x); + + private Dictionary _defines; + private Lazy _logLevelHelpContent; + private Lazy _traceChannelsHelpContent; + + void EnsureInitialized() { + if (_defines == null) { + UpdateDefines(); + } + + if (_logLevelHelpContent == null) { + _logLevelHelpContent = new Lazy(() => { + var result = new GUIContent(FusionCodeDoc.FindEntry(typeof(LogLevel)) ?? new GUIContent()); + result.text = ("This setting is applied with FUSION_LOGLEVEL_* defines.\n" + result.text).Trim(); + return result; + }); + } + + if (_traceChannelsHelpContent == null) { + _traceChannelsHelpContent = new Lazy(() => { + var result = new GUIContent(FusionCodeDoc.FindEntry(typeof(TraceChannels)) ?? new GUIContent()); + result.text = ("This setting is applied with FUSION_TRACE_* defines.\n" + result.text).Trim(); + return result; + }); + } + } + + public void DrawLayoutLevelEnumOnly(ScriptableObject editor) { + var activeLogLevel = GetActiveBuildTargetDefinedLogLevel(); + var invalidActiveLogLevel = activeLogLevel == null; + EditorGUI.BeginChangeCheck(); + + using (new FusionEditorGUI.ShowMixedValueScope(invalidActiveLogLevel)) { + activeLogLevel = (LogLevel)EditorGUILayout.EnumPopup(activeLogLevel ?? LogLevel.Info); + Debug.Assert(activeLogLevel != null); + } + + if (EditorGUI.EndChangeCheck()) { + SetLogLevel(activeLogLevel.Value); + } + } + + public void DrawLogLevelEnum(Rect rect) { + EnsureInitialized(); + var activeLogLevel = GetActiveBuildTargetDefinedLogLevel(); + var invalidActiveLogLevel = activeLogLevel == null; + EditorGUI.BeginChangeCheck(); + + using (new FusionEditorGUI.ShowMixedValueScope(invalidActiveLogLevel)) { + activeLogLevel = (LogLevel)EditorGUI.EnumPopup(rect, activeLogLevel ?? LogLevel.Info); + Debug.Assert(activeLogLevel != null); + } + + if (EditorGUI.EndChangeCheck()) { + SetLogLevel(activeLogLevel.Value); + } + } + + + public void DrawLayout(ScriptableObject editor, bool inlineHelp = true) { + EnsureInitialized(); + + { + var activeLogLevel = GetActiveBuildTargetDefinedLogLevel(); + var invalidActiveLogLevel = activeLogLevel == null; + var rect = inlineHelp ? FusionEditorGUI.LayoutHelpPrefix(editor, "Log Level", _logLevelHelpContent.Value) : EditorGUILayout.GetControlRect(); + EditorGUI.BeginChangeCheck(); + + using (new FusionEditorGUI.ShowMixedValueScope(invalidActiveLogLevel)) { + activeLogLevel = (LogLevel)EditorGUI.EnumPopup(rect, "Log Level", activeLogLevel ?? LogLevel.Info); + Debug.Assert(activeLogLevel != null); + } + + if (invalidActiveLogLevel) { + using (new FusionEditorGUI.WarningScope("Either FUSION_LOGLEVEL_* define is missing for the current build " + + "target or there are more than one defined. Changing the value will ensure there is " + + "exactly one define for each build target.")) { + } + } else if (GetAllBuildTargetsDefinedLogLevel() == null) { + using (new FusionEditorGUI.WarningScope("Not all build targets have the same log level defined. Changing the value will ensure " + + "there is exactly one define for each build target.")) { + } + } + + if (EditorGUI.EndChangeCheck()) { + SetLogLevel(activeLogLevel.Value); + } + } + + { + var activeTraceChannels = GetActiveBuildTargetDefinedTraceChannels(); + var rect = inlineHelp ? FusionEditorGUI.LayoutHelpPrefix(editor, "Trace Channels", _traceChannelsHelpContent.Value) : EditorGUILayout.GetControlRect(); + + EditorGUI.BeginChangeCheck(); + + activeTraceChannels = (TraceChannels)EditorGUI.EnumFlagsField(rect, "Trace Channels", activeTraceChannels); + + if (GetAllBuildTargetsDefinedTraceChannels() == null) { + using (new FusionEditorGUI.WarningScope("Not all build targets have the same trace channels defined. Changing the value will ensure " + + "the values are the same for each build target.")) { + } + } + + if (EditorGUI.EndChangeCheck()) { + SetTraceChannels(activeTraceChannels); + } + } + + } + + private void SetLogLevel(LogLevel activeLogLevel) { + foreach (var kv in _defines) { + var target = kv.Key; + var defines = kv.Value; + + string newDefine = null; + foreach (var (define, level) in _logLevels) { + if (level == activeLogLevel) { + newDefine = define; + continue; + } + ArrayUtility.Remove(ref defines, define); + } + ArrayUtility.Remove(ref defines, "FUSION_LOGLEVEL_TRACE"); + + Debug.Assert(newDefine != null); + if (!ArrayUtility.Contains(defines, newDefine)) { + ArrayUtility.Add(ref defines, newDefine); + } + + PlayerSettings.SetScriptingDefineSymbols(target, string.Join(";", defines)); + } + + UpdateDefines(); + } + + private void SetTraceChannels(TraceChannels activeTraceChannels) { + List definesToAdd = new List(); + List definesToRemove = new List(); + + foreach (var kv in _enablingDefines) { + var channel = kv.Value; + if (activeTraceChannels.HasFlag(channel)) { + definesToAdd.Add(kv.Key); + } else { + definesToRemove.Add(kv.Key); + } + } + + foreach (var kv in _defines) { + var target = kv.Key; + var defines = kv.Value; + + foreach (var d in definesToRemove) { + ArrayUtility.Remove(ref defines, d); + } + + foreach (var d in definesToAdd) { + if (!ArrayUtility.Contains(defines, d)) { + ArrayUtility.Add(ref defines, d); + } + } + + PlayerSettings.SetScriptingDefineSymbols(target, string.Join(";", defines)); + } + + + UpdateDefines(); + } + + public LogLevel? GetActiveBuildTargetDefinedLogLevel() { + EnsureInitialized(); + var activeBuildTarget = NamedBuildTarget.FromBuildTargetGroup(BuildPipeline.GetBuildTargetGroup(EditorUserBuildSettings.activeBuildTarget)); + return GetDefinedLogLevel(activeBuildTarget); + } + + private TraceChannels GetActiveBuildTargetDefinedTraceChannels() { + var activeBuildTarget = NamedBuildTarget.FromBuildTargetGroup(BuildPipeline.GetBuildTargetGroup(EditorUserBuildSettings.activeBuildTarget)); + return GetDefinedTraceChannels(activeBuildTarget); + } + + + private LogLevel? GetAllBuildTargetsDefinedLogLevel() { + LogLevel? result = null; + + foreach (var buildTarget in _defines.Keys) { + var targetLogLevel = GetDefinedLogLevel(buildTarget); + + if (targetLogLevel == null) { + return null; + } + + if (result == null) { + result = targetLogLevel; + } else if (result != targetLogLevel) { + return null; + } + } + + return result; + } + + private TraceChannels? GetAllBuildTargetsDefinedTraceChannels() { + TraceChannels? result = null; + + foreach (var buildTarget in _defines.Keys) { + var targetLogLevel = GetDefinedTraceChannels(buildTarget); + if (result == null) { + result = targetLogLevel; + } else if (result != targetLogLevel) { + return null; + } + } + + return result; + } + + private LogLevel? GetDefinedLogLevel(NamedBuildTarget group) { + LogLevel? result = null; + var defines = _defines[group]; + + foreach (var define in defines) { + if (_logLevels.TryGetValue(define, out var logLevel)) { + if (result != null) { + if (result != logLevel) { + return null; + } + } else { + result = logLevel; + } + } + } + + return result; + } + + private TraceChannels GetDefinedTraceChannels(NamedBuildTarget group) { + var channels = default(TraceChannels); + + var defines = _defines[group]; + foreach (var define in defines) { + if (_enablingDefines.TryGetValue(define, out var channel)) { + channels |= channel; + } + } + + return channels; + } + + private void UpdateDefines() { + _defines = AssetDatabaseUtils.ValidBuildTargetGroups + .Select(NamedBuildTarget.FromBuildTargetGroup) + .ToDictionary(x => x, x => PlayerSettings.GetScriptingDefineSymbols(x).Split(';')); + // extra handling for Dedicated Server builds that is not included by default + _defines[NamedBuildTarget.Server] = PlayerSettings.GetScriptingDefineSymbols(NamedBuildTarget.Server).Split(';'); + } + } + + +} + +#endregion + + +#region PathUtils.cs + +namespace Fusion.Editor { + using System; + + // TODO: this should be moved to the runtime part + static partial class PathUtils { + + public static bool TryMakeRelativeToFolder(string path, string folderWithSlashes, out string result) { + var index = path.IndexOf(folderWithSlashes, StringComparison.Ordinal); + + if (index < 0) { + result = string.Empty; + return false; + } + + if (folderWithSlashes[0] != '/' && index > 0) { + result = string.Empty; + return false; + } + + result = path.Substring(index + folderWithSlashes.Length); + return true; + } + + [Obsolete("Use " + nameof(TryMakeRelativeToFolder) + " instead")] + public static bool MakeRelativeToFolder(string path, string folder, out string result) { + result = string.Empty; + var formattedPath = Normalize(path); + if (formattedPath.Equals(folder, StringComparison.Ordinal) || + formattedPath.EndsWith("/" + folder)) { + return true; + } + var index = formattedPath.IndexOf(folder + "/", StringComparison.Ordinal); + var size = folder.Length + 1; + if (index >= 0 && formattedPath.Length >= size) { + result = formattedPath.Substring(index + size, formattedPath.Length - index - size); + return true; + } + return false; + } + + [Obsolete("Use Normalize instead")] + public static string MakeSane(string path) { + return Normalize(path); + } + + public static string Normalize(string path) { + return path.Replace("\\", "/").Replace("//", "/").TrimEnd('\\', '/').TrimStart('\\', '/'); + } + + public static string GetPathWithoutExtension(string path) { + if (path == null) + return null; + int length; + if ((length = path.LastIndexOf('.')) == -1) + return path; + return path.Substring(0, length); + } + + } +} + +#endregion + + +#region FusionCodeDoc.cs + +namespace Fusion.Editor { + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Reflection; + using System.Text.RegularExpressions; + using System.Xml; + using UnityEditor; + using UnityEngine; + + static class FusionCodeDoc { + public const string Label = "FusionCodeDoc"; + public const string Extension = "xml"; + public const string ExtensionWithDot = "." + Extension; + + private static readonly Dictionary s_parsedCodeDocs = new(); + private static readonly Dictionary<(string assemblyName, string memberKey), (GUIContent withoutType, GUIContent withType)> s_guiContentCache = new(); + + private static string CrefColor => EditorGUIUtility.isProSkin ? "#FFEECC" : "#664400"; + + public static GUIContent FindEntry(MemberInfo member, bool addTypeInfo = true) { + switch (member) { + case FieldInfo field: + return FindEntry(field, addTypeInfo); + case PropertyInfo property: + return FindEntry(property); + case MethodInfo method: + return FindEntry(method); + case Type type: + return FindEntry(type); + default: + throw new ArgumentOutOfRangeException(nameof(member)); + } + } + + public static GUIContent FindEntry(FieldInfo field, bool addTypeInfo = true) { + if (field == null) { + throw new ArgumentNullException(nameof(field)); + } + return FindEntry(field, $"F:{SanitizeTypeName(field.DeclaringType)}.{field.Name}", addTypeInfo); + } + + public static GUIContent FindEntry(PropertyInfo property) { + if (property == null) { + throw new ArgumentNullException(nameof(property)); + } + return FindEntry(property, $"P:{SanitizeTypeName(property.DeclaringType)}.{property.Name}"); + } + + public static GUIContent FindEntry(MethodInfo method) { + if (method == null) { + throw new ArgumentNullException(nameof(method)); + } + return FindEntry(method, $"M:{SanitizeTypeName(method.DeclaringType)}.{method.Name}"); + } + + public static GUIContent FindEntry(Type type) { + if (type == null) { + throw new ArgumentNullException(nameof(type)); + } + return FindEntry(type, $"T:{SanitizeTypeName(type)}"); + } + + private static GUIContent FindEntry(MemberInfo member, string key, bool addTypeInfo = true) { + + Assembly assembly; + if (member is Type type) { + assembly = type.Assembly; + } else { + FusionEditorLog.Assert(member.DeclaringType != null); + assembly = member.DeclaringType.Assembly; + } + + var assemblyName = assembly.GetName().Name; + FusionEditorLog.Assert(assemblyName != null); + + if (s_guiContentCache.TryGetValue((assemblyName, key), out var content)) { + return addTypeInfo ? content.withType : content.withoutType; + } + + if (TryGetEntry(key, out var entry, assemblyName: assemblyName)) { + // at this point we've got docs or not, need to save it now - in case returnType code doc search tries + // to load the same member info, which might happen; same for inheritdoc + content.withoutType = new GUIContent(entry.Summary ?? string.Empty, entry.Tooltip ?? string.Empty); + content.withType = content.withoutType; + } + + s_guiContentCache.Add((assemblyName, key), content); + + if (!string.IsNullOrEmpty(entry.InheritDocKey)) { + // need to resolve the inheritdoc + FusionEditorLog.Assert(entry.InheritDocKey != key); + if (TryResolveInheritDoc(entry.InheritDocKey, out var rootEntry)) { + content.withoutType = new GUIContent(rootEntry.Summary, rootEntry.Tooltip); + content.withType = content.withoutType; + s_guiContentCache[(assemblyName, key)] = content; + } + } + + // now add type info + Type returnType = (member as FieldInfo)?.FieldType ?? (member as PropertyInfo)?.PropertyType; + if (returnType != null) { + var typeEntry = FindEntry(returnType); + string typeSummary = ""; + + if (typeEntry != null) { + typeSummary += $"\n\n[{returnType.Name}] {typeEntry}"; + } + + if (returnType.IsEnum) { + // find all the enum values + foreach (var enumValue in returnType.GetFields(BindingFlags.Static | BindingFlags.Public)) { + var enumValueEntry = FindEntry(enumValue, addTypeInfo: false); + if (enumValueEntry != null) { + typeSummary += $"\n\n[{returnType.Name}.{enumValue.Name}] {enumValueEntry}"; + } + } + } + + if (typeSummary.Length > 0) { + content.withType = AppendContent(content.withType, typeSummary); + s_guiContentCache[(assemblyName, key)] = content; + } + } + + return addTypeInfo ? content.withType : content.withoutType; + + GUIContent AppendContent(GUIContent existing, string append) { + return new GUIContent((existing?.text + append).Trim('\n'), existing?.tooltip ?? string.Empty); + } + } + + private static bool TryResolveInheritDoc(string key, out MemberInfoEntry entry) { + // difficult to tell which assembly this comes from; just check in them all + // also make sure we're not in a loop + var visited = new HashSet(); + var currentKey = key; + + for (;;) { + if (!visited.Add(currentKey)) { + FusionEditorLog.Error($"Inheritdoc loop detected for {key}"); + break; + } + + if (!TryGetEntry(currentKey, out var currentEntry)) { + break; + } + + if (string.IsNullOrEmpty(currentEntry.InheritDocKey)) { + entry = currentEntry; + return true; + } + + currentKey = currentEntry.InheritDocKey; + } + + entry = default; + return false; + } + + private static bool TryGetEntry(string key, out MemberInfoEntry entry, string assemblyName = null) { + foreach (var path in AssetDatabase.FindAssets($"l:{Label} t:TextAsset") + .Select(x => AssetDatabase.GUIDToAssetPath(x))) { + + if (assemblyName != null) { + if (!Path.GetFileNameWithoutExtension(path).Contains(assemblyName, StringComparison.OrdinalIgnoreCase)) { + continue; + } + } + + // has this path been parsed already? + if (!s_parsedCodeDocs.TryGetValue(path, out var parsedCodeDoc)) { + s_parsedCodeDocs.Add(path, null); + + FusionEditorLog.Trace($"Trying to parse {path} for {key}"); + if (TryParseCodeDoc(path, out parsedCodeDoc)) { + s_parsedCodeDocs[path] = parsedCodeDoc; + } else { + FusionEditorLog.Trace($"Failed to parse {path}"); + } + } + + if (parsedCodeDoc != null) { + if (assemblyName != null && parsedCodeDoc.AssemblyName != assemblyName) { + // wrong assembly! + continue; + } + if (parsedCodeDoc.Entries.TryGetValue(key, out entry)) { + return true; + } + } + } + + entry = default; + return false; + } + + private static string SanitizeTypeName(Type type) { + var t = type; + if (type.IsGenericType) { + t = type.GetGenericTypeDefinition(); + } + FusionEditorLog.Assert(t != null); + return t.FullName.Replace('+', '.'); + } + + public static void InvalidateCache() { + s_parsedCodeDocs.Clear(); + s_guiContentCache.Clear(); + } + + private static bool TryParseCodeDoc(string path, out CodeDoc result) { + var xmlDoc = new XmlDocument(); + + try { + xmlDoc.Load(path); + } catch (Exception e) { + FusionEditorLog.Error($"Failed to load {path}: {e}"); + result = null; + return false; + } + + FusionEditorLog.Assert(xmlDoc.DocumentElement != null); + var assemblyName = xmlDoc.DocumentElement.SelectSingleNode("assembly") + ?.SelectSingleNode("name") + ?.FirstChild + ?.Value; + + if (assemblyName == null) { + result = null; + return false; + } + + var members = xmlDoc.DocumentElement.SelectSingleNode("members") + ?.SelectNodes("member"); + + if (members == null) { + result = null; + return false; + } + + var entries = new Dictionary(); + + foreach (XmlNode node in members) { + FusionEditorLog.Assert(node.Attributes != null); + var key = node.Attributes["name"].Value; + var inherit = node.SelectSingleNode("inheritdoc"); + if (inherit != null) { + + // hold on to the ref, will need to resolve it later + FusionEditorLog.Assert(inherit.Attributes != null); + var cref = inherit.Attributes["cref"]?.Value; + if (!string.IsNullOrEmpty(cref)) { + entries.Add(key, new MemberInfoEntry() { + InheritDocKey = cref + }); + continue; + } + } + + var summary = node.SelectSingleNode("summary")?.InnerXml.Trim(); + if (summary == null) { + continue; + } + + // remove generic indicator + summary = summary.Replace("`1", ""); + + // fork tooltip and help summaries + var help = Reformat(summary, false); + var tooltip = Reformat(summary, true); + + if (!entries.TryAdd(key, new MemberInfoEntry() { Summary = help, Tooltip = tooltip })) { + FusionEditorLog.Warn($"Failed to add {key} with {help}: entry already exists ({path})"); + } + } + + result = new CodeDoc() { + AssemblyName = assemblyName, + Entries = entries, + }; + return true; + } + + private static string Reformat(string summary, bool forTooltip) { + // Tooltips don't support formatting tags. Inline help does. + if (forTooltip) { + summary = Regexes.SeeWithCref.Replace(summary, "$1"); + summary = Regexes.See.Replace(summary, "$1"); + summary = Regexes.XmlEmphasizeBrackets.Replace(summary, "$1"); + } else { + var colorstring = $"$1"; + summary = Regexes.SeeWithCref.Replace(summary, colorstring); + summary = Regexes.See.Replace(summary, colorstring); + } + + + summary = Regexes.XmlCodeBracket.Replace(summary, "$1"); + + // Reduce all sequential whitespace characters into a single space. + summary = Regexes.WhitespaceString.Replace(summary, " "); + + // Turn and
into line breaks + summary = Regex.Replace(summary, @"
\s?", "\n\n"); // prevent back to back paras from producing 4 line returns. + summary = Regex.Replace(summary, @"\s?", "\n\n"); + summary = Regex.Replace(summary, @"\s?", "\n\n"); + + // handle lists + for (;;) { + var listMatch = Regexes.BulletPointList.Match(summary); + if (!listMatch.Success) { + break; + } + var innerText = listMatch.Groups[1].Value; + innerText = Regexes.ListItemBracket.Replace(innerText, $"\n\u2022 $1"); + summary = summary.Substring(0, listMatch.Index) + innerText + summary.Substring(listMatch.Index + listMatch.Length); + } + + + // unescape <> + summary = summary.Replace("<", "<"); + summary = summary.Replace(">", ">"); + summary = summary.Replace("&", "&"); + + summary = summary.Trim(); + + return summary; + } + + private struct MemberInfoEntry { + public string Summary; + public string Tooltip; + public string InheritDocKey; + } + + private class CodeDoc { + public string AssemblyName; + public Dictionary Entries; + } + + private class Postprocessor : AssetPostprocessor { + private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths) { + foreach (var path in importedAssets) { + if (!path.StartsWith("Assets/") || !path.EndsWith(ExtensionWithDot)) { + continue; + } + + if (AssetDatabaseUtils.HasLabel(path, Label)) { + FusionEditorLog.Trace($"Code doc {path} was imported, refreshing"); + InvalidateCache(); + continue; + } + + // is there a dll with the same name? + if (!File.Exists(path.Substring(0, path.Length - ExtensionWithDot.Length) + ".dll")) { + FusionEditorLog.Trace($"No DLL next to {path}, not going to add label {Label}."); + continue; + } + + if (!path.StartsWith("Assets/Photon/")) { + FusionEditorLog.Trace($"DLL is out of supported folder, not going to add label: {path}"); + continue; + } + + FusionEditorLog.Trace($"Detected a dll next to {path}, applying label and refreshing."); + AssetDatabaseUtils.SetLabel(path, Label, true); + InvalidateCache(); + } + } + } + + private static class Regexes { + public static readonly Regex SeeWithCref = new(@"", RegexOptions.None); + public static readonly Regex See = new(@"([\w\.\d]*)<\/see\w*>", RegexOptions.None); + public static readonly Regex WhitespaceString = new(@"\s+"); + public static readonly Regex XmlCodeBracket = new(@"([\s\S]*?)"); + public static readonly Regex XmlEmphasizeBrackets = new(@"<\w>([\s\S]*?)"); + public static readonly Regex BulletPointList = new(@"([\s\S]*?)"); + public static readonly Regex ListItemBracket = new(@"\s*([\s\S]*?)\s*"); + } + } +} + +#endregion + + +#region FusionCustomDependency.cs + +namespace Fusion.Editor { + using System; + using System.Diagnostics; + using UnityEditor; + using UnityEngine; + + /// + /// A wrapper around Unity's custom dependencies. Allows refresh to be deferred (if circumstances permit) and works around issues with custom dependencies in MPPM. + /// + public class FusionCustomDependency { + /// + /// Name of the dependency. + /// + public readonly string Name; + + readonly EditorApplication.CallbackFunction _applyHash; + readonly Func _getter; + + /// + /// Global force immediate switch. Set to true to force all the refreshes to be synchronous. + /// + // ReSharper disable once FieldCanBeMadeReadOnly.Global + // ReSharper disable once ConvertToConstant.Global + public static bool IsGlobalImmediateRefreshEnabled = false; + + /// Name of the dependency + /// Hash value getter. If returns null, the dependency will not be updated. + public FusionCustomDependency(string name, Func getter) { + Name = name; + _getter = getter; + _applyHash = () => Update(true); + } + + /// + /// Refreshes the dependency. Under normal circumstances, this will enqueue the operation until the next . + /// The hash will be calculated immediately if any of these is true: + /// - + /// - + /// - + /// + /// Note that if returns true, the immediate refresh will result with an error. + /// + /// + public void Refresh(bool forceImmediate = false) { + if (IsGlobalImmediateRefreshEnabled || forceImmediate || Application.isBatchMode) { + if (AssetDatabase.IsAssetImportWorkerProcess()) { + FusionEditorLog.ErrorImport($"Can't update custom dependencies during Asset Import ({Name})"); + } else { + Update(false); + } + } else { + EditorApplication.delayCall -= _applyHash; + EditorApplication.delayCall += _applyHash; + } + } + + void Update(bool delayed) { + // ReSharper disable once RedundantAssignment + var sw = Stopwatch.StartNew(); + var hash = _getter(); + if (hash.HasValue) { + FusionEditorLog.TraceImport($"Refreshing {Name} dependency hash: {hash} (delayed: {delayed}), took: {sw.Elapsed}"); + AssetDatabaseUtils.RegisterCustomDependencyWithMppmWorkaround(Name, hash.Value); + AssetDatabase.Refresh(); + } else { + FusionEditorLog.TraceImport($"Not refreshing {Name} dependency hash, returned null (delayed: {delayed})"); + } + } + } +} + +#endregion + + +#region FusionEditor.cs + +namespace Fusion.Editor { + using UnityEditor; + + /// + /// Base class for all Photon Common editors. Supports and . + /// + public abstract class FusionEditor : +#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED + Sirenix.OdinInspector.Editor.OdinEditor +#else + UnityEditor.Editor +#endif + { + private EditorButtonDrawer _buttonDrawer; + + /// + /// Prepares the editor by initializing the script header drawer. + /// + protected void PrepareOnInspectorGUI() { + FusionEditorGUI.InjectScriptHeaderDrawer(this); + } + + /// + /// Draws the editor buttons. + /// + protected void DrawEditorButtons() { + _buttonDrawer.Draw(this); + } + + /// + public override void OnInspectorGUI() { + PrepareOnInspectorGUI(); + base.OnInspectorGUI(); + DrawEditorButtons(); + } + + /// + /// Draws the script property field. + /// + protected void DrawScriptPropertyField() { + FusionEditorGUI.ScriptPropertyField(this); + } + +#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED + /// + /// Draws the default inspector. + /// + public new bool DrawDefaultInspector() { + EditorGUI.BeginChangeCheck(); + base.DrawDefaultInspector(); + return EditorGUI.EndChangeCheck(); + } +#else + /// + /// Empty implementations, provided for compatibility with OdinEditor class. + /// + protected virtual void OnEnable() { + } + + /// + /// Empty implementations, provided for compatibility with OdinEditor class. + /// + protected virtual void OnDisable() { + } +#endif + } +} + + +#endregion + + +#region FusionEditorGUI.InlineHelp.cs + +namespace Fusion.Editor { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + using UnityEditor; + using UnityEngine; + + static partial class FusionEditorGUI { + private const float SCROLL_WIDTH = 16f; + private const float LEFT_HELP_INDENT = 8f; + + private static (object, string, int) s_expandedHelp; + + internal static Rect GetInlineHelpButtonRect(Rect position, bool expectFoldout = true, bool forScriptHeader = false) { + var style = FusionEditorSkin.HelpButtonStyle; + + float width = style.fixedWidth <= 0 ? 16.0f : style.fixedWidth; + float height = style.fixedHeight <= 0 ? 16.0f : style.fixedHeight; + + // this 2 lower than line height, but makes it look better + const float FirstLineHeight = 16; + + int offsetY = forScriptHeader ? -1 : 1; + + var buttonRect = new Rect(position.x - width, position.y + (FirstLineHeight - height) / 2 + + offsetY, width, height); + using (new IndentLevelScope(EditorGUI.indentLevel + (expectFoldout ? - 1 : 0))) { + buttonRect.x = EditorGUI.IndentedRect(buttonRect).x; + // give indented items a little extra padding - no need for them to be so crammed + if (buttonRect.x > 8) { + buttonRect.x -= 2; + } + } + + return buttonRect; + } + + + internal static bool DrawInlineHelpButton(Rect buttonRect, bool state, bool doButton = true, bool doIcon = true) { + + var style = FusionEditorSkin.HelpButtonStyle; + + var result = false; + if (doButton) { + EditorGUIUtility.AddCursorRect(buttonRect, MouseCursor.Link); + using (new EnabledScope(true)) { + result = GUI.Button(buttonRect, state ? InlineHelpStyle.HideInlineContent : InlineHelpStyle.ShowInlineContent, GUIStyle.none); + } + } + + if (doIcon) { + // paint over what the inspector has drawn + if (Event.current.type == EventType.Repaint) { + style.Draw(buttonRect, false, false, state, false); + } + } + + return result; + } + + internal static Vector2 GetInlineBoxSize(GUIContent content) { + + // const int InlineBoxExtraHeight = 4; + + var outerStyle = FusionEditorSkin.InlineBoxFullWidthStyle; + var innerStyle = FusionEditorSkin.RichLabelStyle; + + var outerMargin = outerStyle.margin; + var outerPadding = outerStyle.padding; + + var width = UnityInternal.EditorGUIUtility.contextWidth - outerMargin.left - outerMargin.right; + + // well... we do this, because there's no way of knowing the indent and scroll bar existence + // when property height is calculated + width -= 25.0f; + + if (content == null || width <= 0) { + return default; + } + + width -= outerPadding.left + outerPadding.right; + + var height = innerStyle.CalcHeight(content, width); + + // assume min height + height = Mathf.Max(height, EditorGUIUtility.singleLineHeight); + + // add back all the padding + height += outerPadding.top + outerPadding.bottom; + height += outerMargin.top + outerMargin.bottom; + + return new Vector2(width, Mathf.Max(0, height)); + } + + internal static Rect DrawInlineBoxUnderProperty(GUIContent content, Rect propertyRect, Color color, bool drawSelector = false, bool hasFoldout = false) { + using (new EnabledScope(true)) { + + var boxSize = GetInlineBoxSize(content); + + if (Event.current.type == EventType.Repaint && boxSize.y > 0) { + var boxMargin = FusionEditorSkin.InlineBoxFullWidthStyle.margin; + + var boxRect = new Rect() { + x = boxMargin.left, + y = propertyRect.yMax - boxSize.y, + width = UnityInternal.EditorGUIUtility.contextWidth - boxMargin.horizontal, + height = boxSize.y, + }; + + using (new BackgroundColorScope(color)) { + FusionEditorSkin.InlineBoxFullWidthStyle.Draw(boxRect, false, false, false, false); + + var labelRect = boxRect; + labelRect = FusionEditorSkin.InlineBoxFullWidthStyle.padding.Remove(labelRect); + FusionEditorSkin.RichLabelStyle.Draw(labelRect, content, false, false, false, false); + + if (drawSelector) { + var selectorMargin = FusionEditorSkin.InlineSelectorStyle.margin; + + var selectorRect = new Rect() { + x = selectorMargin.left, + y = propertyRect.y - selectorMargin.top, + width = propertyRect.x - selectorMargin.horizontal, + height = propertyRect.height - boxSize.y - selectorMargin.bottom, + }; + + if (hasFoldout) { + selectorRect.width -= 20.0f; + } + + FusionEditorSkin.InlineSelectorStyle.Draw(selectorRect, false, false, false, false); + } + } + } + + propertyRect.height -= boxSize.y; + return propertyRect; + } + } + + + internal static void DrawScriptHeaderBackground(Rect position, Color color) { + if (Event.current.type != EventType.Repaint) { + return; + } + + var style = FusionEditorSkin.ScriptHeaderBackgroundStyle; + var boxMargin = style.margin; + + var boxRect = new Rect() { + x = boxMargin.left, + y = position.y - boxMargin.top, + width = UnityInternal.EditorGUIUtility.contextWidth - boxMargin.horizontal, + height = position.height + boxMargin.bottom, + }; + + using (new BackgroundColorScope(color)) { + style.Draw(boxRect, false, false, false, false); + } + } + + internal static void DrawScriptHeaderIcon(Rect position) { + if (Event.current.type != EventType.Repaint) { + return; + } + + var style = FusionEditorSkin.ScriptHeaderIconStyle; + var boxMargin = style.margin; + var boxRect = boxMargin.Remove(position); + + style.Draw(boxRect, false, false, false, false); + } + + internal static bool InjectScriptHeaderDrawer(Editor editor) => InjectScriptHeaderDrawer(editor, out _); + internal static bool InjectScriptHeaderDrawer(Editor editor, out ScriptFieldDrawer drawer) => InjectScriptHeaderDrawer(editor.serializedObject, out drawer); + internal static bool InjectScriptHeaderDrawer(SerializedObject serializedObject) => InjectScriptHeaderDrawer(serializedObject, out _); + + internal static bool InjectScriptHeaderDrawer(SerializedObject serializedObject, out ScriptFieldDrawer drawer) { + var sp = serializedObject.FindPropertyOrThrow(ScriptPropertyName); + var rootType = serializedObject.targetObject.GetType(); + + var injected = TryInjectDrawer(sp, null, () => null, () => new ScriptFieldDrawer(), out drawer); + if (drawer.attribute == null) { + UnityInternal.PropertyDrawer.SetAttribute(drawer, rootType.GetCustomAttributes(true).SingleOrDefault() ?? new ScriptHelpAttribute()); + } + + return injected; + } + + internal static void SetScriptFieldHidden(Editor editor, bool hidden) { + var sp = editor.serializedObject.FindPropertyOrThrow(ScriptPropertyName); + TryInjectDrawer(sp, null, () => null, () => new ScriptFieldDrawer(), out var drawer); + drawer.ForceHide = hidden; + } + + internal static Rect LayoutHelpPrefix(Editor editor, SerializedProperty property) { + var fieldInfo = UnityInternal.ScriptAttributeUtility.GetFieldInfoFromProperty(property, out _); + if (fieldInfo == null) { + return EditorGUILayout.GetControlRect(true); + } + + var help = FusionCodeDoc.FindEntry(fieldInfo); + return LayoutHelpPrefix(editor, property.propertyPath, help); + } + + internal static Rect LayoutHelpPrefix(ScriptableObject editor, MemberInfo memberInfo) { + var help = FusionCodeDoc.FindEntry(memberInfo); + return LayoutHelpPrefix(editor, memberInfo.Name, help); + } + + internal static Rect LayoutHelpPrefix(ScriptableObject editor, string path, GUIContent help) { + var rect = EditorGUILayout.GetControlRect(true); + + if (help == null) { + return rect; + } + + var buttonRect = GetInlineHelpButtonRect(rect, false); + var wasExpanded = IsHelpExpanded(editor, path); + + if (wasExpanded) { + var helpSize = GetInlineBoxSize(help); + var r = EditorGUILayout.GetControlRect(false, helpSize.y); + r.y = rect.y; + r.height += rect.height; + DrawInlineBoxUnderProperty(help, r, FusionEditorSkin.HelpInlineBoxColor, true); + } + + if (DrawInlineHelpButton(buttonRect, wasExpanded, doButton: true, doIcon: true)) { + SetHelpExpanded(editor, path, !wasExpanded); + } + + return rect; + } + + private static void AddDrawer(SerializedProperty property, PropertyDrawer drawer) { + var handler = UnityInternal.ScriptAttributeUtility.GetHandler(property); + + if (handler.m_PropertyDrawers == null) { + handler.m_PropertyDrawers = new List(); + } + + InsertPropertyDrawerByAttributeOrder(handler.m_PropertyDrawers, drawer); + } + + private static bool TryInjectDrawer(SerializedProperty property, FieldInfo field, Func attributeFactory, Func drawerFactory, out DrawerType drawer) + where DrawerType : PropertyDrawer { + + var handler = UnityInternal.ScriptAttributeUtility.GetHandler(property); + + drawer = GetPropertyDrawer(handler.m_PropertyDrawers); + if (drawer != null) { + return false; + } + + if (handler.Equals(UnityInternal.ScriptAttributeUtility.sharedNullHandler)) { + // need to add one? + handler = UnityInternal.PropertyHandler.New(); + UnityInternal.ScriptAttributeUtility.propertyHandlerCache.SetHandler(property, handler); + } + + var attribute = attributeFactory(); + + drawer = drawerFactory(); + FusionEditorLog.Assert(drawer != null, "drawer != null"); + UnityInternal.PropertyDrawer.SetAttribute(drawer, attribute); + UnityInternal.PropertyDrawer.SetFieldInfo(drawer, field); + + AddDrawer(property, drawer); + + return true; + } + + internal static bool IsHelpExpanded(object id, int pathHash) { + return s_expandedHelp == (id, default, pathHash); + } + + internal static bool IsHelpExpanded(object id, string path) { + return s_expandedHelp == (id, path, default); + } + + internal static void SetHelpExpanded(object id, string path, bool value) { + if (value) { + s_expandedHelp = (id, path, default); + } else { + s_expandedHelp = default; + } + } + + internal static void SetHelpExpanded(object id, int pathHash, bool value) { + if (value) { + s_expandedHelp = (id, default, pathHash); + } else { + s_expandedHelp = default; + } + } + + private static bool HasPropertyDrawer(IEnumerable orderedDrawers) where T : PropertyDrawer { + return orderedDrawers?.Any(x => x is T) ?? false; + } + + private static T GetPropertyDrawer(IEnumerable orderedDrawers) where T : PropertyDrawer { + return orderedDrawers?.OfType().FirstOrDefault(); + } + + internal static int InsertPropertyDrawerByAttributeOrder(List orderedDrawers, T drawer) where T : PropertyDrawer { + if (orderedDrawers == null) { + throw new ArgumentNullException(nameof(orderedDrawers)); + } + + if (drawer == null) { + throw new ArgumentNullException(nameof(drawer)); + } + + var index = orderedDrawers.BinarySearch(drawer, PropertyDrawerOrderComparer.Instance); + if (index < 0) { + index = ~index; + } + + orderedDrawers.Insert(index, drawer); + return index; + } + + internal static class InlineHelpStyle { + public const float MarginOuter = 16.0f; + public static GUIContent HideInlineContent = new("", "Hide"); + public static GUIContent ShowInlineContent = new("", ""); + } + + internal static class LazyAuto { + public static LazyAuto Create(Func valueFactory) { + return new LazyAuto(valueFactory); + } + } + + internal class LazyAuto : Lazy { + public LazyAuto(Func valueFactory) : base(valueFactory) { + } + + public static implicit operator T(LazyAuto lazy) { + return lazy.Value; + } + } + + + private class PropertyDrawerOrderComparer : IComparer { + public static readonly PropertyDrawerOrderComparer Instance = new(); + + public int Compare(PropertyDrawer x, PropertyDrawer y) { + var ox = x.attribute?.order ?? int.MaxValue; + var oy = y.attribute?.order ?? int.MaxValue; + return ox - oy; + } + } + } +} + +#endregion + + +#region FusionEditorGUI.Odin.cs + +namespace Fusion.Editor { + using System; + using UnityEditor; + using UnityEngine; +#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED + using Sirenix.Utilities.Editor; + using Sirenix.OdinInspector.Editor; + using Sirenix.Utilities; +#endif + + static partial class FusionEditorGUI { + internal static T IfOdin(T ifOdin, T ifNotOdin) { +#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED + return ifOdin; +#else + return ifNotOdin; +#endif + } + + internal static UnityEngine.Object ForwardObjectField(Rect position, UnityEngine.Object value, Type objectType, bool allowSceneObjects) { +#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED + return SirenixEditorFields.UnityObjectField(position, value, objectType, allowSceneObjects); +#else + return EditorGUI.ObjectField(position, value, objectType, allowSceneObjects); +#endif + } + + internal static UnityEngine.Object ForwardObjectField(Rect position, GUIContent label, UnityEngine.Object value, Type objectType, bool allowSceneObjects) { +#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED + return SirenixEditorFields.UnityObjectField(position, label, value, objectType, allowSceneObjects); +#else + return EditorGUI.ObjectField(position, label, value, objectType, allowSceneObjects); +#endif + } + + + internal static bool ForwardPropertyField(Rect position, SerializedProperty property, GUIContent label, bool includeChildren, bool lastDrawer = true) { +#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED + if (lastDrawer) { + switch (property.propertyType) { + case SerializedPropertyType.ObjectReference: { + EditorGUI.BeginChangeCheck(); + UnityInternal.ScriptAttributeUtility.GetFieldInfoFromProperty(property, out var fieldType); + var value = SirenixEditorFields.UnityObjectField(position, label, property.objectReferenceValue, fieldType ?? typeof(UnityEngine.Object), true); + if (EditorGUI.EndChangeCheck()) { + property.objectReferenceValue = value; + } + return false; + } + + case SerializedPropertyType.Integer: { + EditorGUI.BeginChangeCheck(); + var value = SirenixEditorFields.IntField(position, label, property.intValue); + if (EditorGUI.EndChangeCheck()) { + property.intValue = value; + } + return false; + } + + case SerializedPropertyType.Float: { + EditorGUI.BeginChangeCheck(); + var value = SirenixEditorFields.FloatField(position, label, property.floatValue); + if (EditorGUI.EndChangeCheck()) { + property.floatValue = value; + } + return false; + } + + case SerializedPropertyType.Color: { + EditorGUI.BeginChangeCheck(); + var value = SirenixEditorFields.ColorField(position, label, property.colorValue); + if (EditorGUI.EndChangeCheck()) { + property.colorValue = value; + } + return false; + } + + case SerializedPropertyType.Vector2: { + EditorGUI.BeginChangeCheck(); + var value = SirenixEditorFields.Vector2Field(position, label, property.vector2Value); + if (EditorGUI.EndChangeCheck()) { + property.vector2Value = value; + } + return false; + } + + case SerializedPropertyType.Vector3: { + EditorGUI.BeginChangeCheck(); + var value = SirenixEditorFields.Vector3Field(position, label, property.vector3Value); + if (EditorGUI.EndChangeCheck()) { + property.vector3Value = value; + } + return false; + } + + case SerializedPropertyType.Vector4: { + EditorGUI.BeginChangeCheck(); + var value = SirenixEditorFields.Vector4Field(position, label, property.vector4Value); + if (EditorGUI.EndChangeCheck()) { + property.vector4Value = value; + } + return false; + } + + case SerializedPropertyType.Quaternion: { + EditorGUI.BeginChangeCheck(); + var value = SirenixEditorFields.RotationField(position, label, property.quaternionValue, GlobalConfig.Instance.QuaternionDrawMode); + if (EditorGUI.EndChangeCheck()) { + property.quaternionValue = value; + } + return false; + } + + case SerializedPropertyType.String: { + EditorGUI.BeginChangeCheck(); + var value = SirenixEditorFields.TextField(position, label, property.stringValue); + if (EditorGUI.EndChangeCheck()) { + property.stringValue = value; + } + return false; + } + + case SerializedPropertyType.Enum: { + UnityInternal.ScriptAttributeUtility.GetFieldInfoFromProperty(property, out var type); + if (type != null && type.IsEnum) { + EditorGUI.BeginChangeCheck(); + bool flags = type.GetCustomAttributes(typeof(FlagsAttribute), false).Length > 0; + Enum value = SirenixEditorFields.EnumDropdown(position, label, (Enum)Enum.ToObject(type, property.intValue)); + if (EditorGUI.EndChangeCheck()) { + property.intValue = Convert.ToInt32(Convert.ChangeType(value, Enum.GetUnderlyingType(type))); + } + return false; + } + + break; + } + + default: + break; + } + } +#endif + if (lastDrawer && !includeChildren) { + return UnityInternal.EditorGUI.DefaultPropertyField(position, property, label); + } + + return EditorGUI.PropertyField(position, property, label, includeChildren); + } + } +} + +#endregion + + +#region FusionEditorGUI.Scopes.cs + +namespace Fusion.Editor { + using System; + using UnityEditor; + using UnityEngine; + + static partial class FusionEditorGUI { + + public sealed class CustomEditorScope : IDisposable { + + private SerializedObject serializedObject; + public bool HadChanges { get; private set; } + + public CustomEditorScope(SerializedObject so) { + serializedObject = so; + EditorGUI.BeginChangeCheck(); + so.UpdateIfRequiredOrScript(); + ScriptPropertyField(so); + } + + public void Dispose() { + HadChanges = EditorGUI.EndChangeCheck(); + serializedObject.ApplyModifiedProperties(); + } + } + + public struct EnabledScope: IDisposable { + private readonly bool value; + + public EnabledScope(bool enabled) { + value = GUI.enabled; + GUI.enabled = enabled; + } + + public void Dispose() { + GUI.enabled = value; + } + } + + public readonly struct BackgroundColorScope : IDisposable { + private readonly Color value; + + public BackgroundColorScope(Color color) { + value = GUI.backgroundColor; + GUI.backgroundColor = color; + } + + public void Dispose() { + GUI.backgroundColor = value; + } + } + + public struct ColorScope: IDisposable { + private readonly Color value; + + public ColorScope(Color color) { + value = GUI.color; + GUI.color = color; + } + + public void Dispose() { + GUI.color = value; + } + } + + public struct ContentColorScope: IDisposable { + private readonly Color value; + + public ContentColorScope(Color color) { + value = GUI.contentColor; + GUI.contentColor = color; + } + + public void Dispose() { + GUI.contentColor = value; + } + } + + public struct FieldWidthScope: IDisposable { + private readonly float value; + + public FieldWidthScope(float fieldWidth) { + value = EditorGUIUtility.fieldWidth; + EditorGUIUtility.fieldWidth = fieldWidth; + } + + public void Dispose() { + EditorGUIUtility.fieldWidth = value; + } + } + + public struct HierarchyModeScope: IDisposable { + private readonly bool value; + + public HierarchyModeScope(bool value) { + this.value = EditorGUIUtility.hierarchyMode; + EditorGUIUtility.hierarchyMode = value; + } + + public void Dispose() { + EditorGUIUtility.hierarchyMode = value; + } + } + + public struct IndentLevelScope: IDisposable { + private readonly int value; + + public IndentLevelScope(int indentLevel) { + value = EditorGUI.indentLevel; + EditorGUI.indentLevel = indentLevel; + } + + public void Dispose() { + EditorGUI.indentLevel = value; + } + } + + public struct LabelWidthScope: IDisposable { + private readonly float value; + + public LabelWidthScope(float labelWidth) { + value = EditorGUIUtility.labelWidth; + EditorGUIUtility.labelWidth = labelWidth; + } + + public void Dispose() { + EditorGUIUtility.labelWidth = value; + } + } + + public struct ShowMixedValueScope: IDisposable { + private readonly bool value; + + public ShowMixedValueScope(bool show) { + value = EditorGUI.showMixedValue; + EditorGUI.showMixedValue = show; + } + + public void Dispose() { + EditorGUI.showMixedValue = value; + } + } + + public struct DisabledGroupScope : IDisposable { + public DisabledGroupScope(bool disabled) { + EditorGUI.BeginDisabledGroup(disabled); + } + + public void Dispose() { + EditorGUI.EndDisabledGroup(); + } + } + + public struct PropertyScope : IDisposable { + public PropertyScope(Rect position, GUIContent label, SerializedProperty property) { + EditorGUI.BeginProperty(position, label, property); + } + + public void Dispose() { + EditorGUI.EndProperty(); + } + } + + public readonly struct PropertyScopeWithPrefixLabel : IDisposable { + private readonly int indent; + + public PropertyScopeWithPrefixLabel(Rect position, GUIContent label, SerializedProperty property, out Rect indentedPosition) { + EditorGUI.BeginProperty(position, label, property); + indentedPosition = EditorGUI.PrefixLabel(position, label); + indent = EditorGUI.indentLevel; + EditorGUI.indentLevel = 0; + } + + public void Dispose() { + EditorGUI.indentLevel = indent; + EditorGUI.EndProperty(); + } + } + + public readonly struct BoxScope: IDisposable { + + private readonly int _indent; + + /// + ///if fields include inline help (?) buttons, use indent : 1 + /// + public BoxScope(string message, int indent = 0, float space = 0.0f) { + EditorGUILayout.BeginVertical(FusionEditorSkin.OutlineBoxStyle); + + if (!string.IsNullOrEmpty(message)) { + EditorGUILayout.LabelField(message, EditorStyles.boldLabel); + } + + if (space > 0.0f) { + GUILayout.Space(space); + } + + _indent = EditorGUI.indentLevel; + if (indent != 0) { + EditorGUI.indentLevel += indent; + } + } + + public void Dispose() { + EditorGUI.indentLevel = _indent; + EditorGUILayout.EndVertical(); + } + } + public struct WarningScope: IDisposable { + + bool _isValid; + + public WarningScope(string message, float space = 0.0f) { + + var backgroundColor = GUI.backgroundColor; + + GUI.backgroundColor = FusionEditorSkin.WarningInlineBoxColor; + EditorGUILayout.BeginVertical(FusionEditorSkin.InlineBoxFullWidthScopeStyle); + GUI.backgroundColor = backgroundColor; + + EditorGUILayout.LabelField(new GUIContent(message, FusionEditorSkin.WarningIcon), FusionEditorSkin.RichLabelStyle); + if (space > 0.0f) { + GUILayout.Space(space); + } + + _isValid = true; + } + + public void Dispose() { + if (_isValid) { + EditorGUILayout.EndVertical(); + } + } + } + + public struct ErrorScope : IDisposable { + + bool _isValid; + + public ErrorScope(string message, float space = 0.0f) { + var backgroundColor = GUI.backgroundColor; + + GUI.backgroundColor = FusionEditorSkin.ErrorInlineBoxColor; + EditorGUILayout.BeginVertical(FusionEditorSkin.InlineBoxFullWidthScopeStyle); + GUI.backgroundColor = backgroundColor; + + EditorGUILayout.LabelField(new GUIContent(message, FusionEditorSkin.ErrorIcon), FusionEditorSkin.RichLabelStyle); + if (space > 0.0f) { + GUILayout.Space(space); + } + + _isValid = true; + } + + public void Dispose() { + if (_isValid) { + EditorGUILayout.EndVertical(); + } + } + } + + public readonly struct GUIContentScope : IDisposable { + + private readonly string _text; + private readonly string _tooltip; + private readonly GUIContent _content; + + public GUIContentScope(GUIContent content) { + _content = content; + _text = content?.text; + _tooltip = content?.tooltip; + } + + public void Dispose() { + if (_content != null) { + _content.text = _text; + _content.tooltip = _tooltip; + } + } + } + } +} + +#endregion + + +#region FusionEditorGUI.Utils.cs + +namespace Fusion.Editor { + using System; + using System.Collections.Generic; + using System.Linq; + using UnityEditor; + using UnityEngine; + + static partial class FusionEditorGUI { + /// + /// The name of the script property in Unity objects + /// + public const string ScriptPropertyName = "m_Script"; + + private const int IconHeight = 14; + + /// + /// GUIContent with a single whitespace + /// + public static readonly GUIContent WhitespaceContent = new(" "); + + internal static Color PrefebOverridenColor => new(1f / 255f, 153f / 255f, 235f / 255f, 0.75f); + + /// + /// Width of the foldout arrow + /// + public static float FoldoutWidth => 16.0f; + + internal static Rect Decorate(Rect rect, string tooltip, MessageType messageType, bool hasLabel = false, bool drawBorder = true, bool drawButton = true, bool rightAligned = false) { + if (hasLabel) { + rect.xMin += EditorGUIUtility.labelWidth; + } + + var content = EditorGUIUtility.TrTextContentWithIcon(string.Empty, tooltip, messageType); + var iconRect = rect; + iconRect.width = Mathf.Min(16, rect.width); + + if (rightAligned) { + iconRect.x = rect.xMax - iconRect.width; + } else { + iconRect.xMin -= iconRect.width; + } + + iconRect.y += (iconRect.height - IconHeight) / 2; + iconRect.height = IconHeight; + + if (drawButton) { + using (new EnabledScope(true)) { + GUI.Label(iconRect, content, GUIStyle.none); + } + } + + //GUI.Label(iconRect, content, new GUIStyle()); + + if (drawBorder) { + Color borderColor; + switch (messageType) { + case MessageType.Warning: + borderColor = new Color(1.0f, 0.5f, 0.0f); + break; + case MessageType.Error: + borderColor = new Color(1.0f, 0.0f, 0.0f); + break; + default: + borderColor = new Color(1f, 1f, 1f, .0f); + break; + } + + GUI.DrawTexture(rect, Texture2D.whiteTexture, ScaleMode.StretchToFill, false, 0, borderColor, 1.0f, 1.0f); + } + + return iconRect; + } + + internal static void AppendTooltip(string tooltip, ref GUIContent label) { + if (!string.IsNullOrEmpty(tooltip)) { + label = new GUIContent(label); + if (string.IsNullOrEmpty(label.tooltip)) { + label.tooltip = tooltip; + } else { + label.tooltip += "\n\n" + tooltip; + } + } + } + + internal static void ScriptPropertyField(Editor editor) { + ScriptPropertyField(editor.serializedObject); + } + + internal static void ScriptPropertyField(SerializedObject obj) { + var scriptProperty = obj.FindProperty(ScriptPropertyName); + if (scriptProperty != null) { + using (new EditorGUI.DisabledScope(true)) { + EditorGUILayout.PropertyField(scriptProperty); + } + } + } + + internal static void Overlay(Rect position, string label) { + GUI.Label(position, label, FusionEditorSkin.OverlayLabelStyle); + } + + internal static void Overlay(Rect position, GUIContent label) { + GUI.Label(position, label, FusionEditorSkin.OverlayLabelStyle); + } + + internal static float GetLinesHeight(int count) { + return count * (EditorGUIUtility.singleLineHeight) + (count - 1) * EditorGUIUtility.standardVerticalSpacing; + } + + internal static float GetLinesHeightWithNarrowModeSupport(int count) { + if (!EditorGUIUtility.wideMode) { + count++; + } + return count * (EditorGUIUtility.singleLineHeight) + (count - 1) * EditorGUIUtility.standardVerticalSpacing; + } + + internal static System.Type GetDrawerTypeIncludingWorkarounds(System.Attribute attribute) { + var drawerType = UnityInternal.ScriptAttributeUtility.GetDrawerTypeForType(attribute.GetType(), false); +#if !UNITY_6000_0_OR_NEWER + if (drawerType == typeof(PropertyDrawerForArrayWorkaround)) { + drawerType = PropertyDrawerForArrayWorkaround.GetDrawerType(attribute.GetType()); + } +#endif + return drawerType; + } + + internal static void DisplayTypePickerMenu(Rect position, Type[] baseTypes, Action callback, Func filter, string noneOptionLabel = "[None]", Type selectedType = null, FusionEditorGUIDisplayTypePickerMenuFlags flags = FusionEditorGUIDisplayTypePickerMenuFlags.Default) { + + var types = new List(); + + foreach (var baseType in baseTypes) { + types.AddRange(TypeCache.GetTypesDerivedFrom(baseType).Where(filter)); + if (filter(baseType)) { + types.Add(baseType); + } + } + + if (baseTypes.Length > 1) { + types = types.Distinct().ToList(); + } + + types.Sort((a, b) => string.CompareOrdinal(a.FullName, b.FullName)); + + + List menuOptions = new List(); + var actualTypes = new Dictionary(); + + menuOptions.Add(new GUIContent(noneOptionLabel)); + actualTypes.Add(noneOptionLabel, null); + + int selectedIndex = -1; + + foreach (var ns in types.GroupBy(x => string.IsNullOrEmpty(x.Namespace) ? "[Global Namespace]" : x.Namespace)) { + foreach (var t in ns) { + var typeName = t.FullName; + if (string.IsNullOrEmpty(typeName)) { + continue; + } + + if (!string.IsNullOrEmpty(t.Namespace)) { + if ((flags & FusionEditorGUIDisplayTypePickerMenuFlags.ShowFullName) == 0) { + typeName = typeName.Substring(t.Namespace.Length + 1); + } + } + + string path; + if ((flags & FusionEditorGUIDisplayTypePickerMenuFlags.GroupByNamespace) != 0) { + path = ns.Key + "/" + typeName; + } else { + path = typeName; + } + + if (actualTypes.ContainsKey(path)) { + continue; + } + + menuOptions.Add(new GUIContent(path)); + actualTypes.Add(path, t); + + if (selectedType == t) { + selectedIndex = menuOptions.Count - 1; + } + } + } + + EditorUtility.DisplayCustomMenu(position, menuOptions.ToArray(), selectedIndex, (userData, options, selected) => { + var path = options[selected]; + var newType = ((Dictionary)userData)[path]; + callback(newType); + }, actualTypes); + } + + + internal static void DisplayTypePickerMenu(Rect position, Type[] baseTypes, Action callback, string noneOptionLabel = "[None]", Type selectedType = null, bool enableAbstract = false, bool enableGenericTypeDefinitions = false, FusionEditorGUIDisplayTypePickerMenuFlags flags = FusionEditorGUIDisplayTypePickerMenuFlags.Default) { + DisplayTypePickerMenu(position, baseTypes, callback, + x => (enableAbstract || !x.IsAbstract) && (enableGenericTypeDefinitions || !x.IsGenericTypeDefinition), + noneOptionLabel: noneOptionLabel, + flags: flags, + selectedType: selectedType); + } + + internal static void DisplayTypePickerMenu(Rect position, Type baseType, Action callback, string noneOptionLabel = "[None]", Type selectedType = null, bool enableAbstract = false, bool enableGenericTypeDefinitions = false, FusionEditorGUIDisplayTypePickerMenuFlags flags = FusionEditorGUIDisplayTypePickerMenuFlags.Default) { + DisplayTypePickerMenu(position, new [] { baseType }, callback, + x => (enableAbstract || !x.IsAbstract) && (enableGenericTypeDefinitions || !x.IsGenericTypeDefinition), + noneOptionLabel: noneOptionLabel, + flags: flags, + selectedType: selectedType); + } + + internal static float GetPropertyHeight(SerializedProperty property) { + return EditorGUI.GetPropertyHeight(property, WhitespaceContent, property.isExpanded || property.IsArrayProperty()); + } + } + + /// + /// Flags for the method + /// and its overloads. + /// + [Flags] + public enum FusionEditorGUIDisplayTypePickerMenuFlags { + /// + /// No special flags + /// + None = 0, + /// + /// Group types by their namespace + /// + GroupByNamespace = 1 << 1, + /// + /// Show the full name of the type including the namespace + /// + ShowFullName = 1 << 0, + /// + /// The default flags + /// + Default = GroupByNamespace, + } +} + +#endregion + + +#region FusionEditorUtility.cs + +namespace Fusion.Editor { + using UnityEditor; + + partial class FusionEditorUtility { + public static void DelayCall(EditorApplication.CallbackFunction callback) { + FusionEditorLog.Assert(callback.Target == null, "DelayCall callback needs to stateless"); + EditorApplication.delayCall -= callback; + EditorApplication.delayCall += callback; + } + } +} + +#endregion + + +#region FusionGlobalScriptableObjectEditorAttribute.cs + +namespace Fusion.Editor { + using System; + using UnityEditor; + + class FusionGlobalScriptableObjectEditorAttribute : FusionGlobalScriptableObjectSourceAttribute { + public FusionGlobalScriptableObjectEditorAttribute(Type objectType) : base(objectType) { + } + + public override FusionGlobalScriptableObjectLoadResult Load(Type type) { + var defaultAssetPath = FusionGlobalScriptableObjectUtils.FindDefaultAssetPath(type, fallbackToSearchWithoutLabel: true); + if (string.IsNullOrEmpty(defaultAssetPath)) { + return default; + } + + var result = (FusionGlobalScriptableObject)AssetDatabase.LoadAssetAtPath(defaultAssetPath, type); + FusionEditorLog.Assert(result); + return result; + } + } +} + +#endregion + + +#region FusionGlobalScriptableObjectUtils.cs + +namespace Fusion.Editor { + using System; + using System.Collections.Generic; + using System.IO; + using System.Reflection; + using UnityEditor; + using UnityEngine; + + /// + /// Utility methods for working with . + /// + public static class FusionGlobalScriptableObjectUtils { + /// + /// The label that is assigned to global assets. + /// + public const string GlobalAssetLabel = "FusionDefaultGlobal"; + + /// + /// Calls on the object. + /// + /// + public static void SetDirty(this FusionGlobalScriptableObject obj) { + EditorUtility.SetDirty(obj); + } + + /// + /// Locates the asset that is going to be used as a global asset for the given type, that is + /// an asset marked with the label. If there are multiple such assets, + /// exception is thrown. If there are no such assets, empty string is returned. + /// + public static string GetGlobalAssetPath() where T : FusionGlobalScriptableObject { + return FindDefaultAssetPath(typeof(T), fallbackToSearchWithoutLabel: false); + } + + /// + /// A wrapper around that returns a value indicating if + /// it was able to find the asset. + /// + /// + /// + /// if the asset was found + public static bool TryGetGlobalAssetPath(out string path) where T : FusionGlobalScriptableObject { + path = FindDefaultAssetPath(typeof(T), fallbackToSearchWithoutLabel: false); + return !string.IsNullOrEmpty(path); + } + + private static FusionGlobalScriptableObjectAttribute GetAttributeOrThrow(Type type) { + var attribute = type.GetCustomAttribute(); + if (attribute == null) { + throw new InvalidOperationException($"Type {type.FullName} needs to be decorated with {nameof(FusionGlobalScriptableObjectAttribute)}"); + } + + return attribute; + } + + /// + /// If the global asset does not exist, creates it based on the type's . + /// + /// + /// If the asset already existed. + public static bool EnsureAssetExists() where T : FusionGlobalScriptableObject { + var defaultAssetPath = FindDefaultAssetPath(typeof(T), fallbackToSearchWithoutLabel: true); + if (!string.IsNullOrEmpty(defaultAssetPath)) { + // already exists + return false; + } + + // need to create a new asset + CreateDefaultAsset(typeof(T)); + return true; + } + + private static FusionGlobalScriptableObject CreateDefaultAsset(Type type) { + var attribute = GetAttributeOrThrow(type); + + var directoryPath = Path.GetDirectoryName(attribute.DefaultPath); + if (!string.IsNullOrEmpty(directoryPath) && !Directory.Exists(directoryPath)) { + Directory.CreateDirectory(directoryPath); + AssetDatabase.Refresh(); + } + + if (File.Exists(attribute.DefaultPath)) { + throw new InvalidOperationException($"Asset file already exists at '{attribute.DefaultPath}'"); + } + + // is this a regular asset? + if (attribute.DefaultPath.EndsWith(".asset", StringComparison.OrdinalIgnoreCase)) { + var instance = (FusionGlobalScriptableObject)ScriptableObject.CreateInstance(type); + + AssetDatabase.CreateAsset(instance, attribute.DefaultPath); + AssetDatabase.SaveAssets(); + + SetGlobal(instance); + + EditorUtility.SetDirty(instance); + AssetDatabase.SaveAssets(); + AssetDatabase.Refresh(); + + FusionEditorLog.TraceImport($"Created new global {type.Name} instance at {attribute.DefaultPath}"); + + return instance; + } else { + string defaultContents = null; + if (!string.IsNullOrEmpty(attribute.DefaultContentsGeneratorMethod)) { + var method = type.GetMethod(attribute.DefaultContentsGeneratorMethod, BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public); + if (method == null) { + throw new InvalidOperationException($"Generator method '{attribute.DefaultContentsGeneratorMethod}' not found on type {type.FullName}"); + } + defaultContents = (string)method.Invoke(null, null); + } + + if (defaultContents == null) { + defaultContents = attribute.DefaultContents; + } + + File.WriteAllText(attribute.DefaultPath, defaultContents ?? string.Empty); + AssetDatabase.ImportAsset(attribute.DefaultPath, ImportAssetOptions.ForceUpdate); + + var instance = (FusionGlobalScriptableObject)AssetDatabase.LoadAssetAtPath(attribute.DefaultPath, type); + if (!instance) { + throw new InvalidOperationException($"Failed to load a newly created asset at '{attribute.DefaultPath}'"); + } + + SetGlobal(instance); + FusionEditorLog.TraceImport($"Created new global {type.Name} instance at {attribute.DefaultPath}"); + return instance; + } + } + + private static bool IsDefault(this FusionGlobalScriptableObject obj) { + return Array.IndexOf(AssetDatabase.GetLabels(obj), GlobalAssetLabel) >= 0; + } + + private static bool SetGlobal(FusionGlobalScriptableObject obj) { + var labels = AssetDatabase.GetLabels(obj); + if (Array.IndexOf(labels, GlobalAssetLabel) >= 0) { + return false; + } + + Array.Resize(ref labels, labels.Length + 1); + labels[^1] = GlobalAssetLabel; + AssetDatabase.SetLabels(obj, labels); + return true; + } + + private static List<(FusionGlobalScriptableObject, bool)> s_cache; + + internal static void CreateFindDefaultAssetPathCache() { + s_cache = new List<(FusionGlobalScriptableObject, bool)>(); + foreach (var it in AssetDatabaseUtils.IterateAssets()) { + var asset = it.pptrValue as FusionGlobalScriptableObject; + if (asset == null) { + continue; + } + + var hasLabel = AssetDatabaseUtils.HasLabel(asset, GlobalAssetLabel); + s_cache.Add((asset, hasLabel)); + } + } + + internal static void ClearFindDefaultAssetPathCache() { + s_cache = null; + } + + internal static string FindDefaultAssetPath(Type type, bool fallbackToSearchWithoutLabel = false) { + var list = new List(); + + if (s_cache != null) { + foreach (var (asset, hasLabel) in s_cache) { + if (!type.IsInstanceOfType(asset)) { + continue; + } + + if (!hasLabel && !fallbackToSearchWithoutLabel) { + continue; + } + + var assetPath = AssetDatabase.GetAssetPath(asset); + Assert.Check(!string.IsNullOrEmpty(assetPath)); + list.Add(assetPath); + } + } else { + var enumerator = AssetDatabaseUtils.IterateAssets(type: type, label: fallbackToSearchWithoutLabel ? null : GlobalAssetLabel); + foreach (var asset in enumerator) { + var path = AssetDatabase.GUIDToAssetPath(asset.guid); + FusionEditorLog.Assert(!string.IsNullOrEmpty(path)); + list.Add(path); + } + } + + if (list.Count == 0) { + return string.Empty; + } + + if (fallbackToSearchWithoutLabel) { + var found = list.FindIndex(x => AssetDatabaseUtils.HasLabel(x, GlobalAssetLabel)); + if (found >= 0) { + // carry on as if the search was without fallback in the first place + list.RemoveAll(x => !AssetDatabaseUtils.HasLabel(x, GlobalAssetLabel)); + fallbackToSearchWithoutLabel = false; + FusionEditorLog.Assert(list.Count >= 1); + } + } + + if (list.Count == 1) { + if (fallbackToSearchWithoutLabel) { + AssetDatabaseUtils.SetLabel(list[0], GlobalAssetLabel, true); + EditorUtility.SetDirty(AssetDatabase.LoadMainAssetAtPath(list[0])); + FusionEditorLog.Log($"Set '{list[0]}' as the default asset for '{type.Name}'"); + } + + return list[0]; + } + + if (fallbackToSearchWithoutLabel) { + throw new InvalidOperationException($"There are no assets of type '{type.Name}' with {GlobalAssetLabel}, but there are multiple candidates: '{string.Join("', '", list)}'. Assign label manually or remove all but one."); + } else { + throw new InvalidOperationException($"There are multiple assets of type '{type.Name}' marked as default: '{string.Join("', '", list)}'. Remove all labels but one."); + } + } + + /// + /// Attempts to import the global asset for the given type. + /// + /// + /// if the asset was found and reimported + public static bool TryImportGlobal() where T : FusionGlobalScriptableObject { + var globalPath = GetGlobalAssetPath(); + if (string.IsNullOrEmpty(globalPath)) { + return false; + } + AssetDatabase.ImportAsset(globalPath); + return true; + } + } +} + +#endregion + + +#region FusionGrid.cs + +namespace Fusion.Editor { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Linq.Expressions; + using UnityEditor; + using UnityEditor.IMGUI.Controls; + using UnityEngine; + using Object = UnityEngine.Object; + +#if UNITY_6000_2_OR_NEWER + using TreeViewState = UnityEditor.IMGUI.Controls.TreeViewState; + using TreeViewItem = UnityEditor.IMGUI.Controls.TreeViewItem; + using TreeView = UnityEditor.IMGUI.Controls.TreeView; +#endif + + [Serializable] + class FusionGridState : TreeViewState { + public MultiColumnHeaderState HeaderState; + public bool SyncSelection; + } + + class FusionGridItem : TreeViewItem { + public virtual Object TargetObject => null; + } + + abstract class FusionGrid : FusionGrid + where TItem : FusionGridItem { + } + + [Serializable] + abstract class FusionGrid + where TState : FusionGridState, new() + where TItem : FusionGridItem + { + [SerializeField] public bool HasValidState; + [SerializeField] public TState State; + [SerializeField] public float UpdatePeriod = 1.0f; + + class GUIState { + public InternalTreeView TreeView; + public MultiColumnHeader MultiColumnHeader; + public SearchField SearchField; + } + + [NonSerialized] private Lazy _gui; + [NonSerialized] private Lazy _columns; + [NonSerialized] private float _nextUpdateTime; + [NonSerialized] private int _lastContentHash; + + public virtual int GetContentHash() { + return 0; + } + + public FusionGrid() { + ResetColumns(); + ResetGUI(); + } + + void ResetColumns() { + _columns = new Lazy(() => { + var columns = CreateColumns().ToArray(); + for (int i = 0; i < columns.Length; ++i) { + ((MultiColumnHeaderState.Column)columns[i]).userData = i; + } + + return columns; + }); + } + + void ResetGUI() { + _gui = new Lazy(() => { + + var result = new GUIState(); + + result.MultiColumnHeader = new MultiColumnHeader(State.HeaderState); + result.MultiColumnHeader.sortingChanged += _ => result.TreeView.Reload(); + result.MultiColumnHeader.ResizeToFit(); + result.SearchField = new SearchField(); + result.SearchField.downOrUpArrowKeyPressed += () => result.TreeView.SetFocusAndEnsureSelectedItem(); + result.TreeView = new InternalTreeView(this, result.MultiColumnHeader); + + return result; + }); + } + + + public void OnInspectorUpdate() { + if (!HasValidState) { + return; + } + + if (!_gui.IsValueCreated) { + return; + } + + if (_nextUpdateTime > Time.realtimeSinceStartup) { + return; + } + + _nextUpdateTime = Time.realtimeSinceStartup + UpdatePeriod; + + var hash = GetContentHash(); + if (_lastContentHash == hash) { + return; + } + + _lastContentHash = hash; + _gui.Value.TreeView.Reload(); + } + + public void OnEnable() { + if (HasValidState) { + return; + } + + var visibleColumns = new List(); + int sortingColumn = -1; + + for (int i = 0; i < _columns.Value.Length; ++i) { + var column = _columns.Value[i]; + + if (sortingColumn < 0 && column.initiallySorted) { + sortingColumn = i; + column.sortedAscending = column.initiallySortedAscending; + } + + if (!column.initiallyVisible) { + continue; + } + + visibleColumns.Add(i); + } + + var headerState = new MultiColumnHeaderState(_columns.Value.Cast().ToArray()) { + visibleColumns = visibleColumns.ToArray(), + sortedColumnIndex = sortingColumn, + }; + + State = new TState() { HeaderState = headerState }; + HasValidState = true; + ResetGUI(); + } + + public void OnGUI(Rect rect) { + _gui.Value.TreeView.OnGUI(rect); + } + + public void DrawToolbarReloadButton() { + if (GUILayout.Button(new GUIContent(FusionEditorSkin.RefreshIcon, "Refresh"), EditorStyles.toolbarButton, GUILayout.ExpandWidth(false))) { + _gui.Value.TreeView.Reload(); + } + } + + public void DrawToolbarSyncSelectionButton() { + EditorGUI.BeginChangeCheck(); + State.SyncSelection = GUILayout.Toggle(State.SyncSelection, "Sync Selection", EditorStyles.toolbarButton); + if (EditorGUI.EndChangeCheck()) { + if (State.SyncSelection) { + _gui.Value.TreeView.SyncSelection(); + } + } + } + + public void DrawToolbarSearchField() { + _gui.Value.TreeView.searchString = _gui.Value.SearchField.OnToolbarGUI(_gui.Value.TreeView.searchString); + } + + public void DrawToolbarResetView() { + if (GUILayout.Button("Reset View", EditorStyles.toolbarButton, GUILayout.ExpandWidth(false))) { + HasValidState = false; + ResetColumns(); + } + } + + public void ResetTree() { + ResetGUI(); + } + + protected abstract IEnumerable CreateColumns(); + protected abstract IEnumerable CreateRows(); + + protected virtual GenericMenu CreateContextMenu(TItem item, TreeView treeView) { + return null; + } + + protected static Column MakeSimpleColumn(Expression> propertyExpression, Column column) { + + string propertyName; + if (propertyExpression.Body is MemberExpression memberExpression) { + propertyName = memberExpression.Member.Name; + } else { + throw new ArgumentException("Expression is not a member access expression."); + } + + var accessor = propertyExpression.Compile(); + Func toString = item => $"{accessor(item)}"; + + column.getSearchText ??= toString; + column.getComparer ??= order => (a, b) => EditorUtility.NaturalCompare(toString(a), toString(b)) * order; + column.cellGUI ??= (item, rect, selected, focused) => TreeView.DefaultGUI.Label(rect, toString(item), selected, focused); + if (string.IsNullOrEmpty(column.headerContent.text) && string.IsNullOrEmpty(column.headerContent.tooltip)) { + column.headerContent = new GUIContent(propertyName); + } + + return column; + } + + public class Column : MultiColumnHeaderState.Column { + public Func getSearchText; + public Func> getComparer; + public Action cellGUI; + public bool initiallyVisible = true; + public bool initiallySorted; + public bool initiallySortedAscending = true; + + // + // [Obsolete("Do not use", true)] + // public new int userData => throw new NotImplementedException(); + } + + class InternalTreeView : TreeView { + public InternalTreeView(FusionGrid grid, MultiColumnHeader header) : base(grid.State, header) { + Grid = grid; + showAlternatingRowBackgrounds = true; + this.Reload(); + } + + public new TState state => (TState)base.state; + + public FusionGrid Grid { get; } + + + protected override void SelectionChanged(IList selectedIds) { + base.SelectionChanged(selectedIds); + if (state.SyncSelection) { + SyncSelection(); + } + } + + protected override void SingleClickedItem(int id) { + if (state.SyncSelection) { + var item = (TItem)FindItem(id, rootItem); + var obj = item.TargetObject; + if (obj) { + EditorGUIUtility.PingObject(obj); + } + } + + base.SingleClickedItem(id); + } + + public void SyncSelection() { + List selection = new List(); + foreach (var id in this.state.selectedIDs) { + if (id == 0) { + continue; + } + var item = (TItem)FindItem(id, rootItem); + var obj = item.TargetObject; + if (obj) { + selection.Add(obj); + } + } + Selection.objects = selection.ToArray(); + } + + + private Column GetColumnForIndex(int index) { + var column = multiColumnHeader.GetColumn(index); + var ud = column.userData; + return Grid._columns.Value[ud]; + } + + protected override TreeViewItem BuildRoot() { + var allItems = new List(); + + var root = new TreeViewItem { + id = 0, + depth = -1, + displayName = "Root" + }; + + foreach (var row in Grid.CreateRows()) { + allItems.Add(row); + } + + SetupParentsAndChildrenFromDepths(root, allItems.Cast().ToList()); + return root; + } + + private class ComparisonComparer : IComparer { + public Comparison Comparison; + public int Compare(TItem x, TItem y) => Comparison(x, y); + } + + private Comparison GetComparision() { + if (multiColumnHeader.sortedColumnIndex < 0) { + return null; + } + var column = GetColumnForIndex(multiColumnHeader.sortedColumnIndex); + var isSortedAscending = multiColumnHeader.IsSortedAscending(multiColumnHeader.sortedColumnIndex); + return column.getComparer(isSortedAscending ? 1 : -1); + } + + protected override IList BuildRows(TreeViewItem root) { + var comparision = GetComparision(); + if (comparision == null) { + return base.BuildRows(root); + } + + // stable sort + return base.BuildRows(root).OrderBy(x => (TItem)x, new ComparisonComparer() { Comparison = comparision }).ToArray(); + } + + protected override void ContextClickedItem(int id) { + var item = (TItem)FindItem(id, rootItem); + if (item == null) { + return; + } + + var menu = Grid.CreateContextMenu(item, this); + if (menu != null) { + menu.ShowAsContext(); + } + } + + protected override void RowGUI(RowGUIArgs args) { + for (var i = 0; i < args.GetNumVisibleColumns(); ++i) { + var cellRect = args.GetCellRect(i); + CenterRectUsingSingleLineHeight(ref cellRect); + var item = (TItem)args.item; + var column = GetColumnForIndex(args.GetColumn(i)); + column.cellGUI?.Invoke(item, cellRect, args.selected, args.focused); + } + } + + protected override bool DoesItemMatchSearch(TreeViewItem item_, string search) { + var item = item_ as TItem; + if (item == null) { + return base.DoesItemMatchSearch(item_, search); + } + + var searchParts = (search ?? "").Split(' ', StringSplitOptions.RemoveEmptyEntries); + if (searchParts.Length == 0) { + return true; + } + + var columns = multiColumnHeader.state.columns; + + for (var i = 0; i < columns.Length; ++i) { + if (!multiColumnHeader.IsColumnVisible(i)) { + continue; + } + + + var column = GetColumnForIndex(i); + var text = column.getSearchText?.Invoke(item); + + if (text == null) { + continue; + } + + bool columnMatchesSearch = true; + foreach (var part in searchParts) { + if (!text.Contains(part, StringComparison.OrdinalIgnoreCase)) { + columnMatchesSearch = false; + break; + } + } + + if (columnMatchesSearch) { + return true; + } + } + + return false; + } + } + + class InternalTreeViewItem : TreeViewItem { + + } + } +} + +#endregion + + +#region FusionMonoBehaviourDefaultEditor.cs + +namespace Fusion.Editor { + using UnityEditor; + + [CustomEditor(typeof(FusionMonoBehaviour), true)] + [CanEditMultipleObjects] + internal class FusionMonoBehaviourDefaultEditor : FusionEditor { + } +} + +#endregion + + +#region FusionPropertyDrawerMetaAttribute.cs + +namespace Fusion.Editor { + using System; + + [AttributeUsage(AttributeTargets.Class)] + class FusionPropertyDrawerMetaAttribute : Attribute { + public bool HasFoldout { get; set; } + public bool HandlesUnits { get; set; } + } +} + +#endregion + + +#region FusionScriptableObjectDefaultEditor.cs + +namespace Fusion.Editor { + using UnityEditor; + + [CustomEditor(typeof(FusionScriptableObject), true)] + internal class FusionScriptableObjectDefaultEditor : FusionEditor { + } +} + +#endregion + + +#region RawDataDrawer.cs + +namespace Fusion.Editor { + using System; + using System.Collections.Generic; + using System.Text; + using UnityEditor; + using UnityEngine; + + struct RawDataDrawer { + private StringBuilder _builder; + private GUIContent _lastValue; + private int _lastHash; + + public void Clear() { + _builder?.Clear(); + _lastHash = 0; + _lastValue = GUIContent.none; + } + + public bool HasContent => _lastValue != null && _lastValue.text.Length > 0; + + public unsafe void Refresh(Span data, int maxLength = 2048, bool addSpaces = true) where T : unmanaged { + + int charactersPerElement = 2 * sizeof(T); + + int arrayHash = 0; + int effectiveArraySize; + { + int length = 0; + int i; + for (i = 0; i < data.Length && length < maxLength; ++i) { + arrayHash = arrayHash * 31 + data[i].GetHashCode(); + length += charactersPerElement; + if (addSpaces) { + length += 1; + } + } + + effectiveArraySize = i; + } + + if (_builder == null || arrayHash != _lastHash) { + var format = "{0:x" + charactersPerElement + "}" + (addSpaces ? " " : ""); + + _builder ??= new StringBuilder(); + _builder.Clear(); + + + for (int i = 0; i < effectiveArraySize; ++i) { + _builder.AppendFormat(format, data[i]); + } + + if (effectiveArraySize < data.Length) { + _builder.AppendLine("..."); + } + + _lastHash = arrayHash; + _lastValue = new GUIContent(_builder.ToString()); + } else { + Debug.Assert(_lastValue != null); + } + } + + public void Refresh(IList values, int maxLength = 2048) { + Assert.Check(values != null); + + const int charactersPerElement = 2; + int arraySize = values.Count; + int arrayHash = 0; + int effectiveArraySize; + { + int length = 0; + int i; + for (i = 0; i < arraySize && length < maxLength; ++i) { + arrayHash = arrayHash * 31 + values[i]; + length += charactersPerElement + 1; + } + + effectiveArraySize = i; + } + + if (_builder == null || arrayHash != _lastHash) { + var format = "{0:x" + charactersPerElement + "} "; + + _builder ??= new StringBuilder(); + _builder.Clear(); + + for (int i = 0; i < effectiveArraySize; ++i) { + _builder.AppendFormat(format, values[i]); + } + + if (effectiveArraySize < arraySize) { + _builder.AppendLine("..."); + } + + _lastHash = arrayHash; + _lastValue = new GUIContent(_builder.ToString()); + } else { + Debug.Assert(_lastValue != null); + } + } + + public void Refresh(SerializedProperty property, int maxLength = 2048) { + Assert.Check(property != null); + Assert.Check(property.isArray); + + int charactersPerElement; + switch (property.arrayElementType) { + case "long": + case "ulong": + charactersPerElement = 16; + break; + case "int": + case "uint": + charactersPerElement = 8; + break; + case "short": + case "ushort": + charactersPerElement = 4; + break; + case "sbyte": + case "byte": + charactersPerElement = 2; + break; + default: + throw new NotImplementedException(property.arrayElementType); + } + + int arrayHash = 0; + int effectiveArraySize; + { + int length = 0; + int i; + for (i = 0; i < property.arraySize && length < maxLength; ++i) { + arrayHash = arrayHash * 31 + property.GetArrayElementAtIndex(i).longValue.GetHashCode(); + length += charactersPerElement + 1; + } + + effectiveArraySize = i; + } + + if (_builder == null || arrayHash != _lastHash) { + var format = "{0:x" + charactersPerElement + "} "; + + _builder ??= new StringBuilder(); + _builder.Clear(); + + for (int i = 0; i < effectiveArraySize; ++i) { + _builder.AppendFormat(format, property.GetArrayElementAtIndex(i).longValue); + } + + if (effectiveArraySize < property.arraySize) { + _builder.AppendLine("..."); + } + + _lastHash = arrayHash; + _lastValue = new GUIContent(_builder.ToString()); + } else { + Debug.Assert(_lastValue != null); + } + } + + + public float GetHeight(float width) { + return FusionEditorSkin.RawDataStyle.Value.CalcHeight(_lastValue ?? GUIContent.none, width); + } + + public string Draw(Rect position) => Draw(GUIContent.none, position); + + public string Draw(GUIContent label, Rect position) { + var id = GUIUtility.GetControlID(UnityInternal.EditorGUI.DelayedTextFieldHash, FocusType.Keyboard, position); + return UnityInternal.EditorGUI.DelayedTextFieldInternal(position, id, label, _lastValue.text ?? string.Empty, "0123456789abcdefABCDEF ", FusionEditorSkin.RawDataStyle); + } + + public string DrawLayout() { + var position = EditorGUILayout.GetControlRect(false, 18f, FusionEditorSkin.RawDataStyle); + return Draw(position); + } + } +} + +#endregion + + +#region ReflectionUtils.cs + +namespace Fusion.Editor { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Linq.Expressions; + using System.Reflection; + using UnityEditor; + + static partial class ReflectionUtils { + public const BindingFlags DefaultBindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance; + + public static Type GetUnityLeafType(this Type type) { + if (type.HasElementType) { + type = type.GetElementType(); + } else if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>)) { + type = type.GetGenericArguments()[0]; + } + + return type; + } + + public static T CreateMethodDelegate(this Type type, string methodName, BindingFlags flags = DefaultBindingFlags) where T : Delegate { + try { + return CreateMethodDelegateInternal(type, methodName, flags); + } catch (Exception ex) { + throw new InvalidOperationException(CreateMethodExceptionMessage(type.Assembly, type.FullName, methodName, flags), ex); + } + } + + public static Delegate CreateMethodDelegate(this Type type, string methodName, BindingFlags flags, Type delegateType) { + try { + return CreateMethodDelegateInternal(type, methodName, flags, delegateType); + } catch (Exception ex) { + throw new InvalidOperationException(CreateMethodExceptionMessage(type.Assembly, type.FullName, methodName, flags, delegateType), ex); + } + } + + public static T CreateMethodDelegate(Assembly assembly, string typeName, string methodName, BindingFlags flags = DefaultBindingFlags) where T : Delegate { + try { + var type = assembly.GetType(typeName, true); + return CreateMethodDelegateInternal(type, methodName, flags); + } catch (Exception ex) { + throw new InvalidOperationException(CreateMethodExceptionMessage(assembly, typeName, methodName, flags), ex); + } + } + + public static Delegate CreateMethodDelegate(Assembly assembly, string typeName, string methodName, BindingFlags flags, Type delegateType) { + try { + var type = assembly.GetType(typeName, true); + return CreateMethodDelegateInternal(type, methodName, flags, delegateType); + } catch (Exception ex) { + throw new InvalidOperationException(CreateMethodExceptionMessage(assembly, typeName, methodName, flags, delegateType), ex); + } + } + + internal static T CreateMethodDelegate(this Type type, string methodName, BindingFlags flags, Type delegateType, params DelegateSwizzle[] fallbackSwizzles) where T : Delegate { + try { + delegateType ??= typeof(T); + + + var method = GetMethodOrThrow(type, methodName, flags, delegateType, fallbackSwizzles, out var swizzle); + if (swizzle == null && typeof(T) == delegateType) { + return (T)Delegate.CreateDelegate(typeof(T), method); + } + + var delegateParameters = typeof(T).GetMethod("Invoke").GetParameters(); + var parameters = new List(); + + for (var i = 0; i < delegateParameters.Length; ++i) { + parameters.Add(Expression.Parameter(delegateParameters[i].ParameterType, $"param_{i}")); + } + + var convertedParameters = new List(); + { + var methodParameters = method.GetParameters(); + if (swizzle == null) { + for (int i = 0, j = method.IsStatic ? 0 : 1; i < methodParameters.Length; ++i, ++j) { + convertedParameters.Add(Expression.Convert(parameters[j], methodParameters[i].ParameterType)); + } + } else { + foreach (var converter in swizzle.Converters) { + convertedParameters.Add(Expression.Invoke(converter, parameters)); + } + } + } + + + MethodCallExpression callExpression; + if (method.IsStatic) { + callExpression = Expression.Call(method, convertedParameters); + } else { + var instance = Expression.Convert(parameters[0], method.DeclaringType); + callExpression = Expression.Call(instance, method, convertedParameters); + } + + var l = Expression.Lambda(typeof(T), callExpression, parameters); + var del = l.Compile(); + return (T)del; + } catch (Exception ex) { + throw new InvalidOperationException(CreateMethodExceptionMessage(type.Assembly, type.FullName, methodName, flags), ex); + } + } + + /// + /// Returns the first found member of the given name. Includes private members. + /// + public static MemberInfo GetMemberIncludingBaseTypes(this Type type, string memberName, BindingFlags flags = DefaultBindingFlags, Type stopAtType = null) { + var members = type.GetMember(memberName, flags); + if (members.Length > 0) { + return members[0]; + } + + type = type.BaseType; + + // loop as long as we have a parent class to search. + while (type != null) { + // No point recursing into the abstracts. + if (type == stopAtType) { + break; + } + + members = type.GetMember(memberName, flags); + if (members.Length > 0) { + return members[0]; + } + + type = type.BaseType; + } + + return null; + } + + /// + /// Normal reflection GetField() won't find private fields in parents (only will find protected). So this recurses the + /// hard to find privates. + /// This is needed since Unity serialization does find inherited privates. + /// + public static FieldInfo GetFieldIncludingBaseTypes(this Type type, string fieldName, BindingFlags flags = DefaultBindingFlags, Type stopAtType = null) { + var field = type.GetField(fieldName, flags); + if (field != null) { + return field; + } + + type = type.BaseType; + + // loop as long as we have a parent class to search. + while (type != null) { + // No point recursing into the abstracts. + if (type == stopAtType) { + break; + } + + field = type.GetField(fieldName, flags); + if (field != null) { + return field; + } + + type = type.BaseType; + } + + return null; + } + + public static FieldInfo GetFieldOrThrow(this Type type, string fieldName, BindingFlags flags = DefaultBindingFlags) { + var field = type.GetField(fieldName, flags); + if (field == null) { + throw new ArgumentOutOfRangeException(nameof(fieldName), CreateFieldExceptionMessage(type.Assembly, type.FullName, fieldName, flags)); + } + + return field; + } + + public static FieldInfo GetFieldOrThrow(this Type type, string fieldName, BindingFlags flags = DefaultBindingFlags) { + return GetFieldOrThrow(type, fieldName, typeof(T), flags); + } + + public static FieldInfo GetFieldOrThrow(this Type type, string fieldName, Type fieldType, BindingFlags flags = DefaultBindingFlags) { + var field = type.GetField(fieldName, flags); + if (field == null) { + throw new ArgumentOutOfRangeException(nameof(fieldName), CreateFieldExceptionMessage(type.Assembly, type.FullName, fieldName, flags)); + } + + if (fieldType != null) { + if (field.FieldType != fieldType) { + throw new InvalidProgramException($"Field {type.FullName}.{fieldName} is of type {field.FieldType}, not expected {fieldType}"); + } + } + + return field; + } + + public static PropertyInfo GetPropertyOrThrow(this Type type, string propertyName, BindingFlags flags = DefaultBindingFlags) { + return GetPropertyOrThrow(type, propertyName, typeof(T), flags); + } + + public static PropertyInfo GetPropertyOrThrow(this Type type, string propertyName, Type propertyType, BindingFlags flags = DefaultBindingFlags) { + var property = type.GetProperty(propertyName, flags); + if (property == null) { + throw new ArgumentOutOfRangeException(nameof(propertyName), CreateFieldExceptionMessage(type.Assembly, type.FullName, propertyName, flags)); + } + + if (property.PropertyType != propertyType) { + throw new InvalidProgramException($"Property {type.FullName}.{propertyName} is of type {property.PropertyType}, not expected {propertyType}"); + } + + return property; + } + + public static PropertyInfo GetPropertyOrThrow(this Type type, string propertyName, BindingFlags flags = DefaultBindingFlags) { + var property = type.GetProperty(propertyName, flags); + if (property == null) { + throw new ArgumentOutOfRangeException(nameof(propertyName), CreateFieldExceptionMessage(type.Assembly, type.FullName, propertyName, flags)); + } + + return property; + } + + public static MethodInfo GetMethodOrThrow(this Type type, string methodName, BindingFlags flags = DefaultBindingFlags) { + var method = type.GetMethod(methodName, flags); + if (method == null) { + throw new ArgumentOutOfRangeException(nameof(methodName), CreateFieldExceptionMessage(type.Assembly, type.FullName, methodName, flags)); + } + + return method; + } + + public static ConstructorInfo GetConstructorInfoOrThrow(this Type type, Type[] types, BindingFlags flags = DefaultBindingFlags) { + var constructor = type.GetConstructor(flags, null, types, null); + if (constructor == null) { + throw new ArgumentOutOfRangeException(nameof(types), CreateConstructorExceptionMessage(type.Assembly, type.FullName, types, flags)); + } + + return constructor; + } + + public static Type GetNestedTypeOrThrow(this Type type, string name, BindingFlags flags) { + var result = type.GetNestedType(name, flags); + if (result == null) { + throw new ArgumentOutOfRangeException(nameof(name), CreateFieldExceptionMessage(type.Assembly, type.FullName, name, flags)); + } + + return result; + } + + public static Func CreateGetter(this Type type, string memberName, BindingFlags flags = DefaultBindingFlags) { + return CreateGetter(type, memberName, flags); + } + + public static Func CreateGetter(this Type type, string memberName, BindingFlags flags = DefaultBindingFlags) { + var candidates = type.GetMembers(flags).Where(x => x.Name == memberName) + .ToList(); + + if (candidates.Count > 1) { + throw new InvalidOperationException($"Multiple members with name {memberName} found in type {type.FullName}"); + } + if (candidates.Count == 0) { + throw new ArgumentOutOfRangeException(nameof(memberName),$"No members with name {memberName} found in type {type.FullName}"); + } + + var candidate = candidates[0]; + bool isStatic = false; + switch (candidate) { + case FieldInfo field: + isStatic = field.IsStatic; + break; + case PropertyInfo property: + isStatic = property.GetMethod.IsStatic; + break; + case MethodInfo method: + isStatic = method.IsStatic; + break; + } + + if (isStatic) { + var getter = CreateStaticAccessorInternal(candidate).GetValue; + return _ => getter(); + } else { + return CreateAccessorInternal(candidate).GetValue; + } + } + + public static InstanceAccessor CreateFieldAccessor(this Type type, string fieldName, Type expectedFieldType = null, BindingFlags flags = DefaultBindingFlags) { + return CreateFieldAccessor(type, fieldName, expectedFieldType); + } + + public static InstanceAccessor CreateFieldAccessor(this Type type, string fieldName, Type expectedFieldType = null, BindingFlags flags = DefaultBindingFlags) { + var field = type.GetFieldOrThrow(fieldName, expectedFieldType ?? typeof(FieldType), BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + return CreateAccessorInternal(field); + } + + public static StaticAccessor CreateStaticFieldAccessor(this Type type, string fieldName, Type expectedFieldType = null) { + return CreateStaticFieldAccessor(type, fieldName, expectedFieldType); + } + + public static StaticAccessor CreateStaticFieldAccessor(this Type type, string fieldName, Type expectedFieldType = null) { + var field = type.GetFieldOrThrow(fieldName, expectedFieldType ?? typeof(FieldType), BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); + return CreateStaticAccessorInternal(field); + } + + public static InstanceAccessor CreatePropertyAccessor(this Type type, string fieldName, Type expectedPropertyType = null, BindingFlags flags = DefaultBindingFlags) { + var field = type.GetPropertyOrThrow(fieldName, expectedPropertyType ?? typeof(PropertyType), BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + return CreateAccessorInternal(field); + } + + public static StaticAccessor CreateStaticPropertyAccessor(this Type type, string fieldName, Type expectedFieldType = null) { + return CreateStaticPropertyAccessor(type, fieldName, expectedFieldType); + } + + public static StaticAccessor CreateStaticPropertyAccessor(this Type type, string fieldName, Type expectedFieldType = null) { + var field = type.GetPropertyOrThrow(fieldName, expectedFieldType ?? typeof(FieldType), BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); + return CreateStaticAccessorInternal(field); + } + + private static string CreateMethodExceptionMessage(Assembly assembly, string typeName, string methodName, BindingFlags flags) { + return CreateMethodExceptionMessage(assembly, typeName, methodName, flags, typeof(T)); + } + + private static string CreateMethodExceptionMessage(Assembly assembly, string typeName, string methodName, BindingFlags flags, Type delegateType) { + return $"{assembly.FullName}.{typeName}.{methodName} with flags: {flags} and type: {delegateType}"; + } + + private static string CreateFieldExceptionMessage(Assembly assembly, string typeName, string fieldName, BindingFlags flags) { + return $"{assembly.FullName}.{typeName}.{fieldName} with flags: {flags}"; + } + + private static string CreateConstructorExceptionMessage(Assembly assembly, string typeName, BindingFlags flags) { + return $"{assembly.FullName}.{typeName}() with flags: {flags}"; + } + + private static string CreateConstructorExceptionMessage(Assembly assembly, string typeName, Type[] types, BindingFlags flags) { + return $"{assembly.FullName}.{typeName}({string.Join(", ", types.Select(x => x.FullName))}) with flags: {flags}"; + } + + private static T CreateMethodDelegateInternal(this Type type, string name, BindingFlags flags) where T : Delegate { + return (T)CreateMethodDelegateInternal(type, name, flags, typeof(T)); + } + + private static Delegate CreateMethodDelegateInternal(this Type type, string name, BindingFlags flags, Type delegateType) { + var method = GetMethodOrThrow(type, name, flags, delegateType); + return Delegate.CreateDelegate(delegateType, null, method); + } + + private static MethodInfo GetMethodOrThrow(Type type, string name, BindingFlags flags, Type delegateType) { + return GetMethodOrThrow(type, name, flags, delegateType, Array.Empty(), out _); + } + + private static MethodInfo FindMethod(Type type, string name, BindingFlags flags, Type returnType, params Type[] parameters) { + var method = type.GetMethod(name, flags, null, parameters, null); + + if (method == null) { + return null; + } + + if (method.ReturnType != returnType) { + return null; + } + + return method; + } + + private static ConstructorInfo GetConstructorOrThrow(Type type, BindingFlags flags, Type delegateType, DelegateSwizzle[] swizzles, out DelegateSwizzle firstMatchingSwizzle) { + var delegateMethod = delegateType.GetMethod("Invoke"); + + var allDelegateParameters = delegateMethod.GetParameters().Select(x => x.ParameterType).ToArray(); + + var constructor = type.GetConstructor(flags, null, allDelegateParameters, null); + if (constructor != null) { + firstMatchingSwizzle = null; + return constructor; + } + + if (swizzles != null) { + foreach (var swizzle in swizzles) { + var swizzled = swizzle.Types; + constructor = type.GetConstructor(flags, null, swizzled, null); + if (constructor != null) { + firstMatchingSwizzle = swizzle; + return constructor; + } + } + } + + var constructors = type.GetConstructors(flags); + throw new ArgumentOutOfRangeException(nameof(delegateType), $"No matching constructor found for {type}, " + + $"signature \"{delegateType}\", " + + $"flags \"{flags}\" and " + + $"params: {string.Join(", ", allDelegateParameters.Select(x => x.FullName))}" + + $", candidates are\n: {string.Join("\n", constructors.Select(x => x.ToString()))}"); + } + + private static MethodInfo GetMethodOrThrow(Type type, string name, BindingFlags flags, Type delegateType, DelegateSwizzle[] swizzles, out DelegateSwizzle firstMatchingSwizzle) { + var delegateMethod = delegateType.GetMethod("Invoke"); + + var allDelegateParameters = delegateMethod.GetParameters().Select(x => x.ParameterType).ToArray(); + + var method = FindMethod(type, name, flags, delegateMethod.ReturnType, flags.HasFlag(BindingFlags.Static) ? allDelegateParameters : allDelegateParameters.Skip(1).ToArray()); + if (method != null) { + firstMatchingSwizzle = null; + return method; + } + + if (swizzles != null) { + foreach (var swizzle in swizzles) { + var swizzled = swizzle.Types; + if (!flags.HasFlag(BindingFlags.Static) && swizzled[0] != type) { + throw new InvalidOperationException(); + } + + method = FindMethod(type, name, flags, delegateMethod.ReturnType, flags.HasFlag(BindingFlags.Static) ? swizzled : swizzled.Skip(1).ToArray()); + if (method != null) { + firstMatchingSwizzle = swizzle; + return method; + } + } + } + + var methods = type.GetMethods(flags); + throw new ArgumentOutOfRangeException(nameof(name), $"No method found matching name \"{name}\", " + + $"signature \"{delegateType}\", " + + $"flags \"{flags}\" and " + + $"params: {string.Join(", ", allDelegateParameters.Select(x => x.FullName))}" + + $", candidates are\n: {string.Join("\n", methods.Select(x => x.ToString()))}"); + } + + public static bool IsArrayOrList(this Type listType) { + if (listType.IsArray) { + return true; + } + + if (listType.IsGenericType && listType.GetGenericTypeDefinition() == typeof(List<>)) { + return true; + } + + return false; + } + + public static Type GetArrayOrListElementType(this Type listType) { + if (listType.IsArray) { + return listType.GetElementType(); + } + + if (listType.IsGenericType && listType.GetGenericTypeDefinition() == typeof(List<>)) { + return listType.GetGenericArguments()[0]; + } + + return null; + } + + public static Type MakeFuncType(params Type[] types) { + return GetFuncType(types.Length).MakeGenericType(types); + } + + private static Type GetFuncType(int argumentCount) { + switch (argumentCount) { + case 1: return typeof(Func<>); + case 2: return typeof(Func<,>); + case 3: return typeof(Func<,,>); + case 4: return typeof(Func<,,,>); + case 5: return typeof(Func<,,,,>); + case 6: return typeof(Func<,,,,,>); + default: throw new ArgumentOutOfRangeException(nameof(argumentCount)); + } + } + + public static Type MakeActionType(params Type[] types) { + if (types.Length == 0) { + return typeof(Action); + } + + return GetActionType(types.Length).MakeGenericType(types); + } + + private static Type GetActionType(int argumentCount) { + switch (argumentCount) { + case 1: return typeof(Action<>); + case 2: return typeof(Action<,>); + case 3: return typeof(Action<,,>); + case 4: return typeof(Action<,,,>); + case 5: return typeof(Action<,,,,>); + case 6: return typeof(Action<,,,,,>); + default: throw new ArgumentOutOfRangeException(nameof(argumentCount)); + } + } + + private static StaticAccessor CreateStaticAccessorInternal(MemberInfo member) { + try { + var valueParameter = Expression.Parameter(typeof(T), "value"); + var canWrite = true; + + UnaryExpression valueExpression; + Expression memberExpression; + + switch (member) { + case PropertyInfo property: + valueExpression = Expression.Convert(valueParameter, property.PropertyType); + memberExpression = Expression.Property(null, property); + canWrite = property.CanWrite; + break; + case FieldInfo field: + valueExpression = Expression.Convert(valueParameter, field.FieldType); + memberExpression = Expression.Field(null, field); + canWrite = field.IsInitOnly == false; + break; + case MethodInfo method when method.GetParameters().Length == 0: + valueExpression = null; + memberExpression = Expression.Call(method); + canWrite = false; + break; + default: + throw new InvalidOperationException($"Unsupported member type {member.GetType().Name}"); + } + + Func getter; + var getExpression = Expression.Convert(memberExpression, typeof(T)); + var getLambda = Expression.Lambda>(getExpression); + getter = getLambda.Compile(); + + Action setter = null; + if (canWrite) { + var setExpression = Expression.Assign(memberExpression, valueExpression); + var setLambda = Expression.Lambda>(setExpression, valueParameter); + setter = setLambda.Compile(); + } + + return new StaticAccessor { + GetValue = getter, + SetValue = setter + }; + } catch (Exception ex) { + throw new InvalidOperationException($"Failed to create accessor for {member.DeclaringType}.{member.Name}", ex); + } + } + + private static InstanceAccessor CreateAccessorInternal(MemberInfo member) { + try { + var instanceParameter = Expression.Parameter(typeof(object), "instance"); + var instanceExpression = Expression.Convert(instanceParameter, member.DeclaringType); + + var valueParameter = Expression.Parameter(typeof(T), "value"); + var canWrite = true; + + UnaryExpression valueExpression; + Expression memberExpression; + + switch (member) { + case PropertyInfo property: + valueExpression = Expression.Convert(valueParameter, property.PropertyType); + memberExpression = Expression.Property(instanceExpression, property); + canWrite = property.CanWrite; + break; + case FieldInfo field: + valueExpression = Expression.Convert(valueParameter, field.FieldType); + memberExpression = Expression.Field(instanceExpression, field); + canWrite = field.IsInitOnly == false; + break; + case MethodInfo method when method.GetParameters().Length == 0: + valueExpression = null; + memberExpression = Expression.Call(instanceExpression, method); + canWrite = false; + break; + default: + throw new InvalidOperationException($"Unsupported member type {member.GetType().Name}"); + } + + var getExpression = Expression.Convert(memberExpression, typeof(T)); + var getLambda = Expression.Lambda>(getExpression, instanceParameter); + var getter = getLambda.Compile(); + + Action setter = null; + if (canWrite) { + var setExpression = Expression.Assign(memberExpression, valueExpression); + var setLambda = Expression.Lambda>(setExpression, instanceParameter, valueParameter); + setter = setLambda.Compile(); + } + + return new InstanceAccessor { + GetValue = getter, + SetValue = setter + }; + } catch (Exception ex) { + throw new InvalidOperationException($"Failed to create accessor for {member.DeclaringType}.{member.Name}", ex); + } + } + + public struct InstanceAccessor { + public Func GetValue; + public Action SetValue; + } + + public struct StaticAccessor { + public Func GetValue; + public Action SetValue; + } + + internal static class DelegateSwizzle { + public static DelegateSwizzle Make(Expression> out0) { + return new DelegateSwizzle(new Expression[] { out0 }, new [] { typeof(Out0)}); + } + + public static DelegateSwizzle Make(Expression> out0, Expression> out1) { + return new DelegateSwizzle(new Expression[] { out0, out1 }, new [] { typeof(Out0), typeof(Out1)}); + } + + public static DelegateSwizzle Make(Expression> out0, Expression> out1, Expression> out3) { + return new DelegateSwizzle(new Expression[] { out0, out1, out3 }, new [] { typeof(Out0), typeof(Out1), typeof(Out3)}); + } + } + + internal class DelegateSwizzle { + public DelegateSwizzle(Expression[] converters, Type[] types) { + Converters = converters; + Types = types; + } + + public Expression[] Converters { get; } + public Type[] Types { get; } + } + +#if UNITY_EDITOR + + public static T CreateEditorMethodDelegate(string editorAssemblyTypeName, string methodName, BindingFlags flags) where T : Delegate { + return CreateMethodDelegate(typeof(Editor).Assembly, editorAssemblyTypeName, methodName, flags); + } + + public static Delegate CreateEditorMethodDelegate(string editorAssemblyTypeName, string methodName, BindingFlags flags, Type delegateType) { + return CreateMethodDelegate(typeof(Editor).Assembly, editorAssemblyTypeName, methodName, flags, delegateType); + } + +#endif + } +} + +#endregion + + +#region SerializedPropertyUtilities.cs + +namespace Fusion.Editor { + using System; + using System.Collections; + using System.Collections.Generic; + using UnityEditor; + + static partial class SerializedPropertyUtilities { + public static SerializedProperty FindPropertyOrThrow(this SerializedObject so, string propertyPath) { + var result = so.FindProperty(propertyPath); + if (result == null) { + throw new ArgumentOutOfRangeException(nameof(propertyPath), $"Property not found: {propertyPath} on {so.targetObject}"); + } + + return result; + } + + public static SerializedProperty FindPropertyRelativeOrThrow(this SerializedProperty sp, string relativePropertyPath) { + var result = sp.FindPropertyRelative(relativePropertyPath); + if (result == null) { + throw new ArgumentOutOfRangeException(nameof(relativePropertyPath), $"Property not found: {relativePropertyPath} (relative to \"{sp.propertyPath}\" of {sp.serializedObject.targetObject}"); + } + + return result; + } + + public static SerializedProperty FindPropertyRelativeToParentOrThrow(this SerializedProperty property, string relativePath) { + var result = FindPropertyRelativeToParent(property, relativePath); + if (result == null) { + throw new ArgumentOutOfRangeException(nameof(relativePath), $"Property not found: {relativePath} (relative to the parent of \"{property.propertyPath}\" of {property.serializedObject.targetObject}"); + } + + return result; + } + + public static SerializedProperty FindPropertyRelativeToParent(this SerializedProperty property, string relativePath) { + + ReadOnlySpan parentPath = property.propertyPath; + + int startIndex = 0; + + do { + // array element? + if (parentPath.EndsWith("]", StringComparison.Ordinal)) { + int arrayDataIndex = parentPath.LastIndexOf(".Array.data["); + if (arrayDataIndex >= 0) { + parentPath = parentPath.Slice(0, arrayDataIndex); + } + } + + var lastDotIndex = parentPath.LastIndexOf('.'); + if (lastDotIndex < 0) { + if (parentPath.Length == 0) { + return null; + } + + parentPath = string.Empty; + } else { + parentPath = parentPath.Slice(0, lastDotIndex); + } + + } while (relativePath[startIndex++] == '^'); + + if (startIndex > 1) { + relativePath = relativePath.Substring(startIndex - 1); + } + + if (parentPath.Length == 0) { + return property.serializedObject.FindProperty(relativePath); + } else { + return property.serializedObject.FindProperty($"{parentPath.ToString()}.{relativePath}"); + } + } + + public static bool IsArrayElement(string propertyPath) { + if (!propertyPath.EndsWith("]", StringComparison.Ordinal)) { + return false; + } + + return true; + } + + public static bool IsArrayElement(this SerializedProperty sp) { + return sp.depth > 0 && IsArrayElement(sp.propertyPath); + } + + public static bool IsArrayElement(this SerializedProperty sp, out int index) { + if (sp.depth == 0) { + index = -1; + return false; + } + + var propertyPath = sp.propertyPath; + if (!propertyPath.EndsWith("]", StringComparison.Ordinal)) { + index = -1; + return false; + } + + var indexStart = propertyPath.LastIndexOf("[", StringComparison.Ordinal); + if (indexStart < 0) { + index = -1; + return false; + } + + index = int.Parse(propertyPath.Substring(indexStart + 1, propertyPath.Length - indexStart - 2)); + return true; + } + + public static SerializedProperty GetArrayFromArrayElement(this SerializedProperty sp) { + var path = sp.propertyPath; + + if (path.EndsWith("]", StringComparison.Ordinal)) { + int arrayDataIndex = path.LastIndexOf(".Array.data[", StringComparison.Ordinal); + if (arrayDataIndex >= 0) { + var arrayPath = path.Substring(0, arrayDataIndex); + return sp.serializedObject.FindProperty(arrayPath); + } + } + + throw new ArgumentException($"Property is not an array element: {path}"); + } + + public static bool IsArrayProperty(this SerializedProperty sp) { + return sp.isArray && sp.propertyType != SerializedPropertyType.String; + } + + public static bool ShouldIncludeChildren(this SerializedProperty sp) { + return sp.isExpanded || sp.propertyType == SerializedPropertyType.Generic || sp.IsArrayProperty(); + } + + + // public static int GetHashCodeForPropertyPath(this SerializedProperty sp) { + // return UnityInternal.SerializedProperty.hashCodeForPropertyPath.GetValue(sp); + // } + + public static int GetHashCodeForPropertyPathWithoutArrayIndex(this SerializedProperty sp) { + return UnityInternal.SerializedProperty.hashCodeForPropertyPathWithoutArrayIndex.GetValue(sp); + } + + + public static SerializedPropertyEnumerable GetChildren(this SerializedProperty property, bool visibleOnly = true) { + return new SerializedPropertyEnumerable(property, visibleOnly); + } + + public class SerializedPropertyEqualityComparer : IEqualityComparer { + public static SerializedPropertyEqualityComparer Instance = new(); + + public bool Equals(SerializedProperty x, SerializedProperty y) { + return SerializedProperty.DataEquals(x, y); + } + + public int GetHashCode(SerializedProperty p) { + bool enterChildren; + var isFirst = true; + var hashCode = 0; + var minDepth = p.depth + 1; + + do { + enterChildren = false; + + switch (p.propertyType) { + case SerializedPropertyType.Integer: + hashCode = HashCodeUtilities.CombineHashCodes(hashCode, p.intValue); + break; + case SerializedPropertyType.Boolean: + hashCode = HashCodeUtilities.CombineHashCodes(hashCode, p.boolValue.GetHashCode()); + break; + case SerializedPropertyType.Float: + hashCode = HashCodeUtilities.CombineHashCodes(hashCode, p.floatValue.GetHashCode()); + break; + case SerializedPropertyType.String: + hashCode = HashCodeUtilities.CombineHashCodes(hashCode, p.stringValue.GetHashCode()); + break; + case SerializedPropertyType.Color: + hashCode = HashCodeUtilities.CombineHashCodes(hashCode, p.colorValue.GetHashCode()); + break; + case SerializedPropertyType.ObjectReference: + hashCode = HashCodeUtilities.CombineHashCodes(hashCode, p.objectReferenceInstanceIDValue); + break; + case SerializedPropertyType.LayerMask: + hashCode = HashCodeUtilities.CombineHashCodes(hashCode, p.intValue); + break; + case SerializedPropertyType.Enum: + hashCode = HashCodeUtilities.CombineHashCodes(hashCode, p.intValue); + break; + case SerializedPropertyType.Vector2: + hashCode = HashCodeUtilities.CombineHashCodes(hashCode, p.vector2Value.GetHashCode()); + break; + case SerializedPropertyType.Vector3: + hashCode = HashCodeUtilities.CombineHashCodes(hashCode, p.vector3Value.GetHashCode()); + break; + case SerializedPropertyType.Vector4: + hashCode = HashCodeUtilities.CombineHashCodes(hashCode, p.vector4Value.GetHashCode()); + break; + case SerializedPropertyType.Vector2Int: + hashCode = HashCodeUtilities.CombineHashCodes(hashCode, p.vector2IntValue.GetHashCode()); + break; + case SerializedPropertyType.Vector3Int: + hashCode = HashCodeUtilities.CombineHashCodes(hashCode, p.vector3IntValue.GetHashCode()); + break; + case SerializedPropertyType.Rect: + hashCode = HashCodeUtilities.CombineHashCodes(hashCode, p.rectValue.GetHashCode()); + break; + case SerializedPropertyType.RectInt: + hashCode = HashCodeUtilities.CombineHashCodes(hashCode, p.rectIntValue.GetHashCode()); + break; + case SerializedPropertyType.ArraySize: + hashCode = HashCodeUtilities.CombineHashCodes(hashCode, p.intValue); + break; + case SerializedPropertyType.Character: + hashCode = HashCodeUtilities.CombineHashCodes(hashCode, p.intValue.GetHashCode()); + break; + case SerializedPropertyType.AnimationCurve: + hashCode = HashCodeUtilities.CombineHashCodes(hashCode, p.animationCurveValue.GetHashCode()); + break; + case SerializedPropertyType.Bounds: + hashCode = HashCodeUtilities.CombineHashCodes(hashCode, p.boundsValue.GetHashCode()); + break; + case SerializedPropertyType.BoundsInt: + hashCode = HashCodeUtilities.CombineHashCodes(hashCode, p.boundsIntValue.GetHashCode()); + break; + case SerializedPropertyType.ExposedReference: + hashCode = HashCodeUtilities.CombineHashCodes(hashCode, p.exposedReferenceValue.GetHashCode()); + break; + default: { + enterChildren = true; + break; + } + } + + if (isFirst) { + if (!enterChildren) + // no traverse needed + { + return hashCode; + } + + // since property is going to be traversed, a copy needs to be made + p = p.Copy(); + isFirst = false; + } + } while (p.Next(enterChildren) && p.depth >= minDepth); + + return hashCode; + } + } + + public struct SerializedPropertyEnumerable : IEnumerable { + private SerializedProperty property; + private bool visible; + + public SerializedPropertyEnumerable(SerializedProperty property, bool visible) { + this.property = property; + this.visible = visible; + } + + public SerializedPropertyEnumerator GetEnumerator() { + return new SerializedPropertyEnumerator(property, visible); + } + + IEnumerator IEnumerable.GetEnumerator() { + return GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() { + return GetEnumerator(); + } + } + + public struct SerializedPropertyEnumerator : IEnumerator { + private SerializedProperty current; + private bool enterChildren; + private bool visible; + private int parentDepth; + + public SerializedPropertyEnumerator(SerializedProperty parent, bool visible) { + current = parent.Copy(); + enterChildren = true; + parentDepth = parent.depth; + this.visible = visible; + } + + public SerializedProperty Current => current; + + SerializedProperty IEnumerator.Current => current; + + object IEnumerator.Current => current; + + public void Dispose() { + current.Dispose(); + } + + public bool MoveNext() { + bool entered = visible ? current.NextVisible(enterChildren) : current.Next(enterChildren); + enterChildren = false; + if (!entered) { + return false; + } + if (current.depth <= parentDepth) { + return false; + } + return true; + } + + public void Reset() { + throw new NotImplementedException(); + } + } + + private static int[] _updateFixedBufferTemp = Array.Empty(); + + internal static bool UpdateFixedBuffer(this SerializedProperty sp, Action fill, Action update, bool write, bool force = false) { + int count = sp.fixedBufferSize; + Array.Resize(ref _updateFixedBufferTemp, Math.Max(_updateFixedBufferTemp.Length, count)); + + // need to get to the first property... `GetFixedBufferElementAtIndex` is slow and allocates + + var element = sp.Copy(); + element.Next(true); // .Array + element.Next(true); // .Array.size + element.Next(true); // .Array.data[0] + + unsafe { + fixed (int* p = _updateFixedBufferTemp) { + Unity.Collections.LowLevel.Unsafe.UnsafeUtility.MemClear(p, count * sizeof(int)); + } + + fill(_updateFixedBufferTemp, count); + + int i = 0; + if (!force) { + // find the first difference + for (; i < count; ++i, element.Next(true)) { + FusionEditorLog.Assert(element.propertyType == SerializedPropertyType.Integer, "Invalid property type, expected integer"); + if (element.intValue != _updateFixedBufferTemp[i]) { + break; + } + } + } + + if (i < count) { + // update data + if (write) { + for (; i < count; ++i, element.Next(true)) { + element.intValue = _updateFixedBufferTemp[i]; + } + } else { + for (; i < count; ++i, element.Next(true)) { + _updateFixedBufferTemp[i] = element.intValue; + } + } + + update(_updateFixedBufferTemp, count); + return true; + } else { + return false; + } + } + } + } +} + +#endregion + + +#region UnityInternal.cs + +// ReSharper disable InconsistentNaming +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member +namespace Fusion.Editor { + using System; + using System.Collections; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + using UnityEditor; + using UnityEngine; + using static ReflectionUtils; + + + static partial class UnityInternal { + + static Assembly FindAssembly(string name) { + return AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.GetName().Name == name); + } + + [UnityEditor.InitializeOnLoad] + public static class AssetDatabase { + public delegate bool TryGetAssetFolderInfoDelegate(string path, out bool rootFolder, out bool immutable); + public static readonly TryGetAssetFolderInfoDelegate TryGetAssetFolderInfo = typeof(UnityEditor.AssetDatabase).CreateMethodDelegate( +#if UNITY_6000_0_OR_NEWER + nameof(TryGetAssetFolderInfo) +#else + "GetAssetFolderInfo" +#endif +); + } + + [UnityEditor.InitializeOnLoad] + public static class Event { + static readonly StaticAccessor s_Current_ = typeof(UnityEngine.Event).CreateStaticFieldAccessor(nameof(s_Current)); + public static UnityEngine.Event s_Current => s_Current_.GetValue(); + } + + [UnityEditor.InitializeOnLoad] + public static class Editor { + public delegate bool DoDrawDefaultInspectorDelegate(SerializedObject obj); + public delegate void BoolSetterDelegate(UnityEditor.Editor editor, bool value); + + public static readonly DoDrawDefaultInspectorDelegate DoDrawDefaultInspector = typeof(UnityEditor.Editor).CreateMethodDelegate(nameof(DoDrawDefaultInspector)); + public static readonly BoolSetterDelegate InternalSetHidden = typeof(UnityEditor.Editor).CreateMethodDelegate(nameof(InternalSetHidden), BindingFlags.NonPublic | BindingFlags.Instance); + } + + + [UnityEditor.InitializeOnLoad] + public static class EditorGUI { + public delegate string DelayedTextFieldInternalDelegate(Rect position, int id, GUIContent label, string value, string allowedLetters, GUIStyle style); + public delegate Rect MultiFieldPrefixLabelDelegate(Rect totalPosition, int id, GUIContent label, int columns); + public delegate string TextFieldInternalDelegate(int id, Rect position, string text, GUIStyle style); + public delegate string ToolbarSearchFieldDelegate(int id, Rect position, string text, bool showWithPopupArrow); + public delegate bool DefaultPropertyFieldDelegate(Rect position, UnityEditor.SerializedProperty property, GUIContent label); + + + public static readonly MultiFieldPrefixLabelDelegate MultiFieldPrefixLabel = typeof(UnityEditor.EditorGUI).CreateMethodDelegate(nameof(MultiFieldPrefixLabel)); + public static readonly TextFieldInternalDelegate TextFieldInternal = typeof(UnityEditor.EditorGUI).CreateMethodDelegate(nameof(TextFieldInternal)); + public static readonly ToolbarSearchFieldDelegate ToolbarSearchField = typeof(UnityEditor.EditorGUI).CreateMethodDelegate(nameof(ToolbarSearchField)); + public static readonly DelayedTextFieldInternalDelegate DelayedTextFieldInternal = typeof(UnityEditor.EditorGUI).CreateMethodDelegate(nameof(DelayedTextFieldInternal)); + public static readonly DefaultPropertyFieldDelegate DefaultPropertyField = typeof(UnityEditor.EditorGUI).CreateMethodDelegate(nameof(DefaultPropertyField)); + + private static readonly FieldInfo s_TextFieldHash = typeof(UnityEditor.EditorGUI).GetFieldOrThrow(nameof(s_TextFieldHash)); + private static readonly FieldInfo s_DelayedTextFieldHash = typeof(UnityEditor.EditorGUI).GetFieldOrThrow(nameof(s_DelayedTextFieldHash)); + private static readonly StaticAccessor s_indent = typeof(UnityEditor.EditorGUI).CreateStaticPropertyAccessor(nameof(indent)); + public static readonly Action EndEditingActiveTextField = typeof(UnityEditor.EditorGUI).CreateMethodDelegate(nameof(EndEditingActiveTextField)); + + public static int TextFieldHash => (int)s_TextFieldHash.GetValue(null); + public static int DelayedTextFieldHash => (int)s_DelayedTextFieldHash.GetValue(null); + internal static float indent => s_indent.GetValue(); + } + + [UnityEditor.InitializeOnLoad] + public static class EditorUtility { + public delegate void DisplayCustomMenuDelegate(Rect position, string[] options, int[] selected, UnityEditor.EditorUtility.SelectMenuItemFunction callback, object userData); + + public static DisplayCustomMenuDelegate DisplayCustomMenu = typeof(UnityEditor.EditorUtility).CreateMethodDelegate(nameof(DisplayCustomMenu), BindingFlags.NonPublic | BindingFlags.Static); + } + + [UnityEditor.InitializeOnLoad] + public static class GUIClip { + public static Type InternalType = typeof(UnityEngine.GUIUtility).Assembly.GetType("UnityEngine.GUIClip", true); + + private static readonly StaticAccessor _visibleRect = InternalType.CreateStaticPropertyAccessor(nameof(visibleRect)); + public static Rect visibleRect => _visibleRect.GetValue(); + } + + [UnityEditor.InitializeOnLoad] + public static class HandleUtility { + public static readonly Action ApplyWireMaterial = typeof(UnityEditor.HandleUtility).CreateMethodDelegate(nameof(ApplyWireMaterial)); + } + + + [UnityEditor.InitializeOnLoad] + public static class LayerMatrixGUI { + private const string TypeName = +#if UNITY_2023_1_OR_NEWER + "UnityEditor.LayerCollisionMatrixGUI2D"; +#else + "UnityEditor.LayerMatrixGUI"; +#endif + + private static readonly Type InternalType = +#if UNITY_2023_1_OR_NEWER + FindAssembly("UnityEditor.Physics2DModule")?.GetType(TypeName, true); +#else + typeof(UnityEditor.Editor).Assembly.GetType(TypeName, true); +#endif + + private static readonly Type InternalGetValueFuncType = InternalType?.GetNestedTypeOrThrow(nameof(GetValueFunc), BindingFlags.Public); + private static readonly Type InternalSetValueFuncType = InternalType?.GetNestedTypeOrThrow(nameof(SetValueFunc), BindingFlags.Public); + +#if UNITY_2023_1_OR_NEWER + private static readonly Delegate _Draw = InternalType?.CreateMethodDelegate(nameof(Draw), BindingFlags.Public | BindingFlags.Static, + typeof(Action<,,>).MakeGenericType( + typeof(GUIContent), InternalGetValueFuncType, InternalSetValueFuncType) + ); +#else + private delegate void Ref2Action(T1 t1, ref T2 t2, T3 t3, T4 t4); + + private static readonly Delegate _DoGUI = InternalType?.CreateMethodDelegate("DoGUI", BindingFlags.Public | BindingFlags.Static, + typeof(Ref2Action<,,,>).MakeGenericType( + typeof(GUIContent), typeof(bool), InternalGetValueFuncType, InternalSetValueFuncType) + ); +#endif + + public delegate bool GetValueFunc(int layerA, int layerB); + public delegate void SetValueFunc(int layerA, int layerB, bool val); + + public static void Draw(GUIContent label, GetValueFunc getValue, SetValueFunc setValue) { + if (InternalType == null) { + throw new InvalidOperationException($"{TypeName} not found"); + } + + var getter = Delegate.CreateDelegate(InternalGetValueFuncType, getValue.Target, getValue.Method); + var setter = Delegate.CreateDelegate(InternalSetValueFuncType, setValue.Target, setValue.Method); + +#if UNITY_2023_1_OR_NEWER + _Draw.DynamicInvoke(label, getter, setter); +#else + bool show = true; + var args = new object[] { label, show, getter, setter }; + _DoGUI.DynamicInvoke(args); +#endif + } + } + + + [UnityEditor.InitializeOnLoad] + public static class DecoratorDrawer { + private static InstanceAccessor m_Attribute = typeof(UnityEditor.DecoratorDrawer).CreateFieldAccessor(nameof(m_Attribute)); + + public static void SetAttribute(UnityEditor.DecoratorDrawer drawer, PropertyAttribute attribute) { + m_Attribute.SetValue(drawer, attribute); + } + } + + [UnityEditor.InitializeOnLoad] + public static class PropertyDrawer { + private static InstanceAccessor m_Attribute = typeof(UnityEditor.PropertyDrawer).CreateFieldAccessor(nameof(m_Attribute)); + private static InstanceAccessor m_FieldInfo = typeof(UnityEditor.PropertyDrawer).CreateFieldAccessor(nameof(m_FieldInfo)); + + public static void SetAttribute(UnityEditor.PropertyDrawer drawer, PropertyAttribute attribute) { + m_Attribute.SetValue(drawer, attribute); + } + + public static void SetFieldInfo(UnityEditor.PropertyDrawer drawer, FieldInfo fieldInfo) { + m_FieldInfo.SetValue(drawer, fieldInfo); + } + } + + [UnityEditor.InitializeOnLoad] + public static class EditorGUIUtility { + private static readonly StaticAccessor s_LastControlID = typeof(UnityEditor.EditorGUIUtility).CreateStaticFieldAccessor(nameof(s_LastControlID)); + + private static readonly StaticAccessor _contentWidth = typeof(UnityEditor.EditorGUIUtility).CreateStaticPropertyAccessor(nameof(contextWidth)); + public static int LastControlID => s_LastControlID.GetValue(); + public static float contextWidth => _contentWidth.GetValue(); + + public delegate UnityEngine.Object GetScriptDelegate(string scriptClass); + public delegate Texture2D GetIconForObjectDelegate(UnityEngine.Object obj); + public delegate GUIContent TempContentDelegate(string text); + public delegate Texture2D GetHelpIconDelegate(MessageType type); + + public static readonly GetScriptDelegate GetScript = typeof(UnityEditor.EditorGUIUtility).CreateMethodDelegate(nameof(GetScript)); + public static readonly GetIconForObjectDelegate GetIconForObject = typeof(UnityEditor.EditorGUIUtility).CreateMethodDelegate(nameof(GetIconForObject)); + public static readonly TempContentDelegate TempContent = typeof(UnityEditor.EditorGUIUtility).CreateMethodDelegate(nameof(TempContent)); + public static readonly GetHelpIconDelegate GetHelpIcon = typeof(UnityEditor.EditorGUIUtility).CreateMethodDelegate(nameof(GetHelpIcon)); + } + + [UnityEditor.InitializeOnLoad] + public static class HierarchyIterator { +#if UNITY_6000_3_OR_NEWER + public delegate void CopySearchFilterFromDelegate(UnityEditor.HierarchyIterator to, UnityEditor.HierarchyIterator from); + public static CopySearchFilterFromDelegate CopySearchFilterFrom = typeof(UnityEditor.HierarchyIterator).CreateMethodDelegate(nameof(CopySearchFilterFrom), + BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); +#else + public delegate void CopySearchFilterFromDelegate(UnityEditor.HierarchyProperty to, UnityEditor.HierarchyProperty from); + public static CopySearchFilterFromDelegate CopySearchFilterFrom = typeof(UnityEditor.HierarchyProperty).CreateMethodDelegate(nameof(CopySearchFilterFrom), + BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); +#endif + } + + [UnityEditor.InitializeOnLoad] + public static class ScriptAttributeUtility { + + public static readonly Type InternalType = typeof(UnityEditor.Editor).Assembly.GetType("UnityEditor.ScriptAttributeUtility", true); + + public delegate FieldInfo GetFieldInfoFromPropertyDelegate(UnityEditor.SerializedProperty property, out Type type); + public static readonly GetFieldInfoFromPropertyDelegate GetFieldInfoFromProperty = + InternalType.CreateMethodDelegate( + "GetFieldInfoFromProperty", + BindingFlags.Static | BindingFlags.NonPublic); + + public delegate Type GetDrawerTypeForTypeDelegate(Type type, bool isManagedReference); + public static readonly GetDrawerTypeForTypeDelegate GetDrawerTypeForType = + InternalType.CreateMethodDelegate( + "GetDrawerTypeForType", + BindingFlags.Static | BindingFlags.NonPublic, + null, + DelegateSwizzle.Make((t, b) => t), // post 2023.3 + DelegateSwizzle.Make((t, b) => t, (t, b) => (Type[])null, (t, b) => b) // pre 2023.3.23 + ); + + public delegate Type GetDrawerTypeForPropertyAndTypeDelegate(UnityEditor.SerializedProperty property, Type type); + public static readonly GetDrawerTypeForPropertyAndTypeDelegate GetDrawerTypeForPropertyAndType = + InternalType.CreateMethodDelegate( + "GetDrawerTypeForPropertyAndType", + BindingFlags.Static | BindingFlags.NonPublic); + + private static readonly GetHandlerDelegate _GetHandler = InternalType.CreateMethodDelegate("GetHandler", BindingFlags.NonPublic | BindingFlags.Static, + MakeFuncType(typeof(UnityEditor.SerializedProperty), PropertyHandler.InternalType) + ); + + public delegate List GetFieldAttributesDelegate(FieldInfo field); + public static readonly GetFieldAttributesDelegate GetFieldAttributes = InternalType.CreateMethodDelegate(nameof(GetFieldAttributes)); + + private static readonly StaticAccessor _propertyHandlerCache = InternalType.CreateStaticPropertyAccessor(nameof(propertyHandlerCache), PropertyHandlerCache.InternalType); + + private static readonly StaticAccessor s_SharedNullHandler = InternalType.CreateStaticFieldAccessor("s_SharedNullHandler", PropertyHandler.InternalType); + private static readonly StaticAccessor s_NextHandler = InternalType.CreateStaticFieldAccessor("s_NextHandler", PropertyHandler.InternalType); + + public static PropertyHandlerCache propertyHandlerCache => new() { + _instance = _propertyHandlerCache.GetValue() + }; + + public static PropertyHandler sharedNullHandler => PropertyHandler.Wrap(s_SharedNullHandler.GetValue()); + public static PropertyHandler nextHandler => PropertyHandler.Wrap(s_NextHandler.GetValue()); + + public static PropertyHandler GetHandler(UnityEditor.SerializedProperty property) { + return PropertyHandler.Wrap(_GetHandler(property)); + } + + private delegate object GetHandlerDelegate(UnityEditor.SerializedProperty property); + } + + public struct PropertyHandlerCache { + [UnityEditor.InitializeOnLoad] + private static class Statics { + public static readonly Type InternalType = typeof(UnityEditor.Editor).Assembly.GetType("UnityEditor.PropertyHandlerCache", true); + public static readonly GetPropertyHashDelegate GetPropertyHash = InternalType.CreateMethodDelegate(nameof(GetPropertyHash)); + + public static readonly GetHandlerDelegate GetHandler = InternalType.CreateMethodDelegate(nameof(GetHandler), BindingFlags.NonPublic | BindingFlags.Instance, + MakeFuncType(InternalType, typeof(UnityEditor.SerializedProperty), PropertyHandler.InternalType)); + + public static readonly SetHandlerDelegate SetHandler = InternalType.CreateMethodDelegate(nameof(SetHandler), BindingFlags.NonPublic | BindingFlags.Instance, + MakeActionType(InternalType, typeof(UnityEditor.SerializedProperty), PropertyHandler.InternalType)); + + public static readonly FieldInfo m_PropertyHandlers = InternalType.GetFieldOrThrow(nameof(m_PropertyHandlers)); + } + + public static Type InternalType => Statics.InternalType; + + public delegate int GetPropertyHashDelegate(UnityEditor.SerializedProperty property); + + public delegate object GetHandlerDelegate(object instance, UnityEditor.SerializedProperty property); + + public delegate void SetHandlerDelegate(object instance, UnityEditor.SerializedProperty property, object handlerInstance); + + public object _instance; + + public PropertyHandler GetHandler(UnityEditor.SerializedProperty property) { + return new PropertyHandler { + _instance = Statics.GetHandler(_instance, property) + }; + } + + public void SetHandler(UnityEditor.SerializedProperty property, PropertyHandler newHandler) { + Statics.SetHandler(_instance, property, newHandler._instance); + } + + public IEnumerable<(int, PropertyHandler)> PropertyHandlers { + get { + var dict = (IDictionary)Statics.m_PropertyHandlers.GetValue(_instance); + foreach (DictionaryEntry entry in dict) { + yield return ((int)entry.Key, PropertyHandler.Wrap(entry.Value)); + } + } + } + } + + public struct PropertyHandler : IEquatable { + [UnityEditor.InitializeOnLoad] + private static class Statics { + public static readonly Type InternalType = typeof(UnityEditor.Editor).Assembly.GetType("UnityEditor.PropertyHandler", true); + public static readonly InstanceAccessor> m_DecoratorDrawers = InternalType.CreateFieldAccessor>(nameof(m_DecoratorDrawers)); + public static readonly InstanceAccessor> m_PropertyDrawers = InternalType.CreateFieldAccessor>(nameof(m_PropertyDrawers)); + } + + + public static Type InternalType => Statics.InternalType; + + public object _instance; + + internal static PropertyHandler Wrap(object instance) { + return new() { + _instance = instance + }; + } + + public static PropertyHandler New() { + return Wrap(Activator.CreateInstance(InternalType)); + } + + public List m_PropertyDrawers { + get => Statics.m_PropertyDrawers.GetValue(_instance); + set => Statics.m_PropertyDrawers.SetValue(_instance, value); + } + + public bool Equals(PropertyHandler other) { + return _instance == other._instance; + } + + public override int GetHashCode() { + return _instance?.GetHashCode() ?? 0; + } + + public override bool Equals(object obj) { + return obj is PropertyHandler h ? Equals(h) : false; + } + + public List decoratorDrawers { + get => Statics.m_DecoratorDrawers.GetValue(_instance); + set => Statics.m_DecoratorDrawers.SetValue(_instance, value); + } + } + + [UnityEditor.InitializeOnLoad] + public static class EditorApplication { + public static readonly Action Internal_CallAssetLabelsHaveChanged = typeof(UnityEditor.EditorApplication).CreateMethodDelegate(nameof(Internal_CallAssetLabelsHaveChanged)); + } + + public struct ObjectSelector { + [UnityEditor.InitializeOnLoad] + private static class Statics { + public static readonly Type InternalType = typeof(UnityEditor.Editor).Assembly.GetType("UnityEditor.ObjectSelector", true); + public static readonly StaticAccessor _tooltip = InternalType.CreateStaticPropertyAccessor(nameof(isVisible)); + public static readonly StaticAccessor _get = InternalType.CreateStaticPropertyAccessor(nameof(get), InternalType); + public static readonly InstanceAccessor _searchFilter = InternalType.CreatePropertyAccessor(nameof(searchFilter)); + } + + private EditorWindow _instance; + + public static bool isVisible => Statics._tooltip.GetValue(); + + public static ObjectSelector get => new() { + _instance = Statics._get.GetValue() + }; + + public string searchFilter { + get => Statics._searchFilter.GetValue(_instance); + set => Statics._searchFilter.SetValue(_instance, value); + } + + private static readonly InstanceAccessor _objectSelectorID = Statics.InternalType.CreateFieldAccessor(nameof(objectSelectorID)); + public int objectSelectorID => _objectSelectorID.GetValue(_instance); + } + + [UnityEditor.InitializeOnLoad] + public class InspectorWindow { + public static readonly Type InternalType = typeof(UnityEditor.Editor).Assembly.GetType("UnityEditor.InspectorWindow", true); + public static readonly InstanceAccessor _isLockedAccessor = InternalType.CreatePropertyAccessor(nameof(isLocked)); + + private readonly EditorWindow _instance; + + public InspectorWindow(EditorWindow instance) { + if (instance == null) { + throw new ArgumentNullException(nameof(instance)); + } + + _instance = instance; + } + + public bool isLocked { + get => _isLockedAccessor.GetValue(_instance); + set => _isLockedAccessor.SetValue(_instance, value); + } + } + + [UnityEditor.InitializeOnLoad] + public static class SerializedProperty { + //public static readonly InstanceAccessor hashCodeForPropertyPath = typeof(UnityEditor.SerializedProperty).CreatePropertyAccessor(nameof(hashCodeForPropertyPath)); + public static readonly InstanceAccessor hashCodeForPropertyPathWithoutArrayIndex = typeof(UnityEditor.SerializedProperty).CreatePropertyAccessor(nameof(hashCodeForPropertyPathWithoutArrayIndex)); + } + + [UnityEditor.InitializeOnLoad] + public static class SplitterGUILayout { + public static readonly Action EndHorizontalSplit = CreateMethodDelegate(typeof(UnityEditor.Editor).Assembly, + "UnityEditor.SplitterGUILayout", "EndHorizontalSplit", BindingFlags.Public | BindingFlags.Static + ); + + public static readonly Action EndVerticalSplit = CreateMethodDelegate(typeof(UnityEditor.Editor).Assembly, + "UnityEditor.SplitterGUILayout", "EndVerticalSplit", BindingFlags.Public | BindingFlags.Static + ); + + public static void BeginHorizontalSplit(SplitterState splitterState, GUIStyle style, params GUILayoutOption[] options) { + _beginHorizontalSplit.DynamicInvoke(splitterState.InternalState, style, options); + } + + public static void BeginVerticalSplit(SplitterState splitterState, GUIStyle style, params GUILayoutOption[] options) { + _beginVerticalSplit.DynamicInvoke(splitterState.InternalState, style, options); + } + + private static readonly Delegate _beginHorizontalSplit = CreateMethodDelegate(typeof(UnityEditor.Editor).Assembly, + "UnityEditor.SplitterGUILayout", "BeginHorizontalSplit", BindingFlags.Public | BindingFlags.Static, + typeof(Action<,,>).MakeGenericType(SplitterState.InternalType, typeof(GUIStyle), typeof(GUILayoutOption[])) + ); + + private static readonly Delegate _beginVerticalSplit = CreateMethodDelegate(typeof(UnityEditor.Editor).Assembly, + "UnityEditor.SplitterGUILayout", "BeginVerticalSplit", BindingFlags.Public | BindingFlags.Static, + typeof(Action<,,>).MakeGenericType(SplitterState.InternalType, typeof(GUIStyle), typeof(GUILayoutOption[])) + ); + } + + [UnityEditor.InitializeOnLoad] + [Serializable] + public class SplitterState : ISerializationCallbackReceiver { + + public static readonly Type InternalType = typeof(UnityEditor.Editor).Assembly.GetType("UnityEditor.SplitterState", true); + private static readonly FieldInfo _relativeSizes = InternalType.GetFieldOrThrow("relativeSizes"); + private static readonly FieldInfo _realSizes = InternalType.GetFieldOrThrow("realSizes"); + private static readonly FieldInfo _splitSize = InternalType.GetFieldOrThrow("splitSize"); + + public string Json = "{}"; + + [NonSerialized] + public object InternalState = FromRelativeInner(new[] { 1.0f }); + + void ISerializationCallbackReceiver.OnAfterDeserialize() { + InternalState = JsonUtility.FromJson(Json, InternalType); + } + + void ISerializationCallbackReceiver.OnBeforeSerialize() { + Json = JsonUtility.ToJson(InternalState); + } + + public static SplitterState FromRelative(float[] relativeSizes, int[] minSizes = null, int[] maxSizes = null, int splitSize = 0) { + var result = new SplitterState(); + result.InternalState = FromRelativeInner(relativeSizes, minSizes, maxSizes, splitSize); + return result; + } + + + private static object FromRelativeInner(float[] relativeSizes, int[] minSizes = null, int[] maxSizes = null, int splitSize = 0) { + return Activator.CreateInstance(InternalType, BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.CreateInstance, + null, + new object[] { relativeSizes, minSizes, maxSizes, splitSize }, + null, null); + } + + public float[] realSizes => ConvertArray((Array)_realSizes.GetValue(InternalState)); + public float[] relativeSizes => ConvertArray((Array)_relativeSizes.GetValue(InternalState)); + public float splitSize => Convert.ToSingle(_splitSize.GetValue(InternalState)); + + private static float[] ConvertArray(Array value) { + float[] result = new float[value.Length]; + for (int i = 0; i < value.Length; ++i) { + result[i] = Convert.ToSingle(value.GetValue(i)); + } + return result; + } + } + + public sealed class InternalStyles { + public static InternalStyles Instance = new InternalStyles(); + + internal LazyGUIStyle InspectorTitlebar => LazyGUIStyle.Create(_ => GetStyle("IN Title")); + internal LazyGUIStyle FoldoutTitlebar => LazyGUIStyle.Create(_ => GetStyle("Titlebar Foldout", "Foldout")); + internal LazyGUIStyle BoxWithBorders => LazyGUIStyle.Create(_ => GetStyle("OL Box")); + internal LazyGUIStyle HierarchyTreeViewLine => LazyGUIStyle.Create(_ => GetStyle("TV Line")); + internal LazyGUIStyle HierarchyTreeViewSceneBackground => LazyGUIStyle.Create(_ => GetStyle("SceneTopBarBg", "ProjectBrowserTopBarBg")); + internal LazyGUIStyle OptionsButtonStyle => LazyGUIStyle.Create(_ => GetStyle("PaneOptions")); + internal LazyGUIStyle AddComponentButton => LazyGUIStyle.Create(_ => GetStyle("AC Button")); + internal LazyGUIStyle AnimationEventTooltip => LazyGUIStyle.Create(_ => GetStyle("AnimationEventTooltip")); + internal LazyGUIStyle AnimationEventTooltipArrow => LazyGUIStyle.Create(_ => GetStyle("AnimationEventTooltipArrow")); + + private static GUIStyle GetStyle(params string[] names) { + var skin = GUI.skin; + + foreach (var name in names) { + var result = skin.FindStyle(name); + if (result != null) { + return result; + } + } + + throw new ArgumentOutOfRangeException($"Style not found: {string.Join(", ", names)}", nameof(names)); + } + } + + public static InternalStyles Styles => InternalStyles.Instance; + } +} +#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member +// ReSharper enable InconsistentNaming + +#endregion + + +#region ArrayLengthAttributeDrawer.Odin.cs + +#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED +namespace Fusion.Editor { + using System; + using System.Collections; + using Sirenix.OdinInspector.Editor; + using UnityEditor; + using UnityEngine; + + partial class ArrayLengthAttributeDrawer { + [FusionOdinAttributeConverter] + static System.Attribute[] ConvertToOdinAttributes(System.Reflection.MemberInfo memberInfo, ArrayLengthAttribute attribute) { + return new[] { new OdinAttributeProxy() { SourceAttribute = attribute } }; + } + + class OdinAttributeProxy : Attribute { + public ArrayLengthAttribute SourceAttribute; + } + + [DrawerPriorityAttribute(DrawerPriorityLevel.WrapperPriority)] + class OdinDrawer : OdinAttributeDrawer { + protected override bool CanDrawAttributeProperty(InspectorProperty property) { + return property.GetUnityPropertyType() == SerializedPropertyType.ArraySize; + } + + protected override void DrawPropertyLayout(GUIContent label) { + var valEntry = Property.ValueEntry; + + var weakValues = valEntry.WeakValues; + for (int i = 0; i < weakValues.Count; ++i) { + var values = (IList)weakValues[i]; + if (values == null) { + continue; + } + + var arraySize = values.Count; + var attr = Attribute.SourceAttribute; + if (arraySize < attr.MinLength) { + arraySize = attr.MinLength; + } else if (arraySize > attr.MaxLength) { + arraySize = attr.MaxLength; + } + + if (values.Count != arraySize) { + if (values is Array array) { + var newArr = Array.CreateInstance(array.GetType().GetElementType(), arraySize); + Array.Copy(array, newArr, Math.Min(array.Length, arraySize)); + weakValues.ForceSetValue(i, newArr); + } else { + while (values.Count > arraySize) { + values.RemoveAt(values.Count - 1); + } + + while (values.Count < arraySize) { + values.Add(null); + } + } + + weakValues.ForceMarkDirty(); + } + } + + CallNextDrawer(label); + } + } + } +} +#endif + +#endregion + + +#region BinaryDataAttributeDrawer.Odin.cs + +#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED +namespace Fusion.Editor { + using Sirenix.OdinInspector; + + partial class BinaryDataAttributeDrawer { + [FusionOdinAttributeConverter] + static System.Attribute[] ConvertToOdinAttributes(System.Reflection.MemberInfo memberInfo, BinaryDataAttribute attribute) { + return new[] { new DrawWithUnityAttribute() }; + } + } +} +#endif + +#endregion + + +#region DoIfAttributeDrawer.Odin.cs + +#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED +namespace Fusion.Editor { + using System; + using System.Reflection; + using Sirenix.OdinInspector.Editor; + using UnityEngine; + + partial class DoIfAttributeDrawer { + + protected abstract class OdinProxyAttributeBase : Attribute { + public DoIfAttributeBase SourceAttribute; + } + + protected abstract class OdinDrawerBase : OdinAttributeDrawer where T : OdinProxyAttributeBase { + protected override bool CanDrawAttributeProperty(InspectorProperty property) { + if (property.IsArrayElement(out _)) { + return false; + } + + return true; + } + + protected override void DrawPropertyLayout(GUIContent label) { + + var doIf = this.Attribute.SourceAttribute; + + bool allPassed = true; + bool anyPassed = false; + + var targetProp = Property.FindPropertyRelativeToParent(doIf.ConditionMember); + if (targetProp == null) { + var objType = Property.ParentType; + if (!_cachedGetters.TryGetValue((objType, doIf.ConditionMember), out var getter)) { + // maybe this is a top-level property then and we can use reflection? + if (Property.GetValueDepth() != 0) { + if (doIf.ErrorOnConditionMemberNotFound) { + FusionEditorLog.ErrorInspector($"Can't check condition for {Property.Path}: non-SerializedProperty checks only work for top-level properties"); + } + } else { + try { + _cachedGetters.Add((objType, doIf.ConditionMember), Property.ParentType.CreateGetter(doIf.ConditionMember, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy)); + } catch (Exception e) { + if (doIf.ErrorOnConditionMemberNotFound) { + FusionEditorLog.ErrorInspector($"Can't check condition for {Property.Path}: unable to create getter for {doIf.ConditionMember} with exception {e}"); + } + } + } + } + + if (getter != null) { + foreach (var obj in Property.GetValueParent().ValueEntry.WeakValues) { + var value = getter(obj); + if (DoIfAttributeDrawer.CheckCondition(doIf, value)) { + anyPassed = true; + } else { + allPassed = false; + } + } + } + } else { + foreach (var value in targetProp.ValueEntry.WeakValues) { + if (DoIfAttributeDrawer.CheckCondition(doIf, value)) { + anyPassed = true; + } else { + allPassed = false; + } + } + } + + DrawPropertyLayout(label, allPassed, anyPassed); + } + + protected abstract void DrawPropertyLayout(GUIContent label, bool allPassed, bool anyPassed); + } + } +} +#endif + +#endregion + + +#region DrawIfAttributeDrawer.Odin.cs + +#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED +namespace Fusion.Editor { + using Sirenix.OdinInspector.Editor; + using UnityEditor; + using UnityEngine; + + partial class DrawIfAttributeDrawer { + + [FusionOdinAttributeConverter] + static System.Attribute[] ConvertToOdinAttributes(System.Reflection.MemberInfo memberInfo, DrawIfAttribute attribute) { + return new[] { new OdinAttributeProxy() { SourceAttribute = attribute } }; + } + + class OdinAttributeProxy : OdinProxyAttributeBase { + } + + [DrawerPriority(DrawerPriorityLevel.WrapperPriority)] + class OdinDrawer : OdinDrawerBase { + protected override void DrawPropertyLayout(GUIContent label, bool allPassed, bool anyPassed) { + var attribute = (DrawIfAttribute)Attribute.SourceAttribute; + if (!allPassed) { + if (attribute.Hide) { + return; + } + } + + using (new EditorGUI.DisabledGroupScope(!allPassed)) { + base.CallNextDrawer(label); + } + } + } + } +} +#endif + +#endregion + + +#region DrawInlineAttributeDrawer.Odin.cs + +namespace Fusion.Editor { + partial class DrawInlineAttributeDrawer { +#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED + [FusionOdinAttributeConverter] + static System.Attribute[] ConvertToOdinAttributes(System.Reflection.MemberInfo memberInfo, DrawInlineAttribute attribute) { + return new System.Attribute[] { new Sirenix.OdinInspector.InlinePropertyAttribute(), new Sirenix.OdinInspector.HideLabelAttribute() }; + } +#endif + } +} + +#endregion + + +#region ErrorIfAttributeDrawer.Odin.cs + +#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED +namespace Fusion.Editor { + using Sirenix.OdinInspector.Editor; + using UnityEngine; + + partial class ErrorIfAttributeDrawer { + + [FusionOdinAttributeConverter] + static System.Attribute[] ConvertToOdinAttributes(System.Reflection.MemberInfo memberInfo, ErrorIfAttribute attribute) { + return new[] { new OdinAttributeProxy() { SourceAttribute = attribute } }; + } + + class OdinAttributeProxy : OdinProxyAttributeBase { + } + + [DrawerPriority(DrawerPriorityLevel.WrapperPriority)] + class OdinDrawer : OdinDrawerBase { + protected override void DrawPropertyLayout(GUIContent label, bool allPassed, bool anyPassed) { + var attribute = (ErrorIfAttribute)Attribute.SourceAttribute; + + base.CallNextDrawer(label); + + if (anyPassed) { + using (new FusionEditorGUI.ErrorScope(attribute.Message)) { + } + } + } + } + } +} +#endif + +#endregion + + +#region FieldEditorButtonAttributeDrawer.Odin.cs + +#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED +namespace Fusion.Editor { + using System; + using System.Linq; + using Sirenix.OdinInspector.Editor; + using UnityEditor; + using UnityEngine; + + partial class FieldEditorButtonAttributeDrawer { + + [FusionOdinAttributeConverter] + static System.Attribute[] ConvertToOdinAttributes(System.Reflection.MemberInfo memberInfo, FieldEditorButtonAttribute attribute) { + return new[] { new OdinAttributeProxy() { SourceAttribute = attribute } }; + } + + class OdinAttributeProxy : Attribute { + public FieldEditorButtonAttribute SourceAttribute; + } + + [DrawerPriority(DrawerPriorityLevel.WrapperPriority)] + class OdinDrawer : OdinAttributeDrawer { + protected override bool CanDrawAttributeProperty(InspectorProperty property) { + return !property.IsArrayElement(out _); + } + + protected override void DrawPropertyLayout(GUIContent label) { + CallNextDrawer(label); + + var buttonRect = EditorGUI.IndentedRect(EditorGUILayout.GetControlRect()); + var attribute = Attribute.SourceAttribute; + var root = this.Property.SerializationRoot; + var targetType = root.ValueEntry.TypeOfValue; + var targetObjects = root.ValueEntry.WeakValues + .OfType() + .ToArray(); + + if (DrawButton(buttonRect, attribute, targetType, targetObjects)) { + this.Property.MarkSerializationRootDirty(); + } + } + } + } +} +#endif + +#endregion + + +#region HideArrayElementLabelAttributeDrawer.Odin.cs + +namespace Fusion.Editor { + partial class HideArrayElementLabelAttributeDrawer { +#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED + [FusionOdinAttributeConverter] + static System.Attribute[] ConvertToOdinAttributes(System.Reflection.MemberInfo memberInfo, HideArrayElementLabelAttribute attribute) { + // not yet supported + return System.Array.Empty(); + } +#endif + } +} + +#endregion + + +#region InlineHelpAttributeDrawer.Odin.cs + +#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED +namespace Fusion.Editor { + using System; + using System.Reflection; + using Sirenix.OdinInspector.Editor; + using UnityEditor; + using UnityEngine; + + partial class InlineHelpAttributeDrawer { + + [FusionOdinAttributeConverter] + static System.Attribute[] ConvertToOdinAttributes(System.Reflection.MemberInfo memberInfo, InlineHelpAttribute attribute) { + return new[] { new OdinAttributeProxy() { SourceAttribute = attribute } }; + } + + class OdinAttributeProxy : Attribute { + public InlineHelpAttribute SourceAttribute; + } + + [DrawerPriority(DrawerPriorityLevel.WrapperPriority)] + class OdinDrawer : OdinAttributeDrawer { + protected override bool CanDrawAttributeProperty(InspectorProperty property) { + if (property.IsArrayElement(out _)) { + return false; + } + + var helpContent = GetHelpContent(property, true); + if (helpContent == GUIContent.none) { + return false; + } + + return true; + } + + private Rect _lastRect; + + private bool GetHasFoldout() { + + var (meta, _) = Property.GetNextPropertyDrawerMetaAttribute(Attribute); + if (meta != null) { + return meta.HasFoldout; + } + + return Property.GetUnityPropertyType() == SerializedPropertyType.Generic; + } + + protected override void DrawPropertyLayout(GUIContent label) { + + Rect buttonRect = default; + bool wasExpanded = false; + + bool hasFoldout = GetHasFoldout(); + Rect propertyRect = _lastRect; + var helpContent = GetHelpContent(Property, Attribute.SourceAttribute.ShowTypeHelp); + + using (new FusionEditorGUI.GUIContentScope(label)) { + + (wasExpanded, buttonRect) = InlineHelpAttributeDrawer.DrawInlineHelpBeforeProperty(label, helpContent, _lastRect, Property.Path.GetHashCode(), EditorGUI.indentLevel, hasFoldout, Property.SerializationRoot); + + EditorGUILayout.BeginVertical(); + this.CallNextDrawer(label); + EditorGUILayout.EndVertical(); + } + + if (Event.current.type == EventType.Repaint) { + _lastRect = GUILayoutUtility.GetLastRect(); + } + + if (propertyRect.width > 1 && propertyRect.height > 1) { + + if (wasExpanded) { + var height = FusionEditorGUI.GetInlineBoxSize(helpContent).y; + EditorGUILayout.GetControlRect(false, height); + propertyRect.height += FusionEditorGUI.GetInlineBoxSize(helpContent).y; + } + + DrawInlineHelpAfterProperty(buttonRect, wasExpanded, helpContent, propertyRect); + } + } + + private GUIContent GetHelpContent(InspectorProperty property, bool includeTypeHelp) { + var parentType = property.ValueEntry.ParentType; + var memberInfo = parentType.GetFieldIncludingBaseTypes(property.Name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + return FusionCodeDoc.FindEntry(memberInfo, includeTypeHelp) ?? GUIContent.none; + } + + } + } +} +#endif + +#endregion + + +#region LayerMatrixAttributeDrawer.Odin.cs + +#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED +namespace Fusion.Editor { + using System; + using Sirenix.OdinInspector.Editor; + using UnityEditor; + using UnityEngine; + + partial class LayerMatrixAttributeDrawer { + + [FusionOdinAttributeConverter] + static System.Attribute[] ConvertToOdinAttributes(System.Reflection.MemberInfo memberInfo, LayerMatrixAttribute attribute) { + return new[] { new OdinAttributeProxy() { SourceAttribute = attribute } }; + } + + class OdinAttributeProxy : Attribute { + public LayerMatrixAttribute SourceAttribute; + } + + class OdinDrawer : OdinAttributeDrawer { + protected override void DrawPropertyLayout(GUIContent label) { + + var rect = EditorGUILayout.GetControlRect(); + var valueRect = EditorGUI.PrefixLabel(rect, label); + if (GUI.Button(valueRect, "Edit")) { + int[] values = (int[])this.Property.ValueEntry.WeakValues[0]; + + PopupWindow.Show(valueRect, new LayerMatrixPopup(label.text, (layerA, layerB) => { + if (layerA >= values.Length) { + return false; + } + return (values[layerA] & (1 << layerB)) != 0; + }, (layerA, layerB, val) => { + if (Mathf.Max(layerA, layerB) >= values.Length) { + Array.Resize(ref values, Mathf.Max(layerA, layerB) + 1); + } + + if (val) { + values[layerA] |= (1 << layerB); + values[layerB] |= (1 << layerA); + } else { + values[layerA] &= ~(1 << layerB); + values[layerB] &= ~(1 << layerA); + } + + // sync other values + for (int i = 1; i < this.Property.ValueEntry.ValueCount; ++i) { + this.Property.ValueEntry.WeakValues.ForceSetValue(i, values.Clone()); + } + + Property.MarkSerializationRootDirty(); + })); + } + } + } + } +} +#endif + +#endregion + + +#region FusionOdinAttributeConverterAttribute.cs + +namespace Fusion.Editor { + using System; + + [AttributeUsage(AttributeTargets.Method)] + class FusionOdinAttributeConverterAttribute : Attribute { + } +} + +#endregion + + +#region FusionOdinAttributeProcessor.cs + +#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED +namespace Fusion.Editor { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + using UnityEngine; + + internal class FusionOdinAttributeProcessor : Sirenix.OdinInspector.Editor.OdinAttributeProcessor { + public override void ProcessChildMemberAttributes(Sirenix.OdinInspector.Editor.InspectorProperty parentProperty, MemberInfo member, List attributes) { + for (int i = 0; i < attributes.Count; ++i) { + var attribute = attributes[i]; + if (attribute is PropertyAttribute) { + + var drawerType = FusionEditorGUI.GetDrawerTypeIncludingWorkarounds(attribute); + if (drawerType != null) { + + var method = drawerType.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) + .FirstOrDefault(x => x.IsDefined(typeof(FusionOdinAttributeConverterAttribute))); + + if (method != null) { + var replacementAttributes = (System.Attribute[])method.Invoke(null, new object[] { member, attribute }) ?? Array.Empty(); + + attributes.RemoveAt(i); + FusionEditorLog.TraceInspector($"Replacing attribute {attribute.GetType().FullName} of {member.ToString()} with {string.Join(", ", replacementAttributes.Select(x => x.GetType().FullName))}"); + + if (replacementAttributes.Length > 0) { + attributes.InsertRange(i, replacementAttributes); + } + + i += replacementAttributes.Length - 1; + continue; + } + } + + if (attribute is DecoratingPropertyAttribute) { + FusionEditorLog.Warn($"Unable to replace {nameof(DecoratingPropertyAttribute)}-derived attribute: {attribute.GetType().FullName}"); + attributes.RemoveAt(i--); + } + } + } + } + } +} +#endif + +#endregion + + +#region FusionOdinExtensions.cs + +#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED +namespace Fusion.Editor { + using System; + using System.Reflection; + using Sirenix.OdinInspector.Editor; + using UnityEditor; + using UnityEngine; + + static class FusionOdinExtensions { + public static bool IsArrayElement(this InspectorProperty property, out int index) { + var propertyPath = property.UnityPropertyPath; + + if (!propertyPath.EndsWith("]", StringComparison.Ordinal)) { + index = -1; + return false; + } + + var indexStart = propertyPath.LastIndexOf("[", StringComparison.Ordinal); + if (indexStart < 0) { + index = -1; + return false; + } + + index = int.Parse(propertyPath.Substring(indexStart + 1, propertyPath.Length - indexStart - 2)); + return true; + } + + public static bool IsArrayProperty(this InspectorProperty property) { + var memberType = property.Info.TypeOfValue; + if (!memberType.IsArrayOrList()) { + return false; + } + + return true; + } + + public static int GetValueDepth(this InspectorProperty property) { + int depth = 0; + + var parent = property.GetValueParent(); + while (parent?.IsTreeRoot == false) { + ++depth; + parent = parent.GetValueParent(); + } + + return depth; + } + + public static InspectorProperty GetValueParent(this InspectorProperty property) { + + var parent = property.Parent; + while (parent?.Info.PropertyType == PropertyType.Group) { + parent = parent.Parent; + } + return parent; + } + + public static SerializedPropertyType GetUnityPropertyType(this InspectorProperty inspectorProperty) { + if (inspectorProperty == null) { + throw new ArgumentNullException(nameof(inspectorProperty)); + } + + var valueType = inspectorProperty.ValueEntry.TypeOfValue; + + if (valueType == typeof(bool)) { + return SerializedPropertyType.Boolean; + } else if (valueType == typeof(int) || valueType == typeof(long) || valueType == typeof(short) || valueType == typeof(byte) || valueType == typeof(uint) || valueType == typeof(ulong) || valueType == typeof(ushort) || valueType == typeof(sbyte)) { + return SerializedPropertyType.Integer; + } else if (valueType == typeof(float) || valueType == typeof(double)) { + return SerializedPropertyType.Float; + } else if (valueType == typeof(string)) { + return SerializedPropertyType.String; + } else if (valueType == typeof(Color)) { + return SerializedPropertyType.Color; + } else if (valueType == typeof(LayerMask)) { + return SerializedPropertyType.LayerMask; + } else if (valueType == typeof(Vector2)) { + return SerializedPropertyType.Vector2; + } else if (valueType == typeof(Vector3)) { + return SerializedPropertyType.Vector3; + } else if (valueType == typeof(Vector4)) { + return SerializedPropertyType.Vector4; + } else if (valueType == typeof(Vector2Int)) { + return SerializedPropertyType.Vector2Int; + } else if (valueType == typeof(Vector3Int)) { + return SerializedPropertyType.Vector3Int; + } else if (valueType == typeof(Rect)) { + return SerializedPropertyType.Rect; + } else if (valueType == typeof(RectInt)) { + return SerializedPropertyType.RectInt; + } else if (valueType == typeof(AnimationCurve)) { + return SerializedPropertyType.AnimationCurve; + } else if (valueType == typeof(Bounds)) { + return SerializedPropertyType.Bounds; + } else if (valueType == typeof(BoundsInt)) { + return SerializedPropertyType.BoundsInt; + } else if (valueType == typeof(Gradient)) { + return SerializedPropertyType.Gradient; + } else if (valueType == typeof(Quaternion)) { + return SerializedPropertyType.Quaternion; + } else if (valueType.IsEnum) { + return SerializedPropertyType.Enum; + } else if (typeof(UnityEngine.Object).IsAssignableFrom(valueType)) { + return SerializedPropertyType.ObjectReference; + } else if (valueType.IsArrayOrList()) { + return SerializedPropertyType.ArraySize; + } + + return SerializedPropertyType.Generic; + } + + public static InspectorProperty FindPropertyRelativeToParent(this InspectorProperty property, string path) { + + InspectorProperty referenceProperty = property; + + int parentIndex = 0; + do { + if (referenceProperty.GetValueParent() == null) { + return null; + } + + referenceProperty = referenceProperty.GetValueParent(); + } while (path[parentIndex++] == '^'); + + if (parentIndex > 1) { + path = path.Substring(parentIndex - 1); + } + + var parts = path.Split('.'); + if (parts.Length == 0) { + return null; + } + + foreach (var part in parts) { + var child = referenceProperty.Children[part]; + if (child != null) { + referenceProperty = child; + } else { + return null; + } + } + + return referenceProperty; + } + + public static (FusionPropertyDrawerMetaAttribute, Attribute) GetNextPropertyDrawerMetaAttribute(this InspectorProperty property, Attribute referenceAttribute) { + + var attributeIndex = referenceAttribute == null ? -1 : property.Attributes.IndexOf(referenceAttribute); + + for (int i = attributeIndex + 1; i < property.Attributes.Count; ++i) { + var otherAttribute = property.Attributes[i]; + if (otherAttribute is DrawerPropertyAttribute == false) { + continue; + } + + var attributeDrawerType = FusionEditorGUI.GetDrawerTypeIncludingWorkarounds(otherAttribute); + if (attributeDrawerType == null) { + continue; + } + + var meta = attributeDrawerType.GetCustomAttribute(); + if (meta != null) { + return (meta, otherAttribute); + } + } + + + var propertyDrawerType = UnityInternal.ScriptAttributeUtility.GetDrawerTypeForType(property.ValueEntry.TypeOfValue, false); + + if (propertyDrawerType != null) { + var meta = propertyDrawerType.GetCustomAttribute(); + if (meta != null) { + return (meta, null); + } + } + + return (null, null); + } + } +} +#endif + +#endregion + + +#region ReadOnlyAttributeDrawer.Odin.cs + +namespace Fusion.Editor { + partial class ReadOnlyAttributeDrawer { +#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED + [FusionOdinAttributeConverter] + static System.Attribute[] ConvertToOdinAttributes(System.Reflection.MemberInfo memberInfo, ReadOnlyAttribute attribute) { + if (attribute.InEditMode && attribute.InPlayMode) { + return new[] { new Sirenix.OdinInspector.ReadOnlyAttribute() }; + } + if (attribute.InEditMode) { + return new[] { new Sirenix.OdinInspector.DisableInEditorModeAttribute() }; + } + if (attribute.InPlayMode) { + return new[] { new Sirenix.OdinInspector.DisableInPlayModeAttribute() }; + } + return System.Array.Empty(); + } +#endif + } +} + +#endregion + + +#region SerializeReferenceTypePickerDrawer.Odin.cs + +#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED +namespace Fusion.Editor { + using System; + + partial class SerializeReferenceTypePickerAttributeDrawer { + [FusionOdinAttributeConverter] + static System.Attribute[] ConvertToOdinAttributes(System.Reflection.MemberInfo memberInfo, SerializeReferenceTypePickerAttribute attribute) { + return Array.Empty(); + } + } +} +#endif + +#endregion + + +#region UnitAttributeDrawer.Odin.cs + +#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED +namespace Fusion.Editor { + using System; + using Sirenix.OdinInspector.Editor; + using UnityEditor; + using UnityEngine; + + partial class UnitAttributeDrawer { + + [FusionOdinAttributeConverter] + static System.Attribute[] ConvertToOdinAttributes(System.Reflection.MemberInfo memberInfo, UnitAttribute attribute) { + return new[] { new OdinAttributeProxy() { SourceAttribute = attribute } }; + } + + class OdinAttributeProxy : Attribute { + public UnitAttribute SourceAttribute; + } + + [DrawerPriority(DrawerPriorityLevel.WrapperPriority)] + class OdinUnitAttributeDrawer : Sirenix.OdinInspector.Editor.OdinAttributeDrawer { + private GUIContent _label; + private Rect _lastRect; + + protected override bool CanDrawAttributeProperty(Sirenix.OdinInspector.Editor.InspectorProperty property) { + + for (Attribute attrib = null;;) { + var (meta, nextAttribute) = property.GetNextPropertyDrawerMetaAttribute(attrib); + attrib = nextAttribute; + if (meta?.HandlesUnits == true) { + if (attrib is OdinAttributeProxy == false) { + return false; + } + } + + if (meta == null || attrib == null) { + break; + } + } + + switch (property.GetUnityPropertyType()) { + case SerializedPropertyType.ArraySize: + return false; + default: + return true; + } + } + + protected sealed override void DrawPropertyLayout(GUIContent label) { + + using (new EditorGUILayout.VerticalScope()) { + this.CallNextDrawer(label); + } + + if (Event.current.type == EventType.Repaint) { + _lastRect = GUILayoutUtility.GetLastRect(); + } + + if (_lastRect.width > 1 && _lastRect.height > 1) { + _label ??= new GUIContent(); + _label.text = UnitToLabel(this.Attribute.SourceAttribute.Unit); + DrawUnitOverlay(_lastRect, _label, Property.GetUnityPropertyType(), false, odinStyle: true); + } + } + } + } +} +#endif + +#endregion + + +#region WarnIfAttributeDrawer.Odin.cs + +#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED +namespace Fusion.Editor { + using Sirenix.OdinInspector.Editor; + using UnityEngine; + + partial class WarnIfAttributeDrawer { + + [FusionOdinAttributeConverter] + static System.Attribute[] ConvertToOdinAttributes(System.Reflection.MemberInfo memberInfo, WarnIfAttribute attribute) { + return new[] { new OdinAttributeProxy() { SourceAttribute = attribute } }; + } + + class OdinAttributeProxy : OdinProxyAttributeBase { + } + + [DrawerPriority(DrawerPriorityLevel.WrapperPriority)] + class OdinDrawer : OdinDrawerBase { + protected override void DrawPropertyLayout(GUIContent label, bool allPassed, bool anyPassed) { + var attribute = (WarnIfAttribute)Attribute.SourceAttribute; + + base.CallNextDrawer(label); + + if (anyPassed) { + using (new FusionEditorGUI.WarningScope(attribute.Message)) { + } + } + } + } + } +} +#endif + +#endregion + + +#region ArrayLengthAttributeDrawer.cs + +namespace Fusion.Editor { + using UnityEditor; + using UnityEngine; + + [CustomPropertyDrawer(typeof(ArrayLengthAttribute))] +#if !UNITY_6000_0_OR_NEWER + [RedirectCustomPropertyDrawer(typeof(ArrayLengthAttribute), typeof(ArrayLengthAttributeDrawer))] + partial class PropertyDrawerForArrayWorkaround { + } +#endif + internal partial class ArrayLengthAttributeDrawer : DecoratingPropertyAttributeDrawer, INonApplicableOnArrayElements { + + private GUIStyle _style; + + private GUIStyle GetStyle() { + if (_style == null) { + _style = new GUIStyle(EditorStyles.miniLabel); + _style.alignment = TextAnchor.MiddleRight; + _style.contentOffset = new Vector2(-2, 0); + _style.normal.textColor = EditorGUIUtility.isProSkin ? new Color(255f / 255f, 221 / 255f, 0 / 255f, 1f) : Color.blue; + } + + return _style; + } + + protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) { + + base.OnGUIInternal(position, property, label); + if (!property.isArray) { + return; + } + + var overlayRect = position; + overlayRect.height = EditorGUIUtility.singleLineHeight; + + var attrib = (ArrayLengthAttribute)attribute; + + // draw length overlay + GUI.Label(overlayRect, $"[{attrib.MaxLength}]", GetStyle()); + + if (property.arraySize > attrib.MaxLength) { + property.arraySize = attrib.MaxLength; + property.serializedObject.ApplyModifiedProperties(); + } else if (property.arraySize < attrib.MinLength) { + property.arraySize = attrib.MinLength; + property.serializedObject.ApplyModifiedProperties(); + } + } + } +} + +#endregion + + +#region AssemblyNameAttributeDrawer.cs + +namespace Fusion.Editor { + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using UnityEditor; + using UnityEngine; + + [CustomPropertyDrawer(typeof(AssemblyNameAttribute))] + internal class AssemblyNameAttributeDrawer : PropertyDrawerWithErrorHandling { + const float DropdownWidth = 20.0f; + + static GUIContent DropdownContent = new GUIContent(""); + + string _lastCheckedAssemblyName; + + [Flags] + enum AsmDefType { + Predefined = 1 << 0, + InPackages = 1 << 1, + InAssets = 1 << 2, + Editor = 1 << 3, + Runtime = 1 << 4, + All = Predefined | InPackages | InAssets | Editor | Runtime, + } + + Dictionary _allAssemblies; + + protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) { + var assemblyName = property.stringValue; + bool notFound = false; + + if (!string.IsNullOrEmpty(assemblyName)) { + if (_allAssemblies == null) { + _allAssemblies = GetAssemblies(AsmDefType.All).ToDictionary(x => x.Name, x => x); + } + + if (!_allAssemblies.TryGetValue(assemblyName, out var assemblyInfo)) { + SetInfo($"Assembly not found: {assemblyName}"); + notFound = true; + } else if (((AssemblyNameAttribute)attribute).RequiresUnsafeCode && !assemblyInfo.AllowUnsafeCode) { + if (assemblyInfo.IsPredefined) { + SetError($"Predefined assemblies need 'Allow Unsafe Code' enabled in Player Settings"); + } else { + SetError($"Assembly does not allow unsafe code"); + } + } + } + + using (new FusionEditorGUI.PropertyScope(position, label, property)) { + EditorGUI.BeginChangeCheck(); + + assemblyName = EditorGUI.TextField(new Rect(position) { xMax = position.xMax - DropdownWidth }, + label, + assemblyName, + notFound ? + new GUIStyle(EditorStyles.textField) { + fontStyle = FontStyle.Italic, + normal = new GUIStyleState() { textColor = Color.gray } + } : EditorStyles.textField + ); + + var dropdownRect = EditorGUI.IndentedRect(new Rect(position) { + xMin = position.xMax - DropdownWidth + }); + + if (EditorGUI.DropdownButton(dropdownRect, DropdownContent, FocusType.Passive)) { + GenericMenu.MenuFunction2 onClicked = (userData) => { + property.stringValue = (string)userData; + property.serializedObject.ApplyModifiedProperties(); + UnityInternal.EditorGUI.EndEditingActiveTextField(); + ClearError(property); + }; + + var menu = new GenericMenu(); + + foreach (var (flag, prefix) in new[] { + (AsmDefType.Editor, "Editor/"), + (AsmDefType.Runtime, "") + }) { + if (menu.GetItemCount() != 0) { + menu.AddSeparator(prefix); + } + + foreach (var asm in GetAssemblies(flag | AsmDefType.InPackages)) { + menu.AddItem(new GUIContent($"{prefix}Packages/{asm.Name}"), string.Equals(asm.Name, assemblyName, StringComparison.OrdinalIgnoreCase), onClicked, asm.Name); + } + + menu.AddSeparator(prefix); + + foreach (var asm in GetAssemblies(flag | AsmDefType.InAssets | AsmDefType.Predefined)) { + menu.AddItem(new GUIContent($"{prefix}{asm.Name}"), string.Equals(asm.Name, assemblyName, StringComparison.OrdinalIgnoreCase), onClicked, asm.Name); + } + } + + menu.DropDown(dropdownRect); + } + + if (EditorGUI.EndChangeCheck()) { + property.stringValue = assemblyName; + property.serializedObject.ApplyModifiedProperties(); + base.ClearError(); + } + } + } + + static IEnumerable GetAssemblies(AsmDefType types) { + var result = new Dictionary(StringComparer.OrdinalIgnoreCase); + + if (types.HasFlag(AsmDefType.Predefined)) { + if (types.HasFlag(AsmDefType.Runtime)) { + yield return new AssemblyInfo("Assembly-CSharp-firstpass", PlayerSettings.allowUnsafeCode, true); + yield return new AssemblyInfo("Assembly-CSharp", PlayerSettings.allowUnsafeCode, true); + } + + if (types.HasFlag(AsmDefType.Editor)) { + yield return new AssemblyInfo("Assembly-CSharp-Editor-firstpass", PlayerSettings.allowUnsafeCode, true); + yield return new AssemblyInfo("Assembly-CSharp-Editor", PlayerSettings.allowUnsafeCode, true); + } + } + + if (types.HasFlag(AsmDefType.InAssets) || types.HasFlag(AsmDefType.InPackages)) { + var query = AssetDatabase.FindAssets("t:asmdef") + .Select(x => AssetDatabase.GUIDToAssetPath(x)) + .Where(x => { + if (types.HasFlag(AsmDefType.InAssets) && x.StartsWith("Assets/")) { + return true; + } else if (types.HasFlag(AsmDefType.InPackages) && x.StartsWith("Packages/")) { + return true; + } else { + return false; + } + }) + .Select(x => JsonUtility.FromJson(File.ReadAllText(x))) + .Where(x => { + bool editorOnly = x.includePlatforms.Length == 1 && x.includePlatforms[0] == "Editor"; + if (types.HasFlag(AsmDefType.Runtime) && !editorOnly) { + return true; + } else if (types.HasFlag(AsmDefType.Editor) && editorOnly) { + return true; + } else { + return false; + } + }); + + foreach (var asmdef in query) { + yield return new AssemblyInfo(asmdef.name, asmdef.allowUnsafeCode, false); + } + } + } + + [Serializable] + private class AsmDefData { + public string[] includePlatforms = Array.Empty(); + public string name = string.Empty; + public bool allowUnsafeCode; + } + + private struct AssemblyInfo { + public string Name; + public bool AllowUnsafeCode; + public bool IsPredefined; + + public AssemblyInfo(string name, bool allowUnsafeCode, bool isPredefined) { + Name = name; + AllowUnsafeCode = allowUnsafeCode; + IsPredefined = isPredefined; + } + } + } +} + +#endregion + + +#region BinaryDataAttributeDrawer.cs + +namespace Fusion.Editor { + using UnityEditor; + using UnityEngine; + + [CustomPropertyDrawer(typeof(BinaryDataAttribute))] +#if !UNITY_6000_0_OR_NEWER + [RedirectCustomPropertyDrawer(typeof(BinaryDataAttribute), typeof(BinaryDataAttributeDrawer))] + partial class PropertyDrawerForArrayWorkaround { + } +#endif + internal partial class BinaryDataAttributeDrawer : PropertyDrawerWithErrorHandling, INonApplicableOnArrayElements { + + private int MaxLines = 16; + private RawDataDrawer _drawer = new RawDataDrawer(); + + protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) { + using (new FusionEditorGUI.PropertyScope(position, label, property)) { + bool wasExpanded = property.isExpanded; + + var foldoutPosition = new Rect(position) { height = EditorGUIUtility.singleLineHeight }; + property.isExpanded = EditorGUI.Foldout(foldoutPosition, property.isExpanded, label); + + if (property.hasMultipleDifferentValues) { + FusionEditorGUI.Overlay(foldoutPosition, $"---"); + } else { + FusionEditorGUI.Overlay(foldoutPosition, $"{property.arraySize}"); + } + + if (!wasExpanded) { + return; + } + + position.yMin += foldoutPosition.height + EditorGUIUtility.standardVerticalSpacing; + using (new FusionEditorGUI.EnabledScope(true)) { + _drawer.Draw(GUIContent.none, position); + } + } + } + + public override float GetPropertyHeight(SerializedProperty property, GUIContent label) { + + if (!property.isExpanded) { + return EditorGUIUtility.singleLineHeight; + } + + _drawer.Refresh(property); + + // space for scrollbar and indent + var width = UnityInternal.EditorGUIUtility.contextWidth - 32.0f; + var height = _drawer.GetHeight(width); + + return EditorGUIUtility.singleLineHeight + + EditorGUIUtility.standardVerticalSpacing + + Mathf.Min(FusionEditorGUI.GetLinesHeight(MaxLines), height); + } + } +} + +#endregion + + +#region BitSetAttributeDrawer.cs + +// namespace Fusion.Editor { +// using System; +// using UnityEditor; +// using UnityEngine; +// +// [CustomPropertyDrawer(typeof(BitSetAttribute))] +// public class BitSetAttributeDrawer : PropertyDrawer { +// +// public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { +// +// if (property.IsArrayElement()) { +// throw new NotSupportedException(); +// } +// +// var longValue = property.longValue; +// +// int bitStart = 0; +// int bitEnd = ((BitSetAttribute)attribute).BitCount; +// +// using (new FusionEditorGUI.PropertyScopeWithPrefixLabel(position, label, property, out var valueRect)) { +// var pos = valueRect; +// +// DrawAndMeasureLabel(valueRect, bitStart, FusionEditorSkin.instance.MiniLabelLowerRight); +// DrawAndMeasureLabel(valueRect, bitEnd, FusionEditorSkin.instance.MiniLabelLowerLeft); +// +// var tmpContent = new GUIContent(); +// tmpContent.text = $"{bitStart}"; +// var bitStartSize = EditorStyles.miniLabel.CalcSize(tmpContent); +// +// +// tmpContent.text = $"{bitEnd}"; +// var bitEndSize = EditorStyles.miniLabel.CalcSize(tmpContent); +// valueRect.width = bitEndSize.x; +// GUI.Label(valueRect, tmpContent, EditorStyles.miniLabel); +// valueRect.x += bitEndSize.x; +// var availableWidth = valueRect.width - bitStartSize.x - bitEndSize.x; +// +// +// // how may per one line? +// const float ToggleWidth = 15.0f; +// +// valueRect.width = ToggleWidth; +// for (int i = 0; i < 16; ++i) { +// EditorGUI.Toggle(valueRect, false); +// valueRect.x += ToggleWidth; +// } +// } +// +// float DrawAndMeasureLabel(Rect position, int label, GUIStyle style) { +// var tmpContent = new GUIContent($"{bitEnd}"); +// var contentSize = style.CalcSize(tmpContent); +// GUI.Label(position, tmpContent, style); +// return contentSize.x; +// } +// +// //base.OnGUI(position, property, label); +// } +// } +// } + +#endregion + + +#region DecoratingPropertyAttributeDrawer.cs + +namespace Fusion.Editor { + using System; + using System.Collections.Generic; + using System.Linq; + using Unity.Profiling; + using UnityEditor; + using UnityEngine; + + internal abstract class DecoratingPropertyAttributeDrawer : PropertyDrawer { + bool _isLastDrawer; + int _nestingLevel; + bool _isInitialized; + + public PropertyDrawer NextDrawer { get; private set; } + + public DecoratingPropertyAttributeDrawer() { + FusionEditorLog.TraceInspector(GetLogMessage("constructor")); + } + + [Obsolete("Derived classes should override and call OnGUIInternal", true)] +#pragma warning disable CS0809 // Obsolete member overrides non-obsolete member + public sealed override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { +#pragma warning restore CS0809 // Obsolete member overrides non-obsolete member + FusionEditorLog.TraceInspector(GetLogMessage($"OnGUI({position}, {property.propertyPath}, {label})")); + EnsureInitialized(property); + InvokeOnGUIInternal(position, property, label); + } + + [Obsolete("Derived classes should override and call GetPropertyHeightInternal", true)] +#pragma warning disable CS0809 // Obsolete member overrides non-obsolete member + public sealed override float GetPropertyHeight(SerializedProperty property, GUIContent label) { +#pragma warning restore CS0809 // Obsolete member overrides non-obsolete member + FusionEditorLog.TraceInspector(GetLogMessage($"GetPropertyHeight({property.propertyPath}, {label})")); + EnsureInitialized(property); + return InvokeGetPropertyHeightInternal(property, label); + } + + protected virtual float GetPropertyHeightInternal(SerializedProperty property, GUIContent label) { + return InvokeGetPropertyHeightOnNextDrawer(property, label); + } + + protected virtual void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) { + FusionEditorLog.TraceInspector(GetLogMessage($"OnGUIInternal({position}, {property.propertyPath}, {label})")); + + if (_nestingLevel != 0) { + FusionEditorLog.Assert(false, $"{property.propertyPath} {GetType().FullName}"); + } + _nestingLevel++; + try { + InvokeOnGUIOnNextDrawer(this, position, property, label); + } finally { + _nestingLevel--; + } + } + + private void InvokeOnGUIOnNextDrawer(DecoratingPropertyAttributeDrawer current, Rect position, SerializedProperty prop, GUIContent label) { + if (NextDrawer != null) { + NextDrawer.OnGUI(position, prop, label); + } else { + FusionEditorGUI.ForwardPropertyField(position, prop, label, prop.ShouldIncludeChildren(), _isLastDrawer); + } + } + + private float InvokeGetPropertyHeightOnNextDrawer(SerializedProperty prop, GUIContent label) { + if (NextDrawer != null) { + return NextDrawer.GetPropertyHeight(prop, label); + } + + var includeChildren = prop.ShouldIncludeChildren(); + if (_isLastDrawer && !includeChildren) { + return EditorGUI.GetPropertyHeight(prop.propertyType, label); + } + return EditorGUI.GetPropertyHeight(prop, label, includeChildren); + } + + private void InvokeOnGUIInternal(Rect position, SerializedProperty prop, GUIContent label) { + if (this is INonApplicableOnArrayElements && prop.IsArrayElement()) { + InvokeOnGUIOnNextDrawer(this, position, prop, label); + } else { + OnGUIInternal(position, prop, label); + } + } + + + private float InvokeGetPropertyHeightInternal(SerializedProperty prop, GUIContent label) { + if (this is INonApplicableOnArrayElements && prop.IsArrayElement()) { + return InvokeGetPropertyHeightOnNextDrawer(prop, label); + } else { + return GetPropertyHeightInternal(prop, label); + } + } + + protected virtual bool EnsureInitialized(SerializedProperty property) { + if (_isInitialized) { + return false; + } + + if (fieldInfo == null) { + // this might happen if this drawer is created dynamically + var field = UnityInternal.ScriptAttributeUtility.GetFieldInfoFromProperty(property, out _); + FusionEditorLog.Assert(field != null, $"Could not find field for property {property.propertyPath} of type {property.serializedObject.targetObject.GetType().FullName} (I'm {GetType().FullName} {GetHashCode()})"); + UnityInternal.PropertyDrawer.SetFieldInfo(this, field); + } + + FusionEditorLog.Assert(attribute != null); + FusionEditorLog.Assert(attribute is DecoratingPropertyAttribute, $"Expected attribute to be of type {nameof(DecoratingPropertyAttribute)} but it's {attribute.GetType().FullName}"); + + _isInitialized = true; + NextDrawer = null; + + var isLastDrawer = false; + var foundSelf = false; + + var fieldAttributes = fieldInfo != null ? UnityInternal.ScriptAttributeUtility.GetFieldAttributes(fieldInfo) : null; + + if (fieldAttributes != null) { + FusionEditorLog.Assert(fieldAttributes.OrderBy(x => x.order).SequenceEqual(fieldAttributes), "Expected field attributes to be sorted"); + FusionEditorLog.Assert(fieldAttributes.Count > 0); + + for (var i = 0; i < fieldAttributes.Count; ++i) { + var fieldAttribute = fieldAttributes[i]; + + var attributeDrawerType = UnityInternal.ScriptAttributeUtility.GetDrawerTypeForPropertyAndType(property, fieldAttribute.GetType()); + if (attributeDrawerType == null) { + FusionEditorLog.TraceInspector(GetLogMessage($"No drawer for {attributeDrawerType}")); + continue; + } + +#if !UNITY_6000_0_OR_NEWER + if (attributeDrawerType == typeof(PropertyDrawerForArrayWorkaround)) { + attributeDrawerType = PropertyDrawerForArrayWorkaround.GetDrawerType(fieldAttribute.GetType()); + } +#endif + + if (attributeDrawerType.IsSubclassOf(typeof(DecoratorDrawer))) { + // decorators are their own thing + continue; + } + + if (property.IsArrayElement() && attributeDrawerType.GetInterface(typeof(INonApplicableOnArrayElements).FullName) != null) { + // skip drawers that are not meant to be used on array elements + continue; + } + + FusionEditorLog.Assert(attributeDrawerType.IsSubclassOf(typeof(PropertyDrawer))); + + if (!foundSelf && fieldAttribute.Equals(attribute)) { + // self + foundSelf = true; + isLastDrawer = true; + FusionEditorLog.TraceInspector(GetLogMessage($"Found self at {i} ({this})")); + continue; + } + + isLastDrawer = false; + } + } + + if (NextDrawer == null && isLastDrawer && fieldInfo != null) { + // try creating type drawer instead + var fieldType = fieldInfo.FieldType; + if (property.IsArrayElement()) { + fieldType = fieldType.GetUnityLeafType(); + } + + var typeDrawerType = UnityInternal.ScriptAttributeUtility.GetDrawerTypeForPropertyAndType(property, fieldType); + if (typeDrawerType != null) { + var drawer = (PropertyDrawer)Activator.CreateInstance(typeDrawerType); + UnityInternal.PropertyDrawer.SetFieldInfo(drawer, fieldInfo); + FusionEditorLog.TraceInspector(GetLogMessage($"Found final drawer is type drawer ({drawer})")); + NextDrawer = drawer; + } + } + + if (isLastDrawer) { + _isLastDrawer = true; + } + + return true; + } + + internal void InitInjected(PropertyDrawer next) { + _isInitialized = true; + NextDrawer = next; + } + + public PropertyDrawer GetNextDrawer(SerializedProperty property) { + if (NextDrawer != null) { + return NextDrawer; + } + + var handler = UnityInternal.ScriptAttributeUtility.propertyHandlerCache.GetHandler(property); + var drawers = handler.m_PropertyDrawers; + var index = drawers.IndexOf(this); + if (index >= 0 && index < drawers.Count - 1) { + return drawers[index + 1]; + } + + return null; + } + + private string GetLogMessage(string message) { + return $"[{GetType().FullName}] [{GetHashCode():X8}] [{fieldInfo?.DeclaringType?.Name}.{fieldInfo?.Name}] {message}"; + } + } +} + +#endregion + + +#region DirectoryPathAttributeDrawer.cs + +namespace Fusion.Editor { + using System; + using System.Collections.Generic; + using System.IO; + using UnityEditor; + using UnityEngine; + + [CustomPropertyDrawer(typeof(DirectoryPathAttribute))] + class DirectoryPathAttributeDrawer : PropertyDrawerWithErrorHandling { + const int MinWidthRequired = 150; + static readonly GUIContent ButtonContent = new GUIContent("..."); + static (string PropertyPath, string Path) _awaitingProperty; + + + protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) { + if (property.propertyType != SerializedPropertyType.String) { + throw new InvalidOperationException($"Only applicable on string properties"); + } + + if (position.width >= MinWidthRequired) { + var buttonWidth = EditorStyles.miniButton.CalcSize(ButtonContent); + position.width -= buttonWidth.x; + + if (GUI.Button(new Rect(position.xMax, position.y, buttonWidth.x, EditorGUIUtility.singleLineHeight), ButtonContent)) { + string propertyPath = property.propertyPath; + string initialFolder = ExpandAndMakeAbsoluteSafe(property.stringValue); + if (!Directory.Exists(initialFolder)) { + initialFolder = "Assets"; + } + + // this can't be done synchronously - something beaks within Unity drawer stack and there's a cryptic + // exception logged + EditorApplication.delayCall += () => { + var path = EditorUtility.OpenFolderPanel("", folder: initialFolder, ""); + + if (string.IsNullOrEmpty(path)) { + return; + } + + path = Path.GetRelativePath(".", path); + path = PathUtils.Normalize(path); + + _awaitingProperty = (propertyPath, path); + EditorApplication.delayCall += () => { + // clear the awaiter in case the property is no longer there + _awaitingProperty = default; + }; + }; + } + } + + EditorGUI.PropertyField(position, property, label); + + if (_awaitingProperty.PropertyPath?.Equals(property.propertyPath) == true) { + property.stringValue = _awaitingProperty.Path; + property.serializedObject.ApplyModifiedProperties(); + _awaitingProperty = default; + } + + if (Directory.Exists(ExpandAndMakeAbsoluteSafe(property.stringValue))) { + ClearError(); + } else { + SetError($"Folder does not exist"); + } + } + + static string ExpandAndMakeAbsoluteSafe(string path) { + var expanded = Environment.ExpandEnvironmentVariables(path); + if (string.IsNullOrEmpty(expanded)) { + return string.Empty; + } + + try { + return Path.GetFullPath(expanded); + } catch { + return string.Empty; + } + } + } +} + +#endregion + + +#region DisplayAsEnumAttributeDrawer.cs + +namespace Fusion.Editor { + using System; + using System.Collections.Generic; + using System.Reflection; + using UnityEditor; + using UnityEngine; + + [CustomPropertyDrawer(typeof(DisplayAsEnumAttribute))] + internal class DisplayAsEnumAttributeDrawer : PropertyDrawerWithErrorHandling { + + private EnumDrawer _enumDrawer; + private Dictionary<(Type, string), Func> _cachedGetters = new Dictionary<(Type, string), Func>(); + + protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) { + var attr = (DisplayAsEnumAttribute)attribute; + var enumType = attr.EnumType; + + if (enumType == null && !string.IsNullOrEmpty(attr.EnumTypeMemberName)) { + + var objType = property.serializedObject.targetObject.GetType(); + if (!_cachedGetters.TryGetValue((objType, attr.EnumTypeMemberName), out var getter)) { + // maybe this is a top-level property then and we can use reflection? + if (property.depth != 0) { + FusionEditorLog.ErrorInspector($"Can't get enum type for {property.propertyPath}: non-SerializedProperty checks only work for top-level properties"); + } else { + try { + getter = objType.CreateGetter(attr.EnumTypeMemberName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy); + } catch (Exception e) { + FusionEditorLog.ErrorInspector($"Can't get enum type for {property.propertyPath}: unable to create getter for {attr.EnumTypeMemberName} with exception {e}"); + } + } + + _cachedGetters.Add((objType, attr.EnumTypeMemberName), getter); + } + + enumType = getter(property.serializedObject.targetObject); + } + + using (new FusionEditorGUI.PropertyScopeWithPrefixLabel(position, label, property, out var valueRect)) { + if (enumType == null) { + SetError($"Unable to get enum type for {property.propertyPath}"); + } else if (!enumType.IsEnum) { + SetError($"Type {enumType} is not an enum type"); + } else { + ClearError(); + _enumDrawer.Draw(valueRect, property, enumType, true); + } + } + } + } +} + +#endregion + + +#region DisplayNameAttributeDrawer.cs + +namespace Fusion.Editor { + using UnityEditor; + using UnityEngine; + + [CustomPropertyDrawer(typeof(DisplayNameAttribute))] +#if !UNITY_6000_0_OR_NEWER + [RedirectCustomPropertyDrawer(typeof(DisplayNameAttribute), typeof(DisplayNameAttributeDrawer))] + partial class PropertyDrawerForArrayWorkaround { + } +#endif + internal class DisplayNameAttributeDrawer : DecoratingPropertyAttributeDrawer, INonApplicableOnArrayElements { + private GUIContent _label = new GUIContent(); + + protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) { + if (((DisplayNameAttribute)attribute).Name == null) { + base.OnGUIInternal(position, property, label); + return; + } + if (label.text == string.Empty && label.image == null || property.IsArrayElement()) { + base.OnGUIInternal(position, property, label); + return; + } + _label.text = ((DisplayNameAttribute)attribute).Name; + _label.image = label.image; + _label.tooltip = label.tooltip; + base.OnGUIInternal(position, property, _label); + } + +#if ODIN_INSPECTOR && !FUSION_ODIN_DISABLED + [FusionOdinAttributeConverter] + static System.Attribute[] ConvertToOdinAttributes(System.Reflection.MemberInfo memberInfo, DisplayNameAttribute attribute) { + return new[] { new Sirenix.OdinInspector.LabelTextAttribute(attribute.Name) }; + } +#endif + } +} + +#endregion + + +#region DoIfAttributeDrawer.cs + +namespace Fusion.Editor { + using System; + using System.Collections.Generic; + using System.Reflection; + using UnityEditor; + + internal abstract partial class DoIfAttributeDrawer : DecoratingPropertyAttributeDrawer, INonApplicableOnArrayElements { + + private static Dictionary<(Type, string), Func> _cachedGetters = new Dictionary<(Type, string), Func>(); + + internal static bool CheckDraw(DoIfAttributeBase doIf, SerializedObject serializedObject) { + var compareProperty = serializedObject.FindProperty(doIf.ConditionMember); + + if (compareProperty != null) { + return CheckProperty(doIf, compareProperty); + } + + return CheckGetter(doIf, serializedObject, 0, string.Empty) == true; + } + + internal static bool CheckDraw(DoIfAttributeBase doIf, SerializedProperty property) { + var compareProperty = property.depth < 0 ? property.FindPropertyRelative(doIf.ConditionMember) : property.FindPropertyRelativeToParent(doIf.ConditionMember); + + if (compareProperty != null) { + return CheckProperty(doIf, compareProperty); + } + + return CheckGetter(doIf, property.serializedObject, property.depth, property.propertyPath) == true; + } + + private static bool CheckProperty(DoIfAttributeBase doIf, SerializedProperty compareProperty) { + switch (compareProperty.propertyType) { + case SerializedPropertyType.Boolean: + case SerializedPropertyType.Integer: + case SerializedPropertyType.Enum: + case SerializedPropertyType.Character: + return CheckCondition(doIf, compareProperty.longValue); + + case SerializedPropertyType.ObjectReference: + return CheckCondition(doIf, compareProperty.objectReferenceInstanceIDValue); + + case SerializedPropertyType.Float: + return CheckCondition(doIf, compareProperty.doubleValue); + + default: + FusionEditorLog.ErrorInspector($"Can't check condition for {compareProperty.propertyPath}: unsupported property type {compareProperty.propertyType}"); + return true; + } + } + + private static bool? CheckGetter(DoIfAttributeBase doIf, SerializedObject serializedObject, int depth, string referencePath) { + var objType = serializedObject.targetObject.GetType(); + if (!_cachedGetters.TryGetValue((objType, doIf.ConditionMember), out var getter)) { + // maybe this is a top-level property then and we can use reflection? + if (depth != 0) { + if (doIf.ErrorOnConditionMemberNotFound) { + FusionEditorLog.ErrorInspector($"Can't check condition for {referencePath}: non-SerializedProperty checks only work for top-level properties (depth:{depth}, conditionMember:{doIf.ConditionMember})"); + } + } else { + try { + getter = objType.CreateGetter(doIf.ConditionMember, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy); + } catch (Exception e) { + if (doIf.ErrorOnConditionMemberNotFound) { + FusionEditorLog.ErrorInspector($"Can't check condition for {referencePath}: unable to create getter for {doIf.ConditionMember} with exception {e}"); + } + } + } + + _cachedGetters.Add((objType, doIf.ConditionMember), getter); + } + + if (getter != null) { + bool? result = null; + foreach (var target in serializedObject.targetObjects) { + bool targetResult = CheckCondition(doIf, getter(target)); + if (result.HasValue && result.Value != targetResult) { + return null; + } else { + result = targetResult; + } + } + + return result; + } else { + return true; + } + } + + public static bool CheckCondition(DoIfAttributeBase attribute, double value) { + if (!attribute._isDouble) throw new InvalidOperationException(); + + var doubleValue = attribute._doubleValue; + switch (attribute.Compare) { + case CompareOperator.Equal: return value == doubleValue; + case CompareOperator.NotEqual: return value != doubleValue; + case CompareOperator.Less: return value < doubleValue; + case CompareOperator.LessOrEqual: return value <= doubleValue; + case CompareOperator.GreaterOrEqual: return value >= doubleValue; + case CompareOperator.Greater: return value > doubleValue; + case CompareOperator.NotZero: return value != 0; + case CompareOperator.IsZero: return value == 0; + case CompareOperator.BitwiseAndNotEqualZero: throw new NotSupportedException(); + default: throw new ArgumentOutOfRangeException(); + } + } + + public static bool CheckCondition(DoIfAttributeBase attribute, long value) { + if (attribute._isDouble) throw new InvalidOperationException(); + + var _longValue = attribute._longValue; + switch (attribute.Compare) { + case CompareOperator.Equal: return value == _longValue; + case CompareOperator.NotEqual: return value != _longValue; + case CompareOperator.Less: return value < _longValue; + case CompareOperator.LessOrEqual: return value <= _longValue; + case CompareOperator.GreaterOrEqual: return value >= _longValue; + case CompareOperator.Greater: return value > _longValue; + case CompareOperator.NotZero: return value != 0; + case CompareOperator.IsZero: return value == 0; + case CompareOperator.BitwiseAndNotEqualZero: return (value & _longValue) != 0; + default: throw new ArgumentOutOfRangeException(); + } + } + + public static bool CheckCondition(DoIfAttributeBase attribute, object value) { + if (attribute._isDouble) { + double converted = 0.0; + if (value != null) { + if (value is UnityEngine.Object o && !o) { + // treat as 0 + } else if (value.GetType().IsValueType) { + converted = Convert.ToDouble(value); + } else { + converted = 1.0; + } + } + + return CheckCondition(attribute, converted); + } else { + long converted = 0; + if (value != null) { + if (value is UnityEngine.Object o && !o) { + // treat as 0 + } else if (value.GetType().IsValueType) { + converted = Convert.ToInt64(value); + } else { + converted = 1; + } + } + + return CheckCondition(attribute, converted); + } + } + } +} + +#endregion + + +#region DrawIfAttributeDrawer.cs + +namespace Fusion.Editor { + using UnityEditor; + using UnityEngine; + + [CustomPropertyDrawer(typeof(DrawIfAttribute))] +#if !UNITY_6000_0_OR_NEWER + [RedirectCustomPropertyDrawer(typeof(DrawIfAttribute), typeof(DrawIfAttributeDrawer))] + partial class PropertyDrawerForArrayWorkaround { + } +#endif + internal partial class DrawIfAttributeDrawer : DoIfAttributeDrawer { + public DrawIfAttribute Attribute => (DrawIfAttribute)attribute; + + protected override float GetPropertyHeightInternal(SerializedProperty property, GUIContent label) { + if (Attribute.Mode == DrawIfMode.ReadOnly || CheckDraw(Attribute, property)) { + return base.GetPropertyHeightInternal(property, label); + } + + return -EditorGUIUtility.standardVerticalSpacing; + } + + protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) { + var readOnly = Attribute.Mode == DrawIfMode.ReadOnly; + var draw = CheckDraw(Attribute, property); + + if (readOnly || draw) { + EditorGUI.BeginDisabledGroup(!draw); + + base.OnGUIInternal(position, property, label); + + EditorGUI.EndDisabledGroup(); + } + } + } + + +} + +#endregion + + +#region DrawInlineAttributeDrawer.cs + +namespace Fusion.Editor { + using UnityEditor; + using UnityEngine; + + [CustomPropertyDrawer(typeof(DrawInlineAttribute))] + [FusionPropertyDrawerMeta(HasFoldout = false)] + internal partial class DrawInlineAttributeDrawer : PropertyDrawer { + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { + EditorGUI.BeginProperty(position, label, property); + + foreach (var childProperty in property.GetChildren()) { + position.height = FusionEditorGUI.GetPropertyHeight(childProperty); + EditorGUI.PropertyField(position, childProperty, true); + position.y += position.height + EditorGUIUtility.standardVerticalSpacing; + } + + EditorGUI.EndProperty(); + } + + public override float GetPropertyHeight(SerializedProperty property, GUIContent label) { + float height = 0f; + + foreach (var childProperty in property.GetChildren()) { + height += FusionEditorGUI.GetPropertyHeight(childProperty) + EditorGUIUtility.standardVerticalSpacing; + } + + height -= EditorGUIUtility.standardVerticalSpacing; + return height; + } + } +} + +#endregion + + +#region ErrorIfAttributeDrawer.cs + +namespace Fusion.Editor { + using UnityEditor; + using UnityEngine; + + [CustomPropertyDrawer(typeof(ErrorIfAttribute))] +#if !UNITY_6000_0_OR_NEWER + [RedirectCustomPropertyDrawer(typeof(ErrorIfAttribute), typeof(ErrorIfAttributeDrawer))] + partial class PropertyDrawerForArrayWorkaround { + } +#endif + internal partial class ErrorIfAttributeDrawer : MessageIfDrawerBase { + private new ErrorIfAttribute Attribute => (ErrorIfAttribute)attribute; + + protected override bool IsBox => Attribute.AsBox; + protected override string Message => Attribute.Message; + protected override MessageType MessageType => MessageType.Error; + protected override Color InlineBoxColor => FusionEditorSkin.ErrorInlineBoxColor; + protected override Texture MessageIcon => FusionEditorSkin.ErrorIcon; + } +} + + +#endregion + + +#region ExpandableEnumAttributeDrawer.cs + +namespace Fusion.Editor { + using System; + using System.Reflection; + using UnityEditor; + using UnityEngine; + + [CustomPropertyDrawer(typeof(ExpandableEnumAttribute))] + internal class ExpandableEnumAttributeDrawer : PropertyDrawerWithErrorHandling { + + private const float ToggleIndent = 5; + + private readonly GUIContent[] _gridOptions = new[] { new GUIContent("Nothing"), new GUIContent("Everything") }; + private EnumDrawer _enumDrawer; + private readonly LazyGUIStyle _buttonStyle = LazyGUIStyle.Create(_ => new GUIStyle(EditorStyles.miniButton) { fontSize = EditorStyles.miniButton.fontSize - 1 }); + + private new ExpandableEnumAttribute attribute => (ExpandableEnumAttribute)base.attribute; + + protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) { + + bool wasExpanded = attribute.AlwaysExpanded || property.isExpanded; + + var rowRect = new Rect(position) { + height = EditorGUIUtility.singleLineHeight, + }; + + using (new FusionEditorGUI.PropertyScope(position, label, property)) { + var valueRect = EditorGUI.PrefixLabel(rowRect, label); + + bool isEnum = property.propertyType == SerializedPropertyType.Enum; + var maskProperty = isEnum ? property : property.FindPropertyRelative("Mask").FindPropertyRelative("values"); + + Mask256 rawValue; + if (isEnum) { + rawValue = new Mask256(maskProperty.longValue); + + } else { + rawValue = new Mask256( + maskProperty.GetFixedBufferElementAtIndex(0).longValue, + maskProperty.GetFixedBufferElementAtIndex(1).longValue, + maskProperty.GetFixedBufferElementAtIndex(2).longValue, + maskProperty.GetFixedBufferElementAtIndex(3).longValue + ); + } + var foldoutRect = new Rect(valueRect) { width = FusionEditorGUI.FoldoutWidth }; + valueRect.xMin += foldoutRect.width; + + EditorGUI.BeginChangeCheck(); + if (wasExpanded) { + if (_enumDrawer.IsFlags && attribute.ShowFlagsButtons) { + int gridValue = -1; + if (rawValue.IsNothing()) { + // nothing + gridValue = 0; + } else if (Equals(_enumDrawer.BitMask & rawValue, _enumDrawer.BitMask)) { + + var test = _enumDrawer.BitMask & rawValue; + if (Equals(test, _enumDrawer.BitMask)) + // everything + gridValue = 1; + } + + // traverse values in reverse; make sure the first alias is used in case there are multiple + if (isEnum) { + for (int i = _enumDrawer.Values.Length; i-- > 0;) { + if (_enumDrawer.Values[i] == 0) { + _gridOptions[0].text = _enumDrawer.Names[i]; + } else if ( _enumDrawer.Values[i] == _enumDrawer.BitMask[0]) { + // Unity's drawer does not replace "Everything" + _gridOptions[1].text = _enumDrawer.Names[i]; + } + } + } + + var gridSelection = GUI.SelectionGrid(valueRect, gridValue, _gridOptions, _gridOptions.Length, _buttonStyle); + if (gridSelection != gridValue) { + if (gridSelection == 0) { + rawValue = default; + } else if (gridSelection == 1) { + rawValue = _enumDrawer.BitMask; + } + } + } else { + // draw a dummy field to consume the prefix + EditorGUI.LabelField(valueRect, GUIContent.none); + } + } else { + if (isEnum) { + var enumValue = (Enum)Enum.ToObject(_enumDrawer.EnumType, rawValue[0]); + if (_enumDrawer.IsFlags) { + enumValue = EditorGUI.EnumFlagsField(valueRect, enumValue); + } else { + enumValue = EditorGUI.EnumPopup(valueRect, enumValue); + } + + rawValue[0] = Convert.ToInt64(enumValue); + } else { + // Droplist for FieldsMask + _enumDrawer.Draw(valueRect, maskProperty, fieldInfo.FieldType, false); + } + } + + if (EditorGUI.EndChangeCheck()) { + if (isEnum) { + maskProperty.longValue = rawValue[0]; + } else { + maskProperty.GetFixedBufferElementAtIndex(0).longValue = rawValue[0]; + maskProperty.GetFixedBufferElementAtIndex(1).longValue = rawValue[1]; + maskProperty.GetFixedBufferElementAtIndex(2).longValue = rawValue[2]; + maskProperty.GetFixedBufferElementAtIndex(3).longValue = rawValue[3]; + } + property.serializedObject.ApplyModifiedProperties(); + } + + if (!attribute.AlwaysExpanded) { + using (new FusionEditorGUI.EnabledScope(true)) { + property.isExpanded = EditorGUI.Toggle(foldoutRect, wasExpanded, EditorStyles.foldout); + } + } + + if (wasExpanded) { + if (Event.current.type == EventType.Repaint) { + EditorStyles.helpBox.Draw(new Rect(position) { yMin = rowRect.yMax }, GUIContent.none, false, false, false, false); + } + + EditorGUI.BeginChangeCheck(); + + rowRect.xMin += ToggleIndent; + + for (int i = 0; i < _enumDrawer.Values.Length; ++i) { + if (_enumDrawer.IsFlags && _enumDrawer.Values[i].IsNothing()) { + continue; + } + + rowRect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing; + + var toggleRect = rowRect; + var buttonRect = new Rect(); + if (attribute.ShowInlineHelp) { + // move the button to keep it in the box + buttonRect = FusionEditorGUI.GetInlineHelpButtonRect(rowRect); + toggleRect.xMin += buttonRect.width + 0; + buttonRect.x += buttonRect.width - 3; + } + + bool wasSelected = _enumDrawer.IsFlags + ? Equals(rawValue & _enumDrawer.Values[i], _enumDrawer.Values[i]) + : Equals(rawValue, _enumDrawer.Values[i]); + if (EditorGUI.ToggleLeft(toggleRect, _enumDrawer.Names[i], wasSelected) != wasSelected) { + if (_enumDrawer.IsFlags) { + if (wasSelected) { + rawValue &= ~_enumDrawer.Values[i]; + } else { + rawValue |= _enumDrawer.Values[i]; + } + } else if (!wasSelected) { + rawValue = _enumDrawer.Values[i]; + } + } + + if (attribute.ShowInlineHelp) { + var helpContent = FusionCodeDoc.FindEntry(_enumDrawer.Fields[i], false); + if (helpContent != null) { + var helpPath = GetHelpPath(property, _enumDrawer.Fields[i]); + + var wasHelpExpanded = FusionEditorGUI.IsHelpExpanded(this, helpPath); + if (wasHelpExpanded) { + var helpSize = FusionEditorGUI.GetInlineBoxSize(helpContent); + var helpRect = rowRect; + helpRect.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing; + helpRect.height = helpSize.y; + + rowRect.y += helpSize.y; + + FusionEditorGUI.DrawInlineBoxUnderProperty(helpContent, helpRect, FusionEditorSkin.HelpInlineBoxColor, true); + } + + buttonRect.x += buttonRect.width; + if (FusionEditorGUI.DrawInlineHelpButton(buttonRect, wasHelpExpanded, doButton: true, doIcon: true)) { + FusionEditorGUI.SetHelpExpanded(this, helpPath, !wasHelpExpanded); + } + } + } + } + + if (EditorGUI.EndChangeCheck()) { + if (isEnum) { + maskProperty.longValue = rawValue[0]; + } else { + maskProperty.GetFixedBufferElementAtIndex(0).longValue = rawValue[0]; + maskProperty.GetFixedBufferElementAtIndex(1).longValue = rawValue[1]; + maskProperty.GetFixedBufferElementAtIndex(2).longValue = rawValue[2]; + maskProperty.GetFixedBufferElementAtIndex(3).longValue = rawValue[3]; + } + property.serializedObject.ApplyModifiedProperties(); + } + } + } + } + + + public override float GetPropertyHeight(SerializedProperty property, GUIContent label) { + + var enumType = property.propertyType == SerializedPropertyType.Enum ? fieldInfo.FieldType.GetUnityLeafType() : fieldInfo.FieldType; + _enumDrawer.EnsureInitialized(enumType, attribute.ShowInlineHelp); + + int rowCount = 0; + + float height; + + var forceExpand = attribute.AlwaysExpanded; + var showHelp = attribute.ShowInlineHelp; + + if (forceExpand || property.isExpanded) { + if (_enumDrawer.IsFlags) { + foreach (var value in _enumDrawer.Values) { + if (value.IsNothing()) { + continue; + } + + ++rowCount; + } + } else { + rowCount = _enumDrawer.Values.Length; + } + + height = (rowCount + 1) * (EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing); + + if (showHelp) { + foreach (var field in _enumDrawer.Fields) { + if (FusionEditorGUI.IsHelpExpanded(this, GetHelpPath(property, field))) { + var helpContent = FusionCodeDoc.FindEntry(field, false); + if (helpContent != null) { + height += FusionEditorGUI.GetInlineBoxSize(helpContent).y; + } + } + } + } + + } else { + height = EditorGUIUtility.singleLineHeight; + } + + return height; + } + + private static string GetHelpPath(SerializedProperty property, FieldInfo field) { + return property.propertyPath + "/" + field.Name; + } + } +} + +#endregion + + +#region FieldEditorButtonAttributeDrawer.cs + +namespace Fusion.Editor { + using System; + using System.Reflection; + using UnityEditor; + using UnityEngine; + using Object = UnityEngine.Object; + + [CustomPropertyDrawer(typeof(FieldEditorButtonAttribute))] +#if !UNITY_6000_0_OR_NEWER + [RedirectCustomPropertyDrawer(typeof(FieldEditorButtonAttribute), typeof(FieldEditorButtonAttributeDrawer))] + partial class PropertyDrawerForArrayWorkaround { + } +#endif + internal partial class FieldEditorButtonAttributeDrawer : DecoratingPropertyAttributeDrawer { + protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) { + + var propertyPosition = position; + propertyPosition.height -= EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing; + + base.OnGUIInternal(propertyPosition, property, label); + + var buttonPosition = position; + buttonPosition.yMin = position.yMax - EditorGUIUtility.singleLineHeight; + + var attribute = (FieldEditorButtonAttribute)this.attribute; + var targetObjects = property.serializedObject.targetObjects; + var targetObjectType = property.serializedObject.targetObject.GetType(); + + if (DrawButton(buttonPosition, attribute, targetObjectType, targetObjects)) { + property.serializedObject.Update(); + property.serializedObject.ApplyModifiedProperties(); + } + } + + private static bool DrawButton(Rect buttonPosition, FieldEditorButtonAttribute attribute, Type targetObjectType, Object[] targetObjects) { + using (new EditorGUI.DisabledGroupScope(!attribute.AllowMultipleTargets && targetObjects.Length > 1)) { + if (GUI.Button(buttonPosition, attribute.Label, EditorStyles.miniButton)) { + var targetMethod = targetObjectType.GetMethod(attribute.TargetMethod, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.FlattenHierarchy); + if (targetMethod == null) { + FusionEditorLog.ErrorInspector($"Unable to find method {attribute.TargetMethod} on type {targetObjectType}"); + } else { + if (targetMethod.IsStatic) { + targetMethod.Invoke(null, null); + } else { + foreach (var targetObject in targetObjects) { + targetMethod.Invoke(targetObject, null); + } + } + + return true; + } + } + + return false; + } + } + + protected override float GetPropertyHeightInternal(SerializedProperty property, GUIContent label) { + return base.GetPropertyHeightInternal(property, label) + EditorGUIUtility.standardVerticalSpacing + EditorGUIUtility.singleLineHeight; + } + } +} + +#endregion + + +#region HideArrayElementLabelAttributeDrawer.cs + +namespace Fusion.Editor { + using UnityEditor; + using UnityEngine; + + [CustomPropertyDrawer(typeof(HideArrayElementLabelAttribute))] + partial class HideArrayElementLabelAttributeDrawer : DecoratingPropertyAttributeDrawer { + protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) { + if (property.IsArrayElement()) { + label = GUIContent.none; + } + base.OnGUIInternal(position, property, label); + } + } +} + +#endregion + + +#region InlineHelpAttributeDrawer.cs + +namespace Fusion.Editor { + using System.Reflection; + using UnityEditor; + using UnityEngine; + + [CustomPropertyDrawer(typeof(InlineHelpAttribute))] +#if !UNITY_6000_0_OR_NEWER + [RedirectCustomPropertyDrawer(typeof(InlineHelpAttribute), typeof(InlineHelpAttributeDrawer))] + partial class PropertyDrawerForArrayWorkaround { + } +#endif + internal partial class InlineHelpAttributeDrawer : DecoratingPropertyAttributeDrawer, INonApplicableOnArrayElements { + bool _initialized; + GUIContent _helpContent; + GUIContent _labelContent; + + protected new InlineHelpAttribute attribute => (InlineHelpAttribute)base.attribute; + + + protected override float GetPropertyHeightInternal(SerializedProperty property, GUIContent label) { + + var height = base.GetPropertyHeightInternal(property, label); + if (height <= 0) { + return height; + } + + EnsureContentInitialized(property); + + if (FusionEditorGUI.IsHelpExpanded(this, property.GetHashCodeForPropertyPathWithoutArrayIndex())) { + if (_helpContent != null) { + height += FusionEditorGUI.GetInlineBoxSize(_helpContent).y; + } + } + + return height; + } + + protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) { + if (position.height <= 0 || _helpContent == null) { + // ignore + base.OnGUIInternal(position, property, label); + return; + } + + FusionEditorLog.Assert(_initialized); + + var nextDrawer = GetNextDrawer(property); + var hasFoldout = HasFoldout(nextDrawer, property); + + using (new FusionEditorGUI.GUIContentScope(label)) { + var (wasExpanded, buttonRect) = DrawInlineHelpBeforeProperty(label, _helpContent, position, property.GetHashCodeForPropertyPathWithoutArrayIndex(), EditorGUI.indentLevel, hasFoldout, this); + + var propertyRect = position; + if (wasExpanded) { + propertyRect.height -= FusionEditorGUI.GetInlineBoxSize(_helpContent).y; + } + base.OnGUIInternal(propertyRect, property, label); + + DrawInlineHelpAfterProperty(buttonRect, wasExpanded, _helpContent, position); + } + } + + private void EnsureContentInitialized(SerializedProperty property) { + if (_initialized) { + return; + } + + _initialized = true; + if (fieldInfo == null) { + return; + } + + _helpContent = FusionCodeDoc.FindEntry(fieldInfo, attribute.ShowTypeHelp); + } + + private bool HasFoldout(PropertyDrawer nextDrawer, SerializedProperty property) { + var drawerMeta = nextDrawer?.GetType().GetCustomAttribute(); + if (drawerMeta != null) { + return drawerMeta.HasFoldout; + } + + if (property.IsArrayProperty()) { + return true; + } + + if (property.propertyType == SerializedPropertyType.Generic) { + return true; + } + + return false; + } + + public static (bool expanded, Rect buttonRect) DrawInlineHelpBeforeProperty(GUIContent label, GUIContent helpContent, Rect propertyRect, int pathHash, int depth, bool hasFoldout, object context, bool drawHelp = false) { + + if (label != null) { + if (!string.IsNullOrEmpty(label.tooltip)) { + label.tooltip += "\n\n"; + } + label.tooltip += helpContent.tooltip; + } + + if (propertyRect.width > 1 && propertyRect.height > 1) { + var buttonRect = FusionEditorGUI.GetInlineHelpButtonRect(propertyRect, hasFoldout); + + if (depth == 0 && hasFoldout) { + buttonRect.x = 16; + if (label != null) { + label.text = " " + label.text; + } + } + + var wasExpanded = FusionEditorGUI.IsHelpExpanded(context, pathHash); + + if (FusionEditorGUI.DrawInlineHelpButton(buttonRect, wasExpanded, doButton: true, doIcon: false)) { + FusionEditorGUI.SetHelpExpanded(context, pathHash, !wasExpanded); + } + + return (wasExpanded, buttonRect); + } + + return default; + } + + public static void DrawInlineHelpAfterProperty(Rect buttonRect, bool wasExpanded, GUIContent helpContent, Rect propertyRect) { + + if (buttonRect.width <= 0 && buttonRect.height <= 0) { + return; + } + + using (new FusionEditorGUI.EnabledScope(true)) { + FusionEditorGUI.DrawInlineHelpButton(buttonRect, wasExpanded, doButton: false, doIcon: true); + } + + if (!wasExpanded) { + return; + } + + FusionEditorGUI.DrawInlineBoxUnderProperty(helpContent, propertyRect, FusionEditorSkin.HelpInlineBoxColor, true); + } + } +} + +#endregion + + +#region INonApplicableOnArrayElements.cs + +namespace Fusion.Editor { + interface INonApplicableOnArrayElements { + } +} + +#endregion + + +#region LayerAttributeDrawer.cs + +namespace Fusion.Editor { + using UnityEditor; + using UnityEngine; + + [CustomPropertyDrawer(typeof(LayerAttribute))] + internal class LayerAttributeDrawer : PropertyDrawer { + public override void OnGUI(Rect p, SerializedProperty prop, GUIContent label) { + EditorGUI.BeginChangeCheck(); + + int value; + + using (new FusionEditorGUI.PropertyScope(p, label, prop)) + using (new FusionEditorGUI.ShowMixedValueScope(prop.hasMultipleDifferentValues)) { + value = EditorGUI.LayerField(p, label, prop.intValue); + } + + if (EditorGUI.EndChangeCheck()) { + prop.intValue = value; + prop.serializedObject.ApplyModifiedProperties(); + } + } + } +} + +#endregion + + +#region LayerMatrixAttributeDrawer.cs + +namespace Fusion.Editor { + using UnityEditor; + using UnityEngine; + + [CustomPropertyDrawer(typeof(LayerMatrixAttribute))] +#if !UNITY_6000_0_OR_NEWER + [RedirectCustomPropertyDrawer(typeof(LayerMatrixAttribute), typeof(LayerMatrixAttributeDrawer))] + partial class PropertyDrawerForArrayWorkaround { + } +#endif + internal partial class LayerMatrixAttributeDrawer : PropertyDrawerWithErrorHandling, INonApplicableOnArrayElements { + + protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) { + using (new FusionEditorGUI.PropertyScopeWithPrefixLabel(position, label, property, out var valueRect)) { + if (GUI.Button(valueRect, "Edit", EditorStyles.miniButton)) { + PopupWindow.Show(valueRect, new LayerMatrixPopup(label?.text ?? property.displayName, + (layerA, layerB) => { + if (layerA >= property.arraySize) { + return false; + } + + return (property.GetArrayElementAtIndex(layerA).intValue & (1 << layerB)) != 0; + }, + (layerA, layerB, val) => { + if (Mathf.Max(layerA, layerB) >= property.arraySize) { + property.arraySize = Mathf.Max(layerA, layerB) + 1; + } + if (val) { + property.GetArrayElementAtIndex(layerA).intValue |= (1 << layerB); + property.GetArrayElementAtIndex(layerB).intValue |= (1 << layerA); + } else { + property.GetArrayElementAtIndex(layerA).intValue &= ~(1 << layerB); + property.GetArrayElementAtIndex(layerB).intValue &= ~(1 << layerA); + } + property.serializedObject.ApplyModifiedProperties(); + })); + } + } + } + + public override float GetPropertyHeight(SerializedProperty property, GUIContent label) { + return EditorGUIUtility.singleLineHeight; + } + + class LayerMatrixPopup : PopupWindowContent { + private const int checkboxSize = 16; + private const int margin = 30; + private const int MaxLayers = 32; + + private readonly GUIContent _label; + private readonly int _numLayers; + private readonly float _labelWidth; + + private readonly UnityInternal.LayerMatrixGUI.GetValueFunc _getter; + private readonly UnityInternal.LayerMatrixGUI.SetValueFunc _setter; + + public LayerMatrixPopup(string label, UnityInternal.LayerMatrixGUI.GetValueFunc getter, UnityInternal.LayerMatrixGUI.SetValueFunc setter) { + _label = new GUIContent(label); + _getter = getter; + _setter = setter; + _labelWidth = 110; + _numLayers = 0; + for (int i = 0; i < MaxLayers; i++) { + string layerName = LayerMask.LayerToName(i); + if (string.IsNullOrEmpty(layerName)) { + continue; + } + + _numLayers++; + _labelWidth = Mathf.Max(_labelWidth, GUI.skin.label.CalcSize(new GUIContent(layerName)).x); + } + } + + public override void OnGUI(Rect rect) { + GUILayout.BeginArea(rect); + + UnityInternal.LayerMatrixGUI.Draw(_label, _getter, _setter); + + GUILayout.EndArea(); + } + + public override Vector2 GetWindowSize() { + int matrixWidth = checkboxSize * _numLayers; + float width = matrixWidth + _labelWidth + margin * 2; + float height = matrixWidth + _labelWidth + 15 + FusionEditorGUI.GetLinesHeight(3); + return new Vector2(Mathf.Max(width, 350), height); + } + } + } +} + +#endregion + + +#region MaxStringByteCountAttributeDrawer.cs + +namespace Fusion.Editor { + using UnityEditor; + using UnityEngine; + + [CustomPropertyDrawer(typeof(MaxStringByteCountAttribute))] + internal class MaxStringByteCountAttributeDrawer : PropertyDrawerWithErrorHandling { + + protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) { + var attribute = (MaxStringByteCountAttribute)this.attribute; + + var encoding = System.Text.Encoding.GetEncoding(attribute.Encoding); + var byteCount = encoding.GetByteCount(property.stringValue); + + using (new FusionEditorGUI.PropertyScope(position, label, property)) { + FusionEditorGUI.ForwardPropertyField(position, property, label, true); + } + + FusionEditorGUI.Overlay(position, $"({byteCount} B)"); + if (byteCount > attribute.ByteCount) { + FusionEditorGUI.Decorate(position, $"{attribute.Encoding} string max size ({attribute.ByteCount} B) exceeded: {byteCount} B", MessageType.Error, hasLabel: true); + } + } + } +} + +#endregion + + +#region MessageIfDrawerBase.cs + +namespace Fusion.Editor { + using UnityEditor; + using UnityEngine; + + internal abstract class MessageIfDrawerBase : DoIfAttributeDrawer { + protected abstract bool IsBox { get; } + protected abstract string Message { get; } + protected abstract MessageType MessageType { get; } + protected abstract Color InlineBoxColor { get; } + protected abstract Texture MessageIcon { get; } + + public DoIfAttributeBase Attribute => (DoIfAttributeBase)attribute; + + private GUIContent _messageContent; + private GUIContent MessageContent { + get { + if (_messageContent == null) { + _messageContent = new GUIContent(Message, MessageIcon, Message); + } + return _messageContent; + } + } + + protected override float GetPropertyHeightInternal(SerializedProperty property, GUIContent label) { + var height = base.GetPropertyHeightInternal(property, label); + + if (IsBox) { + if (CheckDraw(Attribute, property)) { + float extra = CalcBoxHeight(); + height += extra; + } + } + + return height; + } + + protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) { + + if (!CheckDraw(Attribute, property)) { + base.OnGUIInternal(position, property, label); + } else { + if (!IsBox) { + + var decorateRect = position; + decorateRect.height = EditorGUIUtility.singleLineHeight; + decorateRect.xMin += EditorGUIUtility.labelWidth; + + // TODO: should the border be resized for arrays? + // if (property.IsArrayProperty()) { + // decorateRect.xMin = decorateRect.xMax - 48f; + // } + + FusionEditorGUI.AppendTooltip(MessageContent.text, ref label); + + base.OnGUIInternal(position, property, label); + + FusionEditorGUI.Decorate(decorateRect, MessageContent.text, MessageType); + } else { + + position = FusionEditorGUI.DrawInlineBoxUnderProperty(MessageContent, position, InlineBoxColor); + base.OnGUIInternal(position, property, label); + + //position.y += position.height; + //position.height = extra; + //EditorGUI.HelpBox(position, MessageContent.text, MessageType); + + } + } + } + + private float CalcBoxHeight() { + // const float SCROLL_WIDTH = 16f; + // const float LEFT_HELP_INDENT = 8f; + // + // var width = UnityInternal.EditorGUIUtility.contextWidth - /*InlineHelpStyle.MarginOuter -*/ SCROLL_WIDTH - LEFT_HELP_INDENT; + // return EditorStyles.helpBox.CalcHeight(MessageContent, width); + + return FusionEditorGUI.GetInlineBoxSize(MessageContent).y; + } + } +} + +#endregion + + +#region PropertyDrawerForArrayWorkaround.cs + +#if !UNITY_6000_0_OR_NEWER +namespace Fusion.Editor { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + using UnityEditor; + using UnityEngine; + + internal partial class PropertyDrawerForArrayWorkaround : DecoratorDrawer { + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + internal class RedirectCustomPropertyDrawerAttribute : Attribute { + public RedirectCustomPropertyDrawerAttribute(Type attributeType, Type drawerType) { + AttributeType = attributeType; + DrawerType = drawerType; + } + + public Type AttributeType { get; } + public Type DrawerType { get; } + } + + + private static Dictionary _attributeToDrawer = typeof(PropertyDrawerForArrayWorkaround) + .GetCustomAttributes() + .ToDictionary(x => x.AttributeType, x => x.DrawerType); + + private UnityInternal.PropertyHandler _handler; + private PropertyDrawer _drawer; + private bool _initialized; + + public PropertyDrawerForArrayWorkaround() { + _handler = UnityInternal.ScriptAttributeUtility.nextHandler; + + // this handler is going to have a drawer eventually, + // but now we need to make sure it looks like it has drawers before we can actually + // inject them + _handler.m_PropertyDrawers ??= new List() { new DummyPropertyDrawer() }; + } + + public override float GetHeight() { + if (_initialized) { + return 0; + } + + _initialized = true; + + if (!_attributeToDrawer.TryGetValue(attribute.GetType(), out var drawerType)) { + FusionEditorLog.ErrorInspector($"No drawer for {attribute.GetType()}"); + } else if (_handler.decoratorDrawers?.Contains(this) != true) { + FusionEditorLog.Warn($"Unable to forward to {drawerType}."); + } else { + var drawer = (PropertyDrawer)Activator.CreateInstance(drawerType); + UnityInternal.PropertyDrawer.SetAttribute(drawer, attribute); + + FusionEditorLog.Assert(_handler.m_PropertyDrawers != null, "_handler.m_PropertyDrawers != null"); + + var propertyDrawers = _handler.m_PropertyDrawers; + if (propertyDrawers.Count > 0 && propertyDrawers[0] is DummyPropertyDrawer) { + propertyDrawers.RemoveAt(0); + } + + int i = 0; + for (; i < propertyDrawers.Count; ++i) { + if (propertyDrawers[i].attribute == null) { + break; + } + if (propertyDrawers[i].attribute.order > attribute.order) { + // perfect spot! + break; + } + if (propertyDrawers[i].attribute.order == attribute.order) { + // this is tricky; ideally we want to insert exactly in the same order as ScriptAttributeUtility.GetFieldAttributes + // would return, but the field is not available at the moment; so the next best thing is putting the workaround ahead + // unless we've found another workaround + if (!_attributeToDrawer.ContainsKey(propertyDrawers[i].attribute.GetType())) { + break; + } + } + } + + FusionEditorLog.Trace($"Inserting {drawerType} at {i}"); + _handler.m_PropertyDrawers.Insert(i, drawer); + } + + return 0; + } + + public static Type GetDrawerType(Type attributeDrawerType) { + return _attributeToDrawer[attributeDrawerType]; + } + + class DummyPropertyDrawer : PropertyDrawer { + + static bool _errorReported = false; + + public override float GetPropertyHeight(SerializedProperty property, GUIContent label) { + if (!_errorReported) { + _errorReported = true; + FusionEditorLog.WarnInspector($"Drawers for property {property.propertyPath} failed to be injected properly. This may happen if property drawers are created in a non-standard way."); + } + return EditorGUI.GetPropertyHeight(property, label); + } + } + } +} +#endif + +#endregion + + +#region PropertyDrawerWithErrorHandling.cs + +namespace Fusion.Editor { + using System; + using System.Collections.Generic; + using UnityEditor; + using UnityEngine; + + internal abstract class PropertyDrawerWithErrorHandling : PropertyDrawer { + private SerializedProperty _currentProperty; + + private readonly Dictionary _errors = new(); + private bool _hadError; + private string _info; + + public sealed override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { + FusionEditorLog.Assert(_currentProperty == null); + + var decoration = GetDecoration(property); + + if (decoration != null) { + DrawDecoration(position, decoration.Value, label != GUIContent.none, true, false); + } + + + _currentProperty = property; + _hadError = false; + _info = null; + + EditorGUI.BeginChangeCheck(); + + try { + OnGUIInternal(position, property, label); + } catch (ExitGUIException) { + // pass through + } catch (Exception ex) { + SetError(ex.ToString()); + } finally { + // if there was a change but no error clear + if (EditorGUI.EndChangeCheck() && !_hadError) { + ClearError(); + } + + _currentProperty = null; + } + + if (decoration != null) { + DrawDecoration(position, decoration.Value, label != GUIContent.none, false, true); + } + } + + private void DrawDecoration(Rect position, (string, MessageType, bool) decoration, bool hasLabel, bool drawButton = true, bool drawIcon = true) { + var iconPosition = position; + iconPosition.height = EditorGUIUtility.singleLineHeight; + FusionEditorGUI.Decorate(iconPosition, decoration.Item1, decoration.Item2, hasLabel, drawButton: drawButton, drawBorder: decoration.Item3); + } + + private (string, MessageType, bool)? GetDecoration(SerializedProperty property) { + if (_errors.TryGetValue(property.propertyPath, out var error)) { + return (error.message, error.type, true); + } + + if (_info != null) { + return (_info, MessageType.Info, false); + } + + return null; + } + + protected abstract void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label); + + protected void ClearError() { + ClearError(_currentProperty); + } + + protected void ClearError(SerializedProperty property) { + _hadError = false; + _errors.Remove(property.propertyPath); + } + + protected void ClearErrorIfLostFocus() { + if (GUIUtility.keyboardControl != UnityInternal.EditorGUIUtility.LastControlID) { + ClearError(); + } + } + + protected void SetError(string error) { + _hadError = true; + _errors[_currentProperty.propertyPath] = new Entry { + message = error, + type = MessageType.Error + }; + } + + protected void SetError(Exception error) { + SetError(error.ToString()); + } + + protected void SetWarning(string warning) { + if (_errors.TryGetValue(_currentProperty.propertyPath, out var entry) && entry.type == MessageType.Error) { + return; + } + + _errors[_currentProperty.propertyPath] = new Entry { + message = warning, + type = MessageType.Warning + }; + } + + protected void SetInfo(string message) { + if (_errors.TryGetValue(_currentProperty.propertyPath, out var entry) && entry.type == MessageType.Error || entry.type == MessageType.Warning ) { + return; + } + + _errors[_currentProperty.propertyPath] = new Entry { + message = message, + type = MessageType.Info + }; + } + + private struct Entry { + public string message; + public MessageType type; + } + } +} + +#endregion + + +#region RangeExAttributeDrawer.cs + +namespace Fusion.Editor { + using System; + using JetBrains.Annotations; + using UnityEditor; + using UnityEngine; + + [CustomPropertyDrawer(typeof(RangeExAttribute))] + internal partial class RangeExAttributeDrawer : PropertyDrawerWithErrorHandling { + + internal const float FieldWidth = 100.0f; + internal const float Spacing = 5.0f; + internal const float SliderOffset = 2.0f; + internal const float MinSliderWidth = 40.0f; + + [CanBeNull] + GUIContent[] _popupOptions; + + partial void GetFloatValue(SerializedProperty property, ref float? floatValue); + partial void GetIntValue(SerializedProperty property, ref int? intValue); + partial void ApplyFloatValue(SerializedProperty property, float floatValue); + partial void ApplyIntValue(SerializedProperty property, int intValue); + partial void DrawFloatValue(SerializedProperty property, Rect position, GUIContent label, ref float floatValue); + partial void DrawIntValue(SerializedProperty property, Rect position, GUIContent label, ref int intValue); + + + protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) { + var attrib = (RangeExAttribute)this.attribute; + var min = attrib.Min; + var max = attrib.Max; + + int? intValue = null; + float? floatValue = null; + + if (property.propertyType == SerializedPropertyType.Float) { + floatValue = property.floatValue; + } else if (property.propertyType == SerializedPropertyType.Integer) { + intValue = property.intValue; + } else { + GetFloatValue(property, ref floatValue); + + if (!floatValue.HasValue) { + GetIntValue(property, ref intValue); + + // ReSharper disable once ConditionIsAlwaysTrueOrFalse + if (!intValue.HasValue) { + EditorGUI.LabelField(position, label.text, "Use RangeEx with float or int."); + return; + } + } + } + + Debug.Assert(floatValue.HasValue || intValue.HasValue); + + EditorGUI.BeginChangeCheck(); + + using (new FusionEditorGUI.PropertyScope(position, label, property)) { + if (attrib.UseSlider) { + + // slider offset is applied to look like the built-in RangeDrawer + var sliderRect = new Rect(position) { xMin = position.xMin + EditorGUIUtility.labelWidth + SliderOffset, xMax = position.xMax - FieldWidth - Spacing }; + + using (new FusionEditorGUI.LabelWidthScope(position.width - FieldWidth)) { + if (floatValue.HasValue) { + if (attrib.Values != null) { + int valueIndex = FindValueIndex(floatValue.Value); + + if (sliderRect.width > MinSliderWidth) { + using (new EditorGUI.IndentLevelScope(-EditorGUI.indentLevel)) { + EditorGUI.BeginChangeCheck(); + valueIndex = Mathf.RoundToInt(GUI.HorizontalSlider(sliderRect, valueIndex, 0, attrib.Values.Length+1)); + if (EditorGUI.EndChangeCheck()) { + ApplyValue(); + } + } + } + + floatValue = (float)DrawValuePopup(position, label, valueIndex, attrib.Min, attrib.Max, attrib.Values); + } else { + if (sliderRect.width > MinSliderWidth) { + using (new EditorGUI.IndentLevelScope(-EditorGUI.indentLevel)) { + EditorGUI.BeginChangeCheck(); + floatValue = GUI.HorizontalSlider(sliderRect, floatValue.Value, (float)min, (float)max); + if (EditorGUI.EndChangeCheck()) { + ApplyValue(); + } + } + } + + floatValue = DrawValue(property, position, label, floatValue.Value); + } + + + } else { + if (attrib.Values != null) { + int valueIndex = FindValueIndex(intValue.Value); + + if (sliderRect.width > MinSliderWidth) { + using (new EditorGUI.IndentLevelScope(-EditorGUI.indentLevel)) { + EditorGUI.BeginChangeCheck(); + valueIndex = Mathf.RoundToInt(GUI.HorizontalSlider(sliderRect, valueIndex, 0, attrib.Values.Length+1)); + if (EditorGUI.EndChangeCheck()) { + ApplyValue(); + } + } + } + + intValue = Mathf.RoundToInt((float)DrawValuePopup(position, label, valueIndex, attrib.Min, attrib.Max, attrib.Values)); + } else { + + if (sliderRect.width > MinSliderWidth) { + using (new EditorGUI.IndentLevelScope(-EditorGUI.indentLevel)) { + EditorGUI.BeginChangeCheck(); + intValue = Mathf.RoundToInt(GUI.HorizontalSlider(sliderRect, intValue.Value, (float)min, (float)max)); + if (EditorGUI.EndChangeCheck()) { + ApplyValue(); + } + } + } + + intValue = DrawValue(property, position, label, intValue.Value); + } + } + } + } else { + if (floatValue.HasValue) { + floatValue = DrawValue(property, position, label, floatValue.Value); + } else { + intValue = DrawValue(property, position, label, intValue.Value); + } + } + } + + if (EditorGUI.EndChangeCheck()) { + ApplyValue(); + property.serializedObject.ApplyModifiedProperties(); + } + + int FindValueIndex(double val) { + if (val <= attrib.Min) { + return 0; + } else if (val >= attrib.Max) { + return attrib.Values.Length + 1; + } else { + return Array.IndexOf(attrib.Values, val) + 1; + } + } + + void ApplyValue() { + if (floatValue.HasValue) { + floatValue = Clamp(floatValue.Value, attrib); + } else { + Debug.Assert(intValue != null); + intValue = Clamp(intValue.Value, attrib); + } + + if (property.propertyType == SerializedPropertyType.Float) { + Debug.Assert(floatValue != null); + property.floatValue = floatValue.Value; + } else if (property.propertyType == SerializedPropertyType.Integer) { + Debug.Assert(intValue != null); + property.intValue = intValue.Value; + // ReSharper disable once ConditionIsAlwaysTrueOrFalse + } else if (floatValue.HasValue) { + ApplyFloatValue(property, floatValue.Value); + } else { + ApplyIntValue(property, intValue.Value); + } + } + } + + double DrawValuePopup(Rect position, GUIContent label, int index, double min, double max, double[] values) { + if (_popupOptions == null) { + _popupOptions = new GUIContent[2 + values.Length]; + _popupOptions[0] = new GUIContent($"{min}"); + for (int i = 0; i < values.Length; ++i) { + _popupOptions[i + 1] = new GUIContent($"{values[i]}"); + } + _popupOptions[values.Length + 1] = new GUIContent($"{max}"); + } + + index = EditorGUI.Popup(position, label, index, _popupOptions); + + if (index <= 0) { + return min; + } else if (index < values.Length + 1) { + return values[index - 1]; + } else { + return max; + } + } + + private float Clamp(float value, RangeExAttribute attrib) { + return Mathf.Clamp(value, + attrib.ClampMin ? (float)attrib.Min : float.MinValue, + attrib.ClampMax ? (float)attrib.Max : float.MaxValue); + } + + private int Clamp(int value, RangeExAttribute attrib) { + return Mathf.Clamp(value, + attrib.ClampMin ? (int)attrib.Min : int.MinValue, + attrib.ClampMax ? (int)attrib.Max : int.MaxValue); + } + + float DrawValue(SerializedProperty property, Rect position, GUIContent label, float floatValue) { + if (property.propertyType == SerializedPropertyType.Float) { + return EditorGUI.FloatField(position, label, floatValue); + } else { + DrawFloatValue(property, position, label, ref floatValue); + return floatValue; + } + } + + int DrawValue(SerializedProperty property, Rect position, GUIContent label, int intValue) { + if (property.propertyType == SerializedPropertyType.Integer) { + return EditorGUI.IntField(position, label, intValue); + } else { + DrawIntValue(property, position, label, ref intValue); + return intValue; + } + } + } +} + +#endregion + + +#region ReadOnlyAttributeDrawer.cs + +namespace Fusion.Editor { + using UnityEditor; + using UnityEngine; + + [CustomPropertyDrawer(typeof(ReadOnlyAttribute))] +#if !UNITY_6000_0_OR_NEWER + [RedirectCustomPropertyDrawer(typeof(ReadOnlyAttribute), typeof(ReadOnlyAttributeDrawer))] + partial class PropertyDrawerForArrayWorkaround { + } +#endif + internal partial class ReadOnlyAttributeDrawer : DecoratingPropertyAttributeDrawer, INonApplicableOnArrayElements { + protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) { + var attribute = (ReadOnlyAttribute)this.attribute; + bool isPlayMode = EditorApplication.isPlayingOrWillChangePlaymode; + + using (new EditorGUI.DisabledGroupScope(isPlayMode ? attribute.InPlayMode : attribute.InEditMode)) { + base.OnGUIInternal(position, property, label); + } + } + } +} + +#endregion + + +#region ScenePathAttributeDrawer.cs + +namespace Fusion.Editor { + using System.Linq; + using UnityEditor; + using UnityEngine; + + [CustomPropertyDrawer(typeof(ScenePathAttribute))] + internal class ScenePathAttributeDrawer : PropertyDrawerWithErrorHandling { + private SceneAsset[] _allScenes; + + protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) { + var oldScene = AssetDatabase.LoadAssetAtPath(property.stringValue); + if (oldScene == null && !string.IsNullOrEmpty(property.stringValue)) { + // well, maybe by name then? + _allScenes = _allScenes ?? AssetDatabase.FindAssets("t:scene") + .Select(x => AssetDatabase.GUIDToAssetPath(x)) + .Select(x => AssetDatabase.LoadAssetAtPath(x)) + .ToArray(); + + var matchedByName = _allScenes.Where(x => x.name == property.stringValue).ToList(); + ; + + if (matchedByName.Count == 0) { + SetError($"Scene not found: {property.stringValue}"); + } else { + oldScene = matchedByName[0]; + if (matchedByName.Count > 1) { + SetWarning("There are multiple scenes with this name"); + } + } + } + + using (new FusionEditorGUI.PropertyScope(position, label, property)) { + EditorGUI.BeginChangeCheck(); + var newScene = EditorGUI.ObjectField(position, label, oldScene, typeof(SceneAsset), false) as SceneAsset; + if (EditorGUI.EndChangeCheck()) { + var assetPath = AssetDatabase.GetAssetPath(newScene); + property.stringValue = assetPath; + property.serializedObject.ApplyModifiedProperties(); + ClearError(); + } + } + } + } +} + +#endregion + + +#region ScriptFieldDrawer.cs + +namespace Fusion.Editor { + using UnityEditor; + using UnityEngine; + + internal class ScriptFieldDrawer : PropertyDrawer { + + private new ScriptHelpAttribute attribute => (ScriptHelpAttribute)base.attribute; + + public bool ForceHide = false; + + private bool _initialized; + private GUIContent _helpContent; + private GUIContent _headerContent; + + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { + + if (ForceHide || attribute?.Hide == true) { + return; + } + + if (attribute == null) { + EditorGUI.PropertyField(position, property, label); + return; + } + + EnsureInitialized(property); + + var helpButtonRect = FusionEditorGUI.GetInlineHelpButtonRect(position, false); + bool wasHelpExpanded = _helpContent != null && FusionEditorGUI.IsHelpExpanded(this, property.GetHashCodeForPropertyPathWithoutArrayIndex()); + + if (wasHelpExpanded) { + position = FusionEditorGUI.DrawInlineBoxUnderProperty(_helpContent, position, FusionEditorSkin.HelpInlineBoxColor); + } + + if (_helpContent != null) { + using (new FusionEditorGUI.EnabledScope(true)) { + if (FusionEditorGUI.DrawInlineHelpButton(helpButtonRect, wasHelpExpanded, true, false)) { + FusionEditorGUI.SetHelpExpanded(this, property.GetHashCodeForPropertyPathWithoutArrayIndex(), !wasHelpExpanded); + } + } + } + + if (attribute.Style == ScriptHeaderStyle.Unity) { + EditorGUI.PropertyField(position, property, label); + } else { + using (new FusionEditorGUI.EnabledScope(true)) { + if (attribute.BackColor != ScriptHeaderBackColor.None) { + FusionEditorGUI.DrawScriptHeaderBackground(position, FusionEditorSkin.GetScriptHeaderColor(attribute.BackColor)); + } + + var labelPosition = FusionEditorSkin.ScriptHeaderLabelStyle.margin.Remove(position); + EditorGUIUtility.AddCursorRect(labelPosition, MouseCursor.Link); + EditorGUI.LabelField(labelPosition, _headerContent, FusionEditorSkin.ScriptHeaderLabelStyle); + + var e = Event.current; + if (e.type == EventType.MouseDown && position.Contains(e.mousePosition)) { + if (e.clickCount == 1) { + if (!string.IsNullOrEmpty(attribute.Url)) { + Application.OpenURL(attribute.Url); + } + + EditorGUIUtility.PingObject(property.objectReferenceValue); + } else { + AssetDatabase.OpenAsset(property.objectReferenceValue); + } + } + + FusionEditorGUI.DrawScriptHeaderIcon(position); + } + } + + if (_helpContent != null) { + using (new FusionEditorGUI.EnabledScope(true)) { + // paint over what the inspector has drawn + FusionEditorGUI.DrawInlineHelpButton(helpButtonRect, wasHelpExpanded, false, true); + } + } + } + + public override float GetPropertyHeight(SerializedProperty property, GUIContent label) { + + if (ForceHide || attribute?.Hide == true) { + return -EditorGUIUtility.standardVerticalSpacing; + } + + if (attribute == null) { + return EditorGUIUtility.singleLineHeight; + } + + var height = EditorGUIUtility.singleLineHeight; + + if (FusionEditorGUI.IsHelpExpanded(this, property.GetHashCodeForPropertyPathWithoutArrayIndex()) && _helpContent != null) { + height += FusionEditorGUI.GetInlineBoxSize(_helpContent).y; + } + + return height; + } + + private void EnsureInitialized(SerializedProperty property) { + if (_initialized) { + return; + } + + _initialized = true; + + var type = property.serializedObject.targetObject.GetType(); + + _headerContent = new GUIContent(ObjectNames.NicifyVariableName(type.Name).ToUpper()); + _helpContent = FusionCodeDoc.FindEntry(type); + } + } +} + +#endregion + + +#region SerializableTypeDrawer.cs + +namespace Fusion.Editor { + using System; + using UnityEditor; + using UnityEngine; + using UnityEngine.Scripting; + + [CustomPropertyDrawer(typeof(SerializableType<>))] + [CustomPropertyDrawer(typeof(SerializableType))] + [CustomPropertyDrawer(typeof(SerializableTypeAttribute))] + internal class SerializableTypeDrawer : PropertyDrawerWithErrorHandling { + protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) { + + var attr = (SerializableTypeAttribute)attribute; + + var baseType = typeof(object); + var leafType = fieldInfo.FieldType.GetUnityLeafType(); + if (leafType.IsGenericType && leafType.GetGenericTypeDefinition() == typeof(SerializableType<>)) { + baseType = leafType.GetGenericArguments()[0]; + } + if (attr?.BaseType != null) { + baseType = attr.BaseType; + } + + position = EditorGUI.PrefixLabel(position, label); + + var (content, msgType, msg) = GetTypeContent(property, attr?.WarnIfNoPreserveAttribute == true, out var valueProperty); + if (msgType == MessageType.Warning) { + SetWarning(msg); + } else if (msgType == MessageType.Error) { + SetError(msg); + } + + if (EditorGUI.DropdownButton(position, new GUIContent(content), FocusType.Keyboard)) { + ClearError(); + FusionEditorGUI.DisplayTypePickerMenu(position, baseType, t => { + string typeName = string.Empty; + if (t != null) { + typeName = attr?.UseFullAssemblyQualifiedName == false ? SerializableType.GetShortAssemblyQualifiedName(t) : t.AssemblyQualifiedName; + } + + valueProperty.stringValue = typeName; + valueProperty.serializedObject.ApplyModifiedProperties(); + }); + } + } + + + public static (string, MessageType, string) GetTypeContent(SerializedProperty property, bool requirePreserveAttribute, out SerializedProperty valueProperty) { + if (property.propertyType == SerializedPropertyType.String) { + valueProperty = property; + } else { + FusionEditorLog.Assert(property.propertyType == SerializedPropertyType.Generic); + valueProperty = property.FindPropertyRelativeOrThrow(nameof(SerializableType.AssemblyQualifiedName)); + } + + var assemblyQualifiedName = valueProperty.stringValue; + if (string.IsNullOrEmpty(assemblyQualifiedName)) { + return ("[None]", MessageType.None, string.Empty); + } + + try { + var type = Type.GetType(assemblyQualifiedName, true); + + if (requirePreserveAttribute) { + if (!type.IsDefined(typeof(PreserveAttribute), false)) { + return (type.FullName, MessageType.Warning, $"Please mark {type.FullName} with [Preserve] attribute to prevent it from being stripped from the build."); + } + } + + return (type.FullName, MessageType.None, string.Empty); + } catch (Exception e) { + return (assemblyQualifiedName, MessageType.Error, e.ToString()); + } + } + } +} + +#endregion + + +#region SerializeReferenceTypePickerAttributeDrawer.cs + +namespace Fusion.Editor { + using System.Linq; + using UnityEditor; + using UnityEngine; + + [CustomPropertyDrawer(typeof(SerializeReferenceTypePickerAttribute))] + partial class SerializeReferenceTypePickerAttributeDrawer : DecoratingPropertyAttributeDrawer { + + const string NullContent = "Null"; + + protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) { + + var attribute = (SerializeReferenceTypePickerAttribute)this.attribute; + + Rect pickerRect; + if (label == GUIContent.none) { + pickerRect = position; + pickerRect.height = EditorGUIUtility.singleLineHeight; + } else { + pickerRect = EditorGUI.PrefixLabel(new Rect(position) { height = EditorGUIUtility.singleLineHeight }, FusionEditorGUI.WhitespaceContent); + } + + object instance = property.managedReferenceValue; + var instanceType = instance?.GetType(); + + if (EditorGUI.DropdownButton(pickerRect, new GUIContent(instanceType?.FullName ?? NullContent), FocusType.Keyboard)) { + + var types = attribute.Types; + if (!types.Any()) { + types = new[] { fieldInfo.FieldType.GetUnityLeafType() }; + } + + FusionEditorGUI.DisplayTypePickerMenu(pickerRect, types, + t => { + if (t == null) { + instance = null; + } else if (t.IsInstanceOfType(instance)) { + // do nothing + return; + } else { + instance = System.Activator.CreateInstance(t); + } + property.managedReferenceValue = instance; + property.serializedObject.ApplyModifiedProperties(); + }, + noneOptionLabel: NullContent, + selectedType: instanceType, + flags: (attribute.GroupTypesByNamespace ? FusionEditorGUIDisplayTypePickerMenuFlags.GroupByNamespace : 0) | (attribute.ShowFullName ? FusionEditorGUIDisplayTypePickerMenuFlags.ShowFullName : 0)); + } + + base.OnGUIInternal(position, property, label); + } + } +} + +#endregion + + +#region SpaceAfterAttributeDrawer.cs + +namespace Fusion.Editor { + using UnityEditor; + using UnityEngine; + + [CustomPropertyDrawer(typeof(SpaceAfterAttribute))] +#if !UNITY_6000_0_OR_NEWER + [RedirectCustomPropertyDrawer(typeof(SpaceAfterAttribute), typeof(SpaceAfterAttributeDrawer))] + partial class PropertyDrawerForArrayWorkaround { + } +#endif + class SpaceAfterAttributeDrawer : DecoratingPropertyAttributeDrawer, INonApplicableOnArrayElements { + protected override float GetPropertyHeightInternal(SerializedProperty property, GUIContent label) { + var attr = (SpaceAfterAttribute)attribute; + return base.GetPropertyHeightInternal(property, label) + attr.Height; + } + + protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) { + var attr = (SpaceAfterAttribute)attribute; + position.height -= attr.Height; + base.OnGUIInternal(position, property, label); + } + } +} + +#endregion + + +#region ToggleLeftAttributeDrawer.cs + +namespace Fusion.Editor { + using UnityEditor; + using UnityEngine; + + [CustomPropertyDrawer(typeof(ToggleLeftAttribute))] + internal class ToggleLeftAttributeDrawer : PropertyDrawer { + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { + EditorGUI.BeginProperty(position, label, property); + + EditorGUI.BeginChangeCheck(); + var val = EditorGUI.ToggleLeft(position, label, property.boolValue); + + if (EditorGUI.EndChangeCheck()) { + property.boolValue = val; + } + + EditorGUI.EndProperty(); + } + } +} + +#endregion + + +#region UnitAttributeDrawer.cs + +namespace Fusion.Editor { + using System; + using System.Reflection; + using UnityEditor; + using UnityEngine; + + [CustomPropertyDrawer(typeof(UnitAttribute))] + [FusionPropertyDrawerMeta(HandlesUnits = true)] + internal partial class UnitAttributeDrawer : DecoratingPropertyAttributeDrawer { + private GUIContent _label; + + private void EnsureInitialized() { + if (_label == null) { + _label = new GUIContent(UnitToLabel(((UnitAttribute)attribute).Unit)); + } + } + + protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) { + base.OnGUIInternal(position, property, label); + + // check if any of the next drawers handles the unit + for (var nextDrawer = GetNextDrawer(property); nextDrawer != null; nextDrawer = (nextDrawer as DecoratingPropertyAttributeDrawer)?.GetNextDrawer(property)) { + var meta = nextDrawer.GetType().GetCustomAttribute(); + if (meta?.HandlesUnits == true) { + return; + } + } + + EnsureInitialized(); + + var propertyType = property.propertyType; + var isExpanded = property.isExpanded; + + DrawUnitOverlay(position, _label, propertyType, isExpanded); + } + + public static void DrawUnitOverlay(Rect position, GUIContent label, SerializedPropertyType propertyType, bool isExpanded, bool odinStyle = false) { + switch (propertyType) { + + case SerializedPropertyType.Vector2 when odinStyle: + case SerializedPropertyType.Vector3 when odinStyle: + case SerializedPropertyType.Vector4 when odinStyle: { + var pos = position; + int memberCount = (propertyType == SerializedPropertyType.Vector2) ? 2 : + (propertyType == SerializedPropertyType.Vector3) ? 3 : 4; + pos.xMin += EditorGUIUtility.labelWidth; + pos.yMin = pos.yMax - EditorGUIUtility.singleLineHeight; + pos.width /= memberCount; + pos.height = EditorGUIUtility.singleLineHeight; + + for (int i = 0; i < memberCount; ++i) { + FusionEditorGUI.Overlay(pos, label); + pos.x += pos.width; + } + + break; + } + + case SerializedPropertyType.Vector2: + case SerializedPropertyType.Vector3: { + Rect pos = position; + // vector properties get broken down into two lines when there's not enough space + if (EditorGUIUtility.wideMode) { + pos.xMin += EditorGUIUtility.labelWidth; + pos.width /= 3; + } else { + pos.xMin += 12; + pos.yMin = pos.yMax - EditorGUIUtility.singleLineHeight; + pos.width /= (propertyType == SerializedPropertyType.Vector2) ? 2 : 3; + } + + pos.height = EditorGUIUtility.singleLineHeight; + FusionEditorGUI.Overlay(pos, label); + pos.x += pos.width; + FusionEditorGUI.Overlay(pos, label); + if (propertyType == SerializedPropertyType.Vector3) { + pos.x += pos.width; + FusionEditorGUI.Overlay(pos, label); + } + + break; + } + case SerializedPropertyType.Vector4: + if (isExpanded) { + Rect pos = position; + pos.yMin = pos.yMax - 4 * EditorGUIUtility.singleLineHeight - 3 * EditorGUIUtility.standardVerticalSpacing; + pos.height = EditorGUIUtility.singleLineHeight; + for (int i = 0; i < 4; ++i) { + FusionEditorGUI.Overlay(pos, label); + pos.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing; + } + } + + break; + default: { + var pos = position; + pos.height = EditorGUIUtility.singleLineHeight; + FusionEditorGUI.Overlay(pos, label); + } + break; + } + } + + public static string UnitToLabel(Units units) { + switch (units) { + case Units.None: return string.Empty; + case Units.Ticks: return "ticks"; + case Units.Seconds: return "s"; + case Units.MilliSecs: return "ms"; + case Units.Kilobytes: return "kB"; + case Units.Megabytes: return "MB"; + case Units.Normalized: return "normalized"; + case Units.Multiplier: return "multiplier"; + case Units.Percentage: return "%"; + case Units.NormalizedPercentage: return "n%"; + case Units.Degrees: return "\u00B0"; + case Units.PerSecond: return "hz"; + case Units.DegreesPerSecond: return "\u00B0/sec"; + case Units.Radians: return "rad"; + case Units.RadiansPerSecond: return "rad/s"; + case Units.TicksPerSecond: return "ticks/s"; + case Units.Units: return "units"; + case Units.Bytes: return "B"; + case Units.Count: return "count"; + case Units.Packets: return "packets"; + case Units.Frames: return "frames"; + case Units.FramesPerSecond: return "fps"; + case Units.SquareMagnitude: return "mag\u00B2"; + default: throw new ArgumentOutOfRangeException(nameof(units), $"{units}"); + } + } + } +} + +#endregion + + +#region UnityAddressablesRuntimeKeyAttributeDrawer.cs + +#if (FUSION_ADDRESSABLES || FUSION_ENABLE_ADDRESSABLES) && !FUSION_DISABLE_ADDRESSABLES +namespace Fusion.Editor { + using UnityEditor; + using UnityEngine; + using Object = UnityEngine.Object; + + [CustomPropertyDrawer(typeof(UnityAddressablesRuntimeKeyAttribute))] + internal class UnityAddressablesRuntimeKeyAttributeDrawer : PropertyDrawerWithErrorHandling { + protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) { + var attrib = (UnityAddressablesRuntimeKeyAttribute)attribute; + + using (new FusionEditorGUI.PropertyScopeWithPrefixLabel(position, label, property, out position)) { + position.width -= 40; + EditorGUI.PropertyField(position, property, GUIContent.none, false); + Object asset = null; + + var runtimeKey = property.stringValue; + + if (!string.IsNullOrEmpty(runtimeKey)) { + if (!FusionAddressablesUtils.TryParseAddress(runtimeKey, out var _, out var _)) { + SetError($"Not a valid address: {runtimeKey}"); + } else { + asset = FusionAddressablesUtils.LoadEditorInstance(runtimeKey); + if (asset == null) { + SetError($"Asset not found for runtime key: {runtimeKey}"); + } + } + } + + using (new FusionEditorGUI.EnabledScope(asset)) { + position.x += position.width; + position.width = 40; + if (GUI.Button(position, "Ping")) { + EditorGUIUtility.PingObject(asset); + } + } + } + } + } +} +#endif + +#endregion + + +#region UnityAssetGuidAttributeDrawer.cs + +namespace Fusion.Editor { + using System; + using UnityEditor; + using UnityEngine; + + [CustomPropertyDrawer(typeof(UnityAssetGuidAttribute))] + [FusionPropertyDrawerMeta(HasFoldout = false)] + internal class UnityAssetGuidAttributeDrawer : PropertyDrawerWithErrorHandling { + protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) { + string guid; + position.width -= 40; + + if (property.propertyType == SerializedPropertyType.Generic) { + guid = DrawMangledRawGuid(position, property, label); + } else if (property.propertyType == SerializedPropertyType.String) { + using (new FusionEditorGUI.PropertyScopeWithPrefixLabel(position, label, property, out position)) { + EditorGUI.PropertyField(position, property, GUIContent.none, false); + guid = property.stringValue; + } + } else { + throw new InvalidOperationException(); + } + + string assetPath = string.Empty; + + bool parsable = GUID.TryParse(guid, out _); + if (parsable) { + ClearError(); + assetPath = AssetDatabase.GUIDToAssetPath(guid); + } + + using (new FusionEditorGUI.EnabledScope(!string.IsNullOrEmpty(assetPath))) { + position.x += position.width; + position.width = 40; + + if (GUI.Button(position, "Ping")) { + EditorGUIUtility.PingObject(AssetDatabase.LoadMainAssetAtPath(assetPath)); + } + } + + if (string.IsNullOrEmpty(assetPath)) { + if (!parsable && !string.IsNullOrEmpty(guid)) { + SetError($"Invalid GUID: {guid}"); + } else if (!string.IsNullOrEmpty(guid)) { + SetWarning($"GUID not found"); + } + } else { + var asset = AssetDatabase.LoadMainAssetAtPath(assetPath); + if (asset == null) { + SetError($"Asset with this guid does not exist. Last path:\n{assetPath}"); + } else { + SetInfo($"Asset path:\n{assetPath}"); + } + } + } + + private unsafe string DrawMangledRawGuid(Rect position, SerializedProperty property, GUIContent label) { + var inner = property.Copy(); + inner.Next(true); + if (inner.depth != property.depth + 1 || !inner.isFixedBuffer || inner.fixedBufferSize != 2) { + throw new InvalidOperationException(); + } + + var prop0 = inner.GetFixedBufferElementAtIndex(0); + var prop1 = inner.GetFixedBufferElementAtIndex(1); + + string guid; + unsafe { + var rawMangled = stackalloc long[2]; + rawMangled[0] = prop0.longValue; + rawMangled[1] = prop1.longValue; + + Guid guidStruct = default; + CopyAndMangleGuid((byte*)rawMangled, (byte*)&guidStruct); + + using (new FusionEditorGUI.PropertyScope(position, label, property)) { + EditorGUI.BeginChangeCheck(); + guid = EditorGUI.TextField(position, label, guidStruct.ToString("N")); + if (EditorGUI.EndChangeCheck()) { + if (Guid.TryParse(guid, out guidStruct)) { + CopyAndMangleGuid((byte*)&guidStruct, (byte*)rawMangled); + prop0.longValue = rawMangled[0]; + prop1.longValue = rawMangled[1]; + } else { + SetError($"Unable to parse {guid}"); + } + } + } + } + + return guid; + } + + public static unsafe void CopyAndMangleGuid(byte* src, byte* dst) { + dst[0] = src[3]; + dst[1] = src[2]; + dst[2] = src[1]; + dst[3] = src[0]; + + dst[4] = src[5]; + dst[5] = src[4]; + + dst[6] = src[7]; + dst[7] = src[6]; + + dst[8] = src[8]; + dst[9] = src[9]; + dst[10] = src[10]; + dst[11] = src[11]; + dst[12] = src[12]; + dst[13] = src[13]; + dst[14] = src[14]; + dst[15] = src[15]; + } + + public bool HasFoldout(SerializedProperty property) { + return false; + } + } +} + +#endregion + + +#region UnityResourcePathAttributeDrawer.cs + +namespace Fusion.Editor { + using UnityEditor; + using UnityEngine; + + [CustomPropertyDrawer(typeof(UnityResourcePathAttribute))] + internal class UnityResourcePathAttributeDrawer : PropertyDrawerWithErrorHandling { + protected override void OnGUIInternal(Rect position, SerializedProperty property, GUIContent label) { + var attrib = (UnityResourcePathAttribute)attribute; + + using (new FusionEditorGUI.PropertyScopeWithPrefixLabel(position, label, property, out position)) { + position.width -= 40; + EditorGUI.PropertyField(position, property, GUIContent.none, false); + Object asset = null; + + var path = property.stringValue; + if (string.IsNullOrEmpty(path)) { + ClearError(); + } else { + asset = Resources.Load(path, attrib.ResourceType); + if (asset == null) { + SetError($"Resource of type {attrib.ResourceType} not found at {path}"); + } else { + SetInfo(AssetDatabase.GetAssetPath(asset)); + } + } + + using (new FusionEditorGUI.EnabledScope(asset)) { + position.x += position.width; + position.width = 40; + if (GUI.Button(position, "Ping")) { + EditorGUIUtility.PingObject(asset); + } + } + } + } + } +} + +#endregion + + +#region WarnIfAttributeDrawer.cs + +namespace Fusion.Editor { + using UnityEditor; + using UnityEngine; + + [CustomPropertyDrawer(typeof(WarnIfAttribute))] +#if !UNITY_6000_0_OR_NEWER + [RedirectCustomPropertyDrawer(typeof(WarnIfAttribute), typeof(WarnIfAttributeDrawer))] + partial class PropertyDrawerForArrayWorkaround { + } +#endif + partial class WarnIfAttributeDrawer : MessageIfDrawerBase { + private new WarnIfAttribute Attribute => (WarnIfAttribute)attribute; + + protected override bool IsBox => Attribute.AsBox; + protected override string Message => Attribute.Message; + protected override MessageType MessageType => MessageType.Warning; + protected override Color InlineBoxColor => FusionEditorSkin.WarningInlineBoxColor; + protected override Texture MessageIcon => FusionEditorSkin.WarningIcon; + } +} + + +#endregion + + + +#endregion + + +#region Assets/Photon/Fusion/Editor/FusionHierarchyWindowOverlay.cs + +namespace Fusion.Editor { + using System; + using Fusion.Analyzer; + using UnityEditor; + using UnityEngine; + using UnityEngine.SceneManagement; + + internal class FusionHierarchyWindowOverlay { + + [RuntimeInitializeOnLoadMethod] + public static void Initialize() { + UnityEditor.EditorApplication.hierarchyWindowItemOnGUI -= HierarchyWindowOverlay; + UnityEditor.EditorApplication.hierarchyWindowItemOnGUI += HierarchyWindowOverlay; + } + + [StaticField(StaticFieldResetMode.None)] + private static Lazy s_hierarchyOverlayLabelStyle = new Lazy(() => { + var result = new GUIStyle(UnityEditor.EditorStyles.miniButton); + result.alignment = TextAnchor.MiddleCenter; + result.fontSize = 9; + result.padding = new RectOffset(4, 4, 0, 0); + result.fixedHeight = 13f; + return result; + }); + + [StaticField(StaticFieldResetMode.None)] + private static GUIContent s_multipleInstancesContent = EditorGUIUtility.IconContent("Warning", "multiple"); + + private static void HierarchyWindowOverlay(int instanceId, Rect position) { +#if UNITY_6000_3_OR_NEWER + var entityId = (EntityId)instanceId; + var obj = UnityEditor.EditorUtility.EntityIdToObject(entityId); +#else + var entityId = instanceId; + var obj = UnityEditor.EditorUtility.InstanceIDToObject(entityId); +#endif + if (obj != null) { + return; + } + + // find a scene for this id + Scene scene = default; + for (int i = 0; i < SceneManager.sceneCount; ++i) { + var s = SceneManager.GetSceneAt(i); + if (s.handle == entityId) { + scene = s; + break; + } + } + + if (!scene.IsValid()) { + return; + } + + var instances = NetworkRunner.Instances; + + NetworkRunner matchingRunner = null; + bool multipleRunners = false; + + for (int i = 0; i < instances.Count; ++i) { + var runner = instances[i]; + + if (runner.SimulationUnityScene == scene) { + if (matchingRunner == null) { + matchingRunner = runner; + } else { + multipleRunners = true; + break; + } + } + } + + if (!matchingRunner) { + return; + } + + var rect = new Rect(position) { + xMin = position.xMax - 56, + xMax = position.xMax - 2, + yMin = position.yMin + 1, + }; + + { + if (multipleRunners) { + if (EditorGUI.DropdownButton(rect, s_multipleInstancesContent, FocusType.Passive, s_hierarchyOverlayLabelStyle.Value)) { + var menu = new GenericMenu(); + for (int i = 0; i < instances.Count; ++i) { + var runner = instances[i]; + var otherScene = runner.SimulationUnityScene; + if (!otherScene.IsValid()) { + continue; + } + if (otherScene.handle == instanceId) { + menu.AddItem(MakeRunnerContent(runner), false, () => { + EditorGUIUtility.PingObject(runner); + Selection.activeObject = runner; + }); + } + } + menu.ShowAsContext(); + } + } else { + var runner = matchingRunner; + if (GUI.Button(rect, MakeRunnerContent(runner), s_hierarchyOverlayLabelStyle.Value)) { + EditorGUIUtility.PingObject(runner); + Selection.activeGameObject = runner.gameObject; + } + } + } + + GUIContent MakeRunnerContent(NetworkRunner runner) { + return new GUIContent($"{runner.Mode} {(runner.LocalPlayer.IsRealPlayer ? "P" + runner.LocalPlayer.PlayerId.ToString() : "")}"); + } + } + } +} + + +#endregion + + +#region Assets/Photon/Fusion/Editor/FusionHubWindowUtils.cs + +// ---------------------------------------------------------------------------- +// +// PhotonNetwork Framework for Unity - Copyright (C) 2021 Exit Games GmbH +// +// +// MenuItems and in-Editor scripts for PhotonNetwork. +// +// developer@exitgames.com +// ---------------------------------------------------------------------------- + +namespace Fusion.Editor { +#if FUSION_WEAVER && UNITY_EDITOR + using System; + using System.Collections.Generic; + using System.ComponentModel; + using System.IO; + using System.Text.RegularExpressions; + using Photon.Realtime; + using UnityEditor; + using UnityEngine; + using UnityEngine.UI; + + public partial class FusionHubWindow { + /// + /// Section Definition. + /// + internal class Section { + public string Title; + public string Description; + public Action DrawMethod; + public Icon Icon; + + public Section(string title, string description, Action drawMethod, Icon icon) { + Title = title; + Description = description; + DrawMethod = drawMethod; + Icon = icon; + } + } + + public enum Icon { + Setup, + Documentation, + Samples, + Community, + ProductLogo, + PhotonCloud, + FusionIcon, + } + + private static class Constants { + public const string UrlFusionDocsOnline = "https://doc.photonengine.com/fusion/v2/"; + public const string UrlFusionIntro = "https://doc.photonengine.com/fusion/v2/getting-started/fusion-introduction"; + public const string UrlFusionSDK = "https://doc.photonengine.com/fusion/v2/getting-started/sdk-download"; + public const string UrlCloudDashboard = "https://id.photonengine.com/account/signin?email="; + public const string UrlDashboardProfile = "https://dashboard.photonengine.com/Account/Profile"; + public const string UrlDashboard = "https://dashboard.photonengine.com/"; + public const string UrlSampleSection = "https://doc.photonengine.com/fusion/v2/current/samples/overview"; + public const string UrlFusion100 = "https://doc.photonengine.com/fusion/v2/tutorials/shared-mode-basics/overview"; + public const string UrlFusionLoop = "https://doc.photonengine.com/fusion/v2/current/samples/fusion-application-loop"; + public const string UrlHelloFusion = "https://doc.photonengine.com/fusion/v2/current/hello-fusion/hello-fusion"; + public const string UrlHelloFusionVr = "https://doc.photonengine.com/fusion/v2/current/hello-fusion/hello-fusion-vr"; + public const string UrlTanks = "https://doc.photonengine.com/fusion/v2/current/samples/fusion-tanknarok"; + public const string UrlKarts = "https://doc.photonengine.com/fusion/v2/current/samples/fusion-karts"; + public const string UrlDragonHuntersVR = "https://doc.photonengine.com/fusion/v2/current/samples/fusion-dragonhunters-vr"; + + public const string UrlFusionDocApi = "https://doc-api.photonengine.com/en/fusion/v2/index.html"; + public const string WindowTitle = "Photon Fusion 2 Hub"; + public const string Support = "You can contact the Photon Team using one of the following links. You can also go to Photon Documentation in order to get started."; + public const string DiscordText = "Create a Photon account and join the Discord."; + public const string DiscordHeader = "Community"; + public const string DocumentationText = "Open the documentation."; + public const string DocumentationHeader = "Documentation"; + + public const string WelcomeText = "Thank you for installing Photon Fusion 2.\n\n" + + "Once you have set up your Fusion 2 App Id, explore the sections on the left to get started. " + + "More samples, tutorials, and documentation are being added regularly - so check back often."; + + public const string RealtimeAppidSetupInstructions = + @"An Fusion App Id Version 2 is required for networking. + + To acquire an Fusion App Id: + - Open the Photon Dashboard (Log-in as required). + - Select an existing Fusion 2 App Id, or; + - Create a new one (make sure to select SDK Version 2). + - Copy the App Id and paste into the field below (or into the PhotonAppSettings.asset). + "; + + public const string GettingStartedInstructions = + @"Links to demos, tutorials, API references and other information can be found on the PhotonEngine.com website."; + } + + public Texture2D SetupIcon; + public Texture2D DocumentationIcon; + public Texture2D SamplesIcon; + public Texture2D CommunityIcon; + public Texture2D ProductLogo; + public Texture2D PhotonCloudIcon; + public Texture2D FusionIcon; + public Texture2D CorrectIcon; + + private Texture2D GetIcon(Icon icon) { + switch (icon) { + case Icon.Setup: return SetupIcon; + case Icon.Documentation: return DocumentationIcon; + case Icon.Samples: return SamplesIcon; + case Icon.Community: return CommunityIcon; + case Icon.ProductLogo: return ProductLogo; + case Icon.PhotonCloud: return PhotonCloudIcon; + case Icon.FusionIcon: return FusionIcon; + default: return null; + } + } + + [NonSerialized] private Section[] _sections; + + private static string releaseHistoryHeader; + private static List releaseHistoryTextAdded; + private static List releaseHistoryTextChanged; + private static List releaseHistoryTextFixed; + private static List releaseHistoryTextRemoved; + private static List releaseHistoryTextInternal; + + private static string fusionReleaseHistory; + + public GUISkin FusionHubSkin; + + private static GUIStyle _navbarHeaderGraphicStyle; + private static GUIStyle textLabelStyle; + private static GUIStyle headerLabelStyle; + private static GUIStyle releaseNotesStyle; + private static GUIStyle headerTextStyle; + private static GUIStyle buttonActiveStyle; + + private bool InitContent() { + if (_ready.HasValue && _ready.Value) { + return _ready.Value; + } + + // skip while being loaded + if (FusionHubSkin == null) { return false; } + + // Just need to run once + FusionGlobalScriptableObjectUtils.EnsureAssetExists(); + FusionGlobalScriptableObjectUtils.EnsureAssetExists(); + + _sections = new[] { + new Section("Welcome", "Welcome to Photon Fusion 2", DrawWelcomeSection, Icon.Setup), new Section("Fusion 2 Setup", "Setup Photon Fusion 2", DrawSetupSection, Icon.PhotonCloud), + new Section("Tutorials & Samples", "Fusion Tutorials and Samples", DrawSamplesSection, Icon.Samples), + new Section("Documentation", "Photon Fusion Documentation", DrawDocumentationSection, Icon.Documentation), + new Section("Fusion Release Notes", "Fusion Release Notes", DrawFusionReleaseSection, Icon.Documentation), + new Section("Support", "Support and Community Links", DrawSupportSection, Icon.Community), + }; + + Color commonTextColor = Color.white; + + var _guiSkin = FusionHubSkin; + + _navbarHeaderGraphicStyle = new GUIStyle(_guiSkin.button) { alignment = TextAnchor.MiddleCenter }; + + headerTextStyle = new GUIStyle(_guiSkin.label) { fontSize = 18, padding = new RectOffset(12, 8, 8, 8), fontStyle = FontStyle.Bold, normal = { textColor = commonTextColor } }; + + buttonActiveStyle = new GUIStyle(_guiSkin.button) { fontStyle = FontStyle.Bold, normal = { background = _guiSkin.button.active.background, textColor = Color.white } }; + + + textLabelStyle = new GUIStyle(_guiSkin.label) { wordWrap = true, normal = { textColor = commonTextColor }, richText = true, }; + headerLabelStyle = new GUIStyle(textLabelStyle) { fontSize = 15, }; + + releaseNotesStyle = new GUIStyle(textLabelStyle) { richText = true, }; + + return (_ready = true).Value; + } + + private static Action OpenURL(string url, params object[] args) { + return () => { + if (args.Length > 0) { + url = string.Format(url, args); + } + + Application.OpenURL(url); + }; + } + + protected static bool IsAppIdValid() { + if (PhotonAppSettings.TryGetGlobal(out var global) && Guid.TryParse(global.AppSettings.AppIdFusion, out var guid)) { + return true; + } + + return false; + } + + static string titleVersionReformat, sectionReformat, header1Reformat, header2Reformat, header3Reformat, classReformat; + + void InitializeFormatters() { + titleVersionReformat = "$1"; + sectionReformat = "$1"; + header1Reformat = "$1"; + header2Reformat = "$1"; + header3Reformat = "$1"; + classReformat = "$1"; + } + + /// + /// Converts readme files into Unity RichText. + /// + private void PrepareReleaseHistoryText() { + if (sectionReformat == null || sectionReformat == "") { + InitializeFormatters(); + } + + // Fusion + { + try { + var filePath = BuildPath(Application.dataPath, "Photon", "Fusion", "release_history.txt"); + var text = (TextAsset)AssetDatabase.LoadAssetAtPath(filePath, typeof(TextAsset)); + var baseText = text.text; + + // # + baseText = Regex.Replace(baseText, @"^# (.*)", titleVersionReformat); + baseText = Regex.Replace(baseText, @"(?<=\n)# (.*)", header1Reformat); + // ## + baseText = Regex.Replace(baseText, @"(?<=\n)## (.*)", header2Reformat); + // ### + baseText = Regex.Replace(baseText, @"(?<=\n)### (.*)", header3Reformat); + // **Changes** + baseText = Regex.Replace(baseText, @"(?<=\n)\*\*(.*)\*\*", sectionReformat); + // `Class` + baseText = Regex.Replace(baseText, @"\`([^\`]*)\`", classReformat); + + fusionReleaseHistory = baseText; + } catch { + fusionReleaseHistory = "Unable to load Release History."; + } + } + + // Realtime + { + try { + var filePath = BuildPath(Application.dataPath, "Photon", "PhotonRealtime", "Code", "changes-realtime.txt"); + + var text = (TextAsset)AssetDatabase.LoadAssetAtPath(filePath, typeof(TextAsset)); + + var baseText = text.text; + + var regexVersion = new Regex(@"Version (\d+\.?)*", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Multiline); + var regexAdded = new Regex(@"\b(Added:)(.*)\b", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Multiline); + var regexChanged = new Regex(@"\b(Changed:)(.*)\b", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Multiline); + var regexUpdated = new Regex(@"\b(Updated:)(.*)\b", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Multiline); + var regexFixed = new Regex(@"\b(Fixed:)(.*)\b", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Multiline); + var regexRemoved = new Regex(@"\b(Removed:)(.*)\b", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Multiline); + var regexInternal = new Regex(@"\b(Internal:)(.*)\b", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Multiline); + + var matches = regexVersion.Matches(baseText); + + if (matches.Count > 0) { + var currentVersionMatch = matches[0]; + var lastVersionMatch = currentVersionMatch.NextMatch(); + + if (currentVersionMatch.Success && lastVersionMatch.Success) { + Func> itemProcessor = (match) => { + List resultList = new List(); + for (int index = 0; index < match.Count; index++) { + resultList.Add(match[index].Groups[2].Value.Trim()); + } + + return resultList; + }; + + string mainText = baseText.Substring(currentVersionMatch.Index + currentVersionMatch.Length, + lastVersionMatch.Index - lastVersionMatch.Length - 1).Trim(); + + releaseHistoryHeader = currentVersionMatch.Value.Trim(); + releaseHistoryTextAdded = itemProcessor(regexAdded.Matches(mainText)); + releaseHistoryTextChanged = itemProcessor(regexChanged.Matches(mainText)); + releaseHistoryTextChanged.AddRange(itemProcessor(regexUpdated.Matches(mainText))); + releaseHistoryTextFixed = itemProcessor(regexFixed.Matches(mainText)); + releaseHistoryTextRemoved = itemProcessor(regexRemoved.Matches(mainText)); + releaseHistoryTextInternal = itemProcessor(regexInternal.Matches(mainText)); + } + } + } catch { + releaseHistoryHeader = "\nPlease look the file changes-realtime.txt"; + releaseHistoryTextAdded = new List(); + releaseHistoryTextChanged = new List(); + releaseHistoryTextFixed = new List(); + releaseHistoryTextRemoved = new List(); + releaseHistoryTextInternal = new List(); + } + } + } + + public static bool Toggle(bool value) { + var toggle = new GUIStyle("Toggle") { margin = new RectOffset(), padding = new RectOffset() }; + + return EditorGUILayout.Toggle(value, toggle, GUILayout.Width(15)); + } + + private static string BuildPath(params string[] parts) { + var basePath = ""; + + foreach (var path in parts) { + basePath = Path.Combine(basePath, path); + } + + return basePath.Replace(Application.dataPath, Path.GetFileName(Application.dataPath)); + } + } +#endif +} + +#endregion + + +#region Assets/Photon/Fusion/Editor/FusionInstaller.cs + +namespace Fusion.Editor { +#if !FUSION_DEV + using System; + using System.Collections.Generic; + using System.IO; + using System.Text.RegularExpressions; + using UnityEditor; + using UnityEditor.Build; + using UnityEditor.PackageManager; + using UnityEngine; + + [InitializeOnLoad] + internal class FusionInstaller { + // Defines to add + private const string DEFINE_VERSION = "FUSION2"; + private const string DEFINE_WEAVER = "FUSION_WEAVER"; + + // Extended Version Defines + private const string DEFINE_VERSION_EXTENDED_CHECK = @"FUSION(_[\d]+){1,3}(_OR_NEWER)?"; + private const string DEFINE_VERSION_EXTENDED = "FUSION"; + private static string DEFINE_VERSION_EXTENDED_MAJOR => DEFINE_VERSION_EXTENDED + $"_{Versioning.GetCurrentVersion.Major}"; + private static string DEFINE_VERSION_EXTENDED_MAJOR_MINOR => DEFINE_VERSION_EXTENDED_MAJOR + $"_{Versioning.GetCurrentVersion.Minor}"; + private static string DEFINE_VERSION_EXTENDED_MAJOR_MINOR_PATCH => DEFINE_VERSION_EXTENDED_MAJOR_MINOR + $"_{Versioning.GetCurrentVersion.Build}"; + + // Defines for Logs + private const string DEFINE_LOG_CHECK = "FUSION_LOGLEVEL_"; + private const string DEFINE_LOG_DEFAULT = "FUSION_LOGLEVEL_INFO"; + + // Packages to search for + private const string PACKAGE_TO_SEARCH = "nuget.mono-cecil"; + private const string PACKAGE_TO_INSTALL = "com.unity.nuget.mono-cecil@1.10.2"; + + // Constants + private const string PACKAGES_DIR = "Packages"; + private const string MANIFEST_FILE = "manifest.json"; + + static FusionInstaller() { + var defines = GetCurrentDefines(); + + // Check for Defines + if (defines.Contains(DEFINE_WEAVER) && defines.Contains(DEFINE_VERSION) && defines.Contains(DEFINE_VERSION_EXTENDED_MAJOR_MINOR_PATCH)) { + // check version defines here + return; + } + + if (PlayerSettings.runInBackground == false) { + FusionEditorLog.LogInstaller($"Setting {nameof(PlayerSettings)}.{nameof(PlayerSettings.runInBackground)} to true"); + PlayerSettings.runInBackground = true; + } + + var manifest = Path.Combine(Path.GetDirectoryName(Application.dataPath) ?? string.Empty, PACKAGES_DIR, MANIFEST_FILE); + + if (File.ReadAllText(manifest).Contains(PACKAGE_TO_SEARCH)) { + // append defines + TryAddDefine(ref defines, DEFINE_WEAVER, d => d.Contains(DEFINE_WEAVER) == false); + TryAddDefine(ref defines, DEFINE_VERSION, d => d.Contains(DEFINE_VERSION) == false); + + // Remove any previous version defines + CheckDefineForRemoval(ref defines, d => Regex.IsMatch(d, DEFINE_VERSION_EXTENDED_CHECK) == false); + TryAddDefine(ref defines, DEFINE_VERSION_EXTENDED_MAJOR, d => d.Contains(DEFINE_VERSION_EXTENDED_MAJOR) == false); + TryAddDefine(ref defines, DEFINE_VERSION_EXTENDED_MAJOR_MINOR, d => d.Contains(DEFINE_VERSION_EXTENDED_MAJOR_MINOR) == false); + TryAddDefine(ref defines, DEFINE_VERSION_EXTENDED_MAJOR_MINOR_PATCH, d => d.Contains(DEFINE_VERSION_EXTENDED_MAJOR_MINOR_PATCH) == false); + + foreach (var extraVersion in BuildVersionDefines()) { + TryAddDefine(ref defines, extraVersion, d => d.Contains(extraVersion) == false); + } + + // Add default Log Level if none is found + TryAddDefine(ref defines, DEFINE_LOG_DEFAULT, d => d.Contains(DEFINE_LOG_CHECK) == false); + + SetCurrentDefines(defines); + } else { + FusionEditorLog.LogInstaller($"Installing '{PACKAGE_TO_INSTALL}' package"); + Client.Add(PACKAGE_TO_INSTALL); + } + } + + private static void CheckDefineForRemoval(ref string defines, Func check) { + List filteredDefines = new(); + + foreach (var define in defines.Split(";")) { + if (check(define)) { + filteredDefines.Add(define); + } + } + + defines = string.Join(";", filteredDefines); + } + + private static void TryAddDefine(ref string defines, string targetDefine, Func check) { + if (check(defines)) { + defines = $"{defines};{targetDefine}"; + FusionEditorLog.LogInstaller($"Adding Fusion Define Symbol: '{targetDefine}'"); + } + } + + private static IEnumerable BuildVersionDefines() { + for (var i = 2; i <= Versioning.GetCurrentVersion.Major; i++) { + yield return DEFINE_VERSION_EXTENDED + $"_{i}_OR_NEWER"; + + for (var j = 0; j <= Versioning.GetCurrentVersion.Minor; j++) { + yield return DEFINE_VERSION_EXTENDED + $"_{i}_{j}_OR_NEWER"; + } + } + } + + private static string GetCurrentDefines() { +#if UNITY_SERVER + var defines = PlayerSettings.GetScriptingDefineSymbols(UnityEditor.Build.NamedBuildTarget.Server); +#else + var group = BuildPipeline.GetBuildTargetGroup(EditorUserBuildSettings.activeBuildTarget); + var defines = PlayerSettings.GetScriptingDefineSymbols(NamedBuildTarget.FromBuildTargetGroup(group)); +#endif + + return defines; + } + + private static void SetCurrentDefines(string defines) { +#if UNITY_SERVER + PlayerSettings.SetScriptingDefineSymbols(UnityEditor.Build.NamedBuildTarget.Server, defines); +#else + var group = BuildPipeline.GetBuildTargetGroup(EditorUserBuildSettings.activeBuildTarget); + PlayerSettings.SetScriptingDefineSymbols(NamedBuildTarget.FromBuildTargetGroup(group), defines); +#endif + } + } +#endif +} + +#endregion + + +#region Assets/Photon/Fusion/Editor/FusionSceneSetupAssistants.cs + +namespace Fusion.Editor { + using UnityEditor; + + using UnityEngine; + using UnityEngine.SceneManagement; + using System.Collections.Generic; + + public static class FusionSceneSetupAssistants { + + [MenuItem("Tools/Fusion/Scene/Setup Networking in the Scene", false, FusionAssistants.PRIORITY_LOW + 1)] + [MenuItem("GameObject/Fusion/Scene/Setup Networking in the Scene", false, FusionAssistants.PRIORITY + 1)] + public static void AddNetworkingToScene() { + (FusionBootstrap nds, NetworkRunner nr) n = AddNetworkStartup(); + n.nr.gameObject.EnsureComponentExists(); + + // Get scene and mark scene as dirty. + DirtyAndSaveScene(n.nds.gameObject.scene); + } + + public static (FusionBootstrap, NetworkRunner) AddNetworkStartup() { + // Restrict to single AudioListener to disallow multiple active in shared instance mode (preventing log spam) + HandleAudioListeners(); + + // Restrict lights to single active instances node to Lights + HandleLights(); + + // Add NetworkDebugRunner if missing + var nds = FusionAssistants.EnsureExistsInScene("Prototype Network Start"); + + NetworkRunner nr = nds.RunnerPrefab == null ? null : nds.RunnerPrefab.TryGetComponent(out var found) ? found : null; + // Add NetworkRunner to scene if the DebugStart doesn't have one as a prefab set already. + if (nr == null) { + + // Add NetworkRunner to scene if NetworkDebugStart doesn't have one set as a prefab already. + nr = FusionAssistants.EnsureExistsInScene("Prototype Runner"); + + nds.RunnerPrefab = nr; + // The runner go is also our fallback spawn point... so raise it into the air a bit + nr.transform.position = new Vector3(0, 3, 0); + } + + return (nds, nr); + } + + [MenuItem("Tools/Fusion/Scene/Add Current Scene To Build Settings", false, FusionAssistants.PRIORITY_LOW)] + [MenuItem("GameObject/Fusion/Scene/Add Current Scene To Build Settings", false, FusionAssistants.PRIORITY)] + public static void AddCurrentSceneToSettings() { DirtyAndSaveScene(SceneManager.GetActiveScene()); } + public static void DirtyAndSaveScene(Scene scene) { + + UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(scene); + var scenename = scene.path; + + // Give chance to save - required in order to build out. If users cancel will only be able to run in the editor. + if (scenename == "") { + UnityEditor.SceneManagement.EditorSceneManager.SaveModifiedScenesIfUserWantsTo(new Scene[] { scene }); + scenename = scene.path; + } + + // Add scene to Build and Fusion settings + if (scenename != "") { + scene.AddSceneToBuildSettings(); + } + } + + [MenuItem("Tools/Fusion/Scene/Setup Multi-Peer AudioListener Handling", false, FusionAssistants.PRIORITY_LOW + 1)] + [MenuItem("GameObject/Fusion/Scene/Setup Multi-Peer AudioListener Handling", false, FusionAssistants.PRIORITY + 1)] + public static void HandleAudioListeners() { + int count = 0; + foreach (var listener in Object.FindObjectsByType(FindObjectsInactive.Exclude, FindObjectsSortMode.None)) { + count++; + listener.EnsureComponentHasVisibilityNode(); + } + Debug.Log($"{count} {nameof(AudioListener)}(s) found and given a {nameof(RunnerVisibilityLink)} component."); + } + + [MenuItem("Tools/Fusion/Scene/Setup Multi-Peer Lights Handling", false, FusionAssistants.PRIORITY_LOW + 1)] + [MenuItem("GameObject/Fusion/Scene/Setup Multi-Peer Lights Handling", false, FusionAssistants.PRIORITY + 1)] + public static void HandleLights() { + int count = 0; + foreach (var listener in Object.FindObjectsByType(FindObjectsInactive.Exclude, FindObjectsSortMode.None)) { + count++; + listener.EnsureComponentHasVisibilityNode(); + } + Debug.Log($"{count} {nameof(Light)}(s) found and given a {nameof(RunnerVisibilityLink)} component."); + } + + public static void AddSceneToBuildSettings(this Scene scene) { + var buildScenes = EditorBuildSettings.scenes; + bool isInBuildScenes = false; + foreach (var bs in buildScenes) { + if (bs.path == scene.path) { + isInBuildScenes = true; + break; + } + } + if (isInBuildScenes == false) { + var buildList = new List(); + buildList.Add(new EditorBuildSettingsScene(scene.path, true)); + buildList.AddRange(buildScenes); + Debug.Log($"Added '{scene.path}' as first entry in Build Settings."); + EditorBuildSettings.scenes = buildList.ToArray(); + } + } + } +} + + +#endregion + + +#region Assets/Photon/Fusion/Editor/FusionUnitySurrogateBaseWrapper.cs + +namespace Fusion.Editor { + using System; + using Internal; + using UnityEditor; + using UnityEngine; + + internal class FusionUnitySurrogateBaseWrapper : ScriptableObject { + [SerializeReference] + public UnitySurrogateBase Surrogate; + [NonSerialized] + public SerializedProperty SurrogateProperty; + [NonSerialized] + public Type SurrogateType; + } +} + +#endregion + + +#region Assets/Photon/Fusion/Editor/ILWeaverUtils.cs + +namespace Fusion.Editor { + using UnityEditor; + using UnityEditor.Compilation; + + [InitializeOnLoad] + public static class ILWeaverUtils { + [MenuItem("Tools/Fusion/Run Weaver")] + public static void RunWeaver() { + + CompilationPipeline.RequestScriptCompilation( +#if UNITY_2021_1_OR_NEWER + RequestScriptCompilationOptions.CleanBuildCache +#endif + ); + } + } +} + +#endregion + + +#region Assets/Photon/Fusion/Editor/NetworkBehaviourEditor.cs + +namespace Fusion.Editor { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + using UnityEditor; + using UnityEngine; + + [CustomEditor(typeof(NetworkBehaviour), true)] + [CanEditMultipleObjects] + public class NetworkBehaviourEditor : BehaviourEditor { + + internal const string NETOBJ_REQUIRED_WARN_TEXT = "This " + nameof(NetworkBehaviour) + " requires a " + nameof(NetworkObject) + " component to function."; + + IEnumerable ValidTargets => targets + .Cast() + .Where(x => x.Object && x.Object.IsValid && x.Object.gameObject.activeInHierarchy); + + [NonSerialized] + int[] _buffer = Array.Empty(); + + + public override void OnInspectorGUI() { + base.PrepareOnInspectorGUI(); + + bool hasBeenApplied = false; +#if !FUSION_DISABLE_NBEDITOR_PRESERVE_BACKING_FIELDS + // serialize unchanged serialized state into zero-initialized memory; + // this makes sure defaults are preserved + TransferBackingFields(backingFieldsToState: true); +#endif + try { + + // after the original values have been saved, they can be overwritten with + // whatever is in the state + foreach (var target in ValidTargets) { + target.CopyStateToBackingFields(); + } + + // move C# fields to SerializedObject + serializedObject.UpdateIfRequiredOrScript(); + + EditorGUI.BeginChangeCheck(); + + base.DrawDefaultInspector(); + + if (EditorGUI.EndChangeCheck()) { + // serialized properties -> C# fields + serializedObject.ApplyModifiedProperties(); + hasBeenApplied = true; + + // C# fields -> state + foreach (var target in ValidTargets) { + if (target.Object.HasStateAuthority) { + target.CopyBackingFieldsToState(false); + } + } + + } + } finally { +#if !FUSION_DISABLE_NBEDITOR_PRESERVE_BACKING_FIELDS + // now restore the default values + TransferBackingFields(backingFieldsToState: false); + serializedObject.Update(); + if (hasBeenApplied) { + serializedObject.ApplyModifiedProperties(); + } + } +#endif + + DrawNetworkObjectCheck(); + DrawEditorButtons(); + } + + unsafe bool TransferBackingFields(bool backingFieldsToState) { + + if (Allocator.REPLICATE_WORD_SIZE == sizeof(int)) { + int offset = 0; + bool hadChanges = false; + + int requiredSize = ValidTargets.Sum(x => x.WordCount); + if (backingFieldsToState) { + if (_buffer.Length >= requiredSize) { + Array.Clear(_buffer, 0, _buffer.Length); + } else { + _buffer = new int[requiredSize]; + } + } else { + if (_buffer.Length < requiredSize) { + throw new InvalidOperationException("Buffer is too small"); + } + } + + fixed (int* p = _buffer) { + foreach (var target in ValidTargets) { + var ptr = target.Ptr; + + try { + target.Ptr = p + offset; + if (backingFieldsToState) { + target.CopyBackingFieldsToState(false); + } else { + target.CopyStateToBackingFields(); + } + + if (!hadChanges) { + if (Native.MemCmp(target.Ptr, ptr, target.WordCount * Allocator.REPLICATE_WORD_SIZE) != 0) { + hadChanges = true; + } + } + + } finally { + target.Ptr = ptr; + } + + offset += target.WordCount; + } + } + + return hadChanges; + } + } + + + /// + /// Checks if GameObject or parent GameObject has a NetworkObject, and draws a warning and buttons for adding one if not. + /// + /// + void DrawNetworkObjectCheck() { + var targetsWithoutNetworkObjects = targets.Cast().Where(x => x.transform.GetParentComponent() == false).ToList(); + if (targetsWithoutNetworkObjects.Any()) { + + using (new FusionEditorGUI.WarningScope(NETOBJ_REQUIRED_WARN_TEXT, 6f)) { + IEnumerable gameObjects = null; + + if (GUI.Button(EditorGUILayout.GetControlRect(false, 22), "Add Network Object")) { + gameObjects = targetsWithoutNetworkObjects.Select(x => x.gameObject).Distinct(); + } + + if (GUI.Button(EditorGUILayout.GetControlRect(false, 22), "Add Network Object to Root")) { + gameObjects = targetsWithoutNetworkObjects.Select(x => x.transform.root.gameObject).Distinct(); + } + + if (gameObjects != null) { + foreach (var go in gameObjects) { + Undo.AddComponent(go); + } + } + } + } + } + } +} + + +#endregion + + +#region Assets/Photon/Fusion/Editor/NetworkMecanimAnimatorBaker.cs + +namespace Fusion.Editor { + using System.Linq; + using UnityEditor; + using UnityEngine; + + public static class NetworkMecanimAnimatorBaker { + [NetworkObjectBakerEditTimeHandler] + public static bool PostprocessAnimator(NetworkMecanimAnimator animator) { + bool dirty = false; + if (animator.Animator == null) { + animator.Animator = animator.GetComponent(); + if (animator.Animator == null) { + FusionEditorLog.Error($"Cannot bake {animator.name}'s {nameof(NetworkMecanimAnimator)} without an {nameof(Animator)} assigned!", animator.gameObject); + return false; + } else { + dirty = true; + } + } + if (AnimatorControllerTools.GetController(animator.Animator) == null) { + FusionEditorLog.Error($"Cannot bake {animator.name}'s {nameof(NetworkMecanimAnimator)} without an {nameof(UnityEditor.Animations.AnimatorController)} assigned to its {nameof(Animator)}!", animator.gameObject); + return dirty; + } + + AnimatorControllerTools.GetHashesAndNames(animator, null, null, ref animator.TriggerHashes, ref animator.StateHashes); + + // this is dictated by the animator controller + FusionEditorLog.Assert(animator.StateHashes[0] == 0); + foreach (var hash in animator.StateHashes.Skip(1)) { + if (hash >= 0 && hash < animator.StateHashes.Length) { + FusionEditorLog.Error($"State hash {hash} is out of range for {animator.name}", animator.gameObject); + } + } + + FusionEditorLog.Assert(animator.TriggerHashes[0] == 0); + foreach (var hash in animator.TriggerHashes.Skip(1)) { + if (hash >= 0 && hash < animator.TriggerHashes.Length) { + FusionEditorLog.Error($"Trigger hash {hash} is out of range for {animator.name}", animator.gameObject); + } + } + + int wordCount = AnimatorControllerTools.GetWordCount(animator); + if (animator.TotalWords != wordCount) { + animator.TotalWords = wordCount; + EditorUtility.SetDirty(animator); + return true; + } + + return dirty; + } + } +} + + +#endregion + + +#region Assets/Photon/Fusion/Editor/NetworkObjectBakerEditTime.cs + +namespace Fusion.Editor { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + using UnityEditor; + using UnityEngine; + + public class NetworkObjectBakerEditTime : NetworkObjectBaker { + private Dictionary _executionOrderCache = new (); + private ILookup _bakeHandlers; + + public NetworkObjectBakerEditTime() { + _bakeHandlers = TypeCache.GetMethodsWithAttribute() + .Select(m => { + var order = m.GetCustomAttribute().Order; + + var parameters = m.GetParameters(); + Assert.Check(parameters.Length == 1); + + var parameterType = parameters[0].ParameterType; + Assert.Check(parameterType == typeof(NetworkBehaviour) || parameterType.IsSubclassOf(typeof(NetworkBehaviour))); + + var handler = Delegate.CreateDelegate(typeof(Func<,>).MakeGenericType(parameterType, typeof(bool)), m, true); + return (parameterType, order, handler); + }) + .OrderBy(t => t.order) + .ToLookup(t => t.parameterType, (t) => t.handler); + } + + protected override bool TryGetExecutionOrder(MonoBehaviour obj, out int order) { + // is there a cached value? + if (_executionOrderCache.TryGetValue(obj.GetType(), out var orderNullable)) { + order = orderNullable ?? default; + return orderNullable != null; + } + + var monoScript = UnityEditor.MonoScript.FromMonoBehaviour(obj); + if (monoScript) { + orderNullable = UnityEditor.MonoImporter.GetExecutionOrder(monoScript); + } else { + orderNullable = null; + } + + _executionOrderCache.Add(obj.GetType(), orderNullable); + order = orderNullable ?? default; + return orderNullable != null; + } + + protected override void SetDirty(MonoBehaviour obj) { + EditorUtility.SetDirty(obj); + } + + protected override uint GetSortKey(NetworkObject obj) { + var globalId = GlobalObjectId.GetGlobalObjectIdSlow(obj); + int hash = 0; + + hash = HashCodeUtilities.GetHashCodeDeterministic(globalId.identifierType, hash); + hash = HashCodeUtilities.GetHashCodeDeterministic(globalId.assetGUID, hash); + hash = HashCodeUtilities.GetHashCodeDeterministic(globalId.targetObjectId, hash); + hash = HashCodeUtilities.GetHashCodeDeterministic(globalId.targetPrefabId, hash); + + return (uint)hash; + } + + protected override bool PostprocessBehaviour(SimulationBehaviour behaviour) { + for (var type = behaviour.GetType(); type != typeof(SimulationBehaviour) && type != typeof(NetworkBehaviour); type = type.BaseType) { + foreach (var handler in _bakeHandlers[type]) { + if ((bool)handler.DynamicInvoke(behaviour)) { + return true; + } + } + } + + return false; + } + } +} + + +#endregion + + +#region Assets/Photon/Fusion/Editor/NetworkObjectBakerEditTimeHandlerAttribute.cs + +namespace Fusion.Editor { + using System; + + [AttributeUsage(AttributeTargets.Method)] + public class NetworkObjectBakerEditTimeHandlerAttribute : Attribute { + public int Order { get; set; } + } +} + +#endregion + + +#region Assets/Photon/Fusion/Editor/NetworkObjectEditor.cs + +namespace Fusion.Editor { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + using UnityEditor; + using UnityEngine; +#if UNITY_2021_2_OR_NEWER + using UnityEditor.SceneManagement; + +#else + using UnityEditor.Experimental.SceneManagement; +#endif + + [CustomEditor(typeof(NetworkObject), true)] + [InitializeOnLoad] + [CanEditMultipleObjects] + public unsafe class NetworkObjectEditor : BehaviourEditor { + private bool _runtimeInfoFoldout; + + private static PropertyInfo _isSpawnable = typeof(NetworkObject).GetPropertyOrThrow(nameof(NetworkObject.IsSpawnable)); + private static FieldInfo _networkTypeId = typeof(NetworkObject).GetFieldOrThrow(nameof(NetworkObject.NetworkTypeId)); + private static PropertyInfo _networkId = typeof(NetworkObject).GetPropertyOrThrow(nameof(NetworkObject.Id)); + private static FieldInfo _nestingRoot = typeof(NetworkObjectHeader).GetFieldOrThrow(nameof(NetworkObjectHeader.NestingRoot)); + private static FieldInfo _nestingKey = typeof(NetworkObjectHeader).GetFieldOrThrow(nameof(NetworkObjectHeader.NestingKey)); + private static PropertyInfo _InputAuthority = typeof(NetworkObject).GetPropertyOrThrow(nameof(NetworkObject.InputAuthority)); + private static PropertyInfo _StateAuthority = typeof(NetworkObject).GetPropertyOrThrow(nameof(NetworkObject.StateAuthority)); + private static PropertyInfo _HasInputAuthority = typeof(NetworkObject).GetPropertyOrThrow(nameof(NetworkObject.HasInputAuthority)); + private static PropertyInfo _HasStateAuthority = typeof(NetworkObject).GetPropertyOrThrow(nameof(NetworkObject.HasStateAuthority)); + + static string GetLoadInfoString(NetworkObjectGuid guid) { + if (NetworkProjectConfigUtilities.TryGetGlobalPrefabSource(guid, out INetworkPrefabSource prefabSource)) { + return prefabSource.Description; + } + + return "Null"; + } + + public override void OnInspectorGUI() { + FusionEditorGUI.InjectScriptHeaderDrawer(serializedObject); + FusionEditorGUI.ScriptPropertyField(serializedObject); + + // these properties' isExpanded are going to be used for foldouts; that's the easiest + // way to get quasi-persistent foldouts + + var flagsProperty = serializedObject.FindPropertyOrThrow(nameof(NetworkObject.Flags)); + var obj = (NetworkObject)base.target; + var netObjType = typeof(NetworkObject); + + if (targets.Length == 1) { + if (AssetDatabase.IsMainAsset(obj.gameObject) || PrefabStageUtility.GetPrefabStage(obj.gameObject)?.prefabContentsRoot == obj.gameObject) { + Debug.Assert(!AssetDatabaseUtils.IsSceneObject(obj.gameObject)); + + if (!obj.Flags.IsVersionCurrent()) { + using (new FusionEditorGUI.WarningScope("Prefab needs to be re-imported.")) { + if (GUILayout.Button("Reimport")) { + string assetPath = PrefabStageUtility.GetPrefabStage(obj.gameObject)?.assetPath ?? AssetDatabase.GetAssetPath(obj.gameObject); + Debug.Assert(!string.IsNullOrEmpty(assetPath)); + AssetDatabase.ImportAsset(assetPath); + } + } + } else { + EditorGUILayout.Space(); + EditorGUILayout.LabelField("Prefab Settings", EditorStyles.boldLabel); + + // Is Spawnable + { + EditorGUI.BeginChangeCheck(); + + bool spawnable = EditorGUI.Toggle(FusionEditorGUI.LayoutHelpPrefix(this, _isSpawnable), _isSpawnable.Name, !obj.Flags.IsIgnored()); + if (EditorGUI.EndChangeCheck()) { + var value = obj.Flags.SetIgnored(!spawnable); + serializedObject.FindProperty(nameof(NetworkObject.Flags)).intValue = (int)value; + serializedObject.ApplyModifiedProperties(); + } + +#if FUSION_DEV + var prefabGuid = GetPrefabGuid(obj); + FusionEditorGUI.LayoutSelectableLabel(new GUIContent($"Guid"), prefabGuid.ToUnityGuidString()); +#endif + + string loadInfo = "---"; + if (spawnable) { + string assetPath = PrefabStageUtility.GetPrefabStage(obj.gameObject)?.assetPath ?? AssetDatabase.GetAssetPath(obj.gameObject); + if (!string.IsNullOrEmpty(assetPath)) { + var guid = AssetDatabase.AssetPathToGUID(assetPath); + loadInfo = GetLoadInfoString(NetworkObjectGuid.Parse(guid)); + } + } + + EditorGUILayout.LabelField("Prefab Source", loadInfo); + } + } + } else if (AssetDatabaseUtils.IsSceneObject(obj.gameObject)) { + if (!obj.Flags.IsVersionCurrent()) { + if (!EditorApplication.isPlaying) { + using (new FusionEditorGUI.WarningScope("This object hasn't been baked yet. Save the scene or enter playmode.")) { + } + } + } + } + } + + + if (EditorApplication.isPlaying && targets.Length == 1) { + EditorGUILayout.Space(); + flagsProperty.isExpanded = EditorGUILayout.Foldout(flagsProperty.isExpanded, "Runtime Info"); + if (flagsProperty.isExpanded) { + using (new FusionEditorGUI.BoxScope(null, 1)) { + EditorGUI.LabelField(FusionEditorGUI.LayoutHelpPrefix(this, _networkTypeId), _networkTypeId.Name, obj.NetworkTypeId.ToString()); + EditorGUILayout.Toggle("Is Valid", obj.IsValid); + if (obj.IsValid) { + EditorGUI.LabelField(FusionEditorGUI.LayoutHelpPrefix(this, _networkId), _networkId.Name, obj.Id.ToString()); + EditorGUILayout.IntField("Word Count", NetworkObject.GetWordCount(obj)); + + + bool headerIsNull = obj.Meta == null; + EditorGUI.LabelField(FusionEditorGUI.LayoutHelpPrefix(this, _nestingRoot), _nestingRoot.Name, headerIsNull ? "---" : obj.Meta.NestingRoot.ToString()); + EditorGUI.LabelField(FusionEditorGUI.LayoutHelpPrefix(this, _nestingKey), _nestingKey.Name, headerIsNull ? "---" : obj.Meta.NestingKey.ToString()); + + EditorGUI.LabelField(FusionEditorGUI.LayoutHelpPrefix(this, _InputAuthority), _InputAuthority.Name, obj.InputAuthority.ToString()); + EditorGUI.LabelField(FusionEditorGUI.LayoutHelpPrefix(this, _StateAuthority), _StateAuthority.Name, obj.StateAuthority.ToString()); + + EditorGUI.Toggle(FusionEditorGUI.LayoutHelpPrefix(this, _HasInputAuthority), _InputAuthority.Name, obj.HasInputAuthority); + EditorGUI.Toggle(FusionEditorGUI.LayoutHelpPrefix(this, _HasStateAuthority), _StateAuthority.Name, obj.HasStateAuthority); + + EditorGUILayout.Toggle("Is Simulated", obj.IsInSimulation); + EditorGUILayout.Toggle("Is Local PlayerObject", ReferenceEquals(obj.Runner.GetPlayerObject(obj.Runner.LocalPlayer), obj)); + EditorGUILayout.Toggle("Has Main TRSP", obj.Meta?.HasMainTRSP ?? false); + + EditorGUILayout.LabelField("Runtime Flags", obj.RuntimeFlags.ToString()); + EditorGUILayout.LabelField("Header Flags", obj.Meta?.Flags.ToString()); + + + if (obj.Runner.IsClient) { + EditorGUILayout.IntField("Last Received Tick", obj.LastReceiveTick); + } + } + } + } + } + + EditorGUI.BeginChangeCheck(); + + var config = NetworkProjectConfig.Global; + var isPlaying = EditorApplication.isPlaying; + + void DrawToggleFlag(NetworkObjectFlags flag, string name, bool? force = null) { + var x = (obj.Flags & flag) == flag; + + var r = EditorGUILayout.Toggle(name, x); + if (r != x || (force.HasValue && r != force.Value)) { + if (force.HasValue) { + r = force.Value; + } + + if (r) { + obj.Flags |= flag; + } else { + obj.Flags &= ~flag; + } + + EditorUtility.SetDirty(obj); + } + } + + using (new EditorGUI.DisabledScope(isPlaying)) { + EditorGUILayout.Space(); + EditorGUILayout.LabelField("Shared Mode Settings", EditorStyles.boldLabel); + + DrawToggleFlag(NetworkObjectFlags.MasterClientObject, "Is Master Client Object"); + + EditorGUI.BeginDisabledGroup((obj.Flags & NetworkObjectFlags.MasterClientObject) == NetworkObjectFlags.MasterClientObject); + if ((obj.Flags & NetworkObjectFlags.MasterClientObject) == NetworkObjectFlags.MasterClientObject) { + DrawToggleFlag(NetworkObjectFlags.AllowStateAuthorityOverride, "Allow State Authority Override", false); + } else { + DrawToggleFlag(NetworkObjectFlags.AllowStateAuthorityOverride, "Allow State Authority Override"); + } + + if ((obj.Flags & NetworkObjectFlags.MasterClientObject) == NetworkObjectFlags.MasterClientObject) { + DrawToggleFlag(NetworkObjectFlags.DestroyWhenStateAuthorityLeaves, "Destroy When State Authority Leaves", false); + } else { + DrawToggleFlag(NetworkObjectFlags.DestroyWhenStateAuthorityLeaves, "Destroy When State Authority Leaves"); + } + + EditorGUI.EndDisabledGroup(); + + + //var destroyWhenStateAuthLeaves = serializedObject.FindProperty(nameof(NetworkObject.DestroyWhenStateAuthorityLeaves)); + //EditorGUILayout.PropertyField(destroyWhenStateAuthLeaves); + // + //var allowStateAuthorityOverride = serializedObject.FindProperty(nameof(NetworkObject.AllowStateAuthorityOverride)); + //EditorGUILayout.PropertyField(allowStateAuthorityOverride); + + EditorGUILayout.Space(); + EditorGUILayout.LabelField("Interest Management Settings", EditorStyles.boldLabel); + + + var objectInterest = serializedObject.FindProperty(nameof(NetworkObject.ObjectInterest)); + EditorGUILayout.PropertyField(objectInterest); + + if (objectInterest.intValue == (int)NetworkObject.ObjectInterestModes.AreaOfInterest) { + //EditorGUILayout.PropertyField(serializedObject.FindProperty(nameof(NetworkObject.AreaOfInterestTransform))); + } + + //using (new EditorGUI.IndentLevelScope()) { + // EditorGUILayout.PropertyField(serializedObject.FindPropertyOrThrow(nameof(NetworkObject.DefaultInterestGroups))); + //} + } + + if (EditorGUI.EndChangeCheck()) { + serializedObject.ApplyModifiedProperties(); + } + + EditorGUILayout.Space(); + EditorGUI.DrawRect(EditorGUILayout.GetControlRect(false, 1), Color.gray); + EditorGUILayout.Space(); + + EditorGUILayout.LabelField("Baked Data", EditorStyles.boldLabel); + using (new FusionEditorGUI.BoxScope(null, 1)) { + using (new EditorGUI.DisabledScope(true)) { + using (new FusionEditorGUI.ShowMixedValueScope(flagsProperty.hasMultipleDifferentValues)) { + FusionEditorGUI.LayoutSelectableLabel(EditorGUIUtility.TrTextContent(nameof(obj.Flags)), obj.Flags.ToString()); + FusionEditorGUI.LayoutSelectableLabel(EditorGUIUtility.TrTextContent(nameof(obj.SortKey)), obj.SortKey.ToString("X8")); + } + + using (new EditorGUI.IndentLevelScope()) { + EditorGUILayout.PropertyField(serializedObject.FindPropertyOrThrow(nameof(NetworkObject.NestedObjects))); + EditorGUILayout.PropertyField(serializedObject.FindPropertyOrThrow(nameof(NetworkObject.NetworkedBehaviours))); + } + } + } + + // Runtime buttons + + if (obj.Runner && obj.Runner.IsRunning) { + + EditorGUILayout.Space(); + EditorGUI.DrawRect(EditorGUILayout.GetControlRect(false, 1), Color.gray); + EditorGUILayout.Space(); + + // Input Authority Popup + using (new EditorGUI.DisabledScope(obj.HasStateAuthority == false)) { + var elements = GetInputAuthorityPopupContent(obj); + + var index = EditorGUILayout.Popup(_guiContentInputAuthority, elements.currentIndex, elements.content); + if (index != elements.currentIndex) { + obj.AssignInputAuthority(PlayerRef.FromIndex(elements.ids[index])); + } + } + + if (obj.Runner.GameMode == GameMode.Shared) { + if (GUILayout.Button("Request State Authority")) { + obj.RequestStateAuthority(); + } + } + + if (GUILayout.Button("Despawn")) { + obj.Runner.Despawn(obj); + } + } + } + + private static bool Set(UnityEngine.Object host, ref T field, T value, Action setDirty) { + if (!EqualityComparer.Default.Equals(field, value)) { + Trace($"Object dirty: {host} ({field} vs {value})"); + setDirty?.Invoke(host); + field = value; + return true; + } else { + return false; + } + } + + private static bool Set(UnityEngine.Object host, ref T[] field, List value, Action setDirty) { + var comparer = EqualityComparer.Default; + if (field == null || field.Length != value.Count || !field.SequenceEqual(value, comparer)) { + Trace($"Object dirty: {host} ({field} vs {value})"); + setDirty?.Invoke(host); + field = value.ToArray(); + return true; + } else { + return false; + } + } + + [System.Diagnostics.Conditional("FUSION_EDITOR_TRACE")] + private static void Trace(string msg) { + Debug.Log($"[Fusion/NetworkObjectEditor] {msg}"); + } + + public static NetworkObjectGuid GetPrefabGuid(NetworkObject prefab) { + if (prefab == null) { + throw new ArgumentNullException(nameof(prefab)); + } + + if (!AssetDatabase.TryGetGUIDAndLocalFileIdentifier(prefab, out var guidStr, out long _)) { + throw new ArgumentException($"No guid for {prefab}", nameof(prefab)); + } + + return NetworkObjectGuid.Parse(guidStr); + } + + private static GUIContent[] _reusableContent; + private static int[] _reusablePlayerIds; + private static readonly GUIContent _guiContentEmpty = new GUIContent(""); + private static readonly GUIContent _guiContentNone = new GUIContent("None"); + private static readonly GUIContent _guiContentInputAuthority = new GUIContent("Input Authority"); + + private static (int[] ids, GUIContent[] content, int currentIndex) GetInputAuthorityPopupContent(NetworkObject obj) { + int requiredLength = obj.Runner.ActivePlayers.Count() + 2; + if (_reusableContent == null || requiredLength > _reusableContent.Length) { + _reusablePlayerIds = new int[requiredLength]; + _reusablePlayerIds[0] = -1; + _reusablePlayerIds[1] = 0; + _reusableContent = new GUIContent[requiredLength]; + _reusableContent[0] = _guiContentNone; + _reusableContent[1] = _guiContentEmpty; + } + + int indexOfCurrentPlayer = 0; + + // clear + for (int i = 2; i < _reusableContent.Length; i++) { + _reusableContent[i] = _guiContentEmpty; + } + + int index = 2; + + foreach (var player in obj.Runner.ActivePlayers) { + _reusablePlayerIds[index] = player.PlayerId; + _reusableContent[index] = new GUIContent($"Player {player.PlayerId}"); + if (player.PlayerId == obj.InputAuthority.PlayerId) { + indexOfCurrentPlayer = index; + } + index++; + } + return (_reusablePlayerIds, _reusableContent, indexOfCurrentPlayer); + } + } +} + +#endregion + + +#region Assets/Photon/Fusion/Editor/NetworkObjectPostprocessor.cs + +namespace Fusion.Editor { + using System; + using System.Collections.Generic; + using System.Linq; + using UnityEditor; + using UnityEditor.Build; + using UnityEditor.Build.Reporting; + using UnityEditor.SceneManagement; + using UnityEngine; + using UnityEngine.SceneManagement; + + public class NetworkObjectPostprocessor : AssetPostprocessor { + + public static event Action OnBakePrefab; + public static event Action OnBakeScene; + + static NetworkObjectPostprocessor() { + EditorSceneManager.sceneSaving += OnSceneSaving; + EditorApplication.playModeStateChanged += OnPlaymodeChange; + } + + static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths) { + FusionEditorLog.TraceImport($"Postprocessing imported assets [{importedAssets.Length}]:\n{string.Join("\n", importedAssets)}"); + + bool rebuildPrefabHash = false; + + + foreach (var path in importedAssets) { + if (!IsPrefabPath(path)) { + continue; + } + + var go = AssetDatabase.LoadAssetAtPath(path); + if (!go) { + continue; + } + + var isSpawnable = false; + var needsBaking = false; + + var no = go.GetComponent(); + if (no) { + // NO prefab, needs labels adjusted and hash needs to be rebuilt + rebuildPrefabHash = true; + needsBaking = true; + isSpawnable = !no.Flags.IsIgnored(); + } + + if (AssetDatabaseUtils.SetLabel(go, NetworkProjectConfigImporter.FusionPrefabTag, isSpawnable)) { + rebuildPrefabHash = true; + AssetDatabase.ImportAsset(path); + FusionEditorLog.TraceImport(path, "Labels were dirty"); + } else if (no) { + FusionEditorLog.TraceImport(path, "Labels up to date"); + } + + if (needsBaking) { +#if UNITY_2023_1_OR_NEWER || UNITY_2022_3_OR_NEWER + if (Array.IndexOf(movedAssets, path) >= 0) { + // attempting to bake a prefab that has been moved would hang the editor + // https://issuetracker.unity3d.com/issues/editor-freezes-when-prefabutility-dot-loadprefabcontents-is-called-in-assetpostprocessor-dot-onpostprocessallassets-for-a-moved-prefab + continue; + } +#endif + FusionEditorLog.TraceImport(path, "Baking"); + BakePrefab(path, out _); + } + } + + foreach (var path in movedAssets) { + if (!IsPrefabPath(path)) { + continue; + } + if (!AssetDatabaseUtils.HasLabel(path, NetworkProjectConfigImporter.FusionPrefabTag)) { + continue; + } + rebuildPrefabHash = true; + break; + } + + foreach (var path in deletedAssets) { + if (!IsPrefabPath(path)) { + continue; + } + rebuildPrefabHash = true; + break; + } + + if (rebuildPrefabHash) { + NetworkProjectConfigImporter.RebuildPrefabHash(); + } + } + + static bool IsPrefabPath(string path) { + return path.EndsWith(".prefab"); + } + + static bool IsNetworkObjectPrefab(string path, out NetworkObject no) { + if (!path.EndsWith(".prefab")) { + // not a prefab + no = null; + return false; + } + + var go = AssetDatabase.LoadAssetAtPath(path); + if (!go) { + no = null; + return false; + } + + no = go.GetComponent(); + return no; + } + + void OnPostprocessPrefab(GameObject prefab) { + var no = prefab.GetComponent(); + + if (no && no.IsSpawnable) { + var existing = prefab.GetComponent(); + if (existing != null) { + // this is likely a variant prefab, can't add the next one + // also, component loses hide flags at this point, so they need to be restored + // weirdly, this is the only case where altering a component in OnPostprocessPrefab works + // without causing an import warning + existing.Guid = NetworkObjectGuid.Parse(AssetDatabase.AssetPathToGUID(context.assetPath)); + existing.hideFlags = HideFlags.DontSaveInEditor | HideFlags.HideInInspector | HideFlags.NotEditable; + } else { + var indirect = prefab.AddComponent(); + indirect.Guid = NetworkObjectGuid.Parse(AssetDatabase.AssetPathToGUID(context.assetPath)); + indirect.hideFlags |= HideFlags.HideInInspector | HideFlags.NotEditable; + } + } + } + + + static bool BakePrefab(string prefabPath, out GameObject root) { + + root = null; + + var assetGuid = AssetDatabase.AssetPathToGUID(prefabPath); + if (!NetworkObjectGuid.TryParse(assetGuid, out var guid)) { + FusionEditorLog.ErrorImport(prefabPath, $"Unable to parse guid: \"{assetGuid}\", not going to bake"); + return false; + } + + var stageGo = PrefabUtility.LoadPrefabContents(prefabPath); + if (!stageGo) { + FusionEditorLog.ErrorImport(prefabPath, $"Unable to load prefab contents"); + return false; + } + + var sw = System.Diagnostics.Stopwatch.StartNew(); + + try { + bool dirty = false; + bool baked = false; + + if (OnBakePrefab != null) { + var args = new NetworkObjectBakePrefabArgs(_baker, stageGo, prefabPath); + OnBakePrefab(args); + if (args.Handled) { + baked = true; + dirty = args.IsPrefabDirty; + } + } + + if (!baked) { + dirty = _baker.Bake(stageGo).HadChanges; + } + + FusionEditorLog.TraceImport(prefabPath, $"Baking took {sw.Elapsed}, changed: {dirty}"); + + if (dirty) { + root = PrefabUtility.SaveAsPrefabAsset(stageGo, prefabPath); + } + + return root; + } finally { + PrefabUtility.UnloadPrefabContents(stageGo); + } + } + + private static NetworkObjectBaker _baker = new NetworkObjectBakerEditTime(); + + private static void OnPlaymodeChange(PlayModeStateChange change) { + if (change != PlayModeStateChange.ExitingEditMode) { + return; + } + for (int i = 0; i < EditorSceneManager.sceneCount; ++i) { + BakeScene(EditorSceneManager.GetSceneAt(i)); + } + } + + private static void OnSceneSaving(Scene scene, string path) { + BakeScene(scene); + } + + [MenuItem("Tools/Fusion/Scene/Bake Scene Objects", false, FusionAssistants.PRIORITY_LOW - 1)] + [MenuItem("GameObject/Fusion/Scene/Bake Scene Objects", false, FusionAssistants.PRIORITY - 1)] + public static void BakeAllOpenScenes() { + for (int i = 0; i < SceneManager.sceneCount; ++i) { + var scene = SceneManager.GetSceneAt(i); + try { + BakeScene(scene); + } catch (Exception ex) { + Debug.LogError($"Failed to bake scene {scene}: {ex}"); + } + } + } + + public static void BakeScene(Scene scene) { + var sw = System.Diagnostics.Stopwatch.StartNew(); + try { + + if (OnBakeScene != null) { + var args = new NetworkObjectBakeSceneArgs(_baker, scene); + OnBakeScene(args); + if (args.Handled) { + return; + } + } + + foreach (var root in scene.GetRootGameObjects()) { + _baker.Bake(root); + } + + } finally { + FusionEditorLog.TraceImport(scene.path, $"Baking {scene} took: {sw.Elapsed}"); + } + } + } + + public class NetworkObjectBakePrefabArgs { + public bool IsPrefabDirty { get; set; } + public bool Handled { get; set; } + public GameObject LoadedPrefabRoot { get; } + public string Path { get; } + public NetworkObjectBaker Baker { get; } + + public NetworkObjectBakePrefabArgs(NetworkObjectBaker baker, GameObject loadedPrefabRoot, string path) { + LoadedPrefabRoot = loadedPrefabRoot; + Path = path; + Baker = baker; + } + } + + public class NetworkObjectBakeSceneArgs { + public bool Handled { get; set; } + public Scene Scene { get; } + public NetworkObjectBaker Baker { get; } + + public NetworkObjectBakeSceneArgs(NetworkObjectBaker baker, Scene scene) { + Scene = scene; + Baker = baker; + } + } +} + +#endregion + + +#region Assets/Photon/Fusion/Editor/NetworkPrefabSourceFactories.cs + +namespace Fusion.Editor { + using System; + using System.Collections.Generic; + using System.Linq; + using UnityEditor; + using UnityEngine; + + partial interface INetworkAssetSourceFactory { + INetworkPrefabSource TryCreatePrefabSource(in NetworkAssetSourceFactoryContext context); + } + + public class NetworkAssetSourceFactory { + private readonly List _factories = TypeCache.GetTypesDerivedFrom() + .Select(x => (INetworkAssetSourceFactory)Activator.CreateInstance(x)) + .OrderBy(x => x.Order) + .ToList(); + + public INetworkPrefabSource TryCreatePrefabSource(in NetworkAssetSourceFactoryContext context, bool removeFaultedFactories = true) { + for (int i = 0; i < _factories.Count; ++i) { + var factory = _factories[i]; + + try { + var source = factory.TryCreatePrefabSource(in context); + if (source != null) { + return source; + } + } catch (Exception ex) when(removeFaultedFactories) { + FusionEditorLog.Error($"Prefab source factory {factory.GetType().Name} failed for {context.AssetPath}. " + + $"This factory will be removed from the list of available factories during this import." + + $"Reimport of fix the underlying issue: {ex}"); + } + } + + return null; + } + } + + partial class NetworkAssetSourceFactoryStatic { + public INetworkPrefabSource TryCreatePrefabSource(in NetworkAssetSourceFactoryContext context) { + if (TryCreateInternal(context, out var result)) { + result.AssetGuid = NetworkObjectGuid.Parse(context.AssetGuid); + }; + return result; + } + } + + partial class NetworkAssetSourceFactoryResource { + public INetworkPrefabSource TryCreatePrefabSource(in NetworkAssetSourceFactoryContext context) { + if (TryCreateInternal(context, out var result)) { + result.AssetGuid = NetworkObjectGuid.Parse(context.AssetGuid); + }; + return result; + } + } + +#if FUSION_ENABLE_ADDRESSABLES && !FUSION_DISABLE_ADDRESSABLES + partial class NetworkAssetSourceFactoryAddressable { + public INetworkPrefabSource TryCreatePrefabSource(in NetworkAssetSourceFactoryContext context) { + if (TryCreateInternal(context, out var result)) { + result.AssetGuid = NetworkObjectGuid.Parse(context.AssetGuid); + }; + return result; + } + } +#endif +} + +#endregion + + +#region Assets/Photon/Fusion/Editor/NetworkRunnerEditor.cs + +namespace Fusion.Editor { + using System; + using System.Linq; + using System.Runtime.InteropServices; + using UnityEditor; + using UnityEngine; + + [CustomEditor(typeof(NetworkRunner))] + public class NetworkRunnerEditor : BehaviourEditor { + + void Label(string label, T value) { + EditorGUILayout.LabelField(label, (value != null ? value.ToString() : "null")); + } + + public override void OnInspectorGUI() { + base.OnInspectorGUI(); + + var runner = target as NetworkRunner; + if (runner && EditorApplication.isPlaying) { + Label("State", runner.IsRunning ? "Running" : (runner.IsShutdown ? "Shutdown" : "None")); + + if (runner.IsRunning) { + Label("Game Mode", runner.GameMode); + Label("Simulation Mode", runner.Mode); + Label("Is Player", runner.IsPlayer); + Label("Local Player", runner.LocalPlayer); + Label("Has Connection Token?", runner.GetPlayerConnectionToken() != null); + + var localplayerobj = runner.LocalPlayer.IsRealPlayer ? runner.GetPlayerObject(runner.LocalPlayer) : null; + EditorGUILayout.ObjectField("Local PlayerObject", localplayerobj, typeof(NetworkObject), true); + + Label("Is SinglePlayer", runner.IsSinglePlayer); + + if (runner.TryGetSceneInfo(out var sceneInfo)) { + Label("Scene Info", sceneInfo); + } else { + Label("Scene Info", $"Invalid"); + } + + var playerCount = runner.ActivePlayers.Count(); + Label("Active Players", playerCount); + + if (runner.IsServer && playerCount > 0) { + foreach (var player in runner.ActivePlayers) { + + // skip local player + if (runner.LocalPlayer == player) { + continue; + } + + Label("Player:PlayerId", player.PlayerId); + Label("Player:ConnectionType", runner.GetPlayerConnectionType(player)); + Label("Player:UserId", runner.GetPlayerUserId(player)); + Label("Player:RTT", runner.GetPlayerRtt(player)); + Label("Player:Committed?", runner.IsPlayerCommitted(player)); + } + } + + if (runner.IsClient) { + Label("Is Connected To Server", runner.IsConnectedToServer); + Label("Current Connection Type", runner.CurrentConnectionType); + } + } + + Label("Is Cloud Ready", runner.IsCloudReady); + + if (runner.IsCloudReady) { + Label("Is Shared Mode Master Client", runner.IsSharedModeMasterClient); + Label("UserId", runner.UserId); + Label("AuthenticationValues", runner.AuthenticationValues); + } + + Label("SessionInfo:IsValid", runner.SessionInfo.IsValid); + + if (runner.SessionInfo.IsValid) { + Label("SessionInfo:Name", runner.SessionInfo.Name); + Label("SessionInfo:IsVisible", runner.SessionInfo.IsVisible); + Label("SessionInfo:IsOpen", runner.SessionInfo.IsOpen); + Label("SessionInfo:Region", runner.SessionInfo.Region); + } + + Label("LobbyInfo:IsValid", runner.LobbyInfo.IsValid); + + if (runner.LobbyInfo.IsValid) { + Label("LobbyInfo:Name", runner.LobbyInfo.Name); + Label("LobbyInfo:Region", runner.LobbyInfo.Region); + } + } else { + if (runner.TryGetComponent(out var _) == false) { + EditorGUILayout.Space(2); + if (GUI.Button(EditorGUILayout.GetControlRect(), $"Add {nameof(RunnerEnableVisibility)}")) { + runner.gameObject.AddComponent(); + } + } + + } + } + } +} + +#endregion + + +#region Assets/Photon/Fusion/Editor/NetworkSceneDebugStartEditor.cs + +// file deleted + +#endregion + + +#region Assets/Photon/Fusion/Editor/NetworkTRSPEditor.cs + +namespace Fusion.Editor { + + using UnityEditor; + + [CustomEditor(typeof(NetworkTRSP), true)] + public unsafe class NetworkTRSPEditor : NetworkBehaviourEditor { + public override void OnInspectorGUI() { + base.OnInspectorGUI(); + + var t = (NetworkTRSP)target; + using (new EditorGUI.DisabledScope(true)) { + if (t && t.StateBufferIsValid && t.CanReceiveRenderCallback) { + var found = t.Runner.TryFindObject(t.Data.Parent.Object, out var parent); + EditorGUILayout.LabelField("Parent", $"'{(found ? parent.name : "Not Available")}' : {t.Data.Parent.Object.ToString()}"); + } + } + } + } +} + +#endregion + + +#region Assets/Photon/Fusion/Editor/PhotonAppSettingsEditor.cs + +namespace Fusion.Editor { + using System.Collections; + using System.Collections.Generic; + using UnityEngine; + using UnityEditor; + using Photon.Realtime; + + [CustomEditor(typeof(PhotonAppSettings))] + public class PhotonAppSettingsEditor : Editor { + + public override void OnInspectorGUI() { + FusionEditorGUI.InjectScriptHeaderDrawer(serializedObject); + base.DrawDefaultInspector(); + } + + [MenuItem("Tools/Fusion/Realtime Settings", priority = 200)] + public static void PingNetworkProjectConfigAsset() { + EditorGUIUtility.PingObject(PhotonAppSettings.Global); + Selection.activeObject = PhotonAppSettings.Global; + } + } + +} + + + +#endregion + + +#region Assets/Photon/Fusion/Editor/ReflectionUtils.Partial.cs + +namespace Fusion.Editor { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + using System.Runtime.CompilerServices; + using System.Text; + using UnityEngine; + + partial class ReflectionUtils { + public static string GetCSharpConstraints(this Type type) { + if (type == null) { + throw new ArgumentNullException(nameof(type)); + } + + if (!type.IsGenericTypeDefinition) { + return ""; + } + + var result = new StringBuilder(); + + foreach (var genericArg in type.GetGenericArguments()) { + var constraints = new List(); + + var attribs = genericArg.GenericParameterAttributes; + + if (attribs.HasFlag(GenericParameterAttributes.NotNullableValueTypeConstraint)) { + if (genericArg.GetCustomAttributes().Any(x => x.GetType().FullName == "System.Runtime.CompilerServices.IsUnmanagedAttribute")) { + constraints.Add("unmanaged"); + } else { + constraints.Add("struct"); + } + } else if (attribs.HasFlag(GenericParameterAttributes.ReferenceTypeConstraint)) { + constraints.Add("class"); + } else { + foreach (var c in genericArg.GetGenericParameterConstraints().Where(x => !x.IsInterface)) { + constraints.Add(GetCSharpTypeName(c)); + } + } + + foreach (var c in genericArg.GetGenericParameterConstraints().Where(x => x.IsInterface)) { + constraints.Add(GetCSharpTypeName(c)); + } + + if (attribs.HasFlag(GenericParameterAttributes.DefaultConstructorConstraint) && !attribs.HasFlag(GenericParameterAttributes.NotNullableValueTypeConstraint)) { + constraints.Add("new()"); + } + + if (constraints.Any()) { + if (result.Length != 0) { + result.Append(" "); + } + + result.Append($"where {genericArg.Name} : {string.Join(", ", constraints)}"); + } + } + + return result.ToString(); + } + + public static string GetCSharpTypeName(this Type type, string suffix = null, bool includeNamespace = true, bool includeGenerics = true, bool useGenericNames = false, bool shortNameForBuiltIns = true) { + + if (shortNameForBuiltIns) { + if (type == typeof(bool)) { + return "bool"; + } + if (type == typeof(byte)) { + return "byte"; + } + if (type == typeof(sbyte)) { + return "sbyte"; + } + if (type == typeof(short)) { + return "short"; + } + if (type == typeof(ushort)) { + return "ushort"; + } + if (type == typeof(int)) { + return "int"; + } + if (type == typeof(uint)) { + return "uint"; + } + if (type == typeof(long)) { + return "long"; + } + if (type == typeof(ulong)) { + return "ulong"; + } + if (type == typeof(float)) { + return "float"; + } + if (type == typeof(double)) { + return "double"; + } + if (type == typeof(char)) { + return "char"; + } + if (type == typeof(void)) { + return "void"; + } + if (type == typeof(string)) { + return "string"; + } + if (type == typeof(object)) { + return "object"; + } + if (type == typeof(decimal)) { + return "decimal"; + } + } + + string fullName; + + if (includeNamespace) { + fullName = type.FullName; + if (fullName == null) { + if (type.IsGenericParameter) { + fullName = type.Name; + } else { + fullName = type.Namespace + "." + type.Name; + } + } + } else { + fullName = type.Name; + } + + if (useGenericNames && type.IsConstructedGenericType) { + type = type.GetGenericTypeDefinition(); + } + + string result; + if (type.IsGenericType) { + var parentType = fullName.Split('`').First(); + if (includeGenerics) { + var genericArguments = string.Join(", ", type.GetGenericArguments().Select(x => x.GetCSharpTypeName())); + result = $"{parentType}{suffix ?? ""}<{genericArguments}>"; + } else { + result = $"{parentType}{suffix ?? ""}"; + } + } else { + result = fullName + (suffix ?? ""); + } + + return result.Replace('+', '.'); + } + + public static string GetCSharpTypeGenerics(this Type type, bool useGenericNames = false, bool useGenericPlaceholders = false) { + string result; + if (type.IsGenericType) { + var genericArguments = string.Join(", ", type.GetGenericArguments().Select(x => useGenericPlaceholders ? "" : x.GetCSharpTypeName())); + result = $"<{genericArguments}>"; + } else { + result = ""; + } + + result = result.Replace('+', '.'); + return result; + } + + public static string GetCSharpAttributeDefinition(this MemberInfo type) where T : Attribute { + var attributeData = type.GetCustomAttributesData().SingleOrDefault(x => x.AttributeType == typeof(T)); + if (attributeData == null) { + throw new InvalidOperationException($"Attribute {typeof(T).FullName} not found"); + } + + // need a fix for generic typeofs + var constructorArgs = attributeData.ConstructorArguments + .Select(arg => arg.ArgumentType == typeof(Type) ? $"typeof({((Type)arg.Value).GetCSharpTypeName()})" : arg.ToString()); + + // named generic arguments not yet supported + var namedArgs = attributeData.NamedArguments + .Select(arg => arg.ToString()); + + return $"[{attributeData.Constructor.DeclaringType!.FullName}({string.Join(", ", constructorArgs.Concat(namedArgs))})]"; + } + + public static string GetCSharpVisibility(this MemberInfo memberInfo) { + if (memberInfo is Type type) { + return GetTypeVisibility(type.Attributes & TypeAttributes.VisibilityMask); + } + if (memberInfo is MethodBase method) { + return GetMethodVisibility(method.Attributes & MethodAttributes.MemberAccessMask); + } + if (memberInfo is PropertyInfo propertyInfo) { + return GetMethodVisibility(propertyInfo.GetMethod.Attributes & MethodAttributes.MemberAccessMask); + } + if (memberInfo is FieldInfo field) { + return GetFieldVisibility(field.Attributes & FieldAttributes.FieldAccessMask); + } + throw new ArgumentException("MemberInfo is not a valid type", nameof(memberInfo)); + + string GetFieldVisibility(FieldAttributes visibility) { + switch (visibility) { + case FieldAttributes.Public: + return "public"; + case FieldAttributes.Family: + return "protected"; + case FieldAttributes.FamANDAssem: + return "protected internal"; + case FieldAttributes.Assembly: + return "internal"; + default: + return "private"; + } + } + + string GetMethodVisibility(MethodAttributes visibility) { + switch (visibility) { + case MethodAttributes.Public: + return "public"; + case MethodAttributes.Family: + return "protected"; + case MethodAttributes.FamANDAssem: + return "protected internal"; + case MethodAttributes.Assembly: + return "internal"; + default: + return "private"; + } + } + + string GetTypeVisibility(TypeAttributes visibility) { + switch (visibility) { + case TypeAttributes.Public: + case TypeAttributes.NestedPublic: + return "public"; + case TypeAttributes.NestedFamily: + return "protected"; + case TypeAttributes.NestedFamANDAssem: + return "protected internal"; + case TypeAttributes.NestedAssembly: + return "internal"; + case TypeAttributes.NestedPrivate: + return "private"; + default: + return ""; + } + } + } + + public static bool IsBackingField(this FieldInfo fieldInfo, out string propertyName) { + if (!fieldInfo.IsDefined(typeof(CompilerGeneratedAttribute))) { + propertyName = null; + return false; + } + + if (!fieldInfo.IsPrivate) { + propertyName = null; + return false; + } + + if (!fieldInfo.Name.StartsWith("<") && !fieldInfo.Name.EndsWith(">k__BackingField")) { + propertyName = null; + return false; + } + + propertyName = fieldInfo.Name.Substring(1, fieldInfo.Name.Length - 17); + return true; + } + + public static bool IsFixedSizeBuffer(this Type type, out Type elementType, out int size) { + size = default; + elementType = default; + + if (!type.IsValueType) { + return false; + } + + if (!type.Name.EndsWith("e__FixedBuffer")) { + return false; + } + + // this is a bit of a guesswork + if (type.IsDefined(typeof(CompilerGeneratedAttribute)) && + type.IsDefined(typeof(UnsafeValueTypeAttribute)) && + type.StructLayoutAttribute != null) { + // get the .size + size = type.StructLayoutAttribute.Size; + elementType = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)[0].FieldType; + return true; + } + + return false; + } + + public static Type GetDeclaringType(this Type type, Type stopAt) { + Debug.Assert(type != null); + + while (type.DeclaringType != null && type.DeclaringType != stopAt) { + type = type.DeclaringType; + } + + if (stopAt != type.DeclaringType) { + throw new InvalidOperationException($"Type {type} does not have a declaring type {stopAt}"); + } + + return type; + } + } +} + +#endregion + + +#region Assets/Photon/Fusion/Editor/Statistics/FusionStatisticsEditor.cs + +namespace Fusion.Statistics { + using UnityEngine; + using UnityEditor; + + [CustomEditor(typeof(FusionStatistics))] + public class FusionStatisticsEditor : Editor { + public override void OnInspectorGUI() { + FusionStatistics fusionStatistics = (FusionStatistics)target; + + EditorGUI.BeginChangeCheck(); + DrawDefaultInspector(); + + if (EditorGUI.EndChangeCheck()) { + fusionStatistics.OnEditorChange(); + } + + if (GUILayout.Button("Setup Statistics Panel")) + { + fusionStatistics.SetupStatisticsPanel(); + } + if (GUILayout.Button("Destroy Statistics Panel")) + { + fusionStatistics.DestroyStatisticsPanel(); + } + } + } +} + +#endregion + + +#region Assets/Photon/Fusion/Editor/Utilities/AnimatorControllerTools.cs + +// --------------------------------------------------------------------------------------------- +// PhotonNetwork Framework for Unity - Copyright (C) 2020 Exit Games GmbH +// developer@exitgames.com +// --------------------------------------------------------------------------------------------- + +namespace Fusion.Editor { + using System.Collections.Generic; + using UnityEngine; + + using UnityEditor.Animations; + using UnityEditor; + + internal static class AnimatorControllerTools { + //// Attach methods to Fusion.Runtime NetworkedAnimator + //[InitializeOnLoadMethod] + //public static void RegisterFusionDelegates() { + // NetworkedAnimator.GetWordCountDelegate = GetWordCount; + //} + + internal static AnimatorController GetController(Animator a) { + + RuntimeAnimatorController rac = a.runtimeAnimatorController; + AnimatorOverrideController overrideController = rac as AnimatorOverrideController; + + /// recurse until no override controller is found + while (overrideController != null) { + rac = overrideController.runtimeAnimatorController; + overrideController = rac as AnimatorOverrideController; + } + + return rac as AnimatorController; + } + + private static void GetTriggerNames(AnimatorController ctr, List namelist) { + namelist.Clear(); + + foreach (var p in ctr.parameters) + if (p.type == AnimatorControllerParameterType.Trigger) { + if (namelist.Contains(p.name)) { + Debug.LogWarning("Identical Trigger Name Found. Check animator on '" + ctr.name + "' for repeated trigger names."); + } else + namelist.Add(p.name); + } + } + + private static void GetTriggerNames(AnimatorController ctr, List hashlist) { + hashlist.Clear(); + + foreach (var p in ctr.parameters) + if (p.type == AnimatorControllerParameterType.Trigger) { + hashlist.Add(Animator.StringToHash(p.name)); + } + } + + /// ------------------------------ STATES -------------------------------------- + + private static void GetStatesNames(AnimatorController ctr, List namelist) { + namelist.Clear(); + + foreach (var l in ctr.layers) { + var states = l.stateMachine.states; + ExtractNames(ctr, l.name, states, namelist); + + var substates = l.stateMachine.stateMachines; + ExtractSubNames(ctr, l.name, substates, namelist); + } + } + + private static void ExtractSubNames(AnimatorController ctr, string path, ChildAnimatorStateMachine[] substates, List namelist) { + foreach (var s in substates) { + var sm = s.stateMachine; + var subpath = path + "." + sm.name; + + ExtractNames(ctr, subpath, s.stateMachine.states, namelist); + ExtractSubNames(ctr, subpath, s.stateMachine.stateMachines, namelist); + } + } + + private static void ExtractNames(AnimatorController ctr, string path, ChildAnimatorState[] states, List namelist) { + foreach (var st in states) { + string name = st.state.name; + string layerName = path + "." + st.state.name; + if (!namelist.Contains(name)) { + namelist.Add(name); + } + if (namelist.Contains(layerName)) { + Debug.LogWarning("Identical State Name '" + st.state.name + "' Found. Check animator on '" + ctr.name + "' for repeated State names as they cannot be used nor networked."); + } else + namelist.Add(layerName); + } + + } + + private static void GetStatesNames(AnimatorController ctr, List hashlist) { + hashlist.Clear(); + + foreach (var l in ctr.layers) { + var states = l.stateMachine.states; + ExtractHashes(ctr, l.name, states, hashlist); + + var substates = l.stateMachine.stateMachines; + ExtractSubtHashes(ctr, l.name, substates, hashlist); + } + + } + + private static void ExtractSubtHashes(AnimatorController ctr, string path, ChildAnimatorStateMachine[] substates, List hashlist) { + foreach (var s in substates) { + var sm = s.stateMachine; + var subpath = path + "." + sm.name; + + ExtractHashes(ctr, subpath, sm.states, hashlist); + ExtractSubtHashes(ctr, subpath, sm.stateMachines, hashlist); + } + } + + private static void ExtractHashes(AnimatorController ctr, string path, ChildAnimatorState[] states, List hashlist) { + foreach (var st in states) { + int hash = Animator.StringToHash(st.state.name); + string fullname = path + "." + st.state.name; + int layrhash = Animator.StringToHash(fullname); + if (!hashlist.Contains(hash)) { + hashlist.Add(hash); + } + if (hashlist.Contains(layrhash)) { + Debug.LogWarning("Identical State Name '" + st.state.name + "' Found. Check animator on '" + ctr.name + "' for repeated State names as they cannot be used nor networked."); + } else + hashlist.Add(layrhash); + } + } + + //public static void GetTransitionNames(this AnimatorController ctr, List transInfo) + //{ + // transInfo.Clear(); + + // transInfo.Add("0"); + + // foreach (var l in ctr.layers) + // { + // foreach (var st in l.stateMachine.states) + // { + // string sname = l.name + "." + st.state.name; + + // foreach (var t in st.state.transitions) + // { + // string dname = l.name + "." + t.destinationState.name; + // string name = (sname + " -> " + dname); + // transInfo.Add(name); + // //Debug.Log(sname + " -> " + dname + " " + Animator.StringToHash(sname + " -> " + dname)); + // } + // } + // } + + //} + + + //public static void GetTransitions(this AnimatorController ctr, List transInfo) + //{ + // transInfo.Clear(); + + // transInfo.Add(new TransitionInfo(0, 0, 0, 0, 0, 0, false)); + + // int index = 1; + + // foreach (var l in ctr.layers) + // { + // foreach (var st in l.stateMachine.states) + // { + // string sname = l.name + "." + st.state.name; + // int shash = Animator.StringToHash(sname); + + // foreach (var t in st.state.transitions) + // { + // string dname = l.name + "." + t.destinationState.name; + // int dhash = Animator.StringToHash(dname); + // int hash = Animator.StringToHash(sname + " -> " + dname); + // TransitionInfo ti = new TransitionInfo(index, hash, shash, dhash, t.duration, t.offset, t.hasFixedDuration); + // transInfo.Add(ti); + // //Debug.Log(index + " " + sname + " -> " + dname + " " + Animator.StringToHash(sname + " -> " + dname)); + // index++; + // } + // } + // } + //} + + const double AUTO_REBUILD_RATE = 10f; + private static List tempNamesList = new List(); + private static List tempHashList = new List(); + + /// + /// Re-index all of the State and Trigger names in the current AnimatorController. Never hurts to run this (other than hanging the editor for a split second). + /// + internal static void GetHashesAndNames(NetworkMecanimAnimator netAnim, + List sharedTriggNames, + List sharedStateNames, + ref int[] sharedTriggIndexes, + ref int[] sharedStateIndexes + //ref double lastRebuildTime + ) { + + // always get new Animator in case it has changed. + Animator animator = netAnim.Animator; + if (animator == null) + animator = netAnim.GetComponent(); + + if (animator == null) { + return; + } + //if (animator && EditorApplication.timeSinceStartup - lastRebuildTime > AUTO_REBUILD_RATE) { + // lastRebuildTime = EditorApplication.timeSinceStartup; + + AnimatorController ac = GetController(animator); + if (ac != null) { + if (ac.animationClips == null || ac.animationClips.Length == 0) + Debug.LogWarning("'" + animator.name + "' has an Animator with no animation clips. Some Animator Controllers require a restart of Unity, or for a Build to be made in order to initialize correctly."); + + bool haschanged = false; + + GetTriggerNames(ac, tempHashList); + tempHashList.Insert(0, 0); + if (!CompareIntArray(sharedTriggIndexes, tempHashList)) { + sharedTriggIndexes = tempHashList.ToArray(); + haschanged = true; + } + + GetStatesNames(ac, tempHashList); + tempHashList.Insert(0, 0); + if (!CompareIntArray(sharedStateIndexes, tempHashList)) { + sharedStateIndexes = tempHashList.ToArray(); + haschanged = true; + } + + if (sharedTriggNames != null) { + GetTriggerNames(ac, tempNamesList); + tempNamesList.Insert(0, null); + if (!CompareNameLists(tempNamesList, sharedTriggNames)) { + CopyNameList(tempNamesList, sharedTriggNames); + haschanged = true; + } + } + + if (sharedStateNames != null) { + GetStatesNames(ac, tempNamesList); + tempNamesList.Insert(0, null); + if (!CompareNameLists(tempNamesList, sharedStateNames)) { + CopyNameList(tempNamesList, sharedStateNames); + haschanged = true; + } + } + + if (haschanged) { + Debug.Log(animator.name + " has changed. SyncAnimator indexes updated."); + EditorUtility.SetDirty(netAnim); + } + } + //} + } + + /// + /// Returns the 's word count, using the animator's animator controller. + /// + internal static int GetWordCount(NetworkMecanimAnimator nma) { + if (nma.Animator == null) { + return 0; + } + + AnimatorController ac = GetController(nma.Animator); + return NetworkMecanimAnimator.AnimatorData.GetWordCount(nma.SyncSettings, ac.parameters, new int[ac.parameters.Length], ac.layers.Length, out _, out _, out _, out _); + } + + private static bool CompareNameLists(List one, List two) { + if (one.Count != two.Count) + return false; + + for (int i = 0; i < one.Count; i++) + if (one[i] != two[i]) + return false; + + return true; + } + + private static bool CompareIntArray(int[] old, List temp) { + if (ReferenceEquals(old, null)) + return false; + + if (old.Length != temp.Count) + return false; + + for (int i = 0; i < old.Length; i++) + if (old[i] != temp[i]) + return false; + + return true; + } + + private static void CopyNameList(List src, List trg) { + trg.Clear(); + for (int i = 0; i < src.Count; i++) + trg.Add(src[i]); + } + + } + +} + + + +#endregion + + +#region Assets/Photon/Fusion/Editor/Utilities/AssetDatabaseUtils.cs + +namespace Fusion.Editor { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + using UnityEditor; +#if UNITY_2021_2_OR_NEWER + using UnityEditor.SceneManagement; +#else + using UnityEditor.Experimental.SceneManagement; +#endif + + using UnityEngine; + + public static partial class AssetDatabaseUtils { + public static T GetSubAsset(GameObject prefab) where T : ScriptableObject { + + if (!AssetDatabase.IsMainAsset(prefab)) { + throw new InvalidOperationException($"Not a main asset: {prefab}"); + } + + string path = AssetDatabase.GetAssetPath(prefab); + if (string.IsNullOrEmpty(path)) { + throw new InvalidOperationException($"Empty path for prefab: {prefab}"); + } + + var subAssets = AssetDatabase.LoadAllAssetsAtPath(path).OfType().ToList(); + if (subAssets.Count > 1) { + Debug.LogError($"More than 1 asset of type {typeof(T)} on {path}, clean it up manually"); + } + + return subAssets.Count == 0 ? null : subAssets[0]; + } + + public static bool IsSceneObject(GameObject go) { + return ReferenceEquals(PrefabStageUtility.GetPrefabStage(go), null) && (PrefabUtility.IsPartOfPrefabAsset(go) == false || PrefabUtility.GetPrefabAssetType(go) == PrefabAssetType.NotAPrefab); + } + } +} + + +#endregion + + +#region Assets/Photon/Fusion/Editor/Utilities/FusionEditorGUI.cs + +namespace Fusion.Editor { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + using UnityEditor; + using UnityEngine; + + public static partial class FusionEditorGUI { + + public static void LayoutSelectableLabel(GUIContent label, string contents) { + var rect = EditorGUILayout.GetControlRect(); + rect = EditorGUI.PrefixLabel(rect, label); + using (new EditorGUI.IndentLevelScope(-EditorGUI.indentLevel)) { + EditorGUI.SelectableLabel(rect, contents); + } + } + + public static bool DrawDefaultInspector(SerializedObject obj, bool drawScript = true) { + EditorGUI.BeginChangeCheck(); + obj.UpdateIfRequiredOrScript(); + + // Loop through properties and create one field (including children) for each top level property. + SerializedProperty property = obj.GetIterator(); + bool expanded = true; + while (property.NextVisible(expanded)) { + if ( ScriptPropertyName == property.propertyPath ) { + if (drawScript) { + using (new EditorGUI.DisabledScope("m_Script" == property.propertyPath)) { + EditorGUILayout.PropertyField(property, true); + } + } + } else { + EditorGUILayout.PropertyField(property, true); + } + expanded = false; + } + + obj.ApplyModifiedProperties(); + return EditorGUI.EndChangeCheck(); + } + } +} + + +#endregion + + +#region Assets/Photon/Fusion/Editor/Utilities/FusionEditorGUI.Thumbnail.cs + +namespace Fusion.Editor { + using System; + using System.Text; + using UnityEngine; + + public static partial class FusionEditorGUI { + + static readonly int _thumbnailFieldHash = "Thumbnail".GetHashCode(); + static Texture2D _thumbnailBackground; + static GUIStyle _thumbnailStyle; + + public static void DrawTypeThumbnail(Rect position, Type type, string prefixToSkip, string tooltip = null) { + EnsureThumbnailStyles(); + + var acronym = GenerateAcronym(type, prefixToSkip); + var content = new GUIContent(acronym, tooltip ?? type.FullName); + int controlID = GUIUtility.GetControlID(_thumbnailFieldHash, FocusType.Passive, position); + + if (Event.current.type == EventType.Repaint) { + var originalColor = GUI.backgroundColor; + try { + GUI.backgroundColor = GetPersistentColor(type.FullName); + _thumbnailStyle.fixedWidth = position.width; + _thumbnailStyle.Draw(position, content, controlID); + } finally { + GUI.backgroundColor = originalColor; + } + } + } + + static Color GetPersistentColor(string str) { + return GeneratePastelColor(HashCodeUtilities.GetHashDeterministic(str)); + } + + static Color GeneratePastelColor(int seed) { + var rng = new System.Random(seed); + int r = rng.Next(256) + 128; + int g = rng.Next(256) + 128; + int b = rng.Next(256) + 128; + + r = Mathf.Min(r / 2, 255); + g = Mathf.Min(g / 2, 255); + b = Mathf.Min(b / 2, 255); + + var result = new Color32((byte)r, (byte)g, (byte)b, 255); + return result; + } + + static string GenerateAcronym(Type type, string prefixToStrip) { + StringBuilder acronymBuilder = new StringBuilder(); + + var str = type.Name; + if (!string.IsNullOrEmpty(prefixToStrip)) { + if (str.StartsWith(prefixToStrip)) { + str = str.Substring(prefixToStrip.Length); + } + } + + for (int i = 0; i < str.Length; ++i) { + var c = str[i]; + if (i != 0 && char.IsLower(c)) { + continue; + } + acronymBuilder.Append(c); + } + + return acronymBuilder.ToString(); + } + + static void EnsureThumbnailStyles() { + if (_thumbnailBackground != null) { + return; + } + + byte[] data = { + 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, + 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x14, + 0x08, 0x06, 0x00, 0x00, 0x00, 0x8d, 0x89, 0x1d, 0x0d, 0x00, 0x00, 0x00, + 0x01, 0x73, 0x52, 0x47, 0x42, 0x00, 0xae, 0xce, 0x1c, 0xe9, 0x00, 0x00, + 0x00, 0x04, 0x67, 0x41, 0x4d, 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, + 0x61, 0x05, 0x00, 0x00, 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, + 0x0e, 0xc3, 0x00, 0x00, 0x0e, 0xc3, 0x01, 0xc7, 0x6f, 0xa8, 0x64, 0x00, + 0x00, 0x00, 0xf2, 0x49, 0x44, 0x41, 0x54, 0x38, 0x4f, 0xed, 0x95, 0x31, + 0x0a, 0x83, 0x30, 0x14, 0x86, 0x63, 0x11, 0x74, 0x50, 0x74, 0x71, 0xf1, + 0x34, 0x01, 0x57, 0x6f, 0xe8, 0xe0, 0xd0, 0xa5, 0x07, 0x10, 0x0a, 0xbd, + 0x40, 0x0f, 0xe2, 0xa8, 0x9b, 0xee, 0xf6, 0x7d, 0x69, 0x4a, 0xa5, 0xd2, + 0x2a, 0xa6, 0x4b, 0xa1, 0x1f, 0x04, 0x5e, 0xc2, 0xff, 0xbe, 0x68, 0x90, + 0xa8, 0x5e, 0xd0, 0x69, 0x9a, 0x9e, 0xc3, 0x30, 0x1c, 0xa4, 0x9e, 0x3e, + 0x0d, 0x32, 0x64, 0xa5, 0xd6, 0x32, 0x16, 0xf8, 0x51, 0x14, 0x1d, 0xb3, + 0x2c, 0x1b, 0xab, 0xaa, 0x9a, 0xda, 0xb6, 0x9d, 0xd6, 0x20, 0x43, 0x96, + 0x1e, 0x7a, 0x71, 0xdc, 0x55, 0x02, 0x0b, 0x45, 0x51, 0x0c, 0x82, 0x8d, + 0x6f, 0x87, 0x1e, 0x7a, 0xad, 0xd4, 0xa0, 0xd9, 0x65, 0x8f, 0xec, 0x01, + 0xbd, 0x38, 0x70, 0x29, 0xce, 0x81, 0x47, 0x77, 0x05, 0x87, 0x39, 0x53, + 0x0e, 0x77, 0xcb, 0x99, 0xad, 0x81, 0x03, 0x97, 0x27, 0x8f, 0xc9, 0xdc, + 0xbc, 0xbb, 0x2b, 0x9e, 0xe7, 0xa9, 0x83, 0xad, 0xbf, 0xc6, 0x5f, 0xe8, + 0xce, 0x0f, 0x08, 0xe5, 0x63, 0x1c, 0xfb, 0xbe, 0xb7, 0xd3, 0xfd, 0xe0, + 0xc0, 0x75, 0x08, 0x82, 0xe0, 0xda, 0x34, 0x8d, 0x5d, 0xde, 0x0f, 0x0e, + 0x5c, 0xd4, 0x3a, 0xcf, 0x73, 0xe7, 0xcb, 0x01, 0x07, 0x2e, 0x84, 0x2a, + 0x8e, 0xe3, 0x53, 0x59, 0x96, 0xbb, 0xa4, 0xf4, 0xd0, 0x8b, 0xc3, 0xc8, + 0x2c, 0x3e, 0x0b, 0xec, 0x52, 0xd7, 0xf5, 0xd4, 0x75, 0x9d, 0x8d, 0xbf, + 0x87, 0x0c, 0x59, 0x7a, 0xac, 0xec, 0x79, 0xc1, 0xce, 0xd0, 0x49, 0x92, + 0x5c, 0xb8, 0x35, 0xa4, 0x5e, 0x5c, 0xfb, 0xf3, 0x41, 0x86, 0xac, 0xd4, + 0xb3, 0x5f, 0x80, 0x52, 0x37, 0xfd, 0x56, 0x1b, 0x09, 0x40, 0x56, 0xe4, + 0x85, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, + 0x82 + }; + + var texture = new Texture2D(2, 2, TextureFormat.ARGB32, false); + if (!texture.LoadImage(data)) { + throw new InvalidOperationException(); + } + + _thumbnailBackground = texture; + + _thumbnailStyle = new GUIStyle() { + normal = new GUIStyleState { background = _thumbnailBackground, textColor = Color.white }, + border = new RectOffset(6, 6, 6, 6), + padding = new RectOffset(2, 1, 1, 1), + imagePosition = ImagePosition.TextOnly, + alignment = TextAnchor.MiddleCenter, + clipping = TextClipping.Clip, + wordWrap = true, + stretchWidth = false, + fontSize = 8, + fontStyle = FontStyle.Bold, + fixedWidth = texture.width, + }; + + } + + } +} + + +#endregion + + +#region Assets/Photon/Fusion/Editor/Utilities/NetworkProjectConfigUtilities.cs + +namespace Fusion.Editor { + + using UnityEditor; + using UnityEngine; + using UnityEngine.SceneManagement; + using System.Collections.Generic; + using Fusion.Photon.Realtime; + using System.Linq; + using System.IO; + using System; + + /// + /// Editor utilities for creating and managing the singleton. + /// + [InitializeOnLoad] + public static class NetworkProjectConfigUtilities { + + // Constructor runs on project load, allows for startup check for existence of NPC asset. + static NetworkProjectConfigUtilities() { + EditorApplication.playModeStateChanged += (change) => { + if (change == PlayModeStateChange.EnteredEditMode) { + NetworkProjectConfig.UnloadGlobal(); + } + }; + } + + [MenuItem("Tools/Fusion/Network Project Config", priority = 200)] + [MenuItem("Assets/Create/Fusion/Network Project Config", priority = 0)] + static void PingNetworkProjectConfigAsset() { + FusionGlobalScriptableObjectUtils.EnsureAssetExists(); + NetworkProjectConfigUtilities.PingGlobalConfigAsset(true); + } + + [MenuItem("Tools/Fusion/Rebuild Prefab Table", priority = 100)] + public static void RebuildPrefabTable() { + foreach (var prefab in AssetDatabase.FindAssets($"t:prefab") + .Select(AssetDatabase.GUIDToAssetPath) + .Select(x => (GameObject)AssetDatabase.LoadMainAssetAtPath(x))) { + if (prefab.TryGetComponent(out var networkObject) && !networkObject.Flags.IsIgnored()) { + AssetDatabaseUtils.SetLabel(prefab, NetworkProjectConfigImporter.FusionPrefabTag, true); + } else { + AssetDatabaseUtils.SetLabel(prefab, NetworkProjectConfigImporter.FusionPrefabTag, false); + } + } + + AssetDatabase.Refresh(); + ImportGlobalConfig(); + + Debug.Log("Rebuild Prefab Table done."); + } + + public static void PingGlobalConfigAsset(bool select = false) { + if (NetworkProjectConfigAsset.TryGetGlobal(out var config)) { + EditorGUIUtility.PingObject(config); + if (select) { + Selection.activeObject = config; + } + } + } + + public static bool TryGetGlobalPrefabSource(NetworkObjectGuid guid, out T source) where T : class, INetworkPrefabSource { + if (NetworkProjectConfigAsset.TryGetGlobal(out var global)) { + if (global.Config.PrefabTable.GetSource(guid) is T sourceT) { + source = sourceT; + return true; + } + } + source = null; + return false; + } + + public static bool TryGetPrefabId(NetworkObjectGuid guid, out NetworkPrefabId id) { + id = NetworkProjectConfig.Global.PrefabTable.GetId(guid); + return id.IsValid; + } + + public static bool TryGetPrefabId(string prefabPath, out NetworkPrefabId id) { + var guidStr = AssetDatabase.AssetPathToGUID(prefabPath); + if (NetworkObjectGuid.TryParse(guidStr, out var guid)) { + return TryGetPrefabId(guid, out id); + } + + id = default; + return false; + } + + // public static bool TryResolvePrefab(NetworkObjectGuid guid, out NetworkObject prefab) { + // if (TryGetPrefabSource(guid, out NetworkPrefabSourceBase source)) { + // try { + // prefab = NetworkPrefabSourceFactory.ResolveOrThrow(source); + // return true; + // } catch (Exception ex) { + // FusionEditorLog.Trace(ex.ToString()); + // } + // } + // + // prefab = null; + // return false; + // } + + internal static bool TryGetPrefabEditorInstance(NetworkObjectGuid guid, out NetworkObject prefab) { + if (!guid.IsValid) { + prefab = null; + return false; + } + + var path = AssetDatabase.GUIDToAssetPath(guid.ToUnityGuidString()); + if (string.IsNullOrEmpty(path)) { + prefab = null; + return false; + } + + var gameObject = AssetDatabase.LoadAssetAtPath(path); + if (!gameObject) { + prefab = null; + return false; + } + + prefab = gameObject.GetComponent(); + return prefab; + } + + internal static string GetGlobalConfigPath() { + return FusionGlobalScriptableObjectUtils.GetGlobalAssetPath(); + } + + public static bool ImportGlobalConfig() { + return FusionGlobalScriptableObjectUtils.TryImportGlobal(); + } + + public static string SaveGlobalConfig() { + if (NetworkProjectConfigAsset.TryGetGlobal(out var global)) { + return SaveGlobalConfig(global.Config); + } else { + return SaveGlobalConfig(new NetworkProjectConfig()); + } + } + + public static string SaveGlobalConfig(NetworkProjectConfig config) { + FusionGlobalScriptableObjectUtils.EnsureAssetExists(); + string path = GetGlobalConfigPath(); + + var json = EditorJsonUtility.ToJson(config, true); + string existingJson = File.ReadAllText(path); + + if (!string.Equals(json, existingJson)) { + AssetDatabase.MakeEditable(path); + File.WriteAllText(path, json); + } + + AssetDatabase.ImportAsset(path); + return PathUtils.Normalize(path); + } + + private static string[] GetEnabledBuildScenes() { + var scenes = new List(); + + for (int i = 0; i < EditorBuildSettings.scenes.Length; ++i) { + var scene = EditorBuildSettings.scenes[i]; + if (scene.enabled && string.IsNullOrEmpty(scene.path) == false) { + scenes.Add(scene.path); + } + } + + return scenes.ToArray(); + } + } +} + + +#endregion + + +#region Assets/Photon/Fusion/Editor/Utilities/NetworkRunnerUtilities.cs + +namespace Fusion.Editor { + + using System.Collections.Generic; + using UnityEngine; + using UnityEditor; + + public static class NetworkRunnerUtilities { + + static List reusableRunnerList = new List(); + + public static NetworkRunner[] FindActiveRunners() { + var runners = Object.FindObjectsByType(FindObjectsInactive.Exclude, FindObjectsSortMode.InstanceID); + reusableRunnerList.Clear(); + for (int i = 0; i < runners.Length; ++i) { + if (runners[i].IsRunning) + reusableRunnerList.Add(runners[i]); + } + if (reusableRunnerList.Count == runners.Length) + return runners; + + return reusableRunnerList.ToArray(); + } + + public static void FindActiveRunners(List nonalloc) { + var runners = Object.FindObjectsByType(FindObjectsInactive.Exclude, FindObjectsSortMode.InstanceID); + nonalloc.Clear(); + for (int i = 0; i < runners.Length; ++i) { + if (runners[i].IsRunning) + nonalloc.Add(runners[i]); + } + } + + } +} + + + +#endregion + +#endif diff --git a/Assets/Photon/Fusion/Editor/Fusion.Unity.Editor.cs.meta b/Assets/Photon/Fusion/Editor/Fusion.Unity.Editor.cs.meta new file mode 100644 index 00000000..b3aa35ed --- /dev/null +++ b/Assets/Photon/Fusion/Editor/Fusion.Unity.Editor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 91a915d06029929418c86dcc2003f785 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Editor/FusionEditorConfigImporter.cs b/Assets/Photon/Fusion/Editor/FusionEditorConfigImporter.cs new file mode 100644 index 00000000..885d258e --- /dev/null +++ b/Assets/Photon/Fusion/Editor/FusionEditorConfigImporter.cs @@ -0,0 +1,22 @@ +namespace Fusion.Editor { + using System.IO; + using UnityEditor.AssetImporters; + using UnityEngine; + + [ScriptedImporter(0, "editorconfig")] + class FusionEditorConfigImporter : ScriptedImporter { + public override void OnImportAsset(AssetImportContext ctx) { + var path = ctx.assetPath; + var contents = File.ReadAllText(path); + + // create internal text asset for convenience + var mainAsset = new TextAsset(contents); + ctx.AddObjectToAsset("main", mainAsset); + ctx.SetMainObject(mainAsset); + + // write the actual editorconfig for editors to consume + var editorConfigPath = Path.Combine(Path.GetDirectoryName(path), ".editorconfig"); + File.WriteAllText(editorConfigPath, contents); + } + } +} diff --git a/Assets/Photon/Fusion/Editor/FusionEditorConfigImporter.cs.meta b/Assets/Photon/Fusion/Editor/FusionEditorConfigImporter.cs.meta new file mode 100644 index 00000000..8ecbd3f1 --- /dev/null +++ b/Assets/Photon/Fusion/Editor/FusionEditorConfigImporter.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: de703c9ba568c804997917200422ce7e +timeCreated: 1681382704 \ No newline at end of file diff --git a/Assets/Photon/Fusion/Editor/FusionEditorSkin.cs b/Assets/Photon/Fusion/Editor/FusionEditorSkin.cs new file mode 100644 index 00000000..8a660c16 --- /dev/null +++ b/Assets/Photon/Fusion/Editor/FusionEditorSkin.cs @@ -0,0 +1,108 @@ +namespace Fusion.Editor { + using System; + using System.Collections.Generic; + using UnityEditor; + using UnityEngine; + + /// + /// An editor scriptable object that stores UI skins and different Unity inspectors and custom windows. + /// +#if FUSION_DEV + [CreateAssetMenu(menuName = "Fusion/Editor Skin")] +#endif + partial class FusionEditorSkin : ScriptableObject { + + private static FusionEditorSkin s_Instance; + + public static FusionEditorSkin instance { + get { + if (s_Instance == null) { + s_Instance = CreateInstance(); + s_Instance.hideFlags = HideFlags.HideAndDontSave; + } + + return s_Instance; + } + } + + public GUISkin Skin; + + public static GUIStyle HelpButtonStyle => instance.Skin.GetStyle(EditorGUIUtility.isProSkin ? "dark-help-button" : "light-help-button"); + public static GUIStyle InlineBoxFullWidthStyle => instance.Skin.GetStyle("inline-box-full-width"); + public static GUIStyle InlineBoxFullWidthScopeStyle => instance.Skin.GetStyle("inline-box-full-width-scope"); + public static GUIStyle ScriptHeaderBackgroundStyle => instance.Skin.GetStyle("script-header-bg"); + public static GUIStyle ScriptHeaderIconStyle => instance.Skin.GetStyle("script-header-icon"); + public static GUIStyle ScriptHeaderLabelStyle => instance.Skin.GetStyle("script-header-label"); + public static GUIStyle RichLabelStyle => instance.Skin.GetStyle(EditorGUIUtility.isProSkin ? "dark-rich-label" : "light-rich-label"); + public static GUIStyle InlineSelectorStyle => instance.Skin.GetStyle("inline-selector"); + public static GUIStyle OutlineBoxStyle => instance.Skin.GetStyle("outline-box"); + + + public static Color HelpInlineBoxColor => EditorGUIUtility.isProSkin ? new Color(0.317f, 0.337f, 0.352f, 1.000f) : new Color(0.686f, 0.776f, 0.859f); + public static Color WarningInlineBoxColor => EditorGUIUtility.isProSkin ? new Color(0.36f, 0.33f, 0.22f, 1.00f) : new Color(0.98f, 0.94f, 0.80f, 0.90f); + public static Color ErrorInlineBoxColor => EditorGUIUtility.isProSkin ? new Color(0.40f, 0.15f, 0.10f, 1.00f) : new Color(0.9f, 0.70f, 0.70f, 1.00f); + + public static readonly LazyAsset InfoIcon = LazyAsset.Create(() => FindTextureOrThrow(EditorGUIUtility.isProSkin ? "d_console.infoicon.sml@2x" : "console.infoicon.sml@2x")); + public static readonly LazyAsset WarningIcon = LazyAsset.Create(() => FindTextureOrThrow(EditorGUIUtility.isProSkin ? "d_console.warnicon.sml@2x" : "console.warnicon.sml@2x")); + public static readonly LazyAsset ErrorIcon = LazyAsset.Create(() => FindTextureOrThrow(EditorGUIUtility.isProSkin ? "d_console.erroricon.sml@2x" : "console.erroricon.sml@2x")); + public static readonly LazyAsset LoadStateIcon = LazyAsset.Create(() => FindTextureOrThrow("blendSampler")); + public static readonly LazyAsset RefreshIcon = LazyAsset.Create(() => FindTextureOrThrow(EditorGUIUtility.isProSkin ? "d_Refresh@2x" : "Refresh@2x")); + + public static readonly LazyGUIStyle OverlayLabelStyle = LazyGUIStyle.Create(_ => { + var result = new GUIStyle(EditorStyles.miniLabel); + result.alignment = TextAnchor.MiddleRight; + result.contentOffset = new Vector2(-2, 0); + result.normal.textColor = EditorGUIUtility.isProSkin ? new Color(255f / 255f, 221 / 255f, 0 / 255f, 1f) : Color.blue; + return result; + }); + + public static readonly LazyGUIStyle RawDataStyle = LazyGUIStyle.Create(_ => new GUIStyle(EditorStyles.textArea) { wordWrap = true }); + public static readonly LazyGUIStyle DropDownListStyle = LazyGUIStyle.Create(_ => FindBuiltInStyleOrThrow("DropDownButton")); + + private static Texture2D FindTextureOrThrow(string id) { + var texture = EditorGUIUtility.FindTexture(id); + if (texture) { + return texture; + } + + var icon = EditorGUIUtility.IconContent(id); + if (icon?.image) { + return (Texture2D)icon.image; + } + + throw new ArgumentOutOfRangeException($"Could not find texture with id {id}"); + } + + private static GUIStyle FindBuiltInStyleOrThrow(string styleName) { + var style = GUI.skin.FindStyle(styleName) ?? EditorGUIUtility.GetBuiltinSkin(EditorSkin.Inspector).FindStyle(styleName); + + if (style == null) { + throw new ArgumentOutOfRangeException("Missing built-in guistyle " + styleName); + } + + return style; + } + + private Dictionary _scriptHeaderStyles = new() { + { ScriptHeaderBackColor.None, new Color() }, + { ScriptHeaderBackColor.Gray, new Color(0.35f, 0.35f, 0.35f) }, + { ScriptHeaderBackColor.Blue, new Color(0.15f, 0.25f, 0.6f) }, + { ScriptHeaderBackColor.Red, new Color(0.5f, 0.1f, 0.1f) }, + { ScriptHeaderBackColor.Green, new Color(0.1f, 0.4f, 0.1f) }, + { ScriptHeaderBackColor.Orange, new Color(0.6f, 0.35f, 0.1f) }, + { ScriptHeaderBackColor.Black, new Color(0.1f, 0.1f, 0.1f) }, + { ScriptHeaderBackColor.Steel, new Color(0.32f, 0.35f, 0.38f) }, + { ScriptHeaderBackColor.Sand, new Color(0.38f, 0.35f, 0.32f) }, + { ScriptHeaderBackColor.Olive, new Color(0.25f, 0.33f, 0.15f) }, + { ScriptHeaderBackColor.Cyan, new Color(0.25f, 0.5f, 0.5f) }, + { ScriptHeaderBackColor.Violet, new Color(0.35f, 0.2f, 0.4f) }, + }; + + public static Color GetScriptHeaderColor(ScriptHeaderBackColor settingsBackColor) { + if (instance._scriptHeaderStyles.TryGetValue(settingsBackColor, out var color)) { + return color; + } + return default; + } + } +} diff --git a/Assets/Photon/Fusion/Editor/FusionEditorSkin.cs.meta b/Assets/Photon/Fusion/Editor/FusionEditorSkin.cs.meta new file mode 100644 index 00000000..742b2810 --- /dev/null +++ b/Assets/Photon/Fusion/Editor/FusionEditorSkin.cs.meta @@ -0,0 +1,13 @@ +fileFormatVersion: 2 +guid: 2119ee8fcf3d90445867b93c17e27272 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: + - ScriptHeaderFont: {fileID: 12800000, guid: d9d6a85f7c05f99439d73c334199cb58, type: 3} + - Skin: {fileID: 11400000, guid: c809d493e97d33546a90fc00dcd9ba38, type: 2} + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Editor/FusionHubWindow.cs b/Assets/Photon/Fusion/Editor/FusionHubWindow.cs new file mode 100644 index 00000000..d3ddf450 --- /dev/null +++ b/Assets/Photon/Fusion/Editor/FusionHubWindow.cs @@ -0,0 +1,264 @@ +namespace Fusion.Editor { +#if FUSION_WEAVER && UNITY_EDITOR + using System; + using System.Collections.Generic; + using Photon.Realtime; + using UnityEditor; + using UnityEngine; + using EditorUtility = UnityEditor.EditorUtility; + + public partial class FusionHubWindow : EditorWindow { + private const int NavWidth = 256 + 2; + + private static bool? _ready; // true after InitContent(), reset onDestroy, onEnable, etc. + + private static Vector2 _windowSize; + private static Vector2 _windowPosition = new Vector2(100, 100); + + private int _currentSection; + + [MenuItem("Tools/Fusion/Fusion Hub &f", false, 0)] + public static void Open() { + if (Application.isPlaying) { + return; + } + + var window = GetWindow(true, Constants.WindowTitle, true); + window.position = new Rect(_windowPosition, _windowSize); + window.Show(); + } + + private static void ReOpen() { + if (_ready.HasValue && _ready.Value == false) { + Open(); + } + + EditorApplication.update -= ReOpen; + } + + private void OnEnable() { + _ready = false; + _windowSize = new Vector2(800, 540); + + this.minSize = _windowSize; + + // Pre-load Release History + this.PrepareReleaseHistoryText(); + wantsMouseMove = true; + } + + private void OnDestroy() { + _ready = false; + } + + private void OnGUI() { + // skip until ready + if (InitContent() == false) { return; } + + GUI.skin = FusionHubSkin; + + _windowPosition = this.position.position; + + // full window wrapper + using (new EditorGUILayout.HorizontalScope(GUI.skin.window)) { + // Left Nav menu + using (new EditorGUILayout.VerticalScope(GUILayout.MaxWidth(NavWidth), GUILayout.MinWidth(NavWidth))) { + DrawHeader(); + DrawLeftNavMenu(); + } + + // Right Main Content + using (new EditorGUILayout.VerticalScope()) { + DrawContent(); + } + } + + DrawFooter(); + + // Force repaints while mouse is over the window, to keep Hover graphics working (Unity quirk) + var timeSinceStartup = Time.realtimeSinceStartupAsDouble; + if (Event.current.type == EventType.MouseMove && timeSinceStartup > _nextForceRepaint) { + // Cap the repaint rate a bit since we are forcing repaint on mouse move + _nextForceRepaint = timeSinceStartup + .05f; + Repaint(); + } + } + + private double _nextForceRepaint; + private Vector2 _scrollRect; + + private void DrawContent() { + var section = _sections[_currentSection]; + GUILayout.Label(section.Description, headerTextStyle); + + using (new EditorGUILayout.VerticalScope(FusionHubSkin.box)) { + using (var scrollScope = new EditorGUILayout.ScrollViewScope(_scrollRect)) { + _scrollRect = scrollScope.scrollPosition; + section.DrawMethod.Invoke(); + } + } + } + + private void DrawWelcomeSection() { + // Top Welcome content box + GUILayout.Label(Constants.WelcomeText); + GUILayout.Space(16); + + DrawSetupAppIdBox(); + } + + private void DrawSetupSection() { + DrawSetupAppIdBox(); + DrawButtonAction(Icon.FusionIcon, "Fusion Network Project Settings", "Network settings specific to Fusion.", + callback: () => NetworkProjectConfigUtilities.PingGlobalConfigAsset(true)); + DrawButtonAction(Icon.PhotonCloud, "Photon App Settings", "Network settings specific to the Photon transport.", + callback: () => { + EditorGUIUtility.PingObject(Photon.Realtime.PhotonAppSettings.Global); + Selection.activeObject = Photon.Realtime.PhotonAppSettings.Global; + }); + } + + private void DrawDocumentationSection() { + DrawButtonAction(Icon.Documentation, "Fusion Introduction", "The Fusion Introduction web page.", callback: OpenURL(Constants.UrlFusionIntro)); + DrawButtonAction(Icon.Documentation, "SDK and Release Notes", "Link to the latest Fusion version SDK.", callback: OpenURL(Constants.UrlFusionSDK)); + DrawButtonAction(Icon.Documentation, "API Reference", "The API library reference documentation.", callback: OpenURL(Constants.UrlFusionDocApi)); + } + + private void DrawSamplesSection() { + GUILayout.Label("Tutorials", headerLabelStyle); + DrawButtonAction(Icon.Samples, "Fusion 100 Tutorial", "Fusion Fundamentals Tutorial", callback: OpenURL(Constants.UrlFusion100)); + } + + private void DrawFusionReleaseSection() { + GUILayout.Label(fusionReleaseHistory, releaseNotesStyle); + } + + private void DrawReleaseHistoryItem(string label, List items) { + if (items != null && items.Count > 0) { + GUILayout.BeginVertical(); + { + GUILayout.Space(5); + + foreach (string text in items) { + GUILayout.Label(string.Format("- {0}.", text), textLabelStyle); + } + } + GUILayout.EndVertical(); + } + } + + private void DrawSupportSection() { + GUILayout.BeginVertical(); + GUILayout.Space(5); + GUILayout.Label(Constants.Support, textLabelStyle); + GUILayout.EndVertical(); + + GUILayout.Space(15); + + DrawButtonAction(Icon.Community, Constants.DiscordHeader, Constants.DiscordText, callback: OpenURL(Constants.UrlDashboardProfile)); + DrawButtonAction(Icon.Documentation, Constants.DocumentationHeader, Constants.DocumentationText, callback: OpenURL(Constants.UrlFusionDocsOnline)); + } + + private void DrawSetupAppIdBox() { + var realtimeSettings = Photon.Realtime.PhotonAppSettings.Global; + var realtimeAppId = realtimeSettings.AppSettings.AppIdFusion; + // Setting up AppId content box. + using (new EditorGUILayout.VerticalScope(FusionHubSkin.GetStyle("SteelBox") /*contentBoxStyle*/)) { + GUILayout.Label(Constants.RealtimeAppidSetupInstructions); + + DrawButtonAction(Icon.PhotonCloud, "Open the Photon Dashboard", callback: OpenURL(Constants.UrlDashboard)); + EditorGUILayout.Space(4); + + using (new EditorGUILayout.HorizontalScope(FusionHubSkin.GetStyle("SteelBox"))) { + EditorGUI.BeginChangeCheck(); + GUILayout.Label("Fusion App Id:", GUILayout.Width(120)); + var icon = IsAppIdValid() ? CorrectIcon : EditorGUIUtility.FindTexture("console.erroricon.sml"); + GUILayout.Label(icon, GUILayout.Width(24), GUILayout.Height(24)); + var editedAppId = EditorGUILayout.TextField("", realtimeAppId, FusionHubSkin.textField, GUILayout.Height(24)); + + // Check for changes and validate the AppId + if (EditorGUI.EndChangeCheck() && Guid.TryParse(editedAppId, out _)) { + // Update the AppId + realtimeSettings.AppSettings.AppIdFusion = editedAppId; + EditorUtility.SetDirty(realtimeSettings); + AssetDatabase.SaveAssets(); + } + } + } + } + + private void DrawLeftNavMenu() { + for (var i = 0; i < _sections.Length; ++i) { + var section = _sections[i]; + if (DrawNavButton(section, _currentSection == i)) { + _currentSection = i; + } + } + } + + private void DrawHeader() { + GUILayout.Label(GetIcon(Icon.ProductLogo), _navbarHeaderGraphicStyle); + } + + private void DrawFooter() { + GUILayout.BeginHorizontal(FusionHubSkin.window); + { + GUILayout.Label("\u00A9 2025, Exit Games GmbH. All rights reserved."); + } + GUILayout.EndHorizontal(); + } + + private bool DrawNavButton(Section section, bool currentSection) { + var content = new GUIContent() { text = " " + section.Title, image = GetIcon(section.Icon), }; + + var renderStyle = currentSection ? buttonActiveStyle : GUI.skin.button; + return GUILayout.Button(content, renderStyle); + } + + private void DrawButtonAction(Icon icon, string header, string description = null, bool? active = null, Action callback = null, int? width = null) { + DrawButtonAction(GetIcon(icon), header, description, active, callback, width); + } + + private static void DrawButtonAction(Texture2D icon, string header, string description = null, bool? active = null, Action callback = null, int? width = null) { + var padding = GUI.skin.button.padding.top + GUI.skin.button.padding.bottom; + var height = icon.height + padding; + + var renderStyle = active.HasValue && active.Value == true ? buttonActiveStyle : GUI.skin.button; + // Draw text separately (not part of button guiconent) to have control over the space between the icon and the text. + var rect = EditorGUILayout.GetControlRect(false, height, width.HasValue ? GUILayout.Width(width.Value) : GUILayout.ExpandWidth(true)); + var clicked = GUI.Button(rect, icon, renderStyle); + GUI.Label(new Rect(rect) { xMin = rect.xMin + icon.width + 20 }, + description == null ? "" + header + "" : string.Format("{0}\n{1}", header, "" + description + "")); + if (clicked && callback != null) { + callback.Invoke(); + } + } + + /// + /// Unity handling for post asset processing callback. Checks existence of settings assets every time assets change. + /// + private class FusionHubPostProcessor : AssetPostprocessor { + private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths) { + EnsureAssetExists(); + } + + /// + /// Attempts enforce existence of singleton. If Editor is not ready, this method will be deferred one editor update and try again until it succeeds. + /// + [UnityEditor.Callbacks.DidReloadScripts] + private static void EnsureAssetExists() { + if (!PhotonAppSettings.TryGetGlobal(out var global) || global.AppSettings.AppIdFusion == null) { + FusionEditorLog.Trace($"Opening HUB due to missing settings."); + EditorApplication.delayCall += () => { + // Check if it is running in batch mode & ignore + if (UnityEditorInternal.InternalEditorUtility.inBatchMode) { return; } + + // otherwise, open the window + Open(); + }; + } + } + } + } +#endif +} \ No newline at end of file diff --git a/Assets/Photon/Fusion/Editor/FusionHubWindow.cs.meta b/Assets/Photon/Fusion/Editor/FusionHubWindow.cs.meta new file mode 100644 index 00000000..9b1ac91c --- /dev/null +++ b/Assets/Photon/Fusion/Editor/FusionHubWindow.cs.meta @@ -0,0 +1,21 @@ +fileFormatVersion: 2 +guid: 6d3bb7f5f74058d48bec8c16fc988309 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: + - m_ViewDataDictionary: {instanceID: 0} + - SetupIcon: {fileID: 2800000, guid: 0a86c2478d62ef74bb46b7906cac7fb2, type: 3} + - DocumentationIcon: {fileID: 2800000, guid: ac3bb1641dbebe441be81b7b3af3606c, type: 3} + - SamplesIcon: {fileID: 2800000, guid: 24a22d21fe312f44892796563f01c302, type: 3} + - CommunityIcon: {fileID: 2800000, guid: 80faca055f57d5b449f78efe31607c57, type: 3} + - ProductLogo: {fileID: 2800000, guid: 3ffdd609e3329cb46a9ac335d4539399, type: 3} + - PhotonCloudIcon: {fileID: 2800000, guid: 43725de47547036498287ad504fe9f9d, type: 3} + - FusionIcon: {fileID: 2800000, guid: ee9f419d0e0655a4882d707f86b99f2c, type: 3} + - CorrectIcon: {fileID: 2800000, guid: 8958b450dee843f41a091a734ed43ccd, type: 3} + - FusionHubSkin: {fileID: 11400000, guid: db84c499838991343a5a0a419616fcbe, type: 2} + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Editor/FusionRunnerVisibilityControlsWindow.cs b/Assets/Photon/Fusion/Editor/FusionRunnerVisibilityControlsWindow.cs new file mode 100644 index 00000000..ca76f738 --- /dev/null +++ b/Assets/Photon/Fusion/Editor/FusionRunnerVisibilityControlsWindow.cs @@ -0,0 +1,351 @@ +namespace Fusion.Editor { + using System; + using System.Collections.Generic; + using Statistics; + using UnityEngine; + using UnityEditor; + + /// + /// This window contains controls for each active NetworkRunner (see Multi-Peer) including + /// UI toggles for runner SetVisible() and ProvideInput members. NetworkRunner and Player Objects can be pinged in the hierarchy. + /// FusionStats creation shortcuts are provided for convenience as well. + /// + public class FusionRunnerVisibilityControlsWindow : EditorWindow { + private const int WINDOW_MIN_W = 82; + private const int WINDOW_MIN_H = 48; + + private const int STATS_BTTN_WIDE = 66; + private const int STATS_BTTN_SLIM = 24; + private const int RUNNR_BTTN_WIDE = 60; + private const int RUNNR_BTTN_SLIM = 24; + private const int FONT_SIZE = 9; + + private const float TEXT_SWITCH_WIDTH = 200; + private const float WIDE_SWITCH_WIDTH = 380; + + private const double REFRESH_RATE = 1f; + + private static class Labels { + public const string NoActiveRunner = "No Active Runner"; + public const string NoRunners = "No Runners"; + public const string SP = "SP"; + public const string H = "H"; + public const string S = "S"; + public const string C = "C"; + public const string P = "P"; + public const string Dash = "--"; + public const string ProvidingInputs = "\u2002Providing Inputs"; + public const string NoInputs = "\u2002(No Inputs)"; + public const string StatsLeft = "<< Stats"; + public const string StatsRight = "Stats >>"; + public const string ArrowsLeft = "<<"; + public const string ArrowsRight = ">>"; + public const string UserID = "UserID: "; + + public const string VisibilityTooltip = + "This button toggles NetworkRunner.SetVisible() for this NetworkRunner. If [Shift] is held while clicking all other active runners will SetVisible(false), soloing this runner."; + + public const string InputTooltip = + "This button toggles NetworkRunner.ProvideInput for this NetworkRunner. If [Shift] is held while clicking all other active runners will have NetworkRunner.ProvideInput set to false, soloing this runner."; + + public const string StatsTooltip = "Clicking this button at runtime will create a Fusion Statistics panel associated with this NetworkRunner."; + public const string RunnerTooltip = "The name of the NetworkRunner this row controls. Clicking this button will ping the NetworkRunner GameObject in the hierarchy."; + + public const string PlayerObjTooltip = + "The PlayerRef ID associated with this NetworkRunner. If NetworkRunner has a set Player Object, then this button will be enabled and will ping the Player Object in the hierarchy."; + + public static readonly string NoVisibilityWarn = + $"Network Runner does not have a {nameof(RunnerEnableVisibility)} component, or has not been registered with {nameof(NetworkRunnerVisibilityExtensions.EnableVisibilityExtension)}"; + + public const string HoldShift = "Hold Shift while clicking to solo Visibility/Input Provider."; + + public static readonly string RunnerVisibilityNotEnabled = $"Runner visibility not enabled. Add a {nameof(RunnerEnableVisibility)} component to your {nameof(NetworkRunner)} Prefab."; + } + + private static Lazy s_labelStyle = new Lazy(() => new GUIStyle(EditorStyles.label) { fontSize = FONT_SIZE }); + + private static Lazy s_labelTinyStyle = new Lazy(() => new GUIStyle(s_labelStyle.Value) { fontSize = FONT_SIZE - 1 }); + + private static Lazy s_labelCenterStyle = new Lazy(() => new GUIStyle(s_labelStyle.Value) { alignment = TextAnchor.MiddleCenter }); + + private static Lazy s_buttonStyle = new Lazy(() => new GUIStyle(EditorStyles.miniButton) { fontSize = FONT_SIZE }); + + private static Lazy s_helpboxStyle = new Lazy(() => new GUIStyle(EditorStyles.helpBox) { + fontSize = FONT_SIZE, alignment = TextAnchor.MiddleCenter, padding = new RectOffset(6, 6, 6, 6) + }); + + private static Lazy s_invisibleButtonStyle = new Lazy(() => new GUIStyle(EditorStyles.label) { fontSize = FONT_SIZE, padding = new RectOffset() }); + + private static Lazy s_invisibleButtonGrayStyle = new Lazy(() => new GUIStyle(EditorStyles.label) { + fontSize = FONT_SIZE, + normal = { textColor = Color.gray }, + active = { textColor = Color.gray }, + hover = { textColor = Color.gray }, + focused = { textColor = Color.gray }, + padding = new RectOffset() + }); + + private static Lazy Dark = new Lazy(() => EditorGUIUtility.isProSkin ? "d_" : ""); + + private static Lazy s_runnerGC = new Lazy(() => new GUIContent(string.Empty, Labels.RunnerTooltip)); + + private static Lazy s_playerObjGC = new Lazy(() => new GUIContent(string.Empty, Labels.PlayerObjTooltip)); + + private static Lazy s_visibleIcon = new Lazy(() => new GUIContent(EditorGUIUtility.FindTexture(Dark.Value + "scenevis_visible_hover@2x"), Labels.VisibilityTooltip)); + + private static Lazy s_hiddenIcon = new Lazy(() => new GUIContent(EditorGUIUtility.FindTexture(Dark.Value + "scenevis_hidden@2x"), Labels.VisibilityTooltip)); + + private static Lazy s_inputIconLong = + new Lazy(() => new GUIContent(Labels.ProvidingInputs, EditorGUIUtility.FindTexture(Dark.Value + "UnityEditor.GameView@2x"), Labels.InputTooltip)); + + private static Lazy s_inputIconShort = new Lazy(() => new GUIContent(null, EditorGUIUtility.FindTexture(Dark.Value + "UnityEditor.GameView@2x"), Labels.InputTooltip)); + + private static Lazy s_noInputIconLong = new Lazy(() => new GUIContent(Labels.NoInputs, EditorGUIUtility.FindTexture(Dark.Value + "Toolbar Minus@2x"), Labels.InputTooltip)); + + private static Lazy s_noInputIconShort = new Lazy(() => new GUIContent(null, EditorGUIUtility.FindTexture(Dark.Value + "Toolbar Minus@2x"), Labels.InputTooltip)); + + private static Lazy s_noVisibilityWarn = new Lazy(() => new GUIContent(FusionEditorSkin.WarningIcon, Labels.NoVisibilityWarn)); + + private static Lazy s_statsGC = new Lazy(() => new GUIContent(string.Empty, Labels.StatsTooltip)); + private GUIStyle _toolbarButtonStyle; + + /// + /// Window instance. + /// + public static FusionRunnerVisibilityControlsWindow Instance { get; private set; } + + private Vector2 _scrollPosition; + private double _lastRepaintTime; + + private readonly Dictionary _stats = new Dictionary(); + /// + /// Create window instance. + /// + [MenuItem("Window/Fusion/Network Runner Controls")] + [MenuItem("Tools/Fusion/Windows/Network Runner Controls")] + public static void ShowWindow() { + var window = GetWindow(typeof(FusionRunnerVisibilityControlsWindow), false, "Network Runner Controls"); + window.minSize = new Vector2(WINDOW_MIN_W, WINDOW_MIN_H); + Instance = (FusionRunnerVisibilityControlsWindow)window; + } + + private void Awake() { + Instance = this; + } + + private void OnEnable() { + Instance = this; + } + + private void OnDestroy() { + Instance = null; + } + + private void Update() { + // Force a repaint every x seconds in case runner count and runner settings have changed. + if (Time.realtimeSinceStartup - _lastRepaintTime > REFRESH_RATE) { + Repaint(); + } + } + + private void OnGUI() { + _lastRepaintTime = Time.realtimeSinceStartup; + + var currentViewWidth = EditorGUIUtility.currentViewWidth; + var isWide = currentViewWidth > WIDE_SWITCH_WIDTH; + var shortText = currentViewWidth < TEXT_SWITCH_WIDTH; + + _scrollPosition = EditorGUILayout.BeginScrollView(_scrollPosition); + + var runnerWithoutVisibilityEnabledDetected = false; + + if (!Application.isPlaying) { + DrawRunnerRow(null, shortText, isWide, currentViewWidth, ref runnerWithoutVisibilityEnabledDetected); + } else { + var enumerator = NetworkRunner.GetInstancesEnumerator(); + while (enumerator.MoveNext()) { + var runner = enumerator.Current; + DrawRunnerRow(runner, shortText, isWide, currentViewWidth, ref runnerWithoutVisibilityEnabledDetected); + } + + if (NetworkProjectConfig.Global.PeerMode == NetworkProjectConfig.PeerModes.Multiple) { + EditorGUILayout.LabelField(Labels.HoldShift, EditorStyles.miniLabel); + if (runnerWithoutVisibilityEnabledDetected) { + EditorGUILayout.HelpBox(Labels.RunnerVisibilityNotEnabled, MessageType.Warning); + } + } + } + + EditorGUILayout.EndScrollView(); + } + + private void DrawRunnerRow(NetworkRunner runner, bool shortText, bool isWide, float currentViewWidth, ref bool runnerWithoutVisibilityEnabledDetected) { + var runnerIsNull = !runner; + + // Only show active runners. If not playing, we allow a null runner in order to render the disabled buttons. + if (Application.isPlaying && (!runner || !runner.IsRunning)) { + return; + } + + var config = runnerIsNull ? default : runner.Config; + + var isSinglePeer = runnerIsNull || config?.PeerMode == NetworkProjectConfig.PeerModes.Single; + + using (new EditorGUI.DisabledGroupScope(runnerIsNull)) { + EditorGUILayout.BeginHorizontal(); + { + var localPlayer = runnerIsNull ? default : runner.LocalPlayer; + var localPlayerGC = s_playerObjGC.Value; + localPlayerGC.text = localPlayer.IsRealPlayer ? Labels.P + localPlayer.PlayerId : Labels.Dash; + + var runnerName = runnerIsNull ? Labels.NoActiveRunner : + shortText ? (runner.IsServer ? (runner.IsSinglePlayer ? Labels.SP : runner.IsPlayer ? Labels.H : Labels.S) : Labels.C) : + runner.name; + + var runnerGC = s_runnerGC.Value; + + // Draw Runner Names/Buttons + runnerGC.text = runnerIsNull ? Labels.NoRunners : runner.IsSharedModeMasterClient ? $"{runnerName} [MC]" : runnerName; + var runnerRect = EditorGUILayout.GetControlRect(GUILayout.ExpandWidth(true), GUILayout.MinWidth(isWide ? RUNNR_BTTN_WIDE : RUNNR_BTTN_SLIM)); + if (GUI.Button(runnerRect, runnerGC, s_buttonStyle.Value)) { + EditorGUIUtility.PingObject(runner); + Selection.activeGameObject = runner.gameObject; + } + + + if (shortText == false) { + // Draw PlayerRef Id / Local Player Object buttons + var playerRefRect = EditorGUILayout.GetControlRect(GUILayout.Width(38)); + + NetworkObject playerObj; + try { + playerObj = runner.GetPlayerObject(localPlayer); + } catch { + playerObj = null; + } + + using (new EditorGUI.DisabledGroupScope(playerObj == false)) { + if (GUI.Button(playerRefRect, localPlayerGC, s_buttonStyle.Value)) { + if (playerObj) { + EditorGUIUtility.PingObject(runner.GetPlayerObject(localPlayer)); + } + } + } + } + + // Draw Visibility Icons + using (new EditorGUI.DisabledGroupScope(isSinglePeer)) { + runnerGC.text = ""; + var toggleRect = EditorGUILayout.GetControlRect(GUILayout.Width(18)); + + if (isSinglePeer || runner.HasVisibilityEnabled()) { + if (GUI.Button(toggleRect, isSinglePeer || runner.GetVisible() ? s_visibleIcon.Value : s_hiddenIcon.Value, s_invisibleButtonStyle.Value)) { + if ((Event.current.modifiers & (EventModifiers.Shift | EventModifiers.Control | EventModifiers.Command | EventModifiers.Alt)) == 0) { + runner.SetVisible(!runner.GetVisible()); + } else { + var others = NetworkRunner.GetInstancesEnumerator(); + while (others.MoveNext()) { + var other = others.Current; + // Only consider active runners. + if (!other || !other.IsRunning) { + continue; + } + + other.SetVisible(other == runner); + } + } + } + } else { + runnerWithoutVisibilityEnabledDetected = true; + GUI.Label(toggleRect, s_noVisibilityWarn.Value); + } + } + + // Draw Provide Input icon/text + using (new EditorGUI.DisabledGroupScope(runnerIsNull || runner.Mode == SimulationModes.Server)) { + var inputToggleRect = EditorGUILayout.GetControlRect(GUILayout.Width(isWide ? 106 : 18)); + var providingInput = runnerIsNull || runner.ProvideInput; + var inputContent = isWide ? (providingInput ? s_inputIconLong.Value : s_noInputIconLong.Value) : (providingInput ? s_inputIconShort.Value : s_noInputIconShort.Value); + + if (GUI.Button(inputToggleRect, inputContent, providingInput ? s_invisibleButtonStyle.Value : s_invisibleButtonGrayStyle.Value)) { + if ((Event.current.modifiers & (EventModifiers.Shift | EventModifiers.Control | EventModifiers.Command | EventModifiers.Alt)) == 0) { + runner.ProvideInput = !runner.ProvideInput; + } else { + var others = NetworkRunner.GetInstancesEnumerator(); + while (others.MoveNext()) { + var other = others.Current; + // Only consider active runners. + if (!other || !other.IsRunning) { + continue; + } + + other.ProvideInput = other == runner; + } + } + } + } + + // Draw runtime stats creation buttons. Reflection used since this namespace can't see FusionStats. + if (currentViewWidth >= WINDOW_MIN_W + 10) { + var statsLeftRect = EditorGUILayout.GetControlRect(GUILayout.Width(isWide ? STATS_BTTN_WIDE : STATS_BTTN_SLIM)); + var statsRightRect = EditorGUILayout.GetControlRect(GUILayout.Width(isWide ? STATS_BTTN_WIDE : STATS_BTTN_SLIM)); + var statsGC = s_statsGC.Value; + statsGC.text = isWide ? Labels.StatsLeft : Labels.ArrowsLeft; + if (GUI.Button(statsLeftRect, statsGC, s_buttonStyle.Value)) { + CreateOrUpdateFusionStats(runner, CanvasAnchor.TopLeft); + } + + statsGC.text = isWide ? Labels.StatsRight : Labels.ArrowsRight; + if (GUI.Button(statsRightRect, statsGC, s_buttonStyle.Value)) { + CreateOrUpdateFusionStats(runner, CanvasAnchor.TopRight); + } + } + + // Draw UserID + if (currentViewWidth > 600) { + using (new EditorGUI.DisabledGroupScope(true)) { + var userIdString = runnerIsNull ? null : runner.UserId; + var userIdRect = EditorGUILayout.GetControlRect(GUILayout.MinWidth(40), GUILayout.ExpandWidth(true)); + GUI.Label(userIdRect, Labels.UserID + (userIdString ?? Labels.Dash), s_labelTinyStyle.Value); + } + } + } + + EditorGUILayout.EndHorizontal(); + } + } + + private void CreateOrUpdateFusionStats(NetworkRunner runner, CanvasAnchor anchor) { + if (_stats.TryGetValue(runner, out var stats) == false) { + stats = runner.gameObject.AddComponent(); + EditorGUIUtility.PingObject(stats.gameObject); + Selection.activeObject = stats.gameObject; + + _stats.Add(runner, stats); + stats.SetupStatisticsPanel(); + } + + stats.SetCanvasAnchor(anchor); + if (stats.IsPanelActive == false) { + stats.SetupStatisticsPanel(); + } + } + + /// + /// Draw buttons on toolbar. + /// Automatically called by unity. + /// + /// Position of the button. + private void ShowButton(Rect position) { + // button style + if (_toolbarButtonStyle == null) { + _toolbarButtonStyle = new GUIStyle(GUI.skin.button) { padding = new RectOffset() }; + } + + // draw button + if (GUI.Button(position, EditorGUIUtility.IconContent("_Help"), _toolbarButtonStyle)) { + Application.OpenURL("https://doc.photonengine.com/fusion/current/manual/testing-and-tooling/multipeer"); + } + } + } +} \ No newline at end of file diff --git a/Assets/Photon/Fusion/Editor/FusionRunnerVisibilityControlsWindow.cs.meta b/Assets/Photon/Fusion/Editor/FusionRunnerVisibilityControlsWindow.cs.meta new file mode 100644 index 00000000..576592ba --- /dev/null +++ b/Assets/Photon/Fusion/Editor/FusionRunnerVisibilityControlsWindow.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b0b9919e3f6641c4dbd365521e52c809 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Editor/FusionWeaverTriggerImporter.cs b/Assets/Photon/Fusion/Editor/FusionWeaverTriggerImporter.cs new file mode 100644 index 00000000..b0b3c655 --- /dev/null +++ b/Assets/Photon/Fusion/Editor/FusionWeaverTriggerImporter.cs @@ -0,0 +1,65 @@ +namespace Fusion.Editor { + using System.IO; + using System.Linq; + using UnityEditor; + using UnityEditor.AssetImporters; + using UnityEngine; + + [ScriptedImporter(1, ExtensionWithoutDot, NetworkProjectConfigImporter.ImportQueueOffset + 1)] + public class FusionWeaverTriggerImporter : ScriptedImporter { + public const string Extension = "." + ExtensionWithoutDot; + public const string ExtensionWithoutDot = "fusionweavertrigger"; + + [Tooltip("If enabled, runs the weaver when weaving-related changes are detected in the config file.")] + public bool RunWeaverOnConfigChanges = true; + + public override void OnImportAsset(AssetImportContext ctx) { + ctx.DependsOnCustomDependency(DependencyHash.Name); + if (RunWeaverOnConfigChanges && !Application.isBatchMode) { + ILWeaverUtils.RunWeaver(); + } + } + + static readonly FusionCustomDependency DependencyHash = new("FusionILWeaverTriggerImporter/ConfigHash", () => { + if (EditorApplication.isCompiling || EditorApplication.isUpdating) { + return default; + } + + var configPath = NetworkProjectConfigUtilities.GetGlobalConfigPath(); + + if (string.IsNullOrEmpty(configPath)) { + return default; + } + + try { + var cfg = NetworkProjectConfigImporter.LoadConfigFromFile(configPath); + var hash = new Hash128(); + + foreach (var path in cfg.AssembliesToWeave) { + hash.Append(path); + } + + hash.Append(cfg.UseSerializableDictionary ? 1 : 0); + hash.Append(cfg.NullChecksForNetworkedProperties ? 1 : 0); + hash.Append(cfg.CheckRpcAttributeUsage ? 1 : 0); + hash.Append(cfg.CheckNetworkedPropertiesBeingEmpty ? 1 : 0); + + return hash; + } catch { + // ignore the error + return default; + } + }); + + private class Postprocessor : AssetPostprocessor { + private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths) { + foreach (var path in importedAssets) { + if (path.EndsWith(NetworkProjectConfigImporter.Extension)) { + DependencyHash.Refresh(); + break; + } + } + } + } + } +} \ No newline at end of file diff --git a/Assets/Photon/Fusion/Editor/FusionWeaverTriggerImporter.cs.meta b/Assets/Photon/Fusion/Editor/FusionWeaverTriggerImporter.cs.meta new file mode 100644 index 00000000..f5f8d575 --- /dev/null +++ b/Assets/Photon/Fusion/Editor/FusionWeaverTriggerImporter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ad51894a651725c42acc54ccd6a81282 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Editor/NetworkPrefabsInspector.cs b/Assets/Photon/Fusion/Editor/NetworkPrefabsInspector.cs new file mode 100644 index 00000000..65aa29e8 --- /dev/null +++ b/Assets/Photon/Fusion/Editor/NetworkPrefabsInspector.cs @@ -0,0 +1,303 @@ +namespace Fusion.Editor { + using System; + using System.Collections.Generic; + using System.Linq; + using UnityEditor; + using UnityEditor.IMGUI.Controls; + using UnityEngine; + using Object = UnityEngine.Object; + +#if UNITY_6000_2_OR_NEWER + using TreeViewState = UnityEditor.IMGUI.Controls.TreeViewState; + using TreeViewItem = UnityEditor.IMGUI.Controls.TreeViewItem; + using TreeView = UnityEditor.IMGUI.Controls.TreeView; +#endif + + public class NetworkPrefabsInspector : EditorWindow { + + private Grid _grid = new Grid(); + + [MenuItem("Tools/Fusion/Windows/Network Prefabs Inspector")] + [MenuItem("Window/Fusion/Network Prefabs Inspector")] + public static void ShowWindow() { + var window = GetWindow(false, "Network Prefabs Inspector"); + window.Show(); + } + + private void OnEnable() { + _grid.PrefabTable = NetworkProjectConfig.Global.PrefabTable; + _grid.OnEnable(); + } + + private void OnInspectorUpdate() { + _grid.OnInspectorUpdate(); + } + + private void OnGUI() { + using (new EditorGUILayout.HorizontalScope(EditorStyles.toolbar)) { + _grid.DrawToolbarReloadButton(); + _grid.DrawToolbarSyncSelectionButton(); + GUILayout.FlexibleSpace(); + + EditorGUI.BeginChangeCheck(); + _grid.OnlyLoaded = GUILayout.Toggle(_grid.OnlyLoaded, "Loaded Only", EditorStyles.toolbarButton); + if (EditorGUI.EndChangeCheck()) { + _grid.ResetTree(); + } + + _grid.DrawToolbarSearchField(); + } + + var rect = GUILayoutUtility.GetRect(GUIContent.none, GUIStyle.none, GUILayout.ExpandHeight(true), GUILayout.ExpandWidth(true)); + _grid.OnGUI(rect); + } + + + private enum LoadState { + NotLoaded, + Loading, + LoadedNoInstances, + Loaded + } + + [Serializable] + private class InspectorTreeViewState : TreeViewState { + public MultiColumnHeaderState HeaderState; + public bool SyncSelection; + } + + private class GridItem : FusionGridItem { + private readonly NetworkPrefabId _prefabId; + private readonly NetworkPrefabTable _prefabTable; + + public GridItem(NetworkPrefabTable prefabTable, NetworkPrefabId prefabId) { + _prefabId = prefabId; + _prefabTable = prefabTable; + } + + public int InstanceCount => _prefabTable.GetInstancesCount(_prefabId); + + public string Path => AssetDatabase.GUIDToAssetPath(Guid); + + public string Guid => Source?.AssetGuid.ToUnityGuidString() ?? "Null"; + + public override Object TargetObject { + get { + if (Source?.AssetGuid.IsValid == true) { + if (NetworkProjectConfigUtilities.TryGetPrefabEditorInstance(Source.AssetGuid, out var result)) { + return result.gameObject; + } + } + + return null; + } + } + + public INetworkPrefabSource Source => _prefabTable.GetSource(_prefabId); + + public string Description { + get => Source?.Description ?? "Null"; + } + + public LoadState LoadState { + get { + if (!_prefabTable.IsAcquired(_prefabId)) { + return LoadState.NotLoaded; + } + + if (!_prefabTable.GetSource(_prefabId).IsCompleted) { + return LoadState.Loading; + } + + if (_prefabTable.GetInstancesCount(_prefabId) == 0) { + return LoadState.LoadedNoInstances; + } + + return LoadState.Loaded; + } + } + + public NetworkPrefabId PrefabId => _prefabId; + } + + [Serializable] + class Grid : FusionGrid { + + [SerializeField] + public NetworkPrefabTable PrefabTable; + [SerializeField] + public bool OnlyLoaded; + + public override int GetContentHash() { + return PrefabTable?.Version ?? 0; + } + + protected override IEnumerable CreateColumns() { + yield return new() { + headerContent = new GUIContent("State"), + width = 40, + autoResize = false, + cellGUI = (item, rect, _, _) => { + var icon = FusionEditorSkin.LoadStateIcon; + string label = ""; + Color color; + switch (item.LoadState) { + case LoadState.Loaded: + color = Color.green; + label = item.InstanceCount.ToString(); + break; + case LoadState.LoadedNoInstances: + color = Color.yellow; + label = "0"; + break; + case LoadState.Loading: + color = Color.yellow; + color.a = 0.5f; + label = "0"; + break; + default: + color = Color.gray; + break; + } + + using (new FusionEditorGUI.ContentColorScope(color)) { + EditorGUI.LabelField(rect, new GUIContent(label, icon, item.LoadState.ToString())); + } + }, + getComparer = order => (a, b) => { + var result = a.LoadState.CompareTo(b.LoadState) * order; + if (result != 0) { + return result; + } + return a.InstanceCount.CompareTo(b.InstanceCount) * order; + }, + }; + yield return new() { + headerContent = new GUIContent("Type"), + width = 40, + maxWidth = 40, + minWidth = 40, + cellGUI = (item, rect, _, _) => INetworkPrefabSourceDrawer.DrawThumbnail(rect, item.Source), + getComparer = order => (a, b) => EditorUtility.NaturalCompare(a.Source?.GetType().Name ?? "", b.Source?.GetType().Name ?? "") * order, + }; + yield return MakeSimpleColumn(x => x.PrefabId, new() { + cellGUI = (item, rect, selected, focused) => TreeView.DefaultGUI.Label(rect, item.PrefabId.ToString(false, false), selected , focused), + width = 50, + autoResize = false + }); + yield return MakeSimpleColumn(x => x.Path, new() { + initiallySorted = true, + }); + yield return MakeSimpleColumn(x => x.Guid, new() { + initiallyVisible = false + }); + yield return MakeSimpleColumn(x => x.Description, new() { + initiallyVisible = false + }); + } + + protected override IEnumerable CreateRows() { + if (PrefabTable == null) { + yield break; + } + + for (int i = 0; i < PrefabTable.Prefabs.Count; ++i) { + var prefabId = NetworkPrefabId.FromIndex(i); + if (OnlyLoaded && !PrefabTable.IsAcquired(prefabId)) { + continue; + } + yield return new GridItem(PrefabTable, NetworkPrefabId.FromIndex(i)) { id = (int)(i + 1) }; + } + } + + protected override GenericMenu CreateContextMenu(GridItem item, TreeView treeView) { + + var menu = new GenericMenu(); + + var selection = treeView.GetSelection() + .Select(x => NetworkPrefabId.FromIndex(x-1)) + .ToList(); + + var anyLoaded = selection.Any(x => PrefabTable.IsAcquired(x)); + var anyNotLoaded = selection.Any(x => !PrefabTable.IsAcquired(x)); + var anyInstances = selection.Any(x => PrefabTable.GetInstancesCount(x) > 0); + var spawnerRunners = NetworkRunner.Instances.Where(x => x && x.IsRunning && x.CanSpawn).ToArray(); + + var loadContent = new GUIContent("Load"); + var loadAsyncContent = new GUIContent("Load (async)"); + var unloadContent = new GUIContent("Unload"); + var selectInstancesContent = new GUIContent("Select Instances"); + var spawnContent = new GUIContent("Spawn"); + var spawnAsyncContent = new GUIContent("Spawn (async)"); + + if (anyNotLoaded) { + menu.AddItem(loadContent, false, () => { + foreach (var id in selection) { + PrefabTable.Load(id, isSynchronous: true); + } + }); + menu.AddItem(loadAsyncContent, false, () => { + foreach (var id in selection) { + PrefabTable.Load(id, isSynchronous: false); + } + }); + } else { + menu.AddDisabledItem(loadContent); + menu.AddDisabledItem(loadAsyncContent); + } + + if (anyLoaded) { + menu.AddItem(unloadContent, false, () => { + foreach (var id in selection) { + PrefabTable.Unload(id); + } + }); + } else { + menu.AddDisabledItem(unloadContent); + } + + if (anyInstances) { + menu.AddItem(selectInstancesContent, false, () => { + var lookup = new HashSet(selection.Select(x => NetworkObjectTypeId.FromPrefabId(x))); + Selection.objects = FindObjectsByType(FindObjectsInactive.Include, FindObjectsSortMode.None) + .Where(x => x.NetworkTypeId.IsValid && lookup.Contains(x.NetworkTypeId)) + .Select(x => x.gameObject) + .ToArray(); + }); + } else { + menu.AddDisabledItem(selectInstancesContent); + } + + menu.AddSeparator(""); + + if (spawnerRunners.Any()) { + if (spawnerRunners.Length > 1) { + foreach (var runner in spawnerRunners.Where(x => x.CanSpawn)) { + AddSpawnItems($"/{runner.name}", runner); + } + } else { + AddSpawnItems($"", spawnerRunners[0]); + } + } else { + menu.AddDisabledItem(spawnContent); + menu.AddDisabledItem(spawnAsyncContent); + } + + void AddSpawnItems(string s, NetworkRunner networkRunner) { + menu.AddItem(new GUIContent($"{spawnContent.text}{s}"), false, () => { + foreach (var id in selection) { + networkRunner.TrySpawn(id, out _); + } + }); + menu.AddItem(new GUIContent($"{spawnAsyncContent.text}{s}"), false, () => { + foreach (var id in selection) { + networkRunner.SpawnAsync(id); + } + }); + } + + return menu; + } + } + } +} diff --git a/Assets/Photon/Fusion/Editor/NetworkPrefabsInspector.cs.meta b/Assets/Photon/Fusion/Editor/NetworkPrefabsInspector.cs.meta new file mode 100644 index 00000000..2aec62f9 --- /dev/null +++ b/Assets/Photon/Fusion/Editor/NetworkPrefabsInspector.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2a91437e16a1edc41bde1ce3b0114aab +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Editor/NetworkProjectConfigImporter.cs b/Assets/Photon/Fusion/Editor/NetworkProjectConfigImporter.cs new file mode 100644 index 00000000..a52f9a64 --- /dev/null +++ b/Assets/Photon/Fusion/Editor/NetworkProjectConfigImporter.cs @@ -0,0 +1,237 @@ +namespace Fusion.Editor { + using System; + using System.Collections; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using UnityEditor; + using UnityEditor.AssetImporters; + using UnityEditor.PackageManager; + using UnityEngine; + + [ScriptedImporter(3, ExtensionWithoutDot, ImportQueueOffset)] + [HelpURL("https://doc.photonengine.com/fusion/current/manual/network-project-config")] + public class NetworkProjectConfigImporter : ScriptedImporter { + public const string ExtensionWithoutDot = "fusion"; + public const string Extension = "." + ExtensionWithoutDot; + public const int ImportQueueOffset = 1000; + + public const string FusionPrefabTag = "FusionPrefab"; + public const string FusionPrefabTagSearchTerm = "l:FusionPrefab"; + + [Header("Prefabs")] + [DrawInline] + public NetworkPrefabTableOptions PrefabOptions; + +#if FUSION_ENABLE_ADDRESSABLES && !FUSION_DISABLE_ADDRESSABLES + [InitializeOnLoadMethod] + static void RegisterAddressableEventListeners() { + AssetDatabaseUtils.AddAddressableAssetsWithLabelMonitor(FusionPrefabTag, (hash) => { + AddressablesDependency.Refresh(); + }); + } +#endif + + public override void OnImportAsset(AssetImportContext ctx) { + FusionEditorLog.TraceImport(ctx.assetPath, "Staring scripted import"); + + NetworkProjectConfig.UnloadGlobal(); + NetworkProjectConfig config = LoadConfigFromFile(ctx.assetPath); + + var root = ScriptableObject.CreateInstance(); + root.Config = config; + ctx.AddObjectToAsset("root", root); + + root.Prefabs = DiscoverPrefabs(ctx); + root.BehaviourMeta = CreateBehaviourMeta(ctx); + root.PrefabOptions = PrefabOptions; + + ctx.DependsOnCustomDependency(AddressablesDependency.Name); + ctx.DependsOnCustomDependency(ScriptOrderDependency.Name); + ctx.DependsOnCustomDependency(NetworkObjectPrefabDependency.Name); + } + + + public static NetworkProjectConfig LoadConfigFromFile(string path) { + var config = new NetworkProjectConfig(); + try { + var text = File.ReadAllText(path); + if (string.IsNullOrWhiteSpace(text)) { + throw new System.ArgumentException("Empty string"); + } + + EditorJsonUtility.FromJsonOverwrite(text, config); + } catch (System.ArgumentException ex) { + throw new System.ArgumentException($"Failed to parse {path}: {ex.Message}"); + } + + return config; + } + + private static List DiscoverPrefabs(AssetImportContext ctx) { + var result = new List(); + + var factory = new NetworkAssetSourceFactory(); + var detailsLog = new System.Text.StringBuilder(); + var paths = new List(); + + foreach (var it in AssetDatabaseUtils.IterateAssets(label: FusionPrefabTag)) { + var prefabPath = AssetDatabase.GetAssetPath(it.GetObjectId()); + var context = new NetworkAssetSourceFactoryContext(it); + + INetworkPrefabSource source = factory.TryCreatePrefabSource(context); + + if (source == null) { + ctx.LogImportError($"Unable to create prefab asset for {AssetDatabase.GetAssetPath(it.GetObjectId())} ({it.guid})"); + continue; + } + +#if FUSION_EDITOR_TRACE + detailsLog.AppendLine($"{prefabPath} -> {((INetworkPrefabSource)source).Description}"); +#endif + + var index = paths.BinarySearch(prefabPath, StringComparer.Ordinal); + if (index < 0) { + index = ~index; + } else { + ctx.LogImportWarning($"Prefab with path {prefabPath} already added"); + } + + paths.Insert(index, prefabPath); + result.Insert(index, source); + } + + FusionEditorLog.TraceImport($"Discover prefabs details [{result.Count}] :\n{detailsLog}"); + return result; + } + + private NetworkProjectConfigAsset.SerializableSimulationBehaviourMeta[] CreateBehaviourMeta(AssetImportContext ctx) { + var result = new List(); + + foreach (var monoScript in MonoImporter.GetAllRuntimeMonoScripts()) { + var scriptType = monoScript.GetClass(); + if (scriptType?.IsSubclassOf(typeof(SimulationBehaviour)) != true) { + continue; + } + + var executionOrder = MonoImporter.GetExecutionOrder(monoScript); + if (executionOrder == 0) { + // no need to add it to the list + continue; + } + + result.Add(new() { + Type = scriptType, + ExecutionOrder = executionOrder + }); + } + + return result.OrderBy(x => x.ExecutionOrder).ToArray(); + } + + class Postprocessor : AssetPostprocessor { + static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths) { + foreach (var path in deletedAssets) { + if (path.EndsWith(Extension, StringComparison.OrdinalIgnoreCase)) { + NetworkProjectConfig.UnloadGlobal(); + break; + } + } + + foreach (var path in movedAssets) { + if (path.EndsWith(Extension, StringComparison.OrdinalIgnoreCase)) { + NetworkProjectConfig.UnloadGlobal(); + break; + } + } + + foreach (var path in importedAssets) { + if (HasSimulationBehaviours(path)) { + ScriptOrderDependency.Refresh(); + break; + } + } + } + + private static bool HasSimulationBehaviours(string path) { + if (path.EndsWith(".cs", StringComparison.OrdinalIgnoreCase)) { + // check if there is MB in there (with MonoImporter) and if it is a simulation behaviour + var importer = AssetImporter.GetAtPath(path) as MonoImporter; + if (importer == null) { + return false; + } + + var scriptType = importer.GetScript()?.GetClass(); + if (scriptType?.IsSubclassOf(typeof(SimulationBehaviour)) != true) { + return false; + } + + return true; + } + + if (path.EndsWith(".dll", StringComparison.OrdinalIgnoreCase)) { + // check if there is MB in there (with MonoImporter) and if it is a simulation behaviour + foreach (var asset in AssetDatabase.LoadAllAssetsAtPath(path)) { + if (asset is MonoScript monoScript) { + var scriptType = monoScript.GetClass(); + if (scriptType?.IsSubclassOf(typeof(SimulationBehaviour)) == true) { + return true; + } + } + } + + return false; + } + + return false; + } + } + + static readonly FusionCustomDependency ScriptOrderDependency = new("Fusion.ScriptOrderDependency", () => { + var hash = new Hash128(); + + var scripts = MonoImporter.GetAllRuntimeMonoScripts(); + + foreach (var monoScript in scripts) { + var scriptType = monoScript.GetClass(); + + if (scriptType?.IsSubclassOf(typeof(SimulationBehaviour)) != true) { + continue; + } + + var executionOrder = MonoImporter.GetExecutionOrder(monoScript); + + if (executionOrder == 0) { + continue; + } + + hash.Append(scriptType.FullName); + hash.Append(executionOrder); + } + + return hash; + }); + + static readonly FusionCustomDependency AddressablesDependency = new("Fusion.AddressablesDependency", () => { +#if FUSION_ENABLE_ADDRESSABLES && !FUSION_DISABLE_ADDRESSABLES + var assetsSettings = UnityEditor.AddressableAssets.AddressableAssetSettingsDefaultObject.Settings; + if (assetsSettings) { + return assetsSettings.currentHash; + } +#endif + return default; + }); + + static readonly FusionCustomDependency NetworkObjectPrefabDependency = new("Fusion.PrefabsDependency", () => { + var hash = new Hash128(); + foreach (var it in AssetDatabaseUtils.IterateAssets(label: FusionPrefabTag)) { + hash.Append(it.guid); + } + return hash; + }); + + public static void RebuildPrefabHash() { + NetworkObjectPrefabDependency.Refresh(); + } + } +} diff --git a/Assets/Photon/Fusion/Editor/NetworkProjectConfigImporter.cs.meta b/Assets/Photon/Fusion/Editor/NetworkProjectConfigImporter.cs.meta new file mode 100644 index 00000000..1c180993 --- /dev/null +++ b/Assets/Photon/Fusion/Editor/NetworkProjectConfigImporter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 66a64a17d0b40f34f9224317a5a84bf2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Editor/NetworkProjectConfigImporterEditor.cs b/Assets/Photon/Fusion/Editor/NetworkProjectConfigImporterEditor.cs new file mode 100644 index 00000000..e16376d6 --- /dev/null +++ b/Assets/Photon/Fusion/Editor/NetworkProjectConfigImporterEditor.cs @@ -0,0 +1,141 @@ +namespace Fusion.Editor { + + using System; + using System.IO; + using System.Linq; + using UnityEditor; + using UnityEditor.AssetImporters; + using UnityEngine; + + [CustomEditor(typeof(NetworkProjectConfigImporter))] + internal class NetworkProjectConfigImporterEditor : ScriptedImporterEditor { + + private Exception _initializeException; + private LogSettingsDrawer _logSettingsDrawer; + + private static bool _versionExpanded; + private static string _version; + private static string _allVersionInfo; + + public override bool showImportedObject => false; + + protected override Type extraDataType => typeof(NetworkProjectConfigAsset); + + public override void OnInspectorGUI() { + + bool rebuildPrefabTable = false; + + try { + if (_initializeException != null) { + EditorGUILayout.HelpBox(_initializeException.ToString(), MessageType.Error, true); + } else { + + FusionEditorGUI.InjectScriptHeaderDrawer(extraDataSerializedObject); + FusionEditorGUI.ScriptPropertyField(extraDataSerializedObject); + + VersionInfoGUI(); + + using (new EditorGUI.DisabledScope(HasModified())) { + rebuildPrefabTable = GUILayout.Button("Rebuild Prefab Table"); + } + + extraDataSerializedObject.Update(); + EditorGUILayout.PropertyField(extraDataSerializedObject.FindPropertyOrThrow(nameof(NetworkProjectConfigAsset.Config))); + extraDataSerializedObject.ApplyModifiedProperties(); + + EditorGUILayout.PropertyField(serializedObject.FindProperty(nameof(NetworkProjectConfigImporter.PrefabOptions))); + + EditorGUILayout.Space(); + EditorGUILayout.LabelField("Log", EditorStyles.boldLabel); + _logSettingsDrawer.DrawLayout(this, true); + + EditorGUILayout.Space(); + EditorGUILayout.LabelField("Auto-Generated", EditorStyles.boldLabel); + + if (GUILayout.Button("Show Network Prefabs Inspector")) { + NetworkPrefabsInspector.ShowWindow(); + } + + // WORKAROUND: during initial failed imports, this may be an instance of UnityEngine.DefaultAsset instead of the actual asset + if (assetSerializedObject?.targetObject.GetType() == typeof(NetworkProjectConfigAsset)) { + // this has the tendency to overwrite the global enabled flag, so let's make sure it's reset once the scope exists + using (new FusionEditorGUI.EnabledScope(GUI.enabled)) { + EditorGUILayout.PropertyField(assetSerializedObject.FindPropertyOrThrow(nameof(NetworkProjectConfigAsset.Prefabs))); + EditorGUILayout.PropertyField(assetSerializedObject.FindPropertyOrThrow(nameof(NetworkProjectConfigAsset.BehaviourMeta))); + } + } else { + EditorGUILayout.HelpBox("Asset failed to deserialize correctly. Please reimport.", MessageType.Warning); + } + } + } finally { + ApplyRevertGUI(); + } + + if (rebuildPrefabTable) { + NetworkProjectConfigUtilities.RebuildPrefabTable(); + } + } + + private static void VersionInfoGUI() { + if (string.IsNullOrEmpty(_allVersionInfo)) { + var assemblies = System.AppDomain.CurrentDomain.GetAssemblies(); + foreach (var asm in assemblies) { + var assemblyFullName = asm.FullName; + if (assemblyFullName.StartsWith("Fusion.Runtime,")) { + _version = $"{NetworkRunner.BuildType}: {System.Diagnostics.FileVersionInfo.GetVersionInfo(asm.Location).ProductVersion}"; + } + + if (assemblyFullName.StartsWith("Fusion.") || assemblyFullName.StartsWith("Fusion,")) { + var fileVersion = System.Diagnostics.FileVersionInfo.GetVersionInfo(asm.Location).ToString(); + _allVersionInfo += $"{assemblyFullName.Substring(0, assemblyFullName.IndexOf(",", StringComparison.Ordinal))}: {fileVersion} \n"; + } + } + } + + var r = EditorGUILayout.GetControlRect(); + _versionExpanded = EditorGUI.Foldout(r, _versionExpanded, ""); + EditorGUI.LabelField(r, "Fusion Version", _version); + + if (_versionExpanded) { + EditorGUILayout.HelpBox(_allVersionInfo, MessageType.None); + } + } + + protected override void Apply() { + base.Apply(); + + if (targets != null) { + for (int i = 0; i < extraDataTargets.Length; ++i) { + var importer = GetImporter(i); + var wrapper = GetConfigWrapper(i); + + EditorUtility.SetDirty(importer); + + var json = EditorJsonUtility.ToJson(wrapper.Config, true); + File.WriteAllText(importer.assetPath, json); + } + } + } + + protected override void InitializeExtraDataInstance(UnityEngine.Object extraData, int targetIndex) { + try { + var importer = GetImporter(targetIndex); + var extra = (NetworkProjectConfigAsset)extraData; + + extra.Config = NetworkProjectConfigImporter.LoadConfigFromFile(importer.assetPath); + + _initializeException = null; + } catch (Exception ex) { + _initializeException = ex; + } + } + + private NetworkProjectConfigImporter GetImporter(int i) { + return (NetworkProjectConfigImporter)targets[i]; + } + + private NetworkProjectConfigAsset GetConfigWrapper(int i) { + return (NetworkProjectConfigAsset)extraDataTargets[i]; + } + } +} \ No newline at end of file diff --git a/Assets/Photon/Fusion/Editor/NetworkProjectConfigImporterEditor.cs.meta b/Assets/Photon/Fusion/Editor/NetworkProjectConfigImporterEditor.cs.meta new file mode 100644 index 00000000..60fa9a49 --- /dev/null +++ b/Assets/Photon/Fusion/Editor/NetworkProjectConfigImporterEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e94c67db235c29f4891980a488edca07 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Plugins.meta b/Assets/Photon/Fusion/Plugins.meta new file mode 100644 index 00000000..4ed5f064 --- /dev/null +++ b/Assets/Photon/Fusion/Plugins.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 6b6f4866f97414f46894b80092cd5e5b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Plugins/NanoSockets.meta b/Assets/Photon/Fusion/Plugins/NanoSockets.meta new file mode 100644 index 00000000..852fcb7d --- /dev/null +++ b/Assets/Photon/Fusion/Plugins/NanoSockets.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 731a065497c319c4385b40369174b70b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Plugins/NanoSockets/Android.meta b/Assets/Photon/Fusion/Plugins/NanoSockets/Android.meta new file mode 100644 index 00000000..278a9a93 --- /dev/null +++ b/Assets/Photon/Fusion/Plugins/NanoSockets/Android.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4916e347ae38bc046a590e7c47ced694 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Plugins/NanoSockets/Android/arm64-v8a.meta b/Assets/Photon/Fusion/Plugins/NanoSockets/Android/arm64-v8a.meta new file mode 100644 index 00000000..7d2e7a8c --- /dev/null +++ b/Assets/Photon/Fusion/Plugins/NanoSockets/Android/arm64-v8a.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 05a7bd6ee60406945903abcb38977f4d +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Plugins/NanoSockets/Android/arm64-v8a/libnanosockets.so b/Assets/Photon/Fusion/Plugins/NanoSockets/Android/arm64-v8a/libnanosockets.so new file mode 100644 index 00000000..8b44b258 Binary files /dev/null and b/Assets/Photon/Fusion/Plugins/NanoSockets/Android/arm64-v8a/libnanosockets.so differ diff --git a/Assets/Photon/Fusion/Plugins/NanoSockets/Android/arm64-v8a/libnanosockets.so.meta b/Assets/Photon/Fusion/Plugins/NanoSockets/Android/arm64-v8a/libnanosockets.so.meta new file mode 100644 index 00000000..a069682b --- /dev/null +++ b/Assets/Photon/Fusion/Plugins/NanoSockets/Android/arm64-v8a/libnanosockets.so.meta @@ -0,0 +1,82 @@ +fileFormatVersion: 2 +guid: fd330aee9172d2c4087e7baffb8d9f5d +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Android: 0 + Exclude Editor: 1 + Exclude Linux64: 1 + Exclude OSXUniversal: 1 + Exclude Win: 1 + Exclude Win64: 1 + Exclude iOS: 1 + - first: + Android: Android + second: + enabled: 1 + settings: + AndroidSharedLibraryType: Executable + CPU: ARM64 + Is16KbAligned: true + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + CPU: AnyCPU + DefaultValueInitialized: true + OS: AnyOS + - first: + Standalone: Linux64 + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: OSXUniversal + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win64 + second: + enabled: 0 + settings: + CPU: None + - first: + iPhone: iOS + second: + enabled: 0 + settings: + AddToEmbeddedBinaries: false + CPU: AnyCPU + CompileFlags: + FrameworkDependencies: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Plugins/NanoSockets/Android/armeabi-v7a.meta b/Assets/Photon/Fusion/Plugins/NanoSockets/Android/armeabi-v7a.meta new file mode 100644 index 00000000..ee82818d --- /dev/null +++ b/Assets/Photon/Fusion/Plugins/NanoSockets/Android/armeabi-v7a.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: bc4ecf9b6d6cf764494d29689915d00a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Plugins/NanoSockets/Android/armeabi-v7a/libnanosockets.so b/Assets/Photon/Fusion/Plugins/NanoSockets/Android/armeabi-v7a/libnanosockets.so new file mode 100644 index 00000000..f733e8d6 Binary files /dev/null and b/Assets/Photon/Fusion/Plugins/NanoSockets/Android/armeabi-v7a/libnanosockets.so differ diff --git a/Assets/Photon/Fusion/Plugins/NanoSockets/Android/armeabi-v7a/libnanosockets.so.meta b/Assets/Photon/Fusion/Plugins/NanoSockets/Android/armeabi-v7a/libnanosockets.so.meta new file mode 100644 index 00000000..21350e96 --- /dev/null +++ b/Assets/Photon/Fusion/Plugins/NanoSockets/Android/armeabi-v7a/libnanosockets.so.meta @@ -0,0 +1,82 @@ +fileFormatVersion: 2 +guid: 34d5b82ebfd43f840915715f4a3a133d +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Android: 0 + Exclude Editor: 1 + Exclude Linux64: 1 + Exclude OSXUniversal: 1 + Exclude Win: 1 + Exclude Win64: 1 + Exclude iOS: 1 + - first: + Android: Android + second: + enabled: 1 + settings: + AndroidSharedLibraryType: Executable + CPU: ARMv7 + Is16KbAligned: false + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + CPU: AnyCPU + DefaultValueInitialized: true + OS: AnyOS + - first: + Standalone: Linux64 + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: OSXUniversal + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win64 + second: + enabled: 0 + settings: + CPU: None + - first: + iPhone: iOS + second: + enabled: 0 + settings: + AddToEmbeddedBinaries: false + CPU: AnyCPU + CompileFlags: + FrameworkDependencies: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Plugins/NanoSockets/Android/x86.meta b/Assets/Photon/Fusion/Plugins/NanoSockets/Android/x86.meta new file mode 100644 index 00000000..8a6be1cc --- /dev/null +++ b/Assets/Photon/Fusion/Plugins/NanoSockets/Android/x86.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d14659e8fb14f454f9f315f4c6966e19 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Plugins/NanoSockets/Android/x86/libnanosockets.so b/Assets/Photon/Fusion/Plugins/NanoSockets/Android/x86/libnanosockets.so new file mode 100644 index 00000000..017eb35b Binary files /dev/null and b/Assets/Photon/Fusion/Plugins/NanoSockets/Android/x86/libnanosockets.so differ diff --git a/Assets/Photon/Fusion/Plugins/NanoSockets/Android/x86/libnanosockets.so.meta b/Assets/Photon/Fusion/Plugins/NanoSockets/Android/x86/libnanosockets.so.meta new file mode 100644 index 00000000..d5b4cb3f --- /dev/null +++ b/Assets/Photon/Fusion/Plugins/NanoSockets/Android/x86/libnanosockets.so.meta @@ -0,0 +1,82 @@ +fileFormatVersion: 2 +guid: abb420497d9a79343b4647172f1fa2ec +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Android: 0 + Exclude Editor: 1 + Exclude Linux64: 1 + Exclude OSXUniversal: 1 + Exclude Win: 1 + Exclude Win64: 1 + Exclude iOS: 1 + - first: + Android: Android + second: + enabled: 1 + settings: + AndroidSharedLibraryType: Executable + CPU: X86 + Is16KbAligned: false + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + CPU: AnyCPU + DefaultValueInitialized: true + OS: AnyOS + - first: + Standalone: Linux64 + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: OSXUniversal + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win64 + second: + enabled: 0 + settings: + CPU: None + - first: + iPhone: iOS + second: + enabled: 0 + settings: + AddToEmbeddedBinaries: false + CPU: AnyCPU + CompileFlags: + FrameworkDependencies: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Plugins/NanoSockets/Android/x86_64.meta b/Assets/Photon/Fusion/Plugins/NanoSockets/Android/x86_64.meta new file mode 100644 index 00000000..4b2e1914 --- /dev/null +++ b/Assets/Photon/Fusion/Plugins/NanoSockets/Android/x86_64.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4aa814576a9dc13448bd7d205409b0ad +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Plugins/NanoSockets/Android/x86_64/libnanosockets.so b/Assets/Photon/Fusion/Plugins/NanoSockets/Android/x86_64/libnanosockets.so new file mode 100644 index 00000000..4e62671c Binary files /dev/null and b/Assets/Photon/Fusion/Plugins/NanoSockets/Android/x86_64/libnanosockets.so differ diff --git a/Assets/Photon/Fusion/Plugins/NanoSockets/Android/x86_64/libnanosockets.so.meta b/Assets/Photon/Fusion/Plugins/NanoSockets/Android/x86_64/libnanosockets.so.meta new file mode 100644 index 00000000..bd8848ed --- /dev/null +++ b/Assets/Photon/Fusion/Plugins/NanoSockets/Android/x86_64/libnanosockets.so.meta @@ -0,0 +1,84 @@ +fileFormatVersion: 2 +guid: 4017079a9c0c04b488db2e8be8555bd9 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Android: 0 + Exclude Editor: 1 + Exclude Linux64: 1 + Exclude OSXUniversal: 1 + Exclude WebGL: 1 + Exclude Win: 1 + Exclude Win64: 1 + Exclude WindowsStoreApps: 1 + - first: + Android: Android + second: + enabled: 1 + settings: + AndroidSharedLibraryType: Executable + CPU: X86_64 + Is16KbAligned: true + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + CPU: AnyCPU + DefaultValueInitialized: true + OS: AnyOS + - first: + Standalone: Linux64 + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: OSXUniversal + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win64 + second: + enabled: 0 + settings: + CPU: None + - first: + Windows Store Apps: WindowsStoreApps + second: + enabled: 0 + settings: + CPU: X86 + DontProcess: false + PlaceholderPath: + SDK: AnySDK + ScriptingBackend: AnyScriptingBackend + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Plugins/NanoSockets/Linux.meta b/Assets/Photon/Fusion/Plugins/NanoSockets/Linux.meta new file mode 100644 index 00000000..be762d94 --- /dev/null +++ b/Assets/Photon/Fusion/Plugins/NanoSockets/Linux.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 29ea85b7feea6a94c9cf6cf95e68957e +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Plugins/NanoSockets/Linux/libnanosockets.so b/Assets/Photon/Fusion/Plugins/NanoSockets/Linux/libnanosockets.so new file mode 100644 index 00000000..c0a2dbd4 Binary files /dev/null and b/Assets/Photon/Fusion/Plugins/NanoSockets/Linux/libnanosockets.so differ diff --git a/Assets/Photon/Fusion/Plugins/NanoSockets/Linux/libnanosockets.so.meta b/Assets/Photon/Fusion/Plugins/NanoSockets/Linux/libnanosockets.so.meta new file mode 100644 index 00000000..f14f406c --- /dev/null +++ b/Assets/Photon/Fusion/Plugins/NanoSockets/Linux/libnanosockets.so.meta @@ -0,0 +1,80 @@ +fileFormatVersion: 2 +guid: 1353bd98fd16b304f82bc4cb8ff7d03f +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Android: 1 + Exclude Editor: 0 + Exclude Linux64: 0 + Exclude OSXUniversal: 1 + Exclude Win: 0 + Exclude Win64: 0 + Exclude iOS: 1 + - first: + Android: Android + second: + enabled: 0 + settings: + CPU: ARMv7 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 1 + settings: + CPU: AnyCPU + DefaultValueInitialized: true + OS: Linux + - first: + Standalone: Linux64 + second: + enabled: 1 + settings: + CPU: AnyCPU + - first: + Standalone: OSXUniversal + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win + second: + enabled: 1 + settings: + CPU: x86 + - first: + Standalone: Win64 + second: + enabled: 1 + settings: + CPU: x86_64 + - first: + iPhone: iOS + second: + enabled: 0 + settings: + AddToEmbeddedBinaries: false + CPU: AnyCPU + CompileFlags: + FrameworkDependencies: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Plugins/NanoSockets/Metro.meta b/Assets/Photon/Fusion/Plugins/NanoSockets/Metro.meta new file mode 100644 index 00000000..59133ab2 --- /dev/null +++ b/Assets/Photon/Fusion/Plugins/NanoSockets/Metro.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 0bfa6a400f5c0a8489844268c33f59c8 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Plugins/NanoSockets/Metro/ARM.meta b/Assets/Photon/Fusion/Plugins/NanoSockets/Metro/ARM.meta new file mode 100644 index 00000000..1d1e6232 --- /dev/null +++ b/Assets/Photon/Fusion/Plugins/NanoSockets/Metro/ARM.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 077420e8acec9474caa9eb515265b6c1 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Plugins/NanoSockets/Metro/ARM/libnanosockets.dll b/Assets/Photon/Fusion/Plugins/NanoSockets/Metro/ARM/libnanosockets.dll new file mode 100644 index 00000000..f0a8a4b0 Binary files /dev/null and b/Assets/Photon/Fusion/Plugins/NanoSockets/Metro/ARM/libnanosockets.dll differ diff --git a/Assets/Photon/Fusion/Plugins/NanoSockets/Metro/ARM/libnanosockets.dll.meta b/Assets/Photon/Fusion/Plugins/NanoSockets/Metro/ARM/libnanosockets.dll.meta new file mode 100644 index 00000000..5797fbed --- /dev/null +++ b/Assets/Photon/Fusion/Plugins/NanoSockets/Metro/ARM/libnanosockets.dll.meta @@ -0,0 +1,101 @@ +fileFormatVersion: 2 +guid: bf526029f85a37f449500206ee2a9692 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Android: 1 + Exclude Editor: 1 + Exclude Linux64: 1 + Exclude OSXUniversal: 1 + Exclude WebGL: 1 + Exclude Win: 1 + Exclude Win64: 1 + Exclude WindowsStoreApps: 0 + Exclude iOS: 1 + Exclude tvOS: 1 + - first: + Android: Android + second: + enabled: 0 + settings: + CPU: ARMv7 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + CPU: AnyCPU + DefaultValueInitialized: true + OS: AnyOS + - first: + Standalone: Linux64 + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: OSXUniversal + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win64 + second: + enabled: 0 + settings: + CPU: None + - first: + Windows Store Apps: WindowsStoreApps + second: + enabled: 1 + settings: + CPU: ARM + DontProcess: false + PlaceholderPath: + SDK: UWP + ScriptingBackend: AnyScriptingBackend + - first: + iPhone: iOS + second: + enabled: 0 + settings: + AddToEmbeddedBinaries: false + CPU: AnyCPU + CompileFlags: + FrameworkDependencies: + - first: + tvOS: tvOS + second: + enabled: 0 + settings: + CPU: AnyCPU + CompileFlags: + FrameworkDependencies: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Plugins/NanoSockets/Metro/ARM64.meta b/Assets/Photon/Fusion/Plugins/NanoSockets/Metro/ARM64.meta new file mode 100644 index 00000000..58df0860 --- /dev/null +++ b/Assets/Photon/Fusion/Plugins/NanoSockets/Metro/ARM64.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 72539ada9dc4b13488ddc5a9d20dd0ca +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Plugins/NanoSockets/Metro/ARM64/libnanosockets.dll b/Assets/Photon/Fusion/Plugins/NanoSockets/Metro/ARM64/libnanosockets.dll new file mode 100644 index 00000000..a682d009 Binary files /dev/null and b/Assets/Photon/Fusion/Plugins/NanoSockets/Metro/ARM64/libnanosockets.dll differ diff --git a/Assets/Photon/Fusion/Plugins/NanoSockets/Metro/ARM64/libnanosockets.dll.meta b/Assets/Photon/Fusion/Plugins/NanoSockets/Metro/ARM64/libnanosockets.dll.meta new file mode 100644 index 00000000..1c9315e4 --- /dev/null +++ b/Assets/Photon/Fusion/Plugins/NanoSockets/Metro/ARM64/libnanosockets.dll.meta @@ -0,0 +1,101 @@ +fileFormatVersion: 2 +guid: 7d08c7e651f23b94fbcb726124c46744 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Android: 1 + Exclude Editor: 1 + Exclude Linux64: 1 + Exclude OSXUniversal: 1 + Exclude WebGL: 1 + Exclude Win: 1 + Exclude Win64: 1 + Exclude WindowsStoreApps: 0 + Exclude iOS: 1 + Exclude tvOS: 1 + - first: + Android: Android + second: + enabled: 0 + settings: + CPU: ARMv7 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + CPU: AnyCPU + DefaultValueInitialized: true + OS: AnyOS + - first: + Standalone: Linux64 + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: OSXUniversal + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win64 + second: + enabled: 0 + settings: + CPU: None + - first: + Windows Store Apps: WindowsStoreApps + second: + enabled: 1 + settings: + CPU: ARM64 + DontProcess: false + PlaceholderPath: + SDK: UWP + ScriptingBackend: AnyScriptingBackend + - first: + iPhone: iOS + second: + enabled: 0 + settings: + AddToEmbeddedBinaries: false + CPU: AnyCPU + CompileFlags: + FrameworkDependencies: + - first: + tvOS: tvOS + second: + enabled: 0 + settings: + CPU: AnyCPU + CompileFlags: + FrameworkDependencies: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Plugins/NanoSockets/Metro/NanoSockets.dll b/Assets/Photon/Fusion/Plugins/NanoSockets/Metro/NanoSockets.dll new file mode 100644 index 00000000..79b81092 Binary files /dev/null and b/Assets/Photon/Fusion/Plugins/NanoSockets/Metro/NanoSockets.dll differ diff --git a/Assets/Photon/Fusion/Plugins/NanoSockets/Metro/NanoSockets.dll.meta b/Assets/Photon/Fusion/Plugins/NanoSockets/Metro/NanoSockets.dll.meta new file mode 100644 index 00000000..810918e4 --- /dev/null +++ b/Assets/Photon/Fusion/Plugins/NanoSockets/Metro/NanoSockets.dll.meta @@ -0,0 +1,101 @@ +fileFormatVersion: 2 +guid: 28478daed9ce57f4989884143dfc5622 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Android: 1 + Exclude Editor: 1 + Exclude Linux64: 1 + Exclude OSXUniversal: 1 + Exclude WebGL: 1 + Exclude Win: 1 + Exclude Win64: 1 + Exclude WindowsStoreApps: 0 + Exclude iOS: 1 + Exclude tvOS: 1 + - first: + Android: Android + second: + enabled: 0 + settings: + CPU: ARMv7 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + CPU: AnyCPU + DefaultValueInitialized: true + OS: AnyOS + - first: + Standalone: Linux64 + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: OSXUniversal + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win64 + second: + enabled: 0 + settings: + CPU: None + - first: + Windows Store Apps: WindowsStoreApps + second: + enabled: 1 + settings: + CPU: AnyCPU + DontProcess: false + PlaceholderPath: + SDK: UWP + ScriptingBackend: Il2Cpp + - first: + iPhone: iOS + second: + enabled: 0 + settings: + AddToEmbeddedBinaries: false + CPU: AnyCPU + CompileFlags: + FrameworkDependencies: + - first: + tvOS: tvOS + second: + enabled: 0 + settings: + CPU: AnyCPU + CompileFlags: + FrameworkDependencies: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Plugins/NanoSockets/Metro/x64.meta b/Assets/Photon/Fusion/Plugins/NanoSockets/Metro/x64.meta new file mode 100644 index 00000000..f111daa9 --- /dev/null +++ b/Assets/Photon/Fusion/Plugins/NanoSockets/Metro/x64.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 9a3957e4470bb1a428cbefa7fab5f4c9 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Plugins/NanoSockets/Metro/x64/libnanosockets.dll b/Assets/Photon/Fusion/Plugins/NanoSockets/Metro/x64/libnanosockets.dll new file mode 100644 index 00000000..11b08fb9 Binary files /dev/null and b/Assets/Photon/Fusion/Plugins/NanoSockets/Metro/x64/libnanosockets.dll differ diff --git a/Assets/Photon/Fusion/Plugins/NanoSockets/Metro/x64/libnanosockets.dll.meta b/Assets/Photon/Fusion/Plugins/NanoSockets/Metro/x64/libnanosockets.dll.meta new file mode 100644 index 00000000..039a5da5 --- /dev/null +++ b/Assets/Photon/Fusion/Plugins/NanoSockets/Metro/x64/libnanosockets.dll.meta @@ -0,0 +1,101 @@ +fileFormatVersion: 2 +guid: 694d9b6f5f80c3849baef2972f2de1d7 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Android: 1 + Exclude Editor: 1 + Exclude Linux64: 1 + Exclude OSXUniversal: 1 + Exclude WebGL: 1 + Exclude Win: 1 + Exclude Win64: 1 + Exclude WindowsStoreApps: 0 + Exclude iOS: 1 + Exclude tvOS: 1 + - first: + Android: Android + second: + enabled: 0 + settings: + CPU: ARMv7 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + CPU: AnyCPU + DefaultValueInitialized: true + OS: AnyOS + - first: + Standalone: Linux64 + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: OSXUniversal + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win64 + second: + enabled: 0 + settings: + CPU: None + - first: + Windows Store Apps: WindowsStoreApps + second: + enabled: 1 + settings: + CPU: X64 + DontProcess: false + PlaceholderPath: + SDK: UWP + ScriptingBackend: AnyScriptingBackend + - first: + iPhone: iOS + second: + enabled: 0 + settings: + AddToEmbeddedBinaries: false + CPU: AnyCPU + CompileFlags: + FrameworkDependencies: + - first: + tvOS: tvOS + second: + enabled: 0 + settings: + CPU: AnyCPU + CompileFlags: + FrameworkDependencies: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Plugins/NanoSockets/Metro/x86.meta b/Assets/Photon/Fusion/Plugins/NanoSockets/Metro/x86.meta new file mode 100644 index 00000000..a66eed8a --- /dev/null +++ b/Assets/Photon/Fusion/Plugins/NanoSockets/Metro/x86.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4e9ad863db99c124ea5107d35e14463f +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Plugins/NanoSockets/Metro/x86/libnanosockets.dll b/Assets/Photon/Fusion/Plugins/NanoSockets/Metro/x86/libnanosockets.dll new file mode 100644 index 00000000..e882f07d Binary files /dev/null and b/Assets/Photon/Fusion/Plugins/NanoSockets/Metro/x86/libnanosockets.dll differ diff --git a/Assets/Photon/Fusion/Plugins/NanoSockets/Metro/x86/libnanosockets.dll.meta b/Assets/Photon/Fusion/Plugins/NanoSockets/Metro/x86/libnanosockets.dll.meta new file mode 100644 index 00000000..384f397b --- /dev/null +++ b/Assets/Photon/Fusion/Plugins/NanoSockets/Metro/x86/libnanosockets.dll.meta @@ -0,0 +1,101 @@ +fileFormatVersion: 2 +guid: e92559e0ecf9c5e4dacfc22deb3e27a8 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Android: 1 + Exclude Editor: 1 + Exclude Linux64: 1 + Exclude OSXUniversal: 1 + Exclude WebGL: 1 + Exclude Win: 1 + Exclude Win64: 1 + Exclude WindowsStoreApps: 0 + Exclude iOS: 1 + Exclude tvOS: 1 + - first: + Android: Android + second: + enabled: 0 + settings: + CPU: ARMv7 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + CPU: AnyCPU + DefaultValueInitialized: true + OS: AnyOS + - first: + Standalone: Linux64 + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: OSXUniversal + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win64 + second: + enabled: 0 + settings: + CPU: None + - first: + Windows Store Apps: WindowsStoreApps + second: + enabled: 1 + settings: + CPU: X86 + DontProcess: false + PlaceholderPath: + SDK: UWP + ScriptingBackend: AnyScriptingBackend + - first: + iPhone: iOS + second: + enabled: 0 + settings: + AddToEmbeddedBinaries: false + CPU: AnyCPU + CompileFlags: + FrameworkDependencies: + - first: + tvOS: tvOS + second: + enabled: 0 + settings: + CPU: AnyCPU + CompileFlags: + FrameworkDependencies: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Plugins/NanoSockets/NanoSockets.meta b/Assets/Photon/Fusion/Plugins/NanoSockets/NanoSockets.meta new file mode 100644 index 00000000..ea77e449 --- /dev/null +++ b/Assets/Photon/Fusion/Plugins/NanoSockets/NanoSockets.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c6e9c465e465a404f848eb486e1f3485 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Plugins/NanoSockets/NanoSockets/Dynamic.meta b/Assets/Photon/Fusion/Plugins/NanoSockets/NanoSockets/Dynamic.meta new file mode 100644 index 00000000..f5eb429a --- /dev/null +++ b/Assets/Photon/Fusion/Plugins/NanoSockets/NanoSockets/Dynamic.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 942340651da58e045bf0ed7faf2badc4 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Plugins/NanoSockets/NanoSockets/Dynamic/NanoSockets.dll b/Assets/Photon/Fusion/Plugins/NanoSockets/NanoSockets/Dynamic/NanoSockets.dll new file mode 100644 index 00000000..8fbe77b6 Binary files /dev/null and b/Assets/Photon/Fusion/Plugins/NanoSockets/NanoSockets/Dynamic/NanoSockets.dll differ diff --git a/Assets/Photon/Fusion/Plugins/NanoSockets/NanoSockets/Dynamic/NanoSockets.dll.meta b/Assets/Photon/Fusion/Plugins/NanoSockets/NanoSockets/Dynamic/NanoSockets.dll.meta new file mode 100644 index 00000000..4fb912db --- /dev/null +++ b/Assets/Photon/Fusion/Plugins/NanoSockets/NanoSockets/Dynamic/NanoSockets.dll.meta @@ -0,0 +1,128 @@ +fileFormatVersion: 2 +guid: 97dbb2b4c5d67894381958ef748f2a74 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Android: 0 + Exclude Editor: 0 + Exclude GameCoreScarlett: 0 + Exclude GameCoreXboxOne: 0 + Exclude Linux64: 0 + Exclude OSXUniversal: 0 + Exclude PS4: 1 + Exclude PS5: 1 + Exclude Switch: 1 + Exclude VisionOS: 1 + Exclude WebGL: 1 + Exclude Win: 0 + Exclude Win64: 0 + Exclude WindowsStoreApps: 1 + Exclude XboxOne: 1 + Exclude iOS: 1 + Exclude tvOS: 1 + - first: + Android: Android + second: + enabled: 1 + settings: + AndroidSharedLibraryType: Executable + CPU: ARMv7 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 1 + settings: + CPU: AnyCPU + DefaultValueInitialized: true + OS: AnyOS + - first: + GameCoreScarlett: GameCoreScarlett + second: + enabled: 1 + settings: {} + - first: + GameCoreXboxOne: GameCoreXboxOne + second: + enabled: 1 + settings: {} + - first: + Standalone: Linux64 + second: + enabled: 1 + settings: + CPU: AnyCPU + - first: + Standalone: OSXUniversal + second: + enabled: 1 + settings: + CPU: AnyCPU + - first: + Standalone: Win + second: + enabled: 1 + settings: + CPU: x86 + - first: + Standalone: Win64 + second: + enabled: 1 + settings: + CPU: x86_64 + - first: + VisionOS: VisionOS + second: + enabled: 0 + settings: + AddToEmbeddedBinaries: false + CPU: ARM64 + CompileFlags: + FrameworkDependencies: + - first: + Windows Store Apps: WindowsStoreApps + second: + enabled: 0 + settings: + CPU: AnyCPU + DontProcess: false + PlaceholderPath: + SDK: UWP + ScriptingBackend: Il2Cpp + - first: + iPhone: iOS + second: + enabled: 0 + settings: + AddToEmbeddedBinaries: false + CPU: AnyCPU + CompileFlags: + FrameworkDependencies: + - first: + tvOS: tvOS + second: + enabled: 0 + settings: + CPU: AnyCPU + CompileFlags: + FrameworkDependencies: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Plugins/NanoSockets/NanoSockets/Static.meta b/Assets/Photon/Fusion/Plugins/NanoSockets/NanoSockets/Static.meta new file mode 100644 index 00000000..3bcc61f9 --- /dev/null +++ b/Assets/Photon/Fusion/Plugins/NanoSockets/NanoSockets/Static.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 362a7d56bc0569743b35037bb8131c8d +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Plugins/NanoSockets/NanoSockets/Static/NanoSockets.dll b/Assets/Photon/Fusion/Plugins/NanoSockets/NanoSockets/Static/NanoSockets.dll new file mode 100644 index 00000000..6e441f2c Binary files /dev/null and b/Assets/Photon/Fusion/Plugins/NanoSockets/NanoSockets/Static/NanoSockets.dll differ diff --git a/Assets/Photon/Fusion/Plugins/NanoSockets/NanoSockets/Static/NanoSockets.dll.meta b/Assets/Photon/Fusion/Plugins/NanoSockets/NanoSockets/Static/NanoSockets.dll.meta new file mode 100644 index 00000000..8ae1ed7b --- /dev/null +++ b/Assets/Photon/Fusion/Plugins/NanoSockets/NanoSockets/Static/NanoSockets.dll.meta @@ -0,0 +1,136 @@ +fileFormatVersion: 2 +guid: d42594000bed8de429fd22c181be00ab +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Android: 1 + Exclude Editor: 1 + Exclude Linux64: 1 + Exclude OSXUniversal: 1 + Exclude PS4: 0 + Exclude PS5: 0 + Exclude Ounce: 0 + Exclude Switch: 0 + Exclude VisionOS: 0 + Exclude WebGL: 1 + Exclude Win: 1 + Exclude Win64: 1 + Exclude WindowsStoreApps: 1 + Exclude iOS: 0 + Exclude tvOS: 1 + - first: + Android: Android + second: + enabled: 0 + settings: + AndroidSharedLibraryType: Executable + CPU: ARMv7 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + CPU: AnyCPU + DefaultValueInitialized: true + OS: AnyOS + - first: + Nintendo Switch: Ounce + second: + enabled: 1 + settings: {} + - first: + Nintendo Switch: Switch + second: + enabled: 1 + settings: {} + - first: + PS4: PS4 + second: + enabled: 1 + settings: {} + - first: + PS5: PS5 + second: + enabled: 1 + settings: {} + - first: + Standalone: Linux64 + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: OSXUniversal + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win64 + second: + enabled: 0 + settings: + CPU: None + - first: + VisionOS: VisionOS + second: + enabled: 1 + settings: + AddToEmbeddedBinaries: false + CPU: ARM64 + CompileFlags: + FrameworkDependencies: + - first: + Windows Store Apps: WindowsStoreApps + second: + enabled: 0 + settings: + CPU: AnyCPU + DontProcess: false + PlaceholderPath: + SDK: UWP + ScriptingBackend: Il2Cpp + - first: + iPhone: iOS + second: + enabled: 1 + settings: + AddToEmbeddedBinaries: false + CPU: AnyCPU + CompileFlags: + FrameworkDependencies: + - first: + tvOS: tvOS + second: + enabled: 0 + settings: + CPU: AnyCPU + CompileFlags: + FrameworkDependencies: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Plugins/NanoSockets/WebGL.meta b/Assets/Photon/Fusion/Plugins/NanoSockets/WebGL.meta new file mode 100644 index 00000000..7a970fea --- /dev/null +++ b/Assets/Photon/Fusion/Plugins/NanoSockets/WebGL.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 27dc9a9c7cb0ada46a3afb83d3b5e237 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Plugins/NanoSockets/WebGL/NanoSockets.dll b/Assets/Photon/Fusion/Plugins/NanoSockets/WebGL/NanoSockets.dll new file mode 100644 index 00000000..9d0f6330 Binary files /dev/null and b/Assets/Photon/Fusion/Plugins/NanoSockets/WebGL/NanoSockets.dll differ diff --git a/Assets/Photon/Fusion/Plugins/NanoSockets/WebGL/NanoSockets.dll.meta b/Assets/Photon/Fusion/Plugins/NanoSockets/WebGL/NanoSockets.dll.meta new file mode 100644 index 00000000..f1a52d69 --- /dev/null +++ b/Assets/Photon/Fusion/Plugins/NanoSockets/WebGL/NanoSockets.dll.meta @@ -0,0 +1,92 @@ +fileFormatVersion: 2 +guid: bd2e65f8c0aea6e42850309bca2213c1 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Android: 1 + Exclude Editor: 1 + Exclude GameCoreScarlett: 1 + Exclude GameCoreXboxOne: 1 + Exclude Linux64: 1 + Exclude OSXUniversal: 1 + Exclude PS5: 1 + Exclude Switch: 1 + Exclude WebGL: 0 + Exclude Win: 1 + Exclude Win64: 1 + Exclude WindowsStoreApps: 1 + Exclude XboxOne: 1 + - first: + Android: Android + second: + enabled: 0 + settings: + CPU: ARMv7 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + CPU: AnyCPU + DefaultValueInitialized: true + OS: AnyOS + - first: + Standalone: Linux64 + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: OSXUniversal + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win64 + second: + enabled: 0 + settings: + CPU: None + - first: + WebGL: WebGL + second: + enabled: 1 + settings: {} + - first: + Windows Store Apps: WindowsStoreApps + second: + enabled: 0 + settings: + CPU: AnyCPU + DontProcess: false + PlaceholderPath: + SDK: AnySDK + ScriptingBackend: AnyScriptingBackend + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Plugins/NanoSockets/Windows.meta b/Assets/Photon/Fusion/Plugins/NanoSockets/Windows.meta new file mode 100644 index 00000000..359d5f8e --- /dev/null +++ b/Assets/Photon/Fusion/Plugins/NanoSockets/Windows.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 098df69438c79134f81d244d16bbd926 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Plugins/NanoSockets/Windows/nanosockets.dll b/Assets/Photon/Fusion/Plugins/NanoSockets/Windows/nanosockets.dll new file mode 100644 index 00000000..5e6440eb Binary files /dev/null and b/Assets/Photon/Fusion/Plugins/NanoSockets/Windows/nanosockets.dll differ diff --git a/Assets/Photon/Fusion/Plugins/NanoSockets/Windows/nanosockets.dll.meta b/Assets/Photon/Fusion/Plugins/NanoSockets/Windows/nanosockets.dll.meta new file mode 100644 index 00000000..cc51198a --- /dev/null +++ b/Assets/Photon/Fusion/Plugins/NanoSockets/Windows/nanosockets.dll.meta @@ -0,0 +1,92 @@ +fileFormatVersion: 2 +guid: 7b3f94104f169cc4e84342c38a1da2e3 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Android: 1 + Exclude Editor: 0 + Exclude Linux64: 0 + Exclude OSXUniversal: 0 + Exclude WebGL: 1 + Exclude Win: 0 + Exclude Win64: 0 + Exclude WindowsStoreApps: 1 + Exclude iOS: 1 + - first: + Android: Android + second: + enabled: 0 + settings: + CPU: ARMv7 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 1 + settings: + CPU: AnyCPU + DefaultValueInitialized: true + OS: Windows + - first: + Standalone: Linux64 + second: + enabled: 1 + settings: + CPU: None + - first: + Standalone: OSXUniversal + second: + enabled: 1 + settings: + CPU: None + - first: + Standalone: Win + second: + enabled: 1 + settings: + CPU: x86 + - first: + Standalone: Win64 + second: + enabled: 1 + settings: + CPU: x86_64 + - first: + Windows Store Apps: WindowsStoreApps + second: + enabled: 0 + settings: + CPU: AnyCPU + DontProcess: false + PlaceholderPath: + SDK: AnySDK + ScriptingBackend: AnyScriptingBackend + - first: + iPhone: iOS + second: + enabled: 0 + settings: + AddToEmbeddedBinaries: false + CPU: AnyCPU + CompileFlags: + FrameworkDependencies: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Plugins/NanoSockets/iOS.meta b/Assets/Photon/Fusion/Plugins/NanoSockets/iOS.meta new file mode 100644 index 00000000..242182c0 --- /dev/null +++ b/Assets/Photon/Fusion/Plugins/NanoSockets/iOS.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a13941e2ab2c4ac46b0e6195bd37cb0a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Plugins/NanoSockets/iOS/libnanosockets.a b/Assets/Photon/Fusion/Plugins/NanoSockets/iOS/libnanosockets.a new file mode 100644 index 00000000..59d62c98 Binary files /dev/null and b/Assets/Photon/Fusion/Plugins/NanoSockets/iOS/libnanosockets.a differ diff --git a/Assets/Photon/Fusion/Plugins/NanoSockets/iOS/libnanosockets.a.meta b/Assets/Photon/Fusion/Plugins/NanoSockets/iOS/libnanosockets.a.meta new file mode 100644 index 00000000..7942b50c --- /dev/null +++ b/Assets/Photon/Fusion/Plugins/NanoSockets/iOS/libnanosockets.a.meta @@ -0,0 +1,80 @@ +fileFormatVersion: 2 +guid: ce1995280bfb011488cdf6e584ba46bc +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Android: 1 + Exclude Editor: 1 + Exclude Linux64: 1 + Exclude OSXUniversal: 1 + Exclude Win: 1 + Exclude Win64: 1 + Exclude iOS: 0 + - first: + Android: Android + second: + enabled: 0 + settings: + CPU: ARMv7 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + CPU: AnyCPU + DefaultValueInitialized: true + OS: AnyOS + - first: + Standalone: Linux64 + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: OSXUniversal + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win64 + second: + enabled: 0 + settings: + CPU: None + - first: + iPhone: iOS + second: + enabled: 1 + settings: + AddToEmbeddedBinaries: false + CPU: AnyCPU + CompileFlags: + FrameworkDependencies: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Plugins/NanoSockets/libnanosockets_LICENSE.txt b/Assets/Photon/Fusion/Plugins/NanoSockets/libnanosockets_LICENSE.txt new file mode 100644 index 00000000..56ff4006 --- /dev/null +++ b/Assets/Photon/Fusion/Plugins/NanoSockets/libnanosockets_LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Stanislav Denisov (nxrighthere@gmail.com) + +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. \ No newline at end of file diff --git a/Assets/Photon/Fusion/Plugins/NanoSockets/libnanosockets_LICENSE.txt.meta b/Assets/Photon/Fusion/Plugins/NanoSockets/libnanosockets_LICENSE.txt.meta new file mode 100644 index 00000000..2d5897c8 --- /dev/null +++ b/Assets/Photon/Fusion/Plugins/NanoSockets/libnanosockets_LICENSE.txt.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 2007cbe994ec1441bb4dc0783ee8d1f5 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Plugins/NanoSockets/macOS.meta b/Assets/Photon/Fusion/Plugins/NanoSockets/macOS.meta new file mode 100644 index 00000000..438ab177 --- /dev/null +++ b/Assets/Photon/Fusion/Plugins/NanoSockets/macOS.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 0177b91b525310f4a9d4c318c2a15793 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Plugins/NanoSockets/macOS/libnanosockets.dylib b/Assets/Photon/Fusion/Plugins/NanoSockets/macOS/libnanosockets.dylib new file mode 100644 index 00000000..5d720b08 Binary files /dev/null and b/Assets/Photon/Fusion/Plugins/NanoSockets/macOS/libnanosockets.dylib differ diff --git a/Assets/Photon/Fusion/Plugins/NanoSockets/macOS/libnanosockets.dylib.meta b/Assets/Photon/Fusion/Plugins/NanoSockets/macOS/libnanosockets.dylib.meta new file mode 100644 index 00000000..d94b511a --- /dev/null +++ b/Assets/Photon/Fusion/Plugins/NanoSockets/macOS/libnanosockets.dylib.meta @@ -0,0 +1,74 @@ +fileFormatVersion: 2 +guid: 2c0df08904df4144ebd5a1e9b53c4b34 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Editor: 0 + Exclude Linux64: 1 + Exclude OSXUniversal: 0 + Exclude Win: 1 + Exclude Win64: 1 + Exclude WindowsStoreApps: 1 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 1 + settings: + CPU: AnyCPU + DefaultValueInitialized: true + OS: OSX + - first: + Standalone: Linux64 + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: OSXUniversal + second: + enabled: 1 + settings: + CPU: AnyCPU + - first: + Standalone: Win + second: + enabled: 0 + settings: + CPU: x86 + - first: + Standalone: Win64 + second: + enabled: 0 + settings: + CPU: x86_64 + - first: + Windows Store Apps: WindowsStoreApps + second: + enabled: 0 + settings: + CPU: AnyCPU + DontProcess: false + PlaceholderPath: + SDK: AnySDK + ScriptingBackend: AnyScriptingBackend + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Plugins/NanoSockets/tvOS.meta b/Assets/Photon/Fusion/Plugins/NanoSockets/tvOS.meta new file mode 100644 index 00000000..5560e528 --- /dev/null +++ b/Assets/Photon/Fusion/Plugins/NanoSockets/tvOS.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7959109194be73645bf41f70f3d13f15 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Plugins/NanoSockets/tvOS/libnanosockets.a b/Assets/Photon/Fusion/Plugins/NanoSockets/tvOS/libnanosockets.a new file mode 100644 index 00000000..85ff81c4 Binary files /dev/null and b/Assets/Photon/Fusion/Plugins/NanoSockets/tvOS/libnanosockets.a differ diff --git a/Assets/Photon/Fusion/Plugins/NanoSockets/tvOS/libnanosockets.a.meta b/Assets/Photon/Fusion/Plugins/NanoSockets/tvOS/libnanosockets.a.meta new file mode 100644 index 00000000..d95917b9 --- /dev/null +++ b/Assets/Photon/Fusion/Plugins/NanoSockets/tvOS/libnanosockets.a.meta @@ -0,0 +1,101 @@ +fileFormatVersion: 2 +guid: 9a350697ad5cb2e4d9c7826a1ead49f3 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Android: 1 + Exclude Editor: 1 + Exclude Linux64: 1 + Exclude OSXUniversal: 1 + Exclude WebGL: 1 + Exclude Win: 1 + Exclude Win64: 1 + Exclude WindowsStoreApps: 1 + Exclude iOS: 1 + Exclude tvOS: 0 + - first: + Android: Android + second: + enabled: 0 + settings: + CPU: ARMv7 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + CPU: AnyCPU + DefaultValueInitialized: true + OS: AnyOS + - first: + Standalone: Linux64 + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: OSXUniversal + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win64 + second: + enabled: 0 + settings: + CPU: None + - first: + Windows Store Apps: WindowsStoreApps + second: + enabled: 0 + settings: + CPU: AnyCPU + DontProcess: false + PlaceholderPath: + SDK: AnySDK + ScriptingBackend: AnyScriptingBackend + - first: + iPhone: iOS + second: + enabled: 0 + settings: + AddToEmbeddedBinaries: false + CPU: AnyCPU + CompileFlags: + FrameworkDependencies: + - first: + tvOS: tvOS + second: + enabled: 1 + settings: + CPU: ARM64 + CompileFlags: + FrameworkDependencies: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Plugins/NanoSockets/visionOS.meta b/Assets/Photon/Fusion/Plugins/NanoSockets/visionOS.meta new file mode 100644 index 00000000..4f06b780 --- /dev/null +++ b/Assets/Photon/Fusion/Plugins/NanoSockets/visionOS.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8c90beaf793e6fe4d938c3d864369356 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Plugins/NanoSockets/visionOS/libnanosockets.a b/Assets/Photon/Fusion/Plugins/NanoSockets/visionOS/libnanosockets.a new file mode 100644 index 00000000..8f7f4f9b Binary files /dev/null and b/Assets/Photon/Fusion/Plugins/NanoSockets/visionOS/libnanosockets.a differ diff --git a/Assets/Photon/Fusion/Plugins/NanoSockets/visionOS/libnanosockets.a.meta b/Assets/Photon/Fusion/Plugins/NanoSockets/visionOS/libnanosockets.a.meta new file mode 100644 index 00000000..fc2d43b8 --- /dev/null +++ b/Assets/Photon/Fusion/Plugins/NanoSockets/visionOS/libnanosockets.a.meta @@ -0,0 +1,100 @@ +fileFormatVersion: 2 +guid: 9cc4f9853c7fe41db81ba1322ee726af +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Android: 1 + Exclude Editor: 1 + Exclude Linux64: 1 + Exclude OSXUniversal: 1 + Exclude VisionOS: 0 + Exclude Win: 1 + Exclude Win64: 1 + Exclude iOS: 1 + Exclude tvOS: 1 + - first: + Android: Android + second: + enabled: 0 + settings: + AndroidSharedLibraryType: Executable + CPU: ARMv7 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + CPU: AnyCPU + DefaultValueInitialized: true + OS: AnyOS + - first: + Standalone: Linux64 + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: OSXUniversal + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: Win64 + second: + enabled: 0 + settings: + CPU: None + - first: + VisionOS: VisionOS + second: + enabled: 1 + settings: + AddToEmbeddedBinaries: false + CPU: AnyCPU + CompileFlags: + FrameworkDependencies: + - first: + iPhone: iOS + second: + enabled: 0 + settings: + AddToEmbeddedBinaries: false + CPU: AnyCPU + CompileFlags: + FrameworkDependencies: + - first: + tvOS: tvOS + second: + enabled: 0 + settings: + CPU: AnyCPU + CompileFlags: + FrameworkDependencies: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Resources.meta b/Assets/Photon/Fusion/Resources.meta new file mode 100644 index 00000000..614c15fc --- /dev/null +++ b/Assets/Photon/Fusion/Resources.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 49161611fa9bd5440962824dad7f18bc +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Resources/NetworkProjectConfig.fusion b/Assets/Photon/Fusion/Resources/NetworkProjectConfig.fusion new file mode 100644 index 00000000..fb3fa9d4 --- /dev/null +++ b/Assets/Photon/Fusion/Resources/NetworkProjectConfig.fusion @@ -0,0 +1,76 @@ +{ + "Version": 1, + "TypeId": "NetworkProjectConfig", + "PeerMode": 0, + "LagCompensation": { + "Enabled": false, + "HitboxBufferLengthInMs": 200, + "HitboxDefaultCapacity": 512, + "CachedStaticCollidersSize": 64 + }, + "EnqueueIncompleteSynchronousSpawns": false, + "InvokeRenderInBatchMode": true, + "NetworkIdIsObjectName": false, + "HideNetworkObjectInactivityGuard": false, + "AllowClientServerModesInWebGL": false, + "ClientsRecordFrameAndPacketTimingTraces": false, + "Simulation": { + "InputDataWordCount": 0, + "ReplicationFeatures": 1, + "InputTransferMode": 0, + "SimulationUpdateTimeMode": 0, + "PlayerCount": 10, + "TickRateSelection": { + "Client": 64, + "ServerIndex": 0, + "ClientSendIndex": 1, + "ServerSendIndex": 1 + }, + "MaxObjectDestroysSentPerPacket": 32, + "EnableExperimentalPacketLossRecovery": false, + "EnableAdaptivePacketFragmentation": false + }, + "Network": { + "ConnectionTimeout": 10.0, + "ConnectionShutdownTime": 1.0, + "ReliableDataTransferModes": 3 + }, + "HostMigration": { + "EnableAutoUpdate": false, + "UpdateDelay": 10 + }, + "EncryptionConfig": { + "EnableEncryption": false + }, + "NetworkConditions": { + "Enabled": false, + "DelayShape": 0, + "DelayMin": 0.15, + "DelayMax": 0.15, + "DelayPeriod": 0.0, + "DelayThreshold": 0.0, + "AdditionalJitter": 0.05, + "LossChanceShape": 0, + "LossChanceMin": 0.05, + "LossChanceMax": 0.05, + "LossChanceThreshold": 0.0, + "LossChancePeriod": 0.0, + "AdditionalLoss": 0.0 + }, + "Heap": { + "PageShift": 15, + "PageCount": 256, + "GlobalsSize": 0 + }, + "AssembliesToWeave": [ + "Fusion.Unity", + "Assembly-CSharp", + "Assembly-CSharp-firstpass", + "Fusion.Addons.Physics", + "Fusion.Addons.FSM" + ], + "UseSerializableDictionary": true, + "NullChecksForNetworkedProperties": true, + "CheckRpcAttributeUsage": false, + "CheckNetworkedPropertiesBeingEmpty": false +} \ No newline at end of file diff --git a/Assets/Photon/Fusion/Resources/NetworkProjectConfig.fusion.meta b/Assets/Photon/Fusion/Resources/NetworkProjectConfig.fusion.meta new file mode 100644 index 00000000..aeb50a2a --- /dev/null +++ b/Assets/Photon/Fusion/Resources/NetworkProjectConfig.fusion.meta @@ -0,0 +1,15 @@ +fileFormatVersion: 2 +guid: 6c9e758573fd4874d9b8126f640da2f6 +labels: +- FusionDefaultGlobal +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 11500000, guid: 66a64a17d0b40f34f9224317a5a84bf2, type: 3} + PrefabOptions: + UnloadPrefabOnReleasingLastInstance: 0 + UnloadUnusedPrefabsOnShutdown: 0 diff --git a/Assets/Photon/Fusion/Resources/PhotonAppSettings.asset b/Assets/Photon/Fusion/Resources/PhotonAppSettings.asset new file mode 100644 index 00000000..991797ed --- /dev/null +++ b/Assets/Photon/Fusion/Resources/PhotonAppSettings.asset @@ -0,0 +1,30 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 1878438611, guid: 7de3b8b9e1263ad479e2d0c4261b7646, type: 3} + m_Name: PhotonAppSettings + m_EditorClassIdentifier: Fusion.Realtime.dll::Fusion.Photon.Realtime.PhotonAppSettings + AppSettings: + AppIdFusion: 797886dc-1c98-473e-ab5d-d69d3ce312b2 + AppIdChat: + AppIdVoice: + AppVersion: + UseNameServer: 1 + FixedRegion: + Server: + Port: 0 + ProxyServer: + Protocol: 0 + EnableProtocolFallback: 1 + AuthMode: 0 + EnableLobbyStatistics: 0 + encryptionMode: 0 + emptyRoomTtl: 0 diff --git a/Assets/Photon/Fusion/Resources/PhotonAppSettings.asset.meta b/Assets/Photon/Fusion/Resources/PhotonAppSettings.asset.meta new file mode 100644 index 00000000..79606953 --- /dev/null +++ b/Assets/Photon/Fusion/Resources/PhotonAppSettings.asset.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: cbcd3053cb7c60242a8e55d05a4d6aee +labels: +- FusionDefaultGlobal +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Runtime.meta b/Assets/Photon/Fusion/Runtime.meta new file mode 100644 index 00000000..6874b614 --- /dev/null +++ b/Assets/Photon/Fusion/Runtime.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ebd08c30de428c3408dbca64cbbd4e60 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Runtime/Fusion.Unity.AssemblyAttributes.cs b/Assets/Photon/Fusion/Runtime/Fusion.Unity.AssemblyAttributes.cs new file mode 100644 index 00000000..e742d0d9 --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/Fusion.Unity.AssemblyAttributes.cs @@ -0,0 +1,18 @@ +#if !FUSION_DEV + +#region Assets/Photon/Fusion/Runtime/AssemblyAttributes/FusionAssemblyAttributes.Common.cs + +// merged AssemblyAttributes + +#region RegisterResourcesLoader.cs + +// register a default loader; it will attempt to load the asset from their default paths if they happen to be Resources +[assembly: Fusion.FusionGlobalScriptableObjectResource(typeof(Fusion.FusionGlobalScriptableObject), Order = 2000, AllowFallback = true)] + +#endregion + + + +#endregion + +#endif diff --git a/Assets/Photon/Fusion/Runtime/Fusion.Unity.AssemblyAttributes.cs.meta b/Assets/Photon/Fusion/Runtime/Fusion.Unity.AssemblyAttributes.cs.meta new file mode 100644 index 00000000..64592d59 --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/Fusion.Unity.AssemblyAttributes.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 87e3886742c1ae04096a83c7188f1d9b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Runtime/Fusion.Unity.asmdef b/Assets/Photon/Fusion/Runtime/Fusion.Unity.asmdef new file mode 100644 index 00000000..74651ac1 --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/Fusion.Unity.asmdef @@ -0,0 +1,39 @@ +{ + "name": "Fusion.Unity", + "rootNamespace": "Fusion", + "references": [ + "GUID:9e24947de15b9834991c9d8411ea37cf", + "GUID:84651a3751eca9349aac36a66bba901b", + "GUID:2665a8d13d1b3f18800f46e256720795" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": true, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [ + { + "name": "com.unity.burst", + "expression": "", + "define": "FUSION_BURST" + }, + { + "name": "com.unity.addressables", + "expression": "1.0", + "define": "FUSION_ENABLE_ADDRESSABLES" + }, + { + "name": "com.unity.addressables", + "expression": "1.20", + "define": "FUSION_ENABLE_ADDRESSABLES_LOCAL_PHYSICS" + }, + { + "name": "com.unity.multiplayer.playmode", + "expression": "0.6", + "define": "FUSION_ENABLE_MPPM" + } + ], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Assets/Photon/Fusion/Runtime/Fusion.Unity.asmdef.meta b/Assets/Photon/Fusion/Runtime/Fusion.Unity.asmdef.meta new file mode 100644 index 00000000..1dc35122 --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/Fusion.Unity.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 1baf1ffff1031e948b3b228ab3453251 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Runtime/Fusion.Unity.cs b/Assets/Photon/Fusion/Runtime/Fusion.Unity.cs new file mode 100644 index 00000000..9cb814a7 --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/Fusion.Unity.cs @@ -0,0 +1,4315 @@ +#if !FUSION_DEV + +#region Assets/Photon/Fusion/Runtime/FusionAssetSource.Common.cs + +// merged AssetSource + +#region NetworkAssetSourceAddressable.cs + +#if (FUSION_ADDRESSABLES || FUSION_ENABLE_ADDRESSABLES) && !FUSION_DISABLE_ADDRESSABLES +namespace Fusion { + using System; + using UnityEngine; + using UnityEngine.AddressableAssets; + using UnityEngine.ResourceManagement.AsyncOperations; + using static InternalLogStreams; + + /// + /// An Addressables-based implementation of the asset source pattern. The asset is loaded from the Addressables system. + /// + /// + [Serializable] + public partial class NetworkAssetSourceAddressable where T : UnityEngine.Object { + + /// + [Obsolete("Use RuntimeKey instead")] + public AssetReference Address { + get { + if (string.IsNullOrEmpty(RuntimeKey)) { + return default; + } + return FusionAddressablesUtils.CreateAssetReference(RuntimeKey); + } + set { + if (value.IsValid()) { + RuntimeKey = (string)value.RuntimeKey; + } else { + RuntimeKey = string.Empty; + } + } + } + + /// + /// Addressables runtime key. Can be used in any form Addressables supports, such as asset name, label, or address. + /// + [UnityAddressablesRuntimeKey] + public string RuntimeKey; + + [NonSerialized] + private int _acquireCount; + + [NonSerialized] + private AsyncOperationHandle _op; + + /// + public void Acquire(bool synchronous) { + if (_acquireCount == 0) { + LoadInternal(synchronous); + } + _acquireCount++; + } + + /// + public void Release() { + if (_acquireCount <= 0) { + throw new Exception("Asset is not loaded"); + } + if (--_acquireCount == 0) { + UnloadInternal(); + } + } + + /// + public bool IsCompleted => _op.IsDone; + + /// + public T WaitForResult() { + Assert.Check(_op.IsValid()); + if (!_op.IsDone) { + try { + _op.WaitForCompletion(); + } catch (Exception e) when (!Application.isPlaying && typeof(Exception) == e.GetType()) { + LogError?.Log($"An exception was thrown when loading asset: {RuntimeKey}; since this method " + + $"was called from the editor, it may be due to the fact that Addressables don't have edit-time load support. Please use EditorInstance instead."); + throw; + } + } + + if (_op.OperationException != null) { + throw new InvalidOperationException($"Failed to load asset: {RuntimeKey}", _op.OperationException); + } + + Assert.Check(_op.Result != null, "_op.Result != null"); + return ValidateResult(_op.Result); + } + + private void LoadInternal(bool synchronous) { + Assert.Check(!_op.IsValid()); + + _op = Addressables.LoadAssetAsync(RuntimeKey); + if (!_op.IsValid()) { + throw new Exception($"Failed to load asset: {RuntimeKey}"); + } + if (_op.Status == AsyncOperationStatus.Failed) { + throw new Exception($"Failed to load asset: {RuntimeKey}", _op.OperationException); + } + + if (synchronous) { + _op.WaitForCompletion(); + } + } + + private void UnloadInternal() { + if (_op.IsValid()) { + var op = _op; + _op = default; + Addressables.Release(op); + } + } + + private T ValidateResult(object result) { + if (result == null) { + throw new InvalidOperationException($"Failed to load asset: {RuntimeKey}; asset is null"); + } + if (typeof(T).IsSubclassOf(typeof(Component))) { + if (result is GameObject gameObject == false) { + throw new InvalidOperationException($"Failed to load asset: {RuntimeKey}; asset is not a GameObject, but a {result.GetType()}"); + } + + var component = ((GameObject)result).GetComponent(); + if (!component) { + throw new InvalidOperationException($"Failed to load asset: {RuntimeKey}; asset does not contain component {typeof(T)}"); + } + + return component; + } + + if (result is T asset) { + return asset; + } + + throw new InvalidOperationException($"Failed to load asset: {RuntimeKey}; asset is not of type {typeof(T)}, but {result.GetType()}"); + } + + /// + public string Description => "RuntimeKey: " + RuntimeKey; + +#if UNITY_EDITOR + /// + public T EditorInstance => (T)FusionAddressablesUtils.LoadEditorInstance(RuntimeKey); +#endif + } +} +#endif + +#endregion + + +#region NetworkAssetSourceResource.cs + +namespace Fusion { + using System; + using System.Runtime.ExceptionServices; + using UnityEngine; + using Object = UnityEngine.Object; + using UnityResources = UnityEngine.Resources; + + /// + /// Resources-based implementation of the asset source pattern. + /// + /// + [Serializable] + public partial class NetworkAssetSourceResource where T : UnityEngine.Object { + + /// + /// Resource path. Note that this is a Unity resource path, not a file path. + /// + [UnityResourcePath(typeof(Object))] + public string ResourcePath; + /// + /// Sub-object name. If empty, the main object is loaded. + /// + public string SubObjectName; + + [NonSerialized] + private object _state; + [NonSerialized] + private int _acquireCount; + + /// + /// Loads the asset. In synchronous mode, the asset is loaded immediately. In asynchronous mode, the asset is loaded in the background. + /// + /// + public void Acquire(bool synchronous) { + if (_acquireCount == 0) { + LoadInternal(synchronous); + } + _acquireCount++; + } + + /// + /// Unloads the asset. If the asset is not loaded, an exception is thrown. If the asset is loaded multiple times, it is only + /// unloaded when the last acquire is released. + /// + /// + public void Release() { + if (_acquireCount <= 0) { + throw new Exception("Asset is not loaded"); + } + if (--_acquireCount == 0) { + UnloadInternal(); + } + } + + /// + /// Returns if the asset is loaded. + /// + public bool IsCompleted { + get { + if (_state == null) { + // hasn't started + return false; + } + + if (_state is ResourceRequest asyncOp && !asyncOp.isDone) { + // still loading, wait + return false; + } + + return true; + } + } + + /// + /// Blocks until the asset is loaded. If the asset is not loaded, an exception is thrown. + /// + /// The loaded asset + public T WaitForResult() { + Assert.Check(_state != null); + if (_state is ResourceRequest asyncOp) { + if (asyncOp.isDone) { + FinishAsyncOp(asyncOp); + } else { + // just load synchronously, then pass through + _state = null; + LoadInternal(synchronous: true); + } + } + + if (_state == null) { + throw new InvalidOperationException($"Failed to load asset {typeof(T)}: {ResourcePath}[{SubObjectName}]. Asset is null."); + } + + if (_state is T asset) { + return asset; + } + + if (_state is ExceptionDispatchInfo exception) { + exception.Throw(); + throw new NotSupportedException(); + } + + throw new InvalidOperationException($"Failed to load asset {typeof(T)}: {ResourcePath}, SubObjectName: {SubObjectName}"); + } + + private void FinishAsyncOp(ResourceRequest asyncOp) { + try { + var asset = string.IsNullOrEmpty(SubObjectName) ? asyncOp.asset : LoadNamedResource(ResourcePath, SubObjectName); + if (asset) { + _state = asset; + } else { + throw new InvalidOperationException($"Missing Resource: {ResourcePath}, SubObjectName: {SubObjectName}"); + } + } catch (Exception ex) { + _state = ExceptionDispatchInfo.Capture(ex); + } + } + + private static T LoadNamedResource(string resoucePath, string subObjectName) { + var assets = UnityResources.LoadAll(resoucePath); + + for (var i = 0; i < assets.Length; ++i) { + var asset = assets[i]; + if (string.Equals(asset.name, subObjectName, StringComparison.Ordinal)) { + return asset; + } + } + + return null; + } + + private void LoadInternal(bool synchronous) { + Assert.Check(_state == null); + try { + if (synchronous) { + _state = string.IsNullOrEmpty(SubObjectName) ? UnityResources.Load(ResourcePath) : LoadNamedResource(ResourcePath, SubObjectName); + } else { + _state = UnityResources.LoadAsync(ResourcePath); + } + + if (_state == null) { + _state = new InvalidOperationException($"Missing Resource: {ResourcePath}, SubObjectName: {SubObjectName}"); + } + } catch (Exception ex) { + _state = ExceptionDispatchInfo.Capture(ex); + } + } + + private void UnloadInternal() { + if (_state is ResourceRequest asyncOp) { + asyncOp.completed += op => { + // unload stuff + }; + } else if (_state is Object) { + // unload stuff + } + + _state = null; + } + + /// + /// The description of the asset source. Used for debugging. + /// + public string Description => $"Resource: {ResourcePath}{(!string.IsNullOrEmpty(SubObjectName) ? $"[{SubObjectName}]" : "")}"; + +#if UNITY_EDITOR + /// + /// Returns the asset instance for Editor purposes. Does not call . + /// + public T EditorInstance => string.IsNullOrEmpty(SubObjectName) ? UnityResources.Load(ResourcePath) : LoadNamedResource(ResourcePath, SubObjectName); +#endif + } +} + +#endregion + + +#region NetworkAssetSourceStatic.cs + +namespace Fusion { + using System; + using UnityEngine.Serialization; + + /// + /// Hard reference-based implementation of the asset source pattern. This asset source forms a hard reference to the asset and never releases it. + /// This type is meant to be used at runtime. For edit-time, prefer , as it delays + /// actually loading the asset, improving the editor performance. + /// + /// + [Serializable] + public partial class NetworkAssetSourceStatic where T : UnityEngine.Object { + + /// + /// The asset reference. Can point to an asset or to a runtime-created object. + /// + [FormerlySerializedAs("Prefab")] + public T Object; + + /// + [Obsolete("Use Asset instead")] + public T Prefab { + get => Object; + set => Object = value; + } + + /// + /// Returns . + /// + public bool IsCompleted => true; + + /// + /// Does nothing, the asset is always loaded. + /// + public void Acquire(bool synchronous) { + // do nothing + } + + /// + /// Does nothing, the asset is always loaded. + /// + public void Release() { + // do nothing + } + + /// + /// Returns or throws an exception if the reference is missing. + /// + public T WaitForResult() { + if (Object == null) { + throw new InvalidOperationException("Missing static reference"); + } + + return Object; + } + + /// + public string Description { + get { + if (Object) { +#if UNITY_EDITOR + if (UnityEditor.AssetDatabase.TryGetGUIDAndLocalFileIdentifier(Object, out var guid, out long fileID)) { + return $"Static: {guid}, fileID: {fileID}"; + } +#endif + return "Static: " + Object; + } else { + return "Static: (null)"; + } + } + } + +#if UNITY_EDITOR + /// + /// Returns . + /// + public T EditorInstance => Object; +#endif + } +} + +#endregion + + +#region NetworkAssetSourceStaticLazy.cs + +namespace Fusion { + using System; + using UnityEngine; + using UnityEngine.Serialization; + + /// + /// An edit-time optimised version of , taking advantage of Unity's lazy loading of + /// assets. At runtime, this type behaves exactly like , except for the inability + /// to use runtime-created objects. + /// + /// + [Serializable] + public partial class NetworkAssetSourceStaticLazy where T : UnityEngine.Object { + + /// + /// The asset reference. Can only point to an asset, runtime-created objects will not work. + /// + [FormerlySerializedAs("Prefab")] + public LazyLoadReference Object; + + /// + [Obsolete("Use Object instead")] + public LazyLoadReference Prefab { + get => Object; + set => Object = value; + } + + /// + public bool IsCompleted => true; + + /// + public void Acquire(bool synchronous) { + // do nothing + } + + /// + public void Release() { + // do nothing + } + + /// + public T WaitForResult() { + if (Object.asset == null) { + throw new InvalidOperationException("Missing static reference"); + } + + return Object.asset; + } + + /// + public string Description { + get { + if (Object.isBroken) { + return "Static: (broken)"; + } else if (Object.isSet) { +#if UNITY_EDITOR + if (UnityEditor.AssetDatabase.TryGetGUIDAndLocalFileIdentifier(Object, out var guid, out long fileID)) { + return $"Static: {guid}, fileID: {fileID}"; + } +#endif + return "Static: " + Object.asset; + } else { + return "Static: (null)"; + } + } + } + +#if UNITY_EDITOR + /// + public T EditorInstance => Object.asset; +#endif + } +} + +#endregion + + +#region FusionGlobalScriptableObjectAddressAttribute.cs + +namespace Fusion { + using System; + using UnityEngine.Scripting; +#if (FUSION_ADDRESSABLES || FUSION_ENABLE_ADDRESSABLES) && !FUSION_DISABLE_ADDRESSABLES + using UnityEngine.AddressableAssets; + using UnityEngine.ResourceManagement.AsyncOperations; +#endif + using static InternalLogStreams; + + /// + /// If applied at the assembly level, allows to be loaded with Addressables. + /// + [Preserve] + public class FusionGlobalScriptableObjectAddressAttribute : FusionGlobalScriptableObjectSourceAttribute { + /// The type this attribute will attempt to load. + /// The address to load from. + public FusionGlobalScriptableObjectAddressAttribute(Type objectType, string address) : base(objectType) { + Address = address; + } + + /// + /// The address to load from. + /// + public string Address { get; } + + /// + /// Loads the asset from the . Uses WaitForCompletion internally, so platforms that do not support it need + /// to preload the address prior to loading. + /// + public override FusionGlobalScriptableObjectLoadResult Load(Type type) { +#if (FUSION_ADDRESSABLES || FUSION_ENABLE_ADDRESSABLES) && !FUSION_DISABLE_ADDRESSABLES + Assert.Check(!string.IsNullOrEmpty(Address)); + + var op = Addressables.LoadAssetAsync(Address); + var instance = op.WaitForCompletion(); + if (op.Status == AsyncOperationStatus.Succeeded) { + Assert.Check(instance); + return new (instance, x => Addressables.Release(op)); + } + + + LogTrace?.Log($"Failed to load addressable at address {Address} for type {type.FullName}: {op.OperationException}"); + return default; +#else + LogTrace?.Log($"Addressables are not enabled. Unable to load addressable for {type.FullName}"); + return default; +#endif + } + } +} + +#endregion + + +#region FusionGlobalScriptableObjectResourceAttribute.cs + +namespace Fusion { + using System; + using System.IO; + using System.Reflection; + using UnityEngine; + using UnityEngine.Scripting; + using Object = UnityEngine.Object; + using static InternalLogStreams; + + /// + /// If applied at the assembly level, allows to be loaded with Resources. + /// There is a default registration for this attribute, which attempts to load the asset from Resources using path from + /// . + /// + [Preserve] + public class FusionGlobalScriptableObjectResourceAttribute : FusionGlobalScriptableObjectSourceAttribute { + /// The type this attribute will attempt to load. + /// Resources path or /empty if path from + /// is to be used. + public FusionGlobalScriptableObjectResourceAttribute(Type objectType, string resourcePath = "") : base(objectType) { + ResourcePath = resourcePath; + } + + /// + /// Path in Resources. + /// + public string ResourcePath { get; } + /// + /// If loaded in the editor, should the result be instantiated instead of returning the asset itself? The default is . + /// + public bool InstantiateIfLoadedInEditor { get; set; } = true; + + /// + /// Loads the asset from Resources synchronously. + /// + public override FusionGlobalScriptableObjectLoadResult Load(Type type) { + + var attribute = type.GetCustomAttribute(); + Assert.Check(attribute != null); + + string resourcePath; + if (string.IsNullOrEmpty(ResourcePath)) { + string defaultAssetPath = attribute.DefaultPath; + var indexOfResources = defaultAssetPath.LastIndexOf("/Resources/", StringComparison.OrdinalIgnoreCase); + if (indexOfResources < 0) { + LogTrace?.Log($"The default path {defaultAssetPath} does not contain a /Resources/ folder. Unable to load resource for {type.FullName}."); + return default; + } + + // try to load from resources, maybe? + resourcePath = defaultAssetPath.Substring(indexOfResources + "/Resources/".Length); + + // drop the extension + if (Path.HasExtension(resourcePath)) { + resourcePath = resourcePath.Substring(0, resourcePath.LastIndexOf('.')); + } + } else { + resourcePath = ResourcePath; + } + + var instance = UnityEngine.Resources.Load(resourcePath, type); + if (!instance) { + LogTrace?.Log($"Unable to load resource at path {resourcePath} for type {type.FullName}"); + return default; + } + + if (InstantiateIfLoadedInEditor && Application.isEditor) { + var clone = Object.Instantiate(instance); + return new((FusionGlobalScriptableObject)clone, x => Object.Destroy(clone)); + } else { + return new((FusionGlobalScriptableObject)instance, x => UnityEngine.Resources.UnloadAsset(instance)); + } + } + } +} + +#endregion + + + +#endregion + + +#region Assets/Photon/Fusion/Runtime/FusionBurstIntegration.cs + +// deleted + +#endregion + + +#region Assets/Photon/Fusion/Runtime/FusionCoroutine.cs + + +namespace Fusion { + using UnityEngine; + using System; + using System.Collections; + using System.Runtime.ExceptionServices; + + public sealed class FusionCoroutine : ICoroutine, IDisposable { + private readonly IEnumerator _inner; + private Action _completed; + private float _progress; + private Action _activateAsync; + + public FusionCoroutine(IEnumerator inner) { + _inner = inner ?? throw new ArgumentNullException(nameof(inner)); + } + + public event Action Completed + { + add { + _completed += value; + if (IsDone) { + value(this); + } + } + remove => _completed -= value; + } + + public bool IsDone { get; private set; } + public ExceptionDispatchInfo Error { get; private set; } + + bool IEnumerator.MoveNext() { + try { + if (_inner.MoveNext()) { + return true; + } else { + IsDone = true; + _completed?.Invoke(this); + return false; + } + } catch (Exception e) { + IsDone = true; + Error = ExceptionDispatchInfo.Capture(e); + _completed?.Invoke(this); + return false; + } + } + + void IEnumerator.Reset() { + _inner.Reset(); + IsDone = false; + Error = null; + } + + object IEnumerator.Current => _inner.Current; + + public void Dispose() { + if (_inner is IDisposable disposable) { + disposable.Dispose(); + } + } + } +} + +#endregion + + +#region Assets/Photon/Fusion/Runtime/FusionLogInitializer.Partial.cs + +namespace Fusion { + using System.Text; + using System.Threading; + using UnityEngine; + + partial class FusionLogInitializer { + static partial void InitializeUnityLoggerUser(ref FusionUnityLogger logger); + + static FusionUnityLogger CreateLogger(bool isDarkMode) { + return new FusionUnityLogger(System.Threading.Thread.CurrentThread, isDarkMode); + } + } + + /// + /// Fusion logger implementation for Unity. + /// + public class FusionUnityLogger : FusionUnityLoggerBase { + + /// + /// Is true, the active runner's tick will be logged. + /// + public bool LogActiveRunnerTick = false; + + /// + public FusionUnityLogger(Thread mainThread, bool isDarkMode) : base(mainThread, isDarkMode) { + } + + /// + protected override (string, Object) CreateMessage(in LogContext context) { + var sb = GetThreadSafeStringBuilder(out var isMainThread); + Debug.Assert(sb.Length == 0); + + var obj = context.Source?.GetUnityObject(); + + try { + AppendPrefix(sb, context.Flags, context.Prefix); + + var pos = sb.Length; + if (obj != null) { + if (obj is NetworkRunner runner) { + TryAppendRunnerPrefix(sb, runner); + } else if (obj is NetworkObject networkObject) { + TryAppendNetworkObjectPrefix(sb, networkObject); + } else if (obj is SimulationBehaviour simulationBehaviour) { + TryAppendSimulationBehaviourPrefix(sb, simulationBehaviour); + } else { + AppendNameThreadSafe(sb, obj); + } + } + + if (LogActiveRunnerTick) { + for (var enumerator = NetworkRunner.GetInstancesEnumerator(); enumerator.MoveNext();) { + var runner = enumerator.Current; + if (runner == null || !runner.IsSimulationUpdating) { + continue; + } + sb.Append($"[Tick {(int)runner.Tick}{(runner.IsFirstTick ? "F" : "")}{(runner.Stage == 0 ? "" : $" {runner.Stage}")}] "); + } + } + + if (sb.Length > pos) { + sb.Append(": "); + } + + sb.Append(context.Message); + return (sb.ToString(), isMainThread ? obj : null); + } finally { + sb.Clear(); + } + } + + bool TryAppendRunnerPrefix(StringBuilder builder, NetworkRunner runner) { + if ((object)runner == null) { + return false; + } + if (runner.Config?.PeerMode != NetworkProjectConfig.PeerModes.Multiple) { + return false; + } + + AppendNameThreadSafe(builder, runner); + + var localPlayer = runner.LocalPlayer; + if (localPlayer.IsRealPlayer) { + builder.Append("[P").Append(localPlayer.PlayerId).Append("]"); + } else { + builder.Append("[P-]"); + } + + return true; + } + + bool TryAppendNetworkObjectPrefix(StringBuilder builder, NetworkObject networkObject) { + if ((object)networkObject == null) { + return false; + } + + AppendNameThreadSafe(builder, networkObject); + + if (networkObject.Id.IsValid) { + builder.Append(" "); + builder.Append(networkObject.Id.ToString()); + } + + int pos = builder.Length; + if (TryAppendRunnerPrefix(builder, networkObject.Runner)) { + builder.Insert(pos, '@'); + } + + return true; + } + + bool TryAppendSimulationBehaviourPrefix(StringBuilder builder, SimulationBehaviour simulationBehaviour) { + if ((object)simulationBehaviour == null) { + return false; + } + + AppendNameThreadSafe(builder, simulationBehaviour); + + if (simulationBehaviour is NetworkBehaviour nb && nb.Id.IsValid) { + builder.Append(" "); + builder.Append(nb.Id.ToString()); + } + + int pos = builder.Length; + if (TryAppendRunnerPrefix(builder, simulationBehaviour.Runner)) { + builder.Insert(pos, '@'); + } + + return true; + } + } +} + +#endregion + + +#region Assets/Photon/Fusion/Runtime/FusionProfiler.cs + +namespace Fusion { + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using Unity.Collections.LowLevel.Unsafe; + using Unity.Profiling; + using Unity.Profiling.LowLevel; + using Unity.Profiling.LowLevel.Unsafe; + using UnityEngine; + using Object = System.Object; + + public static class FusionProfiler { + [RuntimeInitializeOnLoadMethod] + static void Init() { + Fusion.EngineProfiler.InterpolationOffsetCallback = f => SetCounter(InterpolationOffset, f); + + Fusion.EngineProfiler.ResimulationsCallback = i => SetCounter(Resimulations, i); + Fusion.EngineProfiler.WorldSnapshotSizeCallback = i => SetCounter(WorldSnapshotSize, i); + + Fusion.EngineProfiler.RoundTripTimeCallback = f => SetCounter(RoundTripTime, f); + + Fusion.EngineProfiler.InputSizeCallback = i => SetCounter(InputSize, i); + Fusion.EngineProfiler.InputQueueCallback = i => SetCounter(InputQueue, i); + + Fusion.EngineProfiler.RpcInCallback = i => SetCounterValue(RpcIn, i, true); + Fusion.EngineProfiler.RpcOutCallback = i => SetCounterValue(RpcOut, i, true); + + Fusion.EngineProfiler.InputRecvDeltaCallback = f => SetCounter(InputRecvDelta, f); + Fusion.EngineProfiler.InputRecvDeltaDeviationCallback = f => SetCounter(InputRecvDeltaDeviation, f); + + foreach (var counter in AllocCounters.Values) { + SetCounterValue(counter.Count, 0); + SetCounterValue(counter.Size, 0); + } + + SetCounterValue(ObjectAllocatorUsage, 0); + SetCounterValue(MiscAllocatorUsage, 0); + + Fusion.EngineProfiler.InternalObjectAllocatedCallback = (typeId, size) => { + var entry = AllocCounters[typeId]; + SetCounterValue(entry.Count, 1, true); + SetCounterValue(entry.Size, size, true); + SetCounterValue(typeId == EngineProfiler.InternalSimulationType.Object ? ObjectAllocatorUsage : MiscAllocatorUsage, size, true); + }; + + Fusion.EngineProfiler.InternalObjectFreedCallback = (typeId, size) => { + var entry = AllocCounters[typeId]; + SetCounterValue(entry.Count, -1, true); + SetCounterValue(entry.Size, -size, true); + SetCounterValue(typeId == EngineProfiler.InternalSimulationType.Object ? ObjectAllocatorUsage : MiscAllocatorUsage, -size, true); + }; + + Fusion.EngineProfiler.PacketInCallback = info => { + SetCounterValue(PacketIn.Updates, info.ObjectUpdates, delta: true); + SetCounterValue(PacketIn.Destroys, info.ObjectDestroys, delta: true); + SetCounterValue(PacketIn.Count, 1, delta: true); + }; + + Fusion.EngineProfiler.PacketOutCallback = info => { + SetCounterValue(PacketOut.Updates, info.ObjectUpdates, delta: true); + SetCounterValue(PacketOut.Destroys, info.ObjectDestroys, delta: true); + SetCounterValue(PacketOut.Count, 1, delta: true); + }; + + Fusion.EngineProfiler.PacketLostCallback = info => { + SetCounterValue(PacketLost.Updates, info.ObjectUpdates, delta: true); + SetCounterValue(PacketLost.Destroys, info.ObjectDestroys, delta: true); + SetCounterValue(PacketLost.Count, 1, delta: true); + }; + + Fusion.EngineProfiler.PacketDeliveredCallback = info => { + SetCounterValue(PacketDelivered.Updates, info.ObjectUpdates, delta: true); + SetCounterValue(PacketDelivered.Destroys, info.ObjectDestroys, delta: true); + SetCounterValue(PacketDelivered.Count, 1, delta: true); + }; + + Fusion.EngineProfiler.UDPPacketsOutCallback = count => { + SetCounterValue(UDPPackets, count, delta: true); + }; + } + + public static readonly ProfilerCategory Category = ProfilerCategory.Scripts; + + public static readonly IntPtr InterpolationOffset = CreateCounter("F Interp Offset", ProfilerMarkerDataType.Float, ProfilerMarkerDataUnit.Count); + + public static readonly IntPtr InputSize = CreateCounter("F Client Input Size", ProfilerMarkerDataType.Int32, ProfilerMarkerDataUnit.Bytes); + public static readonly IntPtr InputQueue = CreateCounter("F Client Input Queue", ProfilerMarkerDataType.Int32, ProfilerMarkerDataUnit.Count); + + public static readonly IntPtr WorldSnapshotSize = CreateCounter("F Client Snapshot Size", ProfilerMarkerDataType.Int32, ProfilerMarkerDataUnit.Bytes); + public static readonly IntPtr Resimulations = CreateCounter("F Client Resims", ProfilerMarkerDataType.Int32, ProfilerMarkerDataUnit.Count); + public static readonly IntPtr RoundTripTime = CreateCounter("F Client RTT", ProfilerMarkerDataType.Float, ProfilerMarkerDataUnit.Count); + + public static readonly IntPtr RpcIn = CreateCounterValue("F RPCs In", ProfilerMarkerDataUnit.Count, ProfilerCounterOptions.FlushOnEndOfFrame | ProfilerCounterOptions.ResetToZeroOnFlush); + public static readonly IntPtr RpcOut = CreateCounterValue("F RPCs Out", ProfilerMarkerDataUnit.Count, ProfilerCounterOptions.FlushOnEndOfFrame | ProfilerCounterOptions.ResetToZeroOnFlush); + + public static readonly IntPtr InputRecvDelta = CreateCounter("F Input Recv Delta", ProfilerMarkerDataType.Float, ProfilerMarkerDataUnit.Count); + public static readonly IntPtr InputRecvDeltaDeviation = CreateCounter("F Input Recv Delta Dev", ProfilerMarkerDataType.Float, ProfilerMarkerDataUnit.Count); + + static readonly Dictionary AllocCounters = typeof(EngineProfiler.InternalSimulationType).GetEnumValues() + .Cast() + .ToDictionary(x => x, x => { + var count = CreateCounterValue($"F {x} Count", ProfilerMarkerDataUnit.Count); + var size = CreateCounterValue($"F {x} Size", ProfilerMarkerDataUnit.Bytes); + SetCounterValue(count, 0); + SetCounterValue(count, 0); + return (count, size); + }); + + public static readonly IntPtr ObjectAllocatorUsage = CreateCounterValue("F Object Allocator", ProfilerMarkerDataUnit.Bytes); + public static readonly IntPtr MiscAllocatorUsage = CreateCounterValue("F Misc Allocator", ProfilerMarkerDataUnit.Bytes); + + public static readonly (IntPtr Updates, IntPtr Destroys, IntPtr Count) PacketIn = ( + CreateCounterValue("F Objects In", ProfilerMarkerDataUnit.Count, ProfilerCounterOptions.ResetToZeroOnFlush | ProfilerCounterOptions.FlushOnEndOfFrame), + CreateCounterValue("F Destroys In", ProfilerMarkerDataUnit.Count, ProfilerCounterOptions.ResetToZeroOnFlush | ProfilerCounterOptions.FlushOnEndOfFrame), + CreateCounterValue("F Packet In", ProfilerMarkerDataUnit.Count, ProfilerCounterOptions.ResetToZeroOnFlush | ProfilerCounterOptions.FlushOnEndOfFrame) + ); + + public static readonly (IntPtr Updates, IntPtr Destroys, IntPtr Count) PacketOut = ( + CreateCounterValue("F Objects Out", ProfilerMarkerDataUnit.Count, ProfilerCounterOptions.ResetToZeroOnFlush | ProfilerCounterOptions.FlushOnEndOfFrame), + CreateCounterValue("F Destroys Out", ProfilerMarkerDataUnit.Count, ProfilerCounterOptions.ResetToZeroOnFlush | ProfilerCounterOptions.FlushOnEndOfFrame), + CreateCounterValue("F Packet Out", ProfilerMarkerDataUnit.Count, ProfilerCounterOptions.ResetToZeroOnFlush | ProfilerCounterOptions.FlushOnEndOfFrame) + ); + + public static readonly (IntPtr Updates, IntPtr Destroys, IntPtr Count) PacketLost = ( + CreateCounterValue("F Objects Lost", ProfilerMarkerDataUnit.Count, ProfilerCounterOptions.ResetToZeroOnFlush | ProfilerCounterOptions.FlushOnEndOfFrame), + CreateCounterValue("F Destroys Lost", ProfilerMarkerDataUnit.Count, ProfilerCounterOptions.ResetToZeroOnFlush | ProfilerCounterOptions.FlushOnEndOfFrame), + CreateCounterValue("F Packet Lost", ProfilerMarkerDataUnit.Count, ProfilerCounterOptions.ResetToZeroOnFlush | ProfilerCounterOptions.FlushOnEndOfFrame) + ); + + public static readonly (IntPtr Updates, IntPtr Destroys, IntPtr Count) PacketDelivered = ( + CreateCounterValue("F Objects Delivered", ProfilerMarkerDataUnit.Count, ProfilerCounterOptions.ResetToZeroOnFlush | ProfilerCounterOptions.FlushOnEndOfFrame), + CreateCounterValue("F Destroys Delivered", ProfilerMarkerDataUnit.Count, ProfilerCounterOptions.ResetToZeroOnFlush | ProfilerCounterOptions.FlushOnEndOfFrame), + CreateCounterValue("F Packet Delivered", ProfilerMarkerDataUnit.Count, ProfilerCounterOptions.ResetToZeroOnFlush | ProfilerCounterOptions.FlushOnEndOfFrame) + ); + + static readonly IntPtr UDPPackets = CreateCounterValue("F UDP Packets", ProfilerMarkerDataUnit.Count, ProfilerCounterOptions.ResetToZeroOnFlush | ProfilerCounterOptions.FlushOnEndOfFrame); + + static IntPtr CreateCounter(string name, ProfilerMarkerDataType dataType, ProfilerMarkerDataUnit unit) { +#if ENABLE_PROFILER + var marker = ProfilerUnsafeUtility.CreateMarker(name, ProfilerCategory.Scripts, MarkerFlags.Counter, 1); + ProfilerUnsafeUtility.SetMarkerMetadata(marker, 0, null, (byte)dataType, (byte)unit); + return marker; +#else + return default; +#endif + } + + static IntPtr CreateCounterValue(string name, ProfilerMarkerDataUnit unit, ProfilerCounterOptions options = ProfilerCounterOptions.FlushOnEndOfFrame) { +#if ENABLE_PROFILER + var flags = ProfilerCounterOptions.FlushOnEndOfFrame | options; + unsafe { + var ptr = ProfilerUnsafeUtility.CreateCounterValue(out _, name, ProfilerUnsafeUtility.CategoryScripts, MarkerFlags.Default, (byte)ProfilerMarkerDataType.Int32, (byte)unit, sizeof(int), flags); + return (new IntPtr(ptr)); + } +#else + return default; +#endif + } + + [Conditional("ENABLE_PROFILER")] + static void SetCounter(IntPtr counter, int value) { + if (counter == default) { + return; + } + unsafe { + var data = new ProfilerMarkerData { + Type = (byte)ProfilerMarkerDataType.Int32, + Size = sizeof(int), + Ptr = UnsafeUtility.AddressOf(ref value) + }; + ProfilerUnsafeUtility.SingleSampleWithMetadata(counter, 1, &data); + } + } + + [Conditional("ENABLE_PROFILER")] + static void SetCounter(IntPtr counter, float value) { + if (counter == default) { + return; + } + unsafe { + var data = new ProfilerMarkerData { + Type = (byte)ProfilerMarkerDataType.Float, + Size = sizeof(float), + Ptr = UnsafeUtility.AddressOf(ref value) + }; + ProfilerUnsafeUtility.SingleSampleWithMetadata(counter, 1, &data); + } + } + + [Conditional("ENABLE_PROFILER")] + static void SetCounterValue(IntPtr counter, int value, bool delta = false) { + if (counter == default) { + return; + } + unsafe { + if (delta) { + *(int*)counter += value; + } else { + *(int*)counter = value; + } + } + } + } +} + +#endregion + + +#region Assets/Photon/Fusion/Runtime/FusionRuntimeCheck.cs + +namespace Fusion { + using UnityEngine; + + static class FusionRuntimeCheck { + + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] + static void RuntimeCheck() { + RuntimeUnityFlagsSetup.Check_ENABLE_IL2CPP(); + RuntimeUnityFlagsSetup.Check_ENABLE_MONO(); + + RuntimeUnityFlagsSetup.Check_UNITY_EDITOR(); + RuntimeUnityFlagsSetup.Check_UNITY_GAMECORE(); + RuntimeUnityFlagsSetup.Check_UNITY_SWITCH(); + RuntimeUnityFlagsSetup.Check_UNITY_WEBGL(); + RuntimeUnityFlagsSetup.Check_UNITY_XBOXONE(); + + RuntimeUnityFlagsSetup.Check_NETFX_CORE(); + RuntimeUnityFlagsSetup.Check_NET_4_6(); + RuntimeUnityFlagsSetup.Check_NET_STANDARD_2_0(); + + RuntimeUnityFlagsSetup.Check_UNITY_2019_4_OR_NEWER(); + } + } +} + + +#endregion + + +#region Assets/Photon/Fusion/Runtime/FusionTraceChannelsExtensions.cs + + + +namespace Fusion { + static class TraceChannelsExtensions { + public static TraceChannels AddChannelsFromDefines(this TraceChannels traceChannels) { +#if FUSION_TRACE_GLOBAL + traceChannels |= TraceChannels.Global; +#endif +#if FUSION_TRACE_STUN + traceChannels |= TraceChannels.Stun; +#endif +#if FUSION_TRACE_OBJECT + traceChannels |= TraceChannels.Object; +#endif +#if FUSION_TRACE_NETWORK + traceChannels |= TraceChannels.Network; +#endif +#if FUSION_TRACE_PREFAB + traceChannels |= TraceChannels.Prefab; +#endif +#if FUSION_TRACE_SCENEINFO + traceChannels |= TraceChannels.SceneInfo; +#endif +#if FUSION_TRACE_SCENEMANAGER + traceChannels |= TraceChannels.SceneManager; +#endif +#if FUSION_TRACE_SIMULATIONMESSAGE + traceChannels |= TraceChannels.SimulationMessage; +#endif +#if FUSION_TRACE_HOSTMIGRATION + traceChannels |= TraceChannels.HostMigration; +#endif +#if FUSION_TRACE_ENCRYPTION + traceChannels |= TraceChannels.Encryption; +#endif +#if FUSION_TRACE_DUMMYTRAFFIC + traceChannels |= TraceChannels.DummyTraffic; +#endif +#if FUSION_TRACE_REALTIME + traceChannels |= TraceChannels.Realtime; +#endif +#if FUSION_TRACE_MEMORYTRACK + traceChannels |= TraceChannels.MemoryTrack; +#endif +#if FUSION_TRACE_SNAPSHOTS + traceChannels |= TraceChannels.Snapshots; +#endif +#if FUSION_TRACE_TIME + traceChannels |= TraceChannels.Time; +#endif + return traceChannels; + } + } +} + + +#endregion + + +#region Assets/Photon/Fusion/Runtime/FusionUnityUtility.Common.cs + +// merged UnityUtility + +#region JsonUtilityExtensions.cs + +namespace Fusion { + using System; + using System.Collections; + using System.Collections.Generic; + using System.IO; + using System.Text; + using System.Text.RegularExpressions; + using UnityEngine; + + /// + /// Extends capabilities of by adding type annotations to the serialized JSON, Unity object reference + /// handling and integer enquotement. + /// + public static class JsonUtilityExtensions { + + /// + public delegate Type TypeResolverDelegate(string typeName); + /// + public delegate string TypeSerializerDelegate(Type type); + /// + public delegate string InstanceIDHandlerDelegate(object context, int value); + + private const string TypePropertyName = "$type"; + + /// + /// Enquotes integers in the JSON string that are at least long. This is useful for parsers that + /// interpret large integers as floating point numbers. + /// + /// JSON to process + /// Digit threshold to perfom the enquoting + /// with long integers enquoted. + public static string EnquoteIntegers(string json, int minDigits = 8) { + var result = Regex.Replace(json, $@"(?<="":\s*)(-?[0-9]{{{minDigits},}})(?=[,}}\n\r\s])", "\"$1\"", RegexOptions.Compiled); + return result; + } + + /// + /// Converts the object to JSON with type annotations. + /// + /// Object to be serialized. + /// Handler for UnityEngine.Object references. If the handler returns an empty string, + /// the reference is removed from the final result. + public static string ToJsonWithTypeAnnotation(object obj, InstanceIDHandlerDelegate instanceIDHandler = null) { + var sb = new StringBuilder(1000); + using (var writer = new StringWriter(sb)) { + ToJsonWithTypeAnnotation(obj, writer, instanceIDHandler: instanceIDHandler); + } + return sb.ToString(); + } + + /// + /// Converts the object/IList to JSON with type annotations. + /// + /// Object to be serialized. + /// The output TextWriter. + /// + /// Handler for obtaining serialized type names. If , the short assembly + /// qualified name (namespace + name + assembly name) will be used. + /// Handler for UnityEngine.Object references. If the handler returns an empty string, + /// the reference is removed from the final result. + public static void ToJsonWithTypeAnnotation(object obj, TextWriter writer, int? integerEnquoteMinDigits = null, TypeSerializerDelegate typeSerializer = null, InstanceIDHandlerDelegate instanceIDHandler = null) { + if (obj == null) { + writer.Write("null"); + return; + } + + if (obj is IList list) { + writer.Write("["); + for (var i = 0; i < list.Count; ++i) { + if (i > 0) { + writer.Write(","); + } + + ToJsonInternal(list[i], writer, integerEnquoteMinDigits, typeSerializer, instanceIDHandler); + } + + writer.Write("]"); + } else { + ToJsonInternal(obj, writer, integerEnquoteMinDigits, typeSerializer, instanceIDHandler); + } + } + + + /// + /// Converts JSON with type annotation to an instance of . If the JSON contains type annotations, they need to match + /// the expected result type. If there are no type annotations, use to return the expected type. + /// + /// JSON to be parsed + /// Converts type name to a type instance. + public static T FromJsonWithTypeAnnotation(string json, TypeResolverDelegate typeResolver = null) { + if (typeof(T).IsArray) { + var listType = typeof(List<>).MakeGenericType(typeof(T).GetElementType()); + var list = (IList)Activator.CreateInstance(listType); + FromJsonWithTypeAnnotationInternal(json, typeResolver, list); + + var array = Array.CreateInstance(typeof(T).GetElementType(), list.Count); + list.CopyTo(array, 0); + return (T)(object)array; + } + + if (typeof(T).GetInterface(typeof(IList).FullName) != null) { + var list = (IList)Activator.CreateInstance(typeof(T)); + FromJsonWithTypeAnnotationInternal(json, typeResolver, list); + return (T)list; + } + + return (T)FromJsonWithTypeAnnotationInternal(json, typeResolver); + } + + /// + /// Converts JSON with type annotation. If there are no type annotations, use to return the expected type. + /// + /// JSON to be parsed + /// Converts type name to a type instance. + public static object FromJsonWithTypeAnnotation(string json, TypeResolverDelegate typeResolver = null) { + Assert.Check(json != null); + + var i = SkipWhiteOrThrow(0); + if (json[i] == '[') { + var list = new List(); + + // list + ++i; + for (var expectComma = false;; expectComma = true) { + i = SkipWhiteOrThrow(i); + + if (json[i] == ']') { + break; + } + + if (expectComma) { + if (json[i] != ',') { + throw new InvalidOperationException($"Malformed at {i}: expected ,"); + } + i = SkipWhiteOrThrow(i + 1); + } + + var item = FromJsonWithTypeAnnotationToObject(ref i, json, typeResolver); + list.Add(item); + } + + return list.ToArray(); + } + + return FromJsonWithTypeAnnotationToObject(ref i, json, typeResolver); + + int SkipWhiteOrThrow(int i) { + while (i < json.Length && char.IsWhiteSpace(json[i])) { + i++; + } + + if (i == json.Length) { + throw new InvalidOperationException($"Malformed at {i}: expected more"); + } + + return i; + } + } + + + private static object FromJsonWithTypeAnnotationInternal(string json, TypeResolverDelegate typeResolver = null, IList targetList = null) { + Assert.Check(json != null); + + var i = SkipWhiteOrThrow(0); + if (json[i] == '[') { + var list = targetList ?? new List(); + + // list + ++i; + for (var expectComma = false;; expectComma = true) { + i = SkipWhiteOrThrow(i); + + if (json[i] == ']') { + break; + } + + if (expectComma) { + if (json[i] != ',') { + throw new InvalidOperationException($"Malformed at {i}: expected ,"); + } + + i = SkipWhiteOrThrow(i + 1); + } + + var item = FromJsonWithTypeAnnotationToObject(ref i, json, typeResolver); + list.Add(item); + } + + return targetList ?? ((List)list).ToArray(); + } + + if (targetList != null) { + throw new InvalidOperationException($"Expected list, got {json[i]}"); + } + + return FromJsonWithTypeAnnotationToObject(ref i, json, typeResolver); + + int SkipWhiteOrThrow(int i) { + while (i < json.Length && char.IsWhiteSpace(json[i])) { + i++; + } + + if (i == json.Length) { + throw new InvalidOperationException($"Malformed at {i}: expected more"); + } + + return i; + } + } + + private static void ToJsonInternal(object obj, TextWriter writer, + int? integerEnquoteMinDigits = null, + TypeSerializerDelegate typeResolver = null, + InstanceIDHandlerDelegate instanceIDHandler = null) { + Assert.Check(obj != null); + Assert.Check(writer != null); + + var json = JsonUtility.ToJson(obj); + if (integerEnquoteMinDigits.HasValue) { + json = EnquoteIntegers(json, integerEnquoteMinDigits.Value); + } + + var type = obj.GetType(); + + writer.Write("{\""); + writer.Write(TypePropertyName); + writer.Write("\":\""); + + writer.Write(typeResolver?.Invoke(type) ?? SerializableType.GetShortAssemblyQualifiedName(type)); + + writer.Write('\"'); + + if (json == "{}") { + writer.Write("}"); + } else { + Assert.Check('{' == json[0]); + Assert.Check('}' == json[^1]); + writer.Write(','); + + if (instanceIDHandler != null) { + int i = 1; + + for (;;) { + const string prefix = "{\"instanceID\":"; + + var nextInstanceId = json.IndexOf(prefix, i, StringComparison.Ordinal); + if (nextInstanceId < 0) { + break; + } + + // parse the number that follows; may be negative + var start = nextInstanceId + prefix.Length; + var end = json.IndexOf('}', start); + var instanceId = int.Parse(json.AsSpan(start, end - start)); + + // append that part + writer.Write(json.AsSpan(i, nextInstanceId - i)); + writer.Write(instanceIDHandler(obj, instanceId)); + i = end + 1; + } + + writer.Write(json.AsSpan(i, json.Length - i)); + } else { + writer.Write(json.AsSpan(1, json.Length - 1)); + } + } + } + + private static object FromJsonWithTypeAnnotationToObject(ref int i, string json, TypeResolverDelegate typeResolver) { + if (json[i] == '{') { + var endIndex = FindScopeEnd(json, i, '{', '}'); + if (endIndex < 0) { + throw new InvalidOperationException($"Unable to find end of object's end (starting at {i})"); + } + + Assert.Check(endIndex > i); + Assert.Check(json[endIndex] == '}'); + + var part = json.Substring(i, endIndex - i + 1); + i = endIndex + 1; + + // read the object, only care about the type; there's no way to map dollar-prefixed property to a C# field, + // so some string replacing is necessary + var typeInfo = JsonUtility.FromJson(part.Replace(TypePropertyName, nameof(TypeNameWrapper.__TypeName), StringComparison.Ordinal)); + + Type type; + if (typeResolver != null) { + type = typeResolver(typeInfo.__TypeName); + if (type == null) { + return null; + } + } else { + Assert.Check(!string.IsNullOrEmpty(typeInfo?.__TypeName)); + type = Type.GetType(typeInfo.__TypeName, true); + } + + if (type.IsSubclassOf(typeof(ScriptableObject))) { + var instance = ScriptableObject.CreateInstance(type); + JsonUtility.FromJsonOverwrite(part, instance); + return instance; + } else { + var instance = JsonUtility.FromJson(part, type); + return instance; + } + } + + if (i + 4 < json.Length && json.AsSpan(i, 4).SequenceEqual("null")) { + // is this null? + i += 4; + return null; + } + + throw new InvalidOperationException($"Malformed at {i}: expected {{ or null"); + } + + internal static int FindObjectEnd(string json, int start = 0) { + return FindScopeEnd(json, start, '{', '}'); + } + + private static int FindScopeEnd(string json, int start, char cstart = '{', char cend = '}') { + var depth = 0; + + if (json[start] != cstart) { + return -1; + } + + for (var i = start; i < json.Length; i++) { + if (json[i] == '"') { + // can't be escaped + Assert.Check('\\' != json[i - 1]); + // now skip until the first unescaped quote + while (i < json.Length) { + if (json[++i] == '"') + // are we escaped? + { + if (json[i - 1] != '\\') { + break; + } + } + } + } else if (json[i] == cstart) { + depth++; + } else if (json[i] == cend) { + depth--; + if (depth == 0) { + return i; + } + } + } + + return -1; + } + + [Serializable] + private class TypeNameWrapper { +#pragma warning disable CS0649 // Set by serialization + // ReSharper disable once InconsistentNaming + public string __TypeName; +#pragma warning restore CS0649 + } + } +} + +#endregion + + +#region FusionAddressablesUtils.cs + +#if (FUSION_ADDRESSABLES || FUSION_ENABLE_ADDRESSABLES) && !FUSION_DISABLE_ADDRESSABLES +namespace Fusion { + using System; + using UnityEngine.AddressableAssets; + using Object = UnityEngine.Object; + + /// + /// Utility class for addressables. + /// + public static class FusionAddressablesUtils { + /// + /// Tries to parse the address into main part and sub object name. + /// + /// The address to parse. + /// The main part of the address. + /// The sub object name. + /// if the address is successfully parsed; otherwise, . + public static bool TryParseAddress(string address, out string mainPart, out string subObjectName) { + if (string.IsNullOrEmpty(address)) { + mainPart = null; + subObjectName = null; + return false; + } + + var indexOfSquareBracket = address.IndexOf('['); + var indexOfClosingSquareBracket = address.IndexOf(']'); + + // addresses can only use square brackets for sub object names + // so only such usage is valid: + // - mainAddress[SubObjectName] + // this is not valid: + // - mainAddress[SubObjectName + // - mainAddressSubObjectName] + // - mainAddress[SubObjectName]a + // - mainAddress[] + if ((indexOfSquareBracket == 0) || + (indexOfSquareBracket < 0 && (indexOfClosingSquareBracket >= 0)) || + (indexOfSquareBracket > 0 && (indexOfClosingSquareBracket != address.Length - 1)) || + (indexOfSquareBracket > 0 && (indexOfClosingSquareBracket - indexOfSquareBracket <= 1))) { + mainPart = default; + subObjectName = default; + return false; + } + + if (indexOfSquareBracket < 0) { + mainPart = address; + subObjectName = default; + return true; + } + + mainPart = address.Substring(0, indexOfSquareBracket); + subObjectName = address.Substring(indexOfSquareBracket + 1, address.Length - indexOfSquareBracket - 2); + return true; + } + + /// + /// Creates an asset reference from the given address. + /// + /// The address to create the asset reference from. + /// The created asset reference. + /// Thrown when the main part of the address is not a guid or the address is not valid. + public static AssetReference CreateAssetReference(string address) { + if (TryParseAddress(address, out var mainPart, out var subObjectName)) { + if (System.Guid.TryParse(mainPart, out _)) { + // ok, the main part is a guid, can create asset reference + return new AssetReference(mainPart) { + SubObjectName = subObjectName, + }; + } else { + throw new System.ArgumentException($"The main part of the address is not a guid: {mainPart}", nameof(address)); + } + } else { + throw new System.ArgumentException($"Not a valid address: {address}", nameof(address)); + } + } + +#if UNITY_EDITOR + private static Func s_loadEditorInstance; + + /// + /// Loads the editor instance for the given runtime key. + /// + /// The runtime key. + /// The loaded editor instance. + /// Thrown when the load editor instance handler is not set. + public static Object LoadEditorInstance(string runtimeKey) { + Assert.Check(s_loadEditorInstance != null, $"Call {nameof(SetLoadEditorInstanceHandler)} before using this method"); + return s_loadEditorInstance(runtimeKey); + } + + /// + /// Sets the load editor instance handler. + /// + /// The load editor instance handler. + public static void SetLoadEditorInstanceHandler(Func loadEditorInstance) { + s_loadEditorInstance = loadEditorInstance; + } +#endif + } +} +#endif + +#endregion + + +#region FusionLogInitializer.cs + +namespace Fusion { + using System; + using UnityEngine; + +#if UNITY_EDITOR + using UnityEditor; + using UnityEditor.Build; +#endif + + /// + /// Initializes the logging system for Fusion. Use to completely override the log level and trace channels or + /// to provide a custom logger. Use to override default Unity logger settings. + /// + public static partial class FusionLogInitializer { +#if UNITY_EDITOR + static LogLevel GetEditorLogLevel() { + var currentBuildTarget = EditorUserBuildSettings.activeBuildTarget; + var currentBuildTargetGroup = BuildPipeline.GetBuildTargetGroup(currentBuildTarget); + var currentNamedBuildTarget = NamedBuildTarget.FromBuildTargetGroup(currentBuildTargetGroup); + var defines = PlayerSettings.GetScriptingDefineSymbols(currentNamedBuildTarget).Split(";"); + + const string LogLevelNone = "FUSION_LOGLEVEL_NONE"; + const string LogLevelError = "FUSION_LOGLEVEL_ERROR"; + const string LogLevelWarn = "FUSION_LOGLEVEL_WARN"; + const string LogLevelInfo = "FUSION_LOGLEVEL_INFO"; + const string LogLevelDebug = "FUSION_LOGLEVEL_DEBUG"; + const string LogLevelTrace = "FUSION_LOGLEVEL_TRACE"; + + (string, LogLevel)[] logLevelDefines = { + (LogLevelNone, LogLevel.None), + (LogLevelError, LogLevel.Error), + (LogLevelWarn, LogLevel.Warn), + (LogLevelInfo, LogLevel.Info), + (LogLevelDebug, LogLevel.Debug), + }; + + string defaultLogLevelDefine = LogLevelInfo; + + if (Array.IndexOf(defines, LogLevelTrace) >= 0) { + FusionEditorLog.Warn($"{LogLevelTrace} is not supported in Fusion. Replacing with {LogLevelDebug}."); + ArrayUtility.Remove(ref defines, LogLevelTrace); + defaultLogLevelDefine = LogLevelDebug; + } + + LogLevel? foundLogLevel = null; + foreach (var (define, logLevel) in logLevelDefines) { + if (Array.IndexOf(defines, define) < 0) { + continue; + } + + foundLogLevel = logLevel; + break; + } + + if (foundLogLevel == null) { + if (Application.isPlaying) { + FusionEditorLog.Log($"No log level define set for Fusion. Setting default: {defaultLogLevelDefine}"); + } + + ArrayUtility.Add(ref defines, defaultLogLevelDefine); + PlayerSettings.SetScriptingDefineSymbols(currentNamedBuildTarget, string.Join(";", defines)); + + return LogLevel.Info; + } else { + return foundLogLevel.Value; + } + } +#endif + + /// + /// Initializes the logging system for Fusion. This method is called automatically when the assembly is loaded. + /// +#if UNITY_EDITOR + [UnityEditor.InitializeOnLoadMethod] +#endif + [RuntimeInitializeOnLoadMethod] + public static void Initialize() { + var isDark = false; +#if UNITY_EDITOR + isDark = UnityEditor.EditorGUIUtility.isProSkin; + FusionEditorLog.Initialize(isDark); +#endif + + LogLevel logLevel = +#if FUSION_LOGLEVEL_DEBUG || FUSION_LOGLEVEL_TRACE + LogLevel.Debug; +#elif FUSION_LOGLEVEL_INFO + LogLevel.Info; +#elif FUSION_LOGLEVEL_WARN + LogLevel.Warn; +#elif FUSION_LOGLEVEL_ERROR + LogLevel.Error; +#elif FUSION_LOGLEVEL_NONE + LogLevel.None; +#elif UNITY_EDITOR + GetEditorLogLevel(); +#else + LogLevel.None; + FusionEditorLog.Warn($"No log level define set for Fusion, treating as FUSION_LOGLEVEL_NONE (disabled completely)."); +#endif + + TraceChannels traceChannels = default; + traceChannels = traceChannels.AddChannelsFromDefines(); + InitializeUser(ref logLevel, ref traceChannels); + + if (Log.IsInitialized) { + return; + } + + var logger = CreateLogger(isDarkMode: isDark); + InitializeUnityLoggerUser(ref logger); + Log.Initialize(logLevel, logger.CreateLogStream, traceChannels); + } + + static partial void InitializeUser(ref LogLevel logLevel, ref TraceChannels traceChannels); + } +} + +#endregion + + +#region FusionMppm.cs + +namespace Fusion { + using System; + using System.Diagnostics; + using JetBrains.Annotations; +#if FUSION_ENABLE_MPPM + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Text.RegularExpressions; + using System.Threading; + using UnityEditor; +#if UNITY_EDITOR + using UnityEditor.MPE; +#endif + using UnityEngine; + using Debug = UnityEngine.Debug; +#endif + + // ReSharper disable once IdentifierTypo + /// + /// The current status of MPPM. If the package is not enabled, this will always be . + /// + public enum FusionMppmStatus { + /// + /// MPPM is not installed. + /// + Disabled, + /// + /// This instance is the main instance. Can use to send commands. + /// + MainInstance, + /// + /// This instance is a virtual instance. Will receive commands from the main instance. + /// + VirtualInstance + } + + /// + /// Support for Multiplayer Play Mode (MPPM). It uses named pipes + /// to communicate between the main Unity instance and virtual instances. + /// +#if FUSION_ENABLE_MPPM && UNITY_EDITOR + [InitializeOnLoad] +#endif + // ReSharper disable once IdentifierTypo + public partial class FusionMppm { + + /// + /// The current status of MPPM. + /// + public static readonly FusionMppmStatus Status = FusionMppmStatus.Disabled; + + /// + /// If is , this static field can be used to send commands. + /// + [CanBeNull] + public static readonly FusionMppm MainEditor = null; + + /// + /// Sends a command to all virtual instances. Use as: + /// FusionMppm.MainEditor?.Send + /// + /// + /// + [Conditional("UNITY_EDITOR")] + public void Send(T data) where T : FusionMppmCommand { +#if FUSION_ENABLE_MPPM && UNITY_EDITOR + Assert.Check(Status == FusionMppmStatus.MainInstance, "Only the main instance can send commands"); + BroadcastInternal(data); +#endif + } + + + /// + /// Broadcasts a command to all virtual instances. + /// + /// + /// +#if FUSION_ENABLE_MPPM + [Conditional("UNITY_EDITOR")] +#else + [Conditional("FUSION_ENABLE_MPPM")] +#endif + [Obsolete("Use FusionMppm.Broadcaster?.Send instead")] + public static void Broadcast(T data) where T : FusionMppmCommand { + MainEditor?.Send(data); + } + + private FusionMppm() { + + } + +#if FUSION_ENABLE_MPPM && UNITY_EDITOR + private static readonly string s_mainInstancePath = Path.GetFullPath(Path.Combine(Application.dataPath, "..")); + + private const string PersistentCommandsFolderPath = "Temp/FusionMppm"; + private const string MpeChannelName = "FusionMppm"; + + private readonly int _mpeChannelId = ChannelService.ChannelNameToId(MpeChannelName); + private readonly List<(int connectionId, string guid)> _acks = new List<(int, string)>(); + private readonly Regex _invalidFileCharactersRegex = new Regex(string.Format(@"([{0}]*\.+$)|([{0}]+)", Regex.Escape(new string(Path.GetInvalidFileNameChars())))); + + static FusionMppm() { + + var indexOfMppmPrefix = Application.dataPath.LastIndexOf("/Library/VP/mppm", StringComparison.OrdinalIgnoreCase); + Status = indexOfMppmPrefix < 0 ? FusionMppmStatus.MainInstance : FusionMppmStatus.VirtualInstance; + + // start MPE (this check is canonical) + if (!ChannelService.IsRunning()) { + ChannelService.Start(); + } + + FusionEditorLog.TraceMppm($"Status: {Status}, MainInstancePath: {s_mainInstancePath}"); + + if (Status == FusionMppmStatus.MainInstance) { + + MainEditor = new FusionMppm(); + // set up MPE channel + var disconnect = ChannelService.GetOrCreateChannel(MpeChannelName, MainEditor.ReceiveAck); + Debug.Assert(disconnect != null); + + // ... but since new instances need to e.g. receive all the dependency hashes, set up a folder; + // it needs to be cleared on every Unity start but survive between domain reloads + string folderOwnedKey = $"Owns_{PersistentCommandsFolderPath}"; + + if (Directory.Exists(PersistentCommandsFolderPath) && !SessionState.GetBool(folderOwnedKey, false)) { + FusionEditorLog.TraceMppm($"Deleting leftover files from {PersistentCommandsFolderPath}"); + foreach (var file in Directory.GetFiles(PersistentCommandsFolderPath)) { + File.Delete(file); + } + } + + if (!Directory.Exists(PersistentCommandsFolderPath)) { + FusionEditorLog.TraceMppm($"Creating command folder {PersistentCommandsFolderPath}"); + Directory.CreateDirectory(PersistentCommandsFolderPath); + } + SessionState.SetBool(folderOwnedKey, true); + + } else { + // where is the main instance located? + s_mainInstancePath = Application.dataPath.Substring(0, indexOfMppmPrefix); + + // start the MPE client to await commands + var client = ChannelClient.GetOrCreateClient(MpeChannelName); + client.Start(true); + var disconnect = client.RegisterMessageHandler(data => { + var json = System.Text.Encoding.UTF8.GetString(data); + var message = JsonUtility.FromJson(json); + + FusionEditorLog.TraceMppm($"Received command {message.Data}"); + message.Data.Execute(); + if (message.Data.NeedsAck) { + var ack = new AckMessage() { + Guid = message.Guid + }; + var ackJson = JsonUtility.ToJson(ack); + FusionEditorLog.TraceMppm($"Sending ack {ackJson}"); + var ackBytes = System.Text.Encoding.UTF8.GetBytes(ackJson); + client.Send(ackBytes); + } + }); + Debug.Assert(disconnect != null); + + // read persistent commands from the main instance + var mainInstanceCommandsFolderPath = Path.Combine(s_mainInstancePath, PersistentCommandsFolderPath); + Debug.Assert(Directory.Exists(mainInstanceCommandsFolderPath)); + foreach (var file in Directory.GetFiles(mainInstanceCommandsFolderPath, "*.json")) { + var json = File.ReadAllText(file); + var wrapper = JsonUtility.FromJson(json); + FusionEditorLog.TraceMppm($"Received persistent command {wrapper.Data}"); + wrapper.Data.Execute(); + } + } + } + + private void BroadcastInternal(T data) where T : FusionMppmCommand { + Assert.Check(Status == FusionMppmStatus.MainInstance, "Only the main instance can send commands"); + + var guid = Guid.NewGuid().ToString(); + var wrapper = new CommandWrapper() { + Guid = guid, + Data = data + }; + + var str = JsonUtility.ToJson(wrapper); + var bytes = System.Text.Encoding.UTF8.GetBytes(str); + + FusionEditorLog.TraceMppm($"Broadcasting command {str}"); + ChannelService.BroadcastBinary(_mpeChannelId, bytes); + + var persistentKey = data.PersistentKey; + if (!string.IsNullOrEmpty(persistentKey)) { + var fileName = $"{_invalidFileCharactersRegex.Replace(persistentKey, "_")}.json"; + var filePath = Path.Combine(PersistentCommandsFolderPath, fileName); + FusionEditorLog.TraceMppm($"Saving persistent command to {filePath}"); + File.WriteAllText(filePath, str); + } + + if (data.NeedsAck) { + // well, we need to wait + var channels = ChannelService.GetChannelClientList(); + // how many acks do we need? + var numAcks = channels.Count(x => x.name == MpeChannelName); + WaitForAcks(numAcks, guid); + } + } + + private void ReceiveAck(int connectionId, byte[] data) { + var json = System.Text.Encoding.UTF8.GetString(data); + var message = JsonUtility.FromJson(json); + lock (_acks) { + _acks.Add((connectionId, message.Guid)); + } + FusionEditorLog.TraceMppm($"Received ack {json}"); + } + + private void WaitForAcks(int numAcks, string guid) { + var timer = Stopwatch.StartNew(); + var timeout = TimeSpan.FromSeconds(2); + + FusionEditorLog.TraceMppm($"Waiting for {numAcks} acks for {guid}"); + + while (timer.Elapsed < timeout) { + for (int i = 0; numAcks > 0 && i < _acks.Count; i++) { + var ack = _acks[i]; + if (ack.guid == guid) { + _acks.RemoveAt(i); + numAcks--; + + FusionEditorLog.TraceMppm($"Received ack for {guid} from {ack.connectionId}, {numAcks} left"); + } + } + + if (numAcks <= 0) { + FusionEditorLog.TraceMppm($"All acks received"); + return; + } + + FusionEditorLog.TraceMppm($"Waiting for {numAcks} acks"); + ChannelService.DispatchMessages(); + Thread.Sleep(10); + } + + FusionEditorLog.TraceMppm($"Timeout waiting for acks ({numAcks} left)"); + } + + [Serializable] + private class CommandWrapper { + public string Guid; + [SerializeReference] public FusionMppmCommand Data; + } + + [Serializable] + private class AckMessage { + public string Guid; + } +#endif + } + + /// + /// The base class for all Fusion MPPM commands. + /// + [Serializable] + // ReSharper disable once IdentifierTypo + public abstract class FusionMppmCommand { + /// + /// Execute the command on a virtual instance. + /// + public abstract void Execute(); + /// + /// Does the main instance need to wait for an ack? + /// + public virtual bool NeedsAck => false; + /// + /// If the command is persistent (i.e. needs to be executed on each domain reload), this key is used to store it. + /// + public virtual string PersistentKey => null; + } +} + +#endregion + + +#region FusionMppmRegisterCustomDependencyCommand.cs + +#if UNITY_EDITOR +namespace Fusion { + using System; + using UnityEngine; + + /// + /// A command implementing a workaround for MPPM not syncing custom dependencies. + /// + [Serializable] + public class FusionMppmRegisterCustomDependencyCommand : FusionMppmCommand { + /// + /// Name of the custom dependency. + /// + public string DependencyName; + /// + /// Hash of the custom dependency. + /// + public string Hash; + + /// + public override bool NeedsAck => true; + + /// + public override string PersistentKey => $"Dependency_{DependencyName}"; + + /// + /// Registers a custom dependency with the given name and hash. + /// + public override void Execute() { + FusionEditorLog.TraceMppm($"Registering custom dependency {DependencyName} with hash {Hash}"); + var hash = Hash128.Parse(Hash); + UnityEditor.AssetDatabase.RegisterCustomDependency(DependencyName, hash); + } + } +} +#endif + +#endregion + + +#region FusionUnityExtensions.cs + +namespace Fusion { +#if UNITY_2022_1_OR_NEWER && !UNITY_2022_2_OR_NEWER + using UnityEngine; +#endif + + /// + /// Provides backwards compatibility for Unity API. + /// + public static class FusionUnityExtensions { + + #region New Find API + +#if UNITY_2022_1_OR_NEWER && !UNITY_2022_2_OR_NEWER + public enum FindObjectsInactive { + Exclude, + Include, + } + + public enum FindObjectsSortMode { + None, + InstanceID, + } + + public static T FindFirstObjectByType() where T : Object { + return (T)FindFirstObjectByType(typeof(T), FindObjectsInactive.Exclude); + } + + public static T FindAnyObjectByType() where T : Object { + return (T)FindAnyObjectByType(typeof(T), FindObjectsInactive.Exclude); + } + + public static T FindFirstObjectByType(FindObjectsInactive findObjectsInactive) where T : Object { + return (T)FindFirstObjectByType(typeof(T), findObjectsInactive); + } + + public static T FindAnyObjectByType(FindObjectsInactive findObjectsInactive) where T : Object { + return (T)FindAnyObjectByType(typeof(T), findObjectsInactive); + } + + public static Object FindFirstObjectByType(System.Type type, FindObjectsInactive findObjectsInactive) { + return Object.FindObjectOfType(type, findObjectsInactive == FindObjectsInactive.Include); + } + + public static Object FindAnyObjectByType(System.Type type, FindObjectsInactive findObjectsInactive) { + return Object.FindObjectOfType(type, findObjectsInactive == FindObjectsInactive.Include); + } + + public static T[] FindObjectsByType(FindObjectsSortMode sortMode) where T : Object { + return ConvertObjects(FindObjectsByType(typeof(T), FindObjectsInactive.Exclude, sortMode)); + } + + public static T[] FindObjectsByType( + FindObjectsInactive findObjectsInactive, + FindObjectsSortMode sortMode) + where T : Object { + return ConvertObjects(FindObjectsByType(typeof(T), findObjectsInactive, sortMode)); + } + + public static Object[] FindObjectsByType(System.Type type, FindObjectsSortMode sortMode) { + return FindObjectsByType(type, FindObjectsInactive.Exclude, sortMode); + } + + public static Object[] FindObjectsByType(System.Type type, FindObjectsInactive findObjectsInactive, FindObjectsSortMode sortMode) { + return Object.FindObjectsOfType(type, findObjectsInactive == FindObjectsInactive.Include); + } + + static T[] ConvertObjects(Object[] rawObjects) where T : Object { + if (rawObjects == null) + return (T[])null; + T[] objArray = new T[rawObjects.Length]; + for (int index = 0; index < objArray.Length; ++index) + objArray[index] = (T)rawObjects[index]; + return objArray; + } + +#endif + + #endregion + } +} + +#endregion + + + +#endregion + + +#region Assets/Photon/Fusion/Runtime/NetworkObjectBaker.cs + +//#undef UNITY_EDITOR +namespace Fusion { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + using UnityEngine; + +#if UNITY_EDITOR + using UnityEditor; +#endif + + public class NetworkObjectBaker { + + private List _allNetworkObjects = new List(); + private List _networkObjectsPaths = new List(); + private List _allSimulationBehaviours = new List(); + private TransformPathCache _pathCache = new TransformPathCache(); + private List _arrayBufferNB = new List(); + private List _arrayBufferNO = new List(); + + public struct Result { + public bool HadChanges { get; } + public int ObjectCount { get; } + public int BehaviourCount { get; } + + public Result(bool dirty, int objectCount, int behaviourCount) { + HadChanges = dirty; + ObjectCount = objectCount; + BehaviourCount = behaviourCount; + } + } + + protected virtual void SetDirty(MonoBehaviour obj) { + // do nothing + } + + protected virtual bool TryGetExecutionOrder(MonoBehaviour obj, out int order) { + order = default; + return false; + } + + protected virtual uint GetSortKey(NetworkObject obj) { + return 0; + } + + /// + /// Postprocesses the behaviour. Returns true if the object was marked dirty. + /// + /// + /// + protected virtual bool PostprocessBehaviour(SimulationBehaviour behaviour) { + // do nothing + return false; + } + + public Result Bake(GameObject root) { + + if (root == null) { + throw new ArgumentNullException(nameof(root)); + } + + root.GetComponentsInChildren(true, _allNetworkObjects); + + // remove null ones (missing scripts may cause that) + _allNetworkObjects.RemoveAll(x => x == null); + + if (_allNetworkObjects.Count == 0) { + return new Result(false, 0, 0); + } + + try { + foreach (var obj in _allNetworkObjects) { + _networkObjectsPaths.Add(_pathCache.Create(obj.transform)); + } + + bool dirty = false; + + _allNetworkObjects.Reverse(); + _networkObjectsPaths.Reverse(); + + root.GetComponentsInChildren(true, _allSimulationBehaviours); + _allSimulationBehaviours.RemoveAll(x => x == null); + + int countNO = _allNetworkObjects.Count; + int countSB = _allSimulationBehaviours.Count; + + // start from the leaves + for (int i = 0; i < _allNetworkObjects.Count; ++i) { + var obj = _allNetworkObjects[i]; + + var objDirty = false; + var objActive = obj.gameObject.activeInHierarchy; + int? objExecutionOrder = null; + if (!objActive) { + if (TryGetExecutionOrder(obj, out var order)) { + objExecutionOrder = order; + } else { + Log.Warn($"Unable to get execution order for {obj}. " + + $"Because the object is initially inactive, Fusion is unable to guarantee " + + $"the script's Awake will be invoked before Spawned. Please implement {nameof(TryGetExecutionOrder)}."); + } + } + + // find nested behaviours + _arrayBufferNB.Clear(); + + var path = _networkObjectsPaths[i]; + + string entryPath = path.ToString(); + for (int scriptIndex = _allSimulationBehaviours.Count - 1; scriptIndex >= 0; --scriptIndex) { + var script = _allSimulationBehaviours[scriptIndex]; + var scriptPath = _pathCache.Create(script.transform); + + if (_pathCache.IsEqualOrAncestorOf(path, scriptPath)) { + if (script is NetworkBehaviour nb) { + _arrayBufferNB.Add(nb); + } + + objDirty |= PostprocessBehaviour(script); + + _allSimulationBehaviours.RemoveAt(scriptIndex); + + if (objExecutionOrder != null) { + // check if execution order is ok + if (TryGetExecutionOrder(script, out var scriptOrder)) { + if (objExecutionOrder <= scriptOrder) { + Log.Warn($"{obj} execution order is less or equal than of the script {script}. " + + $"Because the object is initially inactive, Spawned callback will be invoked before the script's Awake on activation."); + } + } else { + Log.Warn($"Unable to get execution order for {script}. " + + $"Because the object is initially inactive, Fusion is unable to guarantee " + + $"the script's Awake will be invoked before Spawned. Please implement {nameof(TryGetExecutionOrder)}."); + } + } + + } else if (_pathCache.Compare(path, scriptPath) < 0) { + // can't discard it yet + } else { + Debug.Assert(_pathCache.Compare(path, scriptPath) > 0); + break; + } + } + + _arrayBufferNB.Reverse(); + objDirty |= Set(obj, ref obj.NetworkedBehaviours, _arrayBufferNB); + + // handle flags + + var flags = obj.Flags; + + if (!flags.IsVersionCurrent()) { + flags = flags.SetCurrentVersion(); + } + + objDirty |= Set(obj, ref obj.Flags, flags); + + // what's left is nested network objects resolution + { + _arrayBufferNO.Clear(); + + // collect descendants; descendants should be continous without gaps here + int j = i - 1; + for (; j >= 0 && _pathCache.IsAncestorOf(path, _networkObjectsPaths[j]); --j) { + _arrayBufferNO.Add(_allNetworkObjects[j]); + } + + int descendantsBegin = j + 1; + Debug.Assert(_arrayBufferNO.Count == i - descendantsBegin); + + objDirty |= Set(obj, ref obj.NestedObjects, _arrayBufferNO); + } + + objDirty |= Set(obj, ref obj.SortKey, GetSortKey(obj)); + + if (objDirty) { + SetDirty(obj); + dirty = true; + } + } + + return new Result(dirty, countNO, countSB); + } finally { + _pathCache.Clear(); + _allNetworkObjects.Clear(); + _allSimulationBehaviours.Clear(); + + _networkObjectsPaths.Clear(); + + _arrayBufferNB.Clear(); + _arrayBufferNO.Clear(); + } + } + + private bool Set(MonoBehaviour host, ref T field, T value) { + if (!EqualityComparer.Default.Equals(field, value)) { + Log.Trace($"Object dirty: {host} ({field} vs {value})"); + field = value; + return true; + } else { + return false; + } + } + + private bool Set(MonoBehaviour host, ref T[] field, List value) { + var comparer = EqualityComparer.Default; + if (field == null || field.Length != value.Count || !field.SequenceEqual(value, comparer)) { + Log.Trace($"Object dirty: {host} ({field} vs {value})"); + field = value.ToArray(); + return true; + } else { + return false; + } + } + + public unsafe readonly struct TransformPath { + public const int MaxDepth = 10; + + public struct _Indices { + public fixed ushort Value[MaxDepth]; + } + + public readonly _Indices Indices; + public readonly ushort Depth; + public readonly ushort Next; + + internal TransformPath(ushort depth, ushort next, List indices, int offset, int count) { + Depth = depth; + Next = next; + + for (int i = 0; i < count; ++i) { + Indices.Value[i] = indices[i + offset]; + } + } + + public override string ToString() { + var builder = new StringBuilder(); + for (int i = 0; i < Depth && i < MaxDepth; ++i) { + if (i > 0) { + builder.Append("/"); + } + builder.Append(Indices.Value[i]); + } + + if (Depth > MaxDepth) { + Debug.Assert(Next > 0); + builder.Append($"/...[{Depth - MaxDepth}]"); + } + + return builder.ToString(); + } + } + + public sealed unsafe class TransformPathCache : IComparer, IEqualityComparer { + + private Dictionary _cache = new Dictionary(); + private List _siblingIndexStack = new List(); + private List _nexts = new List(); + + + public TransformPath Create(Transform transform) { + if (_cache.TryGetValue(transform, out var existing)) { + return existing; + } + + _siblingIndexStack.Clear(); + for (var tr = transform; tr != null; tr = tr.parent) { + _siblingIndexStack.Add(checked((ushort)tr.GetSiblingIndex())); + } + _siblingIndexStack.Reverse(); + + + var depth = checked((ushort)_siblingIndexStack.Count); + + ushort nextPlusOne = 0; + + if (depth > TransformPath.MaxDepth) { + + int i; + if (depth % TransformPath.MaxDepth != 0) { + // tail is going to be partially full + i = depth - (depth % TransformPath.MaxDepth); + } else { + // tail is going to be full + i = depth - TransformPath.MaxDepth; + } + + for (; i > 0; i -= TransformPath.MaxDepth) { + checked { + TransformPath path = new TransformPath((ushort)(depth - i), nextPlusOne, + _siblingIndexStack, i, Mathf.Min(TransformPath.MaxDepth, depth - i)); + _nexts.Add(path); + nextPlusOne = (ushort)_nexts.Count; + } + } + } + + var result = new TransformPath(depth, nextPlusOne, + _siblingIndexStack, 0, Mathf.Min(TransformPath.MaxDepth, depth)); + + _cache.Add(transform, result); + return result; + } + + public void Clear() { + _nexts.Clear(); + _cache.Clear(); + _siblingIndexStack.Clear(); + } + + public bool Equals(TransformPath x, TransformPath y) { + if (x.Depth != y.Depth) { + return false; + } + + return CompareToDepthUnchecked(x, y, x.Depth) == 0; + } + + public int GetHashCode(TransformPath obj) { + int hash = obj.Depth; + return GetHashCode(obj, hash); + } + + public int Compare(TransformPath x, TransformPath y) { + var diff = CompareToDepthUnchecked(x, y, Mathf.Min(x.Depth, y.Depth)); + if (diff != 0) { + return diff; + } + + return x.Depth - y.Depth; + } + + private int CompareToDepthUnchecked(in TransformPath x, in TransformPath y, int depth) { + for (int i = 0; i < depth && i < TransformPath.MaxDepth; ++i) { + int diff = x.Indices.Value[i] - y.Indices.Value[i]; + if (diff != 0) { + return diff; + } + } + + if (depth > TransformPath.MaxDepth) { + Debug.Assert(x.Next > 0); + Debug.Assert(y.Next > 0); + return CompareToDepthUnchecked(_nexts[x.Next - 1], _nexts[y.Next - 1], depth - TransformPath.MaxDepth); + } else { + return 0; + } + } + + private int GetHashCode(in TransformPath path, int hash) { + for (int i = 0; i < path.Depth && i < TransformPath.MaxDepth; ++i) { + hash = hash * 31 + path.Indices.Value[i]; + } + + if (path.Depth > TransformPath.MaxDepth) { + Debug.Assert(path.Next > 0); + hash = GetHashCode(_nexts[path.Next - 1], hash); + } + + return hash; + } + + public bool IsAncestorOf(in TransformPath x, in TransformPath y) { + if (x.Depth >= y.Depth) { + return false; + } + + return CompareToDepthUnchecked(x, y, x.Depth) == 0; + } + + public bool IsEqualOrAncestorOf(in TransformPath x, in TransformPath y) { + if (x.Depth > y.Depth) { + return false; + } + + return CompareToDepthUnchecked(x, y, x.Depth) == 0; + } + + public string Dump(in TransformPath x) { + var builder = new StringBuilder(); + + Dump(x, builder); + + return builder.ToString(); + } + + private void Dump(in TransformPath x, StringBuilder builder) { + for (int i = 0; i < x.Depth && i < TransformPath.MaxDepth; ++i) { + if (i > 0) { + builder.Append("/"); + } + builder.Append(x.Indices.Value[i]); + } + + if (x.Depth > TransformPath.MaxDepth) { + Debug.Assert(x.Next > 0); + builder.Append("/"); + Dump(_nexts[x.Next - 1], builder); + } + } + } + } +} + + +#endregion + + +#region Assets/Photon/Fusion/Runtime/NetworkPrefabSourceUnity.cs + +namespace Fusion { + using System; + using Object = UnityEngine.Object; + + [Serializable] + public class NetworkPrefabSourceStatic : NetworkAssetSourceStatic, INetworkPrefabSource { + public NetworkObjectGuid AssetGuid; + NetworkObjectGuid INetworkPrefabSource.AssetGuid => AssetGuid; + } + + [Serializable] + public class NetworkPrefabSourceStaticLazy : NetworkAssetSourceStaticLazy, INetworkPrefabSource { + public NetworkObjectGuid AssetGuid; + NetworkObjectGuid INetworkPrefabSource.AssetGuid => AssetGuid; + } + + [Serializable] + public class NetworkPrefabSourceResource : NetworkAssetSourceResource, INetworkPrefabSource { + public NetworkObjectGuid AssetGuid; + NetworkObjectGuid INetworkPrefabSource.AssetGuid => AssetGuid; + } + +#if FUSION_ENABLE_ADDRESSABLES && !FUSION_DISABLE_ADDRESSABLES + [Serializable] + public class NetworkPrefabSourceAddressable : NetworkAssetSourceAddressable, INetworkPrefabSource { + public NetworkObjectGuid AssetGuid; + NetworkObjectGuid INetworkPrefabSource.AssetGuid => AssetGuid; + } +#endif +} + +#endregion + + +#region Assets/Photon/Fusion/Runtime/Statistics/FusionStatisticsHelper.cs + +namespace Fusion.Statistics { + using System; + using UnityEngine; + + internal static class FusionStatisticsHelper { + public const float DEFAULT_GRAPH_HEIGHT = 150F; + public const float DEFAULT_HEADER_HEIGHT = 50F; + + internal static void GetStatGraphDefaultSettings(RenderSimStats stat, out string valueTextFormat, out float valueTextMultiplier, out bool ignoreZeroOnAverage, out bool ignoreZeroOnBuffer, out int accumulateTimeMs) { + + valueTextFormat = "{0:0}"; + valueTextMultiplier = 1f; + ignoreZeroOnAverage = false; + ignoreZeroOnBuffer = false; + accumulateTimeMs = 0; // Default is every update, so zero. + + switch (stat) { + case RenderSimStats.InPackets: + case RenderSimStats.OutPackets: + case RenderSimStats.InObjectUpdates: + case RenderSimStats.OutObjectUpdates: + valueTextFormat = "{0:0}"; + accumulateTimeMs = 1000; + break; + + case RenderSimStats.RTT: + valueTextFormat = "{0:0} ms"; + valueTextMultiplier = 1000; + ignoreZeroOnAverage = true; ignoreZeroOnBuffer = true; + break; + + case RenderSimStats.InBandwidth: + case RenderSimStats.OutBandwidth: + case RenderSimStats.InputInBandwidth: + case RenderSimStats.InputOutBandwidth: + valueTextFormat = "{0:0} B"; + accumulateTimeMs = 1000; + break; + + case RenderSimStats.AverageInPacketSize: + case RenderSimStats.AverageOutPacketSize: + valueTextFormat = "{0:0} B"; + ignoreZeroOnBuffer = true; + ignoreZeroOnAverage = true; + break; + + case RenderSimStats.Resimulations: + valueTextFormat = "{0:0}"; + break; + case RenderSimStats.ForwardTicks: + valueTextFormat = "{0:0}"; + break; + + case RenderSimStats.TimeResets: + case RenderSimStats.SimulationSpeed: + case RenderSimStats.InterpolationSpeed: + valueTextFormat = "{0:0}"; + break; + + // All time stats are normalized to use seconds, so 1000 multiplier to be ms. + case RenderSimStats.InputReceiveDelta: + case RenderSimStats.StateReceiveDelta: + case RenderSimStats.SimulationTimeOffset: + case RenderSimStats.InterpolationOffset: + valueTextMultiplier = 1000; + valueTextFormat = "{0:0} ms"; + break; + + case RenderSimStats.GeneralAllocatedMemoryInUse: + case RenderSimStats.ObjectsAllocatedMemoryInUse: + case RenderSimStats.ObjectsAllocatedMemoryFree: + case RenderSimStats.GeneralAllocatedMemoryFree: + valueTextFormat = "{0:0} B"; + break; + + case RenderSimStats.WordsWrittenCount: + case RenderSimStats.WordsReadCount: + valueTextFormat = "{0:0}"; + ignoreZeroOnBuffer = true; + accumulateTimeMs = 1000; + break; + case RenderSimStats.WordsWrittenSize: + case RenderSimStats.WordsReadSize: + valueTextFormat = "{0:0} B"; + ignoreZeroOnBuffer = true; + accumulateTimeMs = 1000; + break; + + default: + valueTextFormat = "{0:0}"; + break; + } + } + + internal static float GetStatDataFromSnapshot(RenderSimStats stat, FusionStatisticsSnapshot simulationStatsSnapshot) { + switch (stat) { + // Sim stats + case RenderSimStats.InPackets: + return simulationStatsSnapshot.InPackets; + case RenderSimStats.OutPackets: + return simulationStatsSnapshot.OutPackets; + case RenderSimStats.RTT: + return simulationStatsSnapshot.RoundTripTime; + case RenderSimStats.InBandwidth: + return simulationStatsSnapshot.InBandwidth; + case RenderSimStats.OutBandwidth: + return simulationStatsSnapshot.OutBandwidth; + case RenderSimStats.Resimulations: + return simulationStatsSnapshot.Resimulations; + case RenderSimStats.ForwardTicks: + return simulationStatsSnapshot.ForwardTicks; + case RenderSimStats.InputInBandwidth: + return simulationStatsSnapshot.InputInBandwidth; + case RenderSimStats.InputOutBandwidth: + return simulationStatsSnapshot.InputOutBandwidth; + case RenderSimStats.AverageInPacketSize: + return simulationStatsSnapshot.InBandwidth / Mathf.Max(simulationStatsSnapshot.InPackets, 1); + case RenderSimStats.AverageOutPacketSize: + return simulationStatsSnapshot.OutBandwidth / Mathf.Max(simulationStatsSnapshot.OutPackets, 1); + case RenderSimStats.InObjectUpdates: + return simulationStatsSnapshot.InObjectUpdates; + case RenderSimStats.OutObjectUpdates: + return simulationStatsSnapshot.OutObjectUpdates; + case RenderSimStats.ObjectsAllocatedMemoryInUse: + return simulationStatsSnapshot.ObjectsAllocMemoryUsedInBytes; + case RenderSimStats.GeneralAllocatedMemoryInUse: + return simulationStatsSnapshot.GeneralAllocMemoryUsedInBytes; + case RenderSimStats.ObjectsAllocatedMemoryFree: + return simulationStatsSnapshot.ObjectsAllocMemoryFreeInBytes; + case RenderSimStats.GeneralAllocatedMemoryFree: + return simulationStatsSnapshot.GeneralAllocMemoryFreeInBytes; + case RenderSimStats.WordsWrittenCount: + return simulationStatsSnapshot.WordsWrittenCount; + case RenderSimStats.WordsWrittenSize: + return simulationStatsSnapshot.WordsWrittenSize; + case RenderSimStats.WordsReadCount: + return simulationStatsSnapshot.WordsReadCount; + case RenderSimStats.WordsReadSize: + return simulationStatsSnapshot.WordsReadSize; + + // Time stats + case RenderSimStats.InputReceiveDelta: + return simulationStatsSnapshot.InputReceiveDelta; + case RenderSimStats.TimeResets: + return simulationStatsSnapshot.TimeResets; + case RenderSimStats.StateReceiveDelta: + return simulationStatsSnapshot.StateReceiveDelta; + case RenderSimStats.SimulationTimeOffset: + return simulationStatsSnapshot.SimulationTimeOffset; + case RenderSimStats.SimulationSpeed: + return simulationStatsSnapshot.SimulationSpeed; + case RenderSimStats.InterpolationOffset: + return simulationStatsSnapshot.InterpolationOffset; + case RenderSimStats.InterpolationSpeed: + return simulationStatsSnapshot.InterpolationSpeed; + } + + return default; + } + } +} + +#endregion + + +#region Assets/Photon/Fusion/Runtime/Statistics/FusionStatsGraphBase.cs + +namespace Fusion.Statistics { + using UnityEngine; + using UnityEngine.UI; + using System; + using System.Globalization; + + public abstract partial class FusionStatsGraphBase : MonoBehaviour { + + private static readonly int Samples = Shader.PropertyToID(SHADER_PROPERTY_SAMPLES); + private static readonly IFormatProvider _formatProvider = CultureInfo.GetCultureInfo("en-US"); + + private const string SHADER_PROPERTY_VALUES = "_Values"; + private const string SHADER_PROPERTY_SAMPLES = "_Samples"; + private const string SHADER_PROPERTY_THRESHOLD_1 = "_Threshold1"; + private const string SHADER_PROPERTY_THRESHOLD_2 = "_Threshold2"; + private const string SHADER_PROPERTY_THRESHOLD_3 = "_Threshold3"; + private const string SHADER_PROPERTY_AVERAGE = "_Average"; + + private int _valuesShaderPropertyID = Shader.PropertyToID(SHADER_PROPERTY_VALUES); + private int _threshold1ShaderPropertyID = Shader.PropertyToID(SHADER_PROPERTY_THRESHOLD_1); + private int _threshold2ShaderPropertyID = Shader.PropertyToID(SHADER_PROPERTY_THRESHOLD_2); + private int _threshold3ShaderPropertyID = Shader.PropertyToID(SHADER_PROPERTY_THRESHOLD_3); + private int _averageShaderPropertyID = Shader.PropertyToID(SHADER_PROPERTY_AVERAGE); + + // serialize private + [SerializeField] private RectTransform _render; + [SerializeField] private RectTransform _header; + [SerializeField] private Image _targetImage; + [SerializeField] private Button _toggleButton; + [SerializeField] private Text _averageValueText; + [SerializeField] private Text _peakValueText; + [SerializeField] private Text _currentValueText; + [Space] [SerializeField] private Text _threshold1Text; + [SerializeField] private Text _threshold2Text; + [SerializeField] private Text _threshold3Text; + + //protected + [Space] [SerializeField] protected float _valueTextMultiplier = 1f; + [SerializeField] [Range(60, 540)] protected int _maxSamples = 300; + [SerializeField] protected float _threshold1; + [SerializeField] protected float _threshold2; + [SerializeField] protected float _threshold3; + [SerializeField] protected bool _ignoreZeroedValuesOnAverageCalculation; + [SerializeField] protected bool _ignoreZeroedValuesOnBuffer; + [SerializeField] protected float _valuesTextUpdateDelay = .1f; + + private FusionStatBuffer _bufferValues; + private float[] _bufferNormalizedValues; + + private float _headerHeight = 25; + private float _renderHeight = 125; + private VerticalLayoutGroup _parentLayoutGroup; + + private float _invertedRenderMaxValue; + private float _lastUpdateTime; + private Material _material; + + private bool Initialized => _bufferNormalizedValues != null; + + protected virtual void Initialize(int accumulateTimeMs) { + _material = new Material(_targetImage.material); + _targetImage.material = _material; + _bufferValues = new FusionStatBuffer(_maxSamples, _ignoreZeroedValuesOnAverageCalculation, accumulateTimeMs); + _bufferNormalizedValues = new float[_maxSamples]; + _parentLayoutGroup = GetComponentInParent(); + + _lookupTable = null; + _lookupMultiplier = 1.0f; + + switch (_valueTextFormat) { + case "{0:0}": { + _lookupTable = LOOKUP_TABLE_0; + _lookupMultiplier = 1.0f; + break; + } + case "{0:0} ms": { + _lookupTable = LOOKUP_TABLE_0ms; + _lookupMultiplier = 1.0f; + break; + } + case "{0:0} B": { + _lookupTable = LOOKUP_TABLE_0_BYTES; + _lookupMultiplier = 1.0f; + break; + } + case "{0:0.00} ms": { + _lookupTable = LOOKUP_TABLE_0_00ms; + _lookupMultiplier = 100.0f; + break; + } + } + + Restore(); + } + + protected virtual void OnEnable() { + var statsRender = GetComponentInParent(true); + if (statsRender) { + statsRender.RegisterGraph(this); + Restore(); + } + } + + protected virtual void OnDisable() { + var statsRender = GetComponentInParent(true); + if (statsRender) { + statsRender.UnregisterGraph(this); + Restore(); + } + } + + protected virtual void AddValueToBuffer(float value, ref DateTime now) { + if (_ignoreZeroedValuesOnBuffer && value == 0) return; + + _bufferValues.Add(value, ref now); + + _invertedRenderMaxValue = 1 / _bufferValues.MaxValue; + + _invertedRenderMaxValue *= .9f; // 10 % more to fell better on render + + for (int i = 0, k = _bufferValues.Index; i < _maxSamples; i++, k = (k+1)%_bufferValues.Length) { + _bufferNormalizedValues[i] = _bufferValues[k] * _invertedRenderMaxValue; + } + + SetGraphValues(_bufferNormalizedValues); + OnSetValues(); + } + + protected virtual void Refit() { + var finalHeight = 0f; + var rect = (RectTransform)transform; + + if (_render.gameObject.activeSelf) + finalHeight += _renderHeight; + if (_header.gameObject.activeSelf) + finalHeight += _headerHeight; + + rect.sizeDelta = new Vector2(rect.sizeDelta.x, finalHeight); + _parentLayoutGroup.enabled = false; + _parentLayoutGroup.enabled = true; + } + + protected virtual void Restore() { + if (Initialized == false) return; + + _material.SetInteger(Samples, _maxSamples); + // The normalized one needs to be cleaned. + Array.Clear(_bufferNormalizedValues, 0, _maxSamples); + Refit(); + } + + public virtual void ToggleRenderDisplay() { + var active = _render.gameObject.activeSelf; + _render.gameObject.SetActive(!active); + + if (active) { + OnDisable(); + _toggleButton.transform.rotation = Quaternion.Euler(0, 0, 90); + } else { + _toggleButton.transform.rotation = Quaternion.identity; + OnEnable(); + } + + Refit(); + } + + protected virtual void OnSetValues() { + if (Time.time >= _lastUpdateTime + _valuesTextUpdateDelay) { + _lastUpdateTime = Time.time; + + _averageValueText.text = GetValueText(_bufferValues.AverageValue * _valueTextMultiplier); + _peakValueText.text = GetValueText(_bufferValues.MaxValue * _valueTextMultiplier); + } + + _currentValueText.text = GetValueText(_bufferValues.LatestValue * _valueTextMultiplier); + + float normalizedThreshold1 = _threshold1 * _invertedRenderMaxValue; + float normalizedThreshold2 = _threshold2 * _invertedRenderMaxValue; + float normalizedThreshold3 = _threshold3 * _invertedRenderMaxValue; + + _material.SetFloat(_threshold1ShaderPropertyID, normalizedThreshold1); + _material.SetFloat(_threshold2ShaderPropertyID, normalizedThreshold2); + _material.SetFloat(_threshold3ShaderPropertyID, normalizedThreshold3); + + _threshold1Text.text = GetValueText(_threshold1 * _valueTextMultiplier); + _threshold2Text.text = GetValueText(_threshold2 * _valueTextMultiplier); + _threshold3Text.text = GetValueText(_threshold3 * _valueTextMultiplier); + + UpdateThresholdPosition(_threshold1Text, normalizedThreshold1); + UpdateThresholdPosition(_threshold2Text, normalizedThreshold2); + UpdateThresholdPosition(_threshold3Text, normalizedThreshold3); + } + + protected void SetThresholds(float threshold1, float threshold2, float threshold3) { + _threshold1 = threshold1 / _valueTextMultiplier; + _threshold2 = threshold2 / _valueTextMultiplier; + _threshold3 = threshold3 / _valueTextMultiplier; + } + + protected void SetIgnoreZeroValues(bool ignoreZeroOnAverage, bool ignoreZeroOnBuffer) { + _ignoreZeroedValuesOnAverageCalculation = ignoreZeroOnAverage; + _ignoreZeroedValuesOnBuffer = ignoreZeroOnBuffer; + _bufferValues.SetIgnoreZeroOnAverage(ignoreZeroOnAverage); + } + + protected void SetValueTextFormat(string value) { + _valueTextFormat = value; + } + + protected void SetValueTextMultiplier(float value) { + _valueTextMultiplier = value; + } + + protected void SetAccumulateTime(int accumulateTimeMs) { + _bufferValues.SetAccumulateTime(accumulateTimeMs); + } + + private void UpdateThresholdPosition(Text text, float thresholdNormalized) { + Vector3 position = text.rectTransform.anchoredPosition3D; + var renderHalfHeight = _targetImage.rectTransform.rect.height * .5f; + + position.y = RemapValue(thresholdNormalized, 0, 1, -renderHalfHeight, renderHalfHeight); + text.rectTransform.anchoredPosition3D = position; + text.gameObject.SetActive(thresholdNormalized < 1 && thresholdNormalized > 0); + } + + protected virtual void SetGraphValues(float[] values) { + if (values == null || values.Length == 0) + return; + + _material.SetFloat(_averageShaderPropertyID, _bufferValues.AverageValue); + _material.SetFloatArray(_valuesShaderPropertyID, values); + } + + private float RemapValue(float value, float iMin, float iMax, float oMin, float oMax) { + if (float.IsNaN(value)) return oMin; + + var t = Mathf.InverseLerp(iMin, iMax, value); + return Mathf.Lerp(oMin, oMax, t); + } + + public abstract void UpdateGraph(NetworkRunner runner, FusionStatisticsManager statisticsManager, ref DateTime now); + + internal struct FusionStatBuffer { + private readonly float[] _buffer; + private int _index; + private int _count; + private int _zeroCount; + private bool _ignoreZeroOnAverage; + private TimeSpan _accumulateTimeSpan; + + private float _sum; + private float _max; + private float _accumulated; + private DateTime _lastBufferInsertTime; + + public int Index => _index; + public int Length => _buffer.Length; + public float MaxValue => _max; + + + public FusionStatBuffer(int size, bool ignoreZeroOnAverage, int accumulateTimeMs) { + _buffer = new float[size]; + _index = 0; + _count = 0; + _zeroCount = 0; + _ignoreZeroOnAverage = ignoreZeroOnAverage; + _accumulateTimeSpan = TimeSpan.FromMilliseconds(accumulateTimeMs); + _sum = 0; + _max = float.MinValue; + _accumulated = 0; + _lastBufferInsertTime = DateTime.MinValue; + } + + public void SetAccumulateTime(int accumulateTimeMs) { + _accumulateTimeSpan = TimeSpan.FromMilliseconds(accumulateTimeMs); + } + + public void SetIgnoreZeroOnAverage(bool value) { + _ignoreZeroOnAverage = value; + } + + public float this[int index] => _buffer[index]; + + public void Add(float value, ref DateTime now) { + + _accumulated += value; + + if (now - _lastBufferInsertTime >= _accumulateTimeSpan) { + AddOnBuffer(_accumulated); + _accumulated = 0; + _lastBufferInsertTime = now; + } + } + + private void AddOnBuffer(float value) { + + var recalculateMax = false; + + if (_count == _buffer.Length) { + var removingValue = _buffer[_index]; + _sum -= removingValue; + + if (removingValue == 0) + _zeroCount = Mathf.Max(0, _zeroCount-1); + + if (removingValue >= _max) { + recalculateMax = true; + } + } else { + _count++; + } + + if (value == 0) + _zeroCount = Mathf.Min(_count-1, _zeroCount+1); + + _buffer[_index] = value; + + _sum += value; + + if (value > _max) { + _max = value; + } + + _index = (_index + 1) % _buffer.Length; + + if (recalculateMax) { + _max = CalculateMax(); + } + } + + public float LatestValue { + get { + if (_count == 0) + return 0; + return _buffer[(_index - 1 + _buffer.Length) % _buffer.Length]; + } + } + + public float AverageValue { + get { + if (_count == 0) + return 0f; + + return _sum / (_ignoreZeroOnAverage ? _count - _zeroCount : _count); + } + } + + private float CalculateMax() + { + float max = float.MinValue; + for (int i = 0; i < _count; i++) { + if (_buffer[i] > max) { + max = _buffer[i]; + } + } + return max; + } + } + } +} + +#endregion + + +#region Assets/Photon/Fusion/Runtime/Statistics/FusionStatsLookup.cs + +namespace Fusion.Statistics { +using UnityEngine; + + public partial class FusionStatsGraphBase { + + [SerializeField] + private string _valueTextFormat = "{0}"; + private string[][] _lookupTable; + private float _lookupMultiplier; + + private string GetValueText(float value) + { + if (_lookupTable != null) + { + int rows = _lookupTable.Length; + int columns = _lookupTable[0].Length; + + int intValue = Mathf.RoundToInt(value * _lookupMultiplier); + if (intValue >= 0 && intValue < rows * columns) + { + int row = intValue % rows; + int column = intValue / rows; + + return _lookupTable[row][column]; + } + } + + return string.Format(_formatProvider, _valueTextFormat, value); + } + + private static readonly string[][] LOOKUP_TABLE_0 = + { + new string[] { "0","100","200","300","400","500","600","700","800","900", }, + new string[] { "1","101","201","301","401","501","601","701","801","901", }, + new string[] { "2","102","202","302","402","502","602","702","802","902", }, + new string[] { "3","103","203","303","403","503","603","703","803","903", }, + new string[] { "4","104","204","304","404","504","604","704","804","904", }, + new string[] { "5","105","205","305","405","505","605","705","805","905", }, + new string[] { "6","106","206","306","406","506","606","706","806","906", }, + new string[] { "7","107","207","307","407","507","607","707","807","907", }, + new string[] { "8","108","208","308","408","508","608","708","808","908", }, + new string[] { "9","109","209","309","409","509","609","709","809","909", }, + new string[] { "10","110","210","310","410","510","610","710","810","910", }, + new string[] { "11","111","211","311","411","511","611","711","811","911", }, + new string[] { "12","112","212","312","412","512","612","712","812","912", }, + new string[] { "13","113","213","313","413","513","613","713","813","913", }, + new string[] { "14","114","214","314","414","514","614","714","814","914", }, + new string[] { "15","115","215","315","415","515","615","715","815","915", }, + new string[] { "16","116","216","316","416","516","616","716","816","916", }, + new string[] { "17","117","217","317","417","517","617","717","817","917", }, + new string[] { "18","118","218","318","418","518","618","718","818","918", }, + new string[] { "19","119","219","319","419","519","619","719","819","919", }, + new string[] { "20","120","220","320","420","520","620","720","820","920", }, + new string[] { "21","121","221","321","421","521","621","721","821","921", }, + new string[] { "22","122","222","322","422","522","622","722","822","922", }, + new string[] { "23","123","223","323","423","523","623","723","823","923", }, + new string[] { "24","124","224","324","424","524","624","724","824","924", }, + new string[] { "25","125","225","325","425","525","625","725","825","925", }, + new string[] { "26","126","226","326","426","526","626","726","826","926", }, + new string[] { "27","127","227","327","427","527","627","727","827","927", }, + new string[] { "28","128","228","328","428","528","628","728","828","928", }, + new string[] { "29","129","229","329","429","529","629","729","829","929", }, + new string[] { "30","130","230","330","430","530","630","730","830","930", }, + new string[] { "31","131","231","331","431","531","631","731","831","931", }, + new string[] { "32","132","232","332","432","532","632","732","832","932", }, + new string[] { "33","133","233","333","433","533","633","733","833","933", }, + new string[] { "34","134","234","334","434","534","634","734","834","934", }, + new string[] { "35","135","235","335","435","535","635","735","835","935", }, + new string[] { "36","136","236","336","436","536","636","736","836","936", }, + new string[] { "37","137","237","337","437","537","637","737","837","937", }, + new string[] { "38","138","238","338","438","538","638","738","838","938", }, + new string[] { "39","139","239","339","439","539","639","739","839","939", }, + new string[] { "40","140","240","340","440","540","640","740","840","940", }, + new string[] { "41","141","241","341","441","541","641","741","841","941", }, + new string[] { "42","142","242","342","442","542","642","742","842","942", }, + new string[] { "43","143","243","343","443","543","643","743","843","943", }, + new string[] { "44","144","244","344","444","544","644","744","844","944", }, + new string[] { "45","145","245","345","445","545","645","745","845","945", }, + new string[] { "46","146","246","346","446","546","646","746","846","946", }, + new string[] { "47","147","247","347","447","547","647","747","847","947", }, + new string[] { "48","148","248","348","448","548","648","748","848","948", }, + new string[] { "49","149","249","349","449","549","649","749","849","949", }, + new string[] { "50","150","250","350","450","550","650","750","850","950", }, + new string[] { "51","151","251","351","451","551","651","751","851","951", }, + new string[] { "52","152","252","352","452","552","652","752","852","952", }, + new string[] { "53","153","253","353","453","553","653","753","853","953", }, + new string[] { "54","154","254","354","454","554","654","754","854","954", }, + new string[] { "55","155","255","355","455","555","655","755","855","955", }, + new string[] { "56","156","256","356","456","556","656","756","856","956", }, + new string[] { "57","157","257","357","457","557","657","757","857","957", }, + new string[] { "58","158","258","358","458","558","658","758","858","958", }, + new string[] { "59","159","259","359","459","559","659","759","859","959", }, + new string[] { "60","160","260","360","460","560","660","760","860","960", }, + new string[] { "61","161","261","361","461","561","661","761","861","961", }, + new string[] { "62","162","262","362","462","562","662","762","862","962", }, + new string[] { "63","163","263","363","463","563","663","763","863","963", }, + new string[] { "64","164","264","364","464","564","664","764","864","964", }, + new string[] { "65","165","265","365","465","565","665","765","865","965", }, + new string[] { "66","166","266","366","466","566","666","766","866","966", }, + new string[] { "67","167","267","367","467","567","667","767","867","967", }, + new string[] { "68","168","268","368","468","568","668","768","868","968", }, + new string[] { "69","169","269","369","469","569","669","769","869","969", }, + new string[] { "70","170","270","370","470","570","670","770","870","970", }, + new string[] { "71","171","271","371","471","571","671","771","871","971", }, + new string[] { "72","172","272","372","472","572","672","772","872","972", }, + new string[] { "73","173","273","373","473","573","673","773","873","973", }, + new string[] { "74","174","274","374","474","574","674","774","874","974", }, + new string[] { "75","175","275","375","475","575","675","775","875","975", }, + new string[] { "76","176","276","376","476","576","676","776","876","976", }, + new string[] { "77","177","277","377","477","577","677","777","877","977", }, + new string[] { "78","178","278","378","478","578","678","778","878","978", }, + new string[] { "79","179","279","379","479","579","679","779","879","979", }, + new string[] { "80","180","280","380","480","580","680","780","880","980", }, + new string[] { "81","181","281","381","481","581","681","781","881","981", }, + new string[] { "82","182","282","382","482","582","682","782","882","982", }, + new string[] { "83","183","283","383","483","583","683","783","883","983", }, + new string[] { "84","184","284","384","484","584","684","784","884","984", }, + new string[] { "85","185","285","385","485","585","685","785","885","985", }, + new string[] { "86","186","286","386","486","586","686","786","886","986", }, + new string[] { "87","187","287","387","487","587","687","787","887","987", }, + new string[] { "88","188","288","388","488","588","688","788","888","988", }, + new string[] { "89","189","289","389","489","589","689","789","889","989", }, + new string[] { "90","190","290","390","490","590","690","790","890","990", }, + new string[] { "91","191","291","391","491","591","691","791","891","991", }, + new string[] { "92","192","292","392","492","592","692","792","892","992", }, + new string[] { "93","193","293","393","493","593","693","793","893","993", }, + new string[] { "94","194","294","394","494","594","694","794","894","994", }, + new string[] { "95","195","295","395","495","595","695","795","895","995", }, + new string[] { "96","196","296","396","496","596","696","796","896","996", }, + new string[] { "97","197","297","397","497","597","697","797","897","997", }, + new string[] { "98","198","298","398","498","598","698","798","898","998", }, + new string[] { "99","199","299","399","499","599","699","799","899","999", }, + }; + + private static readonly string[][] LOOKUP_TABLE_0ms = + { + new string[] { "0ms","100ms","200ms","300ms","400ms","500ms","600ms","700ms","800ms","900ms", }, + new string[] { "1ms","101ms","201ms","301ms","401ms","501ms","601ms","701ms","801ms","901ms", }, + new string[] { "2ms","102ms","202ms","302ms","402ms","502ms","602ms","702ms","802ms","902ms", }, + new string[] { "3ms","103ms","203ms","303ms","403ms","503ms","603ms","703ms","803ms","903ms", }, + new string[] { "4ms","104ms","204ms","304ms","404ms","504ms","604ms","704ms","804ms","904ms", }, + new string[] { "5ms","105ms","205ms","305ms","405ms","505ms","605ms","705ms","805ms","905ms", }, + new string[] { "6ms","106ms","206ms","306ms","406ms","506ms","606ms","706ms","806ms","906ms", }, + new string[] { "7ms","107ms","207ms","307ms","407ms","507ms","607ms","707ms","807ms","907ms", }, + new string[] { "8ms","108ms","208ms","308ms","408ms","508ms","608ms","708ms","808ms","908ms", }, + new string[] { "9ms","109ms","209ms","309ms","409ms","509ms","609ms","709ms","809ms","909ms", }, + new string[] { "10ms","110ms","210ms","310ms","410ms","510ms","610ms","710ms","810ms","910ms", }, + new string[] { "11ms","111ms","211ms","311ms","411ms","511ms","611ms","711ms","811ms","911ms", }, + new string[] { "12ms","112ms","212ms","312ms","412ms","512ms","612ms","712ms","812ms","912ms", }, + new string[] { "13ms","113ms","213ms","313ms","413ms","513ms","613ms","713ms","813ms","913ms", }, + new string[] { "14ms","114ms","214ms","314ms","414ms","514ms","614ms","714ms","814ms","914ms", }, + new string[] { "15ms","115ms","215ms","315ms","415ms","515ms","615ms","715ms","815ms","915ms", }, + new string[] { "16ms","116ms","216ms","316ms","416ms","516ms","616ms","716ms","816ms","916ms", }, + new string[] { "17ms","117ms","217ms","317ms","417ms","517ms","617ms","717ms","817ms","917ms", }, + new string[] { "18ms","118ms","218ms","318ms","418ms","518ms","618ms","718ms","818ms","918ms", }, + new string[] { "19ms","119ms","219ms","319ms","419ms","519ms","619ms","719ms","819ms","919ms", }, + new string[] { "20ms","120ms","220ms","320ms","420ms","520ms","620ms","720ms","820ms","920ms", }, + new string[] { "21ms","121ms","221ms","321ms","421ms","521ms","621ms","721ms","821ms","921ms", }, + new string[] { "22ms","122ms","222ms","322ms","422ms","522ms","622ms","722ms","822ms","922ms", }, + new string[] { "23ms","123ms","223ms","323ms","423ms","523ms","623ms","723ms","823ms","923ms", }, + new string[] { "24ms","124ms","224ms","324ms","424ms","524ms","624ms","724ms","824ms","924ms", }, + new string[] { "25ms","125ms","225ms","325ms","425ms","525ms","625ms","725ms","825ms","925ms", }, + new string[] { "26ms","126ms","226ms","326ms","426ms","526ms","626ms","726ms","826ms","926ms", }, + new string[] { "27ms","127ms","227ms","327ms","427ms","527ms","627ms","727ms","827ms","927ms", }, + new string[] { "28ms","128ms","228ms","328ms","428ms","528ms","628ms","728ms","828ms","928ms", }, + new string[] { "29ms","129ms","229ms","329ms","429ms","529ms","629ms","729ms","829ms","929ms", }, + new string[] { "30ms","130ms","230ms","330ms","430ms","530ms","630ms","730ms","830ms","930ms", }, + new string[] { "31ms","131ms","231ms","331ms","431ms","531ms","631ms","731ms","831ms","931ms", }, + new string[] { "32ms","132ms","232ms","332ms","432ms","532ms","632ms","732ms","832ms","932ms", }, + new string[] { "33ms","133ms","233ms","333ms","433ms","533ms","633ms","733ms","833ms","933ms", }, + new string[] { "34ms","134ms","234ms","334ms","434ms","534ms","634ms","734ms","834ms","934ms", }, + new string[] { "35ms","135ms","235ms","335ms","435ms","535ms","635ms","735ms","835ms","935ms", }, + new string[] { "36ms","136ms","236ms","336ms","436ms","536ms","636ms","736ms","836ms","936ms", }, + new string[] { "37ms","137ms","237ms","337ms","437ms","537ms","637ms","737ms","837ms","937ms", }, + new string[] { "38ms","138ms","238ms","338ms","438ms","538ms","638ms","738ms","838ms","938ms", }, + new string[] { "39ms","139ms","239ms","339ms","439ms","539ms","639ms","739ms","839ms","939ms", }, + new string[] { "40ms","140ms","240ms","340ms","440ms","540ms","640ms","740ms","840ms","940ms", }, + new string[] { "41ms","141ms","241ms","341ms","441ms","541ms","641ms","741ms","841ms","941ms", }, + new string[] { "42ms","142ms","242ms","342ms","442ms","542ms","642ms","742ms","842ms","942ms", }, + new string[] { "43ms","143ms","243ms","343ms","443ms","543ms","643ms","743ms","843ms","943ms", }, + new string[] { "44ms","144ms","244ms","344ms","444ms","544ms","644ms","744ms","844ms","944ms", }, + new string[] { "45ms","145ms","245ms","345ms","445ms","545ms","645ms","745ms","845ms","945ms", }, + new string[] { "46ms","146ms","246ms","346ms","446ms","546ms","646ms","746ms","846ms","946ms", }, + new string[] { "47ms","147ms","247ms","347ms","447ms","547ms","647ms","747ms","847ms","947ms", }, + new string[] { "48ms","148ms","248ms","348ms","448ms","548ms","648ms","748ms","848ms","948ms", }, + new string[] { "49ms","149ms","249ms","349ms","449ms","549ms","649ms","749ms","849ms","949ms", }, + new string[] { "50ms","150ms","250ms","350ms","450ms","550ms","650ms","750ms","850ms","950ms", }, + new string[] { "51ms","151ms","251ms","351ms","451ms","551ms","651ms","751ms","851ms","951ms", }, + new string[] { "52ms","152ms","252ms","352ms","452ms","552ms","652ms","752ms","852ms","952ms", }, + new string[] { "53ms","153ms","253ms","353ms","453ms","553ms","653ms","753ms","853ms","953ms", }, + new string[] { "54ms","154ms","254ms","354ms","454ms","554ms","654ms","754ms","854ms","954ms", }, + new string[] { "55ms","155ms","255ms","355ms","455ms","555ms","655ms","755ms","855ms","955ms", }, + new string[] { "56ms","156ms","256ms","356ms","456ms","556ms","656ms","756ms","856ms","956ms", }, + new string[] { "57ms","157ms","257ms","357ms","457ms","557ms","657ms","757ms","857ms","957ms", }, + new string[] { "58ms","158ms","258ms","358ms","458ms","558ms","658ms","758ms","858ms","958ms", }, + new string[] { "59ms","159ms","259ms","359ms","459ms","559ms","659ms","759ms","859ms","959ms", }, + new string[] { "60ms","160ms","260ms","360ms","460ms","560ms","660ms","760ms","860ms","960ms", }, + new string[] { "61ms","161ms","261ms","361ms","461ms","561ms","661ms","761ms","861ms","961ms", }, + new string[] { "62ms","162ms","262ms","362ms","462ms","562ms","662ms","762ms","862ms","962ms", }, + new string[] { "63ms","163ms","263ms","363ms","463ms","563ms","663ms","763ms","863ms","963ms", }, + new string[] { "64ms","164ms","264ms","364ms","464ms","564ms","664ms","764ms","864ms","964ms", }, + new string[] { "65ms","165ms","265ms","365ms","465ms","565ms","665ms","765ms","865ms","965ms", }, + new string[] { "66ms","166ms","266ms","366ms","466ms","566ms","666ms","766ms","866ms","966ms", }, + new string[] { "67ms","167ms","267ms","367ms","467ms","567ms","667ms","767ms","867ms","967ms", }, + new string[] { "68ms","168ms","268ms","368ms","468ms","568ms","668ms","768ms","868ms","968ms", }, + new string[] { "69ms","169ms","269ms","369ms","469ms","569ms","669ms","769ms","869ms","969ms", }, + new string[] { "70ms","170ms","270ms","370ms","470ms","570ms","670ms","770ms","870ms","970ms", }, + new string[] { "71ms","171ms","271ms","371ms","471ms","571ms","671ms","771ms","871ms","971ms", }, + new string[] { "72ms","172ms","272ms","372ms","472ms","572ms","672ms","772ms","872ms","972ms", }, + new string[] { "73ms","173ms","273ms","373ms","473ms","573ms","673ms","773ms","873ms","973ms", }, + new string[] { "74ms","174ms","274ms","374ms","474ms","574ms","674ms","774ms","874ms","974ms", }, + new string[] { "75ms","175ms","275ms","375ms","475ms","575ms","675ms","775ms","875ms","975ms", }, + new string[] { "76ms","176ms","276ms","376ms","476ms","576ms","676ms","776ms","876ms","976ms", }, + new string[] { "77ms","177ms","277ms","377ms","477ms","577ms","677ms","777ms","877ms","977ms", }, + new string[] { "78ms","178ms","278ms","378ms","478ms","578ms","678ms","778ms","878ms","978ms", }, + new string[] { "79ms","179ms","279ms","379ms","479ms","579ms","679ms","779ms","879ms","979ms", }, + new string[] { "80ms","180ms","280ms","380ms","480ms","580ms","680ms","780ms","880ms","980ms", }, + new string[] { "81ms","181ms","281ms","381ms","481ms","581ms","681ms","781ms","881ms","981ms", }, + new string[] { "82ms","182ms","282ms","382ms","482ms","582ms","682ms","782ms","882ms","982ms", }, + new string[] { "83ms","183ms","283ms","383ms","483ms","583ms","683ms","783ms","883ms","983ms", }, + new string[] { "84ms","184ms","284ms","384ms","484ms","584ms","684ms","784ms","884ms","984ms", }, + new string[] { "85ms","185ms","285ms","385ms","485ms","585ms","685ms","785ms","885ms","985ms", }, + new string[] { "86ms","186ms","286ms","386ms","486ms","586ms","686ms","786ms","886ms","986ms", }, + new string[] { "87ms","187ms","287ms","387ms","487ms","587ms","687ms","787ms","887ms","987ms", }, + new string[] { "88ms","188ms","288ms","388ms","488ms","588ms","688ms","788ms","888ms","988ms", }, + new string[] { "89ms","189ms","289ms","389ms","489ms","589ms","689ms","789ms","889ms","989ms", }, + new string[] { "90ms","190ms","290ms","390ms","490ms","590ms","690ms","790ms","890ms","990ms", }, + new string[] { "91ms","191ms","291ms","391ms","491ms","591ms","691ms","791ms","891ms","991ms", }, + new string[] { "92ms","192ms","292ms","392ms","492ms","592ms","692ms","792ms","892ms","992ms", }, + new string[] { "93ms","193ms","293ms","393ms","493ms","593ms","693ms","793ms","893ms","993ms", }, + new string[] { "94ms","194ms","294ms","394ms","494ms","594ms","694ms","794ms","894ms","994ms", }, + new string[] { "95ms","195ms","295ms","395ms","495ms","595ms","695ms","795ms","895ms","995ms", }, + new string[] { "96ms","196ms","296ms","396ms","496ms","596ms","696ms","796ms","896ms","996ms", }, + new string[] { "97ms","197ms","297ms","397ms","497ms","597ms","697ms","797ms","897ms","997ms", }, + new string[] { "98ms","198ms","298ms","398ms","498ms","598ms","698ms","798ms","898ms","998ms", }, + new string[] { "99ms","199ms","299ms","399ms","499ms","599ms","699ms","799ms","899ms","999ms", }, + }; + + private static readonly string[][] LOOKUP_TABLE_0_BYTES = + { + new string[] { "0 B","100 B","200 B","300 B","400 B","500 B","600 B","700 B","800 B","900 B", }, + new string[] { "1 B","101 B","201 B","301 B","401 B","501 B","601 B","701 B","801 B","901 B", }, + new string[] { "2 B","102 B","202 B","302 B","402 B","502 B","602 B","702 B","802 B","902 B", }, + new string[] { "3 B","103 B","203 B","303 B","403 B","503 B","603 B","703 B","803 B","903 B", }, + new string[] { "4 B","104 B","204 B","304 B","404 B","504 B","604 B","704 B","804 B","904 B", }, + new string[] { "5 B","105 B","205 B","305 B","405 B","505 B","605 B","705 B","805 B","905 B", }, + new string[] { "6 B","106 B","206 B","306 B","406 B","506 B","606 B","706 B","806 B","906 B", }, + new string[] { "7 B","107 B","207 B","307 B","407 B","507 B","607 B","707 B","807 B","907 B", }, + new string[] { "8 B","108 B","208 B","308 B","408 B","508 B","608 B","708 B","808 B","908 B", }, + new string[] { "9 B","109 B","209 B","309 B","409 B","509 B","609 B","709 B","809 B","909 B", }, + new string[] { "10 B","110 B","210 B","310 B","410 B","510 B","610 B","710 B","810 B","910 B", }, + new string[] { "11 B","111 B","211 B","311 B","411 B","511 B","611 B","711 B","811 B","911 B", }, + new string[] { "12 B","112 B","212 B","312 B","412 B","512 B","612 B","712 B","812 B","912 B", }, + new string[] { "13 B","113 B","213 B","313 B","413 B","513 B","613 B","713 B","813 B","913 B", }, + new string[] { "14 B","114 B","214 B","314 B","414 B","514 B","614 B","714 B","814 B","914 B", }, + new string[] { "15 B","115 B","215 B","315 B","415 B","515 B","615 B","715 B","815 B","915 B", }, + new string[] { "16 B","116 B","216 B","316 B","416 B","516 B","616 B","716 B","816 B","916 B", }, + new string[] { "17 B","117 B","217 B","317 B","417 B","517 B","617 B","717 B","817 B","917 B", }, + new string[] { "18 B","118 B","218 B","318 B","418 B","518 B","618 B","718 B","818 B","918 B", }, + new string[] { "19 B","119 B","219 B","319 B","419 B","519 B","619 B","719 B","819 B","919 B", }, + new string[] { "20 B","120 B","220 B","320 B","420 B","520 B","620 B","720 B","820 B","920 B", }, + new string[] { "21 B","121 B","221 B","321 B","421 B","521 B","621 B","721 B","821 B","921 B", }, + new string[] { "22 B","122 B","222 B","322 B","422 B","522 B","622 B","722 B","822 B","922 B", }, + new string[] { "23 B","123 B","223 B","323 B","423 B","523 B","623 B","723 B","823 B","923 B", }, + new string[] { "24 B","124 B","224 B","324 B","424 B","524 B","624 B","724 B","824 B","924 B", }, + new string[] { "25 B","125 B","225 B","325 B","425 B","525 B","625 B","725 B","825 B","925 B", }, + new string[] { "26 B","126 B","226 B","326 B","426 B","526 B","626 B","726 B","826 B","926 B", }, + new string[] { "27 B","127 B","227 B","327 B","427 B","527 B","627 B","727 B","827 B","927 B", }, + new string[] { "28 B","128 B","228 B","328 B","428 B","528 B","628 B","728 B","828 B","928 B", }, + new string[] { "29 B","129 B","229 B","329 B","429 B","529 B","629 B","729 B","829 B","929 B", }, + new string[] { "30 B","130 B","230 B","330 B","430 B","530 B","630 B","730 B","830 B","930 B", }, + new string[] { "31 B","131 B","231 B","331 B","431 B","531 B","631 B","731 B","831 B","931 B", }, + new string[] { "32 B","132 B","232 B","332 B","432 B","532 B","632 B","732 B","832 B","932 B", }, + new string[] { "33 B","133 B","233 B","333 B","433 B","533 B","633 B","733 B","833 B","933 B", }, + new string[] { "34 B","134 B","234 B","334 B","434 B","534 B","634 B","734 B","834 B","934 B", }, + new string[] { "35 B","135 B","235 B","335 B","435 B","535 B","635 B","735 B","835 B","935 B", }, + new string[] { "36 B","136 B","236 B","336 B","436 B","536 B","636 B","736 B","836 B","936 B", }, + new string[] { "37 B","137 B","237 B","337 B","437 B","537 B","637 B","737 B","837 B","937 B", }, + new string[] { "38 B","138 B","238 B","338 B","438 B","538 B","638 B","738 B","838 B","938 B", }, + new string[] { "39 B","139 B","239 B","339 B","439 B","539 B","639 B","739 B","839 B","939 B", }, + new string[] { "40 B","140 B","240 B","340 B","440 B","540 B","640 B","740 B","840 B","940 B", }, + new string[] { "41 B","141 B","241 B","341 B","441 B","541 B","641 B","741 B","841 B","941 B", }, + new string[] { "42 B","142 B","242 B","342 B","442 B","542 B","642 B","742 B","842 B","942 B", }, + new string[] { "43 B","143 B","243 B","343 B","443 B","543 B","643 B","743 B","843 B","943 B", }, + new string[] { "44 B","144 B","244 B","344 B","444 B","544 B","644 B","744 B","844 B","944 B", }, + new string[] { "45 B","145 B","245 B","345 B","445 B","545 B","645 B","745 B","845 B","945 B", }, + new string[] { "46 B","146 B","246 B","346 B","446 B","546 B","646 B","746 B","846 B","946 B", }, + new string[] { "47 B","147 B","247 B","347 B","447 B","547 B","647 B","747 B","847 B","947 B", }, + new string[] { "48 B","148 B","248 B","348 B","448 B","548 B","648 B","748 B","848 B","948 B", }, + new string[] { "49 B","149 B","249 B","349 B","449 B","549 B","649 B","749 B","849 B","949 B", }, + new string[] { "50 B","150 B","250 B","350 B","450 B","550 B","650 B","750 B","850 B","950 B", }, + new string[] { "51 B","151 B","251 B","351 B","451 B","551 B","651 B","751 B","851 B","951 B", }, + new string[] { "52 B","152 B","252 B","352 B","452 B","552 B","652 B","752 B","852 B","952 B", }, + new string[] { "53 B","153 B","253 B","353 B","453 B","553 B","653 B","753 B","853 B","953 B", }, + new string[] { "54 B","154 B","254 B","354 B","454 B","554 B","654 B","754 B","854 B","954 B", }, + new string[] { "55 B","155 B","255 B","355 B","455 B","555 B","655 B","755 B","855 B","955 B", }, + new string[] { "56 B","156 B","256 B","356 B","456 B","556 B","656 B","756 B","856 B","956 B", }, + new string[] { "57 B","157 B","257 B","357 B","457 B","557 B","657 B","757 B","857 B","957 B", }, + new string[] { "58 B","158 B","258 B","358 B","458 B","558 B","658 B","758 B","858 B","958 B", }, + new string[] { "59 B","159 B","259 B","359 B","459 B","559 B","659 B","759 B","859 B","959 B", }, + new string[] { "60 B","160 B","260 B","360 B","460 B","560 B","660 B","760 B","860 B","960 B", }, + new string[] { "61 B","161 B","261 B","361 B","461 B","561 B","661 B","761 B","861 B","961 B", }, + new string[] { "62 B","162 B","262 B","362 B","462 B","562 B","662 B","762 B","862 B","962 B", }, + new string[] { "63 B","163 B","263 B","363 B","463 B","563 B","663 B","763 B","863 B","963 B", }, + new string[] { "64 B","164 B","264 B","364 B","464 B","564 B","664 B","764 B","864 B","964 B", }, + new string[] { "65 B","165 B","265 B","365 B","465 B","565 B","665 B","765 B","865 B","965 B", }, + new string[] { "66 B","166 B","266 B","366 B","466 B","566 B","666 B","766 B","866 B","966 B", }, + new string[] { "67 B","167 B","267 B","367 B","467 B","567 B","667 B","767 B","867 B","967 B", }, + new string[] { "68 B","168 B","268 B","368 B","468 B","568 B","668 B","768 B","868 B","968 B", }, + new string[] { "69 B","169 B","269 B","369 B","469 B","569 B","669 B","769 B","869 B","969 B", }, + new string[] { "70 B","170 B","270 B","370 B","470 B","570 B","670 B","770 B","870 B","970 B", }, + new string[] { "71 B","171 B","271 B","371 B","471 B","571 B","671 B","771 B","871 B","971 B", }, + new string[] { "72 B","172 B","272 B","372 B","472 B","572 B","672 B","772 B","872 B","972 B", }, + new string[] { "73 B","173 B","273 B","373 B","473 B","573 B","673 B","773 B","873 B","973 B", }, + new string[] { "74 B","174 B","274 B","374 B","474 B","574 B","674 B","774 B","874 B","974 B", }, + new string[] { "75 B","175 B","275 B","375 B","475 B","575 B","675 B","775 B","875 B","975 B", }, + new string[] { "76 B","176 B","276 B","376 B","476 B","576 B","676 B","776 B","876 B","976 B", }, + new string[] { "77 B","177 B","277 B","377 B","477 B","577 B","677 B","777 B","877 B","977 B", }, + new string[] { "78 B","178 B","278 B","378 B","478 B","578 B","678 B","778 B","878 B","978 B", }, + new string[] { "79 B","179 B","279 B","379 B","479 B","579 B","679 B","779 B","879 B","979 B", }, + new string[] { "80 B","180 B","280 B","380 B","480 B","580 B","680 B","780 B","880 B","980 B", }, + new string[] { "81 B","181 B","281 B","381 B","481 B","581 B","681 B","781 B","881 B","981 B", }, + new string[] { "82 B","182 B","282 B","382 B","482 B","582 B","682 B","782 B","882 B","982 B", }, + new string[] { "83 B","183 B","283 B","383 B","483 B","583 B","683 B","783 B","883 B","983 B", }, + new string[] { "84 B","184 B","284 B","384 B","484 B","584 B","684 B","784 B","884 B","984 B", }, + new string[] { "85 B","185 B","285 B","385 B","485 B","585 B","685 B","785 B","885 B","985 B", }, + new string[] { "86 B","186 B","286 B","386 B","486 B","586 B","686 B","786 B","886 B","986 B", }, + new string[] { "87 B","187 B","287 B","387 B","487 B","587 B","687 B","787 B","887 B","987 B", }, + new string[] { "88 B","188 B","288 B","388 B","488 B","588 B","688 B","788 B","888 B","988 B", }, + new string[] { "89 B","189 B","289 B","389 B","489 B","589 B","689 B","789 B","889 B","989 B", }, + new string[] { "90 B","190 B","290 B","390 B","490 B","590 B","690 B","790 B","890 B","990 B", }, + new string[] { "91 B","191 B","291 B","391 B","491 B","591 B","691 B","791 B","891 B","991 B", }, + new string[] { "92 B","192 B","292 B","392 B","492 B","592 B","692 B","792 B","892 B","992 B", }, + new string[] { "93 B","193 B","293 B","393 B","493 B","593 B","693 B","793 B","893 B","993 B", }, + new string[] { "94 B","194 B","294 B","394 B","494 B","594 B","694 B","794 B","894 B","994 B", }, + new string[] { "95 B","195 B","295 B","395 B","495 B","595 B","695 B","795 B","895 B","995 B", }, + new string[] { "96 B","196 B","296 B","396 B","496 B","596 B","696 B","796 B","896 B","996 B", }, + new string[] { "97 B","197 B","297 B","397 B","497 B","597 B","697 B","797 B","897 B","997 B", }, + new string[] { "98 B","198 B","298 B","398 B","498 B","598 B","698 B","798 B","898 B","998 B", }, + new string[] { "99 B","199 B","299 B","399 B","499 B","599 B","699 B","799 B","899 B","999 B", }, + }; + + private static readonly string[][] LOOKUP_TABLE_0_00ms = + { + new string[] { "0.00ms","1.00ms","2.00ms","3.00ms","4.00ms","5.00ms","6.00ms","7.00ms","8.00ms","9.00ms", "10.00ms","11.00ms","12.00ms","13.00ms","14.00ms","15.00ms","16.00ms","17.00ms","18.00ms","19.00ms", "20.00ms","21.00ms","22.00ms","23.00ms","24.00ms","25.00ms","26.00ms","27.00ms","28.00ms","29.00ms", "30.00ms","31.00ms","32.00ms","33.00ms","34.00ms","35.00ms","36.00ms","37.00ms","38.00ms","39.00ms", "40.00ms","41.00ms","42.00ms","43.00ms","44.00ms","45.00ms","46.00ms","47.00ms","48.00ms","49.00ms", "50.00ms","51.00ms","52.00ms","53.00ms","54.00ms","55.00ms","56.00ms","57.00ms","58.00ms","59.00ms", "60.00ms","61.00ms","62.00ms","63.00ms","64.00ms","65.00ms","66.00ms","67.00ms","68.00ms","69.00ms", "70.00ms","71.00ms","72.00ms","73.00ms","74.00ms","75.00ms","76.00ms","77.00ms","78.00ms","79.00ms", "80.00ms","81.00ms","82.00ms","83.00ms","84.00ms","85.00ms","86.00ms","87.00ms","88.00ms","89.00ms", "90.00ms","91.00ms","92.00ms","93.00ms","94.00ms","95.00ms","96.00ms","97.00ms","98.00ms","99.00ms", }, + new string[] { "0.01ms","1.01ms","2.01ms","3.01ms","4.01ms","5.01ms","6.01ms","7.01ms","8.01ms","9.01ms", "10.01ms","11.01ms","12.01ms","13.01ms","14.01ms","15.01ms","16.01ms","17.01ms","18.01ms","19.01ms", "20.01ms","21.01ms","22.01ms","23.01ms","24.01ms","25.01ms","26.01ms","27.01ms","28.01ms","29.01ms", "30.01ms","31.01ms","32.01ms","33.01ms","34.01ms","35.01ms","36.01ms","37.01ms","38.01ms","39.01ms", "40.01ms","41.01ms","42.01ms","43.01ms","44.01ms","45.01ms","46.01ms","47.01ms","48.01ms","49.01ms", "50.01ms","51.01ms","52.01ms","53.01ms","54.01ms","55.01ms","56.01ms","57.01ms","58.01ms","59.01ms", "60.01ms","61.01ms","62.01ms","63.01ms","64.01ms","65.01ms","66.01ms","67.01ms","68.01ms","69.01ms", "70.01ms","71.01ms","72.01ms","73.01ms","74.01ms","75.01ms","76.01ms","77.01ms","78.01ms","79.01ms", "80.01ms","81.01ms","82.01ms","83.01ms","84.01ms","85.01ms","86.01ms","87.01ms","88.01ms","89.01ms", "90.01ms","91.01ms","92.01ms","93.01ms","94.01ms","95.01ms","96.01ms","97.01ms","98.01ms","99.01ms", }, + new string[] { "0.02ms","1.02ms","2.02ms","3.02ms","4.02ms","5.02ms","6.02ms","7.02ms","8.02ms","9.02ms", "10.02ms","11.02ms","12.02ms","13.02ms","14.02ms","15.02ms","16.02ms","17.02ms","18.02ms","19.02ms", "20.02ms","21.02ms","22.02ms","23.02ms","24.02ms","25.02ms","26.02ms","27.02ms","28.02ms","29.02ms", "30.02ms","31.02ms","32.02ms","33.02ms","34.02ms","35.02ms","36.02ms","37.02ms","38.02ms","39.02ms", "40.02ms","41.02ms","42.02ms","43.02ms","44.02ms","45.02ms","46.02ms","47.02ms","48.02ms","49.02ms", "50.02ms","51.02ms","52.02ms","53.02ms","54.02ms","55.02ms","56.02ms","57.02ms","58.02ms","59.02ms", "60.02ms","61.02ms","62.02ms","63.02ms","64.02ms","65.02ms","66.02ms","67.02ms","68.02ms","69.02ms", "70.02ms","71.02ms","72.02ms","73.02ms","74.02ms","75.02ms","76.02ms","77.02ms","78.02ms","79.02ms", "80.02ms","81.02ms","82.02ms","83.02ms","84.02ms","85.02ms","86.02ms","87.02ms","88.02ms","89.02ms", "90.02ms","91.02ms","92.02ms","93.02ms","94.02ms","95.02ms","96.02ms","97.02ms","98.02ms","99.02ms", }, + new string[] { "0.03ms","1.03ms","2.03ms","3.03ms","4.03ms","5.03ms","6.03ms","7.03ms","8.03ms","9.03ms", "10.03ms","11.03ms","12.03ms","13.03ms","14.03ms","15.03ms","16.03ms","17.03ms","18.03ms","19.03ms", "20.03ms","21.03ms","22.03ms","23.03ms","24.03ms","25.03ms","26.03ms","27.03ms","28.03ms","29.03ms", "30.03ms","31.03ms","32.03ms","33.03ms","34.03ms","35.03ms","36.03ms","37.03ms","38.03ms","39.03ms", "40.03ms","41.03ms","42.03ms","43.03ms","44.03ms","45.03ms","46.03ms","47.03ms","48.03ms","49.03ms", "50.03ms","51.03ms","52.03ms","53.03ms","54.03ms","55.03ms","56.03ms","57.03ms","58.03ms","59.03ms", "60.03ms","61.03ms","62.03ms","63.03ms","64.03ms","65.03ms","66.03ms","67.03ms","68.03ms","69.03ms", "70.03ms","71.03ms","72.03ms","73.03ms","74.03ms","75.03ms","76.03ms","77.03ms","78.03ms","79.03ms", "80.03ms","81.03ms","82.03ms","83.03ms","84.03ms","85.03ms","86.03ms","87.03ms","88.03ms","89.03ms", "90.03ms","91.03ms","92.03ms","93.03ms","94.03ms","95.03ms","96.03ms","97.03ms","98.03ms","99.03ms", }, + new string[] { "0.04ms","1.04ms","2.04ms","3.04ms","4.04ms","5.04ms","6.04ms","7.04ms","8.04ms","9.04ms", "10.04ms","11.04ms","12.04ms","13.04ms","14.04ms","15.04ms","16.04ms","17.04ms","18.04ms","19.04ms", "20.04ms","21.04ms","22.04ms","23.04ms","24.04ms","25.04ms","26.04ms","27.04ms","28.04ms","29.04ms", "30.04ms","31.04ms","32.04ms","33.04ms","34.04ms","35.04ms","36.04ms","37.04ms","38.04ms","39.04ms", "40.04ms","41.04ms","42.04ms","43.04ms","44.04ms","45.04ms","46.04ms","47.04ms","48.04ms","49.04ms", "50.04ms","51.04ms","52.04ms","53.04ms","54.04ms","55.04ms","56.04ms","57.04ms","58.04ms","59.04ms", "60.04ms","61.04ms","62.04ms","63.04ms","64.04ms","65.04ms","66.04ms","67.04ms","68.04ms","69.04ms", "70.04ms","71.04ms","72.04ms","73.04ms","74.04ms","75.04ms","76.04ms","77.04ms","78.04ms","79.04ms", "80.04ms","81.04ms","82.04ms","83.04ms","84.04ms","85.04ms","86.04ms","87.04ms","88.04ms","89.04ms", "90.04ms","91.04ms","92.04ms","93.04ms","94.04ms","95.04ms","96.04ms","97.04ms","98.04ms","99.04ms", }, + new string[] { "0.05ms","1.05ms","2.05ms","3.05ms","4.05ms","5.05ms","6.05ms","7.05ms","8.05ms","9.05ms", "10.05ms","11.05ms","12.05ms","13.05ms","14.05ms","15.05ms","16.05ms","17.05ms","18.05ms","19.05ms", "20.05ms","21.05ms","22.05ms","23.05ms","24.05ms","25.05ms","26.05ms","27.05ms","28.05ms","29.05ms", "30.05ms","31.05ms","32.05ms","33.05ms","34.05ms","35.05ms","36.05ms","37.05ms","38.05ms","39.05ms", "40.05ms","41.05ms","42.05ms","43.05ms","44.05ms","45.05ms","46.05ms","47.05ms","48.05ms","49.05ms", "50.05ms","51.05ms","52.05ms","53.05ms","54.05ms","55.05ms","56.05ms","57.05ms","58.05ms","59.05ms", "60.05ms","61.05ms","62.05ms","63.05ms","64.05ms","65.05ms","66.05ms","67.05ms","68.05ms","69.05ms", "70.05ms","71.05ms","72.05ms","73.05ms","74.05ms","75.05ms","76.05ms","77.05ms","78.05ms","79.05ms", "80.05ms","81.05ms","82.05ms","83.05ms","84.05ms","85.05ms","86.05ms","87.05ms","88.05ms","89.05ms", "90.05ms","91.05ms","92.05ms","93.05ms","94.05ms","95.05ms","96.05ms","97.05ms","98.05ms","99.05ms", }, + new string[] { "0.06ms","1.06ms","2.06ms","3.06ms","4.06ms","5.06ms","6.06ms","7.06ms","8.06ms","9.06ms", "10.06ms","11.06ms","12.06ms","13.06ms","14.06ms","15.06ms","16.06ms","17.06ms","18.06ms","19.06ms", "20.06ms","21.06ms","22.06ms","23.06ms","24.06ms","25.06ms","26.06ms","27.06ms","28.06ms","29.06ms", "30.06ms","31.06ms","32.06ms","33.06ms","34.06ms","35.06ms","36.06ms","37.06ms","38.06ms","39.06ms", "40.06ms","41.06ms","42.06ms","43.06ms","44.06ms","45.06ms","46.06ms","47.06ms","48.06ms","49.06ms", "50.06ms","51.06ms","52.06ms","53.06ms","54.06ms","55.06ms","56.06ms","57.06ms","58.06ms","59.06ms", "60.06ms","61.06ms","62.06ms","63.06ms","64.06ms","65.06ms","66.06ms","67.06ms","68.06ms","69.06ms", "70.06ms","71.06ms","72.06ms","73.06ms","74.06ms","75.06ms","76.06ms","77.06ms","78.06ms","79.06ms", "80.06ms","81.06ms","82.06ms","83.06ms","84.06ms","85.06ms","86.06ms","87.06ms","88.06ms","89.06ms", "90.06ms","91.06ms","92.06ms","93.06ms","94.06ms","95.06ms","96.06ms","97.06ms","98.06ms","99.06ms", }, + new string[] { "0.07ms","1.07ms","2.07ms","3.07ms","4.07ms","5.07ms","6.07ms","7.07ms","8.07ms","9.07ms", "10.07ms","11.07ms","12.07ms","13.07ms","14.07ms","15.07ms","16.07ms","17.07ms","18.07ms","19.07ms", "20.07ms","21.07ms","22.07ms","23.07ms","24.07ms","25.07ms","26.07ms","27.07ms","28.07ms","29.07ms", "30.07ms","31.07ms","32.07ms","33.07ms","34.07ms","35.07ms","36.07ms","37.07ms","38.07ms","39.07ms", "40.07ms","41.07ms","42.07ms","43.07ms","44.07ms","45.07ms","46.07ms","47.07ms","48.07ms","49.07ms", "50.07ms","51.07ms","52.07ms","53.07ms","54.07ms","55.07ms","56.07ms","57.07ms","58.07ms","59.07ms", "60.07ms","61.07ms","62.07ms","63.07ms","64.07ms","65.07ms","66.07ms","67.07ms","68.07ms","69.07ms", "70.07ms","71.07ms","72.07ms","73.07ms","74.07ms","75.07ms","76.07ms","77.07ms","78.07ms","79.07ms", "80.07ms","81.07ms","82.07ms","83.07ms","84.07ms","85.07ms","86.07ms","87.07ms","88.07ms","89.07ms", "90.07ms","91.07ms","92.07ms","93.07ms","94.07ms","95.07ms","96.07ms","97.07ms","98.07ms","99.07ms", }, + new string[] { "0.08ms","1.08ms","2.08ms","3.08ms","4.08ms","5.08ms","6.08ms","7.08ms","8.08ms","9.08ms", "10.08ms","11.08ms","12.08ms","13.08ms","14.08ms","15.08ms","16.08ms","17.08ms","18.08ms","19.08ms", "20.08ms","21.08ms","22.08ms","23.08ms","24.08ms","25.08ms","26.08ms","27.08ms","28.08ms","29.08ms", "30.08ms","31.08ms","32.08ms","33.08ms","34.08ms","35.08ms","36.08ms","37.08ms","38.08ms","39.08ms", "40.08ms","41.08ms","42.08ms","43.08ms","44.08ms","45.08ms","46.08ms","47.08ms","48.08ms","49.08ms", "50.08ms","51.08ms","52.08ms","53.08ms","54.08ms","55.08ms","56.08ms","57.08ms","58.08ms","59.08ms", "60.08ms","61.08ms","62.08ms","63.08ms","64.08ms","65.08ms","66.08ms","67.08ms","68.08ms","69.08ms", "70.08ms","71.08ms","72.08ms","73.08ms","74.08ms","75.08ms","76.08ms","77.08ms","78.08ms","79.08ms", "80.08ms","81.08ms","82.08ms","83.08ms","84.08ms","85.08ms","86.08ms","87.08ms","88.08ms","89.08ms", "90.08ms","91.08ms","92.08ms","93.08ms","94.08ms","95.08ms","96.08ms","97.08ms","98.08ms","99.08ms", }, + new string[] { "0.09ms","1.09ms","2.09ms","3.09ms","4.09ms","5.09ms","6.09ms","7.09ms","8.09ms","9.09ms", "10.09ms","11.09ms","12.09ms","13.09ms","14.09ms","15.09ms","16.09ms","17.09ms","18.09ms","19.09ms", "20.09ms","21.09ms","22.09ms","23.09ms","24.09ms","25.09ms","26.09ms","27.09ms","28.09ms","29.09ms", "30.09ms","31.09ms","32.09ms","33.09ms","34.09ms","35.09ms","36.09ms","37.09ms","38.09ms","39.09ms", "40.09ms","41.09ms","42.09ms","43.09ms","44.09ms","45.09ms","46.09ms","47.09ms","48.09ms","49.09ms", "50.09ms","51.09ms","52.09ms","53.09ms","54.09ms","55.09ms","56.09ms","57.09ms","58.09ms","59.09ms", "60.09ms","61.09ms","62.09ms","63.09ms","64.09ms","65.09ms","66.09ms","67.09ms","68.09ms","69.09ms", "70.09ms","71.09ms","72.09ms","73.09ms","74.09ms","75.09ms","76.09ms","77.09ms","78.09ms","79.09ms", "80.09ms","81.09ms","82.09ms","83.09ms","84.09ms","85.09ms","86.09ms","87.09ms","88.09ms","89.09ms", "90.09ms","91.09ms","92.09ms","93.09ms","94.09ms","95.09ms","96.09ms","97.09ms","98.09ms","99.09ms", }, + new string[] { "0.10ms","1.10ms","2.10ms","3.10ms","4.10ms","5.10ms","6.10ms","7.10ms","8.10ms","9.10ms", "10.10ms","11.10ms","12.10ms","13.10ms","14.10ms","15.10ms","16.10ms","17.10ms","18.10ms","19.10ms", "20.10ms","21.10ms","22.10ms","23.10ms","24.10ms","25.10ms","26.10ms","27.10ms","28.10ms","29.10ms", "30.10ms","31.10ms","32.10ms","33.10ms","34.10ms","35.10ms","36.10ms","37.10ms","38.10ms","39.10ms", "40.10ms","41.10ms","42.10ms","43.10ms","44.10ms","45.10ms","46.10ms","47.10ms","48.10ms","49.10ms", "50.10ms","51.10ms","52.10ms","53.10ms","54.10ms","55.10ms","56.10ms","57.10ms","58.10ms","59.10ms", "60.10ms","61.10ms","62.10ms","63.10ms","64.10ms","65.10ms","66.10ms","67.10ms","68.10ms","69.10ms", "70.10ms","71.10ms","72.10ms","73.10ms","74.10ms","75.10ms","76.10ms","77.10ms","78.10ms","79.10ms", "80.10ms","81.10ms","82.10ms","83.10ms","84.10ms","85.10ms","86.10ms","87.10ms","88.10ms","89.10ms", "90.10ms","91.10ms","92.10ms","93.10ms","94.10ms","95.10ms","96.10ms","97.10ms","98.10ms","99.10ms", }, + new string[] { "0.11ms","1.11ms","2.11ms","3.11ms","4.11ms","5.11ms","6.11ms","7.11ms","8.11ms","9.11ms", "10.11ms","11.11ms","12.11ms","13.11ms","14.11ms","15.11ms","16.11ms","17.11ms","18.11ms","19.11ms", "20.11ms","21.11ms","22.11ms","23.11ms","24.11ms","25.11ms","26.11ms","27.11ms","28.11ms","29.11ms", "30.11ms","31.11ms","32.11ms","33.11ms","34.11ms","35.11ms","36.11ms","37.11ms","38.11ms","39.11ms", "40.11ms","41.11ms","42.11ms","43.11ms","44.11ms","45.11ms","46.11ms","47.11ms","48.11ms","49.11ms", "50.11ms","51.11ms","52.11ms","53.11ms","54.11ms","55.11ms","56.11ms","57.11ms","58.11ms","59.11ms", "60.11ms","61.11ms","62.11ms","63.11ms","64.11ms","65.11ms","66.11ms","67.11ms","68.11ms","69.11ms", "70.11ms","71.11ms","72.11ms","73.11ms","74.11ms","75.11ms","76.11ms","77.11ms","78.11ms","79.11ms", "80.11ms","81.11ms","82.11ms","83.11ms","84.11ms","85.11ms","86.11ms","87.11ms","88.11ms","89.11ms", "90.11ms","91.11ms","92.11ms","93.11ms","94.11ms","95.11ms","96.11ms","97.11ms","98.11ms","99.11ms", }, + new string[] { "0.12ms","1.12ms","2.12ms","3.12ms","4.12ms","5.12ms","6.12ms","7.12ms","8.12ms","9.12ms", "10.12ms","11.12ms","12.12ms","13.12ms","14.12ms","15.12ms","16.12ms","17.12ms","18.12ms","19.12ms", "20.12ms","21.12ms","22.12ms","23.12ms","24.12ms","25.12ms","26.12ms","27.12ms","28.12ms","29.12ms", "30.12ms","31.12ms","32.12ms","33.12ms","34.12ms","35.12ms","36.12ms","37.12ms","38.12ms","39.12ms", "40.12ms","41.12ms","42.12ms","43.12ms","44.12ms","45.12ms","46.12ms","47.12ms","48.12ms","49.12ms", "50.12ms","51.12ms","52.12ms","53.12ms","54.12ms","55.12ms","56.12ms","57.12ms","58.12ms","59.12ms", "60.12ms","61.12ms","62.12ms","63.12ms","64.12ms","65.12ms","66.12ms","67.12ms","68.12ms","69.12ms", "70.12ms","71.12ms","72.12ms","73.12ms","74.12ms","75.12ms","76.12ms","77.12ms","78.12ms","79.12ms", "80.12ms","81.12ms","82.12ms","83.12ms","84.12ms","85.12ms","86.12ms","87.12ms","88.12ms","89.12ms", "90.12ms","91.12ms","92.12ms","93.12ms","94.12ms","95.12ms","96.12ms","97.12ms","98.12ms","99.12ms", }, + new string[] { "0.13ms","1.13ms","2.13ms","3.13ms","4.13ms","5.13ms","6.13ms","7.13ms","8.13ms","9.13ms", "10.13ms","11.13ms","12.13ms","13.13ms","14.13ms","15.13ms","16.13ms","17.13ms","18.13ms","19.13ms", "20.13ms","21.13ms","22.13ms","23.13ms","24.13ms","25.13ms","26.13ms","27.13ms","28.13ms","29.13ms", "30.13ms","31.13ms","32.13ms","33.13ms","34.13ms","35.13ms","36.13ms","37.13ms","38.13ms","39.13ms", "40.13ms","41.13ms","42.13ms","43.13ms","44.13ms","45.13ms","46.13ms","47.13ms","48.13ms","49.13ms", "50.13ms","51.13ms","52.13ms","53.13ms","54.13ms","55.13ms","56.13ms","57.13ms","58.13ms","59.13ms", "60.13ms","61.13ms","62.13ms","63.13ms","64.13ms","65.13ms","66.13ms","67.13ms","68.13ms","69.13ms", "70.13ms","71.13ms","72.13ms","73.13ms","74.13ms","75.13ms","76.13ms","77.13ms","78.13ms","79.13ms", "80.13ms","81.13ms","82.13ms","83.13ms","84.13ms","85.13ms","86.13ms","87.13ms","88.13ms","89.13ms", "90.13ms","91.13ms","92.13ms","93.13ms","94.13ms","95.13ms","96.13ms","97.13ms","98.13ms","99.13ms", }, + new string[] { "0.14ms","1.14ms","2.14ms","3.14ms","4.14ms","5.14ms","6.14ms","7.14ms","8.14ms","9.14ms", "10.14ms","11.14ms","12.14ms","13.14ms","14.14ms","15.14ms","16.14ms","17.14ms","18.14ms","19.14ms", "20.14ms","21.14ms","22.14ms","23.14ms","24.14ms","25.14ms","26.14ms","27.14ms","28.14ms","29.14ms", "30.14ms","31.14ms","32.14ms","33.14ms","34.14ms","35.14ms","36.14ms","37.14ms","38.14ms","39.14ms", "40.14ms","41.14ms","42.14ms","43.14ms","44.14ms","45.14ms","46.14ms","47.14ms","48.14ms","49.14ms", "50.14ms","51.14ms","52.14ms","53.14ms","54.14ms","55.14ms","56.14ms","57.14ms","58.14ms","59.14ms", "60.14ms","61.14ms","62.14ms","63.14ms","64.14ms","65.14ms","66.14ms","67.14ms","68.14ms","69.14ms", "70.14ms","71.14ms","72.14ms","73.14ms","74.14ms","75.14ms","76.14ms","77.14ms","78.14ms","79.14ms", "80.14ms","81.14ms","82.14ms","83.14ms","84.14ms","85.14ms","86.14ms","87.14ms","88.14ms","89.14ms", "90.14ms","91.14ms","92.14ms","93.14ms","94.14ms","95.14ms","96.14ms","97.14ms","98.14ms","99.14ms", }, + new string[] { "0.15ms","1.15ms","2.15ms","3.15ms","4.15ms","5.15ms","6.15ms","7.15ms","8.15ms","9.15ms", "10.15ms","11.15ms","12.15ms","13.15ms","14.15ms","15.15ms","16.15ms","17.15ms","18.15ms","19.15ms", "20.15ms","21.15ms","22.15ms","23.15ms","24.15ms","25.15ms","26.15ms","27.15ms","28.15ms","29.15ms", "30.15ms","31.15ms","32.15ms","33.15ms","34.15ms","35.15ms","36.15ms","37.15ms","38.15ms","39.15ms", "40.15ms","41.15ms","42.15ms","43.15ms","44.15ms","45.15ms","46.15ms","47.15ms","48.15ms","49.15ms", "50.15ms","51.15ms","52.15ms","53.15ms","54.15ms","55.15ms","56.15ms","57.15ms","58.15ms","59.15ms", "60.15ms","61.15ms","62.15ms","63.15ms","64.15ms","65.15ms","66.15ms","67.15ms","68.15ms","69.15ms", "70.15ms","71.15ms","72.15ms","73.15ms","74.15ms","75.15ms","76.15ms","77.15ms","78.15ms","79.15ms", "80.15ms","81.15ms","82.15ms","83.15ms","84.15ms","85.15ms","86.15ms","87.15ms","88.15ms","89.15ms", "90.15ms","91.15ms","92.15ms","93.15ms","94.15ms","95.15ms","96.15ms","97.15ms","98.15ms","99.15ms", }, + new string[] { "0.16ms","1.16ms","2.16ms","3.16ms","4.16ms","5.16ms","6.16ms","7.16ms","8.16ms","9.16ms", "10.16ms","11.16ms","12.16ms","13.16ms","14.16ms","15.16ms","16.16ms","17.16ms","18.16ms","19.16ms", "20.16ms","21.16ms","22.16ms","23.16ms","24.16ms","25.16ms","26.16ms","27.16ms","28.16ms","29.16ms", "30.16ms","31.16ms","32.16ms","33.16ms","34.16ms","35.16ms","36.16ms","37.16ms","38.16ms","39.16ms", "40.16ms","41.16ms","42.16ms","43.16ms","44.16ms","45.16ms","46.16ms","47.16ms","48.16ms","49.16ms", "50.16ms","51.16ms","52.16ms","53.16ms","54.16ms","55.16ms","56.16ms","57.16ms","58.16ms","59.16ms", "60.16ms","61.16ms","62.16ms","63.16ms","64.16ms","65.16ms","66.16ms","67.16ms","68.16ms","69.16ms", "70.16ms","71.16ms","72.16ms","73.16ms","74.16ms","75.16ms","76.16ms","77.16ms","78.16ms","79.16ms", "80.16ms","81.16ms","82.16ms","83.16ms","84.16ms","85.16ms","86.16ms","87.16ms","88.16ms","89.16ms", "90.16ms","91.16ms","92.16ms","93.16ms","94.16ms","95.16ms","96.16ms","97.16ms","98.16ms","99.16ms", }, + new string[] { "0.17ms","1.17ms","2.17ms","3.17ms","4.17ms","5.17ms","6.17ms","7.17ms","8.17ms","9.17ms", "10.17ms","11.17ms","12.17ms","13.17ms","14.17ms","15.17ms","16.17ms","17.17ms","18.17ms","19.17ms", "20.17ms","21.17ms","22.17ms","23.17ms","24.17ms","25.17ms","26.17ms","27.17ms","28.17ms","29.17ms", "30.17ms","31.17ms","32.17ms","33.17ms","34.17ms","35.17ms","36.17ms","37.17ms","38.17ms","39.17ms", "40.17ms","41.17ms","42.17ms","43.17ms","44.17ms","45.17ms","46.17ms","47.17ms","48.17ms","49.17ms", "50.17ms","51.17ms","52.17ms","53.17ms","54.17ms","55.17ms","56.17ms","57.17ms","58.17ms","59.17ms", "60.17ms","61.17ms","62.17ms","63.17ms","64.17ms","65.17ms","66.17ms","67.17ms","68.17ms","69.17ms", "70.17ms","71.17ms","72.17ms","73.17ms","74.17ms","75.17ms","76.17ms","77.17ms","78.17ms","79.17ms", "80.17ms","81.17ms","82.17ms","83.17ms","84.17ms","85.17ms","86.17ms","87.17ms","88.17ms","89.17ms", "90.17ms","91.17ms","92.17ms","93.17ms","94.17ms","95.17ms","96.17ms","97.17ms","98.17ms","99.17ms", }, + new string[] { "0.18ms","1.18ms","2.18ms","3.18ms","4.18ms","5.18ms","6.18ms","7.18ms","8.18ms","9.18ms", "10.18ms","11.18ms","12.18ms","13.18ms","14.18ms","15.18ms","16.18ms","17.18ms","18.18ms","19.18ms", "20.18ms","21.18ms","22.18ms","23.18ms","24.18ms","25.18ms","26.18ms","27.18ms","28.18ms","29.18ms", "30.18ms","31.18ms","32.18ms","33.18ms","34.18ms","35.18ms","36.18ms","37.18ms","38.18ms","39.18ms", "40.18ms","41.18ms","42.18ms","43.18ms","44.18ms","45.18ms","46.18ms","47.18ms","48.18ms","49.18ms", "50.18ms","51.18ms","52.18ms","53.18ms","54.18ms","55.18ms","56.18ms","57.18ms","58.18ms","59.18ms", "60.18ms","61.18ms","62.18ms","63.18ms","64.18ms","65.18ms","66.18ms","67.18ms","68.18ms","69.18ms", "70.18ms","71.18ms","72.18ms","73.18ms","74.18ms","75.18ms","76.18ms","77.18ms","78.18ms","79.18ms", "80.18ms","81.18ms","82.18ms","83.18ms","84.18ms","85.18ms","86.18ms","87.18ms","88.18ms","89.18ms", "90.18ms","91.18ms","92.18ms","93.18ms","94.18ms","95.18ms","96.18ms","97.18ms","98.18ms","99.18ms", }, + new string[] { "0.19ms","1.19ms","2.19ms","3.19ms","4.19ms","5.19ms","6.19ms","7.19ms","8.19ms","9.19ms", "10.19ms","11.19ms","12.19ms","13.19ms","14.19ms","15.19ms","16.19ms","17.19ms","18.19ms","19.19ms", "20.19ms","21.19ms","22.19ms","23.19ms","24.19ms","25.19ms","26.19ms","27.19ms","28.19ms","29.19ms", "30.19ms","31.19ms","32.19ms","33.19ms","34.19ms","35.19ms","36.19ms","37.19ms","38.19ms","39.19ms", "40.19ms","41.19ms","42.19ms","43.19ms","44.19ms","45.19ms","46.19ms","47.19ms","48.19ms","49.19ms", "50.19ms","51.19ms","52.19ms","53.19ms","54.19ms","55.19ms","56.19ms","57.19ms","58.19ms","59.19ms", "60.19ms","61.19ms","62.19ms","63.19ms","64.19ms","65.19ms","66.19ms","67.19ms","68.19ms","69.19ms", "70.19ms","71.19ms","72.19ms","73.19ms","74.19ms","75.19ms","76.19ms","77.19ms","78.19ms","79.19ms", "80.19ms","81.19ms","82.19ms","83.19ms","84.19ms","85.19ms","86.19ms","87.19ms","88.19ms","89.19ms", "90.19ms","91.19ms","92.19ms","93.19ms","94.19ms","95.19ms","96.19ms","97.19ms","98.19ms","99.19ms", }, + new string[] { "0.20ms","1.20ms","2.20ms","3.20ms","4.20ms","5.20ms","6.20ms","7.20ms","8.20ms","9.20ms", "10.20ms","11.20ms","12.20ms","13.20ms","14.20ms","15.20ms","16.20ms","17.20ms","18.20ms","19.20ms", "20.20ms","21.20ms","22.20ms","23.20ms","24.20ms","25.20ms","26.20ms","27.20ms","28.20ms","29.20ms", "30.20ms","31.20ms","32.20ms","33.20ms","34.20ms","35.20ms","36.20ms","37.20ms","38.20ms","39.20ms", "40.20ms","41.20ms","42.20ms","43.20ms","44.20ms","45.20ms","46.20ms","47.20ms","48.20ms","49.20ms", "50.20ms","51.20ms","52.20ms","53.20ms","54.20ms","55.20ms","56.20ms","57.20ms","58.20ms","59.20ms", "60.20ms","61.20ms","62.20ms","63.20ms","64.20ms","65.20ms","66.20ms","67.20ms","68.20ms","69.20ms", "70.20ms","71.20ms","72.20ms","73.20ms","74.20ms","75.20ms","76.20ms","77.20ms","78.20ms","79.20ms", "80.20ms","81.20ms","82.20ms","83.20ms","84.20ms","85.20ms","86.20ms","87.20ms","88.20ms","89.20ms", "90.20ms","91.20ms","92.20ms","93.20ms","94.20ms","95.20ms","96.20ms","97.20ms","98.20ms","99.20ms", }, + new string[] { "0.21ms","1.21ms","2.21ms","3.21ms","4.21ms","5.21ms","6.21ms","7.21ms","8.21ms","9.21ms", "10.21ms","11.21ms","12.21ms","13.21ms","14.21ms","15.21ms","16.21ms","17.21ms","18.21ms","19.21ms", "20.21ms","21.21ms","22.21ms","23.21ms","24.21ms","25.21ms","26.21ms","27.21ms","28.21ms","29.21ms", "30.21ms","31.21ms","32.21ms","33.21ms","34.21ms","35.21ms","36.21ms","37.21ms","38.21ms","39.21ms", "40.21ms","41.21ms","42.21ms","43.21ms","44.21ms","45.21ms","46.21ms","47.21ms","48.21ms","49.21ms", "50.21ms","51.21ms","52.21ms","53.21ms","54.21ms","55.21ms","56.21ms","57.21ms","58.21ms","59.21ms", "60.21ms","61.21ms","62.21ms","63.21ms","64.21ms","65.21ms","66.21ms","67.21ms","68.21ms","69.21ms", "70.21ms","71.21ms","72.21ms","73.21ms","74.21ms","75.21ms","76.21ms","77.21ms","78.21ms","79.21ms", "80.21ms","81.21ms","82.21ms","83.21ms","84.21ms","85.21ms","86.21ms","87.21ms","88.21ms","89.21ms", "90.21ms","91.21ms","92.21ms","93.21ms","94.21ms","95.21ms","96.21ms","97.21ms","98.21ms","99.21ms", }, + new string[] { "0.22ms","1.22ms","2.22ms","3.22ms","4.22ms","5.22ms","6.22ms","7.22ms","8.22ms","9.22ms", "10.22ms","11.22ms","12.22ms","13.22ms","14.22ms","15.22ms","16.22ms","17.22ms","18.22ms","19.22ms", "20.22ms","21.22ms","22.22ms","23.22ms","24.22ms","25.22ms","26.22ms","27.22ms","28.22ms","29.22ms", "30.22ms","31.22ms","32.22ms","33.22ms","34.22ms","35.22ms","36.22ms","37.22ms","38.22ms","39.22ms", "40.22ms","41.22ms","42.22ms","43.22ms","44.22ms","45.22ms","46.22ms","47.22ms","48.22ms","49.22ms", "50.22ms","51.22ms","52.22ms","53.22ms","54.22ms","55.22ms","56.22ms","57.22ms","58.22ms","59.22ms", "60.22ms","61.22ms","62.22ms","63.22ms","64.22ms","65.22ms","66.22ms","67.22ms","68.22ms","69.22ms", "70.22ms","71.22ms","72.22ms","73.22ms","74.22ms","75.22ms","76.22ms","77.22ms","78.22ms","79.22ms", "80.22ms","81.22ms","82.22ms","83.22ms","84.22ms","85.22ms","86.22ms","87.22ms","88.22ms","89.22ms", "90.22ms","91.22ms","92.22ms","93.22ms","94.22ms","95.22ms","96.22ms","97.22ms","98.22ms","99.22ms", }, + new string[] { "0.23ms","1.23ms","2.23ms","3.23ms","4.23ms","5.23ms","6.23ms","7.23ms","8.23ms","9.23ms", "10.23ms","11.23ms","12.23ms","13.23ms","14.23ms","15.23ms","16.23ms","17.23ms","18.23ms","19.23ms", "20.23ms","21.23ms","22.23ms","23.23ms","24.23ms","25.23ms","26.23ms","27.23ms","28.23ms","29.23ms", "30.23ms","31.23ms","32.23ms","33.23ms","34.23ms","35.23ms","36.23ms","37.23ms","38.23ms","39.23ms", "40.23ms","41.23ms","42.23ms","43.23ms","44.23ms","45.23ms","46.23ms","47.23ms","48.23ms","49.23ms", "50.23ms","51.23ms","52.23ms","53.23ms","54.23ms","55.23ms","56.23ms","57.23ms","58.23ms","59.23ms", "60.23ms","61.23ms","62.23ms","63.23ms","64.23ms","65.23ms","66.23ms","67.23ms","68.23ms","69.23ms", "70.23ms","71.23ms","72.23ms","73.23ms","74.23ms","75.23ms","76.23ms","77.23ms","78.23ms","79.23ms", "80.23ms","81.23ms","82.23ms","83.23ms","84.23ms","85.23ms","86.23ms","87.23ms","88.23ms","89.23ms", "90.23ms","91.23ms","92.23ms","93.23ms","94.23ms","95.23ms","96.23ms","97.23ms","98.23ms","99.23ms", }, + new string[] { "0.24ms","1.24ms","2.24ms","3.24ms","4.24ms","5.24ms","6.24ms","7.24ms","8.24ms","9.24ms", "10.24ms","11.24ms","12.24ms","13.24ms","14.24ms","15.24ms","16.24ms","17.24ms","18.24ms","19.24ms", "20.24ms","21.24ms","22.24ms","23.24ms","24.24ms","25.24ms","26.24ms","27.24ms","28.24ms","29.24ms", "30.24ms","31.24ms","32.24ms","33.24ms","34.24ms","35.24ms","36.24ms","37.24ms","38.24ms","39.24ms", "40.24ms","41.24ms","42.24ms","43.24ms","44.24ms","45.24ms","46.24ms","47.24ms","48.24ms","49.24ms", "50.24ms","51.24ms","52.24ms","53.24ms","54.24ms","55.24ms","56.24ms","57.24ms","58.24ms","59.24ms", "60.24ms","61.24ms","62.24ms","63.24ms","64.24ms","65.24ms","66.24ms","67.24ms","68.24ms","69.24ms", "70.24ms","71.24ms","72.24ms","73.24ms","74.24ms","75.24ms","76.24ms","77.24ms","78.24ms","79.24ms", "80.24ms","81.24ms","82.24ms","83.24ms","84.24ms","85.24ms","86.24ms","87.24ms","88.24ms","89.24ms", "90.24ms","91.24ms","92.24ms","93.24ms","94.24ms","95.24ms","96.24ms","97.24ms","98.24ms","99.24ms", }, + new string[] { "0.25ms","1.25ms","2.25ms","3.25ms","4.25ms","5.25ms","6.25ms","7.25ms","8.25ms","9.25ms", "10.25ms","11.25ms","12.25ms","13.25ms","14.25ms","15.25ms","16.25ms","17.25ms","18.25ms","19.25ms", "20.25ms","21.25ms","22.25ms","23.25ms","24.25ms","25.25ms","26.25ms","27.25ms","28.25ms","29.25ms", "30.25ms","31.25ms","32.25ms","33.25ms","34.25ms","35.25ms","36.25ms","37.25ms","38.25ms","39.25ms", "40.25ms","41.25ms","42.25ms","43.25ms","44.25ms","45.25ms","46.25ms","47.25ms","48.25ms","49.25ms", "50.25ms","51.25ms","52.25ms","53.25ms","54.25ms","55.25ms","56.25ms","57.25ms","58.25ms","59.25ms", "60.25ms","61.25ms","62.25ms","63.25ms","64.25ms","65.25ms","66.25ms","67.25ms","68.25ms","69.25ms", "70.25ms","71.25ms","72.25ms","73.25ms","74.25ms","75.25ms","76.25ms","77.25ms","78.25ms","79.25ms", "80.25ms","81.25ms","82.25ms","83.25ms","84.25ms","85.25ms","86.25ms","87.25ms","88.25ms","89.25ms", "90.25ms","91.25ms","92.25ms","93.25ms","94.25ms","95.25ms","96.25ms","97.25ms","98.25ms","99.25ms", }, + new string[] { "0.26ms","1.26ms","2.26ms","3.26ms","4.26ms","5.26ms","6.26ms","7.26ms","8.26ms","9.26ms", "10.26ms","11.26ms","12.26ms","13.26ms","14.26ms","15.26ms","16.26ms","17.26ms","18.26ms","19.26ms", "20.26ms","21.26ms","22.26ms","23.26ms","24.26ms","25.26ms","26.26ms","27.26ms","28.26ms","29.26ms", "30.26ms","31.26ms","32.26ms","33.26ms","34.26ms","35.26ms","36.26ms","37.26ms","38.26ms","39.26ms", "40.26ms","41.26ms","42.26ms","43.26ms","44.26ms","45.26ms","46.26ms","47.26ms","48.26ms","49.26ms", "50.26ms","51.26ms","52.26ms","53.26ms","54.26ms","55.26ms","56.26ms","57.26ms","58.26ms","59.26ms", "60.26ms","61.26ms","62.26ms","63.26ms","64.26ms","65.26ms","66.26ms","67.26ms","68.26ms","69.26ms", "70.26ms","71.26ms","72.26ms","73.26ms","74.26ms","75.26ms","76.26ms","77.26ms","78.26ms","79.26ms", "80.26ms","81.26ms","82.26ms","83.26ms","84.26ms","85.26ms","86.26ms","87.26ms","88.26ms","89.26ms", "90.26ms","91.26ms","92.26ms","93.26ms","94.26ms","95.26ms","96.26ms","97.26ms","98.26ms","99.26ms", }, + new string[] { "0.27ms","1.27ms","2.27ms","3.27ms","4.27ms","5.27ms","6.27ms","7.27ms","8.27ms","9.27ms", "10.27ms","11.27ms","12.27ms","13.27ms","14.27ms","15.27ms","16.27ms","17.27ms","18.27ms","19.27ms", "20.27ms","21.27ms","22.27ms","23.27ms","24.27ms","25.27ms","26.27ms","27.27ms","28.27ms","29.27ms", "30.27ms","31.27ms","32.27ms","33.27ms","34.27ms","35.27ms","36.27ms","37.27ms","38.27ms","39.27ms", "40.27ms","41.27ms","42.27ms","43.27ms","44.27ms","45.27ms","46.27ms","47.27ms","48.27ms","49.27ms", "50.27ms","51.27ms","52.27ms","53.27ms","54.27ms","55.27ms","56.27ms","57.27ms","58.27ms","59.27ms", "60.27ms","61.27ms","62.27ms","63.27ms","64.27ms","65.27ms","66.27ms","67.27ms","68.27ms","69.27ms", "70.27ms","71.27ms","72.27ms","73.27ms","74.27ms","75.27ms","76.27ms","77.27ms","78.27ms","79.27ms", "80.27ms","81.27ms","82.27ms","83.27ms","84.27ms","85.27ms","86.27ms","87.27ms","88.27ms","89.27ms", "90.27ms","91.27ms","92.27ms","93.27ms","94.27ms","95.27ms","96.27ms","97.27ms","98.27ms","99.27ms", }, + new string[] { "0.28ms","1.28ms","2.28ms","3.28ms","4.28ms","5.28ms","6.28ms","7.28ms","8.28ms","9.28ms", "10.28ms","11.28ms","12.28ms","13.28ms","14.28ms","15.28ms","16.28ms","17.28ms","18.28ms","19.28ms", "20.28ms","21.28ms","22.28ms","23.28ms","24.28ms","25.28ms","26.28ms","27.28ms","28.28ms","29.28ms", "30.28ms","31.28ms","32.28ms","33.28ms","34.28ms","35.28ms","36.28ms","37.28ms","38.28ms","39.28ms", "40.28ms","41.28ms","42.28ms","43.28ms","44.28ms","45.28ms","46.28ms","47.28ms","48.28ms","49.28ms", "50.28ms","51.28ms","52.28ms","53.28ms","54.28ms","55.28ms","56.28ms","57.28ms","58.28ms","59.28ms", "60.28ms","61.28ms","62.28ms","63.28ms","64.28ms","65.28ms","66.28ms","67.28ms","68.28ms","69.28ms", "70.28ms","71.28ms","72.28ms","73.28ms","74.28ms","75.28ms","76.28ms","77.28ms","78.28ms","79.28ms", "80.28ms","81.28ms","82.28ms","83.28ms","84.28ms","85.28ms","86.28ms","87.28ms","88.28ms","89.28ms", "90.28ms","91.28ms","92.28ms","93.28ms","94.28ms","95.28ms","96.28ms","97.28ms","98.28ms","99.28ms", }, + new string[] { "0.29ms","1.29ms","2.29ms","3.29ms","4.29ms","5.29ms","6.29ms","7.29ms","8.29ms","9.29ms", "10.29ms","11.29ms","12.29ms","13.29ms","14.29ms","15.29ms","16.29ms","17.29ms","18.29ms","19.29ms", "20.29ms","21.29ms","22.29ms","23.29ms","24.29ms","25.29ms","26.29ms","27.29ms","28.29ms","29.29ms", "30.29ms","31.29ms","32.29ms","33.29ms","34.29ms","35.29ms","36.29ms","37.29ms","38.29ms","39.29ms", "40.29ms","41.29ms","42.29ms","43.29ms","44.29ms","45.29ms","46.29ms","47.29ms","48.29ms","49.29ms", "50.29ms","51.29ms","52.29ms","53.29ms","54.29ms","55.29ms","56.29ms","57.29ms","58.29ms","59.29ms", "60.29ms","61.29ms","62.29ms","63.29ms","64.29ms","65.29ms","66.29ms","67.29ms","68.29ms","69.29ms", "70.29ms","71.29ms","72.29ms","73.29ms","74.29ms","75.29ms","76.29ms","77.29ms","78.29ms","79.29ms", "80.29ms","81.29ms","82.29ms","83.29ms","84.29ms","85.29ms","86.29ms","87.29ms","88.29ms","89.29ms", "90.29ms","91.29ms","92.29ms","93.29ms","94.29ms","95.29ms","96.29ms","97.29ms","98.29ms","99.29ms", }, + new string[] { "0.30ms","1.30ms","2.30ms","3.30ms","4.30ms","5.30ms","6.30ms","7.30ms","8.30ms","9.30ms", "10.30ms","11.30ms","12.30ms","13.30ms","14.30ms","15.30ms","16.30ms","17.30ms","18.30ms","19.30ms", "20.30ms","21.30ms","22.30ms","23.30ms","24.30ms","25.30ms","26.30ms","27.30ms","28.30ms","29.30ms", "30.30ms","31.30ms","32.30ms","33.30ms","34.30ms","35.30ms","36.30ms","37.30ms","38.30ms","39.30ms", "40.30ms","41.30ms","42.30ms","43.30ms","44.30ms","45.30ms","46.30ms","47.30ms","48.30ms","49.30ms", "50.30ms","51.30ms","52.30ms","53.30ms","54.30ms","55.30ms","56.30ms","57.30ms","58.30ms","59.30ms", "60.30ms","61.30ms","62.30ms","63.30ms","64.30ms","65.30ms","66.30ms","67.30ms","68.30ms","69.30ms", "70.30ms","71.30ms","72.30ms","73.30ms","74.30ms","75.30ms","76.30ms","77.30ms","78.30ms","79.30ms", "80.30ms","81.30ms","82.30ms","83.30ms","84.30ms","85.30ms","86.30ms","87.30ms","88.30ms","89.30ms", "90.30ms","91.30ms","92.30ms","93.30ms","94.30ms","95.30ms","96.30ms","97.30ms","98.30ms","99.30ms", }, + new string[] { "0.31ms","1.31ms","2.31ms","3.31ms","4.31ms","5.31ms","6.31ms","7.31ms","8.31ms","9.31ms", "10.31ms","11.31ms","12.31ms","13.31ms","14.31ms","15.31ms","16.31ms","17.31ms","18.31ms","19.31ms", "20.31ms","21.31ms","22.31ms","23.31ms","24.31ms","25.31ms","26.31ms","27.31ms","28.31ms","29.31ms", "30.31ms","31.31ms","32.31ms","33.31ms","34.31ms","35.31ms","36.31ms","37.31ms","38.31ms","39.31ms", "40.31ms","41.31ms","42.31ms","43.31ms","44.31ms","45.31ms","46.31ms","47.31ms","48.31ms","49.31ms", "50.31ms","51.31ms","52.31ms","53.31ms","54.31ms","55.31ms","56.31ms","57.31ms","58.31ms","59.31ms", "60.31ms","61.31ms","62.31ms","63.31ms","64.31ms","65.31ms","66.31ms","67.31ms","68.31ms","69.31ms", "70.31ms","71.31ms","72.31ms","73.31ms","74.31ms","75.31ms","76.31ms","77.31ms","78.31ms","79.31ms", "80.31ms","81.31ms","82.31ms","83.31ms","84.31ms","85.31ms","86.31ms","87.31ms","88.31ms","89.31ms", "90.31ms","91.31ms","92.31ms","93.31ms","94.31ms","95.31ms","96.31ms","97.31ms","98.31ms","99.31ms", }, + new string[] { "0.32ms","1.32ms","2.32ms","3.32ms","4.32ms","5.32ms","6.32ms","7.32ms","8.32ms","9.32ms", "10.32ms","11.32ms","12.32ms","13.32ms","14.32ms","15.32ms","16.32ms","17.32ms","18.32ms","19.32ms", "20.32ms","21.32ms","22.32ms","23.32ms","24.32ms","25.32ms","26.32ms","27.32ms","28.32ms","29.32ms", "30.32ms","31.32ms","32.32ms","33.32ms","34.32ms","35.32ms","36.32ms","37.32ms","38.32ms","39.32ms", "40.32ms","41.32ms","42.32ms","43.32ms","44.32ms","45.32ms","46.32ms","47.32ms","48.32ms","49.32ms", "50.32ms","51.32ms","52.32ms","53.32ms","54.32ms","55.32ms","56.32ms","57.32ms","58.32ms","59.32ms", "60.32ms","61.32ms","62.32ms","63.32ms","64.32ms","65.32ms","66.32ms","67.32ms","68.32ms","69.32ms", "70.32ms","71.32ms","72.32ms","73.32ms","74.32ms","75.32ms","76.32ms","77.32ms","78.32ms","79.32ms", "80.32ms","81.32ms","82.32ms","83.32ms","84.32ms","85.32ms","86.32ms","87.32ms","88.32ms","89.32ms", "90.32ms","91.32ms","92.32ms","93.32ms","94.32ms","95.32ms","96.32ms","97.32ms","98.32ms","99.32ms", }, + new string[] { "0.33ms","1.33ms","2.33ms","3.33ms","4.33ms","5.33ms","6.33ms","7.33ms","8.33ms","9.33ms", "10.33ms","11.33ms","12.33ms","13.33ms","14.33ms","15.33ms","16.33ms","17.33ms","18.33ms","19.33ms", "20.33ms","21.33ms","22.33ms","23.33ms","24.33ms","25.33ms","26.33ms","27.33ms","28.33ms","29.33ms", "30.33ms","31.33ms","32.33ms","33.33ms","34.33ms","35.33ms","36.33ms","37.33ms","38.33ms","39.33ms", "40.33ms","41.33ms","42.33ms","43.33ms","44.33ms","45.33ms","46.33ms","47.33ms","48.33ms","49.33ms", "50.33ms","51.33ms","52.33ms","53.33ms","54.33ms","55.33ms","56.33ms","57.33ms","58.33ms","59.33ms", "60.33ms","61.33ms","62.33ms","63.33ms","64.33ms","65.33ms","66.33ms","67.33ms","68.33ms","69.33ms", "70.33ms","71.33ms","72.33ms","73.33ms","74.33ms","75.33ms","76.33ms","77.33ms","78.33ms","79.33ms", "80.33ms","81.33ms","82.33ms","83.33ms","84.33ms","85.33ms","86.33ms","87.33ms","88.33ms","89.33ms", "90.33ms","91.33ms","92.33ms","93.33ms","94.33ms","95.33ms","96.33ms","97.33ms","98.33ms","99.33ms", }, + new string[] { "0.34ms","1.34ms","2.34ms","3.34ms","4.34ms","5.34ms","6.34ms","7.34ms","8.34ms","9.34ms", "10.34ms","11.34ms","12.34ms","13.34ms","14.34ms","15.34ms","16.34ms","17.34ms","18.34ms","19.34ms", "20.34ms","21.34ms","22.34ms","23.34ms","24.34ms","25.34ms","26.34ms","27.34ms","28.34ms","29.34ms", "30.34ms","31.34ms","32.34ms","33.34ms","34.34ms","35.34ms","36.34ms","37.34ms","38.34ms","39.34ms", "40.34ms","41.34ms","42.34ms","43.34ms","44.34ms","45.34ms","46.34ms","47.34ms","48.34ms","49.34ms", "50.34ms","51.34ms","52.34ms","53.34ms","54.34ms","55.34ms","56.34ms","57.34ms","58.34ms","59.34ms", "60.34ms","61.34ms","62.34ms","63.34ms","64.34ms","65.34ms","66.34ms","67.34ms","68.34ms","69.34ms", "70.34ms","71.34ms","72.34ms","73.34ms","74.34ms","75.34ms","76.34ms","77.34ms","78.34ms","79.34ms", "80.34ms","81.34ms","82.34ms","83.34ms","84.34ms","85.34ms","86.34ms","87.34ms","88.34ms","89.34ms", "90.34ms","91.34ms","92.34ms","93.34ms","94.34ms","95.34ms","96.34ms","97.34ms","98.34ms","99.34ms", }, + new string[] { "0.35ms","1.35ms","2.35ms","3.35ms","4.35ms","5.35ms","6.35ms","7.35ms","8.35ms","9.35ms", "10.35ms","11.35ms","12.35ms","13.35ms","14.35ms","15.35ms","16.35ms","17.35ms","18.35ms","19.35ms", "20.35ms","21.35ms","22.35ms","23.35ms","24.35ms","25.35ms","26.35ms","27.35ms","28.35ms","29.35ms", "30.35ms","31.35ms","32.35ms","33.35ms","34.35ms","35.35ms","36.35ms","37.35ms","38.35ms","39.35ms", "40.35ms","41.35ms","42.35ms","43.35ms","44.35ms","45.35ms","46.35ms","47.35ms","48.35ms","49.35ms", "50.35ms","51.35ms","52.35ms","53.35ms","54.35ms","55.35ms","56.35ms","57.35ms","58.35ms","59.35ms", "60.35ms","61.35ms","62.35ms","63.35ms","64.35ms","65.35ms","66.35ms","67.35ms","68.35ms","69.35ms", "70.35ms","71.35ms","72.35ms","73.35ms","74.35ms","75.35ms","76.35ms","77.35ms","78.35ms","79.35ms", "80.35ms","81.35ms","82.35ms","83.35ms","84.35ms","85.35ms","86.35ms","87.35ms","88.35ms","89.35ms", "90.35ms","91.35ms","92.35ms","93.35ms","94.35ms","95.35ms","96.35ms","97.35ms","98.35ms","99.35ms", }, + new string[] { "0.36ms","1.36ms","2.36ms","3.36ms","4.36ms","5.36ms","6.36ms","7.36ms","8.36ms","9.36ms", "10.36ms","11.36ms","12.36ms","13.36ms","14.36ms","15.36ms","16.36ms","17.36ms","18.36ms","19.36ms", "20.36ms","21.36ms","22.36ms","23.36ms","24.36ms","25.36ms","26.36ms","27.36ms","28.36ms","29.36ms", "30.36ms","31.36ms","32.36ms","33.36ms","34.36ms","35.36ms","36.36ms","37.36ms","38.36ms","39.36ms", "40.36ms","41.36ms","42.36ms","43.36ms","44.36ms","45.36ms","46.36ms","47.36ms","48.36ms","49.36ms", "50.36ms","51.36ms","52.36ms","53.36ms","54.36ms","55.36ms","56.36ms","57.36ms","58.36ms","59.36ms", "60.36ms","61.36ms","62.36ms","63.36ms","64.36ms","65.36ms","66.36ms","67.36ms","68.36ms","69.36ms", "70.36ms","71.36ms","72.36ms","73.36ms","74.36ms","75.36ms","76.36ms","77.36ms","78.36ms","79.36ms", "80.36ms","81.36ms","82.36ms","83.36ms","84.36ms","85.36ms","86.36ms","87.36ms","88.36ms","89.36ms", "90.36ms","91.36ms","92.36ms","93.36ms","94.36ms","95.36ms","96.36ms","97.36ms","98.36ms","99.36ms", }, + new string[] { "0.37ms","1.37ms","2.37ms","3.37ms","4.37ms","5.37ms","6.37ms","7.37ms","8.37ms","9.37ms", "10.37ms","11.37ms","12.37ms","13.37ms","14.37ms","15.37ms","16.37ms","17.37ms","18.37ms","19.37ms", "20.37ms","21.37ms","22.37ms","23.37ms","24.37ms","25.37ms","26.37ms","27.37ms","28.37ms","29.37ms", "30.37ms","31.37ms","32.37ms","33.37ms","34.37ms","35.37ms","36.37ms","37.37ms","38.37ms","39.37ms", "40.37ms","41.37ms","42.37ms","43.37ms","44.37ms","45.37ms","46.37ms","47.37ms","48.37ms","49.37ms", "50.37ms","51.37ms","52.37ms","53.37ms","54.37ms","55.37ms","56.37ms","57.37ms","58.37ms","59.37ms", "60.37ms","61.37ms","62.37ms","63.37ms","64.37ms","65.37ms","66.37ms","67.37ms","68.37ms","69.37ms", "70.37ms","71.37ms","72.37ms","73.37ms","74.37ms","75.37ms","76.37ms","77.37ms","78.37ms","79.37ms", "80.37ms","81.37ms","82.37ms","83.37ms","84.37ms","85.37ms","86.37ms","87.37ms","88.37ms","89.37ms", "90.37ms","91.37ms","92.37ms","93.37ms","94.37ms","95.37ms","96.37ms","97.37ms","98.37ms","99.37ms", }, + new string[] { "0.38ms","1.38ms","2.38ms","3.38ms","4.38ms","5.38ms","6.38ms","7.38ms","8.38ms","9.38ms", "10.38ms","11.38ms","12.38ms","13.38ms","14.38ms","15.38ms","16.38ms","17.38ms","18.38ms","19.38ms", "20.38ms","21.38ms","22.38ms","23.38ms","24.38ms","25.38ms","26.38ms","27.38ms","28.38ms","29.38ms", "30.38ms","31.38ms","32.38ms","33.38ms","34.38ms","35.38ms","36.38ms","37.38ms","38.38ms","39.38ms", "40.38ms","41.38ms","42.38ms","43.38ms","44.38ms","45.38ms","46.38ms","47.38ms","48.38ms","49.38ms", "50.38ms","51.38ms","52.38ms","53.38ms","54.38ms","55.38ms","56.38ms","57.38ms","58.38ms","59.38ms", "60.38ms","61.38ms","62.38ms","63.38ms","64.38ms","65.38ms","66.38ms","67.38ms","68.38ms","69.38ms", "70.38ms","71.38ms","72.38ms","73.38ms","74.38ms","75.38ms","76.38ms","77.38ms","78.38ms","79.38ms", "80.38ms","81.38ms","82.38ms","83.38ms","84.38ms","85.38ms","86.38ms","87.38ms","88.38ms","89.38ms", "90.38ms","91.38ms","92.38ms","93.38ms","94.38ms","95.38ms","96.38ms","97.38ms","98.38ms","99.38ms", }, + new string[] { "0.39ms","1.39ms","2.39ms","3.39ms","4.39ms","5.39ms","6.39ms","7.39ms","8.39ms","9.39ms", "10.39ms","11.39ms","12.39ms","13.39ms","14.39ms","15.39ms","16.39ms","17.39ms","18.39ms","19.39ms", "20.39ms","21.39ms","22.39ms","23.39ms","24.39ms","25.39ms","26.39ms","27.39ms","28.39ms","29.39ms", "30.39ms","31.39ms","32.39ms","33.39ms","34.39ms","35.39ms","36.39ms","37.39ms","38.39ms","39.39ms", "40.39ms","41.39ms","42.39ms","43.39ms","44.39ms","45.39ms","46.39ms","47.39ms","48.39ms","49.39ms", "50.39ms","51.39ms","52.39ms","53.39ms","54.39ms","55.39ms","56.39ms","57.39ms","58.39ms","59.39ms", "60.39ms","61.39ms","62.39ms","63.39ms","64.39ms","65.39ms","66.39ms","67.39ms","68.39ms","69.39ms", "70.39ms","71.39ms","72.39ms","73.39ms","74.39ms","75.39ms","76.39ms","77.39ms","78.39ms","79.39ms", "80.39ms","81.39ms","82.39ms","83.39ms","84.39ms","85.39ms","86.39ms","87.39ms","88.39ms","89.39ms", "90.39ms","91.39ms","92.39ms","93.39ms","94.39ms","95.39ms","96.39ms","97.39ms","98.39ms","99.39ms", }, + new string[] { "0.40ms","1.40ms","2.40ms","3.40ms","4.40ms","5.40ms","6.40ms","7.40ms","8.40ms","9.40ms", "10.40ms","11.40ms","12.40ms","13.40ms","14.40ms","15.40ms","16.40ms","17.40ms","18.40ms","19.40ms", "20.40ms","21.40ms","22.40ms","23.40ms","24.40ms","25.40ms","26.40ms","27.40ms","28.40ms","29.40ms", "30.40ms","31.40ms","32.40ms","33.40ms","34.40ms","35.40ms","36.40ms","37.40ms","38.40ms","39.40ms", "40.40ms","41.40ms","42.40ms","43.40ms","44.40ms","45.40ms","46.40ms","47.40ms","48.40ms","49.40ms", "50.40ms","51.40ms","52.40ms","53.40ms","54.40ms","55.40ms","56.40ms","57.40ms","58.40ms","59.40ms", "60.40ms","61.40ms","62.40ms","63.40ms","64.40ms","65.40ms","66.40ms","67.40ms","68.40ms","69.40ms", "70.40ms","71.40ms","72.40ms","73.40ms","74.40ms","75.40ms","76.40ms","77.40ms","78.40ms","79.40ms", "80.40ms","81.40ms","82.40ms","83.40ms","84.40ms","85.40ms","86.40ms","87.40ms","88.40ms","89.40ms", "90.40ms","91.40ms","92.40ms","93.40ms","94.40ms","95.40ms","96.40ms","97.40ms","98.40ms","99.40ms", }, + new string[] { "0.41ms","1.41ms","2.41ms","3.41ms","4.41ms","5.41ms","6.41ms","7.41ms","8.41ms","9.41ms", "10.41ms","11.41ms","12.41ms","13.41ms","14.41ms","15.41ms","16.41ms","17.41ms","18.41ms","19.41ms", "20.41ms","21.41ms","22.41ms","23.41ms","24.41ms","25.41ms","26.41ms","27.41ms","28.41ms","29.41ms", "30.41ms","31.41ms","32.41ms","33.41ms","34.41ms","35.41ms","36.41ms","37.41ms","38.41ms","39.41ms", "40.41ms","41.41ms","42.41ms","43.41ms","44.41ms","45.41ms","46.41ms","47.41ms","48.41ms","49.41ms", "50.41ms","51.41ms","52.41ms","53.41ms","54.41ms","55.41ms","56.41ms","57.41ms","58.41ms","59.41ms", "60.41ms","61.41ms","62.41ms","63.41ms","64.41ms","65.41ms","66.41ms","67.41ms","68.41ms","69.41ms", "70.41ms","71.41ms","72.41ms","73.41ms","74.41ms","75.41ms","76.41ms","77.41ms","78.41ms","79.41ms", "80.41ms","81.41ms","82.41ms","83.41ms","84.41ms","85.41ms","86.41ms","87.41ms","88.41ms","89.41ms", "90.41ms","91.41ms","92.41ms","93.41ms","94.41ms","95.41ms","96.41ms","97.41ms","98.41ms","99.41ms", }, + new string[] { "0.42ms","1.42ms","2.42ms","3.42ms","4.42ms","5.42ms","6.42ms","7.42ms","8.42ms","9.42ms", "10.42ms","11.42ms","12.42ms","13.42ms","14.42ms","15.42ms","16.42ms","17.42ms","18.42ms","19.42ms", "20.42ms","21.42ms","22.42ms","23.42ms","24.42ms","25.42ms","26.42ms","27.42ms","28.42ms","29.42ms", "30.42ms","31.42ms","32.42ms","33.42ms","34.42ms","35.42ms","36.42ms","37.42ms","38.42ms","39.42ms", "40.42ms","41.42ms","42.42ms","43.42ms","44.42ms","45.42ms","46.42ms","47.42ms","48.42ms","49.42ms", "50.42ms","51.42ms","52.42ms","53.42ms","54.42ms","55.42ms","56.42ms","57.42ms","58.42ms","59.42ms", "60.42ms","61.42ms","62.42ms","63.42ms","64.42ms","65.42ms","66.42ms","67.42ms","68.42ms","69.42ms", "70.42ms","71.42ms","72.42ms","73.42ms","74.42ms","75.42ms","76.42ms","77.42ms","78.42ms","79.42ms", "80.42ms","81.42ms","82.42ms","83.42ms","84.42ms","85.42ms","86.42ms","87.42ms","88.42ms","89.42ms", "90.42ms","91.42ms","92.42ms","93.42ms","94.42ms","95.42ms","96.42ms","97.42ms","98.42ms","99.42ms", }, + new string[] { "0.43ms","1.43ms","2.43ms","3.43ms","4.43ms","5.43ms","6.43ms","7.43ms","8.43ms","9.43ms", "10.43ms","11.43ms","12.43ms","13.43ms","14.43ms","15.43ms","16.43ms","17.43ms","18.43ms","19.43ms", "20.43ms","21.43ms","22.43ms","23.43ms","24.43ms","25.43ms","26.43ms","27.43ms","28.43ms","29.43ms", "30.43ms","31.43ms","32.43ms","33.43ms","34.43ms","35.43ms","36.43ms","37.43ms","38.43ms","39.43ms", "40.43ms","41.43ms","42.43ms","43.43ms","44.43ms","45.43ms","46.43ms","47.43ms","48.43ms","49.43ms", "50.43ms","51.43ms","52.43ms","53.43ms","54.43ms","55.43ms","56.43ms","57.43ms","58.43ms","59.43ms", "60.43ms","61.43ms","62.43ms","63.43ms","64.43ms","65.43ms","66.43ms","67.43ms","68.43ms","69.43ms", "70.43ms","71.43ms","72.43ms","73.43ms","74.43ms","75.43ms","76.43ms","77.43ms","78.43ms","79.43ms", "80.43ms","81.43ms","82.43ms","83.43ms","84.43ms","85.43ms","86.43ms","87.43ms","88.43ms","89.43ms", "90.43ms","91.43ms","92.43ms","93.43ms","94.43ms","95.43ms","96.43ms","97.43ms","98.43ms","99.43ms", }, + new string[] { "0.44ms","1.44ms","2.44ms","3.44ms","4.44ms","5.44ms","6.44ms","7.44ms","8.44ms","9.44ms", "10.44ms","11.44ms","12.44ms","13.44ms","14.44ms","15.44ms","16.44ms","17.44ms","18.44ms","19.44ms", "20.44ms","21.44ms","22.44ms","23.44ms","24.44ms","25.44ms","26.44ms","27.44ms","28.44ms","29.44ms", "30.44ms","31.44ms","32.44ms","33.44ms","34.44ms","35.44ms","36.44ms","37.44ms","38.44ms","39.44ms", "40.44ms","41.44ms","42.44ms","43.44ms","44.44ms","45.44ms","46.44ms","47.44ms","48.44ms","49.44ms", "50.44ms","51.44ms","52.44ms","53.44ms","54.44ms","55.44ms","56.44ms","57.44ms","58.44ms","59.44ms", "60.44ms","61.44ms","62.44ms","63.44ms","64.44ms","65.44ms","66.44ms","67.44ms","68.44ms","69.44ms", "70.44ms","71.44ms","72.44ms","73.44ms","74.44ms","75.44ms","76.44ms","77.44ms","78.44ms","79.44ms", "80.44ms","81.44ms","82.44ms","83.44ms","84.44ms","85.44ms","86.44ms","87.44ms","88.44ms","89.44ms", "90.44ms","91.44ms","92.44ms","93.44ms","94.44ms","95.44ms","96.44ms","97.44ms","98.44ms","99.44ms", }, + new string[] { "0.45ms","1.45ms","2.45ms","3.45ms","4.45ms","5.45ms","6.45ms","7.45ms","8.45ms","9.45ms", "10.45ms","11.45ms","12.45ms","13.45ms","14.45ms","15.45ms","16.45ms","17.45ms","18.45ms","19.45ms", "20.45ms","21.45ms","22.45ms","23.45ms","24.45ms","25.45ms","26.45ms","27.45ms","28.45ms","29.45ms", "30.45ms","31.45ms","32.45ms","33.45ms","34.45ms","35.45ms","36.45ms","37.45ms","38.45ms","39.45ms", "40.45ms","41.45ms","42.45ms","43.45ms","44.45ms","45.45ms","46.45ms","47.45ms","48.45ms","49.45ms", "50.45ms","51.45ms","52.45ms","53.45ms","54.45ms","55.45ms","56.45ms","57.45ms","58.45ms","59.45ms", "60.45ms","61.45ms","62.45ms","63.45ms","64.45ms","65.45ms","66.45ms","67.45ms","68.45ms","69.45ms", "70.45ms","71.45ms","72.45ms","73.45ms","74.45ms","75.45ms","76.45ms","77.45ms","78.45ms","79.45ms", "80.45ms","81.45ms","82.45ms","83.45ms","84.45ms","85.45ms","86.45ms","87.45ms","88.45ms","89.45ms", "90.45ms","91.45ms","92.45ms","93.45ms","94.45ms","95.45ms","96.45ms","97.45ms","98.45ms","99.45ms", }, + new string[] { "0.46ms","1.46ms","2.46ms","3.46ms","4.46ms","5.46ms","6.46ms","7.46ms","8.46ms","9.46ms", "10.46ms","11.46ms","12.46ms","13.46ms","14.46ms","15.46ms","16.46ms","17.46ms","18.46ms","19.46ms", "20.46ms","21.46ms","22.46ms","23.46ms","24.46ms","25.46ms","26.46ms","27.46ms","28.46ms","29.46ms", "30.46ms","31.46ms","32.46ms","33.46ms","34.46ms","35.46ms","36.46ms","37.46ms","38.46ms","39.46ms", "40.46ms","41.46ms","42.46ms","43.46ms","44.46ms","45.46ms","46.46ms","47.46ms","48.46ms","49.46ms", "50.46ms","51.46ms","52.46ms","53.46ms","54.46ms","55.46ms","56.46ms","57.46ms","58.46ms","59.46ms", "60.46ms","61.46ms","62.46ms","63.46ms","64.46ms","65.46ms","66.46ms","67.46ms","68.46ms","69.46ms", "70.46ms","71.46ms","72.46ms","73.46ms","74.46ms","75.46ms","76.46ms","77.46ms","78.46ms","79.46ms", "80.46ms","81.46ms","82.46ms","83.46ms","84.46ms","85.46ms","86.46ms","87.46ms","88.46ms","89.46ms", "90.46ms","91.46ms","92.46ms","93.46ms","94.46ms","95.46ms","96.46ms","97.46ms","98.46ms","99.46ms", }, + new string[] { "0.47ms","1.47ms","2.47ms","3.47ms","4.47ms","5.47ms","6.47ms","7.47ms","8.47ms","9.47ms", "10.47ms","11.47ms","12.47ms","13.47ms","14.47ms","15.47ms","16.47ms","17.47ms","18.47ms","19.47ms", "20.47ms","21.47ms","22.47ms","23.47ms","24.47ms","25.47ms","26.47ms","27.47ms","28.47ms","29.47ms", "30.47ms","31.47ms","32.47ms","33.47ms","34.47ms","35.47ms","36.47ms","37.47ms","38.47ms","39.47ms", "40.47ms","41.47ms","42.47ms","43.47ms","44.47ms","45.47ms","46.47ms","47.47ms","48.47ms","49.47ms", "50.47ms","51.47ms","52.47ms","53.47ms","54.47ms","55.47ms","56.47ms","57.47ms","58.47ms","59.47ms", "60.47ms","61.47ms","62.47ms","63.47ms","64.47ms","65.47ms","66.47ms","67.47ms","68.47ms","69.47ms", "70.47ms","71.47ms","72.47ms","73.47ms","74.47ms","75.47ms","76.47ms","77.47ms","78.47ms","79.47ms", "80.47ms","81.47ms","82.47ms","83.47ms","84.47ms","85.47ms","86.47ms","87.47ms","88.47ms","89.47ms", "90.47ms","91.47ms","92.47ms","93.47ms","94.47ms","95.47ms","96.47ms","97.47ms","98.47ms","99.47ms", }, + new string[] { "0.48ms","1.48ms","2.48ms","3.48ms","4.48ms","5.48ms","6.48ms","7.48ms","8.48ms","9.48ms", "10.48ms","11.48ms","12.48ms","13.48ms","14.48ms","15.48ms","16.48ms","17.48ms","18.48ms","19.48ms", "20.48ms","21.48ms","22.48ms","23.48ms","24.48ms","25.48ms","26.48ms","27.48ms","28.48ms","29.48ms", "30.48ms","31.48ms","32.48ms","33.48ms","34.48ms","35.48ms","36.48ms","37.48ms","38.48ms","39.48ms", "40.48ms","41.48ms","42.48ms","43.48ms","44.48ms","45.48ms","46.48ms","47.48ms","48.48ms","49.48ms", "50.48ms","51.48ms","52.48ms","53.48ms","54.48ms","55.48ms","56.48ms","57.48ms","58.48ms","59.48ms", "60.48ms","61.48ms","62.48ms","63.48ms","64.48ms","65.48ms","66.48ms","67.48ms","68.48ms","69.48ms", "70.48ms","71.48ms","72.48ms","73.48ms","74.48ms","75.48ms","76.48ms","77.48ms","78.48ms","79.48ms", "80.48ms","81.48ms","82.48ms","83.48ms","84.48ms","85.48ms","86.48ms","87.48ms","88.48ms","89.48ms", "90.48ms","91.48ms","92.48ms","93.48ms","94.48ms","95.48ms","96.48ms","97.48ms","98.48ms","99.48ms", }, + new string[] { "0.49ms","1.49ms","2.49ms","3.49ms","4.49ms","5.49ms","6.49ms","7.49ms","8.49ms","9.49ms", "10.49ms","11.49ms","12.49ms","13.49ms","14.49ms","15.49ms","16.49ms","17.49ms","18.49ms","19.49ms", "20.49ms","21.49ms","22.49ms","23.49ms","24.49ms","25.49ms","26.49ms","27.49ms","28.49ms","29.49ms", "30.49ms","31.49ms","32.49ms","33.49ms","34.49ms","35.49ms","36.49ms","37.49ms","38.49ms","39.49ms", "40.49ms","41.49ms","42.49ms","43.49ms","44.49ms","45.49ms","46.49ms","47.49ms","48.49ms","49.49ms", "50.49ms","51.49ms","52.49ms","53.49ms","54.49ms","55.49ms","56.49ms","57.49ms","58.49ms","59.49ms", "60.49ms","61.49ms","62.49ms","63.49ms","64.49ms","65.49ms","66.49ms","67.49ms","68.49ms","69.49ms", "70.49ms","71.49ms","72.49ms","73.49ms","74.49ms","75.49ms","76.49ms","77.49ms","78.49ms","79.49ms", "80.49ms","81.49ms","82.49ms","83.49ms","84.49ms","85.49ms","86.49ms","87.49ms","88.49ms","89.49ms", "90.49ms","91.49ms","92.49ms","93.49ms","94.49ms","95.49ms","96.49ms","97.49ms","98.49ms","99.49ms", }, + new string[] { "0.50ms","1.50ms","2.50ms","3.50ms","4.50ms","5.50ms","6.50ms","7.50ms","8.50ms","9.50ms", "10.50ms","11.50ms","12.50ms","13.50ms","14.50ms","15.50ms","16.50ms","17.50ms","18.50ms","19.50ms", "20.50ms","21.50ms","22.50ms","23.50ms","24.50ms","25.50ms","26.50ms","27.50ms","28.50ms","29.50ms", "30.50ms","31.50ms","32.50ms","33.50ms","34.50ms","35.50ms","36.50ms","37.50ms","38.50ms","39.50ms", "40.50ms","41.50ms","42.50ms","43.50ms","44.50ms","45.50ms","46.50ms","47.50ms","48.50ms","49.50ms", "50.50ms","51.50ms","52.50ms","53.50ms","54.50ms","55.50ms","56.50ms","57.50ms","58.50ms","59.50ms", "60.50ms","61.50ms","62.50ms","63.50ms","64.50ms","65.50ms","66.50ms","67.50ms","68.50ms","69.50ms", "70.50ms","71.50ms","72.50ms","73.50ms","74.50ms","75.50ms","76.50ms","77.50ms","78.50ms","79.50ms", "80.50ms","81.50ms","82.50ms","83.50ms","84.50ms","85.50ms","86.50ms","87.50ms","88.50ms","89.50ms", "90.50ms","91.50ms","92.50ms","93.50ms","94.50ms","95.50ms","96.50ms","97.50ms","98.50ms","99.50ms", }, + new string[] { "0.51ms","1.51ms","2.51ms","3.51ms","4.51ms","5.51ms","6.51ms","7.51ms","8.51ms","9.51ms", "10.51ms","11.51ms","12.51ms","13.51ms","14.51ms","15.51ms","16.51ms","17.51ms","18.51ms","19.51ms", "20.51ms","21.51ms","22.51ms","23.51ms","24.51ms","25.51ms","26.51ms","27.51ms","28.51ms","29.51ms", "30.51ms","31.51ms","32.51ms","33.51ms","34.51ms","35.51ms","36.51ms","37.51ms","38.51ms","39.51ms", "40.51ms","41.51ms","42.51ms","43.51ms","44.51ms","45.51ms","46.51ms","47.51ms","48.51ms","49.51ms", "50.51ms","51.51ms","52.51ms","53.51ms","54.51ms","55.51ms","56.51ms","57.51ms","58.51ms","59.51ms", "60.51ms","61.51ms","62.51ms","63.51ms","64.51ms","65.51ms","66.51ms","67.51ms","68.51ms","69.51ms", "70.51ms","71.51ms","72.51ms","73.51ms","74.51ms","75.51ms","76.51ms","77.51ms","78.51ms","79.51ms", "80.51ms","81.51ms","82.51ms","83.51ms","84.51ms","85.51ms","86.51ms","87.51ms","88.51ms","89.51ms", "90.51ms","91.51ms","92.51ms","93.51ms","94.51ms","95.51ms","96.51ms","97.51ms","98.51ms","99.51ms", }, + new string[] { "0.52ms","1.52ms","2.52ms","3.52ms","4.52ms","5.52ms","6.52ms","7.52ms","8.52ms","9.52ms", "10.52ms","11.52ms","12.52ms","13.52ms","14.52ms","15.52ms","16.52ms","17.52ms","18.52ms","19.52ms", "20.52ms","21.52ms","22.52ms","23.52ms","24.52ms","25.52ms","26.52ms","27.52ms","28.52ms","29.52ms", "30.52ms","31.52ms","32.52ms","33.52ms","34.52ms","35.52ms","36.52ms","37.52ms","38.52ms","39.52ms", "40.52ms","41.52ms","42.52ms","43.52ms","44.52ms","45.52ms","46.52ms","47.52ms","48.52ms","49.52ms", "50.52ms","51.52ms","52.52ms","53.52ms","54.52ms","55.52ms","56.52ms","57.52ms","58.52ms","59.52ms", "60.52ms","61.52ms","62.52ms","63.52ms","64.52ms","65.52ms","66.52ms","67.52ms","68.52ms","69.52ms", "70.52ms","71.52ms","72.52ms","73.52ms","74.52ms","75.52ms","76.52ms","77.52ms","78.52ms","79.52ms", "80.52ms","81.52ms","82.52ms","83.52ms","84.52ms","85.52ms","86.52ms","87.52ms","88.52ms","89.52ms", "90.52ms","91.52ms","92.52ms","93.52ms","94.52ms","95.52ms","96.52ms","97.52ms","98.52ms","99.52ms", }, + new string[] { "0.53ms","1.53ms","2.53ms","3.53ms","4.53ms","5.53ms","6.53ms","7.53ms","8.53ms","9.53ms", "10.53ms","11.53ms","12.53ms","13.53ms","14.53ms","15.53ms","16.53ms","17.53ms","18.53ms","19.53ms", "20.53ms","21.53ms","22.53ms","23.53ms","24.53ms","25.53ms","26.53ms","27.53ms","28.53ms","29.53ms", "30.53ms","31.53ms","32.53ms","33.53ms","34.53ms","35.53ms","36.53ms","37.53ms","38.53ms","39.53ms", "40.53ms","41.53ms","42.53ms","43.53ms","44.53ms","45.53ms","46.53ms","47.53ms","48.53ms","49.53ms", "50.53ms","51.53ms","52.53ms","53.53ms","54.53ms","55.53ms","56.53ms","57.53ms","58.53ms","59.53ms", "60.53ms","61.53ms","62.53ms","63.53ms","64.53ms","65.53ms","66.53ms","67.53ms","68.53ms","69.53ms", "70.53ms","71.53ms","72.53ms","73.53ms","74.53ms","75.53ms","76.53ms","77.53ms","78.53ms","79.53ms", "80.53ms","81.53ms","82.53ms","83.53ms","84.53ms","85.53ms","86.53ms","87.53ms","88.53ms","89.53ms", "90.53ms","91.53ms","92.53ms","93.53ms","94.53ms","95.53ms","96.53ms","97.53ms","98.53ms","99.53ms", }, + new string[] { "0.54ms","1.54ms","2.54ms","3.54ms","4.54ms","5.54ms","6.54ms","7.54ms","8.54ms","9.54ms", "10.54ms","11.54ms","12.54ms","13.54ms","14.54ms","15.54ms","16.54ms","17.54ms","18.54ms","19.54ms", "20.54ms","21.54ms","22.54ms","23.54ms","24.54ms","25.54ms","26.54ms","27.54ms","28.54ms","29.54ms", "30.54ms","31.54ms","32.54ms","33.54ms","34.54ms","35.54ms","36.54ms","37.54ms","38.54ms","39.54ms", "40.54ms","41.54ms","42.54ms","43.54ms","44.54ms","45.54ms","46.54ms","47.54ms","48.54ms","49.54ms", "50.54ms","51.54ms","52.54ms","53.54ms","54.54ms","55.54ms","56.54ms","57.54ms","58.54ms","59.54ms", "60.54ms","61.54ms","62.54ms","63.54ms","64.54ms","65.54ms","66.54ms","67.54ms","68.54ms","69.54ms", "70.54ms","71.54ms","72.54ms","73.54ms","74.54ms","75.54ms","76.54ms","77.54ms","78.54ms","79.54ms", "80.54ms","81.54ms","82.54ms","83.54ms","84.54ms","85.54ms","86.54ms","87.54ms","88.54ms","89.54ms", "90.54ms","91.54ms","92.54ms","93.54ms","94.54ms","95.54ms","96.54ms","97.54ms","98.54ms","99.54ms", }, + new string[] { "0.55ms","1.55ms","2.55ms","3.55ms","4.55ms","5.55ms","6.55ms","7.55ms","8.55ms","9.55ms", "10.55ms","11.55ms","12.55ms","13.55ms","14.55ms","15.55ms","16.55ms","17.55ms","18.55ms","19.55ms", "20.55ms","21.55ms","22.55ms","23.55ms","24.55ms","25.55ms","26.55ms","27.55ms","28.55ms","29.55ms", "30.55ms","31.55ms","32.55ms","33.55ms","34.55ms","35.55ms","36.55ms","37.55ms","38.55ms","39.55ms", "40.55ms","41.55ms","42.55ms","43.55ms","44.55ms","45.55ms","46.55ms","47.55ms","48.55ms","49.55ms", "50.55ms","51.55ms","52.55ms","53.55ms","54.55ms","55.55ms","56.55ms","57.55ms","58.55ms","59.55ms", "60.55ms","61.55ms","62.55ms","63.55ms","64.55ms","65.55ms","66.55ms","67.55ms","68.55ms","69.55ms", "70.55ms","71.55ms","72.55ms","73.55ms","74.55ms","75.55ms","76.55ms","77.55ms","78.55ms","79.55ms", "80.55ms","81.55ms","82.55ms","83.55ms","84.55ms","85.55ms","86.55ms","87.55ms","88.55ms","89.55ms", "90.55ms","91.55ms","92.55ms","93.55ms","94.55ms","95.55ms","96.55ms","97.55ms","98.55ms","99.55ms", }, + new string[] { "0.56ms","1.56ms","2.56ms","3.56ms","4.56ms","5.56ms","6.56ms","7.56ms","8.56ms","9.56ms", "10.56ms","11.56ms","12.56ms","13.56ms","14.56ms","15.56ms","16.56ms","17.56ms","18.56ms","19.56ms", "20.56ms","21.56ms","22.56ms","23.56ms","24.56ms","25.56ms","26.56ms","27.56ms","28.56ms","29.56ms", "30.56ms","31.56ms","32.56ms","33.56ms","34.56ms","35.56ms","36.56ms","37.56ms","38.56ms","39.56ms", "40.56ms","41.56ms","42.56ms","43.56ms","44.56ms","45.56ms","46.56ms","47.56ms","48.56ms","49.56ms", "50.56ms","51.56ms","52.56ms","53.56ms","54.56ms","55.56ms","56.56ms","57.56ms","58.56ms","59.56ms", "60.56ms","61.56ms","62.56ms","63.56ms","64.56ms","65.56ms","66.56ms","67.56ms","68.56ms","69.56ms", "70.56ms","71.56ms","72.56ms","73.56ms","74.56ms","75.56ms","76.56ms","77.56ms","78.56ms","79.56ms", "80.56ms","81.56ms","82.56ms","83.56ms","84.56ms","85.56ms","86.56ms","87.56ms","88.56ms","89.56ms", "90.56ms","91.56ms","92.56ms","93.56ms","94.56ms","95.56ms","96.56ms","97.56ms","98.56ms","99.56ms", }, + new string[] { "0.57ms","1.57ms","2.57ms","3.57ms","4.57ms","5.57ms","6.57ms","7.57ms","8.57ms","9.57ms", "10.57ms","11.57ms","12.57ms","13.57ms","14.57ms","15.57ms","16.57ms","17.57ms","18.57ms","19.57ms", "20.57ms","21.57ms","22.57ms","23.57ms","24.57ms","25.57ms","26.57ms","27.57ms","28.57ms","29.57ms", "30.57ms","31.57ms","32.57ms","33.57ms","34.57ms","35.57ms","36.57ms","37.57ms","38.57ms","39.57ms", "40.57ms","41.57ms","42.57ms","43.57ms","44.57ms","45.57ms","46.57ms","47.57ms","48.57ms","49.57ms", "50.57ms","51.57ms","52.57ms","53.57ms","54.57ms","55.57ms","56.57ms","57.57ms","58.57ms","59.57ms", "60.57ms","61.57ms","62.57ms","63.57ms","64.57ms","65.57ms","66.57ms","67.57ms","68.57ms","69.57ms", "70.57ms","71.57ms","72.57ms","73.57ms","74.57ms","75.57ms","76.57ms","77.57ms","78.57ms","79.57ms", "80.57ms","81.57ms","82.57ms","83.57ms","84.57ms","85.57ms","86.57ms","87.57ms","88.57ms","89.57ms", "90.57ms","91.57ms","92.57ms","93.57ms","94.57ms","95.57ms","96.57ms","97.57ms","98.57ms","99.57ms", }, + new string[] { "0.58ms","1.58ms","2.58ms","3.58ms","4.58ms","5.58ms","6.58ms","7.58ms","8.58ms","9.58ms", "10.58ms","11.58ms","12.58ms","13.58ms","14.58ms","15.58ms","16.58ms","17.58ms","18.58ms","19.58ms", "20.58ms","21.58ms","22.58ms","23.58ms","24.58ms","25.58ms","26.58ms","27.58ms","28.58ms","29.58ms", "30.58ms","31.58ms","32.58ms","33.58ms","34.58ms","35.58ms","36.58ms","37.58ms","38.58ms","39.58ms", "40.58ms","41.58ms","42.58ms","43.58ms","44.58ms","45.58ms","46.58ms","47.58ms","48.58ms","49.58ms", "50.58ms","51.58ms","52.58ms","53.58ms","54.58ms","55.58ms","56.58ms","57.58ms","58.58ms","59.58ms", "60.58ms","61.58ms","62.58ms","63.58ms","64.58ms","65.58ms","66.58ms","67.58ms","68.58ms","69.58ms", "70.58ms","71.58ms","72.58ms","73.58ms","74.58ms","75.58ms","76.58ms","77.58ms","78.58ms","79.58ms", "80.58ms","81.58ms","82.58ms","83.58ms","84.58ms","85.58ms","86.58ms","87.58ms","88.58ms","89.58ms", "90.58ms","91.58ms","92.58ms","93.58ms","94.58ms","95.58ms","96.58ms","97.58ms","98.58ms","99.58ms", }, + new string[] { "0.59ms","1.59ms","2.59ms","3.59ms","4.59ms","5.59ms","6.59ms","7.59ms","8.59ms","9.59ms", "10.59ms","11.59ms","12.59ms","13.59ms","14.59ms","15.59ms","16.59ms","17.59ms","18.59ms","19.59ms", "20.59ms","21.59ms","22.59ms","23.59ms","24.59ms","25.59ms","26.59ms","27.59ms","28.59ms","29.59ms", "30.59ms","31.59ms","32.59ms","33.59ms","34.59ms","35.59ms","36.59ms","37.59ms","38.59ms","39.59ms", "40.59ms","41.59ms","42.59ms","43.59ms","44.59ms","45.59ms","46.59ms","47.59ms","48.59ms","49.59ms", "50.59ms","51.59ms","52.59ms","53.59ms","54.59ms","55.59ms","56.59ms","57.59ms","58.59ms","59.59ms", "60.59ms","61.59ms","62.59ms","63.59ms","64.59ms","65.59ms","66.59ms","67.59ms","68.59ms","69.59ms", "70.59ms","71.59ms","72.59ms","73.59ms","74.59ms","75.59ms","76.59ms","77.59ms","78.59ms","79.59ms", "80.59ms","81.59ms","82.59ms","83.59ms","84.59ms","85.59ms","86.59ms","87.59ms","88.59ms","89.59ms", "90.59ms","91.59ms","92.59ms","93.59ms","94.59ms","95.59ms","96.59ms","97.59ms","98.59ms","99.59ms", }, + new string[] { "0.60ms","1.60ms","2.60ms","3.60ms","4.60ms","5.60ms","6.60ms","7.60ms","8.60ms","9.60ms", "10.60ms","11.60ms","12.60ms","13.60ms","14.60ms","15.60ms","16.60ms","17.60ms","18.60ms","19.60ms", "20.60ms","21.60ms","22.60ms","23.60ms","24.60ms","25.60ms","26.60ms","27.60ms","28.60ms","29.60ms", "30.60ms","31.60ms","32.60ms","33.60ms","34.60ms","35.60ms","36.60ms","37.60ms","38.60ms","39.60ms", "40.60ms","41.60ms","42.60ms","43.60ms","44.60ms","45.60ms","46.60ms","47.60ms","48.60ms","49.60ms", "50.60ms","51.60ms","52.60ms","53.60ms","54.60ms","55.60ms","56.60ms","57.60ms","58.60ms","59.60ms", "60.60ms","61.60ms","62.60ms","63.60ms","64.60ms","65.60ms","66.60ms","67.60ms","68.60ms","69.60ms", "70.60ms","71.60ms","72.60ms","73.60ms","74.60ms","75.60ms","76.60ms","77.60ms","78.60ms","79.60ms", "80.60ms","81.60ms","82.60ms","83.60ms","84.60ms","85.60ms","86.60ms","87.60ms","88.60ms","89.60ms", "90.60ms","91.60ms","92.60ms","93.60ms","94.60ms","95.60ms","96.60ms","97.60ms","98.60ms","99.60ms", }, + new string[] { "0.61ms","1.61ms","2.61ms","3.61ms","4.61ms","5.61ms","6.61ms","7.61ms","8.61ms","9.61ms", "10.61ms","11.61ms","12.61ms","13.61ms","14.61ms","15.61ms","16.61ms","17.61ms","18.61ms","19.61ms", "20.61ms","21.61ms","22.61ms","23.61ms","24.61ms","25.61ms","26.61ms","27.61ms","28.61ms","29.61ms", "30.61ms","31.61ms","32.61ms","33.61ms","34.61ms","35.61ms","36.61ms","37.61ms","38.61ms","39.61ms", "40.61ms","41.61ms","42.61ms","43.61ms","44.61ms","45.61ms","46.61ms","47.61ms","48.61ms","49.61ms", "50.61ms","51.61ms","52.61ms","53.61ms","54.61ms","55.61ms","56.61ms","57.61ms","58.61ms","59.61ms", "60.61ms","61.61ms","62.61ms","63.61ms","64.61ms","65.61ms","66.61ms","67.61ms","68.61ms","69.61ms", "70.61ms","71.61ms","72.61ms","73.61ms","74.61ms","75.61ms","76.61ms","77.61ms","78.61ms","79.61ms", "80.61ms","81.61ms","82.61ms","83.61ms","84.61ms","85.61ms","86.61ms","87.61ms","88.61ms","89.61ms", "90.61ms","91.61ms","92.61ms","93.61ms","94.61ms","95.61ms","96.61ms","97.61ms","98.61ms","99.61ms", }, + new string[] { "0.62ms","1.62ms","2.62ms","3.62ms","4.62ms","5.62ms","6.62ms","7.62ms","8.62ms","9.62ms", "10.62ms","11.62ms","12.62ms","13.62ms","14.62ms","15.62ms","16.62ms","17.62ms","18.62ms","19.62ms", "20.62ms","21.62ms","22.62ms","23.62ms","24.62ms","25.62ms","26.62ms","27.62ms","28.62ms","29.62ms", "30.62ms","31.62ms","32.62ms","33.62ms","34.62ms","35.62ms","36.62ms","37.62ms","38.62ms","39.62ms", "40.62ms","41.62ms","42.62ms","43.62ms","44.62ms","45.62ms","46.62ms","47.62ms","48.62ms","49.62ms", "50.62ms","51.62ms","52.62ms","53.62ms","54.62ms","55.62ms","56.62ms","57.62ms","58.62ms","59.62ms", "60.62ms","61.62ms","62.62ms","63.62ms","64.62ms","65.62ms","66.62ms","67.62ms","68.62ms","69.62ms", "70.62ms","71.62ms","72.62ms","73.62ms","74.62ms","75.62ms","76.62ms","77.62ms","78.62ms","79.62ms", "80.62ms","81.62ms","82.62ms","83.62ms","84.62ms","85.62ms","86.62ms","87.62ms","88.62ms","89.62ms", "90.62ms","91.62ms","92.62ms","93.62ms","94.62ms","95.62ms","96.62ms","97.62ms","98.62ms","99.62ms", }, + new string[] { "0.63ms","1.63ms","2.63ms","3.63ms","4.63ms","5.63ms","6.63ms","7.63ms","8.63ms","9.63ms", "10.63ms","11.63ms","12.63ms","13.63ms","14.63ms","15.63ms","16.63ms","17.63ms","18.63ms","19.63ms", "20.63ms","21.63ms","22.63ms","23.63ms","24.63ms","25.63ms","26.63ms","27.63ms","28.63ms","29.63ms", "30.63ms","31.63ms","32.63ms","33.63ms","34.63ms","35.63ms","36.63ms","37.63ms","38.63ms","39.63ms", "40.63ms","41.63ms","42.63ms","43.63ms","44.63ms","45.63ms","46.63ms","47.63ms","48.63ms","49.63ms", "50.63ms","51.63ms","52.63ms","53.63ms","54.63ms","55.63ms","56.63ms","57.63ms","58.63ms","59.63ms", "60.63ms","61.63ms","62.63ms","63.63ms","64.63ms","65.63ms","66.63ms","67.63ms","68.63ms","69.63ms", "70.63ms","71.63ms","72.63ms","73.63ms","74.63ms","75.63ms","76.63ms","77.63ms","78.63ms","79.63ms", "80.63ms","81.63ms","82.63ms","83.63ms","84.63ms","85.63ms","86.63ms","87.63ms","88.63ms","89.63ms", "90.63ms","91.63ms","92.63ms","93.63ms","94.63ms","95.63ms","96.63ms","97.63ms","98.63ms","99.63ms", }, + new string[] { "0.64ms","1.64ms","2.64ms","3.64ms","4.64ms","5.64ms","6.64ms","7.64ms","8.64ms","9.64ms", "10.64ms","11.64ms","12.64ms","13.64ms","14.64ms","15.64ms","16.64ms","17.64ms","18.64ms","19.64ms", "20.64ms","21.64ms","22.64ms","23.64ms","24.64ms","25.64ms","26.64ms","27.64ms","28.64ms","29.64ms", "30.64ms","31.64ms","32.64ms","33.64ms","34.64ms","35.64ms","36.64ms","37.64ms","38.64ms","39.64ms", "40.64ms","41.64ms","42.64ms","43.64ms","44.64ms","45.64ms","46.64ms","47.64ms","48.64ms","49.64ms", "50.64ms","51.64ms","52.64ms","53.64ms","54.64ms","55.64ms","56.64ms","57.64ms","58.64ms","59.64ms", "60.64ms","61.64ms","62.64ms","63.64ms","64.64ms","65.64ms","66.64ms","67.64ms","68.64ms","69.64ms", "70.64ms","71.64ms","72.64ms","73.64ms","74.64ms","75.64ms","76.64ms","77.64ms","78.64ms","79.64ms", "80.64ms","81.64ms","82.64ms","83.64ms","84.64ms","85.64ms","86.64ms","87.64ms","88.64ms","89.64ms", "90.64ms","91.64ms","92.64ms","93.64ms","94.64ms","95.64ms","96.64ms","97.64ms","98.64ms","99.64ms", }, + new string[] { "0.65ms","1.65ms","2.65ms","3.65ms","4.65ms","5.65ms","6.65ms","7.65ms","8.65ms","9.65ms", "10.65ms","11.65ms","12.65ms","13.65ms","14.65ms","15.65ms","16.65ms","17.65ms","18.65ms","19.65ms", "20.65ms","21.65ms","22.65ms","23.65ms","24.65ms","25.65ms","26.65ms","27.65ms","28.65ms","29.65ms", "30.65ms","31.65ms","32.65ms","33.65ms","34.65ms","35.65ms","36.65ms","37.65ms","38.65ms","39.65ms", "40.65ms","41.65ms","42.65ms","43.65ms","44.65ms","45.65ms","46.65ms","47.65ms","48.65ms","49.65ms", "50.65ms","51.65ms","52.65ms","53.65ms","54.65ms","55.65ms","56.65ms","57.65ms","58.65ms","59.65ms", "60.65ms","61.65ms","62.65ms","63.65ms","64.65ms","65.65ms","66.65ms","67.65ms","68.65ms","69.65ms", "70.65ms","71.65ms","72.65ms","73.65ms","74.65ms","75.65ms","76.65ms","77.65ms","78.65ms","79.65ms", "80.65ms","81.65ms","82.65ms","83.65ms","84.65ms","85.65ms","86.65ms","87.65ms","88.65ms","89.65ms", "90.65ms","91.65ms","92.65ms","93.65ms","94.65ms","95.65ms","96.65ms","97.65ms","98.65ms","99.65ms", }, + new string[] { "0.66ms","1.66ms","2.66ms","3.66ms","4.66ms","5.66ms","6.66ms","7.66ms","8.66ms","9.66ms", "10.66ms","11.66ms","12.66ms","13.66ms","14.66ms","15.66ms","16.66ms","17.66ms","18.66ms","19.66ms", "20.66ms","21.66ms","22.66ms","23.66ms","24.66ms","25.66ms","26.66ms","27.66ms","28.66ms","29.66ms", "30.66ms","31.66ms","32.66ms","33.66ms","34.66ms","35.66ms","36.66ms","37.66ms","38.66ms","39.66ms", "40.66ms","41.66ms","42.66ms","43.66ms","44.66ms","45.66ms","46.66ms","47.66ms","48.66ms","49.66ms", "50.66ms","51.66ms","52.66ms","53.66ms","54.66ms","55.66ms","56.66ms","57.66ms","58.66ms","59.66ms", "60.66ms","61.66ms","62.66ms","63.66ms","64.66ms","65.66ms","66.66ms","67.66ms","68.66ms","69.66ms", "70.66ms","71.66ms","72.66ms","73.66ms","74.66ms","75.66ms","76.66ms","77.66ms","78.66ms","79.66ms", "80.66ms","81.66ms","82.66ms","83.66ms","84.66ms","85.66ms","86.66ms","87.66ms","88.66ms","89.66ms", "90.66ms","91.66ms","92.66ms","93.66ms","94.66ms","95.66ms","96.66ms","97.66ms","98.66ms","99.66ms", }, + new string[] { "0.67ms","1.67ms","2.67ms","3.67ms","4.67ms","5.67ms","6.67ms","7.67ms","8.67ms","9.67ms", "10.67ms","11.67ms","12.67ms","13.67ms","14.67ms","15.67ms","16.67ms","17.67ms","18.67ms","19.67ms", "20.67ms","21.67ms","22.67ms","23.67ms","24.67ms","25.67ms","26.67ms","27.67ms","28.67ms","29.67ms", "30.67ms","31.67ms","32.67ms","33.67ms","34.67ms","35.67ms","36.67ms","37.67ms","38.67ms","39.67ms", "40.67ms","41.67ms","42.67ms","43.67ms","44.67ms","45.67ms","46.67ms","47.67ms","48.67ms","49.67ms", "50.67ms","51.67ms","52.67ms","53.67ms","54.67ms","55.67ms","56.67ms","57.67ms","58.67ms","59.67ms", "60.67ms","61.67ms","62.67ms","63.67ms","64.67ms","65.67ms","66.67ms","67.67ms","68.67ms","69.67ms", "70.67ms","71.67ms","72.67ms","73.67ms","74.67ms","75.67ms","76.67ms","77.67ms","78.67ms","79.67ms", "80.67ms","81.67ms","82.67ms","83.67ms","84.67ms","85.67ms","86.67ms","87.67ms","88.67ms","89.67ms", "90.67ms","91.67ms","92.67ms","93.67ms","94.67ms","95.67ms","96.67ms","97.67ms","98.67ms","99.67ms", }, + new string[] { "0.68ms","1.68ms","2.68ms","3.68ms","4.68ms","5.68ms","6.68ms","7.68ms","8.68ms","9.68ms", "10.68ms","11.68ms","12.68ms","13.68ms","14.68ms","15.68ms","16.68ms","17.68ms","18.68ms","19.68ms", "20.68ms","21.68ms","22.68ms","23.68ms","24.68ms","25.68ms","26.68ms","27.68ms","28.68ms","29.68ms", "30.68ms","31.68ms","32.68ms","33.68ms","34.68ms","35.68ms","36.68ms","37.68ms","38.68ms","39.68ms", "40.68ms","41.68ms","42.68ms","43.68ms","44.68ms","45.68ms","46.68ms","47.68ms","48.68ms","49.68ms", "50.68ms","51.68ms","52.68ms","53.68ms","54.68ms","55.68ms","56.68ms","57.68ms","58.68ms","59.68ms", "60.68ms","61.68ms","62.68ms","63.68ms","64.68ms","65.68ms","66.68ms","67.68ms","68.68ms","69.68ms", "70.68ms","71.68ms","72.68ms","73.68ms","74.68ms","75.68ms","76.68ms","77.68ms","78.68ms","79.68ms", "80.68ms","81.68ms","82.68ms","83.68ms","84.68ms","85.68ms","86.68ms","87.68ms","88.68ms","89.68ms", "90.68ms","91.68ms","92.68ms","93.68ms","94.68ms","95.68ms","96.68ms","97.68ms","98.68ms","99.68ms", }, + new string[] { "0.69ms","1.69ms","2.69ms","3.69ms","4.69ms","5.69ms","6.69ms","7.69ms","8.69ms","9.69ms", "10.69ms","11.69ms","12.69ms","13.69ms","14.69ms","15.69ms","16.69ms","17.69ms","18.69ms","19.69ms", "20.69ms","21.69ms","22.69ms","23.69ms","24.69ms","25.69ms","26.69ms","27.69ms","28.69ms","29.69ms", "30.69ms","31.69ms","32.69ms","33.69ms","34.69ms","35.69ms","36.69ms","37.69ms","38.69ms","39.69ms", "40.69ms","41.69ms","42.69ms","43.69ms","44.69ms","45.69ms","46.69ms","47.69ms","48.69ms","49.69ms", "50.69ms","51.69ms","52.69ms","53.69ms","54.69ms","55.69ms","56.69ms","57.69ms","58.69ms","59.69ms", "60.69ms","61.69ms","62.69ms","63.69ms","64.69ms","65.69ms","66.69ms","67.69ms","68.69ms","69.69ms", "70.69ms","71.69ms","72.69ms","73.69ms","74.69ms","75.69ms","76.69ms","77.69ms","78.69ms","79.69ms", "80.69ms","81.69ms","82.69ms","83.69ms","84.69ms","85.69ms","86.69ms","87.69ms","88.69ms","89.69ms", "90.69ms","91.69ms","92.69ms","93.69ms","94.69ms","95.69ms","96.69ms","97.69ms","98.69ms","99.69ms", }, + new string[] { "0.70ms","1.70ms","2.70ms","3.70ms","4.70ms","5.70ms","6.70ms","7.70ms","8.70ms","9.70ms", "10.70ms","11.70ms","12.70ms","13.70ms","14.70ms","15.70ms","16.70ms","17.70ms","18.70ms","19.70ms", "20.70ms","21.70ms","22.70ms","23.70ms","24.70ms","25.70ms","26.70ms","27.70ms","28.70ms","29.70ms", "30.70ms","31.70ms","32.70ms","33.70ms","34.70ms","35.70ms","36.70ms","37.70ms","38.70ms","39.70ms", "40.70ms","41.70ms","42.70ms","43.70ms","44.70ms","45.70ms","46.70ms","47.70ms","48.70ms","49.70ms", "50.70ms","51.70ms","52.70ms","53.70ms","54.70ms","55.70ms","56.70ms","57.70ms","58.70ms","59.70ms", "60.70ms","61.70ms","62.70ms","63.70ms","64.70ms","65.70ms","66.70ms","67.70ms","68.70ms","69.70ms", "70.70ms","71.70ms","72.70ms","73.70ms","74.70ms","75.70ms","76.70ms","77.70ms","78.70ms","79.70ms", "80.70ms","81.70ms","82.70ms","83.70ms","84.70ms","85.70ms","86.70ms","87.70ms","88.70ms","89.70ms", "90.70ms","91.70ms","92.70ms","93.70ms","94.70ms","95.70ms","96.70ms","97.70ms","98.70ms","99.70ms", }, + new string[] { "0.71ms","1.71ms","2.71ms","3.71ms","4.71ms","5.71ms","6.71ms","7.71ms","8.71ms","9.71ms", "10.71ms","11.71ms","12.71ms","13.71ms","14.71ms","15.71ms","16.71ms","17.71ms","18.71ms","19.71ms", "20.71ms","21.71ms","22.71ms","23.71ms","24.71ms","25.71ms","26.71ms","27.71ms","28.71ms","29.71ms", "30.71ms","31.71ms","32.71ms","33.71ms","34.71ms","35.71ms","36.71ms","37.71ms","38.71ms","39.71ms", "40.71ms","41.71ms","42.71ms","43.71ms","44.71ms","45.71ms","46.71ms","47.71ms","48.71ms","49.71ms", "50.71ms","51.71ms","52.71ms","53.71ms","54.71ms","55.71ms","56.71ms","57.71ms","58.71ms","59.71ms", "60.71ms","61.71ms","62.71ms","63.71ms","64.71ms","65.71ms","66.71ms","67.71ms","68.71ms","69.71ms", "70.71ms","71.71ms","72.71ms","73.71ms","74.71ms","75.71ms","76.71ms","77.71ms","78.71ms","79.71ms", "80.71ms","81.71ms","82.71ms","83.71ms","84.71ms","85.71ms","86.71ms","87.71ms","88.71ms","89.71ms", "90.71ms","91.71ms","92.71ms","93.71ms","94.71ms","95.71ms","96.71ms","97.71ms","98.71ms","99.71ms", }, + new string[] { "0.72ms","1.72ms","2.72ms","3.72ms","4.72ms","5.72ms","6.72ms","7.72ms","8.72ms","9.72ms", "10.72ms","11.72ms","12.72ms","13.72ms","14.72ms","15.72ms","16.72ms","17.72ms","18.72ms","19.72ms", "20.72ms","21.72ms","22.72ms","23.72ms","24.72ms","25.72ms","26.72ms","27.72ms","28.72ms","29.72ms", "30.72ms","31.72ms","32.72ms","33.72ms","34.72ms","35.72ms","36.72ms","37.72ms","38.72ms","39.72ms", "40.72ms","41.72ms","42.72ms","43.72ms","44.72ms","45.72ms","46.72ms","47.72ms","48.72ms","49.72ms", "50.72ms","51.72ms","52.72ms","53.72ms","54.72ms","55.72ms","56.72ms","57.72ms","58.72ms","59.72ms", "60.72ms","61.72ms","62.72ms","63.72ms","64.72ms","65.72ms","66.72ms","67.72ms","68.72ms","69.72ms", "70.72ms","71.72ms","72.72ms","73.72ms","74.72ms","75.72ms","76.72ms","77.72ms","78.72ms","79.72ms", "80.72ms","81.72ms","82.72ms","83.72ms","84.72ms","85.72ms","86.72ms","87.72ms","88.72ms","89.72ms", "90.72ms","91.72ms","92.72ms","93.72ms","94.72ms","95.72ms","96.72ms","97.72ms","98.72ms","99.72ms", }, + new string[] { "0.73ms","1.73ms","2.73ms","3.73ms","4.73ms","5.73ms","6.73ms","7.73ms","8.73ms","9.73ms", "10.73ms","11.73ms","12.73ms","13.73ms","14.73ms","15.73ms","16.73ms","17.73ms","18.73ms","19.73ms", "20.73ms","21.73ms","22.73ms","23.73ms","24.73ms","25.73ms","26.73ms","27.73ms","28.73ms","29.73ms", "30.73ms","31.73ms","32.73ms","33.73ms","34.73ms","35.73ms","36.73ms","37.73ms","38.73ms","39.73ms", "40.73ms","41.73ms","42.73ms","43.73ms","44.73ms","45.73ms","46.73ms","47.73ms","48.73ms","49.73ms", "50.73ms","51.73ms","52.73ms","53.73ms","54.73ms","55.73ms","56.73ms","57.73ms","58.73ms","59.73ms", "60.73ms","61.73ms","62.73ms","63.73ms","64.73ms","65.73ms","66.73ms","67.73ms","68.73ms","69.73ms", "70.73ms","71.73ms","72.73ms","73.73ms","74.73ms","75.73ms","76.73ms","77.73ms","78.73ms","79.73ms", "80.73ms","81.73ms","82.73ms","83.73ms","84.73ms","85.73ms","86.73ms","87.73ms","88.73ms","89.73ms", "90.73ms","91.73ms","92.73ms","93.73ms","94.73ms","95.73ms","96.73ms","97.73ms","98.73ms","99.73ms", }, + new string[] { "0.74ms","1.74ms","2.74ms","3.74ms","4.74ms","5.74ms","6.74ms","7.74ms","8.74ms","9.74ms", "10.74ms","11.74ms","12.74ms","13.74ms","14.74ms","15.74ms","16.74ms","17.74ms","18.74ms","19.74ms", "20.74ms","21.74ms","22.74ms","23.74ms","24.74ms","25.74ms","26.74ms","27.74ms","28.74ms","29.74ms", "30.74ms","31.74ms","32.74ms","33.74ms","34.74ms","35.74ms","36.74ms","37.74ms","38.74ms","39.74ms", "40.74ms","41.74ms","42.74ms","43.74ms","44.74ms","45.74ms","46.74ms","47.74ms","48.74ms","49.74ms", "50.74ms","51.74ms","52.74ms","53.74ms","54.74ms","55.74ms","56.74ms","57.74ms","58.74ms","59.74ms", "60.74ms","61.74ms","62.74ms","63.74ms","64.74ms","65.74ms","66.74ms","67.74ms","68.74ms","69.74ms", "70.74ms","71.74ms","72.74ms","73.74ms","74.74ms","75.74ms","76.74ms","77.74ms","78.74ms","79.74ms", "80.74ms","81.74ms","82.74ms","83.74ms","84.74ms","85.74ms","86.74ms","87.74ms","88.74ms","89.74ms", "90.74ms","91.74ms","92.74ms","93.74ms","94.74ms","95.74ms","96.74ms","97.74ms","98.74ms","99.74ms", }, + new string[] { "0.75ms","1.75ms","2.75ms","3.75ms","4.75ms","5.75ms","6.75ms","7.75ms","8.75ms","9.75ms", "10.75ms","11.75ms","12.75ms","13.75ms","14.75ms","15.75ms","16.75ms","17.75ms","18.75ms","19.75ms", "20.75ms","21.75ms","22.75ms","23.75ms","24.75ms","25.75ms","26.75ms","27.75ms","28.75ms","29.75ms", "30.75ms","31.75ms","32.75ms","33.75ms","34.75ms","35.75ms","36.75ms","37.75ms","38.75ms","39.75ms", "40.75ms","41.75ms","42.75ms","43.75ms","44.75ms","45.75ms","46.75ms","47.75ms","48.75ms","49.75ms", "50.75ms","51.75ms","52.75ms","53.75ms","54.75ms","55.75ms","56.75ms","57.75ms","58.75ms","59.75ms", "60.75ms","61.75ms","62.75ms","63.75ms","64.75ms","65.75ms","66.75ms","67.75ms","68.75ms","69.75ms", "70.75ms","71.75ms","72.75ms","73.75ms","74.75ms","75.75ms","76.75ms","77.75ms","78.75ms","79.75ms", "80.75ms","81.75ms","82.75ms","83.75ms","84.75ms","85.75ms","86.75ms","87.75ms","88.75ms","89.75ms", "90.75ms","91.75ms","92.75ms","93.75ms","94.75ms","95.75ms","96.75ms","97.75ms","98.75ms","99.75ms", }, + new string[] { "0.76ms","1.76ms","2.76ms","3.76ms","4.76ms","5.76ms","6.76ms","7.76ms","8.76ms","9.76ms", "10.76ms","11.76ms","12.76ms","13.76ms","14.76ms","15.76ms","16.76ms","17.76ms","18.76ms","19.76ms", "20.76ms","21.76ms","22.76ms","23.76ms","24.76ms","25.76ms","26.76ms","27.76ms","28.76ms","29.76ms", "30.76ms","31.76ms","32.76ms","33.76ms","34.76ms","35.76ms","36.76ms","37.76ms","38.76ms","39.76ms", "40.76ms","41.76ms","42.76ms","43.76ms","44.76ms","45.76ms","46.76ms","47.76ms","48.76ms","49.76ms", "50.76ms","51.76ms","52.76ms","53.76ms","54.76ms","55.76ms","56.76ms","57.76ms","58.76ms","59.76ms", "60.76ms","61.76ms","62.76ms","63.76ms","64.76ms","65.76ms","66.76ms","67.76ms","68.76ms","69.76ms", "70.76ms","71.76ms","72.76ms","73.76ms","74.76ms","75.76ms","76.76ms","77.76ms","78.76ms","79.76ms", "80.76ms","81.76ms","82.76ms","83.76ms","84.76ms","85.76ms","86.76ms","87.76ms","88.76ms","89.76ms", "90.76ms","91.76ms","92.76ms","93.76ms","94.76ms","95.76ms","96.76ms","97.76ms","98.76ms","99.76ms", }, + new string[] { "0.77ms","1.77ms","2.77ms","3.77ms","4.77ms","5.77ms","6.77ms","7.77ms","8.77ms","9.77ms", "10.77ms","11.77ms","12.77ms","13.77ms","14.77ms","15.77ms","16.77ms","17.77ms","18.77ms","19.77ms", "20.77ms","21.77ms","22.77ms","23.77ms","24.77ms","25.77ms","26.77ms","27.77ms","28.77ms","29.77ms", "30.77ms","31.77ms","32.77ms","33.77ms","34.77ms","35.77ms","36.77ms","37.77ms","38.77ms","39.77ms", "40.77ms","41.77ms","42.77ms","43.77ms","44.77ms","45.77ms","46.77ms","47.77ms","48.77ms","49.77ms", "50.77ms","51.77ms","52.77ms","53.77ms","54.77ms","55.77ms","56.77ms","57.77ms","58.77ms","59.77ms", "60.77ms","61.77ms","62.77ms","63.77ms","64.77ms","65.77ms","66.77ms","67.77ms","68.77ms","69.77ms", "70.77ms","71.77ms","72.77ms","73.77ms","74.77ms","75.77ms","76.77ms","77.77ms","78.77ms","79.77ms", "80.77ms","81.77ms","82.77ms","83.77ms","84.77ms","85.77ms","86.77ms","87.77ms","88.77ms","89.77ms", "90.77ms","91.77ms","92.77ms","93.77ms","94.77ms","95.77ms","96.77ms","97.77ms","98.77ms","99.77ms", }, + new string[] { "0.78ms","1.78ms","2.78ms","3.78ms","4.78ms","5.78ms","6.78ms","7.78ms","8.78ms","9.78ms", "10.78ms","11.78ms","12.78ms","13.78ms","14.78ms","15.78ms","16.78ms","17.78ms","18.78ms","19.78ms", "20.78ms","21.78ms","22.78ms","23.78ms","24.78ms","25.78ms","26.78ms","27.78ms","28.78ms","29.78ms", "30.78ms","31.78ms","32.78ms","33.78ms","34.78ms","35.78ms","36.78ms","37.78ms","38.78ms","39.78ms", "40.78ms","41.78ms","42.78ms","43.78ms","44.78ms","45.78ms","46.78ms","47.78ms","48.78ms","49.78ms", "50.78ms","51.78ms","52.78ms","53.78ms","54.78ms","55.78ms","56.78ms","57.78ms","58.78ms","59.78ms", "60.78ms","61.78ms","62.78ms","63.78ms","64.78ms","65.78ms","66.78ms","67.78ms","68.78ms","69.78ms", "70.78ms","71.78ms","72.78ms","73.78ms","74.78ms","75.78ms","76.78ms","77.78ms","78.78ms","79.78ms", "80.78ms","81.78ms","82.78ms","83.78ms","84.78ms","85.78ms","86.78ms","87.78ms","88.78ms","89.78ms", "90.78ms","91.78ms","92.78ms","93.78ms","94.78ms","95.78ms","96.78ms","97.78ms","98.78ms","99.78ms", }, + new string[] { "0.79ms","1.79ms","2.79ms","3.79ms","4.79ms","5.79ms","6.79ms","7.79ms","8.79ms","9.79ms", "10.79ms","11.79ms","12.79ms","13.79ms","14.79ms","15.79ms","16.79ms","17.79ms","18.79ms","19.79ms", "20.79ms","21.79ms","22.79ms","23.79ms","24.79ms","25.79ms","26.79ms","27.79ms","28.79ms","29.79ms", "30.79ms","31.79ms","32.79ms","33.79ms","34.79ms","35.79ms","36.79ms","37.79ms","38.79ms","39.79ms", "40.79ms","41.79ms","42.79ms","43.79ms","44.79ms","45.79ms","46.79ms","47.79ms","48.79ms","49.79ms", "50.79ms","51.79ms","52.79ms","53.79ms","54.79ms","55.79ms","56.79ms","57.79ms","58.79ms","59.79ms", "60.79ms","61.79ms","62.79ms","63.79ms","64.79ms","65.79ms","66.79ms","67.79ms","68.79ms","69.79ms", "70.79ms","71.79ms","72.79ms","73.79ms","74.79ms","75.79ms","76.79ms","77.79ms","78.79ms","79.79ms", "80.79ms","81.79ms","82.79ms","83.79ms","84.79ms","85.79ms","86.79ms","87.79ms","88.79ms","89.79ms", "90.79ms","91.79ms","92.79ms","93.79ms","94.79ms","95.79ms","96.79ms","97.79ms","98.79ms","99.79ms", }, + new string[] { "0.80ms","1.80ms","2.80ms","3.80ms","4.80ms","5.80ms","6.80ms","7.80ms","8.80ms","9.80ms", "10.80ms","11.80ms","12.80ms","13.80ms","14.80ms","15.80ms","16.80ms","17.80ms","18.80ms","19.80ms", "20.80ms","21.80ms","22.80ms","23.80ms","24.80ms","25.80ms","26.80ms","27.80ms","28.80ms","29.80ms", "30.80ms","31.80ms","32.80ms","33.80ms","34.80ms","35.80ms","36.80ms","37.80ms","38.80ms","39.80ms", "40.80ms","41.80ms","42.80ms","43.80ms","44.80ms","45.80ms","46.80ms","47.80ms","48.80ms","49.80ms", "50.80ms","51.80ms","52.80ms","53.80ms","54.80ms","55.80ms","56.80ms","57.80ms","58.80ms","59.80ms", "60.80ms","61.80ms","62.80ms","63.80ms","64.80ms","65.80ms","66.80ms","67.80ms","68.80ms","69.80ms", "70.80ms","71.80ms","72.80ms","73.80ms","74.80ms","75.80ms","76.80ms","77.80ms","78.80ms","79.80ms", "80.80ms","81.80ms","82.80ms","83.80ms","84.80ms","85.80ms","86.80ms","87.80ms","88.80ms","89.80ms", "90.80ms","91.80ms","92.80ms","93.80ms","94.80ms","95.80ms","96.80ms","97.80ms","98.80ms","99.80ms", }, + new string[] { "0.81ms","1.81ms","2.81ms","3.81ms","4.81ms","5.81ms","6.81ms","7.81ms","8.81ms","9.81ms", "10.81ms","11.81ms","12.81ms","13.81ms","14.81ms","15.81ms","16.81ms","17.81ms","18.81ms","19.81ms", "20.81ms","21.81ms","22.81ms","23.81ms","24.81ms","25.81ms","26.81ms","27.81ms","28.81ms","29.81ms", "30.81ms","31.81ms","32.81ms","33.81ms","34.81ms","35.81ms","36.81ms","37.81ms","38.81ms","39.81ms", "40.81ms","41.81ms","42.81ms","43.81ms","44.81ms","45.81ms","46.81ms","47.81ms","48.81ms","49.81ms", "50.81ms","51.81ms","52.81ms","53.81ms","54.81ms","55.81ms","56.81ms","57.81ms","58.81ms","59.81ms", "60.81ms","61.81ms","62.81ms","63.81ms","64.81ms","65.81ms","66.81ms","67.81ms","68.81ms","69.81ms", "70.81ms","71.81ms","72.81ms","73.81ms","74.81ms","75.81ms","76.81ms","77.81ms","78.81ms","79.81ms", "80.81ms","81.81ms","82.81ms","83.81ms","84.81ms","85.81ms","86.81ms","87.81ms","88.81ms","89.81ms", "90.81ms","91.81ms","92.81ms","93.81ms","94.81ms","95.81ms","96.81ms","97.81ms","98.81ms","99.81ms", }, + new string[] { "0.82ms","1.82ms","2.82ms","3.82ms","4.82ms","5.82ms","6.82ms","7.82ms","8.82ms","9.82ms", "10.82ms","11.82ms","12.82ms","13.82ms","14.82ms","15.82ms","16.82ms","17.82ms","18.82ms","19.82ms", "20.82ms","21.82ms","22.82ms","23.82ms","24.82ms","25.82ms","26.82ms","27.82ms","28.82ms","29.82ms", "30.82ms","31.82ms","32.82ms","33.82ms","34.82ms","35.82ms","36.82ms","37.82ms","38.82ms","39.82ms", "40.82ms","41.82ms","42.82ms","43.82ms","44.82ms","45.82ms","46.82ms","47.82ms","48.82ms","49.82ms", "50.82ms","51.82ms","52.82ms","53.82ms","54.82ms","55.82ms","56.82ms","57.82ms","58.82ms","59.82ms", "60.82ms","61.82ms","62.82ms","63.82ms","64.82ms","65.82ms","66.82ms","67.82ms","68.82ms","69.82ms", "70.82ms","71.82ms","72.82ms","73.82ms","74.82ms","75.82ms","76.82ms","77.82ms","78.82ms","79.82ms", "80.82ms","81.82ms","82.82ms","83.82ms","84.82ms","85.82ms","86.82ms","87.82ms","88.82ms","89.82ms", "90.82ms","91.82ms","92.82ms","93.82ms","94.82ms","95.82ms","96.82ms","97.82ms","98.82ms","99.82ms", }, + new string[] { "0.83ms","1.83ms","2.83ms","3.83ms","4.83ms","5.83ms","6.83ms","7.83ms","8.83ms","9.83ms", "10.83ms","11.83ms","12.83ms","13.83ms","14.83ms","15.83ms","16.83ms","17.83ms","18.83ms","19.83ms", "20.83ms","21.83ms","22.83ms","23.83ms","24.83ms","25.83ms","26.83ms","27.83ms","28.83ms","29.83ms", "30.83ms","31.83ms","32.83ms","33.83ms","34.83ms","35.83ms","36.83ms","37.83ms","38.83ms","39.83ms", "40.83ms","41.83ms","42.83ms","43.83ms","44.83ms","45.83ms","46.83ms","47.83ms","48.83ms","49.83ms", "50.83ms","51.83ms","52.83ms","53.83ms","54.83ms","55.83ms","56.83ms","57.83ms","58.83ms","59.83ms", "60.83ms","61.83ms","62.83ms","63.83ms","64.83ms","65.83ms","66.83ms","67.83ms","68.83ms","69.83ms", "70.83ms","71.83ms","72.83ms","73.83ms","74.83ms","75.83ms","76.83ms","77.83ms","78.83ms","79.83ms", "80.83ms","81.83ms","82.83ms","83.83ms","84.83ms","85.83ms","86.83ms","87.83ms","88.83ms","89.83ms", "90.83ms","91.83ms","92.83ms","93.83ms","94.83ms","95.83ms","96.83ms","97.83ms","98.83ms","99.83ms", }, + new string[] { "0.84ms","1.84ms","2.84ms","3.84ms","4.84ms","5.84ms","6.84ms","7.84ms","8.84ms","9.84ms", "10.84ms","11.84ms","12.84ms","13.84ms","14.84ms","15.84ms","16.84ms","17.84ms","18.84ms","19.84ms", "20.84ms","21.84ms","22.84ms","23.84ms","24.84ms","25.84ms","26.84ms","27.84ms","28.84ms","29.84ms", "30.84ms","31.84ms","32.84ms","33.84ms","34.84ms","35.84ms","36.84ms","37.84ms","38.84ms","39.84ms", "40.84ms","41.84ms","42.84ms","43.84ms","44.84ms","45.84ms","46.84ms","47.84ms","48.84ms","49.84ms", "50.84ms","51.84ms","52.84ms","53.84ms","54.84ms","55.84ms","56.84ms","57.84ms","58.84ms","59.84ms", "60.84ms","61.84ms","62.84ms","63.84ms","64.84ms","65.84ms","66.84ms","67.84ms","68.84ms","69.84ms", "70.84ms","71.84ms","72.84ms","73.84ms","74.84ms","75.84ms","76.84ms","77.84ms","78.84ms","79.84ms", "80.84ms","81.84ms","82.84ms","83.84ms","84.84ms","85.84ms","86.84ms","87.84ms","88.84ms","89.84ms", "90.84ms","91.84ms","92.84ms","93.84ms","94.84ms","95.84ms","96.84ms","97.84ms","98.84ms","99.84ms", }, + new string[] { "0.85ms","1.85ms","2.85ms","3.85ms","4.85ms","5.85ms","6.85ms","7.85ms","8.85ms","9.85ms", "10.85ms","11.85ms","12.85ms","13.85ms","14.85ms","15.85ms","16.85ms","17.85ms","18.85ms","19.85ms", "20.85ms","21.85ms","22.85ms","23.85ms","24.85ms","25.85ms","26.85ms","27.85ms","28.85ms","29.85ms", "30.85ms","31.85ms","32.85ms","33.85ms","34.85ms","35.85ms","36.85ms","37.85ms","38.85ms","39.85ms", "40.85ms","41.85ms","42.85ms","43.85ms","44.85ms","45.85ms","46.85ms","47.85ms","48.85ms","49.85ms", "50.85ms","51.85ms","52.85ms","53.85ms","54.85ms","55.85ms","56.85ms","57.85ms","58.85ms","59.85ms", "60.85ms","61.85ms","62.85ms","63.85ms","64.85ms","65.85ms","66.85ms","67.85ms","68.85ms","69.85ms", "70.85ms","71.85ms","72.85ms","73.85ms","74.85ms","75.85ms","76.85ms","77.85ms","78.85ms","79.85ms", "80.85ms","81.85ms","82.85ms","83.85ms","84.85ms","85.85ms","86.85ms","87.85ms","88.85ms","89.85ms", "90.85ms","91.85ms","92.85ms","93.85ms","94.85ms","95.85ms","96.85ms","97.85ms","98.85ms","99.85ms", }, + new string[] { "0.86ms","1.86ms","2.86ms","3.86ms","4.86ms","5.86ms","6.86ms","7.86ms","8.86ms","9.86ms", "10.86ms","11.86ms","12.86ms","13.86ms","14.86ms","15.86ms","16.86ms","17.86ms","18.86ms","19.86ms", "20.86ms","21.86ms","22.86ms","23.86ms","24.86ms","25.86ms","26.86ms","27.86ms","28.86ms","29.86ms", "30.86ms","31.86ms","32.86ms","33.86ms","34.86ms","35.86ms","36.86ms","37.86ms","38.86ms","39.86ms", "40.86ms","41.86ms","42.86ms","43.86ms","44.86ms","45.86ms","46.86ms","47.86ms","48.86ms","49.86ms", "50.86ms","51.86ms","52.86ms","53.86ms","54.86ms","55.86ms","56.86ms","57.86ms","58.86ms","59.86ms", "60.86ms","61.86ms","62.86ms","63.86ms","64.86ms","65.86ms","66.86ms","67.86ms","68.86ms","69.86ms", "70.86ms","71.86ms","72.86ms","73.86ms","74.86ms","75.86ms","76.86ms","77.86ms","78.86ms","79.86ms", "80.86ms","81.86ms","82.86ms","83.86ms","84.86ms","85.86ms","86.86ms","87.86ms","88.86ms","89.86ms", "90.86ms","91.86ms","92.86ms","93.86ms","94.86ms","95.86ms","96.86ms","97.86ms","98.86ms","99.86ms", }, + new string[] { "0.87ms","1.87ms","2.87ms","3.87ms","4.87ms","5.87ms","6.87ms","7.87ms","8.87ms","9.87ms", "10.87ms","11.87ms","12.87ms","13.87ms","14.87ms","15.87ms","16.87ms","17.87ms","18.87ms","19.87ms", "20.87ms","21.87ms","22.87ms","23.87ms","24.87ms","25.87ms","26.87ms","27.87ms","28.87ms","29.87ms", "30.87ms","31.87ms","32.87ms","33.87ms","34.87ms","35.87ms","36.87ms","37.87ms","38.87ms","39.87ms", "40.87ms","41.87ms","42.87ms","43.87ms","44.87ms","45.87ms","46.87ms","47.87ms","48.87ms","49.87ms", "50.87ms","51.87ms","52.87ms","53.87ms","54.87ms","55.87ms","56.87ms","57.87ms","58.87ms","59.87ms", "60.87ms","61.87ms","62.87ms","63.87ms","64.87ms","65.87ms","66.87ms","67.87ms","68.87ms","69.87ms", "70.87ms","71.87ms","72.87ms","73.87ms","74.87ms","75.87ms","76.87ms","77.87ms","78.87ms","79.87ms", "80.87ms","81.87ms","82.87ms","83.87ms","84.87ms","85.87ms","86.87ms","87.87ms","88.87ms","89.87ms", "90.87ms","91.87ms","92.87ms","93.87ms","94.87ms","95.87ms","96.87ms","97.87ms","98.87ms","99.87ms", }, + new string[] { "0.88ms","1.88ms","2.88ms","3.88ms","4.88ms","5.88ms","6.88ms","7.88ms","8.88ms","9.88ms", "10.88ms","11.88ms","12.88ms","13.88ms","14.88ms","15.88ms","16.88ms","17.88ms","18.88ms","19.88ms", "20.88ms","21.88ms","22.88ms","23.88ms","24.88ms","25.88ms","26.88ms","27.88ms","28.88ms","29.88ms", "30.88ms","31.88ms","32.88ms","33.88ms","34.88ms","35.88ms","36.88ms","37.88ms","38.88ms","39.88ms", "40.88ms","41.88ms","42.88ms","43.88ms","44.88ms","45.88ms","46.88ms","47.88ms","48.88ms","49.88ms", "50.88ms","51.88ms","52.88ms","53.88ms","54.88ms","55.88ms","56.88ms","57.88ms","58.88ms","59.88ms", "60.88ms","61.88ms","62.88ms","63.88ms","64.88ms","65.88ms","66.88ms","67.88ms","68.88ms","69.88ms", "70.88ms","71.88ms","72.88ms","73.88ms","74.88ms","75.88ms","76.88ms","77.88ms","78.88ms","79.88ms", "80.88ms","81.88ms","82.88ms","83.88ms","84.88ms","85.88ms","86.88ms","87.88ms","88.88ms","89.88ms", "90.88ms","91.88ms","92.88ms","93.88ms","94.88ms","95.88ms","96.88ms","97.88ms","98.88ms","99.88ms", }, + new string[] { "0.89ms","1.89ms","2.89ms","3.89ms","4.89ms","5.89ms","6.89ms","7.89ms","8.89ms","9.89ms", "10.89ms","11.89ms","12.89ms","13.89ms","14.89ms","15.89ms","16.89ms","17.89ms","18.89ms","19.89ms", "20.89ms","21.89ms","22.89ms","23.89ms","24.89ms","25.89ms","26.89ms","27.89ms","28.89ms","29.89ms", "30.89ms","31.89ms","32.89ms","33.89ms","34.89ms","35.89ms","36.89ms","37.89ms","38.89ms","39.89ms", "40.89ms","41.89ms","42.89ms","43.89ms","44.89ms","45.89ms","46.89ms","47.89ms","48.89ms","49.89ms", "50.89ms","51.89ms","52.89ms","53.89ms","54.89ms","55.89ms","56.89ms","57.89ms","58.89ms","59.89ms", "60.89ms","61.89ms","62.89ms","63.89ms","64.89ms","65.89ms","66.89ms","67.89ms","68.89ms","69.89ms", "70.89ms","71.89ms","72.89ms","73.89ms","74.89ms","75.89ms","76.89ms","77.89ms","78.89ms","79.89ms", "80.89ms","81.89ms","82.89ms","83.89ms","84.89ms","85.89ms","86.89ms","87.89ms","88.89ms","89.89ms", "90.89ms","91.89ms","92.89ms","93.89ms","94.89ms","95.89ms","96.89ms","97.89ms","98.89ms","99.89ms", }, + new string[] { "0.90ms","1.90ms","2.90ms","3.90ms","4.90ms","5.90ms","6.90ms","7.90ms","8.90ms","9.90ms", "10.90ms","11.90ms","12.90ms","13.90ms","14.90ms","15.90ms","16.90ms","17.90ms","18.90ms","19.90ms", "20.90ms","21.90ms","22.90ms","23.90ms","24.90ms","25.90ms","26.90ms","27.90ms","28.90ms","29.90ms", "30.90ms","31.90ms","32.90ms","33.90ms","34.90ms","35.90ms","36.90ms","37.90ms","38.90ms","39.90ms", "40.90ms","41.90ms","42.90ms","43.90ms","44.90ms","45.90ms","46.90ms","47.90ms","48.90ms","49.90ms", "50.90ms","51.90ms","52.90ms","53.90ms","54.90ms","55.90ms","56.90ms","57.90ms","58.90ms","59.90ms", "60.90ms","61.90ms","62.90ms","63.90ms","64.90ms","65.90ms","66.90ms","67.90ms","68.90ms","69.90ms", "70.90ms","71.90ms","72.90ms","73.90ms","74.90ms","75.90ms","76.90ms","77.90ms","78.90ms","79.90ms", "80.90ms","81.90ms","82.90ms","83.90ms","84.90ms","85.90ms","86.90ms","87.90ms","88.90ms","89.90ms", "90.90ms","91.90ms","92.90ms","93.90ms","94.90ms","95.90ms","96.90ms","97.90ms","98.90ms","99.90ms", }, + new string[] { "0.91ms","1.91ms","2.91ms","3.91ms","4.91ms","5.91ms","6.91ms","7.91ms","8.91ms","9.91ms", "10.91ms","11.91ms","12.91ms","13.91ms","14.91ms","15.91ms","16.91ms","17.91ms","18.91ms","19.91ms", "20.91ms","21.91ms","22.91ms","23.91ms","24.91ms","25.91ms","26.91ms","27.91ms","28.91ms","29.91ms", "30.91ms","31.91ms","32.91ms","33.91ms","34.91ms","35.91ms","36.91ms","37.91ms","38.91ms","39.91ms", "40.91ms","41.91ms","42.91ms","43.91ms","44.91ms","45.91ms","46.91ms","47.91ms","48.91ms","49.91ms", "50.91ms","51.91ms","52.91ms","53.91ms","54.91ms","55.91ms","56.91ms","57.91ms","58.91ms","59.91ms", "60.91ms","61.91ms","62.91ms","63.91ms","64.91ms","65.91ms","66.91ms","67.91ms","68.91ms","69.91ms", "70.91ms","71.91ms","72.91ms","73.91ms","74.91ms","75.91ms","76.91ms","77.91ms","78.91ms","79.91ms", "80.91ms","81.91ms","82.91ms","83.91ms","84.91ms","85.91ms","86.91ms","87.91ms","88.91ms","89.91ms", "90.91ms","91.91ms","92.91ms","93.91ms","94.91ms","95.91ms","96.91ms","97.91ms","98.91ms","99.91ms", }, + new string[] { "0.92ms","1.92ms","2.92ms","3.92ms","4.92ms","5.92ms","6.92ms","7.92ms","8.92ms","9.92ms", "10.92ms","11.92ms","12.92ms","13.92ms","14.92ms","15.92ms","16.92ms","17.92ms","18.92ms","19.92ms", "20.92ms","21.92ms","22.92ms","23.92ms","24.92ms","25.92ms","26.92ms","27.92ms","28.92ms","29.92ms", "30.92ms","31.92ms","32.92ms","33.92ms","34.92ms","35.92ms","36.92ms","37.92ms","38.92ms","39.92ms", "40.92ms","41.92ms","42.92ms","43.92ms","44.92ms","45.92ms","46.92ms","47.92ms","48.92ms","49.92ms", "50.92ms","51.92ms","52.92ms","53.92ms","54.92ms","55.92ms","56.92ms","57.92ms","58.92ms","59.92ms", "60.92ms","61.92ms","62.92ms","63.92ms","64.92ms","65.92ms","66.92ms","67.92ms","68.92ms","69.92ms", "70.92ms","71.92ms","72.92ms","73.92ms","74.92ms","75.92ms","76.92ms","77.92ms","78.92ms","79.92ms", "80.92ms","81.92ms","82.92ms","83.92ms","84.92ms","85.92ms","86.92ms","87.92ms","88.92ms","89.92ms", "90.92ms","91.92ms","92.92ms","93.92ms","94.92ms","95.92ms","96.92ms","97.92ms","98.92ms","99.92ms", }, + new string[] { "0.93ms","1.93ms","2.93ms","3.93ms","4.93ms","5.93ms","6.93ms","7.93ms","8.93ms","9.93ms", "10.93ms","11.93ms","12.93ms","13.93ms","14.93ms","15.93ms","16.93ms","17.93ms","18.93ms","19.93ms", "20.93ms","21.93ms","22.93ms","23.93ms","24.93ms","25.93ms","26.93ms","27.93ms","28.93ms","29.93ms", "30.93ms","31.93ms","32.93ms","33.93ms","34.93ms","35.93ms","36.93ms","37.93ms","38.93ms","39.93ms", "40.93ms","41.93ms","42.93ms","43.93ms","44.93ms","45.93ms","46.93ms","47.93ms","48.93ms","49.93ms", "50.93ms","51.93ms","52.93ms","53.93ms","54.93ms","55.93ms","56.93ms","57.93ms","58.93ms","59.93ms", "60.93ms","61.93ms","62.93ms","63.93ms","64.93ms","65.93ms","66.93ms","67.93ms","68.93ms","69.93ms", "70.93ms","71.93ms","72.93ms","73.93ms","74.93ms","75.93ms","76.93ms","77.93ms","78.93ms","79.93ms", "80.93ms","81.93ms","82.93ms","83.93ms","84.93ms","85.93ms","86.93ms","87.93ms","88.93ms","89.93ms", "90.93ms","91.93ms","92.93ms","93.93ms","94.93ms","95.93ms","96.93ms","97.93ms","98.93ms","99.93ms", }, + new string[] { "0.94ms","1.94ms","2.94ms","3.94ms","4.94ms","5.94ms","6.94ms","7.94ms","8.94ms","9.94ms", "10.94ms","11.94ms","12.94ms","13.94ms","14.94ms","15.94ms","16.94ms","17.94ms","18.94ms","19.94ms", "20.94ms","21.94ms","22.94ms","23.94ms","24.94ms","25.94ms","26.94ms","27.94ms","28.94ms","29.94ms", "30.94ms","31.94ms","32.94ms","33.94ms","34.94ms","35.94ms","36.94ms","37.94ms","38.94ms","39.94ms", "40.94ms","41.94ms","42.94ms","43.94ms","44.94ms","45.94ms","46.94ms","47.94ms","48.94ms","49.94ms", "50.94ms","51.94ms","52.94ms","53.94ms","54.94ms","55.94ms","56.94ms","57.94ms","58.94ms","59.94ms", "60.94ms","61.94ms","62.94ms","63.94ms","64.94ms","65.94ms","66.94ms","67.94ms","68.94ms","69.94ms", "70.94ms","71.94ms","72.94ms","73.94ms","74.94ms","75.94ms","76.94ms","77.94ms","78.94ms","79.94ms", "80.94ms","81.94ms","82.94ms","83.94ms","84.94ms","85.94ms","86.94ms","87.94ms","88.94ms","89.94ms", "90.94ms","91.94ms","92.94ms","93.94ms","94.94ms","95.94ms","96.94ms","97.94ms","98.94ms","99.94ms", }, + new string[] { "0.95ms","1.95ms","2.95ms","3.95ms","4.95ms","5.95ms","6.95ms","7.95ms","8.95ms","9.95ms", "10.95ms","11.95ms","12.95ms","13.95ms","14.95ms","15.95ms","16.95ms","17.95ms","18.95ms","19.95ms", "20.95ms","21.95ms","22.95ms","23.95ms","24.95ms","25.95ms","26.95ms","27.95ms","28.95ms","29.95ms", "30.95ms","31.95ms","32.95ms","33.95ms","34.95ms","35.95ms","36.95ms","37.95ms","38.95ms","39.95ms", "40.95ms","41.95ms","42.95ms","43.95ms","44.95ms","45.95ms","46.95ms","47.95ms","48.95ms","49.95ms", "50.95ms","51.95ms","52.95ms","53.95ms","54.95ms","55.95ms","56.95ms","57.95ms","58.95ms","59.95ms", "60.95ms","61.95ms","62.95ms","63.95ms","64.95ms","65.95ms","66.95ms","67.95ms","68.95ms","69.95ms", "70.95ms","71.95ms","72.95ms","73.95ms","74.95ms","75.95ms","76.95ms","77.95ms","78.95ms","79.95ms", "80.95ms","81.95ms","82.95ms","83.95ms","84.95ms","85.95ms","86.95ms","87.95ms","88.95ms","89.95ms", "90.95ms","91.95ms","92.95ms","93.95ms","94.95ms","95.95ms","96.95ms","97.95ms","98.95ms","99.95ms", }, + new string[] { "0.96ms","1.96ms","2.96ms","3.96ms","4.96ms","5.96ms","6.96ms","7.96ms","8.96ms","9.96ms", "10.96ms","11.96ms","12.96ms","13.96ms","14.96ms","15.96ms","16.96ms","17.96ms","18.96ms","19.96ms", "20.96ms","21.96ms","22.96ms","23.96ms","24.96ms","25.96ms","26.96ms","27.96ms","28.96ms","29.96ms", "30.96ms","31.96ms","32.96ms","33.96ms","34.96ms","35.96ms","36.96ms","37.96ms","38.96ms","39.96ms", "40.96ms","41.96ms","42.96ms","43.96ms","44.96ms","45.96ms","46.96ms","47.96ms","48.96ms","49.96ms", "50.96ms","51.96ms","52.96ms","53.96ms","54.96ms","55.96ms","56.96ms","57.96ms","58.96ms","59.96ms", "60.96ms","61.96ms","62.96ms","63.96ms","64.96ms","65.96ms","66.96ms","67.96ms","68.96ms","69.96ms", "70.96ms","71.96ms","72.96ms","73.96ms","74.96ms","75.96ms","76.96ms","77.96ms","78.96ms","79.96ms", "80.96ms","81.96ms","82.96ms","83.96ms","84.96ms","85.96ms","86.96ms","87.96ms","88.96ms","89.96ms", "90.96ms","91.96ms","92.96ms","93.96ms","94.96ms","95.96ms","96.96ms","97.96ms","98.96ms","99.96ms", }, + new string[] { "0.97ms","1.97ms","2.97ms","3.97ms","4.97ms","5.97ms","6.97ms","7.97ms","8.97ms","9.97ms", "10.97ms","11.97ms","12.97ms","13.97ms","14.97ms","15.97ms","16.97ms","17.97ms","18.97ms","19.97ms", "20.97ms","21.97ms","22.97ms","23.97ms","24.97ms","25.97ms","26.97ms","27.97ms","28.97ms","29.97ms", "30.97ms","31.97ms","32.97ms","33.97ms","34.97ms","35.97ms","36.97ms","37.97ms","38.97ms","39.97ms", "40.97ms","41.97ms","42.97ms","43.97ms","44.97ms","45.97ms","46.97ms","47.97ms","48.97ms","49.97ms", "50.97ms","51.97ms","52.97ms","53.97ms","54.97ms","55.97ms","56.97ms","57.97ms","58.97ms","59.97ms", "60.97ms","61.97ms","62.97ms","63.97ms","64.97ms","65.97ms","66.97ms","67.97ms","68.97ms","69.97ms", "70.97ms","71.97ms","72.97ms","73.97ms","74.97ms","75.97ms","76.97ms","77.97ms","78.97ms","79.97ms", "80.97ms","81.97ms","82.97ms","83.97ms","84.97ms","85.97ms","86.97ms","87.97ms","88.97ms","89.97ms", "90.97ms","91.97ms","92.97ms","93.97ms","94.97ms","95.97ms","96.97ms","97.97ms","98.97ms","99.97ms", }, + new string[] { "0.98ms","1.98ms","2.98ms","3.98ms","4.98ms","5.98ms","6.98ms","7.98ms","8.98ms","9.98ms", "10.98ms","11.98ms","12.98ms","13.98ms","14.98ms","15.98ms","16.98ms","17.98ms","18.98ms","19.98ms", "20.98ms","21.98ms","22.98ms","23.98ms","24.98ms","25.98ms","26.98ms","27.98ms","28.98ms","29.98ms", "30.98ms","31.98ms","32.98ms","33.98ms","34.98ms","35.98ms","36.98ms","37.98ms","38.98ms","39.98ms", "40.98ms","41.98ms","42.98ms","43.98ms","44.98ms","45.98ms","46.98ms","47.98ms","48.98ms","49.98ms", "50.98ms","51.98ms","52.98ms","53.98ms","54.98ms","55.98ms","56.98ms","57.98ms","58.98ms","59.98ms", "60.98ms","61.98ms","62.98ms","63.98ms","64.98ms","65.98ms","66.98ms","67.98ms","68.98ms","69.98ms", "70.98ms","71.98ms","72.98ms","73.98ms","74.98ms","75.98ms","76.98ms","77.98ms","78.98ms","79.98ms", "80.98ms","81.98ms","82.98ms","83.98ms","84.98ms","85.98ms","86.98ms","87.98ms","88.98ms","89.98ms", "90.98ms","91.98ms","92.98ms","93.98ms","94.98ms","95.98ms","96.98ms","97.98ms","98.98ms","99.98ms", }, + new string[] { "0.99ms","1.99ms","2.99ms","3.99ms","4.99ms","5.99ms","6.99ms","7.99ms","8.99ms","9.99ms", "10.99ms","11.99ms","12.99ms","13.99ms","14.99ms","15.99ms","16.99ms","17.99ms","18.99ms","19.99ms", "20.99ms","21.99ms","22.99ms","23.99ms","24.99ms","25.99ms","26.99ms","27.99ms","28.99ms","29.99ms", "30.99ms","31.99ms","32.99ms","33.99ms","34.99ms","35.99ms","36.99ms","37.99ms","38.99ms","39.99ms", "40.99ms","41.99ms","42.99ms","43.99ms","44.99ms","45.99ms","46.99ms","47.99ms","48.99ms","49.99ms", "50.99ms","51.99ms","52.99ms","53.99ms","54.99ms","55.99ms","56.99ms","57.99ms","58.99ms","59.99ms", "60.99ms","61.99ms","62.99ms","63.99ms","64.99ms","65.99ms","66.99ms","67.99ms","68.99ms","69.99ms", "70.99ms","71.99ms","72.99ms","73.99ms","74.99ms","75.99ms","76.99ms","77.99ms","78.99ms","79.99ms", "80.99ms","81.99ms","82.99ms","83.99ms","84.99ms","85.99ms","86.99ms","87.99ms","88.99ms","89.99ms", "90.99ms","91.99ms","92.99ms","93.99ms","94.99ms","95.99ms","96.99ms","97.99ms","98.99ms","99.99ms", }, + }; + } +} + +#endregion + + +#region Assets/Photon/Fusion/Runtime/Utilities/FusionScalableIMGUI.cs + +namespace Fusion { + using System.Reflection; + using UnityEngine; + + /// + /// In-Game IMGUI style used for the interface. + /// + public static class FusionScalableIMGUI { + private static GUISkin _scalableSkin; + + private static void InitializedGUIStyles(GUISkin baseSkin) { + _scalableSkin = baseSkin == null ? GUI.skin : baseSkin; + + // If no skin was provided, make the built in GuiSkin more tolerable. + if (baseSkin == null) { + _scalableSkin = GUI.skin; + _scalableSkin.button.alignment = TextAnchor.MiddleCenter; + _scalableSkin.label.alignment = TextAnchor.MiddleCenter; + _scalableSkin.textField.alignment = TextAnchor.MiddleCenter; + + _scalableSkin.button.normal.background = _scalableSkin.box.normal.background; + _scalableSkin.button.hover.background = _scalableSkin.window.normal.background; + + _scalableSkin.button.normal.textColor = new Color(.8f, .8f, .8f); + _scalableSkin.button.hover.textColor = new Color(1f, 1f, 1f); + _scalableSkin.button.active.textColor = new Color(1f, 1f, 1f); + _scalableSkin.button.border = new RectOffset(6, 6, 6, 6); + _scalableSkin.window.border = new RectOffset(8, 8, 8, 10); + } else { + // Use the supplied skin as the base. + _scalableSkin = baseSkin; + } + } + + /// + /// Get the custom scalable skin, already resized to the current screen. Provides the height, width, padding and margin used. + /// + /// + public static GUISkin GetScaledSkin(GUISkin baseSkin, out float height, out float width, out int padding, out int margin, out float boxLeft) { + + if (_scalableSkin == null) { + InitializedGUIStyles(baseSkin); + } + + var dimensions = ScaleGuiSkinToScreenHeight(); + height = dimensions.Item1; + width = dimensions.Item2; + padding = dimensions.Item3; + margin = dimensions.Item4; + boxLeft = dimensions.Item5; + return _scalableSkin; + } + + /// + /// Modifies a skin to make it scale with screen height. + /// + /// + /// Returns (height, width, padding, top-margin, left-box-margin) values applied to the GuiSkin + public static (float, float, int, int, float) ScaleGuiSkinToScreenHeight() { + + bool isVerticalAspect = Screen.height > Screen.width; + bool isSuperThin = Screen.height / Screen.width > (17f / 9f); + + float height = Screen.height * .08f; + float width = System.Math.Min(Screen.width * .9f, Screen.height * .6f); + int padding = (int)(height / 4); + int margin = (int)(height / 8); + float boxLeft = (Screen.width - width) * .5f; + + int fontsize = (int)(isSuperThin ? (width - (padding * 2)) * .07f : height * .4f); + var margins = new RectOffset(0, 0, margin, margin); + + _scalableSkin.button.fontSize = fontsize; + _scalableSkin.button.margin = margins; + _scalableSkin.label.fontSize = fontsize; + _scalableSkin.label.padding = new RectOffset(padding, padding, padding, padding); + _scalableSkin.textField.fontSize = fontsize; + _scalableSkin.window.padding = new RectOffset(padding, padding, padding, padding); + _scalableSkin.window.margin = new RectOffset(margin, margin, margin, margin); + + return (height, width, padding, margin, boxLeft); + } + } +} + +#endregion + + +#region Assets/Photon/Fusion/Runtime/Utilities/FusionUnitySceneManagerUtils.cs + +namespace Fusion { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + using UnityEditor; + using UnityEngine; + using UnityEngine.SceneManagement; + + public static class FusionUnitySceneManagerUtils { + + public class SceneEqualityComparer : IEqualityComparer { + public bool Equals(Scene x, Scene y) { + return x.handle == y.handle; + } + + public int GetHashCode(Scene obj) { + return obj.handle; + } + } + + public static bool IsAddedToBuildSettings(this Scene scene) { + if (scene.buildIndex < 0) { + return false; + } + // yep that's a thing: https://docs.unity3d.com/ScriptReference/SceneManagement.Scene-buildIndex.html + if (scene.buildIndex >= SceneManager.sceneCountInBuildSettings) { + return false; + } + return true; + } + +#if UNITY_EDITOR + public static bool AddToBuildSettings(Scene scene) { + if (IsAddedToBuildSettings(scene)) { + return false; + } + + EditorBuildSettings.scenes = + new[] { new EditorBuildSettingsScene(scene.path, true) } + .Concat(EditorBuildSettings.scenes) + .ToArray(); + + Debug.Log($"Added '{scene.path}' as first entry in Build Settings."); + return true; + } +#endif + + public static LocalPhysicsMode GetLocalPhysicsMode(this Scene scene) { + LocalPhysicsMode mode = LocalPhysicsMode.None; + if (scene.GetPhysicsScene() != Physics.defaultPhysicsScene) { + mode |= LocalPhysicsMode.Physics3D; + } + if (scene.GetPhysicsScene2D() != Physics2D.defaultPhysicsScene) { + mode |= LocalPhysicsMode.Physics2D; + } + return mode; + } + + /// + /// Finds all components of type in the scene. + /// + /// + /// + /// + /// + public static T[] GetComponents(this Scene scene, bool includeInactive) where T : Component { + return GetComponents(scene, includeInactive, out _); + } + + /// + /// Finds all components of type in the scene. + /// + /// + /// + /// + /// + /// + public static T[] GetComponents(this Scene scene, bool includeInactive, out GameObject[] rootObjects) where T : Component { + rootObjects = scene.GetRootGameObjects(); + + var partialResult = new List(); + var result = new List(); + + foreach (var go in rootObjects) { + // depth-first, according to docs and verified by our tests + go.GetComponentsInChildren(includeInactive: includeInactive, partialResult); + // AddRange accepts IEnumerable, so there would be an alloc + foreach (var comp in partialResult) { + result.Add(comp); + } + } + return result.ToArray(); + } + + private static readonly List _reusableGameObjectList = new List(); + + /// + /// Finds all components of type in the scene. + /// + /// + /// + /// + /// + /// + public static void GetComponents(this Scene scene, List results, bool includeInactive) where T : Component { + var rootObjects = _reusableGameObjectList; + scene.GetRootGameObjects(rootObjects); + results.Clear(); + + var partialResult = new List(); + + foreach (var go in rootObjects) { + // depth-first, according to docs and verified by our tests + go.GetComponentsInChildren(includeInactive: includeInactive, partialResult); + // AddRange accepts IEnumerable, so there would be an alloc + foreach (var comp in partialResult) { + results.Add(comp); + } + } + } + + /// + /// Finds the first instance of type in the scene. Returns null if no instance found. + /// + /// + /// + /// + /// + public static T FindComponent(this Scene scene, bool includeInactive = false) where T : Component { + var rootObjects = _reusableGameObjectList; + scene.GetRootGameObjects(rootObjects); + + foreach (var go in rootObjects) { + // depth-first, according to docs and verified by our tests + var found = go.GetComponentInChildren(includeInactive); + if (found != null) { + return found; + } + } + return null; + } + + public static bool CanBeUnloaded(this Scene scene) { + if (!scene.isLoaded) { + return false; + } + + for (int i = 0; i < SceneManager.sceneCount; ++i) { + var s = SceneManager.GetSceneAt(i); + if (s != scene && s.isLoaded) { + return true; + } + } + return false; + } + + public static string Dump(this Scene scene) { + StringBuilder result = new StringBuilder(); + + result.Append("[UnityScene:"); + + if (scene.IsValid()) { + result.Append(scene.name); + result.Append(", isLoaded:").Append(scene.isLoaded); + result.Append(", buildIndex:").Append(scene.buildIndex); + result.Append(", isDirty:").Append(scene.isDirty); + result.Append(", path:").Append(scene.path); + result.Append(", rootCount:").Append(scene.rootCount); + result.Append(", isSubScene:").Append(scene.isSubScene); + } else { + result.Append(""); + } + + result.Append(", handle:").Append(scene.handle); + result.Append("]"); + return result.ToString(); + } + + public static string Dump(this LoadSceneParameters loadSceneParameters) { + return $"[LoadSceneParameters: {loadSceneParameters.loadSceneMode}, localPhysicsMode:{loadSceneParameters.localPhysicsMode}]"; + } + + public static int GetSceneBuildIndex(string nameOrPath) { + if (nameOrPath.IndexOf('/') >= 0) { + return SceneUtility.GetBuildIndexByScenePath(nameOrPath); + } else { + for (int i = 0; i < SceneManager.sceneCountInBuildSettings; ++i) { + var scenePath = SceneUtility.GetScenePathByBuildIndex(i); + GetFileNameWithoutExtensionPosition(scenePath, out var nameIndex, out var nameLength); + if (nameLength == nameOrPath.Length && string.Compare(scenePath, nameIndex, nameOrPath, 0, nameLength, true) == 0) { + return i; + } + } + + return -1; + } + } + + public static int GetSceneIndex(IList scenePathsOrNames, string nameOrPath) { + if (nameOrPath.IndexOf('/') >= 0) { + return scenePathsOrNames.IndexOf(nameOrPath); + } else { + for (int i = 0; i < scenePathsOrNames.Count; ++i) { + var scenePath = scenePathsOrNames[i]; + GetFileNameWithoutExtensionPosition(scenePath, out var nameIndex, out var nameLength); + if (nameLength == nameOrPath.Length && string.Compare(scenePath, nameIndex, nameOrPath, 0, nameLength, true) == 0) { + return i; + } + } + return -1; + } + } + + public static void GetFileNameWithoutExtensionPosition(string nameOrPath, out int index, out int length) { + var lastSlash = nameOrPath.LastIndexOf('/'); + if (lastSlash >= 0) { + index = lastSlash + 1; + } else { + index = 0; + } + + var lastDot = nameOrPath.LastIndexOf('.'); + if (lastDot > index) { + length = lastDot - index; + } else { + length = nameOrPath.Length - index; + } + } + } +} + + +#endregion + + +#region Assets/Photon/Fusion/Runtime/Utilities/RunnerVisibility/NetworkRunnerVisibilityExtensions.cs + +namespace Fusion +{ + using System.Collections.Generic; + using System.Linq; + using UnityEngine; + using Analyzer; + + public static class NetworkRunnerVisibilityExtensions { + + // TODO: Still needed? + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] + private static void ResetAllSimulationStatics() { + ResetStatics(); + } + + /// + /// Types that fusion.runtime isn't aware of, which need to be found using names instead. + /// + [StaticField(StaticFieldResetMode.None)] + private static readonly string[] RecognizedBehaviourNames = + { + "EventSystem" + }; + + [StaticField(StaticFieldResetMode.None)] + private static readonly System.Type[] RecognizedBehaviourTypes = { + typeof(IRunnerVisibilityRecognizedType), + typeof(Renderer), + typeof(AudioListener), + typeof(Camera), + typeof(Canvas), + typeof(Light) + }; + + + private static readonly Dictionary DictionaryLookup; + + // Constructor + static NetworkRunnerVisibilityExtensions() { + DictionaryLookup = new Dictionary(); + } + + private class RunnerVisibility { + public bool IsVisible { get; set; } = true; + + public LinkedList Nodes = new LinkedList(); + } + + private static bool _commonLinksWithMissingInputAuthNeedRefresh; + + public static void RetryRefreshCommonLinks() { + _commonLinksWithMissingInputAuthNeedRefresh = false; + RefreshCommonObjectVisibilities(); + } + + public static void EnableVisibilityExtension(this NetworkRunner runner) { + if (runner && DictionaryLookup.ContainsKey(runner) == false) { + DictionaryLookup.Add(runner, new RunnerVisibility()); + } + } + + public static void DisableVisibilityExtension(this NetworkRunner runner) { + if (runner && DictionaryLookup.ContainsKey(runner)) { + DictionaryLookup.Remove(runner); + } + } + + public static bool HasVisibilityEnabled(this NetworkRunner runner) { + return DictionaryLookup.ContainsKey(runner); + } + + public static bool GetVisible(this NetworkRunner runner) { + if (runner == null) { + return false; + } + + if (DictionaryLookup.TryGetValue(runner, out var runnerVisibility) == false) { + return true; + } + + return runnerVisibility.IsVisible; + } + + public static void SetVisible(this NetworkRunner runner, bool isVisibile) { + runner.GetVisibilityInfo().IsVisible = isVisibile; + RefreshRunnerVisibility(runner); + } + + private static LinkedList GetVisibilityNodes(this NetworkRunner runner) { + if (runner == false) { + return null; + } + return runner.GetVisibilityInfo()?.Nodes; + } + + private static RunnerVisibility GetVisibilityInfo(this NetworkRunner runner) { + if (DictionaryLookup.TryGetValue(runner, out var runnerVisibility) == false) { + return null; + } + + return runnerVisibility; + } + + /// + /// Find all component types that contribute to a scene rendering, and associate them with a component, + /// and add them to the runner's list of visibility nodes. + /// + /// + /// + public static void AddVisibilityNodes(this NetworkRunner runner, GameObject go) { + runner.EnableVisibilityExtension(); + + // Check for flag component which indicates object has already been cataloged. + if (go.GetComponent()) {return;} + + go.AddComponent(); + + // Have user EnableOnSingleRunner add RunnerVisibilityControl before we process all nodes. + var existingEnableOnSingles = go.transform.GetComponentsInChildren(true); + List existingNodes = go.GetComponentsInChildren(false).ToList(); + + foreach (var enableOnSingleRunner in existingEnableOnSingles) { + enableOnSingleRunner.AddNodes(existingNodes); + } + + CollectBehavioursAndAddNodes(go, runner, existingNodes); + + RefreshRunnerVisibility(runner); + } + + private static void CollectBehavioursAndAddNodes(GameObject go, NetworkRunner runner, List existingNodes) { + + // If any changes are made to the commons, we need a full refresh. + var commonsNeedRefresh = false; + + var components = go.transform.GetComponentsInChildren(true); + foreach (var comp in components) { + var nodeAlreadyExists = false; + + // Check for broken/missing components + if (comp == null) continue; + // See if devs added a node for this behaviour already + foreach (var existingNode in existingNodes) + if (existingNode.Component == comp) { + nodeAlreadyExists = true; + if (existingNode.IsOnSingleRunner) { + AddNodeToCommonLookup(existingNode); + RegisterNode(existingNode, runner, comp); + commonsNeedRefresh = true; + } + break; + } + + if (nodeAlreadyExists) + continue; + + // No existing node was found, create one if this comp is a recognized render type + + var type = comp.GetType(); + // Only add if comp is one of the behaviours considered render related. + if (IsRecognizedByRunnerVisibility(type)) { + var node = comp.gameObject.AddComponent(); + RegisterNode(node, runner, comp); + } + } + + if (commonsNeedRefresh) { + _commonLinksWithMissingInputAuthNeedRefresh = true; + RefreshCommonObjectVisibilities(); + } + } + + internal static bool IsRecognizedByRunnerVisibility(this System.Type type) { + // First try the faster type based lookup + foreach (var recognizedType in RecognizedBehaviourTypes) { + if (recognizedType.IsAssignableFrom(type)) + return true; + } + + // The try the slower string based (for namespace references not included in the Fusion core). + var typename = type.Name; + foreach (var recognizedNames in RecognizedBehaviourNames) { + if (typename.Contains(recognizedNames)) + return true; + } + + return false; + } + + private static void RegisterNode(RunnerVisibilityLink link, NetworkRunner runner, Component comp) { +// #if DEBUG +// if (runner.GetVisibilityNodes().Contains(node)) +// Log.Warn($"{nameof(RunnerVisibilityNode)} on '{node.name}' already has been registered."); +// #endif + + runner.GetVisibilityNodes().AddLast(link); + link.Initialize(comp, runner); + } + + public static void UnregisterNode(this RunnerVisibilityLink link) { + + if (link == null || link._runner == null) { + return; + } + + var runner = link._runner; + var runnerIsNullOrDestroyed = !(runner); + + if (!runnerIsNullOrDestroyed) { + var visNodes = link._runner.GetVisibilityNodes(); + if (visNodes == null) { + // No VisibilityNodes collection, likely a shutdown condition. + return; + } + } + + if (runnerIsNullOrDestroyed == false && runner.GetVisibilityNodes().Contains(link)) { + runner.GetVisibilityNodes().Remove(link); + } + + // // Remove from the Runner list. + // if (!ReferenceEquals(node, null) && node._node != null && node._node.List != null) { + // node._node.List.Remove(node); + // } + + if (link.Guid != null) { + + if (CommonObjectLookup.TryGetValue(link.Guid, out var clones)) { + if (clones.Contains(link)) { + clones.Remove(link); + } + + // if this is the last instance of this _guid... remove the entry from the lookup. + if (clones.Count == 0) { + CommonObjectLookup.Remove(link.Guid); + } + } + } + } + + + private static void AddNodeToCommonLookup(RunnerVisibilityLink link) { + var guid = link.Guid; + if (string.IsNullOrEmpty(guid)) + return; + + if (!CommonObjectLookup.TryGetValue(guid, out var clones)) { + clones = new List(); + CommonObjectLookup.Add(guid, clones); + } + clones.Add(link); + } + + /// + /// Reapplies a runner's IsVisibile setting to all of its registered visibility nodes. + /// + /// + /// + private static void RefreshRunnerVisibility(NetworkRunner runner, bool refreshCommonObjects = true) { + + // Trying to refresh before the runner has setup. + if (runner.GetVisibilityNodes() == null) { + //Log.Warn($"{nameof(NetworkRunner)} visibility can't be changed. Not ready yet."); + return; + } + + bool enable = runner.GetVisible(); + + foreach (var node in runner.GetVisibilityNodes()) { + + // This should never be null, but just in case... + if (node == null) { + continue; + } + node.SetEnabled(enable); + } + if (refreshCommonObjects) { + RefreshCommonObjectVisibilities(); + } + } + + + /// + /// Dictionary lookup for manually added visibility nodes (which indicates only one instance should be visible at a time), + /// which returns a list of nodes for a given LocalIdentifierInFile. + /// + [StaticField] + private readonly static Dictionary> CommonObjectLookup = new Dictionary>(); + + internal static void RefreshCommonObjectVisibilities() { + var runners = NetworkRunner.GetInstancesEnumerator(); + NetworkRunner serverRunner = null; + NetworkRunner clientRunner = null; + NetworkRunner firstRunner = null; + bool foundInputAuth = false; + + // First find the runner for each preference. + while (runners.MoveNext()) { + var runner = runners.Current; + // Exclude inactive runners TODO: may not be needed after this list is patched to contain only active + if (!runner.IsRunning || !runner.GetVisible() || runner.IsShutdown) + continue; + + if (runner.IsServer) { + serverRunner = runner; + } + + if (!clientRunner && runner.GameMode != GameMode.Server) { + clientRunner = runner; + } + + if (!firstRunner) { + firstRunner = runner; + } + } + + // loop all common objects, making sure to activate only one peer instance. + foreach (var kvp in CommonObjectLookup) { + var clones = kvp.Value; + if (clones.Count > 0) { + NetworkRunner prefRunner; + var firstClone = clones[0]; + + switch (firstClone.PreferredRunner) { + case RunnerVisibilityLink.PreferredRunners.Server: + prefRunner = serverRunner; + break; + case RunnerVisibilityLink.PreferredRunners.Client: + prefRunner = clientRunner; + break; + case RunnerVisibilityLink.PreferredRunners.Auto: + prefRunner = firstRunner; + break; + default: + prefRunner = null; + break; + } + + foundInputAuth = false; + foreach (var clone in clones) { + if (clone.PreferredRunner == RunnerVisibilityLink.PreferredRunners.InputAuthority) { + var inputFound = clone.IsInputAuth(); + clone.Enabled = inputFound && clone._runner.GetVisible(); + foundInputAuth |= inputFound; + } else { + clone.Enabled = ReferenceEquals(clone._runner, prefRunner); + } + } + + if (firstClone.PreferredRunner == RunnerVisibilityLink.PreferredRunners.InputAuthority) { + if (foundInputAuth == false && _commonLinksWithMissingInputAuthNeedRefresh) { + // Signal to refresh later when the object has input information. + _commonLinksWithMissingInputAuthNeedRefresh = false; + firstClone.InvokeRefreshCommonObjectVisibilities(1f); + } + } + } + } + } + + [StaticFieldResetMethod] + internal static void ResetStatics() { + CommonObjectLookup.Clear(); + } + } +} + + +#endregion + +#endif diff --git a/Assets/Photon/Fusion/Runtime/Fusion.Unity.cs.meta b/Assets/Photon/Fusion/Runtime/Fusion.Unity.cs.meta new file mode 100644 index 00000000..971c1088 --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/Fusion.Unity.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4a69219964762b240b6138a56c829f26 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Runtime/FusionAddressablePrefabsPreloader.cs b/Assets/Photon/Fusion/Runtime/FusionAddressablePrefabsPreloader.cs new file mode 100644 index 00000000..70da0713 --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/FusionAddressablePrefabsPreloader.cs @@ -0,0 +1,41 @@ +namespace Fusion { + using System.Collections.Generic; + using UnityEngine; + +#if FUSION_ENABLE_ADDRESSABLES && !FUSION_DISABLE_ADDRESSABLES + using UnityEngine.AddressableAssets; + using UnityEngine.ResourceManagement.AsyncOperations; +#endif + + public class FusionAddressablePrefabsPreloader : MonoBehaviour { +#if FUSION_ENABLE_ADDRESSABLES && !FUSION_DISABLE_ADDRESSABLES + private List> _handles = new List>(); + + private async void Start() { + var config = NetworkProjectConfig.Global; + + // there are a few ways to load an asset with Addressables (by label, by IResourceLocation, by address etc.) + // but it seems that they're not fully interchangeable, i.e. loading by label will not make loading by address + // be reported as done immediately; hence the only way to preload an asset for Quantum is to replicate + // what it does internally, i.e. load with the very same parameters + + foreach (var (id, source) in config.PrefabTable.GetEntries()) { + if (source is NetworkPrefabSourceAddressable addressable) { + // we can't just LoadAssetAsync() because source does it, too: + // https://forum.unity.com/threads/1-15-1-assetreference-not-allow-loadassetasync-twice.959910/ + var key = addressable.RuntimeKey; + var handle = Addressables.LoadAssetAsync(key); + await handle.Task; + _handles.Add(handle); + } + } + } + + private void OnDestroy() { + foreach (var handle in _handles) { + Addressables.Release(handle); + } + } +#endif + } +} diff --git a/Assets/Photon/Fusion/Runtime/FusionAddressablePrefabsPreloader.cs.meta b/Assets/Photon/Fusion/Runtime/FusionAddressablePrefabsPreloader.cs.meta new file mode 100644 index 00000000..87e95a1d --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/FusionAddressablePrefabsPreloader.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ccb29b1c478478c418765bc307a473ca +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Runtime/FusionBasicBillboard.cs b/Assets/Photon/Fusion/Runtime/FusionBasicBillboard.cs new file mode 100644 index 00000000..500d0242 --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/FusionBasicBillboard.cs @@ -0,0 +1,75 @@ +namespace Fusion { + using UnityEngine; + + + /// + /// Component which automatically faces this GameObject toward the supplied Camera. If Camera == null, will face towards Camera.main. + /// + [Fusion.ScriptHelp(BackColor = ScriptHeaderBackColor.Olive)] + [ExecuteAlways] + public class FusionBasicBillboard : Fusion.Behaviour { + + /// + /// Force a particular camera to billboard this object toward. Leave null to use Camera.main. + /// + [InlineHelp] + public Camera Camera; + + // Camera find is expensive, so do it once per update for ALL implementations + static float _lastCameraFindTime; + static Camera _currentCam; + + private void OnEnable() { + UpdateLookAt(); + } + + private void OnDisable() { + transform.localRotation = default; + } + + Camera MainCamera { + set { + _currentCam = value; + } + get { + + var time = Time.time; + // Only look for the camera once per Update. + if (time == _lastCameraFindTime) + return _currentCam; + + _lastCameraFindTime = time; + var cam = Camera.main; + _currentCam = cam; + return cam; + } + } + +#if UNITY_EDITOR + private void OnDrawGizmos() { + LateUpdate(); + } +#endif + + private void LateUpdate() { + UpdateLookAt(); + } + + public void UpdateLookAt() { + + var cam = Camera ? Camera : MainCamera; + + if (cam) { + if (enabled) { + transform.rotation = cam.transform.rotation; + } + } + } + + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] + static void ResetStatics() { + _currentCam = default; + _lastCameraFindTime = default; + } + } +} \ No newline at end of file diff --git a/Assets/Photon/Fusion/Runtime/FusionBasicBillboard.cs.meta b/Assets/Photon/Fusion/Runtime/FusionBasicBillboard.cs.meta new file mode 100644 index 00000000..5c21f5ac --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/FusionBasicBillboard.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 25acde9209338d24d83cc1c826297be4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Runtime/FusionBootstrap.cs b/Assets/Photon/Fusion/Runtime/FusionBootstrap.cs new file mode 100644 index 00000000..0f7b4be4 --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/FusionBootstrap.cs @@ -0,0 +1,676 @@ +namespace Fusion { + using System; + using Fusion.Sockets; + using System.Collections; + using System.Threading.Tasks; + using UnityEngine; + using UnityEngine.SceneManagement; + using System.Collections.Generic; + using System.Linq; + using Statistics; + using UnityEngine.Serialization; + +#if UNITY_EDITOR + using UnityEditor; +#endif + + /// + /// A Fusion prototyping class for starting up basic networking. Add this component to your startup scene, and supply a . + /// Can be set to automatically startup the network, display an in-game menu, or allow simplified start calls like . + /// + [DisallowMultipleComponent] + [AddComponentMenu("Fusion/Fusion Bootstrap")] + [ScriptHelp(BackColor = ScriptHeaderBackColor.Steel)] + public class FusionBootstrap : Fusion.Behaviour { + + /// + /// Selection for how will behave at startup. + /// + public enum StartModes { + UserInterface, + Automatic, + Manual + } + + /// + /// The current stage of connection or shutdown. + /// + public enum Stage { + Disconnected, + StartingUp, + UnloadOriginalScene, + ConnectingServer, + ConnectingClients, + AllConnected, + } + + [Serializable] + class StartCommand : FusionMppmCommand { + public string RoomName; + public SceneRef InitialScene; + public int ClientCount; + public bool IsShared; + + public override void Execute() { + Instance = this; + } + + public static StartCommand Instance; + } + + /// + /// Supply a Prefab or a scene object which has the component on it, + /// as well as any runner dependent components which implement , + /// such as or your own custom INetworkInput implementations. + /// + [InlineHelp] + [WarnIf(nameof(RunnerPrefab), false, "No " + nameof(RunnerPrefab) + " supplied. Will search for a " + nameof(NetworkRunner) + " in the scene at startup.")] + public NetworkRunner RunnerPrefab; + + /// + /// Select how network startup will be triggered. Automatically, by in-game menu selection, or exclusively by script. + /// + [InlineHelp] + [WarnIf(nameof(StartMode), (long)StartModes.Manual, "Start network by calling the methods " + nameof(StartHost) + "(), " + nameof(StartServer) + "(), " + nameof(StartClient) + "(), " + nameof(StartHostPlusClients) + "(), or " + nameof(StartServerPlusClients) + "()")] + public StartModes StartMode = StartModes.UserInterface; + + /// + /// When is set to , this option selects if the + /// will be started as a dedicated server, or as a host (which is a server with a local player). + /// + [InlineHelp] + [UnityEngine.Serialization.FormerlySerializedAs("Server")] + [DrawIf(nameof(StartMode), (long)StartModes.Automatic, Hide = true)] + public GameMode AutoStartAs = GameMode.Shared; + + /// + /// will not render GUI elements while == . + /// + [InlineHelp] + [DrawIf(nameof(StartMode), (long)StartModes.UserInterface, Hide = true)] + public bool AutoHideGUI = true; + + /// + /// The number of client instances that will be created if running in Mulit-Peer Mode. + /// When using the Select start mode, this number will be the default value for the additional clients option box. + /// + [InlineHelp] + [DrawIf(nameof(ShowAutoClients), Hide = true)] + public int AutoClients = 1; + + /// + /// How long to wait after starting a peer before starting the next one. + /// + [InlineHelp] + public float ClientStartDelay = 0.1f; + + /// + /// The port that server/host will use. + /// + [InlineHelp] + public ushort ServerPort; // Any port + + /// + /// The default room name to use when connecting to photon cloud. + /// + [InlineHelp] + public string DefaultRoomName = string.Empty; // empty/null means Random Room Name + + [NonSerialized] + NetworkRunner _server; + + /// + /// The Scene that will be loaded after network shutdown completes (all peers have disconnected). + /// If this field is null or invalid, will be set to the current scene when runs Awake(). + /// + [InlineHelp] + [ScenePath] + public string InitialScenePath; + + // TODO: this is debt + static string _initialScenePath; + + /// + /// Indicates which step of the startup process is currently in. + /// + [InlineHelp] + [SerializeField] + [ReadOnly] + protected Stage _currentStage; + + /// + /// Requires Multiplayer Play Mode (MPPM) to be installed. If enabled, will automatically join the virtual instance. + /// + [DrawIf(nameof(IsMPPMEnabled), true)] + [Header("Multiplayer Play Mode")] + public bool AutoConnectVirtualInstances = true; + /// + /// How much to wait before the main instance lets the virtual instances connect. + /// + [DrawIf(nameof(IsMPPMEnabled), true)] + public float VirtualInstanceConnectDelay = 1f; + + /// + /// Indicates which step of the startup process is currently in. + /// + public Stage CurrentStage { + get => _currentStage; + internal set { + _currentStage = value; +#if UNITY_EDITOR + // Hack to force an inspector refresh when this value changes, as it affects which buttons are shown. + EditorUtility.SetDirty(this); +#endif + } + } + + /// + /// The index number used for the last created peer. + /// + public int LastCreatedClientIndex { get; internal set; } + + /// + /// The server mode that was used for initial startup. Used to inform UI which client modes should be available. + /// + public GameMode CurrentServerMode { get; internal set; } + + protected bool CanAddClients => CurrentStage == Stage.AllConnected && CurrentServerMode > 0 && CurrentServerMode != GameMode.Shared && CurrentServerMode != GameMode.Single; + protected bool CanAddSharedClients => CurrentStage == Stage.AllConnected && CurrentServerMode > 0 && CurrentServerMode == GameMode.Shared; + protected bool IsShutdown => CurrentStage == Stage.Disconnected; + protected bool IsShutdownAndMultiPeer => CurrentStage == Stage.Disconnected && UsingMultiPeerMode; + + protected bool UsingMultiPeerMode => NetworkProjectConfig.Global.PeerMode == NetworkProjectConfig.PeerModes.Multiple; + protected bool ShowAutoClients => UsingMultiPeerMode && (StartMode == StartModes.UserInterface || (StartMode == StartModes.Automatic && AutoStartAs != GameMode.Single)); + + +#if UNITY_EDITOR + protected virtual void Reset() { + if (TryGetComponent(out var ndsg) == false) { + ndsg = gameObject.AddComponent(); + } + } + +#endif + + + protected virtual void Start() { + + if (_initialScenePath == null) { + if (string.IsNullOrEmpty(InitialScenePath)) { + var currentScene = SceneManager.GetActiveScene(); + if (currentScene.IsValid()) { + _initialScenePath = currentScene.path; + } else { + // Last fallback is the first entry in the build settings + _initialScenePath = SceneManager.GetSceneByBuildIndex(0).path; + } + + InitialScenePath = _initialScenePath; + } else { + _initialScenePath = InitialScenePath; + } + } + + var config = NetworkProjectConfig.Global; + var isMultiPeer = config.PeerMode == NetworkProjectConfig.PeerModes.Multiple; + + var existingRunner = FindFirstObjectByType(); + + if (existingRunner && existingRunner != RunnerPrefab) { + if (existingRunner.State != NetworkRunner.States.Shutdown) { + // disable + enabled = false; + + // destroy this and GUI (if exists), and return + var gui = GetComponent(); + if (gui) { + Destroy(gui); + } + + Destroy(this); + return; + } else { + // If no RunnerPrefab is supplied, use the scene runner. + if (RunnerPrefab == null) { + RunnerPrefab = existingRunner; + } + } + } + + if (FusionMppm.Status == FusionMppmStatus.VirtualInstance && AutoConnectVirtualInstances) { + StartCoroutine(StartWithMppmVirtualInstance()); + return; + } + + switch (StartMode) { + case StartModes.Manual: + // skip + return; + case StartModes.Automatic: { + if (TryGetSceneRef(out var sceneRef)) { + int clientCount; + if (AutoStartAs == GameMode.Single) { + clientCount = 0; + } else if (isMultiPeer) { + clientCount = AutoClients; + } else if (AutoStartAs == GameMode.Client || AutoStartAs == GameMode.Shared || AutoStartAs == GameMode.AutoHostOrClient) { + clientCount = 1; + } else { + clientCount = 0; + } + + StartCoroutine(StartWithClients(AutoStartAs, sceneRef, clientCount)); + } + + break; + } + default: { + ShowUserInterface(); + break; + } + } + } + + protected void ShowUserInterface() { + if (TryGetComponent(out var gui) == false) { + gui = gameObject.AddComponent(); + } + gui.enabled = true; + } + + private bool TryGetSceneRef(out SceneRef sceneRef) { + var activeScene = SceneManager.GetActiveScene(); + if (activeScene.buildIndex < 0 || activeScene.buildIndex >= SceneManager.sceneCountInBuildSettings) { + sceneRef = default; + return false; + } else { + sceneRef = SceneRef.FromIndex(activeScene.buildIndex); + return true; + } + } + + /// + /// Start a single player instance. + /// + [EditorButton(EditorButtonVisibility.PlayMode)] + [DrawIf(nameof(IsShutdown), Hide = true)] + public virtual void StartSinglePlayer() { + if (TryGetSceneRef(out var sceneRef)) { + StartCoroutine(StartWithClients(GameMode.Single, sceneRef, 0)); + } + } + + /// + /// Start a server instance. + /// + [EditorButton(EditorButtonVisibility.PlayMode)] + [DrawIf(nameof(IsShutdown), Hide = true)] + public virtual void StartServer() { + if (TryGetSceneRef(out var sceneRef)) { + StartCoroutine(StartWithClients(GameMode.Server, sceneRef, 0)); + } + } + + /// + /// Start a host instance. This is a server instance, with a local player. + /// + [EditorButton(EditorButtonVisibility.PlayMode)] + [DrawIf(nameof(IsShutdown), Hide = true)] + public virtual void StartHost() { + if (TryGetSceneRef(out var sceneRef)) { + StartCoroutine(StartWithClients(GameMode.Host, sceneRef, 0)); + } + } + + /// + /// Start a client instance. + /// + [EditorButton(EditorButtonVisibility.PlayMode)] + [DrawIf(nameof(IsShutdown), Hide = true)] + public virtual void StartClient() { + StartCoroutine(StartWithClients(GameMode.Client, default, 1)); + } + + [EditorButton(EditorButtonVisibility.PlayMode)] + [DrawIf(nameof(IsShutdown), Hide = true)] + public virtual void StartSharedClient() { + if (TryGetSceneRef(out var sceneRef)) { + StartCoroutine(StartWithClients(GameMode.Shared, sceneRef, 1)); + } + } + + [EditorButton("Start Auto Host Or Client", EditorButtonVisibility.PlayMode)] + [DrawIf(nameof(IsShutdown), Hide = true)] + public virtual void StartAutoClient() { + if (TryGetSceneRef(out var sceneRef)) { + StartCoroutine(StartWithClients(GameMode.AutoHostOrClient, sceneRef, 1)); + } + } + + /// + /// Start a Fusion server instance, and the number of client instances indicated by . + /// InstanceMode must be set to Multi-Peer mode, as this requires multiple instances. + /// + [EditorButton(EditorButtonVisibility.PlayMode)] + [DrawIf(nameof(IsShutdown), Hide = true)] + public virtual void StartServerPlusClients() { + StartServerPlusClients(AutoClients); + } + + /// + /// Start a Fusion host instance, and the number of client instances indicated by . + /// InstanceMode must be set to Multi-Peer mode, as this requires multiple instances. + /// + [EditorButton(EditorButtonVisibility.PlayMode)] + [DrawIf(nameof(IsShutdown), Hide = true)] + public void StartHostPlusClients() { + StartHostPlusClients(AutoClients); + } + + [EditorButton(EditorButtonVisibility.PlayMode)] + [DrawIf(nameof(CurrentStage), Hide = true)] + public void Shutdown() { + ShutdownAll(); + } + + /// + /// Start a Fusion server instance, and the indicated number of client instances. + /// InstanceMode must be set to Multi-Peer mode, as this requires multiple instances. + /// + public virtual void StartServerPlusClients(int clientCount) { + if (NetworkProjectConfig.Global.PeerMode == NetworkProjectConfig.PeerModes.Multiple) { + if (TryGetSceneRef(out var sceneRef)) { + StartCoroutine(StartWithClients(GameMode.Server, sceneRef, clientCount)); + } + } else { + Debug.LogWarning($"Unable to start multiple {nameof(NetworkRunner)}s in Unique Instance mode."); + } + } + + /// + /// Start a Fusion host instance (server with local player), and the indicated number of additional client instances. + /// InstanceMode must be set to Multi-Peer mode, as this requires multiple instances. + /// + public void StartHostPlusClients(int clientCount) { + if (NetworkProjectConfig.Global.PeerMode == NetworkProjectConfig.PeerModes.Multiple) { + if (TryGetSceneRef(out var sceneRef)) { + StartCoroutine(StartWithClients(GameMode.Host, sceneRef, clientCount)); + } + } else { + Debug.LogWarning($"Unable to start multiple {nameof(NetworkRunner)}s in Unique Instance mode."); + } + } + + /// + /// Start a Fusion host instance (server with local player), and the indicated number of additional client instances. + /// InstanceMode must be set to Multi-Peer mode, as this requires multiple instances. + /// + public void StartMultipleClients(int clientCount) { + if (NetworkProjectConfig.Global.PeerMode == NetworkProjectConfig.PeerModes.Multiple) { + if (TryGetSceneRef(out var sceneRef)) { + StartCoroutine(StartWithClients(GameMode.Client, sceneRef, clientCount)); + } + } else { + Debug.LogWarning($"Unable to start multiple {nameof(NetworkRunner)}s in Unique Instance mode."); + } + } + + /// + /// Start as Room on the Photon cloud, and connects as one or more clients. + /// + /// + public void StartMultipleSharedClients(int clientCount) { + if (NetworkProjectConfig.Global.PeerMode == NetworkProjectConfig.PeerModes.Multiple) { + if (TryGetSceneRef(out var sceneRef)) { + StartCoroutine(StartWithClients(GameMode.Shared, sceneRef, clientCount)); + } + } else { + Debug.LogWarning($"Unable to start multiple {nameof(NetworkRunner)}s in Unique Instance mode."); + } + } + + public void StartMultipleAutoClients(int clientCount) { + if (NetworkProjectConfig.Global.PeerMode == NetworkProjectConfig.PeerModes.Multiple) { + if (TryGetSceneRef(out var sceneRef)) { + StartCoroutine(StartWithClients(GameMode.AutoHostOrClient, sceneRef, clientCount)); + } + } else { + Debug.LogWarning($"Unable to start multiple {nameof(NetworkRunner)}s in Unique Instance mode."); + } + } + + public void ShutdownAll() { + foreach (var runner in NetworkRunner.Instances.ToList()) { + if (runner != null && runner.IsRunning) { + runner.Shutdown(); + } + } + + SceneManager.LoadSceneAsync(_initialScenePath); + // Destroy our DontDestroyOnLoad objects to finish the reset + Destroy(RunnerPrefab.gameObject); + Destroy(gameObject); + CurrentStage = Stage.Disconnected; + CurrentServerMode = 0; + } + + + protected IEnumerator StartWithClients(GameMode serverMode, SceneRef sceneRef, int clientCount) { + // Avoid double clicks or disallow multiple startup calls. + if (CurrentStage != Stage.Disconnected) { + yield break; + } + + bool includesServerStart = serverMode != GameMode.Shared && serverMode != GameMode.Client && serverMode != GameMode.AutoHostOrClient; + + if (!includesServerStart && clientCount == 0) { + Debug.LogError($"{nameof(GameMode)} is set to {serverMode}, and {nameof(clientCount)} is set to zero. Starting no network runners."); + yield break; + } + + CurrentStage = Stage.StartingUp; + + var currentScene = SceneManager.GetActiveScene(); + + // must have a runner + if (!RunnerPrefab) { + Debug.LogError($"{nameof(RunnerPrefab)} not set, can't perform debug start."); + yield break; + } + + // Clone the RunnerPrefab so we can safely delete the startup scene (the prefab might be part of it, rather than an asset). + RunnerPrefab = Instantiate(RunnerPrefab); + DontDestroyOnLoad(RunnerPrefab); + RunnerPrefab.name = "Temporary Runner Prefab"; + + // Single-peer can't start more than one peer. Validate clientCount to make sure it complies with current PeerMode. + var config = NetworkProjectConfig.Global; + if (config.PeerMode != NetworkProjectConfig.PeerModes.Multiple) { + int maxClientsAllowed = includesServerStart ? 0 : 1; + if (clientCount > maxClientsAllowed) { + Debug.LogWarning($"Instance mode must be set to {nameof(NetworkProjectConfig.PeerModes.Multiple)} to perform a debug start multiple peers. Restricting client count to {maxClientsAllowed}."); + clientCount = maxClientsAllowed; + } + } + + // If NDS is starting more than 1 shared or auto client, they need to use the same Session Name, otherwise, they will end up on different Rooms + // as Fusion creates a Random Session Name when no name is passed on the args + var localMultipeerCheck = (serverMode == GameMode.Shared || serverMode == GameMode.AutoHostOrClient || serverMode == GameMode.Server || serverMode == GameMode.Host) && + clientCount > 1 && + config.PeerMode == NetworkProjectConfig.PeerModes.Multiple; + var isMppmMainInstance = FusionMppm.Status == FusionMppmStatus.MainInstance; + + if ((localMultipeerCheck || isMppmMainInstance) && string.IsNullOrEmpty(DefaultRoomName)) { + DefaultRoomName = Guid.NewGuid().ToString(); + Debug.Log($"Generated Session Name: {DefaultRoomName}"); + } + + if (gameObject.transform.parent) { + Debug.LogWarning($"{nameof(FusionBootstrap)} can't be a child game object, un-parenting."); + gameObject.transform.parent = null; + } + + DontDestroyOnLoad(gameObject); + CurrentServerMode = serverMode; + + // start server, just take address from it + if (includesServerStart) { + _server = Instantiate(RunnerPrefab); + _server.name = serverMode.ToString(); + + var serverTask = InitializeNetworkRunner(_server, serverMode, NetAddress.Any(ServerPort), sceneRef, (runner) => { +#if FUSION_DEV + var name = _server.name; // closures do not capture values, need a local var to save it + Debug.Log($"Server NetworkRunner '{name}' started."); +#endif + }); + + while (serverTask.IsCompleted == false) { + yield return new WaitForSeconds(1f); + } + + if (serverTask.IsFaulted) { + Log.Debug($"Unable to start server: {serverTask.Exception}"); + + ShutdownAll(); + yield break; + } + + // this action is called after InitializeNetworkRunner for the server has completed startup + yield return StartClients(clientCount, serverMode, sceneRef); + } else { + yield return StartClients(clientCount, serverMode, sceneRef); + } + + if (FusionMppm.Status == FusionMppmStatus.MainInstance && serverMode != GameMode.Single) { + if (VirtualInstanceConnectDelay > 0) { + yield return new WaitForSecondsRealtime(VirtualInstanceConnectDelay); + } + FusionMppm.MainEditor?.Send(new StartCommand { + RoomName = DefaultRoomName, + InitialScene = sceneRef, + ClientCount = 1, + IsShared = serverMode == GameMode.Shared + }); + } + } + + protected IEnumerator StartWithMppmVirtualInstance() { + while (StartCommand.Instance == null) { + yield return null; + } + + var command = StartCommand.Instance; + StartCommand.Instance = null; + + DefaultRoomName = command.RoomName; + yield return StartClients(command.ClientCount, command.IsShared ? GameMode.Shared : GameMode.Client, command.InitialScene); + } + + [EditorButton("Add Additional Client", EditorButtonVisibility.PlayMode)] + [DrawIf(nameof(CanAddClients), Hide = true)] + public void AddClient() { + if (TryGetSceneRef(out var sceneRef)) { + AddClient(GameMode.Client, sceneRef); + } + } + + [EditorButton("Add Additional Shared Client", EditorButtonVisibility.PlayMode)] + [DrawIf(nameof(CanAddSharedClients), Hide = true)] + public void AddSharedClient() { + if (TryGetSceneRef(out var sceneRef)) { + AddClient(GameMode.Shared, sceneRef); + } + } + + public Task AddClient(GameMode serverMode, SceneRef sceneRef) { + var client = Instantiate(RunnerPrefab); + DontDestroyOnLoad(client); + + client.name = $"Client {(Char)(65 + LastCreatedClientIndex++)}"; + + // if server mode is Shared or AutoHostOrClient, then game client mode is the same as the server, otherwise it is client + var mode = GameMode.Client; + switch (serverMode) { + case GameMode.Shared: + case GameMode.AutoHostOrClient: + mode = serverMode; + break; + } + +#if FUSION_DEV + var clientTask = InitializeNetworkRunner(client, mode, NetAddress.Any(), sceneRef, (runner) => { + var name = client.name; // closures do not capture values, need a local var to save it + Debug.Log($"Client NetworkRunner '{name}' started."); + }); +#else + var clientTask = InitializeNetworkRunner(client, mode, NetAddress.Any(), sceneRef, null); +#endif + + return clientTask; + } + + protected IEnumerator StartClients(int clientCount, GameMode serverMode, SceneRef sceneRef = default) { + + CurrentStage = Stage.ConnectingClients; + + var clientTasks = new List(); + for (int i = 0; i < clientCount; ++i) { + clientTasks.Add(AddClient(serverMode, sceneRef)); + yield return new WaitForSeconds(ClientStartDelay); + } + + var clientsStartTask = Task.WhenAll(clientTasks); + + while (clientsStartTask.IsCompleted == false) { + yield return new WaitForSeconds(1f); + } + + if (clientsStartTask.IsFaulted) { + Debug.LogWarning(clientsStartTask.Exception); + } + + CurrentStage = Stage.AllConnected; + } + + protected virtual Task InitializeNetworkRunner(NetworkRunner runner, GameMode gameMode, NetAddress address, SceneRef scene, Action onGameStarted, + INetworkRunnerUpdater updater = null) { + + var sceneManager = runner.GetComponent(); + if (sceneManager == null) { + Debug.Log($"NetworkRunner does not have any component implementing {nameof(INetworkSceneManager)} interface, adding {nameof(NetworkSceneManagerDefault)}.", runner); + sceneManager = runner.gameObject.AddComponent(); + } + + var objectProvider = runner.GetComponent(); + if (objectProvider == null) { + Debug.Log($"NetworkRunner does not have any component implementing {nameof(INetworkObjectProvider)} interface, adding {nameof(NetworkObjectProviderDefault)}.", runner); + objectProvider = runner.gameObject.AddComponent(); + } + + var sceneInfo = new NetworkSceneInfo(); + if (scene.IsValid) { + sceneInfo.AddSceneRef(scene, LoadSceneMode.Additive); + } + + return runner.StartGame(new StartGameArgs { + GameMode = gameMode, + Address = address, + Scene = sceneInfo, + SessionName = DefaultRoomName, + OnGameStarted = onGameStarted, + SceneManager = sceneManager, + Updater = updater, + ObjectProvider = objectProvider, + }); + } + + private static bool IsMPPMEnabled => FusionMppm.Status != FusionMppmStatus.Disabled; + + /// + /// Only show the GUI if the StartMode is set to UserInterface and not being run in a Virtual Instance (MPPM). + /// + public bool ShouldShowGUI => StartMode == StartModes.UserInterface && + !(AutoConnectVirtualInstances && FusionMppm.Status == FusionMppmStatus.VirtualInstance); + } +} \ No newline at end of file diff --git a/Assets/Photon/Fusion/Runtime/FusionBootstrap.cs.meta b/Assets/Photon/Fusion/Runtime/FusionBootstrap.cs.meta new file mode 100644 index 00000000..91c28fff --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/FusionBootstrap.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 88230848386761045af440d808ee3efa +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Runtime/FusionBootstrapDebugGUI.cs b/Assets/Photon/Fusion/Runtime/FusionBootstrapDebugGUI.cs new file mode 100644 index 00000000..8c577bd0 --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/FusionBootstrapDebugGUI.cs @@ -0,0 +1,359 @@ +namespace Fusion { + using System; + using UnityEngine; + using System.Collections.Generic; + + /// + /// Companion component for . Automatically added as needed for rendering in-game networking IMGUI. + /// + [RequireComponent(typeof(FusionBootstrap))] + [AddComponentMenu("Fusion/Fusion Boostrap Debug GUI")] + [ScriptHelp(BackColor = ScriptHeaderBackColor.Steel)] + public class FusionBootstrapDebugGUI : Fusion.Behaviour { + + /// + /// When enabled, the in-game user interface buttons can be activated with the keys H (Host), S (Server) and C (Client). + /// + [InlineHelp] + public bool EnableHotkeys; + + /// + /// The GUISkin to use as the base for the scalable in-game UI. + /// + [InlineHelp] + public GUISkin BaseSkin; + + FusionBootstrap _networkDebugStart; + string _clientCount; + bool _isMultiplePeerMode; + + Dictionary _nicifiedStageNames; + +#if UNITY_EDITOR + + protected virtual void Reset() { + _networkDebugStart = EnsureNetworkDebugStartExists(); + _clientCount = _networkDebugStart.AutoClients.ToString(); + BaseSkin = GetAsset("e59b35dfeb4b6f54e9b2791b2a40a510"); + } + +#endif + + protected virtual void OnValidate() { + ValidateClientCount(); + } + + protected void ValidateClientCount() { + if (_clientCount == null) { + _clientCount = "1"; + } else { + _clientCount = System.Text.RegularExpressions.Regex.Replace(_clientCount, "[^0-9]", ""); + } + } + protected int GetClientCount() { + try { + return Convert.ToInt32(_clientCount); + } catch { + return 0; + } + } + + protected virtual void Awake() { + + _nicifiedStageNames = ConvertEnumToNicifiedNameLookup("Fusion Status: "); + _networkDebugStart = EnsureNetworkDebugStartExists(); + _clientCount = _networkDebugStart.AutoClients.ToString(); + ValidateClientCount(); + } + protected virtual void Start() { + _isMultiplePeerMode = NetworkProjectConfig.Global.PeerMode == NetworkProjectConfig.PeerModes.Multiple; + } + + protected FusionBootstrap EnsureNetworkDebugStartExists() { + if (_networkDebugStart) { + if (_networkDebugStart.gameObject == gameObject) + return _networkDebugStart; + } + + if (TryGetBehaviour(out var found)) { + _networkDebugStart = found; + return found; + } + + _networkDebugStart = AddBehaviour(); + return _networkDebugStart; + } + + private void Update() { + + var nds = EnsureNetworkDebugStartExists(); + if (!nds.ShouldShowGUI) { + return; + } + + var currentstage = nds.CurrentStage; + if (currentstage != FusionBootstrap.Stage.Disconnected) { + return; + } + + if (EnableHotkeys) { + if (Input.GetKeyDown(KeyCode.I)) { + _networkDebugStart.StartSinglePlayer(); + } + + if (Input.GetKeyDown(KeyCode.H)) { + if (_isMultiplePeerMode) { + StartHostWithClients(_networkDebugStart); + } else { + _networkDebugStart.StartHost(); + } + } + + if (Input.GetKeyDown(KeyCode.S)) { + if (_isMultiplePeerMode) { + StartServerWithClients(_networkDebugStart); + } else { + _networkDebugStart.StartServer(); + } + } + + if (Input.GetKeyDown(KeyCode.C)) { + if (_isMultiplePeerMode) { + StartMultipleClients(nds); + } else { + nds.StartClient(); + } + } + + if (Input.GetKeyDown(KeyCode.A)) { + if (_isMultiplePeerMode) { + StartMultipleAutoClients(nds); + } else { + nds.StartAutoClient(); + } + } + + if (Input.GetKeyDown(KeyCode.P)) { + if (_isMultiplePeerMode) { + StartMultipleSharedClients(nds); + } else { + nds.StartSharedClient(); + } + } + } + } + + protected virtual void OnGUI() { + + var nds = EnsureNetworkDebugStartExists(); + if (!nds.ShouldShowGUI) { + return; + } + + var currentstage = nds.CurrentStage; + if (nds.AutoHideGUI && currentstage == FusionBootstrap.Stage.AllConnected) { + return; + } + + var holdskin = GUI.skin; + + GUI.skin = FusionScalableIMGUI.GetScaledSkin(BaseSkin, out var height, out var width, out var padding, out var margin, out var leftBoxMargin); + + GUILayout.BeginArea(new Rect(leftBoxMargin, margin, width, Screen.height)); + { + GUILayout.BeginVertical(GUI.skin.window); + { + GUILayout.BeginHorizontal(GUILayout.Height(height)); + { + var stagename = _nicifiedStageNames.TryGetValue(nds.CurrentStage, out var stage) ? stage : "Unrecognized Stage"; + GUILayout.Label(stagename, new GUIStyle(GUI.skin.label) { fontSize = (int)(GUI.skin.label.fontSize * .8f), alignment = TextAnchor.UpperLeft }); + + // Add button to hide Shutdown option after all connect, which just enables AutoHide - so that interface will reappear after a disconnect. + if (nds.AutoHideGUI == false && nds.CurrentStage == FusionBootstrap.Stage.AllConnected) { + if (GUILayout.Button("X", GUILayout.ExpandHeight(true), GUILayout.Width(height))) { + nds.AutoHideGUI = true; + } + } + } + GUILayout.EndHorizontal(); + } + GUILayout.EndVertical(); + + GUILayout.BeginVertical(GUI.skin.window); + { + + if (currentstage == FusionBootstrap.Stage.Disconnected) { + + GUILayout.BeginHorizontal(); + { + GUILayout.Label("Room:", GUILayout.Height(height), GUILayout.Width(width * .33f)); + nds.DefaultRoomName = GUILayout.TextField(nds.DefaultRoomName, 25, GUILayout.Height(height)); + } + GUILayout.EndHorizontal(); + + if (GUILayout.Button(EnableHotkeys ? "Start Single Player (I)" : "Start Single Player", GUILayout.Height(height))) { + nds.StartSinglePlayer(); + } + + if (GUILayout.Button(EnableHotkeys ? "Start Shared Client (P)" : "Start Shared Client", GUILayout.Height(height))) { + if (_isMultiplePeerMode) { + StartMultipleSharedClients(nds); + } else { + nds.StartSharedClient(); + } + } + + if (GUILayout.Button(EnableHotkeys ? "Start Server (S)" : "Start Server", GUILayout.Height(height))) { + if (_isMultiplePeerMode) { + StartServerWithClients(nds); + + } else { + nds.StartServer(); + } + } + + if (GUILayout.Button(EnableHotkeys ? "Start Host (H)" : "Start Host", GUILayout.Height(height))) { + if (_isMultiplePeerMode) { + StartHostWithClients(nds); + } else { + nds.StartHost(); + } + } + + if (GUILayout.Button(EnableHotkeys ? "Start Client (C)" : "Start Client", GUILayout.Height(height))) { + if (_isMultiplePeerMode) { + StartMultipleClients(nds); + } else { + nds.StartClient(); + } + } + + if (GUILayout.Button(EnableHotkeys ? "Start Auto Host Or Client (A)" : "Start Auto Host Or Client", GUILayout.Height(height))) { + if (_isMultiplePeerMode) { + StartMultipleAutoClients(nds); + } else { + nds.StartAutoClient(); + } + } + + if (_isMultiplePeerMode) { + + GUILayout.BeginHorizontal(/*GUI.skin.button*/); + { + GUILayout.Label("Client Count:", GUILayout.Height(height)); + GUILayout.Label("", GUILayout.Width(4)); + string newcount = GUILayout.TextField(_clientCount, 10, GUILayout.Width(width * .25f), GUILayout.Height(height)); + if (_clientCount != newcount) { + // Remove everything but numbers from our client count string. + _clientCount = newcount; + ValidateClientCount(); + } + } + GUILayout.EndHorizontal(); + } + } else { + + if (GUILayout.Button("Shutdown", GUILayout.Height(height))) { + _networkDebugStart.ShutdownAll(); + } + } + + GUILayout.EndVertical(); + } + } + GUILayout.EndArea(); + + GUI.skin = holdskin; + } + + private void StartHostWithClients(FusionBootstrap nds) { + int count; + try { + count = Convert.ToInt32(_clientCount); + } catch { + count = 0; + } + nds.StartHostPlusClients(count); + } + + private void StartServerWithClients(FusionBootstrap nds) { + int count; + try { + count = Convert.ToInt32(_clientCount); + } catch { + count = 0; + } + nds.StartServerPlusClients(count); + } + + private void StartMultipleClients(FusionBootstrap nds) { + int count; + try { + count = Convert.ToInt32(_clientCount); + } catch { + count = 0; + } + nds.StartMultipleClients(count); + } + + private void StartMultipleAutoClients(FusionBootstrap nds) { + int.TryParse(_clientCount, out int count); + nds.StartMultipleAutoClients(count); + } + + private void StartMultipleSharedClients(FusionBootstrap nds) { + int count; + try { + count = Convert.ToInt32(_clientCount); + } catch { + count = 0; + } + nds.StartMultipleSharedClients(count); + } + + // TODO Move to a utility + public static Dictionary ConvertEnumToNicifiedNameLookup(string prefix = null, Dictionary nonalloc = null) where T : System.Enum { + + System.Text.StringBuilder sb = new System.Text.StringBuilder(); + + if (nonalloc == null) { + nonalloc = new Dictionary(); + } else { + nonalloc.Clear(); + } + + var names = Enum.GetNames(typeof(T)); + var values = Enum.GetValues(typeof(T)); + for (int i = 0, cnt = names.Length; i < cnt; ++i) { + sb.Clear(); + if (prefix != null) { + sb.Append(prefix); + } + var name = names[i]; + for (int n = 0; n < name.Length; n++) { + // If this character is a capital and it is not the first character add a space. + // This is because we don't want a space before the word has even begun. + if (char.IsUpper(name[n]) == true && n != 0) { + sb.Append(" "); + } + + // Add the character to our new string + sb.Append(name[n]); + } + nonalloc.Add((T)values.GetValue(i), sb.ToString()); + } + return nonalloc; + } +#if UNITY_EDITOR + + public static T GetAsset(string Guid) where T : UnityEngine.Object { + var path = UnityEditor.AssetDatabase.GUIDToAssetPath(Guid); + if (string.IsNullOrEmpty(path)) { + return null; + } else { + return UnityEditor.AssetDatabase.LoadAssetAtPath(path); + } + } +#endif + } +} \ No newline at end of file diff --git a/Assets/Photon/Fusion/Runtime/FusionBootstrapDebugGUI.cs.meta b/Assets/Photon/Fusion/Runtime/FusionBootstrapDebugGUI.cs.meta new file mode 100644 index 00000000..e6fb1313 --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/FusionBootstrapDebugGUI.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 8ba5764f4714bd64ab9efab127938e77 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: + - BaseSkin: {fileID: 11400000, guid: e59b35dfeb4b6f54e9b2791b2a40a510, type: 2} + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Runtime/FusionBootstrapDebugGUISkin.guiskin b/Assets/Photon/Fusion/Runtime/FusionBootstrapDebugGUISkin.guiskin new file mode 100644 index 00000000..dd311393 --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/FusionBootstrapDebugGUISkin.guiskin @@ -0,0 +1,1560 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 1 + m_Script: {fileID: 12001, guid: 0000000000000000e000000000000000, type: 0} + m_Name: FusionBootstrapDebugGUISkin + m_EditorClassIdentifier: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_box: + m_Name: box + m_Normal: + m_Background: {fileID: 11001, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.79999995, g: 0.79999995, b: 0.79999995, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 6 + m_Right: 6 + m_Top: 6 + m_Bottom: 6 + m_Margin: + m_Left: 4 + m_Right: 4 + m_Top: 4 + m_Bottom: 4 + m_Padding: + m_Left: 4 + m_Right: 4 + m_Top: 4 + m_Bottom: 4 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 1 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_button: + m_Name: button + m_Normal: + m_Background: {fileID: 2800000, guid: c7d0c96f4d96542cd9a593afbcf2c535, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Hover: + m_Background: {fileID: 2800000, guid: 0bf88ab770bd548f2a1c9eecd1c8be06, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Active: + m_Background: {fileID: 2800000, guid: c90b937da87ec4183bab56bc75798697, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.9, g: 0.9, b: 0.9, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.9019608, g: 0.9019608, b: 0.9019608, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.9, g: 0.9, b: 0.9, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Border: + m_Left: 8 + m_Right: 8 + m_Top: 8 + m_Bottom: 8 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 1 + m_Bottom: 1 + m_Padding: + m_Left: 6 + m_Right: 6 + m_Top: 3 + m_Bottom: 3 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 5 + m_FontStyle: 0 + m_Alignment: 4 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_toggle: + m_Name: toggle + m_Normal: + m_Background: {fileID: 11018, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.89112896, g: 0.89112896, b: 0.89112896, a: 1} + m_Hover: + m_Background: {fileID: 11014, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Active: + m_Background: {fileID: 11013, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 11016, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.8901961, g: 0.8901961, b: 0.8901961, a: 1} + m_OnHover: + m_Background: {fileID: 11015, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnActive: + m_Background: {fileID: 11017, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 14 + m_Right: 0 + m_Top: 14 + m_Bottom: 0 + m_Margin: + m_Left: 4 + m_Right: 4 + m_Top: 4 + m_Bottom: 4 + m_Padding: + m_Left: 15 + m_Right: 0 + m_Top: 3 + m_Bottom: 0 + m_Overflow: + m_Left: -1 + m_Right: 0 + m_Top: -4 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_label: + m_Name: label + m_Normal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.9, g: 0.9, b: 0.9, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 4 + m_Right: 4 + m_Top: 4 + m_Bottom: 4 + m_Padding: + m_Left: 3 + m_Right: 3 + m_Top: 3 + m_Bottom: 3 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 5 + m_FontStyle: 0 + m_Alignment: 3 + m_WordWrap: 1 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_textField: + m_Name: textfield + m_Normal: + m_Background: {fileID: 11024, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.79999995, g: 0.79999995, b: 0.79999995, a: 1} + m_Hover: + m_Background: {fileID: 11026, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.9, g: 0.9, b: 0.9, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 11026, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnNormal: + m_Background: {fileID: 11025, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 4 + m_Right: 4 + m_Top: 4 + m_Bottom: 4 + m_Margin: + m_Left: 4 + m_Right: 4 + m_Top: 4 + m_Bottom: 4 + m_Padding: + m_Left: 3 + m_Right: 3 + m_Top: 3 + m_Bottom: 3 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 5 + m_FontStyle: 0 + m_Alignment: 4 + m_WordWrap: 0 + m_RichText: 0 + m_TextClipping: 1 + m_ImagePosition: 3 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_textArea: + m_Name: textarea + m_Normal: + m_Background: {fileID: 11024, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.9019608, g: 0.9019608, b: 0.9019608, a: 1} + m_Hover: + m_Background: {fileID: 11026, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0.79999995, g: 0.79999995, b: 0.79999995, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 11025, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 4 + m_Right: 4 + m_Top: 4 + m_Bottom: 4 + m_Margin: + m_Left: 4 + m_Right: 4 + m_Top: 4 + m_Bottom: 4 + m_Padding: + m_Left: 3 + m_Right: 3 + m_Top: 3 + m_Bottom: 3 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 1 + m_RichText: 0 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_window: + m_Name: window + m_Normal: + m_Background: {fileID: 2800000, guid: a7a6709449d68482c8c03bb546ddcaec, type: 3} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 1, g: 1, b: 1, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 8 + m_Right: 8 + m_Top: 8 + m_Bottom: 10 + m_Margin: + m_Left: 1 + m_Right: 1 + m_Top: 1 + m_Bottom: 1 + m_Padding: + m_Left: 3 + m_Right: 3 + m_Top: 3 + m_Bottom: 3 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 1 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: -18} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_horizontalSlider: + m_Name: horizontalslider + m_Normal: + m_Background: {fileID: 11009, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 3 + m_Right: 3 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 4 + m_Right: 4 + m_Top: 4 + m_Bottom: 4 + m_Padding: + m_Left: -1 + m_Right: -1 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: -2 + m_Bottom: -3 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 2 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 12 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_horizontalSliderThumb: + m_Name: horizontalsliderthumb + m_Normal: + m_Background: {fileID: 11011, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 11012, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 11010, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 4 + m_Right: 4 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 7 + m_Right: 7 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: -1 + m_Right: -1 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 2 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 12 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_verticalSlider: + m_Name: verticalslider + m_Normal: + m_Background: {fileID: 11021, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 3 + m_Bottom: 3 + m_Margin: + m_Left: 4 + m_Right: 4 + m_Top: 4 + m_Bottom: 4 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: -1 + m_Bottom: -1 + m_Overflow: + m_Left: -2 + m_Right: -3 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 12 + m_FixedHeight: 0 + m_StretchWidth: 0 + m_StretchHeight: 1 + m_verticalSliderThumb: + m_Name: verticalsliderthumb + m_Normal: + m_Background: {fileID: 11011, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 11012, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 11010, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 7 + m_Bottom: 7 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: -1 + m_Bottom: -1 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 12 + m_FixedHeight: 0 + m_StretchWidth: 0 + m_StretchHeight: 1 + m_horizontalScrollbar: + m_Name: horizontalscrollbar + m_Normal: + m_Background: {fileID: 11008, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 9 + m_Right: 9 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 4 + m_Right: 4 + m_Top: 1 + m_Bottom: 4 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 2 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 15 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_horizontalScrollbarThumb: + m_Name: horizontalscrollbarthumb + m_Normal: + m_Background: {fileID: 11007, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 6 + m_Right: 6 + m_Top: 6 + m_Bottom: 6 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 6 + m_Right: 6 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: -1 + m_Bottom: 1 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 13 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_horizontalScrollbarLeftButton: + m_Name: horizontalscrollbarleftbutton + m_Normal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_horizontalScrollbarRightButton: + m_Name: horizontalscrollbarrightbutton + m_Normal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_verticalScrollbar: + m_Name: verticalscrollbar + m_Normal: + m_Background: {fileID: 11020, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 9 + m_Bottom: 9 + m_Margin: + m_Left: 1 + m_Right: 4 + m_Top: 4 + m_Bottom: 4 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 1 + m_Bottom: 1 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 15 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_verticalScrollbarThumb: + m_Name: verticalscrollbarthumb + m_Normal: + m_Background: {fileID: 11019, guid: 0000000000000000e000000000000000, type: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 6 + m_Right: 6 + m_Top: 6 + m_Bottom: 6 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 6 + m_Bottom: 6 + m_Overflow: + m_Left: -1 + m_Right: -1 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 2 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 15 + m_FixedHeight: 0 + m_StretchWidth: 0 + m_StretchHeight: 1 + m_verticalScrollbarUpButton: + m_Name: verticalscrollbarupbutton + m_Normal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_verticalScrollbarDownButton: + m_Name: verticalscrollbardownbutton + m_Normal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_ScrollView: + m_Name: scrollview + m_Normal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 1 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_CustomStyles: + - m_Name: thumb + m_Normal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + - m_Name: leftbutton + m_Normal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + - m_Name: rightbutton + m_Normal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Hover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Active: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Focused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnNormal: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnHover: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnActive: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_OnFocused: + m_Background: {fileID: 0} + m_ScaledBackgrounds: [] + m_TextColor: {r: 0, g: 0, b: 0, a: 1} + m_Border: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Margin: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Overflow: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_Font: {fileID: 0} + m_FontSize: 0 + m_FontStyle: 0 + m_Alignment: 0 + m_WordWrap: 0 + m_RichText: 1 + m_TextClipping: 0 + m_ImagePosition: 0 + m_ContentOffset: {x: 0, y: 0} + m_FixedWidth: 0 + m_FixedHeight: 0 + m_StretchWidth: 1 + m_StretchHeight: 0 + m_Settings: + m_DoubleClickSelectsWord: 1 + m_TripleClickSelectsLine: 1 + m_CursorColor: {r: 1, g: 1, b: 1, a: 1} + m_CursorFlashSpeed: -1 + m_SelectionColor: {r: 1, g: 0.38403907, b: 0, a: 0.7} diff --git a/Assets/Photon/Fusion/Runtime/FusionBootstrapDebugGUISkin.guiskin.meta b/Assets/Photon/Fusion/Runtime/FusionBootstrapDebugGUISkin.guiskin.meta new file mode 100644 index 00000000..6c66f848 --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/FusionBootstrapDebugGUISkin.guiskin.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e59b35dfeb4b6f54e9b2791b2a40a510 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Runtime/FusionBootstrapDebugGUIStartButton.png b/Assets/Photon/Fusion/Runtime/FusionBootstrapDebugGUIStartButton.png new file mode 100644 index 00000000..602f3e70 Binary files /dev/null and b/Assets/Photon/Fusion/Runtime/FusionBootstrapDebugGUIStartButton.png differ diff --git a/Assets/Photon/Fusion/Runtime/FusionBootstrapDebugGUIStartButton.png.meta b/Assets/Photon/Fusion/Runtime/FusionBootstrapDebugGUIStartButton.png.meta new file mode 100644 index 00000000..980959b6 --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/FusionBootstrapDebugGUIStartButton.png.meta @@ -0,0 +1,122 @@ +fileFormatVersion: 2 +guid: c7d0c96f4d96542cd9a593afbcf2c535 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMasterTextureLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 0 + wrapV: 0 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 0 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Server + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + nameFileIdTable: {} + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Runtime/FusionBootstrapDebugGUIStartButtonDown.png b/Assets/Photon/Fusion/Runtime/FusionBootstrapDebugGUIStartButtonDown.png new file mode 100644 index 00000000..6b8c7345 Binary files /dev/null and b/Assets/Photon/Fusion/Runtime/FusionBootstrapDebugGUIStartButtonDown.png differ diff --git a/Assets/Photon/Fusion/Runtime/FusionBootstrapDebugGUIStartButtonDown.png.meta b/Assets/Photon/Fusion/Runtime/FusionBootstrapDebugGUIStartButtonDown.png.meta new file mode 100644 index 00000000..14c8ceb3 --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/FusionBootstrapDebugGUIStartButtonDown.png.meta @@ -0,0 +1,122 @@ +fileFormatVersion: 2 +guid: c90b937da87ec4183bab56bc75798697 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMasterTextureLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 0 + wrapV: 0 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 0 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Server + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + nameFileIdTable: {} + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Runtime/FusionBootstrapDebugGUIStartButtonOver.png b/Assets/Photon/Fusion/Runtime/FusionBootstrapDebugGUIStartButtonOver.png new file mode 100644 index 00000000..3ebc21ce Binary files /dev/null and b/Assets/Photon/Fusion/Runtime/FusionBootstrapDebugGUIStartButtonOver.png differ diff --git a/Assets/Photon/Fusion/Runtime/FusionBootstrapDebugGUIStartButtonOver.png.meta b/Assets/Photon/Fusion/Runtime/FusionBootstrapDebugGUIStartButtonOver.png.meta new file mode 100644 index 00000000..0d7ba28b --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/FusionBootstrapDebugGUIStartButtonOver.png.meta @@ -0,0 +1,122 @@ +fileFormatVersion: 2 +guid: 0bf88ab770bd548f2a1c9eecd1c8be06 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMasterTextureLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 0 + wrapV: 0 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 0 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Server + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + nameFileIdTable: {} + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Runtime/FusionBootstrapDebugGUIStartWindow.png b/Assets/Photon/Fusion/Runtime/FusionBootstrapDebugGUIStartWindow.png new file mode 100644 index 00000000..dc81709c Binary files /dev/null and b/Assets/Photon/Fusion/Runtime/FusionBootstrapDebugGUIStartWindow.png differ diff --git a/Assets/Photon/Fusion/Runtime/FusionBootstrapDebugGUIStartWindow.png.meta b/Assets/Photon/Fusion/Runtime/FusionBootstrapDebugGUIStartWindow.png.meta new file mode 100644 index 00000000..9c81199b --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/FusionBootstrapDebugGUIStartWindow.png.meta @@ -0,0 +1,122 @@ +fileFormatVersion: 2 +guid: a7a6709449d68482c8c03bb546ddcaec +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMasterTextureLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 0 + wrapV: 0 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 0 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 32 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 0 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Server + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + nameFileIdTable: {} + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Runtime/NetworkCharacterController.cs b/Assets/Photon/Fusion/Runtime/NetworkCharacterController.cs new file mode 100644 index 00000000..d60d2437 --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/NetworkCharacterController.cs @@ -0,0 +1,156 @@ +namespace Fusion { + using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; + using UnityEngine; + + [StructLayout(LayoutKind.Explicit)] + [NetworkStructWeaved(WORDS + 4)] + public unsafe struct NetworkCCData : INetworkStruct { + public const int WORDS = NetworkTRSPData.WORDS + 4; + public const int SIZE = WORDS * 4; + + [FieldOffset(0)] + public NetworkTRSPData TRSPData; + + [FieldOffset((NetworkTRSPData.WORDS + 0) * Allocator.REPLICATE_WORD_SIZE)] + int _grounded; + + [FieldOffset((NetworkTRSPData.WORDS + 1) * Allocator.REPLICATE_WORD_SIZE)] + Vector3Compressed _velocityData; + + public bool Grounded { + get => _grounded == 1; + set => _grounded = (value ? 1 : 0); + } + + public Vector3 Velocity { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _velocityData; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set => _velocityData = value; + } + } + + [DisallowMultipleComponent] + [RequireComponent(typeof(CharacterController))] + [NetworkBehaviourWeaved(NetworkCCData.WORDS)] + // ReSharper disable once CheckNamespace + public sealed unsafe class NetworkCharacterController : NetworkTRSP, INetworkTRSPTeleport, IBeforeAllTicks, IAfterAllTicks, IBeforeCopyPreviousState { + new ref NetworkCCData Data => ref ReinterpretState(); + + [Header("Character Controller Settings")] + public float gravity = -20.0f; + public float jumpImpulse = 8.0f; + public float acceleration = 10.0f; + public float braking = 10.0f; + public float maxSpeed = 2.0f; + public float rotationSpeed = 15.0f; + + Tick _initial; + CharacterController _controller; + + public Vector3 Velocity { + get => Data.Velocity; + set => Data.Velocity = value; + } + + public bool Grounded { + get => Data.Grounded; + set => Data.Grounded = value; + } + + public void Teleport(Vector3? position = null, Quaternion? rotation = null) { + _controller.enabled = false; + NetworkTRSP.Teleport(this, transform, position, rotation); + _controller.enabled = true; + } + + + public void Jump(bool ignoreGrounded = false, float? overrideImpulse = null) { + if (Data.Grounded || ignoreGrounded) { + var newVel = Data.Velocity; + newVel.y += overrideImpulse ?? jumpImpulse; + Data.Velocity = newVel; + } + } + + public void Move(Vector3 direction) { + var deltaTime = Runner.DeltaTime; + var previousPos = transform.position; + var moveVelocity = Data.Velocity; + + direction = direction.normalized; + + if (Data.Grounded && moveVelocity.y < 0) { + moveVelocity.y = 0f; + } + + moveVelocity.y += gravity * Runner.DeltaTime; + + var horizontalVel = default(Vector3); + horizontalVel.x = moveVelocity.x; + horizontalVel.z = moveVelocity.z; + + if (direction == default) { + horizontalVel = Vector3.Lerp(horizontalVel, default, braking * deltaTime); + } else { + horizontalVel = Vector3.ClampMagnitude(horizontalVel + direction * acceleration * deltaTime, maxSpeed); + transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(direction), rotationSpeed * Runner.DeltaTime); + } + + moveVelocity.x = horizontalVel.x; + moveVelocity.z = horizontalVel.z; + + _controller.Move(moveVelocity * deltaTime); + + Data.Velocity = (transform.position - previousPos) * Runner.TickRate; + Data.Grounded = _controller.isGrounded; + } + + public override void Spawned() { + _initial = default; + TryGetComponent(out _controller); + // Without disabling and re-enabling the CharacterController here, the first Move call will reset the position to 0,0,0 instead of + // keeping the position it was spawned at. Presumably disabling it clears some kind of internally cached "previous position" value + _controller.enabled = false; + _controller.enabled = true; + CopyToBuffer(); + } + + public override void Render() { + NetworkTRSP.Render(this, transform, false, false, false, ref _initial); + } + + void IBeforeAllTicks.BeforeAllTicks(bool resimulation, int tickCount) { + CopyToEngine(); + } + + void IAfterAllTicks.AfterAllTicks(bool resimulation, int tickCount) { + CopyToBuffer(); + } + + void IBeforeCopyPreviousState.BeforeCopyPreviousState() { + CopyToBuffer(); + } + + void Awake() { + TryGetComponent(out _controller); + } + + void CopyToBuffer() { + Data.TRSPData.Position = transform.position; + Data.TRSPData.Rotation = transform.rotation; + } + + void CopyToEngine() { + // CC must be disabled before resetting the transform state + _controller.enabled = false; + + // set position and rotation + transform.SetPositionAndRotation(Data.TRSPData.Position, Data.TRSPData.Rotation); + + // Re-enable CC + _controller.enabled = true; + } + } +} \ No newline at end of file diff --git a/Assets/Photon/Fusion/Runtime/NetworkCharacterController.cs.meta b/Assets/Photon/Fusion/Runtime/NetworkCharacterController.cs.meta new file mode 100644 index 00000000..3a882c7e --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/NetworkCharacterController.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 378411a89480da345945c2f888327a2d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Runtime/NetworkObjectProviderDefault.cs b/Assets/Photon/Fusion/Runtime/NetworkObjectProviderDefault.cs new file mode 100644 index 00000000..9032df6a --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/NetworkObjectProviderDefault.cs @@ -0,0 +1,97 @@ +namespace Fusion { + using System; + using System.Collections.Generic; + using UnityEngine; + using UnityEngine.SceneManagement; + using UnityEngine.Serialization; + + public class NetworkObjectProviderDefault : Fusion.Behaviour, INetworkObjectProvider { + /// + /// If enabled, the provider will delay acquiring a prefab instance if the scene manager is busy. + /// + [InlineHelp] + public bool DelayIfSceneManagerIsBusy = true; + + public virtual NetworkObjectAcquireResult AcquirePrefabInstance(NetworkRunner runner, in NetworkPrefabAcquireContext context, out NetworkObject instance) { + + instance = null; + + if (DelayIfSceneManagerIsBusy && runner.SceneManager.IsBusy) { + return NetworkObjectAcquireResult.Retry; + } + + NetworkObject prefab; + try { + prefab = runner.Prefabs.Load(context.PrefabId, isSynchronous: context.IsSynchronous); + } catch (Exception ex) { + Log.Error($"Failed to load prefab: {ex}"); + return NetworkObjectAcquireResult.Failed; + } + + if (!prefab) { + // this is ok, as long as Fusion does not require the prefab to be loaded immediately; + // if an instance for this prefab is still needed, this method will be called again next update + return NetworkObjectAcquireResult.Retry; + } + + instance = InstantiatePrefab(runner, prefab); + Assert.Check(instance); + + if (context.DontDestroyOnLoad) { + runner.MakeDontDestroyOnLoad(instance.gameObject); + } else { + runner.MoveToRunnerScene(instance.gameObject); + } + + runner.Prefabs.AddInstance(context.PrefabId); + return NetworkObjectAcquireResult.Success; + } + + public virtual void ReleaseInstance(NetworkRunner runner, in NetworkObjectReleaseContext context) { + var instance = context.Object; + + if (!context.IsBeingDestroyed) { + if (context.TypeId.IsPrefab) { + DestroyPrefabInstance(runner, context.TypeId.AsPrefabId, instance); + } else if (context.TypeId.IsSceneObject) { + DestroySceneObject(runner, context.TypeId.AsSceneObjectId, instance); + } else if (context.IsNestedObject) { + DestroyPrefabNestedObject(runner, instance); + } else { + throw new NotImplementedException($"Unknown type id {context.TypeId}"); + } + } + + if (context.TypeId.IsPrefab) { + runner.Prefabs.RemoveInstance(context.TypeId.AsPrefabId); + } + } + + public NetworkPrefabId GetPrefabId(NetworkRunner runner, NetworkObjectGuid prefabGuid) { + return runner.Prefabs.GetId(prefabGuid); + } + + protected virtual NetworkObject InstantiatePrefab(NetworkRunner runner, NetworkObject prefab) { + return Instantiate(prefab); + } + + protected virtual void DestroyPrefabInstance(NetworkRunner runner, NetworkPrefabId prefabId, NetworkObject instance) { + Destroy(instance.gameObject); + } + + protected virtual void DestroyPrefabNestedObject(NetworkRunner runner, NetworkObject instance) { + Destroy(instance.gameObject); + } + + protected virtual void DestroySceneObject(NetworkRunner runner, NetworkSceneObjectId sceneObjectId, NetworkObject instance) { + Destroy(instance.gameObject); + } + + void INetworkObjectProvider.Shutdown(NetworkRunner runner) { + var prefabs = runner.Prefabs; + if (prefabs?.Options.UnloadUnusedPrefabsOnShutdown == true) { + prefabs.UnloadUnreferenced(includeIncompleteLoads: true); + } + } + } +} \ No newline at end of file diff --git a/Assets/Photon/Fusion/Runtime/NetworkObjectProviderDefault.cs.meta b/Assets/Photon/Fusion/Runtime/NetworkObjectProviderDefault.cs.meta new file mode 100644 index 00000000..ae5ee355 --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/NetworkObjectProviderDefault.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 10533623ed629624c8acc6869f609bf2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Runtime/NetworkSceneManagerDefault.cs b/Assets/Photon/Fusion/Runtime/NetworkSceneManagerDefault.cs new file mode 100644 index 00000000..a0fcee0c --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/NetworkSceneManagerDefault.cs @@ -0,0 +1,846 @@ +namespace Fusion { + using System; + using System.Collections; + using System.Collections.Generic; + using System.Linq; + using UnityEngine; + using UnityEngine.SceneManagement; +#if FUSION_ENABLE_ADDRESSABLES && !FUSION_DISABLE_ADDRESSABLES + using System.Threading.Tasks; + using UnityEngine.AddressableAssets; + using UnityEngine.ResourceManagement.AsyncOperations; + using UnityEngine.ResourceManagement.ResourceProviders; +#endif + + public class NetworkSceneManagerDefault : Fusion.Behaviour, INetworkSceneManager { + /// + /// If enabled and there is an already loaded scene that matches what the scene manager has intended to load, + /// that scene will be used instead and load will be avoided. + /// + [InlineHelp] + [ToggleLeft] + public bool IsSceneTakeOverEnabled = true; + + /// + /// Should all scene load errors be logged into the console? If disabled, errors can still be retrieved via the + /// or . + /// + [InlineHelp] + [ToggleLeft] + public bool LogSceneLoadErrors = true; + + /// + /// If enabled the scenemanager despawns all runtime spawned prefab instances (not scene objects) before unloading a scene. + /// If the peer does not have StateAuthority over the object it is destroyed instead of despawned. + /// If disabled the destroy will be indirectly done via the scene unload from Unity however it will be async and might be delayed, + /// this can lead to the scene change being synchronized in an earlier tick than the destroys. + /// + [InlineHelp] + [ToggleLeft] + public bool DestroySpawnedPrefabsOnSceneUnload = true; + + /// + /// All the scenes loaded by all the managers. Used when is enabled. + /// + private static Dictionary _allOwnedScenes = new Dictionary(new FusionUnitySceneManagerUtils.SceneEqualityComparer()); + + /// + /// In multiple peer mode, each runner maintains its own scene where all the newly loaded scenes + /// are moved to. This is to make sure physics are properly sandboxed. + /// + private List _multiPeerSceneRoots = new List(); + private MultiPeerSceneRoot _multiPeerActiveRoot; + + /// + /// List of running coroutines. Only one is actually executed at a time. + /// + private List _runningCoroutines = new List(); + + /// + /// For remote clients, this manager first unloads old scenes then loads the new ones. It might happen that all + /// the current scenes need to be unloaded and in such case a temp scene needs to be created to ensure at least one + /// scene loaded at all times. + /// + private Scene _tempUnloadScene; + + /// + /// Scene used when Multiple Peer mode is used. Each loaded scene is merged into this one, allowing + /// for multiple runners to have separate cross-scene physics. + /// + public Scene MultiPeerScene { get; private set; } + + /// + /// Root for DontDestroyOnLoad objects. Instantiated on . + /// + public Transform MultiPeerDontDestroyOnLoadRoot { get; private set; } + + public NetworkRunner Runner { get; private set; } + + private bool IsMultiplePeer => Runner.Config.PeerMode == NetworkProjectConfig.PeerModes.Multiple; + private bool _isLoading; + + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] + static void ClearStatics() { + _allOwnedScenes.Clear(); + } + + static NetworkSceneManagerDefault() { + SceneManager.sceneUnloaded += (s) => _allOwnedScenes.Remove(s); + } + + #region INetworkSceneManager + + public virtual void Initialize(NetworkRunner runner) { + Log.TraceSceneManager(runner, $"Initialize with {runner}"); + +#if FUSION_ENABLE_ADDRESSABLES && !FUSION_DISABLE_ADDRESSABLES + LoadAddressableScenePathsAsync(); +#endif + + Debug.Assert(Runner == null); + Runner = runner; + + // assign an empty scene with a separate physics stage immediately, so that they won't spawn anything on the currently active scene + // an lose track of it + if (IsMultiplePeer) { + var scene = SceneManager.CreateScene($"{runner.name}_{runner.LocalPlayer}", + new CreateSceneParameters(LocalPhysicsMode.Physics2D | LocalPhysicsMode.Physics3D)); + Log.TraceSceneManager(Runner, $"Assigned an initial scene: {scene.Dump()}"); + + MultiPeerScene = scene; + MultiPeerDontDestroyOnLoadRoot = new GameObject("[DontDestroyOnLoad]").transform; + SceneManager.MoveGameObjectToScene(MultiPeerDontDestroyOnLoadRoot.gameObject, MultiPeerScene); + } + } + + public virtual void Shutdown() { + + Log.TraceSceneManager(Runner, $"Shutdown with {Runner}"); + + Runner = null; + + // clear owned scenes in case this manager is reused + var ownedScenes = _allOwnedScenes + .Where(x => x.Value == this) + .Select(x => x.Key) + .ToList(); + + foreach (var ownedScene in ownedScenes) { + _allOwnedScenes.Remove(ownedScene); + } + + _multiPeerSceneRoots.Clear(); + _multiPeerActiveRoot = null; + + MultiPeerDontDestroyOnLoadRoot = null; + + var sceneToUnload = MultiPeerScene; + MultiPeerScene = default; + + if (sceneToUnload.isLoaded) { + if (!sceneToUnload.CanBeUnloaded()) { + SceneManager.CreateScene($"FusionSceneManager_TempEmptyScene"); + } + SceneManager.UnloadSceneAsync(sceneToUnload); + } + } + + public virtual bool IsBusy { + get { + if (_isLoading) { + return true; + } + + if (IsMultiplePeer && _multiPeerSceneRoots.Count == 0) { + // nothing to spawn on + return true; + } + + return false; + } + } + + public virtual Scene MainRunnerScene { + get { + if (IsMultiplePeer) { + return MultiPeerScene; + } else { + return SceneManager.GetActiveScene(); + } + } + } + + public virtual bool IsRunnerScene(Scene scene) { + if (IsMultiplePeer) { + return scene == MultiPeerScene; + } else { + return true; + } + } + + public virtual bool TryGetPhysicsScene2D(out PhysicsScene2D scene2D) { + var mainScene = MainRunnerScene; + if (mainScene.IsValid()) { + scene2D = mainScene.GetPhysicsScene2D(); + return true; + } else { + scene2D = default; + return false; + } + } + + public virtual bool TryGetPhysicsScene3D(out PhysicsScene scene3D) { + var mainScene = MainRunnerScene; + if (mainScene.IsValid()) { + scene3D = mainScene.GetPhysicsScene(); + return true; + } else { + scene3D = default; + return false; + } + } + + public virtual void MakeDontDestroyOnLoad(GameObject obj) { + if (IsMultiplePeer) { + Debug.Assert(obj.transform.parent == null || obj.transform.parent == MultiPeerDontDestroyOnLoadRoot); + obj.transform.SetParent(MultiPeerDontDestroyOnLoadRoot, true); + } else { + DontDestroyOnLoad(obj); + } + } + + public bool MoveGameObjectToScene(GameObject gameObject, SceneRef sceneRef) { + if (IsMultiplePeer) { + // find the first matching scene ref + foreach (var root in _multiPeerSceneRoots) { + if (sceneRef != default && root.SceneRef != sceneRef) { + continue; + } + + if (sceneRef == default) { + // if scene ref is not specified, use the active root, if it exists + if (_multiPeerActiveRoot && root != _multiPeerActiveRoot) { + continue; + } + } + + if (gameObject.scene != MultiPeerScene) { + gameObject.transform.SetParent(null, true); + SceneManager.MoveGameObjectToScene(gameObject, MultiPeerScene); + + if (Application.isBatchMode == false) + Runner.AddVisibilityNodes(gameObject); + } + + gameObject.transform.SetParent(root.transform, true); + return true; + } + + return false; + } else { + if (sceneRef == default) { + // do nothing, all scenes belong to the runner + return true; + } + + for (int i = 0; i < SceneManager.sceneCount; ++i) { + var scene = SceneManager.GetSceneAt(i); + if (scene.isLoaded && GetSceneRef(scene.path) == sceneRef) { + SceneManager.MoveGameObjectToScene(gameObject, scene); + return true; + } + } + + return false; + } + } + + public virtual NetworkSceneAsyncOp LoadScene(SceneRef sceneRef, NetworkLoadSceneParameters parameters) { + Log.TraceSceneManager(Runner, $"Load scene {sceneRef} called with parameters: {parameters}"); + return NetworkSceneAsyncOp.FromCoroutine(sceneRef, StartTracedCoroutine(LoadSceneCoroutine(sceneRef, parameters))); + } + + public virtual NetworkSceneAsyncOp UnloadScene(SceneRef sceneRef) { + Log.TraceSceneManager(Runner, $"Unload scene {sceneRef} called"); + return NetworkSceneAsyncOp.FromCoroutine(sceneRef, StartTracedCoroutine(UnloadSceneCoroutine(sceneRef))); + } + + public virtual SceneRef GetSceneRef(string sceneNameOrPath) { + int buildIndex = FusionUnitySceneManagerUtils.GetSceneBuildIndex(sceneNameOrPath); + if (buildIndex >= 0) { + return SceneRef.FromIndex(buildIndex); + } + +#if FUSION_ENABLE_ADDRESSABLES && !FUSION_DISABLE_ADDRESSABLES + // this may be a blocking call due to WaitForCompletion being used internally + if (!TryGetAddressableScenes(out var addressableScenes)) { + Log.Error(this, $"Failed to resolve addressable scene paths, won't be able to resolve {sceneNameOrPath} or any other addressable scene."); + addressableScenes = Array.Empty(); + } + + var index = FusionUnitySceneManagerUtils.GetSceneIndex(addressableScenes, sceneNameOrPath); + if (index >= 0) { + return SceneRef.FromPath(addressableScenes[index]); + } +#endif + + return SceneRef.None; + } + + public SceneRef GetSceneRef(GameObject gameObject) { + if (IsMultiplePeer) { + if (gameObject.scene != MultiPeerScene) { + // not a part of this scene + return default; + } + + // find among scene roots + var sceneRoot = gameObject.transform.root; + foreach (var root in _multiPeerSceneRoots) { + if (root.transform == sceneRoot) { + return root.SceneRef; + } + } + + return default; + } else { + var scene = gameObject.scene; + return GetSceneRef(scene.path); + } + } + + public bool OnSceneInfoChanged(NetworkSceneInfo sceneInfo, NetworkSceneInfoChangeSource changeSource) { + // implement this method and return true if you want to handle scene info changes manually + return false; + } + + #endregion + + protected virtual IEnumerator LoadSceneCoroutine(SceneRef sceneRef, NetworkLoadSceneParameters sceneParams) { + Runner.InvokeSceneLoadStart(sceneRef); + + Scene scene = default; + + using (MakeLoadingScope()) { + Log.TraceSceneManager(Runner, $"LoadSceneCoroutine called with {sceneRef}, {sceneParams}"); + var localPhysicsMode = sceneParams.LocalPhysicsMode; + var loadSceneMode = sceneParams.LoadSceneMode; + + if (IsMultiplePeer) { + if (localPhysicsMode != LocalPhysicsMode.None) { + throw new ArgumentException($"Local physics mode is not supported in multiple peer mode", + nameof(sceneParams)); + } + + if (loadSceneMode == LoadSceneMode.Single) { + // all the current scenes need to be "unloaded", except possibly for the one + // that matches the sceneRef, if scene take over is enabled + loadSceneMode = LoadSceneMode.Additive; + + try { + foreach (var root in _multiPeerSceneRoots) { + Log.TraceSceneManager(Runner, $"Destroying scene {sceneRef} root {root.name} due to single-mode load"); + Destroy(root.gameObject); + } + + // wait for each root to be destroyed + foreach (var root in _multiPeerSceneRoots) { + while (root != null) { + yield return null; + } + } + } finally { + _multiPeerSceneRoots.Clear(); + } + } + } + else + { + if (DestroySpawnedPrefabsOnSceneUnload && loadSceneMode == LoadSceneMode.Single) + { + for (int i = 0; i < SceneManager.sceneCount; i++) { + // find the scene to unload + var sceneToBeUnloaded = SceneManager.GetSceneAt(i); // will be unloaded by Unity on scene load + var sceneRefToBeUnloaded = GetSceneRef(sceneToBeUnloaded.path); + + if (sceneRefToBeUnloaded != SceneRef.None) { + DestroyAllRuntimeSpawnedObjectsInScene(sceneToBeUnloaded, sceneRefToBeUnloaded); + } + } + } + } + + if (IsSceneTakeOverEnabled) { + // check if a loaded scene can be taken over + Scene candidate = FindSceneToTakeOver(sceneRef); + if (candidate.IsValid()) { + Log.TraceSceneManager(Runner, $"Taking over {sceneRef}: {candidate.Dump()}"); + + if (candidate.GetLocalPhysicsMode() != localPhysicsMode) { + throw new InvalidOperationException($"Tried to take over {candidate.Dump()} for {sceneRef}, but physics mode were different: {candidate.GetLocalPhysicsMode()} != {localPhysicsMode}"); + } + + scene = candidate; + MarkSceneAsOwned(sceneRef, candidate); + + if (loadSceneMode == LoadSceneMode.Single && !IsMultiplePeer) { + // need to unload scenes manually, multiple peer mode is handled at the beginning of this method, because + // it always needs to the manual cleanup for single mode + for (int i = 0; i < SceneManager.sceneCount; i++) { + var toUnload = SceneManager.GetSceneAt(i); + if (toUnload != candidate) { + Log.TraceSceneManager(Runner, $"Unloading {sceneRef} ({toUnload.Dump()}) due to single-mode take over of {candidate.Dump()}"); + yield return SceneManager.UnloadSceneAsync(toUnload); + } + } + } + } + } + + if (!scene.IsValid()) { +#if FUSION_ENABLE_ADDRESSABLES && !FUSION_DISABLE_ADDRESSABLES + if (loadSceneMode == LoadSceneMode.Single) { + // single mode unloads all the scenes anyway + _addressableOperations.Clear(); + } +#endif + + if (sceneRef.IsIndex) { + Log.TraceSceneManager(Runner, $"Loading scene {sceneRef} with build index {sceneRef.AsIndex} with mode {loadSceneMode}"); + var op = SceneManager.LoadSceneAsync(sceneRef.AsIndex, + new LoadSceneParameters(loadSceneMode, localPhysicsMode)); + if (op == null) { + throw new InvalidOperationException($"Scene not found: {sceneRef.AsIndex}"); + } + + Debug.Assert(SceneManager.sceneCount > 0); + scene = SceneManager.GetSceneAt(SceneManager.sceneCount - 1); + MarkSceneAsOwned(sceneRef, scene); + + Debug.Assert(scene.buildIndex == sceneRef.AsIndex); + + while (!op.isDone) { + OnLoadSceneProgress(sceneRef, op.progress); + yield return null; + } + } else { +#if FUSION_ENABLE_ADDRESSABLES && !FUSION_DISABLE_ADDRESSABLES + if (!TryGetAddressableScenes(out var addressableScenes)) { + Log.Error(this, $"Failed to resolve addressable scene paths, won't be able to resolve {sceneRef}"); + addressableScenes = Array.Empty(); + } + + string sceneAddress = null; + foreach (var path in addressableScenes) { + if (sceneRef.IsPath(path)) { + sceneAddress = path; + break; + } + } + + if (sceneAddress == null) { + throw new InvalidOperationException($"Unable to find addressable scene path for {sceneRef}"); + } + + Log.TraceSceneManager(Runner, $"Loading scene {sceneRef} from addressable: {sceneAddress}"); + +#if FUSION_ENABLE_ADDRESSABLES_LOCAL_PHYSICS + var loadSceneParameters = new LoadSceneParameters(loadSceneMode, localPhysicsMode); +#else + if (localPhysicsMode != LocalPhysicsMode.None) { + throw new InvalidOperationException($"{nameof(LocalPhysicsMode)} is not supported in this version of Addressables"); + } + var loadSceneParameters = loadSceneMode; +#endif + var op = Addressables.LoadSceneAsync(sceneAddress, loadSceneParameters); + + // to get the scene a callback is used, as it fires immediately when loading finished, + // compared to waiting for the coroutine to resume + scene = default; + op.Completed += op => { + if (op.Status == AsyncOperationStatus.Succeeded) { + scene = op.Result.Scene; + MarkSceneAsOwned(sceneRef, scene); + } + }; + + op.Destroyed += _ => { + // this will happen in MP mode when scenes are merged or when a scene is loaded in a single mode + if (_addressableOperations.Remove(sceneRef)) { + Log.TraceSceneManager(Runner, $"Destroyed Addressables op for {sceneRef}"); + } + }; + + _addressableOperations.Add(sceneRef, op); + + while (!op.IsDone) { + OnLoadSceneProgress(sceneRef, op.PercentComplete); + yield return null; + } + + if (!op.IsValid()) { + throw new InvalidOperationException($"Loading operation for {sceneRef} has been destroyed"); + } + + if (op.Status == AsyncOperationStatus.Failed) { + Addressables.Release(op); + throw new InvalidOperationException($"Failed to load scene from addressable: {sceneAddress}"); + } +#else + throw new InvalidOperationException($"SceneRef {sceneRef} points to an addressable scene, but FUSION_ENABLE_ADDRESSABLES is not defined"); +#endif + } + } + } + + yield return StartCoroutine(OnSceneLoaded(sceneRef, scene, sceneParams)); + } + + protected virtual IEnumerator UnloadSceneCoroutine(SceneRef sceneRef) { + Log.TraceSceneManager(Runner, $"UnloadSceneCoroutine called for {sceneRef}"); + + using (MakeLoadingScope()) { + if (IsMultiplePeer) { + // in multiple peer, the unload simply destroys the scene root + for (int i = 0; i < _multiPeerSceneRoots.Count; ++i) { + var root = _multiPeerSceneRoots[i]; + if (root.SceneRef == sceneRef) { + + if (root == _multiPeerActiveRoot) { + _multiPeerActiveRoot = null; + } + + _multiPeerSceneRoots.RemoveAt(i); + Log.TraceSceneManager(Runner, $"Destroying scene root {root.name} for {sceneRef}"); + + Log.TraceSceneManager(Runner, $"Started unloading {root.Scene.ToString()} for {sceneRef}"); + Destroy(root.gameObject); + while (root != null) { + yield return null; + } + + Log.TraceSceneManager(Runner, $"Finished unloading {root.Scene.ToString()} for {sceneRef}"); + yield break; + } + } + + throw new ArgumentOutOfRangeException($"Did not find a scene to unload: {sceneRef}", nameof(sceneRef)); + } else { + Scene sceneToUnload = default; + + // find the scene to unload + for (int i = 0; i < SceneManager.sceneCount; ++i) { + var scene = SceneManager.GetSceneAt(i); + if (GetSceneRef(scene.path) == sceneRef) { + sceneToUnload = scene; + break; + } + } + + if (!sceneToUnload.IsValid()) { + throw new ArgumentOutOfRangeException($"Did not find a scene to unload: {sceneRef}", nameof(sceneRef)); + } + + if (DestroySpawnedPrefabsOnSceneUnload) { + DestroyAllRuntimeSpawnedObjectsInScene(sceneToUnload, sceneRef); + } + + + Log.TraceSceneManager(Runner, $"Started unloading {sceneToUnload.Dump()} for {sceneRef}"); + + if (!sceneToUnload.CanBeUnloaded()) { + Log.Warn(Runner, $"Scene {sceneToUnload.Dump()} can't be unloaded for {sceneRef}, creating a temporary scene to unload it"); + Debug.Assert(!_tempUnloadScene.IsValid()); + _tempUnloadScene = SceneManager.CreateScene($"FusionSceneManager_TempEmptyScene"); + } + +#if FUSION_ENABLE_ADDRESSABLES && !FUSION_DISABLE_ADDRESSABLES + if (_addressableOperations.TryGetValue(sceneRef, out var asyncOp)) { + Log.TraceSceneManager(Runner, $"Unloading addressable scene {sceneToUnload.Dump()} for {sceneRef}"); + yield return Addressables.UnloadSceneAsync(asyncOp); + } else +#endif + { + Log.TraceSceneManager(Runner, $"Unloading {sceneToUnload.Dump()} for {sceneRef}"); + var op = SceneManager.UnloadSceneAsync(sceneToUnload); + if (op == null) { + throw new InvalidOperationException($"Failed to unload {sceneToUnload.Dump()}"); + } + + yield return op; + } + + Log.TraceSceneManager(Runner, $"Finished unloading {sceneToUnload.Dump()} for {sceneRef}"); + } + } + } + + protected virtual IEnumerator OnSceneLoaded(SceneRef sceneRef, Scene scene, NetworkLoadSceneParameters sceneParams) { + Log.TraceSceneManager(Runner, $"Finished loading, starting processing {scene.Dump()} for {sceneRef}"); + + var sceneObjects = scene.GetComponents(includeInactive: true, out var rootObjects); + + // since it is impossible to get objects in deterministic order (sibling index is 0 for all root objects in builds), + // scene objects need to be sorted by something that will guarantee the order + Array.Sort(sceneObjects, NetworkObjectSortKeyComparer.Instance); + + if (IsMultiplePeer) { + // create a root GO for all the gameObjects in the newly loaded scene + var newSceneRoot = new GameObject($"[{scene.name}]").AddComponent(); + newSceneRoot.SceneRef = sceneRef; + newSceneRoot.SceneHandle = scene.handle; + newSceneRoot.Scene = scene; + newSceneRoot.ScenePath = scene.path; + + SceneManager.MoveGameObjectToScene(newSceneRoot.gameObject, scene); + + foreach (var rootGameObject in rootObjects) { + rootGameObject.transform.SetParent(newSceneRoot.transform, true); + } + + // store the info + _multiPeerSceneRoots.Add(newSceneRoot); + + Log.TraceSceneManager(Runner, $"Merging {scene.Dump()} to {MultiPeerScene.Dump()} for {sceneRef}"); + SceneManager.MergeScenes(scene, MultiPeerScene); + + if (sceneParams.IsActiveOnLoad) { + _multiPeerActiveRoot = newSceneRoot; + } + } else { + if (sceneParams.IsActiveOnLoad) { + SceneManager.SetActiveScene(scene); + } + } + + // register scene objects; this will deactivate GameObjects for clients + // the additional loadId parameter is passed to ensure each scene load + // yields unique type ids for scene objects + Runner.RegisterSceneObjects(sceneRef, sceneObjects, loadId: sceneParams.LoadId); + + Log.TraceSceneManager(Runner, $"Finished loading & processing {scene.Dump()} for {sceneRef}"); + Runner.InvokeSceneLoadDone(new SceneLoadDoneArgs(sceneRef, sceneObjects, scene, rootObjects)); + yield break; + } + + protected virtual void OnLoadSceneProgress(SceneRef sceneRef, float progress) { + Log.TraceSceneManager(Runner, $"Loading scene progress {sceneRef} ({progress:P2})"); + } + + private void DestroyAllRuntimeSpawnedObjectsInScene(Scene scene, SceneRef sceneRef) { + Log.TraceSceneManager(Runner, $"destroying runtime spawned NetworkObjects in scene {scene.Dump()} for {sceneRef}"); + foreach (var networkObject in Runner.GetAllNetworkObjects()) { + // This exists to ensure all object meta is destroyed when unloading the scene to prevent objects from getting despawned and spawned again repeadetly on scene unload. + // Scene objects are ignored as they can't be spawned again when the scene is unloaded. + if (networkObject.gameObject.scene == scene && networkObject.NetworkTypeId.IsSceneObject == false) { + if (networkObject.HasStateAuthority) { + // despawn to ensure the object is immediately added to destroy queue. (Unity destroy callback is delayed until end of Update() + Runner.Despawn(networkObject); + } else { + Destroy(networkObject.gameObject); + } + } + } + } + + private Scene FindSceneToTakeOver(SceneRef sceneRef) { + for (int i = 0; i < SceneManager.sceneCount; ++i) { + var candidate = SceneManager.GetSceneAt(i); + if (!candidate.isLoaded) { + continue; + } + + if (GetSceneRef(candidate.path) != sceneRef) { + continue; + } + + if (_allOwnedScenes.ContainsKey(candidate)) { + continue; + } + + return candidate; + } + + return default; + } + + private ICoroutine StartTracedCoroutine(IEnumerator inner) { + var coro = new FusionCoroutine(inner); + + _runningCoroutines.Add(coro); + + coro.Completed += x => { + + if (LogSceneLoadErrors && x.Error != null) { + Log.Error(Runner, $"Failed async op: {x.Error.SourceException}"); + } + + // remove this one from the list + var index = _runningCoroutines.IndexOf((ICoroutine)x); + Debug.Assert(index == 0, "Expected the completed coroutine to be the first in the list"); + _runningCoroutines.RemoveAt(index); + + // start the next one + if (index < _runningCoroutines.Count) { + Log.TraceSceneManager(Runner, $"Starting enqueued coroutine {index} of {_runningCoroutines.Count}"); + StartCoroutine(_runningCoroutines[index]); + } + }; + + if (_runningCoroutines.Count == 1) { + // start immediately + StartCoroutine(coro); + } else { + Log.TraceSceneManager(Runner, $"Enqueued coroutine, there are already {_runningCoroutines.Count - 1} running"); + } + + return coro; + } + + protected LoadingScope MakeLoadingScope() { + return new LoadingScope(this); + } + + protected void MarkSceneAsOwned(SceneRef sceneRef, Scene scene) { + if (_allOwnedScenes.TryGetValue(scene, out var manager)) { + Log.Warn(Runner, $"Scene {scene.Dump()} (for {sceneRef}) already owned by {manager}"); + } else { + _allOwnedScenes.Add(scene, this); + } + } + + private NetworkSceneAsyncOp FailOp(SceneRef sceneRef, Exception exception) { + if (LogSceneLoadErrors) { + Log.Error(Runner, $"Failed with: {exception}"); + } + + return NetworkSceneAsyncOp.FromError(sceneRef, exception); + } + +#if FUSION_ENABLE_ADDRESSABLES && !FUSION_DISABLE_ADDRESSABLES + /// + /// A label by which addressable scenes can be discovered. + /// + [InlineHelp] + public string AddressableScenesLabel = "FusionScenes"; + + public NetworkSceneManagerDefault() { + _addressableScenesTask = new(() => GetAddressableScenes()); + } + + public Task LoadAddressableScenePathsAsync() { + return _addressableScenesTask.Value.Task; + } + + /// + /// Creates a task that resolves addressable scene paths. By default, this method locates all the addressable scenes with + /// label. Override this method to provide a custom implementation. For example, user + /// might want to have a pre-defined set of addressable scenes to avoid the wait: + /// + /// protected override GetAddressableScenesResult GetAddressableScenes() { + /// return Task.FromResult(new string[] { + /// "Assets/Scenes/AddressableScene1.unity", + /// "Assets/Scenes/AddressableScene2.unity", + /// }); + /// } + /// + /// + /// A task representing resolve operation and optionally a delegate to be invoked before the task is going to be + /// awaited synchronously + protected virtual GetAddressableScenesResult GetAddressableScenes() { + Log.TraceSceneManager(Runner, $"Locating addressable scenes with label: {AddressableScenesLabel}"); + + var tcs = new TaskCompletionSource(); + var result = Addressables.LoadResourceLocationsAsync(AddressableScenesLabel, typeof(SceneInstance)); + + result.Completed += op => { + try { + if (op.Status == AsyncOperationStatus.Failed) { + tcs.SetException(op.OperationException); + } else { + var paths = op.Result.Select(x => x.PrimaryKey).ToArray(); + Log.TraceSceneManager(Runner, $"Found {paths.Length} addressable scenes: {string.Join(", ", paths)}"); + tcs.SetResult(paths); + } + } finally { + Addressables.Release(op); + } + }; + + return new GetAddressableScenesResult { + Task = tcs.Task, + + // awaiting tasks synchronously does not play well with addressables; simply waiting will block the main thread and that's it. + // addressables *need* to have WaitForCompletion called + BeforeWaitForCompletion = () => { + if (result.IsValid()) { + result.WaitForCompletion(); + } + }, + }; + } + + /// + /// Returns the timeout for addressable scene paths to be resolved. By default, this method returns 10 seconds. + /// + /// + protected virtual TimeSpan GetAddressableScenePathsTimeout() { + return TimeSpan.FromSeconds(10); + } + + private bool TryGetAddressableScenes(out string[] addressableScenes) { + if (!_addressableScenesTask.IsValueCreated) { + Log.Warn(Runner, $"Going to block the thread in wait for addressable scene paths being resolved, call and await {nameof(LoadAddressableScenePathsAsync)} to avoid this."); + } + + var t = _addressableScenesTask.Value; + if (!t.Task.IsCompleted) { + t.BeforeWaitForCompletion?.Invoke(); + + if (!t.Task.Wait(GetAddressableScenePathsTimeout())) { + addressableScenes = null; + return false; + } + } + + addressableScenes = t.Task.Result; + return true; + } + + protected struct GetAddressableScenesResult { + public Task Task; + public Action BeforeWaitForCompletion; + public static implicit operator GetAddressableScenesResult(Task task) { + return new GetAddressableScenesResult { + Task = task, + }; + } + } + + private Lazy _addressableScenesTask; + private Dictionary> _addressableOperations = new(); +#endif + + protected sealed class MultiPeerSceneRoot : MonoBehaviour { + public SceneRef SceneRef; + public string ScenePath; + public int SceneHandle; + public Scene Scene; + } + + protected struct LoadingScope : IDisposable { + private readonly NetworkSceneManagerDefault _manager; + + public LoadingScope(NetworkSceneManagerDefault manager) { + _manager = manager; + _manager._isLoading = true; + Log.TraceSceneManager(manager.Runner, "Loading scope started"); + } + + public void Dispose() { + _manager._isLoading = false; + Log.TraceSceneManager(_manager.Runner, "Loading scope ended"); + } + } + } +} \ No newline at end of file diff --git a/Assets/Photon/Fusion/Runtime/NetworkSceneManagerDefault.cs.meta b/Assets/Photon/Fusion/Runtime/NetworkSceneManagerDefault.cs.meta new file mode 100644 index 00000000..373ed1c7 --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/NetworkSceneManagerDefault.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 90a93abf1a391964a94e5c139605105b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Runtime/RuntimeAssets.meta b/Assets/Photon/Fusion/Runtime/RuntimeAssets.meta new file mode 100644 index 00000000..61203fe3 --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/RuntimeAssets.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b39ad3f7538fa4d489eab1f1431116ce +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Runtime/RuntimeAssets/JetBrainsMono OFL.txt b/Assets/Photon/Fusion/Runtime/RuntimeAssets/JetBrainsMono OFL.txt new file mode 100644 index 00000000..0d0d19ae --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/RuntimeAssets/JetBrainsMono OFL.txt @@ -0,0 +1,93 @@ +Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/Assets/Photon/Fusion/Runtime/RuntimeAssets/JetBrainsMono OFL.txt.meta b/Assets/Photon/Fusion/Runtime/RuntimeAssets/JetBrainsMono OFL.txt.meta new file mode 100644 index 00000000..e701e0fb --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/RuntimeAssets/JetBrainsMono OFL.txt.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 01cd9d77222d74c74a540c1ec3a85c7a +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Runtime/RuntimeAssets/JetBrainsMono-Regular.ttf b/Assets/Photon/Fusion/Runtime/RuntimeAssets/JetBrainsMono-Regular.ttf new file mode 100644 index 00000000..65e8bfe4 Binary files /dev/null and b/Assets/Photon/Fusion/Runtime/RuntimeAssets/JetBrainsMono-Regular.ttf differ diff --git a/Assets/Photon/Fusion/Runtime/RuntimeAssets/JetBrainsMono-Regular.ttf.meta b/Assets/Photon/Fusion/Runtime/RuntimeAssets/JetBrainsMono-Regular.ttf.meta new file mode 100644 index 00000000..2e170a6c --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/RuntimeAssets/JetBrainsMono-Regular.ttf.meta @@ -0,0 +1,22 @@ +fileFormatVersion: 2 +guid: a4ed52b30ac954791b7f3c691f1a348e +TrueTypeFontImporter: + externalObjects: {} + serializedVersion: 4 + fontSize: 16 + forceTextureCase: -2 + characterSpacing: 0 + characterPadding: 1 + includeFontData: 1 + fontNames: + - JetBrains Mono + fallbackFontReferences: + - {fileID: 12800000, guid: 3c311f7773b444db7b0972edd68fd30a, type: 3} + customCharacters: + fontRenderingMode: 0 + ascentCalculationMode: 1 + useLegacyBoundsCalculation: 0 + shouldRoundAdvanceValue: 1 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Runtime/RuntimeAssets/Roboto-Regular.ttf b/Assets/Photon/Fusion/Runtime/RuntimeAssets/Roboto-Regular.ttf new file mode 100644 index 00000000..ddf4bfac Binary files /dev/null and b/Assets/Photon/Fusion/Runtime/RuntimeAssets/Roboto-Regular.ttf differ diff --git a/Assets/Photon/Fusion/Runtime/RuntimeAssets/Roboto-Regular.ttf.meta b/Assets/Photon/Fusion/Runtime/RuntimeAssets/Roboto-Regular.ttf.meta new file mode 100644 index 00000000..9bf4eb61 --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/RuntimeAssets/Roboto-Regular.ttf.meta @@ -0,0 +1,21 @@ +fileFormatVersion: 2 +guid: 5b587a998c6994e4a9a7e724729f140e +TrueTypeFontImporter: + externalObjects: {} + serializedVersion: 4 + fontSize: 16 + forceTextureCase: -2 + characterSpacing: 0 + characterPadding: 1 + includeFontData: 1 + fontNames: + - Roboto + fallbackFontReferences: [] + customCharacters: + fontRenderingMode: 0 + ascentCalculationMode: 1 + useLegacyBoundsCalculation: 0 + shouldRoundAdvanceValue: 1 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Runtime/Statistics.meta b/Assets/Photon/Fusion/Runtime/Statistics.meta new file mode 100644 index 00000000..67474152 --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/Statistics.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b11e317a3f6457f469fc57774081f48c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Runtime/Statistics/FusionNetworkObjectStatistics.cs b/Assets/Photon/Fusion/Runtime/Statistics/FusionNetworkObjectStatistics.cs new file mode 100644 index 00000000..d92f315e --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/Statistics/FusionNetworkObjectStatistics.cs @@ -0,0 +1,31 @@ +namespace Fusion.Statistics { +using UnityEngine; + + [RequireComponent(typeof(NetworkObject)), DisallowMultipleComponent] + [AddComponentMenu("Fusion/Statistics/Network Object Statistics")] + public class FusionNetworkObjectStatistics : MonoBehaviour { + [HideInInspector] + public NetworkObject NetworkObject; + + private void ToggleMonitoring(bool value) { + NetworkObject = GetComponent(); + if (NetworkObject.Runner && NetworkObject.Runner.IsRunning) { + if (NetworkObject.Runner.TryGetComponent(out var statistics)) { + if (statistics.MonitorNetworkObject(NetworkObject, this, value)) + return; + } + } + + // If not running or don't have the statistics manager or NO is already added on the graph, destroy for now. + Destroy(this); + } + + private void OnEnable() { + ToggleMonitoring(true); + } + + private void OnDisable() { + ToggleMonitoring(false); + } + } +} \ No newline at end of file diff --git a/Assets/Photon/Fusion/Runtime/Statistics/FusionNetworkObjectStatistics.cs.meta b/Assets/Photon/Fusion/Runtime/Statistics/FusionNetworkObjectStatistics.cs.meta new file mode 100644 index 00000000..c0e76c78 --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/Statistics/FusionNetworkObjectStatistics.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 526b142195d343c39a9184405f9ed5e0 +timeCreated: 1711133243 \ No newline at end of file diff --git a/Assets/Photon/Fusion/Runtime/Statistics/FusionNetworkObjectStatsGraph.cs b/Assets/Photon/Fusion/Runtime/Statistics/FusionNetworkObjectStatsGraph.cs new file mode 100644 index 00000000..e017bc93 --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/Statistics/FusionNetworkObjectStatsGraph.cs @@ -0,0 +1,92 @@ +namespace Fusion.Statistics { +using System; +using UnityEngine; +using UnityEngine.UI; + + [Flags] + internal enum NetworkObjectStat{ + InBandwidth = 1 << 0, + OutBandwidth = 1 << 1, + InPackets = 1 << 2, + OutPackets = 1 << 3, + AverageInPacketSize = 1 << 4, + AverageOutPacketSize = 1 << 5 + } + + public class FusionNetworkObjectStatsGraph : FusionStatsGraphBase { + [SerializeField] private Text _description; + private NetworkId _id; + private NetworkObjectStat _stat; + private FusionNetworkObjectStatsGraphCombine _combineParentGraph; + + public override void UpdateGraph(NetworkRunner runner, FusionStatisticsManager statisticsManager, ref DateTime now) { + AddValueToBuffer(GetNetworkObjectStatValue(statisticsManager), ref now); + } + + private float GetNetworkObjectStatValue(FusionStatisticsManager statisticsManager) { + if (statisticsManager.ObjectStatisticsManager.GetNetworkObjectStatistics(_id, out var snapshot)) { + switch (_stat) { + case NetworkObjectStat.InBandwidth: + return snapshot.InBandwidth; + case NetworkObjectStat.OutBandwidth: + return snapshot.OutBandwidth; + case NetworkObjectStat.InPackets: + return snapshot.InPackets; + case NetworkObjectStat.OutPackets: + return snapshot.OutPackets; + case NetworkObjectStat.AverageInPacketSize: + return snapshot.InBandwidth / Mathf.Max(1, snapshot.InPackets); + case NetworkObjectStat.AverageOutPacketSize: + return snapshot.OutBandwidth / Mathf.Max(1, snapshot.OutPackets); + } + } + + return -1; + } + + internal void SetupNetworkObjectStat(NetworkId id, NetworkObjectStat stat) { + _id = id; + _stat = stat; + _description.text = _stat.ToString(); + + string valueTextFormat; + float threshold1 = 0, threshold2 = 0, threshold3 = 0; + float valueTextMultiplier = 1; + bool ignoreZeroOnAverage = false, ignoreZeroOnBuffer = false; + int accumulateTimeMs = 0; + + switch (stat) { + + case NetworkObjectStat.InBandwidth: + case NetworkObjectStat.OutBandwidth: + valueTextFormat = "{0:0} B"; + accumulateTimeMs = 1000; + _description.text += " (Per second)"; + break; + case NetworkObjectStat.AverageInPacketSize: + case NetworkObjectStat.AverageOutPacketSize: + valueTextFormat = "{0:0} B"; + ignoreZeroOnAverage = true; + ignoreZeroOnBuffer = true; + break; + + case NetworkObjectStat.InPackets: + case NetworkObjectStat.OutPackets: + valueTextFormat = "{0:0}"; + accumulateTimeMs = 1000; + _description.text += " (Per second)"; + break; + + default: + valueTextFormat = "{0:0}"; + break; + } + + SetValueTextFormat(valueTextFormat); + SetValueTextMultiplier(valueTextMultiplier); + SetThresholds(threshold1, threshold2, threshold3); + SetIgnoreZeroValues(ignoreZeroOnAverage, ignoreZeroOnBuffer); + Initialize(accumulateTimeMs); + } + } +} \ No newline at end of file diff --git a/Assets/Photon/Fusion/Runtime/Statistics/FusionNetworkObjectStatsGraph.cs.meta b/Assets/Photon/Fusion/Runtime/Statistics/FusionNetworkObjectStatsGraph.cs.meta new file mode 100644 index 00000000..e3b512b2 --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/Statistics/FusionNetworkObjectStatsGraph.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 6c65e6f8cc0e421195e5884e060e7ead +timeCreated: 1711029971 \ No newline at end of file diff --git a/Assets/Photon/Fusion/Runtime/Statistics/FusionNetworkObjectStatsGraphCombine.cs b/Assets/Photon/Fusion/Runtime/Statistics/FusionNetworkObjectStatsGraphCombine.cs new file mode 100644 index 00000000..32472942 --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/Statistics/FusionNetworkObjectStatsGraphCombine.cs @@ -0,0 +1,139 @@ +namespace Fusion.Statistics { +using UnityEngine; +using System.Collections.Generic; +using UnityEngine.UI; +using System; + + public class FusionNetworkObjectStatsGraphCombine : MonoBehaviour { + + [SerializeField] private Text _titleText; + [SerializeField] private Dropdown _statDropdown; + [SerializeField] private NetworkObjectStat _statsToRender; + [SerializeField] private RectTransform _rect; + [SerializeField] private RectTransform _combinedGraphRender; + [SerializeField] private Button _toggleButton; + + private float _headerHeight = 50; + private float _graphHeight = 150; + + private Dictionary _statsGraphs; + [SerializeField] + private FusionNetworkObjectStatsGraph _statsGraphPrefab; + + private ContentSizeFitter _parentContentSizeFitter; + + /// + /// Gets the unique identifier of the network object. + /// + /// + /// The network object identifier. + /// + public NetworkId NetworkObjectID => _networkObject.Id; + + private NetworkObject _networkObject; + private FusionStatistics _fusionStatistics; + private FusionNetworkObjectStatistics _objectStatisticsInstance; + + public void SetupNetworkObject(NetworkObject networkObject, FusionStatistics fusionStatistics, FusionNetworkObjectStatistics objectStatisticsInstance) { + _networkObject = networkObject; + _fusionStatistics = fusionStatistics; + _objectStatisticsInstance = objectStatisticsInstance; + } + + private void Start() { + _statsGraphs = new Dictionary(); + _parentContentSizeFitter = GetComponentInParent(); + + List options = new List(); + + options.Add(new Dropdown.OptionData("Toggle Stats")); + + foreach (var option in Enum.GetNames(typeof(NetworkObjectStat))) { + options.Add(new Dropdown.OptionData(option)); + } + + _statDropdown.options = options; + + _statDropdown.onValueChanged.AddListener(OnDropDownChanged); + + UpdateHeight(); + + _titleText.text = _networkObject.Name; + } + + private void OnDropDownChanged(int arg0) { + if (arg0 <= 0) return; // No stat selected. + arg0--; // Remove the first label + + NetworkObjectStat stat = (NetworkObjectStat)(1 << arg0); + + if ((_statsToRender & stat) == stat) { + _statsToRender &= ~stat; // Removed the flag + DestroyStatGraph(stat); + } else { + _statsToRender |= stat; // Set the flag + InstantiateStatGraph(stat); + } + + UpdateHeight(); + + // Set the first label again. + _statDropdown.SetValueWithoutNotify(0); + } + + private void InstantiateStatGraph(NetworkObjectStat stat) { + FusionNetworkObjectStatsGraph graph = Instantiate(_statsGraphPrefab, _combinedGraphRender); + graph.SetupNetworkObjectStat(NetworkObjectID, stat); + _statsGraphs.Add(stat, graph); + } + + private void DestroyStatGraph(NetworkObjectStat stat) { + _statsGraphs[stat].gameObject.SetActive(false); + Destroy(_statsGraphs[stat].gameObject); + _statsGraphs.Remove(stat); + } + + private void UpdateHeight(float overrideValue = -1) { + var sizeDelta = _rect.sizeDelta; + var height = overrideValue >= 0 ? overrideValue : _headerHeight + _statsGraphs.Count * _graphHeight; + _rect.sizeDelta = new Vector2(sizeDelta.x,height); + + // Need to refresh vertical scroll + _parentContentSizeFitter.enabled = false; + _parentContentSizeFitter.enabled = true; + } + + private void OnDisable() { + if (_statsGraphs == null) return; + foreach (var graph in _statsGraphs.Values) { + graph.gameObject.SetActive(false); + } + } + + private void OnEnable() { + if (_statsGraphs == null) return; + foreach (var graph in _statsGraphs.Values) { + graph.gameObject.SetActive(true); + } + } + + public void ToggleRenderDisplay() { + var active = _combinedGraphRender.gameObject.activeSelf; + _combinedGraphRender.gameObject.SetActive(!active); + + if (active) { + OnDisable(); + UpdateHeight(_headerHeight); + _toggleButton.transform.rotation = Quaternion.Euler(0, 0, 90); + } else { + OnEnable(); + UpdateHeight(); + _toggleButton.transform.rotation = Quaternion.identity; + } + } + + public void DestroyCombinedGraph() { + _fusionStatistics.MonitorNetworkObject(_networkObject, _objectStatisticsInstance, false); + } + } +} \ No newline at end of file diff --git a/Assets/Photon/Fusion/Runtime/Statistics/FusionNetworkObjectStatsGraphCombine.cs.meta b/Assets/Photon/Fusion/Runtime/Statistics/FusionNetworkObjectStatsGraphCombine.cs.meta new file mode 100644 index 00000000..378881cc --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/Statistics/FusionNetworkObjectStatsGraphCombine.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 19334f7ed30f4e8ea9f48a51534f09dd +timeCreated: 1711030624 \ No newline at end of file diff --git a/Assets/Photon/Fusion/Runtime/Statistics/FusionStatistics.cs b/Assets/Photon/Fusion/Runtime/Statistics/FusionStatistics.cs new file mode 100644 index 00000000..a299ab7c --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/Statistics/FusionStatistics.cs @@ -0,0 +1,262 @@ +namespace Fusion.Statistics { + using System; + using UnityEngine; +using System.Collections.Generic; +using System.Linq; +using UnityEngine.EventSystems; +using UnityEngine.Profiling; + using UnityEngine.Serialization; + + [RequireComponent(typeof(NetworkRunner))] + [DisallowMultipleComponent] + [AddComponentMenu("Fusion/Statistics/Fusion Statistics")] + public class FusionStatistics : SimulationBehaviour, ISpawned { + internal List ActiveGraphs => _statsGraph; + + // Setup prefabs + private GameObject _statsCanvasPrefab; + private FusionNetworkObjectStatsGraphCombine _objectGraphCombinePrefab; + + private const string STATS_CANVAS_PREFAB_PATH = "FusionStatsResources/FusionStatsRenderPanel"; + private const string STATS_OBJECT_COMBINE_PREFAB_PATH = "FusionStatsResources/NetworkObjectStatistics"; + + private List _statsGraph; + private FusionStatsPanelHeader _header; + private FusionStatsConfig _config; + private FusionStatsCanvas _statsCanvas; + private GameObject _statsPanelObject; + private Dictionary _objectStatsGraphCombines; + + [InlineHelp] + [ExpandableEnum] + [SerializeField] private RenderSimStats _statsEnabled; + + [InlineHelp] + [SerializeField] private CanvasAnchor _canvasAnchor = CanvasAnchor.TopRight; + + + [FormerlySerializedAs("_statsConfig")] [SerializeField] + [Header("Custom configuration to override default values.\nSelect only one stat flag per configuration.")] + private List _statsCustomConfig = new List(); + + internal List StatsCustomConfig => _statsCustomConfig; + + /// + /// Gets a value indicating whether the statistics panel is active. + /// + public bool IsPanelActive => _statsPanelObject != false; + + [System.Serializable] + public struct FusionStatisticsStatCustomConfig { + public RenderSimStats Stat; + public float Threshold1; + public float Threshold2; + public float Threshold3; + public bool IgnoreZeroOnBuffer; + public bool IgnoreZeroOnAverageCalculation; + public int AccumulateTimeMs; + } + + private void Awake() { + _statsGraph = new List(); + _statsCanvasPrefab = Resources.Load(STATS_CANVAS_PREFAB_PATH); + _objectGraphCombinePrefab = Resources.Load(STATS_OBJECT_COMBINE_PREFAB_PATH); + + if (_statsCanvasPrefab == null || _objectGraphCombinePrefab == null) { + Log.Error($"Error loading the required assets for Fusion Statistics, destroying stats instance. Make sure that the following paths are valid for the Fusion Statistics resource assets: \n 1. {STATS_CANVAS_PREFAB_PATH} \n 2. {STATS_OBJECT_COMBINE_PREFAB_PATH}"); + Destroy(this); + } + } + + void ISpawned.Spawned() { + SetupStatisticsPanel(); + } + + /// + /// Sets the custom configuration for Fusion Statistics. + /// + /// The list of custom configurations for Fusion Statistics. + public void SetStatsCustomConfig(List customConfig) { + if (customConfig == default) { + Log.Warn("Trying to set a null Fusion Statistics custom stats config"); + return; + } + + _statsCustomConfig = customConfig; + ApplyCustomConfig(); + } + + /// + /// Sets the anchor position of the Fusion Statistics canvas. + /// + /// The anchor position of the canvas (TopLeft or TopRight). + public void SetCanvasAnchor(CanvasAnchor anchor) { + _canvasAnchor = anchor; + if (_statsCanvas == false) return; + _statsCanvas.SetCanvasAnchor(anchor); + } + + private void ApplyCustomConfig() { + if (!_header) return; + _header.ApplyStatsConfig(_statsCustomConfig); + } + + /// + /// Called from a custom editor script. + /// Will update any editor information into the fusion statistics. + /// + public void OnEditorChange() { + RenderEnabledStats(); + ApplyCustomConfig(); + SetCanvasAnchor(_canvasAnchor); + } + + private void RenderEnabledStats() { + if (IsPanelActive == false) return; + _header.SetStatsToRender(_statsEnabled); + } + + internal void UpdateStatsEnabled(RenderSimStats stats) { + _statsEnabled = stats; + } + + /// + /// Sets up the statistics panel for Fusion statistic tracking. + /// + public void SetupStatisticsPanel() { + if (IsPanelActive) return; + + // Was not registered on the Runner yet + if (Runner == null) { + var runner = GetComponent(); + + if (runner.IsRunning == false) { + Log.Warn($"Network Runner on ({runner.gameObject}) is not yet running."); + return; + } + + runner.AddGlobal(this); + // Return because when spawned is called the setup method will be called again. + return; + } + + _objectStatsGraphCombines = new Dictionary(); + + _statsPanelObject = Instantiate(_statsCanvasPrefab, transform); + _statsCanvas = _statsPanelObject.GetComponentInChildren(); + _statsCanvas.SetupStatsCanvas(this, _canvasAnchor, DestroyStatisticsPanel); + _header = _statsPanelObject.GetComponentInChildren(); + _header.SetupHeader(Runner.LocalPlayer.ToString(), this); + _config = _statsPanelObject.GetComponentInChildren(true); + + _statsPanelObject.AddComponent(); + ApplyCustomConfig(); + + Runner.AddVisibilityNodes(_statsPanelObject); + + if (_statsEnabled != 0) + RenderEnabledStats(); + + // Setup Event system + if (!EventSystem.current) { + Log.Debug("Fusion Statistics: No event system detected, creating one."); + new GameObject("EventSystem-FusionStatistics", typeof(EventSystem), typeof(StandaloneInputModule)); + } + } + + /// + /// Sets the world anchor for Fusion Statistics. Set null to return to screen space overlay. + /// + /// The FusionStatsWorldAnchor component that defines the anchor object. Null to return to screen space overlay. + /// The scale of the statistics panel. + public void SetWorldAnchor(FusionStatsWorldAnchor anchor, float scale) { + _config.SetWorldCanvasScale(scale); + + if (anchor == null) { + _config.ResetToCanvasAnchor(); + } else { + _config.SetWorldAnchor(anchor.transform); + } + } + + /// + /// Destroys the statistics panel. + /// + public void DestroyStatisticsPanel() { + var keys = _objectStatsGraphCombines?.Keys.ToArray(); + if (keys != null) { + foreach (var fusionNetworkObjectStatistics in keys) { + MonitorNetworkObject(fusionNetworkObjectStatistics.NetworkObject, fusionNetworkObjectStatistics, false); + } + } + + _objectStatsGraphCombines?.Clear(); + _statsGraph.Clear(); + + Destroy(_statsPanelObject); + _statsPanelObject = null; + + if (Runner) { + if (Runner.TryGetFusionStatistics(out var statisticsManager)) { + statisticsManager.ObjectStatisticsManager.ClearMonitoredNetworkObjects(); + } + } + } + + public bool MonitorNetworkObject(NetworkObject networkObject, FusionNetworkObjectStatistics objectStatisticsInstance, bool monitor) { + + if (Runner.TryGetFusionStatistics(out var statisticsManager)) { + statisticsManager.ObjectStatisticsManager.MonitorNetworkObjectStatistics(networkObject.Id, monitor); + } + + if (monitor) { + + // If Id already monitored on the stats, return false to destroy the object statistics instance. + if (_objectStatsGraphCombines.ContainsKey(objectStatisticsInstance)) + return false; + + var graphCombine = Instantiate(_objectGraphCombinePrefab, _header.ContentRect); + graphCombine.SetupNetworkObject(networkObject, this, objectStatisticsInstance); + _objectStatsGraphCombines.Add(objectStatisticsInstance, graphCombine); + } else { + + if (_objectStatsGraphCombines.Remove(objectStatisticsInstance, out var graphCombine)) { + Destroy(graphCombine.gameObject); + Destroy(objectStatisticsInstance); + } + } + + return true; + } + + void UpdateAllGraphs(FusionStatisticsManager statisticsManager) { + var now = DateTime.Now; + foreach (var statsGraphBase in _statsGraph) { + statsGraphBase.UpdateGraph(Runner, statisticsManager, ref now); + } + } + + public void RegisterGraph(FusionStatsGraphBase graph) { + _statsGraph.Add(graph); + } + + public void UnregisterGraph(FusionStatsGraphBase graph) { + _statsGraph.Remove(graph); + } + + private void Update() { + // Safety exit + if (!Runner) return; + + + Profiler.BeginSample("Fusion Statistics Update Graph"); + + // Collect and update + if (Runner.TryGetFusionStatistics(out var statisticsManager)) { + UpdateAllGraphs(statisticsManager); + } + + Profiler.EndSample(); + } + } +} \ No newline at end of file diff --git a/Assets/Photon/Fusion/Runtime/Statistics/FusionStatistics.cs.meta b/Assets/Photon/Fusion/Runtime/Statistics/FusionStatistics.cs.meta new file mode 100644 index 00000000..39ff1ad3 --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/Statistics/FusionStatistics.cs.meta @@ -0,0 +1,15 @@ +fileFormatVersion: 2 +guid: 9c6080750a33c9d428004642d0e07dd6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: + - _statsCanvasPrefab: {fileID: 6292187590335715622, guid: 367c5caf25960d8419724c6c96708538, + type: 3} + - _objectGraphCombinePrefab: {fileID: 7935946544523926548, guid: 7cc7f808e527f6e4f98257acbf47ad87, + type: 3} + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Runtime/Statistics/FusionStatsCanvas.cs b/Assets/Photon/Fusion/Runtime/Statistics/FusionStatsCanvas.cs new file mode 100644 index 00000000..cae2cd9e --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/Statistics/FusionStatsCanvas.cs @@ -0,0 +1,207 @@ +namespace Fusion.Statistics { + using System; + using System.Collections.Generic; + using UnityEngine; + using UnityEngine.Events; + using UnityEngine.EventSystems; + using UnityEngine.UI; + + /// + /// The side to attach the statistics panel anchor. + /// + public enum CanvasAnchor {TopLeft, TopRight} + + + public class FusionStatsCanvas : MonoBehaviour, IDragHandler, IEndDragHandler, IBeginDragHandler { + [Header("General References")] + [SerializeField] private Canvas _canvas; + [SerializeField] private CanvasScaler _canvasScaler; + [SerializeField] private RectTransform _canvasPanel; + + [Space] [Header("Panel References")] + [SerializeField] private RectTransform _contentPanel; + [SerializeField] private RectTransform _contentContainer; + [SerializeField] private RectTransform _bottomPanel; + [SerializeField] private FusionStatsPanelHeader _header; + + [Space] [Header("Misc")] + [SerializeField] private Button _hideButton; + [SerializeField] private Button _closeButton; + + [Space] [Header("World Anchor Panel Settings")] + [SerializeField] private FusionStatsConfig _config; + + private bool _isColapsed => !_contentPanel.gameObject.activeSelf; + + private CanvasAnchor _anchor; + + private enum DragMode {None, DragCanvas, ResizeContent} + + private DragMode _dragMode; + private static int _statsCanvasActiveCount = 0; + + internal void SetupStatsCanvas(FusionStatistics fusionStatistics, CanvasAnchor canvasAnchor, UnityAction closeButtonAction) { + _anchor = canvasAnchor; + _canvasPanel.anchoredPosition = GetDefinedAnchorPosition(); + var maxOffsetMultiplier = Mathf.Min(_statsCanvasActiveCount, 3); + _canvasPanel.anchoredPosition += Vector2.down * (FusionStatisticsHelper.DEFAULT_HEADER_HEIGHT * maxOffsetMultiplier); + + //Setup buttons + _closeButton.onClick.RemoveAllListeners(); + _closeButton.onClick.AddListener(closeButtonAction); + + _hideButton.onClick.RemoveAllListeners(); + _hideButton.onClick.AddListener(ToggleHide); + + // Setup runner statistics ref + _config.SetupStatisticReference(fusionStatistics); + } + + public void OnBeginDrag(PointerEventData eventData) { + if (_config.IsWorldAnchored) return; + + if (_dragMode != DragMode.None) return; // Already dragging. + var dragBeginPos = eventData.pressPosition; + var rectT = _bottomPanel; + dragBeginPos = rectT.InverseTransformPoint(dragBeginPos); + var resize = rectT.rect.Contains(dragBeginPos) && eventData.button == PointerEventData.InputButton.Right; + _dragMode = resize ? DragMode.ResizeContent : DragMode.DragCanvas; + } + + public void OnDrag(PointerEventData eventData) { + if (_config.IsWorldAnchored) return; + + switch (_dragMode) { + case DragMode.DragCanvas: + _canvasPanel.anchoredPosition += eventData.delta / _canvas.scaleFactor; + break; + case DragMode.ResizeContent: + UpdateContentContainerHeight(eventData.delta.y / _canvas.scaleFactor); + break; + } + } + + public void OnEndDrag(PointerEventData eventData) { + if (_config.IsWorldAnchored) return; + + if (CheckDraggableRectVisibility(_canvasPanel) == false) + SnapPanelBackToOriginPos(); + + if (_dragMode == DragMode.ResizeContent) { + var currentSize = _contentPanel.sizeDelta.y; + var visibleGraphHeight = 0f; + var remaining = 0f; + for (int i = 0; i < _contentContainer.childCount; i++) { + var prevHeight = visibleGraphHeight; + visibleGraphHeight += ((RectTransform)_contentContainer.GetChild(i)).sizeDelta.y + 10; + + if (visibleGraphHeight >= currentSize) { + if (currentSize - prevHeight < visibleGraphHeight - currentSize) { + remaining = currentSize - prevHeight; + } else { + remaining = -(visibleGraphHeight - currentSize); + } + + break; + } + } + UpdateContentContainerHeight(remaining); + } + + _dragMode = DragMode.None; + } + + public void SnapPanelBackToOriginPos() { + _canvasPanel.anchoredPosition = GetDefinedAnchorPosition(); + } + + private void UpdateContentContainerHeight(float yDelta) { + var height = _contentPanel.sizeDelta.y; + var targetHeight = height - yDelta; + SetContentPanelHeight(targetHeight); + } + + internal void ToggleHide() { + var active = _contentPanel.gameObject.activeSelf; + _hideButton.transform.rotation = active ? Quaternion.Euler(0, 0, 90) : Quaternion.identity; + _contentPanel.gameObject.SetActive(!active); + _bottomPanel.gameObject.SetActive(!active); + } + + // Better offscreen check for later. + private bool CheckDraggableRectVisibility(RectTransform rectTransform) { + var anchoredPos = rectTransform.anchoredPosition; + var size = rectTransform.rect.size; + + if (Mathf.Abs(anchoredPos.x) >= _canvasScaler.referenceResolution.x * .5f + size.x * .5f) + return false; + + // anchor is on top. + if (anchoredPos.y >= _canvasScaler.referenceResolution.y * .5f + size.y || anchoredPos.y <= -_canvasScaler.referenceResolution.y * .5f) + return false; + + return true; + } + + private void SetContentPanelHeight(float value) { + if (value < FusionStatisticsHelper.DEFAULT_GRAPH_HEIGHT) { + value = FusionStatisticsHelper.DEFAULT_GRAPH_HEIGHT; + }else { + var maxHeight = Screen.height / _canvas.scaleFactor - 2 * FusionStatisticsHelper.DEFAULT_HEADER_HEIGHT; + if (value > maxHeight) { + value = maxHeight; + } + } + + _contentPanel.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, value); + _contentPanel.gameObject.SetActive(false); + _contentPanel.gameObject.SetActive(true); + } + + private void AdaptContentHeightToGraphs() { + var neededHeight = 0f; + for (int i = 0; i < _contentContainer.childCount; i++) { + neededHeight += ((RectTransform)_contentContainer.GetChild(i)).sizeDelta.y + 10; // +10 spacing + } + + float maxHeight = Screen.height / _canvas.scaleFactor - 2 * FusionStatisticsHelper.DEFAULT_HEADER_HEIGHT; + + if (neededHeight > maxHeight) { + neededHeight = maxHeight; + } + if (neededHeight < FusionStatisticsHelper.DEFAULT_GRAPH_HEIGHT) { + neededHeight = FusionStatisticsHelper.DEFAULT_GRAPH_HEIGHT; + } + + SetContentPanelHeight(neededHeight); + } + + private void OnEnable() { + _statsCanvasActiveCount++; + _header.OnRenderStatsUpdate += AdaptContentHeightToGraphs; + } + + private void OnDisable() { + _statsCanvasActiveCount--; + _header.OnRenderStatsUpdate -= AdaptContentHeightToGraphs; + } + + public void SetCanvasAnchor(CanvasAnchor anchor) { + _anchor = anchor; + SnapPanelBackToOriginPos(); + } + + private Vector2 GetDefinedAnchorPosition() { + var refRes = _canvasScaler.referenceResolution; + switch (_anchor) { + case CanvasAnchor.TopRight: + return refRes * .5f - Vector2.right * (_canvasPanel.sizeDelta.x * .5f); + case CanvasAnchor.TopLeft: + refRes.x *= -1; + return refRes * .5f + Vector2.right * (_canvasPanel.sizeDelta.x * .5f); + default: + return Vector2.zero; + } + } + } +} \ No newline at end of file diff --git a/Assets/Photon/Fusion/Runtime/Statistics/FusionStatsCanvas.cs.meta b/Assets/Photon/Fusion/Runtime/Statistics/FusionStatsCanvas.cs.meta new file mode 100644 index 00000000..3eb6d7f1 --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/Statistics/FusionStatsCanvas.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 25ab394f9fec45f692c631c30839e2ee +timeCreated: 1712603842 \ No newline at end of file diff --git a/Assets/Photon/Fusion/Runtime/Statistics/FusionStatsConfig.cs b/Assets/Photon/Fusion/Runtime/Statistics/FusionStatsConfig.cs new file mode 100644 index 00000000..650caead --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/Statistics/FusionStatsConfig.cs @@ -0,0 +1,107 @@ +namespace Fusion.Statistics { + using System; + using System.Collections.Generic; + using UnityEngine; + using UnityEngine.Serialization; + using UnityEngine.UI; + + public class FusionStatsConfig : MonoBehaviour { + + public bool IsWorldAnchored => _worldTransformAnchor != null; + + [SerializeField] private Button _worldAnchorButtonPrefab; + [SerializeField] private Transform _worldAnchorListContainer; + [SerializeField] private GameObject _configPanel; + [SerializeField] private Canvas _canvas; + [SerializeField] private RectTransform _renderPanelRectTransform; + + private Transform _worldTransformAnchor; + private float _worldCanvasScale = 0.005f; + + private FusionStatistics _fusionStatistics; + + private static List _worldAnchorCandidates = new List(); + private static event Action _onWorldAnchorCandidatesUpdate; + + internal static void SetWorldAnchorCandidate(Transform candidate, bool register) { + if (register) { + if (_worldAnchorCandidates.Contains(candidate) == false) + _worldAnchorCandidates.Add(candidate); + } else { + _worldAnchorCandidates.Remove(candidate); + } + + _onWorldAnchorCandidatesUpdate?.Invoke(); + } + + internal void SetupStatisticReference(FusionStatistics fusionStatistics) { + _fusionStatistics = fusionStatistics; + } + + public void ToggleConfigPanel() { + _configPanel.SetActive(!_configPanel.activeSelf); + } + + public void ToggleUseWorldAnchor(bool value) { + // If true, the buttons will trigger the re-parenting logic. + if (value == false) { + ResetToCanvasAnchor(); + } + } + + internal void SetWorldAnchor(Transform worldTransformAnchor) { + _canvas.renderMode = RenderMode.WorldSpace; + _renderPanelRectTransform.localScale = Vector3.one * _worldCanvasScale; + _renderPanelRectTransform.localPosition = Vector3.zero; + + + if (worldTransformAnchor == _worldTransformAnchor) return; + _renderPanelRectTransform.SetParent(worldTransformAnchor); + _worldTransformAnchor = worldTransformAnchor; + _renderPanelRectTransform.localPosition = Vector3.zero; + } + + public void SetWorldCanvasScale(float value) { + _worldCanvasScale = value; + } + + internal void ResetToCanvasAnchor() { + // Was called from editor destroy + if (!_fusionStatistics) + return; + + var childPanel = (RectTransform)_renderPanelRectTransform.GetChild(0); + + _renderPanelRectTransform.SetParent(_fusionStatistics.transform); + _canvas.renderMode = RenderMode.ScreenSpaceOverlay; + _renderPanelRectTransform.localScale = Vector3.one; + _renderPanelRectTransform.localPosition = Vector3.zero; + childPanel.localPosition = Vector3.zero; + childPanel.anchoredPosition = Vector3.zero; + _worldTransformAnchor = default; + } + + private void UpdateWorldAnchorButtons() { + // Clear all old buttons, ok because it should not be frequent + for (int i = _worldAnchorListContainer.childCount-1; i >= 0 ; i--) { + Destroy(_worldAnchorListContainer.GetChild(i).gameObject); + } + + foreach (var candidate in _worldAnchorCandidates) { + var button = Instantiate(_worldAnchorButtonPrefab, _worldAnchorListContainer); + button.onClick.AddListener(() => SetWorldAnchor(candidate)); + button.GetComponentInChildren().text = candidate.name; + } + } + + private void OnEnable() { + _onWorldAnchorCandidatesUpdate -= UpdateWorldAnchorButtons; + _onWorldAnchorCandidatesUpdate += UpdateWorldAnchorButtons; + UpdateWorldAnchorButtons(); + } + + private void OnDestroy() { + _onWorldAnchorCandidatesUpdate -= UpdateWorldAnchorButtons; + } + } +} \ No newline at end of file diff --git a/Assets/Photon/Fusion/Runtime/Statistics/FusionStatsConfig.cs.meta b/Assets/Photon/Fusion/Runtime/Statistics/FusionStatsConfig.cs.meta new file mode 100644 index 00000000..e67e3eb8 --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/Statistics/FusionStatsConfig.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 586894e5cee94d0e857cf1dbc7716646 +timeCreated: 1712679394 \ No newline at end of file diff --git a/Assets/Photon/Fusion/Runtime/Statistics/FusionStatsGraphDefault.cs b/Assets/Photon/Fusion/Runtime/Statistics/FusionStatsGraphDefault.cs new file mode 100644 index 00000000..286ea75f --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/Statistics/FusionStatsGraphDefault.cs @@ -0,0 +1,59 @@ +namespace Fusion.Statistics { + using System; + using System.Collections.Generic; + using UnityEngine; + using UnityEngine.UI; + + public class FusionStatsGraphDefault : FusionStatsGraphBase { + internal RenderSimStats Stat => _selectedStats; + private RenderSimStats _selectedStats; + [SerializeField] private Text _descriptionText; + + protected override void Initialize(int accumulateTimeMs) { + base.Initialize(accumulateTimeMs); + _descriptionText.text = _selectedStats.ToString(); + if (_statsAdditionalInfo.TryGetValue(Stat, out var info)) { + _descriptionText.text += $" {info}"; + } + } + + public override void UpdateGraph(NetworkRunner runner, FusionStatisticsManager statisticsManager, + ref DateTime now) { + var value = FusionStatisticsHelper.GetStatDataFromSnapshot(_selectedStats, statisticsManager.CompleteSnapshot); + AddValueToBuffer(value, ref now); + } + + public virtual void ApplyCustomStatsConfig(FusionStatistics.FusionStatisticsStatCustomConfig config) { + SetThresholds(config.Threshold1, config.Threshold2, config.Threshold3); + SetIgnoreZeroValues(config.IgnoreZeroOnAverageCalculation, config.IgnoreZeroOnBuffer); + SetAccumulateTime(config.AccumulateTimeMs); + } + + internal void SetupDefaultGraph(RenderSimStats stat) { + _selectedStats = stat; + + FusionStatisticsHelper.GetStatGraphDefaultSettings(_selectedStats, out var valueTextFormat, + out var valueTextMultiplier, out var ignoreZeroOnAverage, out var ignoreZeroOnBuffer, out var bufferTimeSpan); + + SetValueTextFormat(valueTextFormat); + SetValueTextMultiplier(valueTextMultiplier); + SetIgnoreZeroValues(ignoreZeroOnAverage, ignoreZeroOnBuffer); + Initialize(bufferTimeSpan); + } + + private Dictionary _statsAdditionalInfo = new Dictionary() { + { RenderSimStats.InPackets, "(Per second)" }, + { RenderSimStats.OutPackets, "(Per second)" }, + { RenderSimStats.InObjectUpdates, "(Per second)" }, + { RenderSimStats.OutObjectUpdates, "(Per second)" }, + { RenderSimStats.InBandwidth, "(Per second)" }, + { RenderSimStats.OutBandwidth, "(Per second)" }, + { RenderSimStats.InputInBandwidth, "(Per second)" }, + { RenderSimStats.InputOutBandwidth, "(Per second)" }, + { RenderSimStats.WordsWrittenSize, "(Per second)" }, + { RenderSimStats.WordsWrittenCount, "(Per second)" }, + { RenderSimStats.WordsReadCount, "(Per second)" }, + { RenderSimStats.WordsReadSize, "(Per second)" }, + }; + } +} \ No newline at end of file diff --git a/Assets/Photon/Fusion/Runtime/Statistics/FusionStatsGraphDefault.cs.meta b/Assets/Photon/Fusion/Runtime/Statistics/FusionStatsGraphDefault.cs.meta new file mode 100644 index 00000000..8588fd8f --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/Statistics/FusionStatsGraphDefault.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a1c807bdcb1b9874697735bf2b27b24c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Runtime/Statistics/FusionStatsGraphMaterial.mat b/Assets/Photon/Fusion/Runtime/Statistics/FusionStatsGraphMaterial.mat new file mode 100644 index 00000000..fc4e61d1 --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/Statistics/FusionStatsGraphMaterial.mat @@ -0,0 +1,89 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!21 &2100000 +Material: + serializedVersion: 8 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: FusionStatsGraphMaterial + m_Shader: {fileID: 4800000, guid: f723a494700aee146aa96072604b48c0, type: 3} + m_ValidKeywords: [] + m_InvalidKeywords: [] + m_LightmapFlags: 4 + m_EnableInstancingVariants: 0 + m_DoubleSidedGI: 0 + m_CustomRenderQueue: -1 + stringTagMap: {} + disabledShaderPasses: [] + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: + - _BumpMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailAlbedoMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailMask: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailNormalMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _EmissionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MainTex: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MetallicGlossMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _OcclusionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _ParallaxMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + m_Ints: [] + m_Floats: + - _BumpScale: 1 + - _Cutoff: 0.5 + - _DetailNormalMapScale: 1 + - _DstBlend: 0 + - _FadeColorIntensity: 0.25 + - _GlossMapScale: 1 + - _Glossiness: 0.5 + - _GlossyReflections: 1 + - _LinesThickness: 0 + - _Metallic: 0 + - _Mode: 0 + - _OcclusionStrength: 1 + - _Parallax: 0.02 + - _PointsThickness: 2 + - _SideFalloff: 1 + - _SmoothnessTextureChannel: 0 + - _SpecularHighlights: 1 + - _SrcBlend: 1 + - _UVSec: 0 + - _ZWrite: 1 + m_Colors: + - _AverageColor: {r: 1, g: 1, b: 1, a: 0} + - _BaseColor: {r: 0, g: 1, b: 0, a: 1} + - _Color: {r: 1, g: 1, b: 1, a: 1} + - _EmissionColor: {r: 0, g: 0, b: 0, a: 1} + - _Threshold1Color: {r: 1, g: 1, b: 0, a: 1} + - _Threshold2Color: {r: 1, g: 0.5019608, b: 0, a: 1} + - _Threshold3Color: {r: 1, g: 0, b: 0, a: 1} + m_BuildTextureStacks: [] diff --git a/Assets/Photon/Fusion/Runtime/Statistics/FusionStatsGraphMaterial.mat.meta b/Assets/Photon/Fusion/Runtime/Statistics/FusionStatsGraphMaterial.mat.meta new file mode 100644 index 00000000..50acd04d --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/Statistics/FusionStatsGraphMaterial.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 20f9b98d6ad29584c912ad547627b2cf +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Runtime/Statistics/FusionStatsGraphShader.shader b/Assets/Photon/Fusion/Runtime/Statistics/FusionStatsGraphShader.shader new file mode 100644 index 00000000..1c7920a1 --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/Statistics/FusionStatsGraphShader.shader @@ -0,0 +1,176 @@ +Shader "Fusion/Fusion Stats Graph" +{ + Properties + { + _BaseColor ("Base Color", Color) = (0.0, 1.0, 0.0, 1.0) + _AverageColor ("Average Color", Color) = (1.0, 1.0, 1.0, 0.0) + _Threshold1Color ("Threshold 1 Color", Color) = (1.0, 1.0, 0.0, 1.0) + _Threshold2Color ("Threshold 2 Color", Color) = (1.0, 0.5, 0.0, 1.0) + _Threshold3Color ("Threshold 3 Color", Color) = (1.0, 0.0, 0.0, 1.0) + + _FadeColorIntensity ("Fade Color Intensity", Float) = 1.0 + _PointsThickness ("Points Thickness", Float) = 1.0 + _LinesThickness ("Lines Thickness", Float) = 1.0 + _SideFalloff ("Side Falloff", Float) = 1.0 + } + + SubShader + { + Tags + { + "Queue" = "Transparent" + "RenderType" = "Transparent" + "PreviewType" = "Plane" + "IgnoreProjector" = "True" + "CanUseSpriteAtlas" = "True" + } + + Cull Off + Lighting Off + ZWrite Off + ZTest Off + Blend One OneMinusSrcAlpha + + Pass + { + CGPROGRAM + + #pragma vertex vert + #pragma fragment frag + + #include "UnityCG.cginc" + + struct appdata + { + float4 vertex : POSITION; + float4 color : COLOR; + float2 texcoord : TEXCOORD0; + + UNITY_VERTEX_INPUT_INSTANCE_ID + }; + + struct v2f + { + float4 vertex : SV_POSITION; + fixed4 color : COLOR; + float2 texcoord : TEXCOORD0; + + UNITY_VERTEX_OUTPUT_STEREO + }; + + fixed4 _BaseColor; + fixed4 _AverageColor; + fixed4 _Threshold1Color; + fixed4 _Threshold2Color; + fixed4 _Threshold3Color; + + fixed _Threshold1; + fixed _Threshold2; + fixed _Threshold3; + fixed _FadeColorIntensity; + fixed _PointsThickness; + fixed _LinesThickness; + fixed _SideFalloff; + + uniform float _Values[512]; + uniform float _Samples; + uniform float _Average; + + v2f vert(appdata input) + { + v2f output; + + UNITY_SETUP_INSTANCE_ID(input); + UNITY_INITIALIZE_OUTPUT(v2f, output); + UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output); + + output.vertex = UnityObjectToClipPos(input.vertex); + output.texcoord = input.texcoord; + output.color = input.color; + + return output; + } + + fixed4 frag(v2f input) : SV_Target + { + fixed4 color = input.color; + + fixed x = input.texcoord.x; + fixed y = input.texcoord.y; + + float value = _Values[floor(x * _Samples)]; + + color = _BaseColor; + + if (_Threshold1 > 0.0 && value >= _Threshold1) + { + color = _Threshold1Color; + } + if (_Threshold2 > 0.0 && value >= _Threshold2) + { + color = _Threshold2Color; + } + if (_Threshold3 > 0.0 && value >= _Threshold3) + { + color = _Threshold3Color; + } + + if (y > value) + { + color.a = 0.0; + } + else if (y < value - 0.01 * _PointsThickness) + { + color.a = y * _FadeColorIntensity; + } + else + { + color.a = 1.0; + } + + if (_LinesThickness > 0.0) + { + if (_AverageColor.a > 0.0 && y < _Average && y > _Average - 0.01 * _LinesThickness) + { + color = _AverageColor; + } + + if (_Threshold1Color.a > 0.0 && y < _Threshold1 && y > _Threshold1 - 0.01 * _LinesThickness) + { + color = _Threshold1Color; + } + + if (_Threshold2Color.a > 0.0 && y < _Threshold2 && y > _Threshold2 - 0.01 * _LinesThickness) + { + color = _Threshold2Color; + } + + if (_Threshold3Color.a > 0.0 && y < _Threshold3 && y > _Threshold3 - 0.01 * _LinesThickness) + { + color = _Threshold3Color; + } + } + + if (_SideFalloff > 0.0) + { + float sideFalloff = 0.01 * _SideFalloff; + + if (x < sideFalloff) + { + color.a *= 1.0 - (sideFalloff - x) / sideFalloff; + } + else if (x > 1.0 - sideFalloff) + { + color.a *= (1.0 - x) / sideFalloff; + } + } + + color.rgb *= color.a; + + return color; + } + + ENDCG + } + } +} diff --git a/Assets/Photon/Fusion/Runtime/Statistics/FusionStatsGraphShader.shader.meta b/Assets/Photon/Fusion/Runtime/Statistics/FusionStatsGraphShader.shader.meta new file mode 100644 index 00000000..e5a8d869 --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/Statistics/FusionStatsGraphShader.shader.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: f723a494700aee146aa96072604b48c0 +ShaderImporter: + externalObjects: {} + defaultTextures: [] + nonModifiableTextures: [] + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Runtime/Statistics/FusionStatsPanelHeader.cs b/Assets/Photon/Fusion/Runtime/Statistics/FusionStatsPanelHeader.cs new file mode 100644 index 00000000..6a0daf01 --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/Statistics/FusionStatsPanelHeader.cs @@ -0,0 +1,283 @@ +namespace Fusion.Statistics { +using UnityEngine; +using UnityEngine.UI; +using System; +using System.Collections.Generic; + + /// + /// List of all simulation stats able to render on a graph. + /// + [Flags] + public enum RenderSimStats { + /// + /// Incoming packets. + /// + InPackets = 1 << 0, + + /// + /// Outgoing packets. + /// + OutPackets = 1 << 1, + + /// + /// Round Trip Time. + /// + RTT = 1 << 2, + + /// + /// In Bandwidth in Bytes. + /// + InBandwidth = 1 << 3, + + /// + /// Out Bandwidth in Bytes. + /// + OutBandwidth = 1 << 4, + + /// + /// Amount of re-simulation ticks executed. + /// + Resimulations = 1 << 5, + + /// + /// Amount of forward ticks executed. + /// + ForwardTicks = 1 << 6, + + /// + /// Average measured time between two input/state packets (from same client) received by the server. + /// + InputReceiveDelta = 1 << 7, + + /// + /// Time sync abruptly reset count. + /// + TimeResets = 1 << 8, + + /// + /// Average measured time between two state packets (from server) received by the client. + /// + StateReceiveDelta = 1 << 9, + + /// + /// Average buffering for prediction. + /// + SimulationTimeOffset = 1 << 10, + + /// + /// How much the simulation is currently sped up / slowed down. + /// + SimulationSpeed = 1 << 11, + + /// + /// Average buffering for interpolation. + /// + InterpolationOffset = 1 << 12, + + /// + /// How much interpolation is currently sped up / slowed down. + /// + InterpolationSpeed = 1 << 13, + + /// + /// Input in bandwidth. + /// + InputInBandwidth = 1 << 14, + + /// + /// Input out bandwidth. + /// + InputOutBandwidth = 1 << 15, + + /// + /// Average size for received packet. + /// + AverageInPacketSize = 1 << 16, + + /// + /// Average size for sent packet. + /// + AverageOutPacketSize = 1 << 17, + + /// + /// Amount of object updates received. + /// + InObjectUpdates = 1 << 18, + + /// + /// Amount of object updates sent. + /// + OutObjectUpdates = 1 << 19, + + /// + /// Memory in use for the object allocator. + /// + ObjectsAllocatedMemoryInUse = 1 << 20, + + /// + /// Memory in use for the general allocator. + /// + GeneralAllocatedMemoryInUse = 1 << 21, + + /// + /// Memory free for the object allocator. + /// + ObjectsAllocatedMemoryFree = 1 << 22, + + /// + /// Memory free for the general allocator. + /// + GeneralAllocatedMemoryFree = 1 << 23, + + /// + /// Amount of written words. How many networked changes are being sent. + /// + WordsWrittenCount = 1 << 24, + + /// + /// Size of all last written words in Bytes. + /// + WordsWrittenSize = 1 << 25, + + /// + /// Amount of read words. How many networked changes are being received. + /// + WordsReadCount = 1 << 26, + + /// + /// Size of all last read words in Bytes. + /// + WordsReadSize = 1 << 27, + } + + public class FusionStatsPanelHeader : MonoBehaviour { + public event Action OnRenderStatsUpdate; + + [SerializeField] private Text _statsHeaderTitle; + [SerializeField] private Dropdown _statsDropdown; + [SerializeField] private FusionStatsGraphDefault _defaultGraphPrefab; + + public RectTransform ContentRect; + + private Dictionary _defaultStatsGraph; + private FusionStatistics _fusionStatistics; + private RenderSimStats _statsToRender; + + public void SetupHeader(string title, FusionStatistics fusionStatistics) { + _statsHeaderTitle.text = title; + _fusionStatistics = fusionStatistics; + + SetupDropdown(); + } + + private void SetupDropdown() { + _defaultStatsGraph = new Dictionary(); + + List options = new List(); + + options.Add(new Dropdown.OptionData("Toggle Stats")); + + foreach (var option in Enum.GetNames(typeof(RenderSimStats))) { + options.Add(new Dropdown.OptionData(option)); + } + + _statsDropdown.options = options; + + _statsDropdown.onValueChanged.AddListener(OnDropDownChanged); + } + + internal void SetStatsToRender(RenderSimStats stats) { + // Early exit + if (stats == _statsToRender) return; + + // For each possible stat + foreach (RenderSimStats renderSimStat in Enum.GetValues(typeof(RenderSimStats))) { + // If it is set on the stats received + if ((stats & renderSimStat) == renderSimStat) { + // And if it is not already set on the stats to render... add it + if ((_statsToRender & renderSimStat) != renderSimStat) { + AddStat(renderSimStat); + } + } + // else if is NOT set on the stats received + else { + // And if it is set on the stats to render... remove + if ((_statsToRender & renderSimStat) == renderSimStat) { + RemoveStat(renderSimStat); + } + } + } + + // Make sure they are equal now. + _statsToRender = stats; + } + + private void AddStat(RenderSimStats stat) { + _statsToRender |= stat; // Set the flag + InstantiateStatGraph(stat); + InvokeRenderStatsUpdate(); + } + + private void RemoveStat(RenderSimStats stat) { + _statsToRender &= ~stat; // Removed the flag + DestroyStatGraph(stat); + InvokeRenderStatsUpdate(); + } + + private void InvokeRenderStatsUpdate() { + OnRenderStatsUpdate?.Invoke(); + } + + private void OnDropDownChanged(int arg0) { + if (arg0 <= 0) return; // No stat selected. + arg0--; // Remove the first label + + RenderSimStats stat = (RenderSimStats)(1 << arg0); + + if ((_statsToRender & stat) == stat) { + RemoveStat(stat); + } else { + AddStat(stat); + } + + // Set the first label again. + _statsDropdown.SetValueWithoutNotify(0); + + _fusionStatistics.UpdateStatsEnabled(_statsToRender); + } + + private void InstantiateStatGraph(RenderSimStats stat) { + FusionStatsGraphDefault graph = Instantiate(_defaultGraphPrefab, ContentRect); + graph.SetupDefaultGraph(stat); + TryApplyCustomStatConfig(graph); + _defaultStatsGraph.Add(stat, graph); + } + + private void DestroyStatGraph(RenderSimStats stat) { + if (_defaultStatsGraph.Remove(stat, out var statsGraphDefault)) { + Destroy(statsGraphDefault.gameObject); + } + } + + private void TryApplyCustomStatConfig(FusionStatsGraphDefault graph) { + // Need to do this way because unity cannot serialize a dictionary. + foreach (var config in _fusionStatistics.StatsCustomConfig) { + if (config.Stat == graph.Stat) { + ApplyCustomStatsConfig(graph, config); + } + } + } + + private void ApplyCustomStatsConfig(FusionStatsGraphDefault graph, FusionStatistics.FusionStatisticsStatCustomConfig config) { + graph.ApplyCustomStatsConfig(config); + } + + internal void ApplyStatsConfig(List statsConfig) { + foreach (var config in statsConfig) { + if (_defaultStatsGraph.TryGetValue(config.Stat, out var graph)) { + ApplyCustomStatsConfig(graph, config); + } + } + } + } +} \ No newline at end of file diff --git a/Assets/Photon/Fusion/Runtime/Statistics/FusionStatsPanelHeader.cs.meta b/Assets/Photon/Fusion/Runtime/Statistics/FusionStatsPanelHeader.cs.meta new file mode 100644 index 00000000..36864685 --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/Statistics/FusionStatsPanelHeader.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 0672ffa0bdc147e7b368e081ab5e7285 +timeCreated: 1710192229 \ No newline at end of file diff --git a/Assets/Photon/Fusion/Runtime/Statistics/FusionStatsWorldAnchor.cs b/Assets/Photon/Fusion/Runtime/Statistics/FusionStatsWorldAnchor.cs new file mode 100644 index 00000000..06e1297c --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/Statistics/FusionStatsWorldAnchor.cs @@ -0,0 +1,25 @@ +namespace Fusion.Statistics { + using System; + using UnityEngine; + + [DisallowMultipleComponent] + [AddComponentMenu("Fusion/Statistics/Statistics World Anchor")] + public class FusionStatsWorldAnchor : MonoBehaviour { + private void OnEnable() { + FusionStatsConfig.SetWorldAnchorCandidate(transform, true); + } + + private void OnDisable() { + FusionStatsConfig.SetWorldAnchorCandidate(transform, false); + } + + private void OnDestroy() { + // Saving stats if is child + var stats = transform.GetComponentInChildren(); + if (stats) { + stats.transform.SetParent(null); + stats.GetComponentInChildren(true).ResetToCanvasAnchor(); + } + } + } +} \ No newline at end of file diff --git a/Assets/Photon/Fusion/Runtime/Statistics/FusionStatsWorldAnchor.cs.meta b/Assets/Photon/Fusion/Runtime/Statistics/FusionStatsWorldAnchor.cs.meta new file mode 100644 index 00000000..180f9b89 --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/Statistics/FusionStatsWorldAnchor.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: b922ea457e8e4de6bfea00a8cc1eca80 +timeCreated: 1712606226 \ No newline at end of file diff --git a/Assets/Photon/Fusion/Runtime/Statistics/Prefabs.meta b/Assets/Photon/Fusion/Runtime/Statistics/Prefabs.meta new file mode 100644 index 00000000..4582c19d --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/Statistics/Prefabs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7bbab8d6d9806b548839f21543e696d4 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Runtime/Statistics/Prefabs/FusionStatsSimpleButton.prefab b/Assets/Photon/Fusion/Runtime/Statistics/Prefabs/FusionStatsSimpleButton.prefab new file mode 100644 index 00000000..d3388874 --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/Statistics/Prefabs/FusionStatsSimpleButton.prefab @@ -0,0 +1,202 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &3987400527868405841 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 4827688222978096223} + - component: {fileID: 3929318550392185910} + - component: {fileID: 7855319467316828452} + - component: {fileID: 4920168020725175909} + m_Layer: 5 + m_Name: FusionStatsSimpleButton + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &4827688222978096223 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3987400527868405841} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 860521090280365962} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 50} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &3929318550392185910 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3987400527868405841} + m_CullTransparentMesh: 1 +--- !u!114 &7855319467316828452 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3987400527868405841} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.29411766, g: 0.29411766, b: 0.29411766, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 0} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!114 &4920168020725175909 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3987400527868405841} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Navigation: + m_Mode: 3 + m_WrapAround: 0 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 1 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_SelectedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_SelectedTrigger: Selected + m_DisabledTrigger: Disabled + m_Interactable: 1 + m_TargetGraphic: {fileID: 7855319467316828452} + m_OnClick: + m_PersistentCalls: + m_Calls: [] +--- !u!1 &4034818165790845292 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 860521090280365962} + - component: {fileID: 8155235640972728683} + - component: {fileID: 889677207960826122} + m_Layer: 5 + m_Name: Text (Legacy) + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &860521090280365962 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4034818165790845292} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4827688222978096223} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0.000061035156, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &8155235640972728683 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4034818165790845292} + m_CullTransparentMesh: 1 +--- !u!114 &889677207960826122 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4034818165790845292} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 14 + m_FontStyle: 0 + m_BestFit: 1 + m_MinSize: 10 + m_MaxSize: 40 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: Button diff --git a/Assets/Photon/Fusion/Runtime/Statistics/Prefabs/FusionStatsSimpleButton.prefab.meta b/Assets/Photon/Fusion/Runtime/Statistics/Prefabs/FusionStatsSimpleButton.prefab.meta new file mode 100644 index 00000000..f90d99cf --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/Statistics/Prefabs/FusionStatsSimpleButton.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 8e3a30446d6463e48adea83d18ce9654 +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Runtime/Statistics/Prefabs/NOStatGraph.prefab b/Assets/Photon/Fusion/Runtime/Statistics/Prefabs/NOStatGraph.prefab new file mode 100644 index 00000000..faf1e93a --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/Statistics/Prefabs/NOStatGraph.prefab @@ -0,0 +1,384 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &1719597503058426708 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1719597503058426707} + - component: {fileID: 1719597503058426706} + m_Layer: 5 + m_Name: NOStatGraph + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1719597503058426707 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1719597503058426708} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 6113166891111545665} + - {fileID: 6239836229493852532} + m_Father: {fileID: 0} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 400, y: 150} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1719597503058426706 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1719597503058426708} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 6c65e6f8cc0e421195e5884e060e7ead, type: 3} + m_Name: + m_EditorClassIdentifier: + _render: {fileID: 6113166891111545665} + _header: {fileID: 6239836229493852532} + _targetImage: {fileID: 4962068735173936647} + _toggleButton: {fileID: 0} + _ignoreZeroedValuesOnAverageCalculation: 0 + _ignoreZeroedValuesOnBuffer: 0 + _valuesTextUpdateDelay: 0.1 + _valueTextMultiplier: 1 + _averageValueText: {fileID: 5977692791235129321} + _peakValueText: {fileID: 5180300036338728672} + _currentValueText: {fileID: 8872655689014498417} + _threshold1: 0 + _threshold2: 0 + _threshold3: 0 + _threshold1Text: {fileID: 574183341331817218} + _threshold2Text: {fileID: 4974124732164616916} + _threshold3Text: {fileID: 7292999253169489219} + _maxSamples: 128 + _valueTextFormat: '{0:0}' + _description: {fileID: 391098838509034737} +--- !u!1 &6453443907668438141 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1788213164568613159} + - component: {fileID: 4074940008736195761} + - component: {fileID: 391098838509034737} + m_Layer: 5 + m_Name: Description + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1788213164568613159 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6453443907668438141} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 6239836229493852532} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0.8, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &4074940008736195761 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6453443907668438141} + m_CullTransparentMesh: 1 +--- !u!114 &391098838509034737 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6453443907668438141} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 14 + m_FontStyle: 1 + m_BestFit: 1 + m_MinSize: 10 + m_MaxSize: 20 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: Description +--- !u!1 &8718060512283005892 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 6239836229493852532} + - component: {fileID: 384158204715830663} + - component: {fileID: 423454910494282654} + m_Layer: 5 + m_Name: Header + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &6239836229493852532 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8718060512283005892} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1788213164568613159} + m_Father: {fileID: 1719597503058426707} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0.85} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &384158204715830663 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8718060512283005892} + m_CullTransparentMesh: 1 +--- !u!114 &423454910494282654 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8718060512283005892} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.3529412, g: 0.5882353, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 0} + m_Type: 0 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!1001 &1719597502569185417 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + m_TransformParent: {fileID: 1719597503058426707} + m_Modifications: + - target: {fileID: 4831069612263668680, guid: a09170bc25723ba48b6d4d36f6201acd, + type: 3} + propertyPath: m_RootOrder + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4831069612263668680, guid: a09170bc25723ba48b6d4d36f6201acd, + type: 3} + propertyPath: m_AnchorMax.y + value: 0.85 + objectReference: {fileID: 0} + - target: {fileID: 4831069612263668680, guid: a09170bc25723ba48b6d4d36f6201acd, + type: 3} + propertyPath: m_SizeDelta.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4831069612263668680, guid: a09170bc25723ba48b6d4d36f6201acd, + type: 3} + propertyPath: m_LocalPosition.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4831069612263668680, guid: a09170bc25723ba48b6d4d36f6201acd, + type: 3} + propertyPath: m_LocalPosition.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4831069612263668680, guid: a09170bc25723ba48b6d4d36f6201acd, + type: 3} + propertyPath: m_LocalPosition.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4831069612263668680, guid: a09170bc25723ba48b6d4d36f6201acd, + type: 3} + propertyPath: m_AnchoredPosition.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4831069612263668680, guid: a09170bc25723ba48b6d4d36f6201acd, + type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4831069612263668680, guid: a09170bc25723ba48b6d4d36f6201acd, + type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4831069612263668680, guid: a09170bc25723ba48b6d4d36f6201acd, + type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 9126926557282158578, guid: a09170bc25723ba48b6d4d36f6201acd, + type: 3} + propertyPath: m_Name + value: StatisticsRenderGraph + objectReference: {fileID: 0} + m_RemovedComponents: [] + m_SourcePrefab: {fileID: 100100000, guid: a09170bc25723ba48b6d4d36f6201acd, type: 3} +--- !u!114 &574183341331817218 stripped +MonoBehaviour: + m_CorrespondingSourceObject: {fileID: 1164976986725983115, guid: a09170bc25723ba48b6d4d36f6201acd, + type: 3} + m_PrefabInstance: {fileID: 1719597502569185417} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!114 &4962068735173936647 stripped +MonoBehaviour: + m_CorrespondingSourceObject: {fileID: 5981329958659966606, guid: a09170bc25723ba48b6d4d36f6201acd, + type: 3} + m_PrefabInstance: {fileID: 1719597502569185417} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!114 &4974124732164616916 stripped +MonoBehaviour: + m_CorrespondingSourceObject: {fileID: 5970252233686725213, guid: a09170bc25723ba48b6d4d36f6201acd, + type: 3} + m_PrefabInstance: {fileID: 1719597502569185417} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!114 &5180300036338728672 stripped +MonoBehaviour: + m_CorrespondingSourceObject: {fileID: 5780690898318835305, guid: a09170bc25723ba48b6d4d36f6201acd, + type: 3} + m_PrefabInstance: {fileID: 1719597502569185417} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!114 &5977692791235129321 stripped +MonoBehaviour: + m_CorrespondingSourceObject: {fileID: 4983298122956582752, guid: a09170bc25723ba48b6d4d36f6201acd, + type: 3} + m_PrefabInstance: {fileID: 1719597502569185417} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!224 &6113166891111545665 stripped +RectTransform: + m_CorrespondingSourceObject: {fileID: 4831069612263668680, guid: a09170bc25723ba48b6d4d36f6201acd, + type: 3} + m_PrefabInstance: {fileID: 1719597502569185417} + m_PrefabAsset: {fileID: 0} +--- !u!114 &7292999253169489219 stripped +MonoBehaviour: + m_CorrespondingSourceObject: {fileID: 8280093310795793866, guid: a09170bc25723ba48b6d4d36f6201acd, + type: 3} + m_PrefabInstance: {fileID: 1719597502569185417} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!114 &8872655689014498417 stripped +MonoBehaviour: + m_CorrespondingSourceObject: {fileID: 7854063927993816312, guid: a09170bc25723ba48b6d4d36f6201acd, + type: 3} + m_PrefabInstance: {fileID: 1719597502569185417} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: diff --git a/Assets/Photon/Fusion/Runtime/Statistics/Prefabs/NOStatGraph.prefab.meta b/Assets/Photon/Fusion/Runtime/Statistics/Prefabs/NOStatGraph.prefab.meta new file mode 100644 index 00000000..8d9bedf5 --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/Statistics/Prefabs/NOStatGraph.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 302a42a4eb4d5e540b803b1f86597505 +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Runtime/Statistics/Prefabs/SingleStatistics.prefab b/Assets/Photon/Fusion/Runtime/Statistics/Prefabs/SingleStatistics.prefab new file mode 100644 index 00000000..2e4ad2e6 --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/Statistics/Prefabs/SingleStatistics.prefab @@ -0,0 +1,625 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &2059135424099053782 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2059135424099053777} + - component: {fileID: 2059135424099053778} + - component: {fileID: 2059135424099053776} + m_Layer: 5 + m_Name: ToggleButton + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &2059135424099053777 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2059135424099053782} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 6870472896872226928} + m_Father: {fileID: 6662754291284625420} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0.1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &2059135424099053778 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2059135424099053782} + m_CullTransparentMesh: 1 +--- !u!114 &2059135424099053776 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2059135424099053782} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Navigation: + m_Mode: 3 + m_WrapAround: 0 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 0 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_SelectedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_SelectedTrigger: Selected + m_DisabledTrigger: Disabled + m_Interactable: 1 + m_TargetGraphic: {fileID: 0} + m_OnClick: + m_PersistentCalls: + m_Calls: + - m_Target: {fileID: 2059135424270465704} + m_TargetAssemblyTypeName: Fusion.Stats.FusionStatsGraphBase, Fusion.Unity + m_MethodName: ToggleRenderDisplay + m_Mode: 1 + m_Arguments: + m_ObjectArgument: {fileID: 0} + m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine + m_IntArgument: 0 + m_FloatArgument: 0 + m_StringArgument: + m_BoolArgument: 0 + m_CallState: 2 +--- !u!1 &2059135424237535789 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2059135424237535788} + - component: {fileID: 2059135424270465704} + m_Layer: 5 + m_Name: SingleStatistics + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &2059135424237535788 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2059135424237535789} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1070322759179670889} + - {fileID: 6662754291284625420} + m_Father: {fileID: 0} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 400, y: 150} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &2059135424270465704 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2059135424237535789} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: a1c807bdcb1b9874697735bf2b27b24c, type: 3} + m_Name: + m_EditorClassIdentifier: + _render: {fileID: 1070322759179670889} + _header: {fileID: 6662754291284625420} + _targetImage: {fileID: 2220300178287962159} + _toggleButton: {fileID: 2059135424099053776} + _ignoreZeroedValuesOnAverageCalculation: 0 + _ignoreZeroedValuesOnBuffer: 0 + _valuesTextUpdateDelay: 0.1 + _valueTextMultiplier: 1 + _averageValueText: {fileID: 646794221100057025} + _peakValueText: {fileID: 2155191676243792072} + _currentValueText: {fileID: 2391087390145165913} + _threshold1: 0 + _threshold2: 0 + _threshold3: 0 + _threshold1Text: {fileID: 6772060006189485354} + _threshold2Text: {fileID: 2237018056329157884} + _threshold3Text: {fileID: 4555716653057387371} + _maxSamples: 128 + _valueTextFormat: '{0:0}' + _selectedStats: 0 + _descriptionText: {fileID: 3003985804431238575} +--- !u!1 &2885396740209662036 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 6662754291284625420} + - component: {fileID: 6532386780719339218} + - component: {fileID: 3096909071055530399} + m_Layer: 5 + m_Name: Header + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &6662754291284625420 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2885396740209662036} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 8019411793580211070} + - {fileID: 2059135424099053777} + m_Father: {fileID: 2059135424237535788} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 1} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: -12.5} + m_SizeDelta: {x: 0, y: 25} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &6532386780719339218 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2885396740209662036} + m_CullTransparentMesh: 1 +--- !u!114 &3096909071055530399 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2885396740209662036} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.39215687, g: 0.39215687, b: 0.39215687, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 0} + m_Type: 0 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!1 &4311662245131508133 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 6870472896872226928} + - component: {fileID: 4798023581314036480} + - component: {fileID: 3314414533859350682} + m_Layer: 5 + m_Name: Icon + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &6870472896872226928 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4311662245131508133} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 2059135424099053777} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &4798023581314036480 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4311662245131508133} + m_CullTransparentMesh: 1 +--- !u!114 &3314414533859350682 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4311662245131508133} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 21300000, guid: 330babd635d5dfc4691ae3151ffc4491, type: 3} + m_Type: 0 + m_PreserveAspect: 1 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!1 &5877711169838791531 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 8019411793580211070} + - component: {fileID: 4664219767356560693} + - component: {fileID: 3003985804431238575} + m_Layer: 5 + m_Name: Description + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &8019411793580211070 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5877711169838791531} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 6662754291284625420} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.1, y: 0} + m_AnchorMax: {x: 0.9, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: -4} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &4664219767356560693 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5877711169838791531} + m_CullTransparentMesh: 1 +--- !u!114 &3003985804431238575 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5877711169838791531} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 14 + m_FontStyle: 1 + m_BestFit: 1 + m_MinSize: 10 + m_MaxSize: 20 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: Description +--- !u!1001 &5607514626138009249 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + m_TransformParent: {fileID: 2059135424237535788} + m_Modifications: + - target: {fileID: 4831069612263668680, guid: a09170bc25723ba48b6d4d36f6201acd, + type: 3} + propertyPath: m_Pivot.x + value: 0.5 + objectReference: {fileID: 0} + - target: {fileID: 4831069612263668680, guid: a09170bc25723ba48b6d4d36f6201acd, + type: 3} + propertyPath: m_Pivot.y + value: 0.5 + objectReference: {fileID: 0} + - target: {fileID: 4831069612263668680, guid: a09170bc25723ba48b6d4d36f6201acd, + type: 3} + propertyPath: m_RootOrder + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4831069612263668680, guid: a09170bc25723ba48b6d4d36f6201acd, + type: 3} + propertyPath: m_AnchorMax.x + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 4831069612263668680, guid: a09170bc25723ba48b6d4d36f6201acd, + type: 3} + propertyPath: m_AnchorMax.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4831069612263668680, guid: a09170bc25723ba48b6d4d36f6201acd, + type: 3} + propertyPath: m_AnchorMin.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4831069612263668680, guid: a09170bc25723ba48b6d4d36f6201acd, + type: 3} + propertyPath: m_AnchorMin.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4831069612263668680, guid: a09170bc25723ba48b6d4d36f6201acd, + type: 3} + propertyPath: m_SizeDelta.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4831069612263668680, guid: a09170bc25723ba48b6d4d36f6201acd, + type: 3} + propertyPath: m_SizeDelta.y + value: 125 + objectReference: {fileID: 0} + - target: {fileID: 4831069612263668680, guid: a09170bc25723ba48b6d4d36f6201acd, + type: 3} + propertyPath: m_LocalPosition.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4831069612263668680, guid: a09170bc25723ba48b6d4d36f6201acd, + type: 3} + propertyPath: m_LocalPosition.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4831069612263668680, guid: a09170bc25723ba48b6d4d36f6201acd, + type: 3} + propertyPath: m_LocalPosition.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4831069612263668680, guid: a09170bc25723ba48b6d4d36f6201acd, + type: 3} + propertyPath: m_LocalRotation.w + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 4831069612263668680, guid: a09170bc25723ba48b6d4d36f6201acd, + type: 3} + propertyPath: m_LocalRotation.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4831069612263668680, guid: a09170bc25723ba48b6d4d36f6201acd, + type: 3} + propertyPath: m_LocalRotation.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4831069612263668680, guid: a09170bc25723ba48b6d4d36f6201acd, + type: 3} + propertyPath: m_LocalRotation.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4831069612263668680, guid: a09170bc25723ba48b6d4d36f6201acd, + type: 3} + propertyPath: m_AnchoredPosition.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4831069612263668680, guid: a09170bc25723ba48b6d4d36f6201acd, + type: 3} + propertyPath: m_AnchoredPosition.y + value: 62.5 + objectReference: {fileID: 0} + - target: {fileID: 4831069612263668680, guid: a09170bc25723ba48b6d4d36f6201acd, + type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4831069612263668680, guid: a09170bc25723ba48b6d4d36f6201acd, + type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 4831069612263668680, guid: a09170bc25723ba48b6d4d36f6201acd, + type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 9126926557282158578, guid: a09170bc25723ba48b6d4d36f6201acd, + type: 3} + propertyPath: m_Name + value: StatisticsRenderGraph + objectReference: {fileID: 0} + - target: {fileID: 9126926557282158578, guid: a09170bc25723ba48b6d4d36f6201acd, + type: 3} + propertyPath: m_IsActive + value: 1 + objectReference: {fileID: 0} + m_RemovedComponents: [] + m_SourcePrefab: {fileID: 100100000, guid: a09170bc25723ba48b6d4d36f6201acd, type: 3} +--- !u!114 &646794221100057025 stripped +MonoBehaviour: + m_CorrespondingSourceObject: {fileID: 4983298122956582752, guid: a09170bc25723ba48b6d4d36f6201acd, + type: 3} + m_PrefabInstance: {fileID: 5607514626138009249} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!224 &1070322759179670889 stripped +RectTransform: + m_CorrespondingSourceObject: {fileID: 4831069612263668680, guid: a09170bc25723ba48b6d4d36f6201acd, + type: 3} + m_PrefabInstance: {fileID: 5607514626138009249} + m_PrefabAsset: {fileID: 0} +--- !u!114 &2155191676243792072 stripped +MonoBehaviour: + m_CorrespondingSourceObject: {fileID: 5780690898318835305, guid: a09170bc25723ba48b6d4d36f6201acd, + type: 3} + m_PrefabInstance: {fileID: 5607514626138009249} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!114 &2220300178287962159 stripped +MonoBehaviour: + m_CorrespondingSourceObject: {fileID: 5981329958659966606, guid: a09170bc25723ba48b6d4d36f6201acd, + type: 3} + m_PrefabInstance: {fileID: 5607514626138009249} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!114 &2237018056329157884 stripped +MonoBehaviour: + m_CorrespondingSourceObject: {fileID: 5970252233686725213, guid: a09170bc25723ba48b6d4d36f6201acd, + type: 3} + m_PrefabInstance: {fileID: 5607514626138009249} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!114 &2391087390145165913 stripped +MonoBehaviour: + m_CorrespondingSourceObject: {fileID: 7854063927993816312, guid: a09170bc25723ba48b6d4d36f6201acd, + type: 3} + m_PrefabInstance: {fileID: 5607514626138009249} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!114 &4555716653057387371 stripped +MonoBehaviour: + m_CorrespondingSourceObject: {fileID: 8280093310795793866, guid: a09170bc25723ba48b6d4d36f6201acd, + type: 3} + m_PrefabInstance: {fileID: 5607514626138009249} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!114 &6772060006189485354 stripped +MonoBehaviour: + m_CorrespondingSourceObject: {fileID: 1164976986725983115, guid: a09170bc25723ba48b6d4d36f6201acd, + type: 3} + m_PrefabInstance: {fileID: 5607514626138009249} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: diff --git a/Assets/Photon/Fusion/Runtime/Statistics/Prefabs/SingleStatistics.prefab.meta b/Assets/Photon/Fusion/Runtime/Statistics/Prefabs/SingleStatistics.prefab.meta new file mode 100644 index 00000000..ff995538 --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/Statistics/Prefabs/SingleStatistics.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 483568e790e53a243a4f436e924e90b9 +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Runtime/Statistics/Prefabs/StatisticsRenderGraph.prefab b/Assets/Photon/Fusion/Runtime/Statistics/Prefabs/StatisticsRenderGraph.prefab new file mode 100644 index 00000000..4a2f0ab8 --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/Statistics/Prefabs/StatisticsRenderGraph.prefab @@ -0,0 +1,958 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &724195450048490934 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2252466015236110801} + - component: {fileID: 8237614070168901424} + - component: {fileID: 1164976986725983115} + m_Layer: 5 + m_Name: Threshold 1 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &2252466015236110801 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 724195450048490934} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 3852939355018933397} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0.5} + m_AnchorMax: {x: 1, y: 0.5} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 30} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &8237614070168901424 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 724195450048490934} + m_CullTransparentMesh: 1 +--- !u!114 &1164976986725983115 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 724195450048490934} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 8 + m_FontStyle: 0 + m_BestFit: 1 + m_MinSize: 5 + m_MaxSize: 15 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: 1 +--- !u!1 &1346113333905028555 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1996528426999223811} + - component: {fileID: 7493037385389740767} + - component: {fileID: 5981329958659966606} + m_Layer: 5 + m_Name: GraphRender + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1996528426999223811 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1346113333905028555} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4831069612263668680} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &7493037385389740767 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1346113333905028555} + m_CullTransparentMesh: 1 +--- !u!114 &5981329958659966606 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1346113333905028555} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 2100000, guid: 20f9b98d6ad29584c912ad547627b2cf, type: 2} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 0 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 21300000, guid: 2f112e6067c75b34f8c6233878db13b4, type: 3} + m_Type: 0 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!1 &1713622682841279402 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 8245948005965945133} + - component: {fileID: 2944837472607786133} + - component: {fileID: 88432356876806002} + m_Layer: 5 + m_Name: Current + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &8245948005965945133 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1713622682841279402} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4831069612263668680} + m_RootOrder: 2 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.7, y: 0} + m_AnchorMax: {x: 0.85, y: 0} + m_AnchoredPosition: {x: 0, y: 25} + m_SizeDelta: {x: 0, y: 50} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &2944837472607786133 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1713622682841279402} + m_CullTransparentMesh: 1 +--- !u!114 &88432356876806002 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1713622682841279402} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 14 + m_FontStyle: 1 + m_BestFit: 1 + m_MinSize: 10 + m_MaxSize: 20 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: 'Current:' +--- !u!1 &1866382379870605257 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 630422623976466404} + - component: {fileID: 4635913912757587435} + - component: {fileID: 8280093310795793866} + m_Layer: 5 + m_Name: Threshold 3 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &630422623976466404 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1866382379870605257} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 3852939355018933397} + m_RootOrder: 2 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0.5} + m_AnchorMax: {x: 1, y: 0.5} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 30} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &4635913912757587435 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1866382379870605257} + m_CullTransparentMesh: 1 +--- !u!114 &8280093310795793866 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1866382379870605257} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 8 + m_FontStyle: 0 + m_BestFit: 1 + m_MinSize: 5 + m_MaxSize: 15 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: 3 +--- !u!1 &2926125212941636726 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 7767554146471260023} + - component: {fileID: 9159602532881435272} + - component: {fileID: 7854063927993816312} + m_Layer: 5 + m_Name: CurrentValue + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &7767554146471260023 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2926125212941636726} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4831069612263668680} + m_RootOrder: 3 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.85, y: 0} + m_AnchorMax: {x: 1, y: 0} + m_AnchoredPosition: {x: 0, y: 25} + m_SizeDelta: {x: 0, y: 50} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &9159602532881435272 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2926125212941636726} + m_CullTransparentMesh: 1 +--- !u!114 &7854063927993816312 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2926125212941636726} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 14 + m_FontStyle: 1 + m_BestFit: 1 + m_MinSize: 10 + m_MaxSize: 20 + m_Alignment: 3 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: 0.0 +--- !u!1 &3091450967103008972 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 8724134812417612050} + - component: {fileID: 2037306754755500012} + - component: {fileID: 5780690898318835305} + m_Layer: 5 + m_Name: PeakValue + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &8724134812417612050 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3091450967103008972} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4831069612263668680} + m_RootOrder: 7 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.15, y: 0} + m_AnchorMax: {x: 0.3, y: 0} + m_AnchoredPosition: {x: 0, y: 25} + m_SizeDelta: {x: 0, y: 50} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &2037306754755500012 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3091450967103008972} + m_CullTransparentMesh: 1 +--- !u!114 &5780690898318835305 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3091450967103008972} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 14 + m_FontStyle: 1 + m_BestFit: 1 + m_MinSize: 10 + m_MaxSize: 20 + m_Alignment: 3 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: 0.0 +--- !u!1 &3122924414019610630 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 3141646064265744334} + - component: {fileID: 7432501649756783534} + - component: {fileID: 5759711218940510673} + m_Layer: 5 + m_Name: Peak + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &3141646064265744334 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3122924414019610630} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4831069612263668680} + m_RootOrder: 6 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0.15, y: 0} + m_AnchoredPosition: {x: 0, y: 25} + m_SizeDelta: {x: 0, y: 50} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &7432501649756783534 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3122924414019610630} + m_CullTransparentMesh: 1 +--- !u!114 &5759711218940510673 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3122924414019610630} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 14 + m_FontStyle: 1 + m_BestFit: 1 + m_MinSize: 10 + m_MaxSize: 20 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: 'Peak:' +--- !u!1 &4466228370271718180 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 3852939355018933397} + m_Layer: 5 + m_Name: Thresholds Texts + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &3852939355018933397 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4466228370271718180} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 2252466015236110801} + - {fileID: 7848575700833239251} + - {fileID: 630422623976466404} + m_Father: {fileID: 4831069612263668680} + m_RootOrder: 8 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0.1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!1 &4748543092155706786 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1197363314016853701} + - component: {fileID: 6559507284176787114} + - component: {fileID: 4983298122956582752} + m_Layer: 5 + m_Name: AverageValue + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1197363314016853701 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4748543092155706786} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4831069612263668680} + m_RootOrder: 5 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.55, y: 0} + m_AnchorMax: {x: 0.7, y: 0} + m_AnchoredPosition: {x: 0, y: 25} + m_SizeDelta: {x: 0, y: 50} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &6559507284176787114 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4748543092155706786} + m_CullTransparentMesh: 1 +--- !u!114 &4983298122956582752 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4748543092155706786} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 14 + m_FontStyle: 1 + m_BestFit: 1 + m_MinSize: 10 + m_MaxSize: 20 + m_Alignment: 3 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: 0.0 +--- !u!1 &6021428339324464465 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 517706616007655284} + - component: {fileID: 5597891647223780865} + - component: {fileID: 1335454418943084614} + m_Layer: 5 + m_Name: Average + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &517706616007655284 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6021428339324464465} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4831069612263668680} + m_RootOrder: 4 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.3, y: 0} + m_AnchorMax: {x: 0.55, y: 0} + m_AnchoredPosition: {x: 0, y: 25} + m_SizeDelta: {x: 0, y: 50} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &5597891647223780865 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6021428339324464465} + m_CullTransparentMesh: 1 +--- !u!114 &1335454418943084614 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6021428339324464465} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 14 + m_FontStyle: 1 + m_BestFit: 1 + m_MinSize: 10 + m_MaxSize: 20 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: 'Average:' +--- !u!1 &6109184620530364220 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 4722110937780803752} + - component: {fileID: 2696435566306223433} + - component: {fileID: 5094385119256644395} + m_Layer: 5 + m_Name: Background + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &4722110937780803752 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6109184620530364220} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4831069612263668680} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &2696435566306223433 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6109184620530364220} + m_CullTransparentMesh: 1 +--- !u!114 &5094385119256644395 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6109184620530364220} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0, g: 0, b: 0, a: 0.627451} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 0} + m_Type: 0 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!1 &6188757312191444877 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 7848575700833239251} + - component: {fileID: 5649024345479520547} + - component: {fileID: 5970252233686725213} + m_Layer: 5 + m_Name: Threshold 2 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &7848575700833239251 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6188757312191444877} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 3852939355018933397} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0.5} + m_AnchorMax: {x: 1, y: 0.5} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 30} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &5649024345479520547 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6188757312191444877} + m_CullTransparentMesh: 1 +--- !u!114 &5970252233686725213 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6188757312191444877} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 8 + m_FontStyle: 0 + m_BestFit: 1 + m_MinSize: 5 + m_MaxSize: 15 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: 2 +--- !u!1 &9126926557282158578 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 4831069612263668680} + m_Layer: 5 + m_Name: StatisticsRenderGraph + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &4831069612263668680 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 9126926557282158578} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 4722110937780803752} + - {fileID: 1996528426999223811} + - {fileID: 8245948005965945133} + - {fileID: 7767554146471260023} + - {fileID: 517706616007655284} + - {fileID: 1197363314016853701} + - {fileID: 3141646064265744334} + - {fileID: 8724134812417612050} + - {fileID: 3852939355018933397} + m_Father: {fileID: 0} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} diff --git a/Assets/Photon/Fusion/Runtime/Statistics/Prefabs/StatisticsRenderGraph.prefab.meta b/Assets/Photon/Fusion/Runtime/Statistics/Prefabs/StatisticsRenderGraph.prefab.meta new file mode 100644 index 00000000..c9bb90ae --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/Statistics/Prefabs/StatisticsRenderGraph.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: a09170bc25723ba48b6d4d36f6201acd +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Runtime/Statistics/Resources.meta b/Assets/Photon/Fusion/Runtime/Statistics/Resources.meta new file mode 100644 index 00000000..2cec67a5 --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/Statistics/Resources.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7fb8caeac2583754bba8211a64c5937d +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Runtime/Statistics/Resources/FusionStatsResources.meta b/Assets/Photon/Fusion/Runtime/Statistics/Resources/FusionStatsResources.meta new file mode 100644 index 00000000..715e43dd --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/Statistics/Resources/FusionStatsResources.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 48d535affc4755b4ca6cc5030fcefe8e +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Runtime/Statistics/Resources/FusionStatsResources/FusionStatsRenderPanel.prefab b/Assets/Photon/Fusion/Runtime/Statistics/Resources/FusionStatsResources/FusionStatsRenderPanel.prefab new file mode 100644 index 00000000..b993c18d --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/Statistics/Resources/FusionStatsResources/FusionStatsRenderPanel.prefab @@ -0,0 +1,3812 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &296907524895270441 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 927423170172022377} + - component: {fileID: 6080766778120091937} + - component: {fileID: 2382256151208409527} + m_Layer: 5 + m_Name: Item Label + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &927423170172022377 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 296907524895270441} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 3837350979005311482} + m_RootOrder: 2 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 5, y: -0.5} + m_SizeDelta: {x: -30, y: -3} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &6080766778120091937 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 296907524895270441} + m_CullTransparentMesh: 1 +--- !u!114 &2382256151208409527 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 296907524895270441} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 14 + m_FontStyle: 0 + m_BestFit: 0 + m_MinSize: 10 + m_MaxSize: 40 + m_Alignment: 3 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: Option A +--- !u!1 &450196901384560306 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 6610307122184596111} + - component: {fileID: 7409514058102779475} + - component: {fileID: 427679676323379231} + m_Layer: 5 + m_Name: Anchors + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 0 +--- !u!224 &6610307122184596111 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 450196901384560306} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1168932652254079938} + - {fileID: 1686984425751832837} + - {fileID: 1124920397678522379} + - {fileID: 477196622607162314} + m_Father: {fileID: 572159163871529636} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 1} + m_AnchorMax: {x: 0, y: 1} + m_AnchoredPosition: {x: 318.73383, y: -140} + m_SizeDelta: {x: 537.46765, y: 280} + m_Pivot: {x: 0.5, y: 1} +--- !u!114 &7409514058102779475 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 450196901384560306} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 59f8146938fff824cb5fd77236b75775, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_ChildAlignment: 1 + m_Spacing: 50 + m_ChildForceExpandWidth: 1 + m_ChildForceExpandHeight: 1 + m_ChildControlWidth: 1 + m_ChildControlHeight: 0 + m_ChildScaleWidth: 0 + m_ChildScaleHeight: 0 + m_ReverseArrangement: 0 +--- !u!114 &427679676323379231 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 450196901384560306} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 3245ec927659c4140ac4f8d17403cc18, type: 3} + m_Name: + m_EditorClassIdentifier: + m_HorizontalFit: 0 + m_VerticalFit: 1 +--- !u!1 &917123943428587860 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 5285992156372422460} + - component: {fileID: 4632650506623703531} + - component: {fileID: 5977993800933949821} + - component: {fileID: 262217792482920898} + m_Layer: 5 + m_Name: Header + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &5285992156372422460 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 917123943428587860} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 5382875147398180877} + - {fileID: 3989361488725653217} + - {fileID: 7433301365304185931} + - {fileID: 5574684215558927408} + m_Father: {fileID: 8027230122607095318} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 50} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &4632650506623703531 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 917123943428587860} + m_CullTransparentMesh: 1 +--- !u!114 &5977993800933949821 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 917123943428587860} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.29803923, g: 0.29803923, b: 0.29803923, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 0} + m_Type: 0 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!114 &262217792482920898 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 917123943428587860} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0672ffa0bdc147e7b368e081ab5e7285, type: 3} + m_Name: + m_EditorClassIdentifier: + _statsHeaderTitle: {fileID: 881536996716943226} + _statsDropdown: {fileID: 2080231674002687154} + _defaultGraphPrefab: {fileID: 2059135424270465704, guid: 483568e790e53a243a4f436e924e90b9, + type: 3} + ContentRect: {fileID: 6292187591491288750} +--- !u!1 &1503813790580135071 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 4303828710957683955} + - component: {fileID: 1299675491587590411} + - component: {fileID: 6243284916778826150} + m_Layer: 5 + m_Name: Bottom + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &4303828710957683955 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1503813790580135071} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 224388680075742015} + - {fileID: 8814555744409120550} + m_Father: {fileID: 8027230122607095318} + m_RootOrder: 2 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 50} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &1299675491587590411 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1503813790580135071} + m_CullTransparentMesh: 1 +--- !u!114 &6243284916778826150 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1503813790580135071} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.29803923, g: 0.29803923, b: 0.29803923, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 0} + m_Type: 0 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!1 &1568532603313312427 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 4976424470216617084} + m_Layer: 5 + m_Name: Content + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &4976424470216617084 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1568532603313312427} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 3837350979005311482} + m_Father: {fileID: 8026527165385821844} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 1} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 28} + m_Pivot: {x: 0.5, y: 1} +--- !u!1 &1828022320003098605 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 6636497739420481598} + - component: {fileID: 3235681171821384415} + - component: {fileID: 8412750623390943344} + m_Layer: 5 + m_Name: Checkmark + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &6636497739420481598 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1828022320003098605} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 2403154199320671083} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &3235681171821384415 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1828022320003098605} + m_CullTransparentMesh: 1 +--- !u!114 &8412750623390943344 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1828022320003098605} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 10901, guid: 0000000000000000f000000000000000, type: 0} + m_Type: 0 + m_PreserveAspect: 1 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!1 &1883275715399744130 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1263552108005136549} + - component: {fileID: 1698066012933752592} + - component: {fileID: 1948862615874527565} + m_Layer: 5 + m_Name: Label + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1263552108005136549 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1883275715399744130} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 3989361488725653217} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: -12.5, y: 0} + m_SizeDelta: {x: -24.999998, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &1698066012933752592 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1883275715399744130} + m_CullTransparentMesh: 1 +--- !u!114 &1948862615874527565 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1883275715399744130} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 8 + m_FontStyle: 0 + m_BestFit: 1 + m_MinSize: 5 + m_MaxSize: 25 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: +--- !u!1 &1916770829566701656 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1124920397678522379} + - component: {fileID: 1662305527057851675} + - component: {fileID: 4264257475501652114} + m_Layer: 5 + m_Name: WorldAnchorText + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1124920397678522379 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1916770829566701656} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 6610307122184596111} + m_RootOrder: 2 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 1} + m_AnchorMax: {x: 0, y: 1} + m_AnchoredPosition: {x: 268.73383, y: -205} + m_SizeDelta: {x: 537.46765, y: 50} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &1662305527057851675 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1916770829566701656} + m_CullTransparentMesh: 1 +--- !u!114 &4264257475501652114 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1916770829566701656} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 14 + m_FontStyle: 0 + m_BestFit: 1 + m_MinSize: 10 + m_MaxSize: 40 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: Select a world anchor +--- !u!1 &2017222037046255706 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 8564924659068174899} + - component: {fileID: 6397794905303550562} + - component: {fileID: 6336409677060328531} + m_Layer: 5 + m_Name: Item Checkmark + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &8564924659068174899 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2017222037046255706} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 3837350979005311482} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0.5} + m_AnchorMax: {x: 0, y: 0.5} + m_AnchoredPosition: {x: 10, y: 0} + m_SizeDelta: {x: 20, y: 20} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &6397794905303550562 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2017222037046255706} + m_CullTransparentMesh: 1 +--- !u!114 &6336409677060328531 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2017222037046255706} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 10901, guid: 0000000000000000f000000000000000, type: 0} + m_Type: 0 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!1 &2075829086491720034 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2789940167209188642} + - component: {fileID: 657543604899880963} + - component: {fileID: 6526536338370985381} + m_Layer: 5 + m_Name: Label + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &2789940167209188642 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2075829086491720034} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 2555621151382365687} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 22.500006, y: -0.5} + m_SizeDelta: {x: -54.99999, y: -3} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &657543604899880963 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2075829086491720034} + m_CullTransparentMesh: 1 +--- !u!114 &6526536338370985381 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2075829086491720034} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 30 + m_FontStyle: 1 + m_BestFit: 0 + m_MinSize: 3 + m_MaxSize: 40 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: USE WORLD ANCHOR +--- !u!1 &2260606867377423930 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 4358828552806876014} + - component: {fileID: 7176401020106546730} + - component: {fileID: 8089818525740899139} + - component: {fileID: 4212281135499709642} + m_Layer: 5 + m_Name: Template + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 0 +--- !u!224 &4358828552806876014 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2260606867377423930} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 8026527165385821844} + - {fileID: 375958577868125381} + m_Father: {fileID: 3989361488725653217} + m_RootOrder: 2 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 0} + m_AnchoredPosition: {x: 0, y: 2} + m_SizeDelta: {x: 0, y: 300} + m_Pivot: {x: 0.5, y: 1} +--- !u!222 &7176401020106546730 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2260606867377423930} + m_CullTransparentMesh: 1 +--- !u!114 &8089818525740899139 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2260606867377423930} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.12941177, g: 0.121568635, b: 0.1137255, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!114 &4212281135499709642 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2260606867377423930} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 1aa08ab6e0800fa44ae55d278d1423e3, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Content: {fileID: 4976424470216617084} + m_Horizontal: 0 + m_Vertical: 1 + m_MovementType: 2 + m_Elasticity: 0.1 + m_Inertia: 1 + m_DecelerationRate: 0.135 + m_ScrollSensitivity: 1 + m_Viewport: {fileID: 8026527165385821844} + m_HorizontalScrollbar: {fileID: 0} + m_VerticalScrollbar: {fileID: 4191686406449404269} + m_HorizontalScrollbarVisibility: 0 + m_VerticalScrollbarVisibility: 2 + m_HorizontalScrollbarSpacing: 0 + m_VerticalScrollbarSpacing: -3 + m_OnValueChanged: + m_PersistentCalls: + m_Calls: [] +--- !u!1 &2395013929874465445 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2555621151382365687} + - component: {fileID: 6559128930672141901} + - component: {fileID: 6308134254595640437} + - component: {fileID: 5071677293573474134} + m_Layer: 5 + m_Name: UseWorldAnchorToggle + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &2555621151382365687 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2395013929874465445} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 2403154199320671083} + - {fileID: 2789940167209188642} + m_Father: {fileID: 572159163871529636} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 1} + m_AnchorMax: {x: 0, y: 1} + m_AnchoredPosition: {x: 318.73383, y: -82.5} + m_SizeDelta: {x: 537.46765, y: 65} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &6559128930672141901 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2395013929874465445} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 9085046f02f69544eb97fd06b6048fe2, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Navigation: + m_Mode: 3 + m_WrapAround: 0 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 1 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_SelectedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_SelectedTrigger: Selected + m_DisabledTrigger: Disabled + m_Interactable: 1 + m_TargetGraphic: {fileID: 6245781937633786945} + toggleTransition: 1 + graphic: {fileID: 8412750623390943344} + m_Group: {fileID: 0} + onValueChanged: + m_PersistentCalls: + m_Calls: + - m_Target: {fileID: 450196901384560306} + m_TargetAssemblyTypeName: UnityEngine.GameObject, UnityEngine + m_MethodName: SetActive + m_Mode: 0 + m_Arguments: + m_ObjectArgument: {fileID: 0} + m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine + m_IntArgument: 0 + m_FloatArgument: 0 + m_StringArgument: + m_BoolArgument: 0 + m_CallState: 2 + - m_Target: {fileID: 3957241495320675052} + m_TargetAssemblyTypeName: Fusion.Statistics.FusionStatsConfig, Fusion.Unity + m_MethodName: ToggleUseWorldAnchor + m_Mode: 0 + m_Arguments: + m_ObjectArgument: {fileID: 0} + m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine + m_IntArgument: 0 + m_FloatArgument: 0 + m_StringArgument: + m_BoolArgument: 0 + m_CallState: 2 + m_IsOn: 0 +--- !u!222 &6308134254595640437 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2395013929874465445} + m_CullTransparentMesh: 1 +--- !u!114 &5071677293573474134 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2395013929874465445} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.29411766, g: 0.29411766, b: 0.29411766, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 0} + m_Type: 0 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!1 &2613022872605145687 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2403154199320671083} + - component: {fileID: 4040038085056550764} + - component: {fileID: 6245781937633786945} + m_Layer: 5 + m_Name: Background + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &2403154199320671083 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2613022872605145687} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 6636497739420481598} + m_Father: {fileID: 2555621151382365687} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0.1} + m_AnchorMax: {x: 0, y: 0.9} + m_AnchoredPosition: {x: 25, y: 0} + m_SizeDelta: {x: 40, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &4040038085056550764 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2613022872605145687} + m_CullTransparentMesh: 1 +--- !u!114 &6245781937633786945 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2613022872605145687} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.19607845, g: 0.19607845, b: 0.19607845, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!1 &3171101988932277585 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 572159163871529636} + - component: {fileID: 7092181186932412521} + - component: {fileID: 2372842688621769211} + m_Layer: 5 + m_Name: ConfigContent + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &572159163871529636 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3171101988932277585} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 2555621151382365687} + - {fileID: 6610307122184596111} + m_Father: {fileID: 6838621023463656079} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: -335} + m_Pivot: {x: 0.5, y: 1} +--- !u!114 &7092181186932412521 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3171101988932277585} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 59f8146938fff824cb5fd77236b75775, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Padding: + m_Left: 50 + m_Right: 50 + m_Top: 50 + m_Bottom: 50 + m_ChildAlignment: 1 + m_Spacing: 25 + m_ChildForceExpandWidth: 1 + m_ChildForceExpandHeight: 1 + m_ChildControlWidth: 1 + m_ChildControlHeight: 0 + m_ChildScaleWidth: 0 + m_ChildScaleHeight: 0 + m_ReverseArrangement: 0 +--- !u!114 &2372842688621769211 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3171101988932277585} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 3245ec927659c4140ac4f8d17403cc18, type: 3} + m_Name: + m_EditorClassIdentifier: + m_HorizontalFit: 0 + m_VerticalFit: 2 +--- !u!1 &3301112477005502176 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1065890354712666684} + - component: {fileID: 609952629944027543} + - component: {fileID: 3881652386668956354} + m_Layer: 5 + m_Name: Arrow + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1065890354712666684 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3301112477005502176} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 3989361488725653217} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 1, y: 0.5} + m_AnchorMax: {x: 1, y: 0.5} + m_AnchoredPosition: {x: -15, y: 0} + m_SizeDelta: {x: 20, y: 20} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &609952629944027543 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3301112477005502176} + m_CullTransparentMesh: 1 +--- !u!114 &3881652386668956354 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3301112477005502176} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 10915, guid: 0000000000000000f000000000000000, type: 0} + m_Type: 0 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!1 &3331457412343644071 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1686984425751832837} + - component: {fileID: 6769806717441625033} + m_Layer: 5 + m_Name: Slider + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1686984425751832837 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3331457412343644071} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 4012602926370247461} + - {fileID: 6855401079373184632} + - {fileID: 3744544927640672967} + m_Father: {fileID: 6610307122184596111} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 1} + m_AnchorMax: {x: 0, y: 1} + m_AnchoredPosition: {x: 268.73383, y: -115} + m_SizeDelta: {x: 537.46765, y: 30} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &6769806717441625033 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3331457412343644071} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 67db9e8f0e2ae9c40bc1e2b64352a6b4, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Navigation: + m_Mode: 3 + m_WrapAround: 0 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 1 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_SelectedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_SelectedTrigger: Selected + m_DisabledTrigger: Disabled + m_Interactable: 1 + m_TargetGraphic: {fileID: 1705728362680999372} + m_FillRect: {fileID: 2188908086795456516} + m_HandleRect: {fileID: 7023102707314010424} + m_Direction: 0 + m_MinValue: 0.0001 + m_MaxValue: 0.015 + m_WholeNumbers: 0 + m_Value: 0.01 + m_OnValueChanged: + m_PersistentCalls: + m_Calls: + - m_Target: {fileID: 3957241495320675052} + m_TargetAssemblyTypeName: Fusion.Statistics.FusionStatsConfig, Fusion.Unity + m_MethodName: SetWorldCanvasScale + m_Mode: 0 + m_Arguments: + m_ObjectArgument: {fileID: 0} + m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine + m_IntArgument: 0 + m_FloatArgument: 0 + m_StringArgument: + m_BoolArgument: 0 + m_CallState: 2 +--- !u!1 &3463855378305203050 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1339279120340190741} + - component: {fileID: 4502413431868840935} + - component: {fileID: 8382138302434164689} + m_Layer: 5 + m_Name: Image + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1339279120340190741 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3463855378305203050} + m_LocalRotation: {x: 0, y: 0, z: 1, w: 0} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 8814555744409120550} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 180} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &4502413431868840935 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3463855378305203050} + m_CullTransparentMesh: 1 +--- !u!114 &8382138302434164689 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3463855378305203050} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 21300000, guid: 604f27d227e3caa44a1d6626d599fbdb, type: 3} + m_Type: 0 + m_PreserveAspect: 1 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!1 &3504210335582561714 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 693242371825235521} + - component: {fileID: 1755218764800685990} + - component: {fileID: 4452141900100473086} + m_Layer: 5 + m_Name: Handle + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &693242371825235521 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3504210335582561714} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 6252741372366799842} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 20, y: 20} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &1755218764800685990 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3504210335582561714} + m_CullTransparentMesh: 1 +--- !u!114 &4452141900100473086 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3504210335582561714} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.3019608, g: 0.2784314, b: 0.25882354, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!1 &3595094140606880989 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 5574684215558927408} + - component: {fileID: 41215807294970293} + - component: {fileID: 4065608549222381798} + m_Layer: 5 + m_Name: CloseButton + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &5574684215558927408 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3595094140606880989} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1591118638952534522} + m_Father: {fileID: 5285992156372422460} + m_RootOrder: 3 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.9, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &41215807294970293 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3595094140606880989} + m_CullTransparentMesh: 1 +--- !u!114 &4065608549222381798 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3595094140606880989} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Navigation: + m_Mode: 3 + m_WrapAround: 0 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 0 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_SelectedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_SelectedTrigger: Selected + m_DisabledTrigger: Disabled + m_Interactable: 1 + m_TargetGraphic: {fileID: 0} + m_OnClick: + m_PersistentCalls: + m_Calls: [] +--- !u!1 &3667798437314863526 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 4012602926370247461} + - component: {fileID: 1514754391495410048} + - component: {fileID: 4170836325630034280} + m_Layer: 5 + m_Name: Background + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &4012602926370247461 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3667798437314863526} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1686984425751832837} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0.25} + m_AnchorMax: {x: 1, y: 0.75} + m_AnchoredPosition: {x: 0.000061035156, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &1514754391495410048 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3667798437314863526} + m_CullTransparentMesh: 1 +--- !u!114 &4170836325630034280 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3667798437314863526} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 10907, guid: 0000000000000000f000000000000000, type: 0} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!1 &3810731356357453683 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 8814555744409120550} + - component: {fileID: 6473997784280870296} + - component: {fileID: 5058290458191783136} + m_Layer: 5 + m_Name: ConfigButton + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &8814555744409120550 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3810731356357453683} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1339279120340190741} + m_Father: {fileID: 4303828710957683955} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.1, y: 0} + m_AnchorMax: {x: 0.2, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &6473997784280870296 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3810731356357453683} + m_CullTransparentMesh: 1 +--- !u!114 &5058290458191783136 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3810731356357453683} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Navigation: + m_Mode: 3 + m_WrapAround: 0 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 0 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_SelectedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_SelectedTrigger: Selected + m_DisabledTrigger: Disabled + m_Interactable: 1 + m_TargetGraphic: {fileID: 0} + m_OnClick: + m_PersistentCalls: + m_Calls: + - m_Target: {fileID: 3957241495320675052} + m_TargetAssemblyTypeName: Fusion.Statistics.FusionStatsConfig, Fusion.Unity + m_MethodName: ToggleConfigPanel + m_Mode: 1 + m_Arguments: + m_ObjectArgument: {fileID: 0} + m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine + m_IntArgument: 0 + m_FloatArgument: 0 + m_StringArgument: + m_BoolArgument: 0 + m_CallState: 2 +--- !u!1 &4141254962398203782 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 5382875147398180877} + - component: {fileID: 4214712296660959222} + - component: {fileID: 871143328332220330} + m_Layer: 5 + m_Name: HideButton + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &5382875147398180877 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4141254962398203782} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 8658926502648632053} + m_Father: {fileID: 5285992156372422460} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0.1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &4214712296660959222 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4141254962398203782} + m_CullTransparentMesh: 1 +--- !u!114 &871143328332220330 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4141254962398203782} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Navigation: + m_Mode: 3 + m_WrapAround: 0 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 0 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_SelectedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_SelectedTrigger: Selected + m_DisabledTrigger: Disabled + m_Interactable: 1 + m_TargetGraphic: {fileID: 0} + m_OnClick: + m_PersistentCalls: + m_Calls: [] +--- !u!1 &4411715663831834403 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1591118638952534522} + - component: {fileID: 2849875383231486298} + - component: {fileID: 9173782273847465805} + m_Layer: 5 + m_Name: Image + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1591118638952534522 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4411715663831834403} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 5574684215558927408} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &2849875383231486298 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4411715663831834403} + m_CullTransparentMesh: 1 +--- !u!114 &9173782273847465805 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4411715663831834403} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 21300000, guid: 5853bc00e6bfda84ea9030e0486a1c44, type: 3} + m_Type: 0 + m_PreserveAspect: 1 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!1 &5018131404604097352 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 375958577868125381} + - component: {fileID: 8552835080833923373} + - component: {fileID: 6338734629592433320} + - component: {fileID: 4191686406449404269} + m_Layer: 5 + m_Name: Scrollbar + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &375958577868125381 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5018131404604097352} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 6252741372366799842} + m_Father: {fileID: 4358828552806876014} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 1, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 20, y: 0} + m_Pivot: {x: 1, y: 1} +--- !u!222 &8552835080833923373 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5018131404604097352} + m_CullTransparentMesh: 1 +--- !u!114 &6338734629592433320 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5018131404604097352} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.12941177, g: 0.121568635, b: 0.1137255, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 10907, guid: 0000000000000000f000000000000000, type: 0} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!114 &4191686406449404269 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5018131404604097352} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 2a4db7a114972834c8e4117be1d82ba3, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Navigation: + m_Mode: 3 + m_WrapAround: 0 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 1 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_SelectedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_SelectedTrigger: Selected + m_DisabledTrigger: Disabled + m_Interactable: 1 + m_TargetGraphic: {fileID: 4452141900100473086} + m_HandleRect: {fileID: 693242371825235521} + m_Direction: 2 + m_Value: 1 + m_Size: 1 + m_NumberOfSteps: 0 + m_OnValueChanged: + m_PersistentCalls: + m_Calls: [] +--- !u!1 &5712591298910240332 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 3837350979005311482} + - component: {fileID: 2933570309669979043} + m_Layer: 5 + m_Name: Item + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &3837350979005311482 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5712591298910240332} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 4792827706127554191} + - {fileID: 8564924659068174899} + - {fileID: 927423170172022377} + m_Father: {fileID: 4976424470216617084} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0.5} + m_AnchorMax: {x: 1, y: 0.5} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 50} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &2933570309669979043 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5712591298910240332} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 9085046f02f69544eb97fd06b6048fe2, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Navigation: + m_Mode: 3 + m_WrapAround: 0 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 1 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_SelectedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_SelectedTrigger: Selected + m_DisabledTrigger: Disabled + m_Interactable: 1 + m_TargetGraphic: {fileID: 5273467927689211623} + toggleTransition: 1 + graphic: {fileID: 6336409677060328531} + m_Group: {fileID: 0} + onValueChanged: + m_PersistentCalls: + m_Calls: [] + m_IsOn: 1 +--- !u!1 &5863773932699493694 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 8026527165385821844} + - component: {fileID: 7715101580831707659} + - component: {fileID: 1948704136448794893} + - component: {fileID: 453597718859586928} + m_Layer: 5 + m_Name: Viewport + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &8026527165385821844 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5863773932699493694} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 4976424470216617084} + m_Father: {fileID: 4358828552806876014} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: -17, y: 0} + m_Pivot: {x: 0, y: 1} +--- !u!222 &7715101580831707659 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5863773932699493694} + m_CullTransparentMesh: 1 +--- !u!114 &1948704136448794893 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5863773932699493694} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 10917, guid: 0000000000000000f000000000000000, type: 0} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!114 &453597718859586928 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5863773932699493694} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 31a19414c41e5ae4aae2af33fee712f6, type: 3} + m_Name: + m_EditorClassIdentifier: + m_ShowMaskGraphic: 0 +--- !u!1 &6209499070607545133 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 6838621023463656079} + - component: {fileID: 3142381927239967443} + - component: {fileID: 6554504833874049800} + - component: {fileID: 3957241495320675052} + - component: {fileID: 1162667201874202897} + - component: {fileID: 1218302641093858326} + m_Layer: 5 + m_Name: ConfigPanel + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 0 +--- !u!224 &6838621023463656079 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6209499070607545133} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 572159163871529636} + m_Father: {fileID: 6249409201006848425} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &3142381927239967443 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6209499070607545133} + m_CullTransparentMesh: 1 +--- !u!114 &6554504833874049800 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6209499070607545133} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.19607845, g: 0.19607845, b: 0.19607845, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 10907, guid: 0000000000000000f000000000000000, type: 0} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!114 &3957241495320675052 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6209499070607545133} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 586894e5cee94d0e857cf1dbc7716646, type: 3} + m_Name: + m_EditorClassIdentifier: + _worldAnchorButtonPrefab: {fileID: 4920168020725175909, guid: 8e3a30446d6463e48adea83d18ce9654, + type: 3} + _worldAnchorListContainer: {fileID: 477196622607162314} + _configPanel: {fileID: 6209499070607545133} + _canvas: {fileID: 6292187590335715621} + _renderPanelRectTransform: {fileID: 6292187590335715618} +--- !u!114 &1162667201874202897 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6209499070607545133} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 3312d7739989d2b4e91e6319e9a96d76, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Padding: {x: 0, y: 0, z: 0, w: 0} + m_Softness: {x: 0, y: 0} +--- !u!114 &1218302641093858326 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6209499070607545133} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 1aa08ab6e0800fa44ae55d278d1423e3, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Content: {fileID: 572159163871529636} + m_Horizontal: 0 + m_Vertical: 1 + m_MovementType: 1 + m_Elasticity: 0.1 + m_Inertia: 1 + m_DecelerationRate: 0.135 + m_ScrollSensitivity: 1 + m_Viewport: {fileID: 6838621023463656079} + m_HorizontalScrollbar: {fileID: 0} + m_VerticalScrollbar: {fileID: 0} + m_HorizontalScrollbarVisibility: 0 + m_VerticalScrollbarVisibility: 0 + m_HorizontalScrollbarSpacing: 0 + m_VerticalScrollbarSpacing: 0 + m_OnValueChanged: + m_PersistentCalls: + m_Calls: [] +--- !u!1 &6292187590335715622 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 6292187590335715618} + - component: {fileID: 6292187590335715621} + - component: {fileID: 6292187590335715620} + - component: {fileID: 6292187590335715623} + - component: {fileID: 2886547366966261910} + m_Layer: 5 + m_Name: FusionStatsRenderPanel + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &6292187590335715618 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6292187590335715622} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 0, y: 0, z: 0} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 8027230122607095318} + m_Father: {fileID: 0} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0, y: 0} +--- !u!223 &6292187590335715621 +Canvas: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6292187590335715622} + m_Enabled: 1 + serializedVersion: 3 + m_RenderMode: 0 + m_Camera: {fileID: 0} + m_PlaneDistance: 100 + m_PixelPerfect: 0 + m_ReceivesEvents: 1 + m_OverrideSorting: 0 + m_OverridePixelPerfect: 0 + m_SortingBucketNormalizedSize: 0 + m_VertexColorAlwaysGammaSpace: 0 + m_AdditionalShaderChannelsFlag: 0 + m_SortingLayerID: 0 + m_SortingOrder: 1000 + m_TargetDisplay: 0 +--- !u!114 &6292187590335715620 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6292187590335715622} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3} + m_Name: + m_EditorClassIdentifier: + m_UiScaleMode: 1 + m_ReferencePixelsPerUnit: 100 + m_ScaleFactor: 1 + m_ReferenceResolution: {x: 1920, y: 1080} + m_ScreenMatchMode: 0 + m_MatchWidthOrHeight: 0.5 + m_PhysicalUnit: 3 + m_FallbackScreenDPI: 96 + m_DefaultSpriteDPI: 96 + m_DynamicPixelsPerUnit: 1 + m_PresetInfoIsWorld: 0 +--- !u!114 &6292187590335715623 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6292187590335715622} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: dc42784cf147c0c48a680349fa168899, type: 3} + m_Name: + m_EditorClassIdentifier: + m_IgnoreReversedGraphics: 1 + m_BlockingObjects: 0 + m_BlockingMask: + serializedVersion: 2 + m_Bits: 32 +--- !u!114 &2886547366966261910 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6292187590335715622} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 25ab394f9fec45f692c631c30839e2ee, type: 3} + m_Name: + m_EditorClassIdentifier: + _canvas: {fileID: 6292187590335715621} + _canvasScaler: {fileID: 6292187590335715620} + _canvasPanel: {fileID: 8027230122607095318} + _contentPanel: {fileID: 6249409201006848425} + _contentContainer: {fileID: 6292187591491288750} + _bottomPanel: {fileID: 4303828710957683955} + _header: {fileID: 262217792482920898} + _hideButton: {fileID: 871143328332220330} + _closeButton: {fileID: 4065608549222381798} + _config: {fileID: 3957241495320675052} +--- !u!1 &6292187591151371836 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 6292187591151371837} + - component: {fileID: 6292187591151371835} + - component: {fileID: 6292187591151371834} + - component: {fileID: 7901287736355797325} + - component: {fileID: 8893587270098083440} + m_Layer: 5 + m_Name: ChartPanel + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &6292187591151371837 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6292187591151371836} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 6292187591491288750} + m_Father: {fileID: 6249409201006848425} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &6292187591151371835 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6292187591151371836} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 3312d7739989d2b4e91e6319e9a96d76, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Padding: {x: 0, y: 0, z: 0, w: 0} + m_Softness: {x: 0, y: 0} +--- !u!114 &6292187591151371834 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6292187591151371836} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 1aa08ab6e0800fa44ae55d278d1423e3, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Content: {fileID: 6292187591491288750} + m_Horizontal: 0 + m_Vertical: 1 + m_MovementType: 1 + m_Elasticity: 0.1 + m_Inertia: 1 + m_DecelerationRate: 0.135 + m_ScrollSensitivity: 1 + m_Viewport: {fileID: 6292187591151371837} + m_HorizontalScrollbar: {fileID: 0} + m_VerticalScrollbar: {fileID: 0} + m_HorizontalScrollbarVisibility: 0 + m_VerticalScrollbarVisibility: 0 + m_HorizontalScrollbarSpacing: 0 + m_VerticalScrollbarSpacing: 0 + m_OnValueChanged: + m_PersistentCalls: + m_Calls: [] +--- !u!222 &7901287736355797325 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6292187591151371836} + m_CullTransparentMesh: 1 +--- !u!114 &8893587270098083440 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6292187591151371836} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 0} + m_Type: 0 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!1 &6292187591491288737 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 6292187591491288750} + - component: {fileID: 6292187591491288751} + - component: {fileID: 3171255269551313185} + m_Layer: 5 + m_Name: Content + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &6292187591491288750 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6292187591491288737} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 6292187591151371837} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: -0.00061035156} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 1} +--- !u!114 &6292187591491288751 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6292187591491288737} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 59f8146938fff824cb5fd77236b75775, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_ChildAlignment: 1 + m_Spacing: 10 + m_ChildForceExpandWidth: 1 + m_ChildForceExpandHeight: 1 + m_ChildControlWidth: 1 + m_ChildControlHeight: 0 + m_ChildScaleWidth: 0 + m_ChildScaleHeight: 0 + m_ReverseArrangement: 0 +--- !u!114 &3171255269551313185 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6292187591491288737} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 3245ec927659c4140ac4f8d17403cc18, type: 3} + m_Name: + m_EditorClassIdentifier: + m_HorizontalFit: 0 + m_VerticalFit: 2 +--- !u!1 &6507627528404376700 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1852896463748910887} + - component: {fileID: 6943075781912058280} + - component: {fileID: 1340238071988593647} + m_Layer: 5 + m_Name: Image + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1852896463748910887 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6507627528404376700} + m_LocalRotation: {x: 0, y: 0, z: 1, w: 0} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 224388680075742015} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 180} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &6943075781912058280 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6507627528404376700} + m_CullTransparentMesh: 1 +--- !u!114 &1340238071988593647 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6507627528404376700} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 21300000, guid: d3f33adb909e26443914391fba875e4c, type: 3} + m_Type: 0 + m_PreserveAspect: 1 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!1 &6515669774179580333 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 4792827706127554191} + - component: {fileID: 418627333846033938} + - component: {fileID: 5273467927689211623} + m_Layer: 5 + m_Name: Item Background + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &4792827706127554191 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6515669774179580333} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 3837350979005311482} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &418627333846033938 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6515669774179580333} + m_CullTransparentMesh: 1 +--- !u!114 &5273467927689211623 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6515669774179580333} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.3019608, g: 0.2784314, b: 0.25882354, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 0} + m_Type: 0 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!1 &6744478828054370314 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 477196622607162314} + - component: {fileID: 7217487843922329809} + - component: {fileID: 7526098132316199424} + m_Layer: 5 + m_Name: Buttons + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &477196622607162314 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6744478828054370314} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 6610307122184596111} + m_RootOrder: 3 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 1} + m_AnchorMax: {x: 0, y: 1} + m_AnchoredPosition: {x: 268.73383, y: -280} + m_SizeDelta: {x: 537.46765, y: 0} + m_Pivot: {x: 0.5, y: 1} +--- !u!114 &7217487843922329809 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6744478828054370314} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 59f8146938fff824cb5fd77236b75775, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_ChildAlignment: 1 + m_Spacing: 20 + m_ChildForceExpandWidth: 1 + m_ChildForceExpandHeight: 1 + m_ChildControlWidth: 1 + m_ChildControlHeight: 0 + m_ChildScaleWidth: 0 + m_ChildScaleHeight: 0 + m_ReverseArrangement: 0 +--- !u!114 &7526098132316199424 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6744478828054370314} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 3245ec927659c4140ac4f8d17403cc18, type: 3} + m_Name: + m_EditorClassIdentifier: + m_HorizontalFit: 0 + m_VerticalFit: 1 +--- !u!1 &6877641876292919014 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 224388680075742015} + - component: {fileID: 171484904497770328} + - component: {fileID: 6528907354992410554} + m_Layer: 5 + m_Name: ResetPositionButton + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &224388680075742015 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6877641876292919014} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1852896463748910887} + m_Father: {fileID: 4303828710957683955} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.8, y: 0} + m_AnchorMax: {x: 0.9, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &171484904497770328 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6877641876292919014} + m_CullTransparentMesh: 1 +--- !u!114 &6528907354992410554 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6877641876292919014} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Navigation: + m_Mode: 3 + m_WrapAround: 0 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 0 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_SelectedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_SelectedTrigger: Selected + m_DisabledTrigger: Disabled + m_Interactable: 1 + m_TargetGraphic: {fileID: 0} + m_OnClick: + m_PersistentCalls: + m_Calls: + - m_Target: {fileID: 2886547366966261910} + m_TargetAssemblyTypeName: Fusion.Statistics.FusionStatsCanvas, Fusion.Unity + m_MethodName: SnapPanelBackToOriginPos + m_Mode: 1 + m_Arguments: + m_ObjectArgument: {fileID: 0} + m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine + m_IntArgument: 0 + m_FloatArgument: 0 + m_StringArgument: + m_BoolArgument: 0 + m_CallState: 2 +--- !u!1 &7231760756432212803 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 3989361488725653217} + - component: {fileID: 3982928290895880973} + - component: {fileID: 5136569984234807193} + - component: {fileID: 2080231674002687154} + m_Layer: 5 + m_Name: Dropdown (Legacy) + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &3989361488725653217 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7231760756432212803} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1263552108005136549} + - {fileID: 1065890354712666684} + - {fileID: 4358828552806876014} + m_Father: {fileID: 5285992156372422460} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.1, y: 0} + m_AnchorMax: {x: 0.4, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &3982928290895880973 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7231760756432212803} + m_CullTransparentMesh: 1 +--- !u!114 &5136569984234807193 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7231760756432212803} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.29803923, g: 0.29803923, b: 0.29803923, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 0} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!114 &2080231674002687154 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7231760756432212803} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0d0b652f32a2cc243917e4028fa0f046, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Navigation: + m_Mode: 3 + m_WrapAround: 0 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 1 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_SelectedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_SelectedTrigger: Selected + m_DisabledTrigger: Disabled + m_Interactable: 1 + m_TargetGraphic: {fileID: 5136569984234807193} + m_Template: {fileID: 4358828552806876014} + m_CaptionText: {fileID: 1948862615874527565} + m_CaptionImage: {fileID: 0} + m_ItemText: {fileID: 2382256151208409527} + m_ItemImage: {fileID: 0} + m_Value: 0 + m_Options: + m_Options: [] + m_OnValueChanged: + m_PersistentCalls: + m_Calls: [] + m_AlphaFadeSpeed: 0.15 +--- !u!1 &7525954125703251587 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 6252741372366799842} + m_Layer: 5 + m_Name: Sliding Area + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &6252741372366799842 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7525954125703251587} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 693242371825235521} + m_Father: {fileID: 375958577868125381} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: -20, y: -20} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!1 &7598987002355063944 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 3744544927640672967} + m_Layer: 5 + m_Name: Handle Slide Area + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &3744544927640672967 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7598987002355063944} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 7023102707314010424} + m_Father: {fileID: 1686984425751832837} + m_RootOrder: 2 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0.000061035156, y: 0} + m_SizeDelta: {x: -20, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!1 &7604973467647894474 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1168932652254079938} + - component: {fileID: 8402788901407489174} + - component: {fileID: 3531757581058593943} + m_Layer: 5 + m_Name: ScaleText + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1168932652254079938 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7604973467647894474} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 6610307122184596111} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 1} + m_AnchorMax: {x: 0, y: 1} + m_AnchoredPosition: {x: 268.73383, y: -25} + m_SizeDelta: {x: 537.46765, y: 50} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &8402788901407489174 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7604973467647894474} + m_CullTransparentMesh: 1 +--- !u!114 &3531757581058593943 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7604973467647894474} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 14 + m_FontStyle: 0 + m_BestFit: 1 + m_MinSize: 10 + m_MaxSize: 40 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: Scale canvas ( reaply world anchor ) +--- !u!1 &8107707257880827151 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 7023102707314010424} + - component: {fileID: 1745521605012915971} + - component: {fileID: 1705728362680999372} + m_Layer: 5 + m_Name: Handle + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &7023102707314010424 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8107707257880827151} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 3744544927640672967} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 20, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &1745521605012915971 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8107707257880827151} + m_CullTransparentMesh: 1 +--- !u!114 &1705728362680999372 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8107707257880827151} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 10913, guid: 0000000000000000f000000000000000, type: 0} + m_Type: 0 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!1 &8313513399928304450 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 8658926502648632053} + - component: {fileID: 8720588274503342997} + - component: {fileID: 6002329679024451180} + m_Layer: 5 + m_Name: Image + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &8658926502648632053 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8313513399928304450} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 5382875147398180877} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &8720588274503342997 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8313513399928304450} + m_CullTransparentMesh: 1 +--- !u!114 &6002329679024451180 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8313513399928304450} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 21300000, guid: 330babd635d5dfc4691ae3151ffc4491, type: 3} + m_Type: 0 + m_PreserveAspect: 1 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!1 &8670642868029845811 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2188908086795456516} + - component: {fileID: 7286782285552522392} + - component: {fileID: 3427099167128688909} + m_Layer: 5 + m_Name: Fill + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &2188908086795456516 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8670642868029845811} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 6855401079373184632} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 1} + m_AnchoredPosition: {x: -0.000061035156, y: 0} + m_SizeDelta: {x: 10, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &7286782285552522392 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8670642868029845811} + m_CullTransparentMesh: 1 +--- !u!114 &3427099167128688909 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8670642868029845811} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!1 &8831250793518815267 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 6249409201006848425} + m_Layer: 5 + m_Name: ContentPanel + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &6249409201006848425 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8831250793518815267} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 6292187591151371837} + - {fileID: 6838621023463656079} + m_Father: {fileID: 8027230122607095318} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 150} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!1 &8836683132837878985 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 8027230122607095318} + - component: {fileID: 1436372552552797792} + - component: {fileID: 561972637311690088} + m_Layer: 5 + m_Name: Panel + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &8027230122607095318 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8836683132837878985} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 5285992156372422460} + - {fileID: 6249409201006848425} + - {fileID: 4303828710957683955} + m_Father: {fileID: 6292187590335715618} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: -907, y: 0} + m_SizeDelta: {x: 638, y: 0} + m_Pivot: {x: 0.5, y: 1} +--- !u!114 &1436372552552797792 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8836683132837878985} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 59f8146938fff824cb5fd77236b75775, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_ChildAlignment: 1 + m_Spacing: 0 + m_ChildForceExpandWidth: 1 + m_ChildForceExpandHeight: 1 + m_ChildControlWidth: 1 + m_ChildControlHeight: 0 + m_ChildScaleWidth: 0 + m_ChildScaleHeight: 0 + m_ReverseArrangement: 0 +--- !u!114 &561972637311690088 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8836683132837878985} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 3245ec927659c4140ac4f8d17403cc18, type: 3} + m_Name: + m_EditorClassIdentifier: + m_HorizontalFit: 0 + m_VerticalFit: 1 +--- !u!1 &8911041559424233441 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 6855401079373184632} + m_Layer: 5 + m_Name: Fill Area + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &6855401079373184632 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8911041559424233441} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 2188908086795456516} + m_Father: {fileID: 1686984425751832837} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0.25} + m_AnchorMax: {x: 1, y: 0.75} + m_AnchoredPosition: {x: -5, y: 0} + m_SizeDelta: {x: -20, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!1 &9094959809584265358 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 7433301365304185931} + - component: {fileID: 1970464735477762513} + - component: {fileID: 881536996716943226} + m_Layer: 5 + m_Name: Title + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &7433301365304185931 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 9094959809584265358} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 5285992156372422460} + m_RootOrder: 2 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.4, y: 0} + m_AnchorMax: {x: 0.6, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &1970464735477762513 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 9094959809584265358} + m_CullTransparentMesh: 1 +--- !u!114 &881536996716943226 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 9094959809584265358} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 14 + m_FontStyle: 0 + m_BestFit: 1 + m_MinSize: 8 + m_MaxSize: 25 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: Title diff --git a/Assets/Photon/Fusion/Runtime/Statistics/Resources/FusionStatsResources/FusionStatsRenderPanel.prefab.meta b/Assets/Photon/Fusion/Runtime/Statistics/Resources/FusionStatsResources/FusionStatsRenderPanel.prefab.meta new file mode 100644 index 00000000..5343fc36 --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/Statistics/Resources/FusionStatsResources/FusionStatsRenderPanel.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 367c5caf25960d8419724c6c96708538 +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Runtime/Statistics/Resources/FusionStatsResources/NetworkObjectStatistics.prefab b/Assets/Photon/Fusion/Runtime/Statistics/Resources/FusionStatsResources/NetworkObjectStatistics.prefab new file mode 100644 index 00000000..49c25a56 --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/Statistics/Resources/FusionStatsResources/NetworkObjectStatistics.prefab @@ -0,0 +1,1754 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &4429093212114442652 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 9217485777233084089} + - component: {fileID: 7288890198270706181} + - component: {fileID: 5025754270617725529} + m_Layer: 5 + m_Name: ToggleButton + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &9217485777233084089 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4429093212114442652} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 2909819280916653775} + m_Father: {fileID: 7935946543829100456} + m_RootOrder: 3 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0.1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &7288890198270706181 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4429093212114442652} + m_CullTransparentMesh: 1 +--- !u!114 &5025754270617725529 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4429093212114442652} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Navigation: + m_Mode: 3 + m_WrapAround: 0 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 0 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_SelectedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_SelectedTrigger: Selected + m_DisabledTrigger: Disabled + m_Interactable: 1 + m_TargetGraphic: {fileID: 0} + m_OnClick: + m_PersistentCalls: + m_Calls: + - m_Target: {fileID: 7935946544523926548} + m_TargetAssemblyTypeName: Fusion.Statistics.FusionNetworkObjectStatsGraphCombine, + Fusion.Unity + m_MethodName: ToggleRenderDisplay + m_Mode: 1 + m_Arguments: + m_ObjectArgument: {fileID: 0} + m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine + m_IntArgument: 0 + m_FloatArgument: 0 + m_StringArgument: + m_BoolArgument: 0 + m_CallState: 2 +--- !u!1 &7935946542549790127 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 7935946542549790124} + - component: {fileID: 7935946542549790114} + - component: {fileID: 7935946542549790125} + m_Layer: 5 + m_Name: Arrow + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &7935946542549790124 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7935946542549790127} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 7935946542638416901} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 1, y: 0.5} + m_AnchorMax: {x: 1, y: 0.5} + m_AnchoredPosition: {x: -15, y: 0} + m_SizeDelta: {x: 20, y: 20} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &7935946542549790114 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7935946542549790127} + m_CullTransparentMesh: 1 +--- !u!114 &7935946542549790125 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7935946542549790127} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 10915, guid: 0000000000000000f000000000000000, type: 0} + m_Type: 0 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!1 &7935946542604869272 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 7935946542604869273} + m_Layer: 5 + m_Name: Content + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &7935946542604869273 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7935946542604869272} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 7935946544613686829} + m_Father: {fileID: 7935946542954877197} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 1} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 28} + m_Pivot: {x: 0.5, y: 1} +--- !u!1 &7935946542638416900 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 7935946542638416901} + - component: {fileID: 7935946542638417016} + - component: {fileID: 7935946542638417019} + - component: {fileID: 7935946542638417018} + m_Layer: 5 + m_Name: Dropdown (Legacy) + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &7935946542638416901 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7935946542638416900} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 7935946543866924156} + - {fileID: 7935946542549790124} + - {fileID: 7935946543980191648} + m_Father: {fileID: 7935946543829100456} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.1, y: 0} + m_AnchorMax: {x: 0.4, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: -10, y: -10} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &7935946542638417016 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7935946542638416900} + m_CullTransparentMesh: 1 +--- !u!114 &7935946542638417019 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7935946542638416900} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.29803923, g: 0.29803923, b: 0.29803923, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 0} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!114 &7935946542638417018 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7935946542638416900} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0d0b652f32a2cc243917e4028fa0f046, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Navigation: + m_Mode: 3 + m_WrapAround: 0 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 1 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_SelectedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_SelectedTrigger: Selected + m_DisabledTrigger: Disabled + m_Interactable: 1 + m_TargetGraphic: {fileID: 7935946542638417019} + m_Template: {fileID: 7935946543980191648} + m_CaptionText: {fileID: 7935946543866924157} + m_CaptionImage: {fileID: 0} + m_ItemText: {fileID: 7935946544552512572} + m_ItemImage: {fileID: 0} + m_Value: 0 + m_Options: + m_Options: [] + m_OnValueChanged: + m_PersistentCalls: + m_Calls: [] + m_AlphaFadeSpeed: 0.15 +--- !u!1 &7935946542954877196 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 7935946542954877197} + - component: {fileID: 7935946542954877184} + - component: {fileID: 7935946542954877187} + - component: {fileID: 7935946542954877186} + m_Layer: 5 + m_Name: Viewport + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &7935946542954877197 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7935946542954877196} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 7935946542604869273} + m_Father: {fileID: 7935946543980191648} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: -17, y: 0} + m_Pivot: {x: 0, y: 1} +--- !u!222 &7935946542954877184 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7935946542954877196} + m_CullTransparentMesh: 1 +--- !u!114 &7935946542954877187 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7935946542954877196} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 10917, guid: 0000000000000000f000000000000000, type: 0} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!114 &7935946542954877186 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7935946542954877196} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 31a19414c41e5ae4aae2af33fee712f6, type: 3} + m_Name: + m_EditorClassIdentifier: + m_ShowMaskGraphic: 0 +--- !u!1 &7935946543486103709 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 7935946543486103698} + - component: {fileID: 7935946543486103696} + - component: {fileID: 7935946543486103699} + m_Layer: 5 + m_Name: Title + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &7935946543486103698 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7935946543486103709} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 7935946543829100456} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.4, y: 0} + m_AnchorMax: {x: 0.9, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: -10} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &7935946543486103696 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7935946543486103709} + m_CullTransparentMesh: 1 +--- !u!114 &7935946543486103699 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7935946543486103709} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 14 + m_FontStyle: 0 + m_BestFit: 1 + m_MinSize: 5 + m_MaxSize: 20 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: Network Object ID (Name) +--- !u!1 &7935946543795778886 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 7935946543795778887} + - component: {fileID: 7935946543795779002} + - component: {fileID: 7935946543795778884} + m_Layer: 5 + m_Name: CloseButton + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &7935946543795778887 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7935946543795778886} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 7935946544367666318} + m_Father: {fileID: 7935946543829100456} + m_RootOrder: 2 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.9, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &7935946543795779002 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7935946543795778886} + m_CullTransparentMesh: 1 +--- !u!114 &7935946543795778884 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7935946543795778886} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Navigation: + m_Mode: 3 + m_WrapAround: 0 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 0 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_SelectedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_SelectedTrigger: Selected + m_DisabledTrigger: Disabled + m_Interactable: 1 + m_TargetGraphic: {fileID: 0} + m_OnClick: + m_PersistentCalls: + m_Calls: + - m_Target: {fileID: 7935946544523926548} + m_TargetAssemblyTypeName: Fusion.Statistics.FusionNetworkObjectStatsGraphCombine, + Fusion.Unity + m_MethodName: DestroyCombinedGraph + m_Mode: 1 + m_Arguments: + m_ObjectArgument: {fileID: 0} + m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine + m_IntArgument: 0 + m_FloatArgument: 0 + m_StringArgument: + m_BoolArgument: 0 + m_CallState: 2 +--- !u!1 &7935946543813294334 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 7935946543813294335} + - component: {fileID: 7935946543813294333} + - component: {fileID: 7935946543813294332} + m_Layer: 5 + m_Name: Item Background + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &7935946543813294335 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7935946543813294334} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 7935946544613686829} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &7935946543813294333 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7935946543813294334} + m_CullTransparentMesh: 1 +--- !u!114 &7935946543813294332 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7935946543813294334} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.3019608, g: 0.2784314, b: 0.25882354, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 0} + m_Type: 0 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!1 &7935946543829100459 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 7935946543829100456} + - component: {fileID: 7935946543829100462} + - component: {fileID: 7935946543829100457} + m_Layer: 5 + m_Name: NetworkObjectStatsCombineHeader + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &7935946543829100456 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7935946543829100459} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 7935946542638416901} + - {fileID: 7935946543486103698} + - {fileID: 7935946543795778887} + - {fileID: 9217485777233084089} + m_Father: {fileID: 7935946544523926550} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 1} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: -25} + m_SizeDelta: {x: 0, y: 30} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &7935946543829100462 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7935946543829100459} + m_CullTransparentMesh: 1 +--- !u!114 &7935946543829100457 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7935946543829100459} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.3529412, g: 0.5882353, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 0} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!1 &7935946543866924159 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 7935946543866924156} + - component: {fileID: 7935946543866924146} + - component: {fileID: 7935946543866924157} + m_Layer: 5 + m_Name: Label + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &7935946543866924156 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7935946543866924159} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 7935946542638416901} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: -12.5, y: 0} + m_SizeDelta: {x: -24.999998, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &7935946543866924146 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7935946543866924159} + m_CullTransparentMesh: 1 +--- !u!114 &7935946543866924157 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7935946543866924159} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 8 + m_FontStyle: 0 + m_BestFit: 1 + m_MinSize: 5 + m_MaxSize: 25 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: +--- !u!1 &7935946543980191651 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 7935946543980191648} + - component: {fileID: 7935946543980191655} + - component: {fileID: 7935946543980191654} + - component: {fileID: 7935946543980191649} + m_Layer: 5 + m_Name: Template + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 0 +--- !u!224 &7935946543980191648 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7935946543980191651} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 7935946542954877197} + - {fileID: 7935946544015447456} + m_Father: {fileID: 7935946542638416901} + m_RootOrder: 2 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 0} + m_AnchoredPosition: {x: 0, y: 2} + m_SizeDelta: {x: 0, y: 150} + m_Pivot: {x: 0.5, y: 1} +--- !u!222 &7935946543980191655 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7935946543980191651} + m_CullTransparentMesh: 1 +--- !u!114 &7935946543980191654 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7935946543980191651} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.12941177, g: 0.121568635, b: 0.1137255, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!114 &7935946543980191649 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7935946543980191651} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 1aa08ab6e0800fa44ae55d278d1423e3, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Content: {fileID: 7935946542604869273} + m_Horizontal: 0 + m_Vertical: 1 + m_MovementType: 2 + m_Elasticity: 0.1 + m_Inertia: 1 + m_DecelerationRate: 0.135 + m_ScrollSensitivity: 1 + m_Viewport: {fileID: 7935946542954877197} + m_HorizontalScrollbar: {fileID: 0} + m_VerticalScrollbar: {fileID: 7935946544015447457} + m_HorizontalScrollbarVisibility: 0 + m_VerticalScrollbarVisibility: 2 + m_HorizontalScrollbarSpacing: 0 + m_VerticalScrollbarSpacing: -3 + m_OnValueChanged: + m_PersistentCalls: + m_Calls: [] +--- !u!1 &7935946544015447459 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 7935946544015447456} + - component: {fileID: 7935946544015447463} + - component: {fileID: 7935946544015447462} + - component: {fileID: 7935946544015447457} + m_Layer: 5 + m_Name: Scrollbar + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &7935946544015447456 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7935946544015447459} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 7935946544258524343} + m_Father: {fileID: 7935946543980191648} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 1, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 20, y: 0} + m_Pivot: {x: 1, y: 1} +--- !u!222 &7935946544015447463 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7935946544015447459} + m_CullTransparentMesh: 1 +--- !u!114 &7935946544015447462 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7935946544015447459} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.12941177, g: 0.121568635, b: 0.1137255, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 10907, guid: 0000000000000000f000000000000000, type: 0} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!114 &7935946544015447457 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7935946544015447459} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 2a4db7a114972834c8e4117be1d82ba3, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Navigation: + m_Mode: 3 + m_WrapAround: 0 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 1 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_SelectedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_SelectedTrigger: Selected + m_DisabledTrigger: Disabled + m_Interactable: 1 + m_TargetGraphic: {fileID: 7935946544067754001} + m_HandleRect: {fileID: 7935946544067754000} + m_Direction: 2 + m_Value: 0 + m_Size: 1 + m_NumberOfSteps: 0 + m_OnValueChanged: + m_PersistentCalls: + m_Calls: [] +--- !u!1 &7935946544040279663 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 7935946544040279660} + - component: {fileID: 7935946544040279650} + - component: {fileID: 7935946544040279661} + m_Layer: 5 + m_Name: Item Checkmark + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &7935946544040279660 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7935946544040279663} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 7935946544613686829} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0.5} + m_AnchorMax: {x: 0, y: 0.5} + m_AnchoredPosition: {x: 10, y: 0} + m_SizeDelta: {x: 20, y: 20} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &7935946544040279650 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7935946544040279663} + m_CullTransparentMesh: 1 +--- !u!114 &7935946544040279661 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7935946544040279663} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 10901, guid: 0000000000000000f000000000000000, type: 0} + m_Type: 0 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!1 &7935946544067754003 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 7935946544067754000} + - component: {fileID: 7935946544067754006} + - component: {fileID: 7935946544067754001} + m_Layer: 5 + m_Name: Handle + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &7935946544067754000 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7935946544067754003} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 7935946544258524343} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 20, y: 20} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &7935946544067754006 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7935946544067754003} + m_CullTransparentMesh: 1 +--- !u!114 &7935946544067754001 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7935946544067754003} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.3019608, g: 0.2784314, b: 0.25882354, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!1 &7935946544172031220 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 7935946544172031221} + - component: {fileID: 7935946544172031210} + - component: {fileID: 6314402665509014801} + m_Layer: 5 + m_Name: StatsRenders + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &7935946544172031221 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7935946544172031220} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 7935946544523926550} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: -50} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 1} +--- !u!114 &7935946544172031210 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7935946544172031220} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 59f8146938fff824cb5fd77236b75775, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Padding: + m_Left: 0 + m_Right: 0 + m_Top: 0 + m_Bottom: 0 + m_ChildAlignment: 0 + m_Spacing: 0 + m_ChildForceExpandWidth: 1 + m_ChildForceExpandHeight: 1 + m_ChildControlWidth: 1 + m_ChildControlHeight: 0 + m_ChildScaleWidth: 0 + m_ChildScaleHeight: 0 + m_ReverseArrangement: 0 +--- !u!114 &6314402665509014801 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7935946544172031220} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 3245ec927659c4140ac4f8d17403cc18, type: 3} + m_Name: + m_EditorClassIdentifier: + m_HorizontalFit: 0 + m_VerticalFit: 2 +--- !u!1 &7935946544258524342 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 7935946544258524343} + m_Layer: 5 + m_Name: Sliding Area + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &7935946544258524343 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7935946544258524342} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 7935946544067754000} + m_Father: {fileID: 7935946544015447456} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: -20, y: -20} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!1 &7935946544367666313 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 7935946544367666318} + - component: {fileID: 7935946544367666316} + - component: {fileID: 7935946544367666319} + m_Layer: 5 + m_Name: Icon + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &7935946544367666318 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7935946544367666313} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 7935946543795778887} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &7935946544367666316 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7935946544367666313} + m_CullTransparentMesh: 1 +--- !u!114 &7935946544367666319 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7935946544367666313} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 21300000, guid: 5853bc00e6bfda84ea9030e0486a1c44, type: 3} + m_Type: 0 + m_PreserveAspect: 1 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!1 &7935946544523926545 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 7935946544523926550} + - component: {fileID: 7935946544523926551} + - component: {fileID: 7935946544523926548} + m_Layer: 5 + m_Name: NetworkObjectStatistics + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &7935946544523926550 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7935946544523926545} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 7935946543829100456} + - {fileID: 7935946544172031221} + m_Father: {fileID: 0} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 400, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &7935946544523926551 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7935946544523926545} + m_CullTransparentMesh: 1 +--- !u!114 &7935946544523926548 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7935946544523926545} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 19334f7ed30f4e8ea9f48a51534f09dd, type: 3} + m_Name: + m_EditorClassIdentifier: + _titleText: {fileID: 7935946543486103699} + _statDropdown: {fileID: 7935946542638417018} + _statsToRender: 0 + _rect: {fileID: 7935946544523926550} + _combinedGraphRender: {fileID: 7935946544172031221} + _toggleButton: {fileID: 5025754270617725529} + _statsGraphPrefab: {fileID: 1719597503058426706, guid: 302a42a4eb4d5e540b803b1f86597505, + type: 3} +--- !u!1 &7935946544552512574 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 7935946544552512575} + - component: {fileID: 7935946544552512573} + - component: {fileID: 7935946544552512572} + m_Layer: 5 + m_Name: Item Label + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &7935946544552512575 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7935946544552512574} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 7935946544613686829} + m_RootOrder: 2 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 5, y: -0.5} + m_SizeDelta: {x: -30, y: -3} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &7935946544552512573 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7935946544552512574} + m_CullTransparentMesh: 1 +--- !u!114 &7935946544552512572 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7935946544552512574} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 14 + m_FontStyle: 0 + m_BestFit: 0 + m_MinSize: 10 + m_MaxSize: 40 + m_Alignment: 3 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: Option A +--- !u!1 &7935946544613686828 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 7935946544613686829} + - component: {fileID: 7935946544613686818} + m_Layer: 5 + m_Name: Item + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &7935946544613686829 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7935946544613686828} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 7935946543813294335} + - {fileID: 7935946544040279660} + - {fileID: 7935946544552512575} + m_Father: {fileID: 7935946542604869273} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0.5} + m_AnchorMax: {x: 1, y: 0.5} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 50} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &7935946544613686818 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 7935946544613686828} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 9085046f02f69544eb97fd06b6048fe2, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Navigation: + m_Mode: 3 + m_WrapAround: 0 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 1 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_SelectedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_SelectedTrigger: Selected + m_DisabledTrigger: Disabled + m_Interactable: 1 + m_TargetGraphic: {fileID: 7935946543813294332} + toggleTransition: 1 + graphic: {fileID: 7935946544040279661} + m_Group: {fileID: 0} + onValueChanged: + m_PersistentCalls: + m_Calls: [] + m_IsOn: 1 +--- !u!1 &8309871081496572797 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2909819280916653775} + - component: {fileID: 8076674677568403127} + - component: {fileID: 7247707786847415638} + m_Layer: 5 + m_Name: Icon + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &2909819280916653775 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8309871081496572797} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 9217485777233084089} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &8076674677568403127 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8309871081496572797} + m_CullTransparentMesh: 1 +--- !u!114 &7247707786847415638 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8309871081496572797} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 21300000, guid: 330babd635d5dfc4691ae3151ffc4491, type: 3} + m_Type: 0 + m_PreserveAspect: 1 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 diff --git a/Assets/Photon/Fusion/Runtime/Statistics/Resources/FusionStatsResources/NetworkObjectStatistics.prefab.meta b/Assets/Photon/Fusion/Runtime/Statistics/Resources/FusionStatsResources/NetworkObjectStatistics.prefab.meta new file mode 100644 index 00000000..7daf810f --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/Statistics/Resources/FusionStatsResources/NetworkObjectStatistics.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 7cc7f808e527f6e4f98257acbf47ad87 +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Runtime/Statistics/StatsIcons.meta b/Assets/Photon/Fusion/Runtime/Statistics/StatsIcons.meta new file mode 100644 index 00000000..cd67b396 --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/Statistics/StatsIcons.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 9547e9072f77f75438aff775104614e7 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Runtime/Statistics/StatsIcons/FusionStatsCancelIcon.png b/Assets/Photon/Fusion/Runtime/Statistics/StatsIcons/FusionStatsCancelIcon.png new file mode 100644 index 00000000..3c43155c Binary files /dev/null and b/Assets/Photon/Fusion/Runtime/Statistics/StatsIcons/FusionStatsCancelIcon.png differ diff --git a/Assets/Photon/Fusion/Runtime/Statistics/StatsIcons/FusionStatsCancelIcon.png.meta b/Assets/Photon/Fusion/Runtime/Statistics/StatsIcons/FusionStatsCancelIcon.png.meta new file mode 100644 index 00000000..dbb01548 --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/Statistics/StatsIcons/FusionStatsCancelIcon.png.meta @@ -0,0 +1,135 @@ +fileFormatVersion: 2 +guid: 5853bc00e6bfda84ea9030e0486a1c44 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 12 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + ignoreMasterTextureLimit: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 200 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + cookieLightType: 1 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 256 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 256 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Server + maxTextureSize: 256 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Android + maxTextureSize: 256 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + nameFileIdTable: {} + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Runtime/Statistics/StatsIcons/FusionStatsGearIcon.png b/Assets/Photon/Fusion/Runtime/Statistics/StatsIcons/FusionStatsGearIcon.png new file mode 100644 index 00000000..60c9a141 Binary files /dev/null and b/Assets/Photon/Fusion/Runtime/Statistics/StatsIcons/FusionStatsGearIcon.png differ diff --git a/Assets/Photon/Fusion/Runtime/Statistics/StatsIcons/FusionStatsGearIcon.png.meta b/Assets/Photon/Fusion/Runtime/Statistics/StatsIcons/FusionStatsGearIcon.png.meta new file mode 100644 index 00000000..f5c01b0d --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/Statistics/StatsIcons/FusionStatsGearIcon.png.meta @@ -0,0 +1,108 @@ +fileFormatVersion: 2 +guid: 604f27d227e3caa44a1d6626d599fbdb +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 200 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 128 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 128 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Runtime/Statistics/StatsIcons/FusionStatsResetIcon.png b/Assets/Photon/Fusion/Runtime/Statistics/StatsIcons/FusionStatsResetIcon.png new file mode 100644 index 00000000..4b8db911 Binary files /dev/null and b/Assets/Photon/Fusion/Runtime/Statistics/StatsIcons/FusionStatsResetIcon.png differ diff --git a/Assets/Photon/Fusion/Runtime/Statistics/StatsIcons/FusionStatsResetIcon.png.meta b/Assets/Photon/Fusion/Runtime/Statistics/StatsIcons/FusionStatsResetIcon.png.meta new file mode 100644 index 00000000..1fb856e3 --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/Statistics/StatsIcons/FusionStatsResetIcon.png.meta @@ -0,0 +1,108 @@ +fileFormatVersion: 2 +guid: d3f33adb909e26443914391fba875e4c +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 200 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 256 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 256 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Runtime/Statistics/StatsIcons/FusionStatsToggleArrowIcon.png b/Assets/Photon/Fusion/Runtime/Statistics/StatsIcons/FusionStatsToggleArrowIcon.png new file mode 100644 index 00000000..5a6322f7 Binary files /dev/null and b/Assets/Photon/Fusion/Runtime/Statistics/StatsIcons/FusionStatsToggleArrowIcon.png differ diff --git a/Assets/Photon/Fusion/Runtime/Statistics/StatsIcons/FusionStatsToggleArrowIcon.png.meta b/Assets/Photon/Fusion/Runtime/Statistics/StatsIcons/FusionStatsToggleArrowIcon.png.meta new file mode 100644 index 00000000..aabb732c --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/Statistics/StatsIcons/FusionStatsToggleArrowIcon.png.meta @@ -0,0 +1,108 @@ +fileFormatVersion: 2 +guid: 330babd635d5dfc4691ae3151ffc4491 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 0 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 1 + wrapV: 1 + wrapW: 0 + nPOTScale: 0 + lightmap: 0 + compressionQuality: 50 + spriteMode: 1 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 200 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 1 + spriteTessellationDetail: -1 + textureType: 8 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 128 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 3 + buildTarget: Standalone + maxTextureSize: 128 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: 5e97eb03825dee720800000000000000 + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Runtime/Utilities.meta b/Assets/Photon/Fusion/Runtime/Utilities.meta new file mode 100644 index 00000000..1e146a95 --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/Utilities.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 9e22d12b23fafcf42a3b720254974ed9 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Runtime/Utilities/RunnerVisibility.meta b/Assets/Photon/Fusion/Runtime/Utilities/RunnerVisibility.meta new file mode 100644 index 00000000..c4921a3a --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/Utilities/RunnerVisibility.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5ac8b97c4c9f1e34ca713fc9ba9144be +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Runtime/Utilities/RunnerVisibility/EnableOnSingleRunner.cs b/Assets/Photon/Fusion/Runtime/Utilities/RunnerVisibility/EnableOnSingleRunner.cs new file mode 100644 index 00000000..2c8d0c5d --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/Utilities/RunnerVisibility/EnableOnSingleRunner.cs @@ -0,0 +1,120 @@ + +using System.Collections.Generic; +using Fusion.Analyzer; +using UnityEngine; + +namespace Fusion { + + #if UNITY_EDITOR + using UnityEditor; + #endif + + + /// + /// Automatically adds a for each indicated component. + /// These indicated components will be limited to no more than one enabled instance when running in Multi-Peer mode. + /// + [AddComponentMenu("Fusion/Enable On Single Runner")] + public class EnableOnSingleRunner : Fusion.Behaviour { + + /// + /// If more than one runner instance is visible, this indicates which peer's clone of this entity should be visible. + /// + [InlineHelp] + [SerializeField] +#pragma warning disable IDE0044 // Add readonly modifier + public RunnerVisibilityLink.PreferredRunners PreferredRunner; +#pragma warning restore IDE0044 // Add readonly modifier + + /// + /// Collection of components that will be marked for Multi-Peer mode as objects that should only have one enabled instance. + /// + [InlineHelp] + public UnityEngine.Component[] Components = new Component[0]; + + /// + /// Prefix for the GUIDs of components which are added at runtime. + /// + [HideInInspector] + [SerializeField] + private string _guid = System.Guid.NewGuid().ToString().Substring(0, 19); + + /// + /// At runtime startup, this adds a for each component reference to this GameObject. + /// + internal void AddNodes(List existingNodes) { + for(int i = 0; i < Components.Length; ++i) { + var found = false; + foreach (var existingNode in existingNodes) { + if (existingNode.Component == Components[i]) { + found = true; + break; + } + } + + if (found) continue; + var node = Components[i].gameObject.AddComponent(); + node.Guid = _guid + i; + node.Component = Components[i]; + node.SetupOnSingleRunnerLink(PreferredRunner); + existingNodes.Add(node); + } + } + + /// + /// Finds visual/audio components on this GameObject, and adds them to the Components collection. + /// + [EditorButton("Find on GameObject", EditorButtonVisibility.EditMode, dirtyObject: true)] + public void FindRecognizedTypes() { + Components = FindRecognizedComponentsOnGameObject(gameObject); + } + + /// + /// Finds visual/audio nested components on this GameObject and its children, and adds them to the Components collection. + /// + [EditorButton("Find in Nested Children", EditorButtonVisibility.EditMode, dirtyObject: true)] + public void FindNestedRecognizedTypes() { + Components = FindRecognizedNestedComponents(gameObject); + } + + [StaticField(StaticFieldResetMode.None)] + private static readonly List reusableComponentsList = new List(); + [StaticField(StaticFieldResetMode.None)] + private static readonly List reusableComponentsList2 = new List(); + + private static Component[] FindRecognizedComponentsOnGameObject(GameObject go) { + try { + go.GetComponents(reusableComponentsList); + reusableComponentsList2.Clear(); + foreach (var comp in reusableComponentsList) { + var type = comp.GetType(); + if (type.IsRecognizedByRunnerVisibility()) { + reusableComponentsList2.Add(comp); + } + } + return reusableComponentsList2.ToArray(); + } finally { + reusableComponentsList.Clear(); + reusableComponentsList2.Clear(); + } + } + + private static Component[] FindRecognizedNestedComponents(GameObject go) { + try { + go.transform.GetNestedComponentsInChildren(reusableComponentsList, true); + reusableComponentsList2.Clear(); + foreach (var comp in reusableComponentsList) { + var type = comp.GetType(); + if (type.IsRecognizedByRunnerVisibility()) { + reusableComponentsList2.Add(comp); + } + } + return reusableComponentsList2.ToArray(); + } finally { + reusableComponentsList.Clear(); + reusableComponentsList2.Clear(); + } + } + } +} + diff --git a/Assets/Photon/Fusion/Runtime/Utilities/RunnerVisibility/EnableOnSingleRunner.cs.meta b/Assets/Photon/Fusion/Runtime/Utilities/RunnerVisibility/EnableOnSingleRunner.cs.meta new file mode 100644 index 00000000..07030e87 --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/Utilities/RunnerVisibility/EnableOnSingleRunner.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d73b0a95be81246699cf0d9ecdc01863 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Runtime/Utilities/RunnerVisibility/RunnerAOIGizmos.cs b/Assets/Photon/Fusion/Runtime/Utilities/RunnerVisibility/RunnerAOIGizmos.cs new file mode 100644 index 00000000..215ba3ab --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/Utilities/RunnerVisibility/RunnerAOIGizmos.cs @@ -0,0 +1,85 @@ +namespace Fusion { + using System; + using System.Collections.Generic; + using UnityEngine; + + /// + /// Add this Component to the NetworkRunner Prefab or GameObject. If Interest Management is enabled in NetworkProjectConfig ReplicationFeatures, + /// gizmos will be shown that indicate active Area Of Interest cells. These gizmos are currently NOT applicable to Shared Mode and will only + /// render for the Server/Host peer. + /// + [RequireComponent(typeof(NetworkRunner))] + [ScriptHelp(BackColor = ScriptHeaderBackColor.Sand)] + [DisallowMultipleComponent] + public class RunnerAOIGizmos : SimulationBehaviour { +#if UNITY_EDITOR + + [Flags] + public enum GizmoOptionsEnum { + ShowActiveServerZones = 1, + ShowPlayerInterest = 2, + } + + [System.Serializable] + public struct CustomOptions { + public Color ServerZonesColor; + public Color PlayerInterestColor; + } + + [ExpandableEnum(AlwaysExpanded = true)] + public GizmoOptionsEnum GizmoOptions = GizmoOptionsEnum.ShowActiveServerZones | GizmoOptionsEnum.ShowPlayerInterest; + + public CustomOptions Customization = new CustomOptions() { + ServerZonesColor = new Color(0.25f, 0.25f, 0.25f, 0.75f), + PlayerInterestColor = new Color(255f / 255f, 21f / 255f, 21 / 255f, 0.2f), + }; + + private List<(Vector3 center, Vector3 size, int playerCount, int objectCount)> _reusableGizmoData; + + private void OnEnabled() { + + } + + private void OnDrawGizmos() { + + if (enabled == false) { + return; + } + + if (GizmoOptions == 0) { + return; + } + + var runner = Runner; + + if ((object)runner == null || runner.IsRunning == false) { + return; + } + + var datas = _reusableGizmoData ??= new List<(Vector3 center, Vector3 size, int playerCount, int objectCount)>(); + var colors = Customization; + + runner.GetAreaOfInterestGizmoData(datas); + + for (int i = 0; i < datas.Count; i++) { + var data = datas[i]; + var c = datas[i].center; + var s = datas[i].size; + + // Draw server actives zone boxes + if (data.objectCount > 0) { + Gizmos.color = colors.ServerZonesColor; + Gizmos.DrawWireCube(data.center, data.size); + } + + // Draw player interest regions + if (data.playerCount > 0) { + Gizmos.color = colors.PlayerInterestColor; + Gizmos.DrawCube(c, s); + } + } + Gizmos.color = Color.white; + } +#endif + } +} \ No newline at end of file diff --git a/Assets/Photon/Fusion/Runtime/Utilities/RunnerVisibility/RunnerAOIGizmos.cs.meta b/Assets/Photon/Fusion/Runtime/Utilities/RunnerVisibility/RunnerAOIGizmos.cs.meta new file mode 100644 index 00000000..b9ecbac4 --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/Utilities/RunnerVisibility/RunnerAOIGizmos.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a09b7c3c2f0604fef93beb7057001dbb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Runtime/Utilities/RunnerVisibility/RunnerEnableVisibility.cs b/Assets/Photon/Fusion/Runtime/Utilities/RunnerVisibility/RunnerEnableVisibility.cs new file mode 100644 index 00000000..07574bd5 --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/Utilities/RunnerVisibility/RunnerEnableVisibility.cs @@ -0,0 +1,85 @@ +namespace Fusion { + using System; + using System.Collections.Generic; + using Sockets; + using UnityEngine; + + /// + /// When running in Multi-Peer mode, this component automatically will register the associated + /// with , + /// and will automatically attach loaded scene objects and spawned objects with the peers visibility handling. + /// + [ScriptHelp(BackColor = ScriptHeaderBackColor.Sand)] + [DisallowMultipleComponent] + public class RunnerEnableVisibility : Behaviour, INetworkRunnerCallbacks { + + private void Awake() { + var runner = GetComponentInParent(); + if (runner) { + // Optimistically register this as if we are running multi-peer (can't know yet) + runner.EnableVisibilityExtension(); + + // Just to be safe against double registration. + runner.ObjectAcquired -= RunnerOnObjectAcquired; + runner.ObjectAcquired += RunnerOnObjectAcquired; + } + } + + private void OnDestroy() { + if (TryGetComponent(out var runner)) { + runner.DisableVisibilityExtension(); + runner.RemoveCallbacks(this); + runner.ObjectAcquired -= RunnerOnObjectAcquired; + } + } + + private void RunnerOnObjectAcquired(NetworkRunner runner, NetworkObject obj) { + if (runner.IsRunning == false) return; + if (runner.Config.PeerMode == NetworkProjectConfig.PeerModes.Single) { + Destroy(this); + return; + } + + runner.AddVisibilityNodes(obj.gameObject); + } + + public void OnReliableDataProgress(NetworkRunner runner, PlayerRef player, ReliableKey key, float progress) { + } + + void INetworkRunnerCallbacks.OnSceneLoadDone(NetworkRunner runner) { + if (runner.IsRunning == false) return; + if (runner.Config.PeerMode == NetworkProjectConfig.PeerModes.Single) { + Destroy(this); + return; + } + + var scene = runner.SimulationUnityScene; + + if (scene.IsValid()) + foreach (var obj in scene.GetRootGameObjects()) + runner.AddVisibilityNodes(obj); + } + + #region Unused + + void INetworkRunnerCallbacks.OnObjectExitAOI(NetworkRunner runner, NetworkObject obj, PlayerRef player) { } + void INetworkRunnerCallbacks.OnObjectEnterAOI(NetworkRunner runner, NetworkObject obj, PlayerRef player) { } + void INetworkRunnerCallbacks.OnPlayerJoined(NetworkRunner runner, PlayerRef player) { } + void INetworkRunnerCallbacks.OnPlayerLeft(NetworkRunner runner, PlayerRef player) { } + void INetworkRunnerCallbacks.OnInput(NetworkRunner runner, NetworkInput input) { } + void INetworkRunnerCallbacks.OnInputMissing(NetworkRunner runner, PlayerRef player, NetworkInput input) { } + void INetworkRunnerCallbacks.OnShutdown(NetworkRunner runner, ShutdownReason shutdownReason) { } + void INetworkRunnerCallbacks.OnConnectedToServer(NetworkRunner runner) { } + void INetworkRunnerCallbacks.OnDisconnectedFromServer(NetworkRunner runner, NetDisconnectReason reason) { } + void INetworkRunnerCallbacks.OnConnectRequest(NetworkRunner runner, NetworkRunnerCallbackArgs.ConnectRequest request, byte[] token) { } + void INetworkRunnerCallbacks.OnConnectFailed(NetworkRunner runner, NetAddress remoteAddress, NetConnectFailedReason reason) { } + void INetworkRunnerCallbacks.OnUserSimulationMessage(NetworkRunner runner, SimulationMessagePtr message) { } + void INetworkRunnerCallbacks.OnSessionListUpdated(NetworkRunner runner, List sessionList) { } + void INetworkRunnerCallbacks.OnCustomAuthenticationResponse(NetworkRunner runner, Dictionary data) { } + void INetworkRunnerCallbacks.OnHostMigration(NetworkRunner runner, HostMigrationToken hostMigrationToken) { } + void INetworkRunnerCallbacks.OnReliableDataReceived(NetworkRunner runner, PlayerRef player, ReliableKey key, ArraySegment data) { } + void INetworkRunnerCallbacks.OnSceneLoadStart(NetworkRunner runner) { } + + #endregion + } +} \ No newline at end of file diff --git a/Assets/Photon/Fusion/Runtime/Utilities/RunnerVisibility/RunnerEnableVisibility.cs.meta b/Assets/Photon/Fusion/Runtime/Utilities/RunnerVisibility/RunnerEnableVisibility.cs.meta new file mode 100644 index 00000000..cdb508e8 --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/Utilities/RunnerVisibility/RunnerEnableVisibility.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b8edbe7108d8a43ab838cf8b8d9453df +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Runtime/Utilities/RunnerVisibility/RunnerLagCompensationGizmos.cs b/Assets/Photon/Fusion/Runtime/Utilities/RunnerVisibility/RunnerLagCompensationGizmos.cs new file mode 100644 index 00000000..3416067b --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/Utilities/RunnerVisibility/RunnerLagCompensationGizmos.cs @@ -0,0 +1,80 @@ +namespace Fusion +{ + using LagCompensation; + using UnityEngine; + + [ScriptHelp(BackColor = ScriptHeaderBackColor.Sand)] + [DisallowMultipleComponent] + public class RunnerLagCompensationGizmos : Behaviour + { + public bool DrawSnapshotHistory; + public bool DrawBroadphaseNodes; + + public Color StateAuthHitboxCollor = Color.green; + public Color NonStateAuthHitboxCollor = Color.cyan; + + private NetworkRunner _runner; + + private void Awake() + { + _runner = GetComponentInParent(); + + if (_runner == null) { + Debug.LogWarning($"{this} was not able to find the NetworkRunner reference. Destroying the component."); + Destroy(this); + } + } + + private void OnDrawGizmos() + { + if (_runner == null || _runner.IsRunning == false || _runner.GetVisible() == false || _runner.LagCompensation?.DrawInfo == default) return; + + if (DrawBroadphaseNodes) + { + RenderBHVBroadphase(); + } + + if (DrawSnapshotHistory) + { + RenderHitboxHistory(); + } + } + + private void RenderHitboxHistory() + { + Gizmos.color = _runner.IsServer ? StateAuthHitboxCollor : NonStateAuthHitboxCollor; + + foreach (var snapshotDrawInfo in _runner.LagCompensation.DrawInfo.SnapshotHistoryDraw) { + foreach (var colliderDrawInfo in snapshotDrawInfo) { + Gizmos.matrix = colliderDrawInfo.LocalToWorldMatrix; + switch (colliderDrawInfo.Type) { + case HitboxTypes.Box: + Gizmos.DrawWireCube(colliderDrawInfo.Offset, colliderDrawInfo.BoxExtents * 2); + break; + case HitboxTypes.Sphere: + Gizmos.DrawWireSphere(colliderDrawInfo.Offset, colliderDrawInfo.Radius); + break; + case HitboxTypes.Capsule: + LagCompensationDraw.GizmosDrawWireCapsule(colliderDrawInfo.CapsuleTopCenter, colliderDrawInfo.CapsuleBottomCenter, colliderDrawInfo.Radius); + break; + default: + Debug.LogWarning($"HitboxType {colliderDrawInfo.Type} not supported to draw."); + break; + } + } + } + + Gizmos.matrix = Matrix4x4.identity; + } + + private void RenderBHVBroadphase() + { + var initialColor = Color.green; + + foreach (var nodeDrawInfo in _runner.LagCompensation.DrawInfo.BVHDraw) { + Gizmos.color = initialColor + Color.red * nodeDrawInfo.Depth / nodeDrawInfo.MaxDepth; + Gizmos.DrawWireCube(nodeDrawInfo.Bounds.center, nodeDrawInfo.Bounds.size); + } + } + } +} \ No newline at end of file diff --git a/Assets/Photon/Fusion/Runtime/Utilities/RunnerVisibility/RunnerLagCompensationGizmos.cs.meta b/Assets/Photon/Fusion/Runtime/Utilities/RunnerVisibility/RunnerLagCompensationGizmos.cs.meta new file mode 100644 index 00000000..6d6f9f96 --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/Utilities/RunnerVisibility/RunnerLagCompensationGizmos.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7551cd937fd7ae84eb38a9dbbcd67c6f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Runtime/Utilities/RunnerVisibility/RunnerVisibilityLink.cs b/Assets/Photon/Fusion/Runtime/Utilities/RunnerVisibility/RunnerVisibilityLink.cs new file mode 100644 index 00000000..04096cd7 --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/Utilities/RunnerVisibility/RunnerVisibilityLink.cs @@ -0,0 +1,245 @@ + +namespace Fusion { + + using System; + using System.Collections.Generic; + using Fusion.Analyzer; + using UnityEngine; + + /// + /// Flags a MonoBehaviour class as a RunnerVisibilityControl recognized type. + /// Will be included in runner visibility handling, and will be found by component finds. + /// + public interface IRunnerVisibilityRecognizedType { + + } + + /// + /// Identifies visible/audible components (such as renderers, canvases, lights) that should be enabled/disabled by runner visibility handling. + /// Automatically added to scene objects and spawned objects during play if running in . + /// Additionally this component can be added manually at development time to identify specific Behaviours or Renderers you would like to restrict to one enabled copy at a time. + /// + [AddComponentMenu("")] + public sealed class RunnerVisibilityLink : MonoBehaviour { + + /// + /// The peer runner that will be used if more than one runner is visible, and this node was manually added by developer (indicating only one instance should be visible at a time). + /// + public enum PreferredRunners { + /// + /// The first visible runner will be used. + /// + Auto, + /// + /// The server peer/runner will be used if visible. + /// + Server, + /// + /// The first client peer/runner will be used if visible. + /// + Client, + /// + /// The components will only be enabled on the instance that has input authority over the NetworkObject. Unlike the other options, this expects a NetworkObject to work and it will search its children and parents for it. + /// + InputAuthority, + } + + private enum ComponentType { + None, + Renderer, + Behaviour + } + + /// + /// If more than one runner instance is visible, this indicates which peer's clone of this entity should be visible. + /// + [SerializeField] +#pragma warning disable IDE0044 // Add readonly modifier + public PreferredRunners PreferredRunner; +#pragma warning restore IDE0044 // Add readonly modifier + + /// + /// The associated component with this node. This Behaviour or Renderer will be enabled/disabled when its NetworkRunner.IsVisible value is changed. + /// + public Component Component; + + public bool IsOnSingleRunner { get; private set; } + + /// + /// Guid is used for common objects (user flagged components that should only run in one instance), to identify matching clones. + /// + [SerializeField] + [ReadOnly] + internal string Guid; + + // TODO: This can be removed later. Here for backwards compat for the short term as users may still be using this component. + // Ultimately this component will always be invisible. + [SerializeField] + [HideInInspector] + internal bool _showAtRuntime; + + // cached runtime + internal NetworkRunner _runner; + private ComponentType _componentType; + private NetworkObject _networkObject; + private bool _originalState; + + /// + /// Set to false to indicate that this object should remain disabled even when is set to true. + /// + public bool DefaultState { + get { + return _originalState; + } + set { + _originalState = value; + } + } + + // internal LinkedListNode _node; + + internal bool Enabled { + get { return _componentType == ComponentType.Renderer ? (Component as Renderer).enabled : (Component as UnityEngine.Behaviour).enabled; } + set { + if (Component == null) { + return; + } + if (_componentType == ComponentType.Renderer) + (Component as Renderer).enabled = value; + else { + (Component as UnityEngine.Behaviour).enabled = value; + } + } + } + + + // TODO: Can be removed most likely now that Node is not user accessible. + // Reset finds the first viable component and automatically adds it + private void Reset() { + _showAtRuntime = true; + Guid = System.Guid.NewGuid().ToString(); + } + + private bool AssociateComponent(Component component) { + Component = component; + var type = component.GetType(); + if (component as Renderer != null) { + _componentType = ComponentType.Renderer; + return true; + } else if (component as UnityEngine.Behaviour != null) { + _componentType = ComponentType.Behaviour; + return true; + } + return false; + } + + private void OnValidate() { + + if (Component != null) { + if (Component.transform != transform) { + Debug.LogWarning($"{nameof(RunnerVisibilityLink)} can only be associated with components on the same GameObject."); + Component = null; + return; + } + + if (AssociateComponent(Component)) + return; + + Debug.LogWarning($"{nameof(RunnerVisibilityLink)} can only be associated with Components that can be enabled/disabled."); + Component = null; + } + } + + private void Awake() { + // TODO: once deprecated, make this flag always the case and remove the bool check. + if (!_showAtRuntime) + this.hideFlags = HideFlags.HideInInspector; + } + + private void OnDestroy() { + this.UnregisterNode(); + } + + internal void Initialize(UnityEngine.Component comp, NetworkRunner runner) { + _runner = runner; + + // First look into children + _networkObject = GetComponentInChildren(); + if (!_networkObject) + _networkObject = GetComponentInParent(); + + if (!_networkObject && PreferredRunner == PreferredRunners.InputAuthority) + Log.Warn($"No NetworkObject found for RunnerVisibilityLink on {gameObject.name} with preferred runner as Input Authority. EnableOnSingleRunner will always disable it."); + + if (comp is Renderer renderer) { + _componentType = ComponentType.Renderer; + _originalState = renderer.enabled; + renderer.enabled = runner.GetVisible() && _originalState; + //_node = node; + Component = comp; + } else if (comp is UnityEngine.Behaviour behaviour) { + _componentType = ComponentType.Behaviour; + _originalState = behaviour.enabled; + behaviour.enabled = runner.GetVisible() && _originalState; + // _node = node; + Component = comp; + } + } + + /// + /// Sets the visibility state of this node. + /// + /// + public void SetEnabled(bool enabled) { + if (enabled) { + + // If this object was originally disabled, we will want to keep it that way, unless it looks like the user enabled the object directly since the last time this was called. + if (_originalState == false) { + + // TODO: These only partially work + // User has directly enabled this object - assume it is meant to be enabled + if (Enabled) { + _originalState = true; + } else { + // original state was disabled, so leave it that way. + return; + } + } + Enabled = true; + + } else { + + // TODO: These only partially work + // Detect/store if user has manually disabled the component + //if (_originalState == true && Enabled == false) { + // _originalState = false; + //} + + Enabled = false; + } + } + + internal bool IsInputAuth() { + if (_networkObject && _networkObject.IsValid) { + return _networkObject.HasInputAuthority; + } + + return false; + } + + internal void SetupOnSingleRunnerLink(PreferredRunners preferredRunner) { + PreferredRunner = preferredRunner; + IsOnSingleRunner = true; + } + + internal void InvokeRefreshCommonObjectVisibilities(float time) { + StopAllCoroutines(); + Invoke(nameof(RetryRefreshCommonLinks), time); + } + + private void RetryRefreshCommonLinks() { + NetworkRunnerVisibilityExtensions.RetryRefreshCommonLinks(); + } + } +} + diff --git a/Assets/Photon/Fusion/Runtime/Utilities/RunnerVisibility/RunnerVisibilityLink.cs.meta b/Assets/Photon/Fusion/Runtime/Utilities/RunnerVisibility/RunnerVisibilityLink.cs.meta new file mode 100644 index 00000000..0fc02be1 --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/Utilities/RunnerVisibility/RunnerVisibilityLink.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 972a7cfee50c64022a98777550cfb7d6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/Runtime/Utilities/RunnerVisibility/RunnerVisibilityLinksRoot.cs b/Assets/Photon/Fusion/Runtime/Utilities/RunnerVisibility/RunnerVisibilityLinksRoot.cs new file mode 100644 index 00000000..e14bc641 --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/Utilities/RunnerVisibility/RunnerVisibilityLinksRoot.cs @@ -0,0 +1,15 @@ + +namespace Fusion { + + using UnityEngine; + + /// + /// Flag component which indicates a NetworkObject has already been factored into a Runner's VisibilityNode list. + /// + [AddComponentMenu("")] + internal class RunnerVisibilityLinksRoot : MonoBehaviour { + private void Awake() { + this.hideFlags = HideFlags.HideInInspector; + } + } +} diff --git a/Assets/Photon/Fusion/Runtime/Utilities/RunnerVisibility/RunnerVisibilityLinksRoot.cs.meta b/Assets/Photon/Fusion/Runtime/Utilities/RunnerVisibility/RunnerVisibilityLinksRoot.cs.meta new file mode 100644 index 00000000..1fe89ea8 --- /dev/null +++ b/Assets/Photon/Fusion/Runtime/Utilities/RunnerVisibility/RunnerVisibilityLinksRoot.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c370cac09f73d42fab172498cc96e499 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/build_info.txt b/Assets/Photon/Fusion/build_info.txt new file mode 100644 index 00000000..3f1c70de --- /dev/null +++ b/Assets/Photon/Fusion/build_info.txt @@ -0,0 +1,3 @@ +build: 2.0.11 Stable 1743 +date: 2026-02-09 23:21:53 +git: 2.0/hotfix/2.0.11-hotfix (515be20a5) \ No newline at end of file diff --git a/Assets/Photon/Fusion/build_info.txt.meta b/Assets/Photon/Fusion/build_info.txt.meta new file mode 100644 index 00000000..287581ef --- /dev/null +++ b/Assets/Photon/Fusion/build_info.txt.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 7d903aaee8321384991b6195565dae93 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/package.json b/Assets/Photon/Fusion/package.json new file mode 100644 index 00000000..87e985e0 --- /dev/null +++ b/Assets/Photon/Fusion/package.json @@ -0,0 +1,5 @@ +{ + "name": "com.exitgames.photonfusion", + "version": "1.1.0", + "displayName": "Photon Fusion" +} \ No newline at end of file diff --git a/Assets/Photon/Fusion/package.json.meta b/Assets/Photon/Fusion/package.json.meta new file mode 100644 index 00000000..71ca51a1 --- /dev/null +++ b/Assets/Photon/Fusion/package.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 72a4f749a85f41c4c9bf79e0f66f8c71 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/Fusion/release_history.txt b/Assets/Photon/Fusion/release_history.txt new file mode 100644 index 00000000..242a2119 --- /dev/null +++ b/Assets/Photon/Fusion/release_history.txt @@ -0,0 +1,1033 @@ +# 2.0.11 + +## Stable + +### Build 1743 (Feb 09, 2026) + +**Bug Fixes** + +- Fixed: Implement unused UnloadUnusedPrefabsOnShutdown function (backport from 2.1) +- Fixed: Inconsistent `NetworkId` between clients in long-running sessions + +# 2.0.10 + +## Stable + +### Build 1688 (Jan 27, 2026) + +**Bug Fixes** + +- Fixed: ConnectionToken null on Single Player Mode + +# 2.0.9 + +## Stable + +### Build 1566 (Dec 08, 2025) + +**What's New** + +- Added `Networkrunner.CommittedPlayers`, which lists only players both fully in the room and the Fusion session +- Equality Operators and `GetHashCode()` to `ReliableKey` + +**Bug Fixes** + +- Fixed: Compatibility with Unity 6.3 +- Fixed: `NetworkTransform` area of interest override was being overwritten internally +- Fixed: Invalid warning message suggesting to use `[Networked]` for float/vector properties on structs +- Fixed: RPCs targeting state authority were not being received when outside the area of interest +- Fixed: Lag compensation internal AABB constructor fix assignment order + +# 2.0.8 + +## Stable + +### Build 1402 (Oct 17, 2025) + +**What's New** + +- Added `Networkrunner.CommittedPlayers`, which lists only players both fully in the room and the Fusion session + +**Changes** + +- EnableExperimentalPacketLossRecovery renamed to EnableAdaptivePacketFragmentation. When enabled, lost packets will cause next Fusion packets to be sent without fragmentation, thus reducing the chance of an ongoing packet loss due to fragments being reordered and rejected + +**Bug Fixes** + +- Fixed: Correct the online manual link for `NetworkRunner` +- Fixed: Warnings related to `TreeView` and `TreeViewState` being obsolete +- Fixed: `NetBitBuffer` - false-positive assertion when reading a negative value with `ReadInt32VarLength` in a Debug build +- Fixed: `NetworkTransform` SyncScale in shared mode + +# 2.0.7 + +## Stable + +### Build 1327 (Sep 26, 2025) + +**What's New** + +- `NetworkRunner.FailedToResolveNetworkObject` - an event that can be used to detect cases where `[Networked]` `NetworkObject` properties return null not due to underlying `NetworkId` being zero, but because the object was not found (due to AoI, being destroyed locally etc.) +- Fusion Version Scripting Define Symbols +- Support to Nintendo Switch 2 +- `SimulationConfig.EnableExperimentalPacketLossRecovery` - if enabled, lost packets will cause next Fusion packets to be sent without fragmentation, thus reducing the chance of an ongoing packet loss due to fragments being reordered and rejected +- Memory profiler counters + +**Changes** + +- Photon Realtime SDK to 4.1.8.16 +- `Failed to unwrap` warning no longer logged for `NetworkBehaviour` references that fail to resolve, regardless of the reason +- `Failed to find behaviour` is now logged for an invalid `NetworkBehaviour` on a valid `NetworkObject` +- Clamp `PlayerCount` to a valid range (1-2048) in Shared Mode +- Clamp Heap `PageCount` to a valid range (16-4096) in Shared Mode +- All Fusion profiler counters are now marked with "F" prefix +- Update NanoSockets for Android to support 16 KB page sizes +- `FusionProfiler` no longer needs `FUSION_PROFILER_INTEGRATION` define and core profiler package +- `NetworkObjectBaker` uses `Fusion.Log` instead of `UnityEngine.Debug` + +**Bug Fixes** + +- Fixed: NullRef in Editor when running WebGL +- Fixed: NMA error when animator has 8 bool parameters Or a multiple of 8 +- Fixed: Editor error when interacting with invalid network objects (Header can be uninitialized and causes null ref, checking Ptr to avoid it) +- Fixed: Global INetworkRunner AOI Callbacks are executed on clients in Host/Server mode and not just on the Server +- Fixed: NetworkLinkedList.IndexOf returns -1 if the value is not found +- Fixed: "Buffer should be null" assertions after a failed send +- Fixed: VisionOS NanoSockets Static Library +- Fixed: Reset re-join attempts when peer is able to join Session +- Fixed: Prefabs not being replicated until a resimulation tick occur +- Fixed: First item in SimulationConfig (ReplicationFeatures) does not draw it's help correctly in Unity inspector +- Fixed: `NetBitBuffer` - false-positive assertion when writing a negative value with `WriteInt32VarLength` in a Debug build +- Fixed: Assert "byteCount >= NetNotifyHeader.SIZE_IN_BYTES" +- Fixed: Fusion statistics packets now counts fragmented packets +- Fixed: Occasional `NullReferenceException` when shutting down Shared client with scheduling enabled +- Fixed: Crash in some Unity versions when opening the project in batch mode due to forced recompilation + +# 2.0.6 + +## Stable + +### Build 1034 (May 08, 2025) + +**Breaking Changes** + +- NetworkObjects that remains after shared mode authority leaves now has state authority set to PlayerRef.None + +**What's New** + +- NetworkRunner.LocalAddress to access the address Fusion bound to for direct connections + +**Changes** + +- Photon Realtime SDK to 4.1.8.15 +- DestroyWhenStateAuthorityLeaves prefab flag is now independent from the AllowStateAuthorityOverride +- Shared mode master client can now despawn network objects with PlayerRef.None as state authority +- Clicking animator bake failure error messages now shows the relevant gameObject in the hierarchy +- Photon Realtime SDK to 4.1.8.14 +- Force ShutdownTime to 1 second on Fusion Plugin +- Leave Session before disconnect from Photon Cloud +- Keep Session running only if Host/Server disconnected by Timeout +- Editor trace logs are more granular, with following defines controlling specific streams: `FUSION_EDITOR_TRACE_IMPORT`, `FUSION_EDITOR_TRACE_INSPECTOR`, `FUSION_EDITOR_TRACE_TEST`, FUSION_EDITOR_TRACE_MPPM` +- Spawned calls wrapped in try-catch so that an exception does not prevent other callbacks from executing +- `Simulation.IsStateAuthority` handles `PlayerRef.None` correctly (i.e. when it represents the host) +- `Simulation.IsInputAuthority` handles `PlayerRef.None` and `PlayerRef.MasterClient` correctly + +**Bug Fixes** + +- Fixed: In Shared Mode, if a state authority makes a state change which races against the server removing state authority from it, then flag the original state needing to be re-sent +- Fixed: Runtime spawned objects in scene are spawned/despawned multiple times in scene unload with a large amount of objects +- Fixed: Check StartGameArgs.Config for PlayerCount +- Fixed: Shutdown normally if Disconnected By PluginLogic +- Fixed: Client not rolling objects back to latest snapshot state in certain conditions +- Fixed: Prefab table not updating if running in batchmode +- Fixed: Stop repeated Unity warning logging if simulating multi peer physics scenes by not simulating the default scene +- Fixed: Client sometimes not updating `LatestServerTick` after receiving a new snapshot +- Fixed: Unable to update Session properties after Host Migration + +# 2.0.5 + +## Stable + +### Build 920 (Feb 12, 2025) + +**What's New** + +- Support for breakpoints: ClientServer Mode only. Working on both Multi-Peer and Unity MPPM +- `SimulationMessage.GetRawData` +- `Fusion.Unsafe` - a replacement for `System.Runtime.CompilerServices.Unsafe` + +**Changes** + +- `NetworkRunner` tries to quickly rejoin the session automatically (Client-Server mode only) +- Skip opening Fusion Hub in batchmode +- `NetworkTRSP` state authority change error correction delta default value set to 0 +- `SimulationMessage.GetData` is now obsolete +- `NetworkObjectHeader` up to `Flags` fields is now read only +- Improve support for Break-points + +**Bug Fixes** + +- Fixed: Stability and robustness fixes when under heavy network load +- Fixed: Unable to spawn during StateAuthorityChanged callback on MasterClientObjects +- Fixed: Incorrect reset/jump of time on proxy objects after a frame of exceptionally long duration +- Fixed: Lag Compensation box overlap problem with hitbox Y rotation +- Fixed: A sequence of lost packets resulting in a partial object data being sent +- Fixed: Lag Compensation box overlap having problems with hitboxes offset +- Fixed: `IndexOutOfRange` exception when reading negative offsets in skip data +- Fixed: `ReadObjectDataIntoPtr` throwing null ref exception in case of repeated word check errors +- Fixed: No flags for attached objects +- Fixed: Lag compensation layer masks being ignored with subtick accuracy +- Fixed: Check word count before Allocating an object (always) and compare against page size +- Fixed: Apply NetworkTransform.DisableSharedModeInterpolation in Single Mode +- Fixed: Intro Sample Menu Dependency +- Fixed: Odin drawers not finding underlying fields +- Fixed: Realtime ConnectionHandler stacking + +# 2.0.4 + +## Stable + +### Build 884 (Dec 18, 2024) + +**Breaking Changes** + +- Requires Photon Voice 2.57 or newer +- `NetworkSerializeMethodAttribute.MaxSize` is obsolete and no longer functional + +**What's New** + +- `SceneRef.Parse` +- `NetworkObject.IsNested` and `NetworkObject.NestingRoot` +- `INetworkObjectProvider.GetPrefabId`, `INetworkObjectProvider.Shutdown` and `INetworkObjectProvider.Initialize` (with default implementations) +- `NetworkObjectBakerEditTimeHandlerAttribute` - apply to a method for it to be invoked during baking of a specific `NetworkBehaviour` +- `NetworkRunner.CloudAddressRewriter` (Support for Realtime AddressRewriter) +- `NetworkRunner.GetAllNetworkObjects` zero alloc overload +- `INetworkObjectProvider.GetPrefabId`. `NetworkRunner.Spawn` overloads that accept `NetworkObjectGuid` call this method to translate to the prefab id. Previously config's prefab table was checked; the new method allows for a more flexible approach +- NetworkObject force remote RenderTimeFrame option +- `NetworkObjectBaker.PostprocessBehaviour` +- `NetworkObjectBakerEditTimeHandlerAttribute` - decorate a method with this attribute to enable NetworkBehaviour postprocessing in `NetworkObjectBakerEditTime` +- `INetworkObjectProvider.Initialize` and `INetworkObjectProvider.Shutdown` (have default empty implementations) + +**Changes** + +- `AppSettings.NetworkLogging`. Warnings and errors are logged according to the global log level, info messages are output when FUSION_TRACE_REALTIME is defined +- `NetworkTRSP` render logic now smoothly correct the render error caused by state authority changes +- `StateAuthorityChanged` callback now called on proxies also +- `Runner.ActivePlayers` on clients wont allocate a list anymore +- `FusionUnityLogger` registers itself during edit time as well, with `[InitializeOnLoadMethod]` +- Photon Realtime SDK to 4.1.8.11 +- AfterRender callback now called on proxy objects +- `NetworkObjectBaker.PostprocessBehaviour` now returns `true` if the script has been made dirty and requires a prefab update +- `NetworkAssetSourceStatic.Prefab` -> `NetworkAssetSourceStatic.Object` +- All Fusion Dlls are now built with NET standard 2.1 profile + +**Bug Fixes** + +- Fixed: `NetworkRunner.GetPlayerObject` is immediately available after calling `NetworkRunner.SetPlayerObject` in shared mode +- Fixed: `NetworkTransform` blinking to previous remote position when releasing state authority +- Fixed: `NetworkButton.SetUp` method setting it down instead +- Fixed: `OnChangedRender` is not called on private Networked Properties of base types when inheriting +- Fixed: NullRef when despawning the `NetworkObject` on `StateAuthorityChanged` callback +- Fixed: RpcInfo.Source in single player mode now correctly points to the source player +- Fixed: NetworkRunner.SessionInfo Support for Single Player +- Fixed: Usion Hub high CPU usage +- Fixed: Improved interpolation delay to reduce jitter in the movement of remote objects +- Fixed: Lag compensation box overlap broadphase AABB not working correctly when rotated +- Fixed: Getting a `PropertyReader` for `NetworkObject` properties throwing an exception +- Fixed: Release state authority on spawned correctly synchronized +- Fixed: Client stuttering for a few seconds after resuming from suspension +- Fixed: Possible `NullReferenceException` when shutting down +- Fixed: SceneInfo not being returned correctly before the first tick in shared mode +- Fixed: `NetworkProjectConfig.EnqueueIncompleteSynchronousSpawns` works again + +# 2.0.3 + +## Stable + +### Build 858 (Sep 10, 2024) + +**What's New** + +- Flag to enable ClientServer Modes in WebGL builds + +**Bug Fixes** + +- Fixed: `FusionAddressablePrefablsPreloader.Start` return type changed from `Task` to `void` +- Fixed: Warning in FusionAddressablePrefabsPreloader +- Fixed: TaskManager Delay for WebGL Builds +- Fixed: Destroyed Objects on Host Migration +- Fixed: NetworkRunner.TryFindBehaviour Out Of Range Exception +- Fixed: Internal Protocol Message Validation Check + +# 2.0.2 + +## Stable + +### Build 851 (Aug 16, 2024) + +**Breaking Changes** + +- Changed the return type of NetworkSceneManagerDefault.GetAddressableScenes. The new return type (GetAddressableScenesResult) can also store a delegate to be invoked before a synchronous wait + +**What's New** + +- Behaviour statistics accessible from the NetworkRunner +- NetworkRunner.CloudConnectionLost +- Support to quick Reconnect and Rejoin Session +- NetworkRunner.IsInSession +- NetworkRunner can now get a list of objects in the area of interest for a specific player on the server +- NetworkRunner.ReliableDataSendRate +- Fusion Statistics simulation stats word count and word total size +- Access to a list with all network objects in the simulation from the NetworkRunner API +- Support for multiple Custom STUN Servers +- Option to run simulation using DeltaTime instead of UnscaledDeltaTime +- Warning when Shared Mode is started with a different tickrate than the Shared Mode tickrate +- Now possible to get the available regions from NetworkRunner API +- Lag Compensation statistics snapshot accessible from the Lag Compensation API +- Memory statistics snapshot accessible from the NetworkRunner API to provide detailed allocation information +- Fusion Statistics free memory stats + +**Changes** + +- Photon Cloud Ping Interval to 200ms +- Photon Realtime SDK to 4.1.8.8 +- Review Realtime Disconnect Timeout +- An error message is logged if a non-master client spawns a prefab with `NetworkSpawnFlags.SharedModeStateAuthMasterClient` flag or with "Is Master Client Object" selected without passing `NetworkSpawnFlags.SharedModeStateAuthLocalPlayer` flag. The prefab will be spawned regardless, but with authority set to the local player +- Fusion Statistics panel now has an offset to make it easier to see other panels in multi-peer +- StartGameArgs use cached regions is now default to true +- Fusion Statistics can now be moved to world anchor object from code +- Fusion Statistics canvas can now be resized + +**Bug Fixes** + +- Fixed: Fusion Bootstrap Multipeer Mode Session names +- Fixed: Shared mode - RPCs sent immediately after object is spawned are no longer rejected +- Fixed: Shared mode clients sometimes using different pairs of snapshots to interpolate different NBs on the same remote object +- Fixed: Network Objects not following Master Client if update happens on Player Join +- Fixed: Error when despawning an NO with nested proxy object in shared mode +- Fixed: Fusion statistics graph shader only rendering in one eye in VR +- Fixed: `NetworkSceneManagerDefault` timing out its addressable scenes resolution in some cases +- Fixed: HitboxRoot throwing exception when destroyed directly +- Fixed: Fusion statistics canvas snap back when out of screen +- Fixed: Out of Range on NetworkBehaviour.NetworkDeserialize +- Fixed: `NetworkObjectProviderDefault` always returns `Retry` if a returned prefab is null. This restores functionality of config's `EnqueueIncompleteSynchronousSpawns` option +- Fixed: `NetworkPrefabTable` correctly awaits a synchronous load that follows an asynchronous one +- Fixed: Client crashing on `InvalidOperationException: Cannot take items from an empty buffer.` (again) +- Fixed: Parse Words greater than Word Count +- Fixed: Scene info inconsistencies when `NetworkRunner.LoadScene` is called immediately after starting a runner +- Fixed: `AssertException` when syncing `NetworkMecanimAnimator` without any bool parameters +- Fixed: Failed to Forward Simulation Message to a Missing Connection +- Fixed: Hardened ReadObjectDataIntoPtr against malformed packets +- Fixed: Default Unity physics not being autosimulated in multi-peer +- Fixed: MPPM now supports starting FusionBootstrap in shared mode +- Fixed: Occasional NullRef when spawning network objects under certain conditions + +# 2.0.1 + +## Stable + +### Build 835 (May 22, 2024) + +**What's New** + +- Multiplayer Play Mode (MMPM) support +- `FusionBoostrap.AutoConnectVirtualInstances` (true by default) - if MPPM is installed, virtual instances will join the main instance once it starts a session + +**Changes** + +- Fusion Statistics create a new EventSystem if none is detected + +**Bug Fixes** + +- Fixed: Fusion Menu Missing flag on Unity 6 +- Fixed: Hardened edge case race condition during disconnect +- Fixed: Fix for too aggressive sequencing bounds check +- Fixed: Race condition assert in OnPacketDelivered +- Fixed: Unable to get the player object on PlayerLeft callback + +# 2.0.0 + +For a more in-depth overview over what changed check: [Coming from Fusion 1.0](https://doc.photonengine.com/fusion/v2/getting-started/migration/coming-from-fusion-v1) + +**Breaking Changes** + +- Renamed addressable define from FUSION_USE_ADDRESSABLES -> FUSION_ENABLE_ADDRESSABLES +- `NetworkRunner.Spawn` method family now uses `NetworkSpawnFlags` parameter instead of bool flags +- PlayerRef / SceneRef no longer directly cast to/from int +- `INetworkkObjectPool` is now `INetworkObjectProvdier` and changed parameters of acquire and release functions +- `NetworkRunner.AddSimulationBehaviour` is now internal +- Removed ability for `SimulationBehaviours` to exist on networked objects, only `NetworkBehaviours` are used on `NetworkObjects` now. +- `(int)PlayerRef` -> `PlayerRef.PlayerId` and `PlayerRef.IsValid` -> `PlayerRef.IsRealPlayer` +- `(int)SceneRef` -> `SceneRef.FromIndex` +- `NetworkObject.Guid` -> `NetworkObject.NetworkTypeId` +- `Runner.Simulation.X` APIs now under `Runner.X` +- `INetworkSceneManager` interface API changed. `Runner.SetActiveScene` -> `Runner.LoadScene` + +**What's New** + +- `ChangeDector` that allows to detect changes for `NetworkProperties` in `Render`, `FUN` or at custom intervals. +- `PropertyReaders` and `TryGetSnapshotBuffers`, to allow for custom interpolation and previous networked property state. +- `NetworkBehaviourBufferInterpolator` for simplified interpolation for built-in types and custom registered types. +- LagCompensation support for 2D physics. +- Server/Client Simulation/Send/ rates can now be adjusted individually in host/server mode. Fixed to 20 for shared mode. +- StartGameArgs.UseDefaultPhotonCloudPorts +- NetworkSceneManager now supports loading addressable scenes. +- NetworkSceneManager now supports additive loading of scenes. +- Added Last Received Tick on the network object runtime debug info +- `NetworkSceneManagerDefault.GetMultiplePeerTargetScene` - ability to override the logic of choosing a root scene object in MP mode +- `NetworkProjectConfig.EnqueueIncompleteSynchronousSpawns` - if enabled, synchronous incomplete spawns (Addressables etc.) get enqueued instead of throwing an exception +- `NetworkObjectAcquireResult.Ignore` - if object provider returns this, Fusion will no longer try to acquire an instance for a state object +- `SceneRef.FromPath` - creates a scene ref based on a hash of a path. Useful for easily creating `SceneRefs` of Addressable scenes +- `NetworkPrefabId` constructor removed, use `FromIndex` or `FromRaw` instead +- AOI `INetworkRunnerCallbacks` for when objects enter and exit AOI. `OnObjectEnterAOI`, `OnObjectExitAOI` + +**Changes** + +- Shared mode ticks are now aligned between clients. +- `NetworkPhysics` are now provided via a separate addon that comes as part of the Fusion package. +- Hitbox buffer size in NetworkProjectConfig is now in milliseconds instead of ticks. +- `NetworkSceneManagerDefault` works with Addressables 1.19.x, but will throw at an attempt of loading an addressable scene with local physics mode +- SimilationBehaviours are now found on children of Runner. Disabled behaviours are ignored +- INetworkRunnerCallbacks can be found on children of Runner +- BehaviourMeta in `NetworkProjectConfig` gets refreshed whenever relevant script's execution order change +- RPCs in Shared mode are no longer treated as if they were always declared with `TickAligned=false` +- `Render` is now invoked right after `MonoBehaviour.Update` (previously: before `MonoBehaviour.LateUpdate`) +- `RpcAttribute.InvokeResim` is now deprecated and no longer used +- Updated Photon Realtime SDK to v4.1.6.23 +- `NetworkSceneManagerDefault`: if no scenes are loaded in MP mode, an inactive limbo root will be created to host any new objects. Once a "real" scene has been loaded, objects are going to be evicted from the limbo +- JoinSessionLobby to include `useDefaultCloudPorts` argument +- `InitializeNetworkObjectAssignRunner` needlessly made NBs owned twice +- Completely removed predictive spawns +- `SimulationBehaviours` are no longer Unity-null and `isActiveAndEnabled` checked when invoking callbacks. Flags set in OnEnable/OnDisable/OnDestroy are used instead. User code does not need any patching, any flag setting is handled by the weaver +- `NetworkConditions` fields are hidden unless it is enabled +- Look of inline help and script header is now driven by `FusionEditorSkin.guiskin` +- Renamed `CastEnumAttribute` to `DisplayAsEnumAttribute`; it also handles long/ulong fields now +- `DrawIf/WarnIfAttributes` can point to methods/properties, as long as they're top-level +- `INetworkSceneManager.OnSceneInfoChanged` is now always invoked right before `IBeforeTick` +- `NetworkSceneManagerDefault` reports itself busy until first `OnSceneInfoChanged` arrives +- `NetworkSceneManagerDefault` will move a spawned object to DontDestroyOnLoad in Multiple Peer mode if there are no scenes loaded for a runner. A warning explaining what happened will be emit as well +- `NetworkObjectProviderDefault` no longer fails for synchronous acquires that fail to return an instance immediately +- Simplified `NetworkSceneMangerDefault` - always uses dynamic addressable scenes list (finds scenes under a given label) + +**Removed** + +- Removed `InterpolationTarget` from `NetworkTransform`. The object with the `NetworkTransform` is now interpolated directly. +- Removed `[Accuracy]` and `[AccuracyRange]` range attributes. Accuracy is no longer supported +- Remove `PlayerRefSet` from `RpcInvokeInfo`. Since PlayerRef is no longer a linear index, fixed-size collection is not possible. +- Removed `[OrderBefore]` and `[OrderAfter]`. Use Unity execution order instead. +- Removed predicted Spawning and spawn keys. Predicted spawning is no longer supported. +- Removed Delta Snapshots state transfer mode. + +**Bug Fixes** + +- Fixed: Angle AngleLerp method now returns correct value if a and b are equal +- Fixed: Drag and drop into collections being broken on custom Photon behaviours +- Fixed: SharedMode: some destroys initiated by the master client not being propagated +- Fixed: `NetworkBehaviour.Id` returning `default` in `Despawned` +- Fixed: `NullReferenceExceptions` when accessing `NO` properties without it being fully initialized +- Fixed: `AssertException` when initializing `NO` with multiple nested sub hierarchies +- Fixed: `NetworkRunner.GetRunnerForScene` not working for single-peer with multiple scenes +- Fixed: Runner Visibility was not accounting for non-NB child components (like audio listener) +- Fixed: Weaving of `NetworkBehaviour` and subtypes +- Fixed: Issue with SetSimulationState allowing you to set none state auth object as simulated in shared mode +- Fixed: "return pattern of RpcInvokeInfo not recognised" error when using RpcInvokeInfo +- Fixed: `Render` invoked for disabled `NetworkBehaviours` +- Fixed: "Apply" button doing nothing when entering Play mode with `NetworkProjectConfig` having unsaved changes +- Fixed: _masterClient and RuntimeConfig.MasterClient were not assigned until there was a client switch +- Fixed: Inspector errors in `FusionStats`. +- Fixed: Scale handling of NetworkTransform +- Fixed: Issue with remote objects always spawning at 0,0,0 with NetworkTransform +- Fixed: Assert error on shutdown because object/behaviours weren't in simulation list +- Fixed: `INetworkSceneManager.OnSceneInfoChanged` being called inconsistently in shared mode +- Fixed: Set PlayerTTL to 0 to avoid delay in reconnect +- Fixed: `Simulation.IsStateSource` recognizes host player +- Fixed: `NetworkObject.GetRpcSourceAuthorityMask` +- Fixed: `NetworkObjectTypeId.Equals` +- Fixed: `StackOverflowException` when accessing `NetworkObject.Id` +- Fixed: Invalid user-types assembly name +- Fixed: Assertion when shutting down a runner that has despawned a nested object right before the shutdown +- Fixed: ILWeaver: `FieldAccessException` in RPCs +- Fixed: `SceneRef.ToString` throwing for path hashes +- Fixed: Scene info not being read properly by `Simulation.GetSceneInfo` +- Fixed: `INetworkSceneManager.OnSceneChanged` being called repeatedly for clients +- Fixed: Prefab order being locale-dependent +- Fixed: All folders ending with `Resources` incorrectly recognized as `Resources` folders, making some of the contained prefabs unspawnable +- Fixed: "unresolved external symbol" runtime errors if an abstract class deriving from `NetworkBehaviour` has no `[Networked]` properties + +## Stable + +### Build 834 (May 13, 2024) + +**What's New** + +- New Fusion Statistics +- NetAddress.HasAddress +- NetAddress.IsIPv4 +- `FusionWeaverTriggerImporter.RunWeaverOnConfigChanges` - if enabled, runs the weaver when weaving-related changes are detected in the config file (true by default) + +**Changes** + +- Photon Realtime SDK to 4.1.8.5 +- Call despawned on SimulationBehaviour with IDespawned interface when runner shutdown +- Setting client always interested in NetworkObjects it has InputAuthority over +- Adding runner visibility nodes to objects moved to the runner scene +- Shared Mode Player AoI Radius Max to 300 +- Weaver: if an assembly does not support unsafe code, an error will appear in the config inspector +- Log SDK Version on StartGame +- SendMessage Error Logs to Warnings + +**Bug Fixes** + +- Fixed: Race condition when shared mode master client leaves while other clients are still loading a scene resulting in "is already attached to a different id" warning flood +- Fixed: Single STUN Server Handling +- Fixed: NetworkSceneManagerDefault MoveToRunnerScene in multi peer +- Fixed: `NetworkSceneInfo` unable to store meta information for scenes 6 & 7 +- Fixed: Change detector unresolved private method in base class +- Fixed: ChangeDetector Enumerable Changed method +- Fixed: Runner visibility issues +- Fixed: Client briefly using incorrect interpolation data after object becomes active after being inactive for some time +- Fixed: `GetDrawerTypeForTypeDelegate` error in Unity 2022.3.23 or newer +- Fixed: Client time sync throwing `InvalidOperationException: Cannot take items from an empty buffer.` after a reset +- Fixed: NullRef on GetPlayerConnectionType if Server checks LocalPlayer +- Fixed: Double Connection from Same Player +- Fixed: Using a field as a `Default` for multiple `Networked` properties resulting in an inspector crash due to stack overflow + +## RC5 + +### Build 824 (Mar 26, 2024) + +**Bug Fixes** + +- Fixed: Shared Mode State not being replicated +- Fixed: RPCs not receiving objects with global AOI + +### Build 823 (Mar 25, 2024) + +**Bug Fixes** + +- Fixed: Object does not exist: [Id:X], but is marked as confirmed + +### Build 822 (Mar 21, 2024) + +**What's New** + +- Hitbox collider position and rotation data for LagCompensatedHit +- Added new API to control which network behaviours state is replicated to each player +- Added new input sending mode for high player count / low input size games like first person shooters +- Fusion Menu Package +- VisionOS version of nanosockets library +- StartGameArgs.SessionNameGenerator + +**Changes** + +- Minor improvements when updating Lag compensation hitbox roots +- Improved lag compensation subtick broadphase +Hitbox roots now are interpolated correctly even if they are unique to one candidates snapshot +- Photon Realtime SDK to 4.1.8.1 +- Simulation Behaviours now have Spawned and Despawned called if they inherit the interfaces +- Renamed settings on `TimeSyncConfiguration` and updated their descriptions. +- `ExtraSimulationOffset` is now `RedundantInputs`. +- `ExtraInterpolationOffset` is now `RedundantSnapshots` +- Max payload size for RPC to 512 bytes +- RPCs are culled if payload size is over `RpcAttribute.MaxPayloadSize` bytes +- Improved performance of SendPackets loop +- Implemented packet throttling to 2 packets/second when the client vanishes +- Removed lag compensation lazy initialization. Make sure to enable lag comp in the network project config to use it +- Photon uses AlternativeUdpPorts only when Port is default + +**Removed** + +- Physics Add-on from SDK package + +**Bug Fixes** + +- Fixed: Remove NetworkObject from AoI when destroyed +- Fixed: Logs in Release Mode +- Fixed: SinglePlayer PlayerRef to 1 +- Fixed: Changed shared mode send rate to 16 +- Fixed: NRB would immediately revert inspector changes made to the RigidBody.IsKinematic value on the State Authority when no interpolation target was being used. Changes made to isKinematic in the inspector and in Update() should not stick as expected +- Fixed: InPackets and OutPackets SimulationStats displaying incorrect values on the fusion stats +- Fixed: NetworkMecanimAnimator was not writing int parameters to the network buffer, now they are +- Fixed: Typo in `RpcLocalInvokeResult.TargetPlayerIsNotLocal` (was: `RpcLocalInvokeResult.TagetPlayerIsNotLocal`) +- Fixed: Lag Compensation Client hitbox registration window and subtick imprecision +- Fixed: Property readers for collection of reference types throwing an exception +- Fixed: `LayerMatrixGUI` error on latest Unity 2023.2 +- Fixed: Convert ping on game overlay from seconds to milliseconds +- Fixed: Lag compensation lazy initialization leaving empty spots on the buffer +- Fixed: Capsule hitbox narrowphase offset problem +- Fixed: Minor details on the Intro Sample +- Fixed: IPhotonEncryptor Type Auto Load + +## RC3 + +### Build 805 (Jan 17, 2024) + +**What's New** + +- Encryption Support +- Mobile support for the introduction sample +- `int IElementReaderWriter.GetElementHashCode(T element);` +- Added OnChangedRender attribute to receive a callback before Render when a networked property changes + +**Changes** + +- Made `Runner.RemoveSimulationBehaviour` internal. Use `Runner.RemoveGlobal` instead +- Added shared mode support to intro sample +- `[NetworkSerialize]` methods no longer need to have a `NetworkRunner` parameter if a `[NetworkDeserialize]` method has one +- Lag compensation capsule settings now matches unity capsule +- Scheduling is now the default replication feature + +**Bug Fixes** + +- Fixed: NetAddress.CreateFromIpPort should ignore IPv6 Scope ID +- Fixed: MC change not replicating MasterClient objects correctly +- Fixed: MasterClient objects not being active on priority list on the new mc +- Fixed: Avoid attach already valid Scene Network Objects +- Fixed: Free object from priority list in shared mode clients +- Fixed: Handle Disconnect while waiting for Join confirmation +- Fixed: `FusionGlobalScriptableObject.GetMethodsWithAttribute` not handling `ReflectionTypeLoadException` errors +- Fixed: Confirm Join Timeout +- Fixed: Newly simulated objects blinking positions when reparenting +- Fixed: Invalid `GetElementHashCode` generation for NBs +- Fixed: `NetworkDictionary` key lookup not being deterministic for `System.String`, `NetworkObject` and `NetworkBehaviour` keys +- Fixed: "SerializedObject target has been destroyed" error when clicking "Rebuild Prefab Table" in the config editor (Unity 2022.3.11+) + +## RC + +### Build 801 (Dec 12, 2023) + +**Changes** + +- `NetworkRigidbody` XML Docs +- `NetworkProjectConfigImporter` refreshes now whenever a new `NetworkObject` prefab is created, a prefab is moved or deleted +- NetworkRigidbody EnsurerHasRunnerSimulatePhysics() changed from private to protected +- _deferredTeleport field in NetworkRigidbody changed from private to protected + +**Removed** + +- Obsolete API + +**Bug Fixes** + +- Fixed: NetworkCharacterController Spawn at 0,0,0 +- Fixed: Issue with full consistency not acting properly when ReplicateTo was being used +- Fixed: NetworkObjects not being replicated on clients correctly when scheduling only enabled +- Fixed: Revert & Apply buttons always disabled in `NetworkProjectConfig` inspector +- Fixed: Odin: inspector errors for `[Networked]` properties when using `[FoldoutGroups]` +Dev: Imported PhotonUnityEditorCommon@f81d7ff1bfa4ef962bd64e664a3c50a44e922404 +- Fixed: FusionStats NetworkObject stats would always fail to find stat data, now will correctly retry until found +- Fixed: NetworkRigidbody fixed so State Authority changes in Shared Mode retain Rigidbody velocity on the new State Authority +- Fixed: Timming issue with master client objects state authority changed detection +- Fixed: NetworkRigidbody now un-dirties the interpolation target when SetInterpolationTarget(null) is called. This prevents null errors due to setting the target to null at runtime +- Fixed: Test Suit Platforms +- Fixed: `NullReferenceException` thrown by `NetworkProjectConfigImporterEditor` if initial prefab table import fails +- Fixed: NetworkRigidbody3D ResetRigidbody() now checks for isKinematic before reseting velocity +- Fixed: Change detector having incorrect ticks set +- Fixed: "InvalidOperationException" during connection establishing on server +- Fixed: Allocations in Timelines.Update +- Fixed: Allocations in Client.GetInput +- Fixed: Invalid properties when NetworkRunner is Uninitialized +- Fixed: NRB handling of unparented Kinematic RBs now always applies pos/rot to Transform rather than Rigidbody +- Fixed: Fusion.Addon.Physics assembly correctly renamed in NetworkProjectConfig.AssembliesToWeave defaults +- Fixed: Editor freezing on prefab rename workaround applied to Unity 2022.3 as well + +### Build 797 (Nov 07, 2023) + +**What's New** + +- UsePreciseRotation option to NRB +- Expose EncryptionMode via FusionAppSettings + +**Changes** + +- Calls to Load/Unload scene are correctly deferred until the initial info is processed +- Improved error messages for `SerializedPropertyUtilities.FindPropertyOrThrow` + +**Removed** + +- IPredictedDespawnBehaviour + +**Bug Fixes** + +- Fixed: NRB was not pushing position and rotation values to the transform when SyncParent was disabled +- Fixed: "Shutdown" button on NetworkRunner inspector now implemented +- Fixed: NetworkTransform throwing null ref on disabled child object +- Fixed: `NetworkProjectConfig` handles initial import external errors better, without spamming with errors + +## Nightly + +### Build 795 (Nov 04, 2023) + +**What's New** + +- Bool session property +- Ability to use cached best region summary to speed up runner startup + +**Changes** + +- NRB's UsePreciseRotation field made public +- Made NRB Teleport handling methods virtual +- Added UsePreciseRotation option to NRB +- Photon Realtime SDK to 4.1.7.2 + +**Removed** + +- StartGameArgs.DisableClientSessionCreation + +**Bug Fixes** + +- Fixed: NRB was not pushing position and rotation values to the transform when SyncParent was disabled. Could produce some janky un-parenting results for any nested NOs that were using that NRB Object as their parent +- Fixed: NRB CopyToEngine handling of isParented. Was always treating object as parented when SyncParent was enabled +- Fixed: Added a workaround for Unity 2023.1 crashing when prefab contents are loaded for moved prefabs in asset postprocessor +- Fixed: Odin - HideArrayElementLabelAttribute handling + +### Build 785 (Oct 18, 2023) + +**Bug Fixes** + +- Fixed: Network transform parent sync being lost because of interpolation + +### Build 782 (Oct 12, 2023) + +**Changes** + +- Expose EmptyRoomTtl via AppSettings +- Replaced usages of transform.SetLocalPositionAndRotation in NRB, as Unity repeatedly added/removed that method from API + +### Build 780 (Oct 07, 2023) + +**What's New** + +- Restored `NetworkProjectConfigUtilities.SaveGlobalConfig` + +**Changes** + +- Made NetworkRigidbody CopyToBuffer() and CopyToEngine() methods virtual + +### Build 779 (Oct 06, 2023) + +**What's New** + +- FUSION2 to Scripting Define Symbols +- Expose EncryptionMode via FusionAppSettings +- FusionAppSettings to expose some Realtime Settings + +**Changes** + +- Added NetDisconnectReason to OnDisconnectedFromServer callback + +**Bug Fixes** + +- Fixed: Added Null Check to IBeforeAllTicks on NetworkRigidbody. Was possible for InterpolationTarget to become null since last Render() and throw a null error +- Fixed: Missing CancellationToken on JoinSessionLobby + +### Build 777 (Oct 04, 2023) + +**What's New** + +- Error message when reutilizing a NetworkRunner + +**Bug Fixes** + +- Fixed: `NPT.Unload` not unloading anything + +### Build 776 (Oct 03, 2023) + +**Changes** + +- Weaver: floats and vectors can now be ref-returned + +### Build 775 (Sep 30, 2023) + +**Changes** + +- SimulationBehaviourUpdater no longer uses NPC.Global, using a passed instance instead + +**Bug Fixes** + +- Fixed: Removed NRB Physics.autoSimulation usage for Unity 2022.3 and newer + +### Build 774 (Sep 29, 2023) + +**What's New** + +- `NB.IsEditorWritable` (internal property) + +**Changes** + +- [Networked] properties backing fields receive [DrawIfAttribute], to make sure they are read only for clients without state authority + +**Bug Fixes** + +- Fixed: `UnitySurrogateBaseWrapper` not being a standalone script warning +- Fixed: `FailedToCreateInstance` error for when less than 64 prefabs are registered + +### Build 772 (Sep 27, 2023) + +**Changes** + +- Deferred runner `Initialized` invoke and renamed to `OnGameStart` +- If RPC returns `RpcInvokeInfo`, no error will be logged if target or flags prevent the RPC from being sent +- `TickRate` properties fall back to 0 instead of infinity if rates are 0 too +- `StateReplicator` throws `AssertException` in some erroneous scenarios even in Release builds + +**Bug Fixes** + +- Fixed: `IndexOutOfRangeException` on reimporting NetworkPrefabTable + +### Build 771 (Sep 26, 2023) + +**What's New** + +- NetworkRigidbody classes added handling for StateAuthorityChanged in Shared Mode +- NetworkPrefabTable ref tracking +- Huge improvements to `Network Prefabs Inspector` + +**Changes** + +- Removed DisabledAutoSyncTransforms field from RunnerSimulatePhysics component. Now will just display and inspector warning if AutoSyncTransforms is enabled in unity Physics settings + +**Bug Fixes** + +- Fixed: FusionBootstrap: trying to spawn clients in Single mode if set to Automatic +- Fixed: `IsNested` flag cleared prematurely in some destroy scenarios +- Fixed: `IsNested` flags never assigned to a remote nested object + +### Build 768 (Sep 23, 2023) + +**Changes** + +- Photon Realtime SDK to 4.1.7.0 + +### Build 766 (Sep 21, 2023) + +**What's New** + +- NetworkPrefabTableInspector ("Fusion/Windows/Network Prefabs Inspector") + +**Changes** + +- Quantum and Fusion use common code for loading dynamic assets now. Stage is set for finally being able to track spawned prefab instances +- `SimulationBehaviourUpdater` no longer uses global config" +This reverts commit 3afe58fd7b35c0d790407cf7ccf71a7d1e0e668d +- `SimulationBehaviourUpdater` no longer uses global config +- `NetworkPrefabTable.TryAdd` accepts invalid guids now; it simply won't map that guid to the prefab id, meaning that it can only be accessed with `NetworkPrefabId` + +**Bug Fixes** + +- Fixed: Pending prefab import was a legacy thing + +### Build 764 (Sep 19, 2023) + +**What's New** + +- Lag Compensation Capsule Hitbox + +### Build 761 (Sep 15, 2023) + +**Bug Fixes** + +- Fixed: NPC parse-related error on importing a package + +### Build 759 (Sep 13, 2023) + +**Changes** + +- `ILogger` string parameter changed to `object` (from `string`) +- `FusionUnityLogger.LogException` will first log the exception type and then spawn a thread to rethrow it (if UNITY_EDITOR is defined). This ensures double clicking on a log entry takes you to the throw location + +**Bug Fixes** + +- Fixed: Spawn position and rotation values pushed to NRB rigidbody in Spawned(), ensuring RB values are immediately correct before first simulation +- Fixed: NetworkTransform now uses a public getter for the SimulationConfig rather than an internal one. NT implementation can be copied as is as a basis for custom implementations in Unity + +### Build 758 (Sep 12, 2023) + +**What's New** + +- NetworkRigidbody added missing AOI enabled check. Root NetworkTRSP components no longer required for parenting if AOI is disabled + +**Changes** + +- NetworkTRSP.IsMainTRSP setter is now protected + +### Build 756 (Sep 08, 2023) + +**Bug Fixes** + +- Fixed: Explicit interest in shared mode + +### Build 754 (Sep 06, 2023) + +**What's New** + +- `NetworkRunner.LoadScene` `setActiveOnLoad` parameter - passing true will active the scene right after loading, before invoking Spawned on contained NOs +- Added support for disconnect token byte[]. up to 128 bytes + +### Build 750 (Sep 03, 2023) + +**What's New** + +- MasterClient Runner now identified with [MC] in in Runner Visibility Controls Window + +### Build 749 (Sep 02, 2023) + +**Changes** + +- Hitbox matrix TRS custom calculation + +**Bug Fixes** + +- Fixed: Stackoverflow when setting HitboxRoot min bounding radius with no hitboxes +- Fixed: Lag compensation sphere overlap query hitpoint calculation + +### Build 747 (Aug 25, 2023) + +**What's New** + +- IM to lag compensation to update only interested HitboxRoots + +### Build 746 (Aug 24, 2023) + +**What's New** + +- NetworkRigidbody in Shared Mode will by default leave Unity physics auto-simulate enabled. Adding RunnerPhysicsSimulate to the NetworkRunner is still possible to explicitly give Fusion physics control + +**Changes** + +- NRB added improved support for NRBs with non-NB parents + +### Build 745 (Aug 23, 2023) + +**Bug Fixes** + +- Fixed: "Shutdown" button on NetworkRunner inspsctor now implemented + +### Build 744 (Aug 22, 2023) + +**Bug Fixes** + +- Fixed: FusionBootstrap can now correctly start in Single Peer with the Auto setting (previously created 0 clients) + +### Build 741 (Aug 18, 2023) + +**Bug Fixes** + +- Fixed: RunnerSimulationPhysics now gets and sets NetworkPhysicsInfo correctly for Shared Mode + +### Build 738 (Aug 11, 2023) + +**Changes** + +- Added NRB handling for pooling. Despawn() now resets the RB velocities and restores IsKinematic to original value from Spawned(). Added DisableSyncTransforms to RunnerSimulatePhysics classes, to make disabling optional + +### Build 737 (Aug 10, 2023) + +**Bug Fixes** + +- Fixed: Runner.IsForward being true on BeforeAllTicks when resimulating +- Fixed: MasterClientObjects not being simulated by the new master client + +### Build 736 (Aug 09, 2023) + +**Changes** + +- Removed IPredictedDespawnBehaviour + +**Bug Fixes** + +- Fixed: NetworkTransform throwing null ref on disabled child object +Called manually the awake from spawned if the transform was not yet cached +- Fixed: Issue where objects without state authority could be despawned + +### Build 735 (Aug 08, 2023) + +**What's New** + +- NetworkPhysicsInfo internal struct object (id: 4). Not going to exist in shared mode until the plugin is updated + +### Build 734 (Aug 07, 2023) + +**Bug Fixes** + +- Fixed: Spawned objects ending up on host scene + +### Build 733 (Aug 05, 2023) + +**Changes** + +- Calls to Load/Unload scene are correctly deferred until the initial info is processed +Cleaning up before the merge +wip +wip +simplifying initial scene load flow + +**Bug Fixes** + +- Fixed: Occasionally not being able to spawn prefabs using static references +- Fixed: Destroying object without state attached throwing an exception +- Fixed: Clients not resetting their scene info snapshot upon connecting +- Fixed: Addressable scenes discovery blocking Unity, if something goes wrong +- Fixed: Throwing exception in task-based scene ops + +### Build 732 (Aug 04, 2023) + +**Changes** + +- RunnerAOIGizmos will render even if the Server runner is set to not visible (multi-peer mode) + +**Bug Fixes** + +- Fixed: Bug with aoi cell calculations + +### Build 731 (Aug 03, 2023) + +**Changes** + +- MaxPlayers now accepts int values + +### Build 730 (Aug 02, 2023) + +**Changes** + +- Renamed NetworkRigidbody.MovingTeleport() to just Teleport(), replacing the previous basic Teleport() implementation + +**Bug Fixes** + +- Fixed: Physics not updating when scenes are loaded with local physics mode in single-peer mode + +### Build 729 (Aug 01, 2023) + +**What's New** + +- Public property NetworkRigidbody3D.Rigidbody and NetworkRigidbody2D.Rigidbody added for consistency with previous Fusion NRB + +### Build 728 (Jul 29, 2023) + +**Bug Fixes** + +- Fixed: Weaver: Invalid casts for non-serialized dictionaries when `UseSerializableDictionary` is enabled +- Fixed: Issue with latest state changes from server not being available when state authority switch happens +- Fixed: SceneRef obsolete warning +- Fixed: Removed IPredictedSpawnBehaviour + +### Build 719 (Jul 11, 2023) + +**Changes** + +- Hitbox colliders and BVH nodes arrays now resize on demand + +**Bug Fixes** + +- Fixed: `NetworkRunner.LoadScene` ignoring `localPhysicsMode` parameter + +### Build 717 (Jul 07, 2023) + +**Bug Fixes** + +- Fixed: RunnerSimulatePhysics3D obsolete warning - updated to use Physics.simulationMode with 2022_3 and newer + +### Build 716 (Jul 05, 2023) + +- Initial 2.0 release + diff --git a/Assets/Photon/Fusion/release_history.txt.meta b/Assets/Photon/Fusion/release_history.txt.meta new file mode 100644 index 00000000..423d882b --- /dev/null +++ b/Assets/Photon/Fusion/release_history.txt.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 5705adcaad7c31b439ed88af045846a1 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/FusionDemos.meta b/Assets/Photon/FusionDemos.meta new file mode 100644 index 00000000..6579d3b7 --- /dev/null +++ b/Assets/Photon/FusionDemos.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b742d3cc00a520d41bb3803393ab5b00 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/FusionMenu.meta b/Assets/Photon/FusionMenu.meta new file mode 100644 index 00000000..32906d70 --- /dev/null +++ b/Assets/Photon/FusionMenu.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 647c4f8be2e2e244ca1bafe11c03770b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/PhotonLibs.meta b/Assets/Photon/PhotonLibs.meta new file mode 100644 index 00000000..2addd2f7 --- /dev/null +++ b/Assets/Photon/PhotonLibs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ca82e98fbd5ad224e8325a9edc1e2aca +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/PhotonLibs/WebSocket.meta b/Assets/Photon/PhotonLibs/WebSocket.meta new file mode 100644 index 00000000..b99fd956 --- /dev/null +++ b/Assets/Photon/PhotonLibs/WebSocket.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5f2c9ba077001c047a95f74db3b891de +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/PhotonLibs/WebSocket/PhotonWebSocket.asmdef b/Assets/Photon/PhotonLibs/WebSocket/PhotonWebSocket.asmdef new file mode 100644 index 00000000..683dff08 --- /dev/null +++ b/Assets/Photon/PhotonLibs/WebSocket/PhotonWebSocket.asmdef @@ -0,0 +1,15 @@ +{ + "name": "PhotonWebSocket", + "references": [], + "includePlatforms": [ + "WebGL" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Assets/Photon/PhotonLibs/WebSocket/PhotonWebSocket.asmdef.meta b/Assets/Photon/PhotonLibs/WebSocket/PhotonWebSocket.asmdef.meta new file mode 100644 index 00000000..f8690be2 --- /dev/null +++ b/Assets/Photon/PhotonLibs/WebSocket/PhotonWebSocket.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: a10ff188cbfd201409863c062b118e1d +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/PhotonLibs/WebSocket/SocketWebTcp.cs b/Assets/Photon/PhotonLibs/WebSocket/SocketWebTcp.cs new file mode 100644 index 00000000..85f367b9 --- /dev/null +++ b/Assets/Photon/PhotonLibs/WebSocket/SocketWebTcp.cs @@ -0,0 +1,352 @@ +#if UNITY_WEBGL || WEBSOCKET || WEBSOCKET_PROXYCONFIG + +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) Exit Games GmbH. All rights reserved. +// +// +// Internal class to encapsulate the network i/o functionality for the realtime library. +// +// developer@exitgames.com +// -------------------------------------------------------------------------------------------------------------------- + + +namespace ExitGames.Client.Photon +{ + using System; + + #if UNITY_2019_3_OR_NEWER + using UnityEngine.Scripting; + #endif + + /// + /// Internal class to encapsulate the network i/o functionality for the realtime library. + /// + [Preserve] + public class SocketWebTcp : IPhotonSocket, IDisposable + { + private WebSocket sock; + + private readonly object syncer = new object(); + + [Preserve] + public SocketWebTcp(PeerBase npeer) : base(npeer) + { + this.ServerAddress = npeer.ServerAddress; + this.ProxyServerAddress = npeer.ProxyServerAddress; + if (this.ReportDebugOfLevel(DebugLevel.INFO)) + { + this.Listener.DebugReturn(DebugLevel.INFO, "SocketWebTcp() "+ WebSocket.Implementation+". Server: " + this.ServerAddress + (String.IsNullOrEmpty(this.ProxyServerAddress) ? "" : ", Proxy: " + this.ProxyServerAddress)); + } + + this.PollReceive = false; + } + + public void Dispose() + { + this.State = PhotonSocketState.Disconnecting; + + if (this.sock != null) + { + try + { + if (this.sock.Connected) + { + this.sock.Close(); + } + } + catch (Exception ex) + { + this.EnqueueDebugReturn(DebugLevel.INFO, "Exception in SocketWebTcp.Dispose(): " + ex); + } + } + + this.sock = null; + this.State = PhotonSocketState.Disconnected; + } + + + public override bool Connect() + { + this.State = PhotonSocketState.Connecting; + + + if (!this.ConnectAddress.Contains("IPv6")) + { + this.ConnectAddress += "&IPv6"; // this makes the Photon Server return a host name for the next server (NS points to MS and MS points to GS) + } + + // earlier, we read the proxy address/scheme and failed to connect entirely, if that wasn't successful... + // it was either successful (using the resulting proxy address) or no connect at all... + + // we want: + // WITH support: fail if the scheme is wrong or use it if possible + // WITHOUT support: use proxy address, if it's a direct value (not a scheme we provide) or fail if it's a scheme + + string proxyServerAddress; + if (!this.ReadProxyConfigScheme(this.ProxyServerAddress, this.ServerAddress, out proxyServerAddress)) + { + this.Listener.DebugReturn(DebugLevel.INFO, "ReadProxyConfigScheme() failed. Using no proxy."); + } + + try + { + this.sock = new WebSocket(new Uri(this.ConnectAddress), proxyServerAddress, this.OpenCallback, this.ReceiveCallback, this.ErrorCallback, this.CloseCallback, this.SerializationProtocol); + this.sock.DebugReturn = (DebugLevel l, string s) => + { + if (this.State != PhotonSocketState.Disconnected) + { + this.Listener.DebugReturn(l, this.State + " " + s); + } + }; + + this.sock.Connect(); + return true; + } + catch (Exception e) + { + this.Listener.DebugReturn(DebugLevel.ERROR, "SocketWebTcp.Connect() caught exception: " + e); + return false; + } + } + + private void CloseCallback(int code, string reason) + { + if (this.State == PhotonSocketState.Connecting) + { + this.HandleException(StatusCode.ExceptionOnConnect); // sets state to Disconnecting + return; + } + + // passing-on close only if this socket is still used / expected to be connected + if (this.State != PhotonSocketState.Disconnecting && this.State != PhotonSocketState.Disconnected) + { + this.Listener.DebugReturn(DebugLevel.ERROR, "SocketWebTcp.CloseCallback(). Going to disconnect. Server: " + this.ServerAddress + " Error: " + code + " Reason: " + reason); + this.HandleException(StatusCode.DisconnectByServerReasonUnknown); // sets state to Disconnecting + } + } + + // code can be from JsLib or WebSocket-Sharp, so it is not guaranteed to be the same in both cases + private void ErrorCallback(int code, string message) + { + // passing-on errors only if this socket is still used / expected to be connected + if (this.State != PhotonSocketState.Disconnecting && this.State != PhotonSocketState.Disconnected) + { + this.Listener.DebugReturn(DebugLevel.ERROR, "SocketWebTcp.ErrorCallback(). Going to disconnect. Server: " + this.ServerAddress + " Error: " + code + " Message: " + message); + this.HandleException(this.State != PhotonSocketState.Connected ? StatusCode.ExceptionOnConnect : StatusCode.ExceptionOnReceive); // sets state to Disconnecting + } + } + + private void OpenCallback() + { + if (State == PhotonSocketState.Connecting) + { + this.State = PhotonSocketState.Connected; + this.peerBase.OnConnect(); + } + } + + + /// + /// Attempts to read a proxy configuration defined by a address prefix. Only available to Industries Circle members on demand. + /// + /// + /// Extended proxy support is available to Industries Circle members. Where available, proxy addresses may be defined as 'auto:', 'pac:' or 'system:'. + /// In all other cases, the proxy address is used as is and fails to read configs (if one of the listed schemes is used). + /// + /// Requires file ProxyAutoConfig.cs and compile define: WEBSOCKET_PROXYCONFIG_SUPPORT. + /// + /// Proxy address from the server configuration. + /// Url to connect to (one of the Photon servers). + /// Resulting proxy URL to use. + /// False if there is some error and the resulting proxy address should not be used. + private bool ReadProxyConfigScheme(string proxyAddress, string url, out string proxyUrl) + { + proxyUrl = null; + + #if !WEBSOCKET_PROXYCONFIG + + if (!string.IsNullOrEmpty(proxyAddress)) + { + if (proxyAddress.StartsWith("auto:") || proxyAddress.StartsWith("pac:") || proxyAddress.StartsWith("system:")) + { + this.Listener.DebugReturn(DebugLevel.WARNING, "Proxy configuration via auto, pac or system is only supported with the WEBSOCKET_PROXYCONFIG define. Using no proxy instead."); + return true; + } + proxyUrl = proxyAddress; + } + + return true; + + #else + + if (!string.IsNullOrEmpty(proxyAddress)) + { + var httpUrl = url.ToString().Replace("ws://", "http://").Replace("wss://", "https://"); // http(s) schema required in GetProxyForUrlUsingPac call + bool auto = proxyAddress.StartsWith("auto:", StringComparison.InvariantCultureIgnoreCase); + bool pac = proxyAddress.StartsWith("pac:", StringComparison.InvariantCultureIgnoreCase); + + if (auto || pac) + { + string pacUrl = ""; + if (pac) + { + pacUrl = proxyAddress.Substring(4); + if (pacUrl.IndexOf("://") == -1) + { + pacUrl = "http://" + pacUrl; //default to http + } + } + + string processTypeStr = auto ? "auto detect" : "pac url " + pacUrl; + + this.Listener.DebugReturn(DebugLevel.INFO, "WebSocket Proxy: " + url + " " + processTypeStr); + + string errDescr = ""; + var err = ProxyAutoConfig.GetProxyForUrlUsingPac(httpUrl, pacUrl, out proxyUrl, out errDescr); + + if (err != 0) + { + this.Listener.DebugReturn(DebugLevel.ERROR, "WebSocket Proxy: " + url + " " + processTypeStr + " ProxyAutoConfig.GetProxyForUrlUsingPac() error: " + err + " (" + errDescr + ")"); + return false; + } + } + else if (proxyAddress.StartsWith("system:", StringComparison.InvariantCultureIgnoreCase)) + { + this.Listener.DebugReturn(DebugLevel.INFO, "WebSocket Proxy: " + url + " system settings"); + string proxyAutoConfigPacUrl; + var err = ProxySystemSettings.GetProxy(out proxyUrl, out proxyAutoConfigPacUrl); + if (err != 0) + { + this.Listener.DebugReturn(DebugLevel.ERROR, "WebSocket Proxy: " + url + " system settings ProxySystemSettings.GetProxy() error: " + err); + return false; + } + if (proxyAutoConfigPacUrl != null) + { + if (proxyAutoConfigPacUrl.IndexOf("://") == -1) + { + proxyAutoConfigPacUrl = "http://" + proxyAutoConfigPacUrl; //default to http + } + this.Listener.DebugReturn(DebugLevel.INFO, "WebSocket Proxy: " + url + " system settings AutoConfigURL: " + proxyAutoConfigPacUrl); + string errDescr = ""; + err = ProxyAutoConfig.GetProxyForUrlUsingPac(httpUrl, proxyAutoConfigPacUrl, out proxyUrl, out errDescr); + + if (err != 0) + { + this.Listener.DebugReturn(DebugLevel.ERROR, "WebSocket Proxy: " + url + " system settings AutoConfigURLerror: " + err + " (" + errDescr + ")"); + return false; + } + } + } + else + { + proxyUrl = proxyAddress; + } + + this.Listener.DebugReturn(DebugLevel.INFO, "WebSocket Proxy: " + url + " -> " + (string.IsNullOrEmpty(proxyUrl) ? "DIRECT" : "PROXY " + proxyUrl)); + } + + return true; + #endif + } + + + public override bool Disconnect() + { + if (this.ReportDebugOfLevel(DebugLevel.INFO)) + { + this.Listener.DebugReturn(DebugLevel.INFO, "SocketWebTcp.Disconnect()"); + } + + this.State = PhotonSocketState.Disconnecting; + + lock (this.syncer) + { + if (this.sock != null) + { + try + { + this.sock.Close(); + } + catch (Exception ex) + { + this.Listener.DebugReturn(DebugLevel.ERROR, "Exception in SocketWebTcp.Disconnect(): " + ex); + } + + this.sock = null; + } + } + + this.State = PhotonSocketState.Disconnected; + return true; + } + + /// Used by TPeer + public override PhotonSocketError Send(byte[] data, int length) + { + if (this.State != PhotonSocketState.Connected) + { + return PhotonSocketError.Skipped; + } + + try + { + if (data.Length > length) + { + byte[] trimmedData = new byte[length]; + Buffer.BlockCopy(data, 0, trimmedData, 0, length); + data = trimmedData; + } + + if (this.sock != null) + { + this.sock.Send(data); + } + } + catch (Exception e) + { + this.Listener.DebugReturn(DebugLevel.ERROR, "Cannot send to: " + this.ServerAddress + ". " + e.Message); + + this.HandleException(StatusCode.Exception); + return PhotonSocketError.Exception; + } + + return PhotonSocketError.Success; + } + + + public override PhotonSocketError Receive(out byte[] data) + { + data = null; + return PhotonSocketError.NoData; + } + + public void ReceiveCallback(byte[] buf, int len) + { + // once the websocket is disconnecting / disconnected, it should not receive anything anymore + if (State == PhotonSocketState.Disconnecting || State == PhotonSocketState.Disconnected) + { + return; + } + + try + { + this.HandleReceivedDatagram(buf, len, false); + } + catch (Exception e) + { + if (this.State != PhotonSocketState.Disconnecting && this.State != PhotonSocketState.Disconnected) + { + if (this.ReportDebugOfLevel(DebugLevel.ERROR)) + { + this.EnqueueDebugReturn(DebugLevel.ERROR, "SocketWebTcp.ReceiveCallback() caught exception. Going to disconnect. State: " + this.State + ". Server: '" + this.ServerAddress + "' Exception: " + e); + } + + this.HandleException(StatusCode.ExceptionOnReceive); + } + } + } + } +} + +#endif \ No newline at end of file diff --git a/Assets/Photon/PhotonLibs/WebSocket/SocketWebTcp.cs.meta b/Assets/Photon/PhotonLibs/WebSocket/SocketWebTcp.cs.meta new file mode 100644 index 00000000..7f04d3c3 --- /dev/null +++ b/Assets/Photon/PhotonLibs/WebSocket/SocketWebTcp.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ac953d6a57a9ea94e96ec689598995d5 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/Photon/PhotonLibs/WebSocket/WebSocket.cs b/Assets/Photon/PhotonLibs/WebSocket/WebSocket.cs new file mode 100644 index 00000000..1ea990ad --- /dev/null +++ b/Assets/Photon/PhotonLibs/WebSocket/WebSocket.cs @@ -0,0 +1,347 @@ +#if UNITY_WEBGL || WEBSOCKET || WEBSOCKET_PROXYCONFIG + +#if UNITY_WEBGL && !UNITY_EDITOR +#define PHOTON_WEBSOCKET_JS +#else +#define PHOTON_WEBSOCKET_CS +#endif + +// -------------------------------------------------------------------------------------------------------------------- +// +// Provided originally by Unity to cover WebSocket support in WebGL and the Editor. Modified by Exit Games GmbH. +// +// developer@exitgames.com +// -------------------------------------------------------------------------------------------------------------------- + + +namespace ExitGames.Client.Photon +{ + using System; + + #if PHOTON_WEBSOCKET_JS + using System.Collections.Generic; + using System.Runtime.InteropServices; + using System.Text; + using AOT; + #else + using WebSocketSharp; + using System.Security.Authentication; + #endif + + + // changed mProxyAddress to ProxyAddress + // changed mUrl to Url + + public partial class WebSocket + { + /// Server address + public Uri Url { get; private set; } + + /// Only supported by WebSocket-sharp dll. + public string ProxyAddress { get; private set; } + + /// Photon uses this to agree on a serialization protocol. Either: GpBinaryV16 or GpBinaryV18. Based on enum SerializationProtocol. + private readonly string protocols = "GpBinaryV16"; + + + /// True after the websocket callback OnConnect until close or (permanent) error. + public bool Connected { get; private set; } + + /// Null until some error happened in underlying websocket. + public string Error { get; private set; } + + + // callbacks to higher level + private Action recvCallback; + private Action openCallback; + private Action errorCallback; + private Action closeCallback; + // logging callback + public Action DebugReturn { get; set; } + + + public WebSocket(Uri url, string proxyAddress, Action openCallback, Action recvCallback, Action errorCallback, Action closeCallback, string protocols = null) + { + this.Url = url; + this.ProxyAddress = proxyAddress; + + this.recvCallback = recvCallback; + this.openCallback = openCallback; + this.errorCallback = errorCallback; + this.closeCallback = closeCallback; + + if (!string.IsNullOrEmpty(protocols)) + { + this.protocols = protocols; + } + + string scheme = this.Url.Scheme; + if (!scheme.Equals("ws") && !scheme.Equals("wss")) + { + throw new ArgumentException("Unsupported protocol: " + scheme); + } + } + } + + + // .net specific implementation using websocket-sharp.dll + public partial class WebSocket + { + #if PHOTON_WEBSOCKET_CS + + public const string Implementation = "WebSocketSharp"; + + WebSocketSharp.WebSocket m_Socket; + + + public void Connect() + { + this.m_Socket = new WebSocketSharp.WebSocket(this.Url.ToString(), new string[] {this.protocols}); + this.m_Socket.Log.Output = (ld, f) => + { + var s = string.Format("WebSocketSharp: {0}", ld.Message); + switch (ld.Level) + { + case LogLevel.Trace: + case LogLevel.Debug: + this.DebugReturn(DebugLevel.ALL, s); + break; + case LogLevel.Info: + this.DebugReturn(DebugLevel.INFO, s); + break; + case LogLevel.Warn: + this.DebugReturn(DebugLevel.WARNING, s); + break; + case LogLevel.Error: + case LogLevel.Fatal: + this.DebugReturn(DebugLevel.ERROR, s); + break; + } + }; + + this.m_Socket.OnOpen += (sender, e) => + { + this.Connected = true; + this.openCallback(); + }; + this.m_Socket.OnMessage += (sender, e) => + { + this.recvCallback(e.RawData, e.RawData.Length); + }; + this.m_Socket.OnError += (sender, e) => + { + this.Connected = false; + this.Error = e.Message + (e.Exception == null ? "" : " / " + e.Exception); + this.errorCallback(0, e.Message); + }; + this.m_Socket.OnClose += (sender, e) => + { + this.Connected = false; + this.closeCallback(e.Code, e.Reason); + }; + + + if (!String.IsNullOrEmpty(this.ProxyAddress)) + { + string user = null; + string pass = null; + + var authDelim = this.ProxyAddress.IndexOf("@"); + if (authDelim != -1) + { + user = this.ProxyAddress.Substring(0, authDelim); + this.ProxyAddress = this.ProxyAddress.Substring(authDelim + 1); + var passDelim = user.IndexOf(":"); + if (passDelim != -1) + { + pass = user.Substring(passDelim + 1); + user = user.Substring(0, passDelim); + } + } + + // throws an exception, if scheme not specified + this.m_Socket.SetProxy("http://" + this.ProxyAddress, user, pass); + } + + if (this.m_Socket.IsSecure) + { + this.m_Socket.SslConfiguration.EnabledSslProtocols = this.m_Socket.SslConfiguration.EnabledSslProtocols | (SslProtocols)(3072 | 768); + } + + + this.m_Socket.ConnectAsync(); + } + + + public void Close() + { + // at this low level we are fine with closing the socket async / non-blocking + this.m_Socket.CloseAsync(); + } + + public void Send(byte[] buffer) + { + this.m_Socket.Send(buffer); + } + + #endif + } + + + // js/native specific implementation + public partial class WebSocket + { + #if PHOTON_WEBSOCKET_JS + + public const string Implementation = "JsLib"; + + static Dictionary instances = new Dictionary(); + + [DllImport("__Internal")] + private static extern int SocketCreate(string url, string protocols, Action openCallbackStatic, Action recvCallbackStatic, Action errorCallbackStatic, Action closeCallbackStatic); + + [DllImport("__Internal")] + private static extern int SocketState (int socketInstance); + + [DllImport("__Internal")] + private static extern void SocketSend (int socketInstance, byte[] ptr, int length); + + [DllImport("__Internal")] + private static extern void SocketClose (int socketInstance); + + [DllImport("__Internal")] + private static extern int SocketError (int socketInstance, byte[] ptr, int length); + + private int m_NativeRef = 0; + + // TODO: discuss if we need this anymore?! + public bool ConnectedOLD + { + get { return SocketState(m_NativeRef) != 0; } + } + + private const int SocketErrorBufferSize = 1024; + private readonly byte[] socketErrorBuffer = new byte[SocketErrorBufferSize]; + + // TODO: discuss if we need this anymore?! + public string ErrorOLD + { + get { + int result = SocketError (m_NativeRef, this.socketErrorBuffer, SocketErrorBufferSize); + + if (result == 0) + return null; + + return Encoding.UTF8.GetString (this.socketErrorBuffer); + } + } + + + public void Connect() + { + m_NativeRef = SocketCreate (this.Url.ToString(), this.protocols, OpenCallbackStatic, RecvCallbackStatic, ErrorCallbackStatic, CloseCallbackStatic); + instances[m_NativeRef] = this; + } + + public void Close() + { + SocketClose(m_NativeRef); + } + + + public void Send(byte[] buffer) + { + SocketSend (m_NativeRef, buffer, buffer.Length); + } + + + [MonoPInvokeCallback(typeof(Action))] + public static void RecvCallbackStatic(int instance, IntPtr p, int len) + { + instances[instance].RecvCallbackInstance(p, len); + } + + private byte[] receiveBuffer; + + public void RecvCallbackInstance(IntPtr p, int len) + { + if (this.receiveBuffer == null || this.receiveBuffer.Length < len) + { + this.receiveBuffer = new byte[len]; + } + Marshal.Copy(p, this.receiveBuffer, 0, len); + + this.recvCallback(this.receiveBuffer, len); + } + + + + [MonoPInvokeCallback(typeof(Action))] + public static void OpenCallbackStatic(int instance) + { + instances[instance].OpenCallbackInstance(); + } + + public void OpenCallbackInstance() + { + this.Connected = true; + this.openCallback(); + } + + + + [MonoPInvokeCallback(typeof(Action))] + public static void ErrorCallbackStatic(int instance, int code) + { + string msg; + switch (code) + { + case 1001: + msg = "Endpoint going away."; + break; + case 1002: + msg = "Protocol error."; + break; + case 1003: + msg = "Unsupported message."; + break; + case 1005: + msg = "No status."; + break; + case 1006: + msg = "Abnormal disconnection."; + break; + case 1009: + msg = "Data frame too large."; + break; + default: + msg = "Error " + code; + break; + } + + instances[instance].ErrorCallbackInstance(code, msg); + } + + public void ErrorCallbackInstance(int code, string msg) + { + this.Connected = false; + this.errorCallback(code, msg); + } + + + [MonoPInvokeCallback(typeof(Action))] + public static void CloseCallbackStatic(int instance, int code) + { + string msg = "n/a from jslib"; + instances[instance].CloseCallbackInstance(code, msg); + } + + public void CloseCallbackInstance(int code, string msg) + { + this.Connected = false; + this.closeCallback(code, msg); + } + #endif + } +} +#endif \ No newline at end of file diff --git a/Assets/Photon/PhotonLibs/WebSocket/WebSocket.cs.meta b/Assets/Photon/PhotonLibs/WebSocket/WebSocket.cs.meta new file mode 100644 index 00000000..241c9218 --- /dev/null +++ b/Assets/Photon/PhotonLibs/WebSocket/WebSocket.cs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b1bad04f7805f764dba77f0d4518e0f0 +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: diff --git a/Assets/Photon/PhotonLibs/WebSocket/WebSocket.jslib b/Assets/Photon/PhotonLibs/WebSocket/WebSocket.jslib new file mode 100644 index 00000000..16825e80 --- /dev/null +++ b/Assets/Photon/PhotonLibs/WebSocket/WebSocket.jslib @@ -0,0 +1,88 @@ +var LibraryWebSockets = { +$webSocketInstances: [], + +SocketCreate: function(url, protocols, openCallback, recvCallback, errorCallback, closeCallback) +{ + var str = UTF8ToString(url); + var prot = UTF8ToString(protocols); + var socket = { + socket: new WebSocket(str, [prot]), + error: null, + sendBufForShared: null, + send: typeof(SharedArrayBuffer) == "function" ? // SharedArrayBuffer is available and will not crash in 'isinstance' check + function (socketInstance, ptr, length) { + if (HEAPU8.buffer instanceof SharedArrayBuffer) { + if (!this.sendBufForShared || this.sendBufForShared.byteLength < length) { + this.sendBufForShared = new ArrayBuffer(length); + } + var u8arr = new Uint8Array(this.sendBufForShared, 0, length); + u8arr.set(new Uint8Array(HEAPU8.buffer, ptr, length)); + this.socket.send(u8arr); + } else { + this.socket.send(new Uint8Array(HEAPU8.buffer, ptr, length)); + } + } + : + function (socketInstance, ptr, length) { // SharedArrayBuffer is not defined, ptr type is always ArrayBuffer + this.socket.send(new Uint8Array(HEAPU8.buffer, ptr, length)); + } + } + var instance = webSocketInstances.push(socket) - 1; + socket.socket.binaryType = 'arraybuffer'; + + socket.socket.onopen = function () { + {{{ makeDynCall('vi', 'openCallback') }}}(instance); + } + socket.socket.onmessage = function (e) { + if (e.data instanceof ArrayBuffer) + { + const b = e.data; + const ptr = _malloc(b.byteLength); + const dataHeap = new Int8Array(HEAPU8.buffer, ptr, b.byteLength); + dataHeap.set(new Int8Array(b)); + {{{ makeDynCall('viii', 'recvCallback') }}}(instance, ptr, b.byteLength); + _free(ptr); + } + }; + socket.socket.onerror = function (e) { + {{{ makeDynCall('vii', 'errorCallback') }}}(instance, e.code); + } + socket.socket.onclose = function (e) { + if (e.code != 1000) + { + {{{ makeDynCall('vii', 'closeCallback') }}}(instance, e.code); + } + } + return instance; +}, + +SocketState: function (socketInstance) +{ + var socket = webSocketInstances[socketInstance]; + return socket.socket.readyState; +}, + +SocketError: function (socketInstance, ptr, bufsize) +{ + var socket = webSocketInstances[socketInstance]; + if (socket.error == null) + return 0; + stringToUTF8(socket.error, ptr, bufsize); + return 1; +}, + +SocketSend: function (socketInstance, ptr, bufsize) +{ + var socket = webSocketInstances[socketInstance]; + socket.send(socketInstance, ptr, bufsize); +}, + +SocketClose: function (socketInstance) +{ + var socket = webSocketInstances[socketInstance]; + socket.socket.close(); +} +}; + +autoAddDeps(LibraryWebSockets, '$webSocketInstances'); +mergeInto(LibraryManager.library, LibraryWebSockets); diff --git a/Assets/Photon/PhotonLibs/WebSocket/WebSocket.jslib.meta b/Assets/Photon/PhotonLibs/WebSocket/WebSocket.jslib.meta new file mode 100644 index 00000000..144b9812 --- /dev/null +++ b/Assets/Photon/PhotonLibs/WebSocket/WebSocket.jslib.meta @@ -0,0 +1,32 @@ +fileFormatVersion: 2 +guid: 04bb5f307f2e48b4fbaa6da865fd4091 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + WebGL: WebGL + second: + enabled: 1 + settings: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/PhotonLibs/WebSocket/websocket-sharp.README b/Assets/Photon/PhotonLibs/WebSocket/websocket-sharp.README new file mode 100644 index 00000000..55690fba --- /dev/null +++ b/Assets/Photon/PhotonLibs/WebSocket/websocket-sharp.README @@ -0,0 +1,5 @@ + +UPDATED: WebSocket/websocket-sharp.dll from our own Git Fork. Commit: 77f74bd +forked from https://github.com/sta/websocket-sharp.git + +websocket-sharp is provided under The MIT License as mentioned here: https://github.com/sta/websocket-sharp#license \ No newline at end of file diff --git a/Assets/Photon/PhotonLibs/WebSocket/websocket-sharp.README.meta b/Assets/Photon/PhotonLibs/WebSocket/websocket-sharp.README.meta new file mode 100644 index 00000000..47cc15d3 --- /dev/null +++ b/Assets/Photon/PhotonLibs/WebSocket/websocket-sharp.README.meta @@ -0,0 +1,4 @@ +fileFormatVersion: 2 +guid: 3e262c2b04eaa8440987b50a91e86674 +DefaultImporter: + userData: diff --git a/Assets/Photon/PhotonLibs/WebSocket/websocket-sharp.dll b/Assets/Photon/PhotonLibs/WebSocket/websocket-sharp.dll new file mode 100644 index 00000000..c0414205 Binary files /dev/null and b/Assets/Photon/PhotonLibs/WebSocket/websocket-sharp.dll differ diff --git a/Assets/Photon/PhotonLibs/WebSocket/websocket-sharp.dll.meta b/Assets/Photon/PhotonLibs/WebSocket/websocket-sharp.dll.meta new file mode 100644 index 00000000..009e5657 --- /dev/null +++ b/Assets/Photon/PhotonLibs/WebSocket/websocket-sharp.dll.meta @@ -0,0 +1,140 @@ +fileFormatVersion: 2 +guid: 748eb70bc0d7515498ef73fed155520a +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + : Any + second: + enabled: 0 + settings: + Exclude Android: 0 + Exclude Editor: 0 + Exclude Linux: 0 + Exclude Linux64: 0 + Exclude LinuxUniversal: 0 + Exclude OSXUniversal: 0 + Exclude WebGL: 1 + Exclude Win: 0 + Exclude Win64: 0 + Exclude WindowsStoreApps: 1 + - first: + : OSXIntel + second: + enabled: 0 + settings: + CPU: AnyCPU + - first: + : OSXIntel64 + second: + enabled: 0 + settings: + CPU: AnyCPU + - first: + : WP8 + second: + enabled: 0 + settings: + CPU: AnyCPU + DontProcess: False + PlaceholderPath: + - first: + Android: Android + second: + enabled: 1 + settings: + CPU: ARMv7 + - first: + Any: + second: + enabled: 1 + settings: {} + - first: + Editor: Editor + second: + enabled: 1 + settings: + CPU: AnyCPU + DefaultValueInitialized: true + OS: AnyOS + - first: + Facebook: Win + second: + enabled: 0 + settings: + CPU: AnyCPU + - first: + Facebook: Win64 + second: + enabled: 0 + settings: + CPU: AnyCPU + - first: + Standalone: Linux + second: + enabled: 1 + settings: + CPU: x86 + - first: + Standalone: Linux64 + second: + enabled: 1 + settings: + CPU: AnyCPU + - first: + Standalone: LinuxUniversal + second: + enabled: 1 + settings: + CPU: AnyCPU + - first: + Standalone: OSXUniversal + second: + enabled: 1 + settings: + CPU: AnyCPU + - first: + Standalone: Win + second: + enabled: 1 + settings: + CPU: AnyCPU + - first: + Standalone: Win64 + second: + enabled: 1 + settings: + CPU: AnyCPU + - first: + Windows Store Apps: WindowsStoreApps + second: + enabled: 0 + settings: + CPU: AnyCPU + DontProcess: False + PlaceholderPath: + SDK: AnySDK + ScriptingBackend: AnyScriptingBackend + - first: + XboxOne: XboxOne + second: + enabled: 0 + settings: {} + - first: + iPhone: iOS + second: + enabled: 0 + settings: + CompileFlags: + FrameworkDependencies: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/PhotonLibs/changes-library.txt b/Assets/Photon/PhotonLibs/changes-library.txt new file mode 100644 index 00000000..a44c0cb2 --- /dev/null +++ b/Assets/Photon/PhotonLibs/changes-library.txt @@ -0,0 +1,1980 @@ + +Photon C# Client Library - Changelog +www.photonengine.com - Join our Discord Server: https://dashboard.photonengine.com/account/profile + + +Version 4.1.8.16 (31. July 2025 - rev8016) + Added: SupportClass overload for CalculateCrc() with offset. + Changed: CalculateCrc() without offset is now obsolete. + Note: Minimal release to make a small update (see above) available for the Voice SDK. + +Version 4.1.8.15 (08. May 2025 - rev7931) + Updated: WebSocket.jslib to use a new Emscripten macro for callbacks to Unity. This fixes issues of v4.1.8.14 with logs like "Module.dynCall_viii is not a function". These fixes update the jslib to newer Emcripten versions used by Unity. + Note: Due to changes above, this library now requires Unity 2020+. You could use the previous version if you need support for Unity 2019 or older. + Added: Support for SharedArrayBuffer sending. This is used when Unity 6 is set to use experimental C/C++ Threading Support in WebAssembly export. + +Version 4.1.8.14 (11. March 2025 - rev7866) + Changed: WebSocket implementation for Unity WebGL/JS exports and WebSocket-sharp based implementation (in PhotonLibs\WebSocket\). + Note: The goal is to process incoming messages in WebGL (and with WebSocket-sharp) more directly. This specifically helps the Voice SDK on WebGL to process the stream while in background. + Changed: WebSocketTcp and WebSocket to receive incoming messages via callbacks. At the same time our WebSocket code now also implements all typical callbacks for WS/WSS. + Changed: WebSocket.jslib replicates "standard" JS websocket methods and callbacks in its interface instead of caching messages for polling. + Changed: PreserveAttribute in SupportClass.cs is now public, so it can be used in WebSocket code as needs be. + +Version 4.1.8.13 (09. January 2025 - rev7815) + No notable changes. + +Version 4.1.8.12 (16. December 2024 - rev7801) + Fixed: Some allocations that can be avoided. For example the EventData.Sender is no longer allocating anything, if the Sender of an event was not set (it is considered ActorNumber 0). + Changed: PhotonPeer.ClientSdkId is now public and can be set by SDKs. + Changed: InitV3 to use ClientSdkIdShifted, which all init requests should use. + +Version 4.1.8.11 (17. October 2024 - rev7735) + Changed: Internal TryParseAddress method, which failed for URLs without a port. This did not work well with the Realtime API's AddressRewriter and URLs for Discord Activities (which typically use a scheme but not a port). + +Version 4.1.8.10 (14. October 2024 - rev7732) + Internal: Preparation to support .Net Standard 2.1 builds. + Updated: WebSocket for WebGL exports (WebSocket\WebSocket.jslib) to support Unity's (experimental) option to "Enable Native C/C++ Multithreading". + +Version 4.1.8.9 (02. September 2024 - rev7660) + No notable changes. + +Version 4.1.8.8 (02. August 2024 - rev7638) + Fixed: TCP specific issue where RTT always goes up. This TCP RTT bug was first in v4.1.8.5 (13. May 2024 - rev7556). + Changed: SendPing handling for the fix. Pings are again directly sent right away - unless the peer was sending a regular, stand-alone init-request. Then waitForInitResponse is used to prevent sending a ping message early. + Changed: SendPing is now called even if the peer is not yet in "Connected" state to allow send pings in the WSS case where an init-response may have been lost (see waitForInitResponse ref doc). + Changed: SendPing will now check its own ping-interval. + Changed: FetchServerTimestamp to call SendPing with the ping-interval check, which means you can fetch the server timestamp only in ping-intervals, too. It was never the intention you could fetch it very frequently. In fact, fetching it should be done only if you are sure the time drifted significantly or RTT improved. + +Version 4.1.8.7 (05. July 2024 - rev7622) + Fixed: SendAcksOnly() for TCP and WS/WSS. It now sends a ping immediately to actually keep the connection up if SendOutgoingCommands() is not called for longer (e.g. when PUN IsMessageQueueRunning is false). + Note: The above issue was introduced in rev7553, 8. May 2024. So v4.1.8.5 and v4.1.8.6 would time out for TCP or WebSockets, if SendOutgoingCommands() was paused longer. + Changed: TPeer.SendPing() to have an optional parameter to send the ping immediately. This allows SendAcksOnly to send it without sending anything queued. + Changed: TPeer.Reset() to also update lastPingActivity. + +Version 4.1.8.6 (25. May 2024 - rev7571) + No notable changes. The Realtime API has fixes for non-Unity projects. + +Version 4.1.8.5 (13. May 2024 - rev7556) + Changed: TPeer.SendPing() will now enqueue the message instead of sending it immmediately. This makes sure a ping is never sent as first message (which has to be an Init). + Changed: To re-using a ParameterDictionary for pinging in WS / WSS. + +Version 4.1.8.4 (26. April 2024 - rev7530) + Changed: SendAcksOnly and SendOutgoingCommands logging (they only log successful re-sends). + Changed: In QueueSentCommand the reliableCommandsSent is increased only for commands that are sent the first time (not counting repeats anymore). + +Version 4.1.8.3 (24. April 2024 - rev7519) + Changed: Sending of reliable commands. There is now a "Send Window" which limits how many reliable commands are being sent, based off the lowest sent sequence number, only the next XY sequence numbers can be sent. This moves with incoming acknowledgements. This avoids spamming the server, which caused resends. + Note: Per channel, the Send Window indirectly affects unreliable and even unsequenced commands by ending the sending for a channel early. Use a separate channel (higher number) for larger chunks of data to avoid related lag. + Renamed: PhotonPeer.SequenceDeltaLimitSends to SendWindowSize. + Removed: PhotonPeer.SequenceDeltaLimitResends. If a reliable command needs re-sending, it is something the server is waiting for, so we just send. + Changed: Resending of commands (UDP). SendOutgoingCommands now makes sure to return true if any of the resends does not fit in the current datagram. Internally, the next check for resends is scheduled "immediately". + +Version 4.1.8.2 (25. March 2024 - rev7471) + Added: PhotonLibs\WebSocket\PhotonWebSocket.asmdef, which wraps up the code and dlls for WebGL exports. This is auto referenced. + +Version 4.1.8.1 (12. March 2024 - rev7444) + Fixed: SocketTcp which was reading the wrong TCP Header byte as "reliable" indicator. This meant, the TrafficStatsIncoming were wrong. + +Version 4.1.8.0 (31. January 2024 - rev7364) + No notable changes. The Realtime API has some breaking changes. Check that changelog. + +Version 4.1.7.4 (13. December 2023 - rev7284) + Fixed: PhotonPeer.BytesOut internal counting. + +Version 4.1.7.3 (07. December 2023 - rev7252) + No notable changes. + +Version 4.1.7.2 (20. September 2023 - rev7218) + Fixed: DisconnectTimeout is no longer capped. You can set any value client side. Server side timeouts can only be set for Self Hosted or Enterprise Server. + +Version 4.1.7.1 (06. September 2023 - rev7148) + Fixed: Internal state changes to report UDP based Client Timeouts to the server. + +Version 4.1.7.0 (18. August 2023 - rev7111) + Added: PhotonPeer.SocketErrorCode (int) to provide the low level error code (relevant when the connection gets lost). + Changed: SocketUdp to report WouldBlock exceptions as "busy". The handling code could use this to not try send anything else in this loop. + Added: Preserve attribute to SocketWebTcp constructor to keep Unity from stripping it from builds. + +Version 4.1.6.26 (09. August 2023 - rev7076) + Fixed: PhotonClientWebSocket.Send() waits up to 5ms after sending async to allow the message being written to the network layer. + Fixed: PhotonClientWebSocket.Send() now provides a distinct return code if the WebSocket did not yet finish sending the data. In that case, the memory which was used for it, is not returned to the pooling. This can cause allocations but fixes cases where messages were modified after they were considered sent. + Changed: PhotonClientWebSocket connecting to the server is cancelled after 7 second max (was: 5). + Changed: PhotonClientWebSocket AsyncConnectAndReceive will log to the debug callback instead of using Console.Writeline(). + Changed: PhotonClientWebSocket error handling for readTask.Wait(). + +Version 4.1.6.25 (05. June 2023 - rev6925) + Added: TPeer now measures longestSentCall in SendData(). + Changed: TPeer.SendOutgoingCommands() to return false if the socket is still busy (which means "no need to call send again in this loop). + +Version 4.1.6.24 (15. May 2023 - rev6894) + Changed: PhotonClientWebSocket.Send() will now Wait up to 3ms for the Send-Task before it returns "busy". This allows the task to complete and the TPeer can send multiple messages until it send MTU bytes. That avoids increasing lag, if the game logic calls SendOutgoingCommands() sparingly. + Changed: PeerBase.Reset() will now also clear the ActionQueue. + Fixed: WebSocket connection issues where clients never get into a Connected state. This is a workaround for cases where a WebSocket bug skips the callback for the Init Response from the server. This is a rare issue which does not reproduce 100% of the time. + Changed: WebSocket connections will now send pings in one second intervals as long as the WebSocket is connected (and the peerConnectionState is not Disconnected). + Changed: If a ping result arrives while the peer is still Connecting, it is used as Init Response on WebSocket connections. + Changed: TPeer.FetchServerTimestamp() to only log something (INFO level) if the peer is not connected and initialized yet. FetchServerTimestamp can be repeated and does not need a SendError callback. + Changed: PhotonClientWebSocket.Disconnect() to call CloseAsync() if the WebSocket is already in state CloseReceived. This should just close the socket locally. + +Version 4.1.6.23 (15. March 2023 - rev6743) + Fixed: Handling of unknown command types in Initialize(EnetPeer peer, byte[] inBuff, ref int readingOffset). Reading offset is set by Size value. + Fixed: ReadCustomType() and ReadCustomTypeArray() to check if DeserializeStreamFunction is null, before using it. There was a check which prevented issues but it wasn't correct. + +Version 4.1.6.22 (21. December 2022 - rev6637) + Note: No notable changes. Only Reatime API changed. + +Version 4.1.6.21 (14. December 2022 - rev6623) + Note: No notable changes. + +Version 4.1.6.20 (09. November 2022 - rev6568) + Changed: EnetPeer.Disconnect() will no longer Sleep for a minimum of time. Instead the Socket might want to "linger" on close, if needs be. + Fixed: Discarded unreliable commands did not return memory that was pooled (causing additional allocations). + Changed: SocketUdp to use a non-blocking Socket. Before receiving data, we Poll the socket. This fixes an issue where apps were freezing for up to a second on freeze (specifically on Unity IL2CPP Windows Standalone builds). + +Version 4.1.6.19 (12. October 2022 - rev6519) + Changed: Handling of incorrect stream data. + +Version 4.1.6.18 (20. September 2022 - rev6501) + Changed: WebSocket.jslib (in Unity related SDKs) to use stringToUTF8 instead of writeStringToMemory (which is obsolete). + +Version 4.1.6.17 (02. August 2022 - rev6472) + Changed: SequenceDeltaLimitResends to 100 and SequenceDeltaLimitSends to 75. + +Version 4.1.6.16 (20. June 2022) + Note: No notable changes. + +Version 4.1.6.15 (02. June 2022) + Note: No notable changes. + +Version 4.1.6.14 (26. April 2022 - rev6414) + Fixed: WSS PhotonClientWebSocket blocking the main thread when sending isn't possible. Without loss. This affects the RTT however. + Changed: PhotonClientWebSocket will return PhotonSocketError.Busy, if previous Task is not completed yet. + Changed: TPeer.SendData passes the inner result code (PhotonSocketError) to the calling method. + Changed: TPeer.SendOutgoingCommands() will skip sending, if SendData() returns PhotonSocketError.Busy. + +Version 4.1.6.13 (29. March 2022 - rev6399) + Fixed: Fragment length for Datagram Encryption gets recalculated when the encryption gets enabled. + Fixed: Resends will stop early, if the mtu-udpBufferIndex is less than 80. + Changed: Read for a specified GpType. If it is unknown, this will throw a InvalidDataException instead of reading data in undefined ways. The server is sending us the types, so in regular use, this should actually not happen. It is however cleaner to debug data this way. + Fixed: Several comments and log and exception messages. + Changed: Protocol18.WriteCustomTypeBody to use the size written by the serialize-method, instead of using the returned value. + Added: Short delay before closing a socket to give disconnect messages a better chance of being sent out. + +Version 4.1.6.12 (30. November 2021 - rev6299) + Changed: EnetPeer.Disconnect() sets state Disconnecting immediately. + Changed: EnetPeer.ReceiveIncomingCommands() will skip incoming datagrams when in state Disconnected. + Changed: EnetPeer.ReceiveIncomingCommands() checks the incoming challenge as early as possible to avoid using data from outdated connections. + Changed: How Datagram Encryption is enabled, when the first encrypted datagram arrives. + Changed: Some logged messages. + Added: Parameter "length" to SupportClass.ByteArrayToString to convert only a part of a byte[] to string. + Internal: Changed conversion from ParameterDictionary to Dictionary so that StructWrapper does not leak to resulting Dictionary. + +Version 4.1.6.11 (23. November 2021 - rev6288) + Fixed: Payload encryption initialization (without DiffieHellman) for TCP connection. + Fixed: Protocol16.DeserializeCustom() to check if length of read bytes is available (else, a byte[] is returned). + Changed: Custom Type arrays will return UnknownType[] if the array's item-type is unknown. If the custom type is known but items do not read properly, they will be null. + Added: Checks if each generated Custom Type instance can be assigned to the Custom Type array (and skips those that are null or can't be assigned). + Changed: Protocol18Read.ReadCustomType() and ReadCustomTypeArray() to throw error for size < 0. In that case, it is not possible to continue reading and the data is compromized. + Removed: Some logging code that was specific for Unity. + Removed: Exception handling for OnEvent and OnOperationResponse calls. This turned out somewhat inflexible and was hiding valuable debug information. Instead of the low level try/catch, projects should wrap DispatchIncomingCommands in try/catch blocks. + +Version 4.1.6.10 (21. October 2021 - rev6243) + Fixed: ReadCustomTypeArray to return an object[], if any item in the array is unreadable. This object[] contains any properly read custom types but also items of UnknownType. This is of course likely to cause exceptions in the dispatch callbacks. + Added: Exception handling to OnEvent and OnOperationResponse calls. + +Version 4.1.6.9 (12. October 2021 - rev6231) + Fixed: Reliable UDP protocol to send pings, if no reliable command was acknowledged or sent recently. This bug was preventing a client timeout in some cases. + +Version 4.1.6.8 (30. September 2021 - rev6222) + Fixed: ReadCustomType to not use custom deserialization methods, should the reported size be less than available bytes. + Fixed: ReadCustomType and ReadCustomTypeArray to also skip custom types when size is 0. + +Version 4.1.6.7 (27. September 2021 - rev6214) + Fixed: A bug in Protocol18 reader for very long arrays. + +Version 4.1.6.6 (21. September 2021 - rev6204) + Changed: Dns resolution. It is suspected to fail sometimes due to a Mono/Unity bug (issue 6940). Changing from only attempting Dns.GetHostEntry to first attempting Dns.GetHostAddresses followed by Dns.GetHostByName in case of an Exception (yes obsolete api but maybe a different implementation). + Removed: C# wrapper classes for native plugins (sockets and encryption). These classes will be part of the plugin packages. + Changed: PhotonPeer values NativeSocketLibAvailable, NativePayloadEncryptionLibAvailable and NativeDatagramEncryptionLibAvailable are now obsolete. Unity's IL2CPP does not implement PrelinkAll in IL2CPP and we moved the wrapper classes for native libs out of the assembly. + Added: PayloadEncryptorType and related usage to be able to set another Payload Encryption implementation. This is only for completeness. The managed implementation, which is used by default, is fine. + Fixed: TrafficStatsIncoming for PhotonClientWebSocket (used by default in WS/WSS connections in the .Net Standard 2.0 dll). + +Version 4.1.6.5 (24. August 2021 - rev6181) + Fixed: Protocol16 to throw a NotSupportedException when a length value can not be serialized as short (as defined). This affects arrays (except byte[]), strings, hashtables, dictionaries and other variable length values. + Updated: C# classes to the latest state of the native PhotonEncryptorPlugin API. + +Version 4.1.6.4 (26. July 2021 - rev6143) + Changed: ByteArraySlice and ByteArraySlicePool to achieve thread safety. + Changed: PhotonClientWebSocket.AsyncConnectAndReceive to reuse memory. It may still grow somewhat as memory allocated for very large messages is not released but rather held to receive as big messages. This class is not used for WebGL exports. + Removed: Surplus logging in PhotonClientWebSocket implementation. + Added: Null-check in PhotonClientWebSocket.Send() if the socket may be disconnecting already. Added fitting error log. + Changed: TCP implementation to avoid memory allocations. It now uses a Queue which is pooled and re-used. DispatchIncomingCommands() releases the StreamBuffers back into the pool. + Fixed: TrafficStats counting of ping-result command size. This was still counting the buffer size instead of received-data size (usually less). + +Version 4.1.6.3 (24. June 2021 - rev6099) + Fixed: An issue with deserialization of custom types. + +Version 4.1.6.2 (23. June 2021 - rev6092) + Fixed: Potential NullReferenceException in PhotonClientWebSocket.Disconnect(). If called before the connect-flow finished, the instance was null. + +Version 4.1.6.1 (09. June 2021 - rev6086) + Fixed: TPeer.TotalPacketCount and TotalCommandsInPackets counters. They were increased in two places for the Init message. + Fixed: TPeer.TrafficStatsOutgoing.TotalCommandsInPackets counting in SendData, which was adding the remaining count in queues instead of +1. + Changed: TPeer.SendOutgoingCommands(), which will now stop sending if more than MTU size of data was sent. + Note: Messages are not fragmented for TCP/WSS, so they can be well over MTU size as such. But SendOutgoingCommands() will now return if there are more messages, so sending can be limited somewhat. + Changed: PhotonClientWebSocket will pause the task only for 50ms if nothing was received. + Changed: Log level of PhotonClientWebSocket incoming bytes logging. It's now level "ALL". + +Version 4.1.6.0 (19. May 2021 - rev6070) + Changed: PhotonPeer.ClientVersion is now marked as obsolete. The value should be static and so we added PhotonPeer.Version instead as replacement. + Internal: EnetPeer.Reset() now sets the timeoutInt = 0 and does a sentReliableCommands.Clear() instead of replacing it. + Changed: PhotonPeer.IsSendingOnlyAcks is now obsolete. Use SendAcksOnly() where needed. + Changed: EnetPeer.QueueSentCommand will not set the timeoutInt. Instead, this should be set when the next resends are checked in SendOutgoing. + Changed: Resends of reliable commands are now done directly, without them ever leaving the sentReliableCommands list. This has the benefit of being able to resend those commands in sequence. + Internal: On resend, timeoutInt becomes the lowest value found in the sentReliableCommands. It stays unchanged, if any command wasn't resent. + Added: SerializeCommandToBuffer, which is split out of SerializeToBuffer(Queue commandList). Allows sending single commands. + Changed: QueueSentCommand will not add reliable commands to the sentReliableCommands list, if the parameter signals it was in there already. + Changed: PepareWebSocketUrl() will now write the parameter "libversion" instead of "clientversion". Renamed according to Slack discussion. + Changed: PhotonPeer time keeping to use a Stopwatch per peer, instead of one in the SupportClass. + Changed: PeerBase now has a Stopwatch and PeerBase.timeInt will use that. + Removed: All internal usages of SupportClass.GetTickCount(). + Note: Socket implementations should replace their usage of SupportClass.GetTickCount() - peerBase.timeBase with this.peerBase.timeInt. + Changed: PhotonPeer.LocalMsTimestampDelegate, SupportClass.IntegerMillisecondsDelegate, .IntegerMilliseconds and .GetTickCount() are obsolete now. + Added: EnetPeer.QUICK_RESEND_QUEUELIMIT (value: 25) to suspend quick resending if the sent-queue is larger than that value. + Internal: Minor changes to NCommand. + Added: PhotonPeer SequenceDeltaLimitSends and SequenceDeltaLimitResends to prevent sending more commands, when the server does not keep up with acknowledging them. If those values are 0, this new logic is disabled. + Added: EnetChannel.highestReceivedAck to keep track of the highest sequence number ACKed by the server. + Changed: EnetPeer.SendOutgoingCommands() and .SendAcksOnly() to implement the limit. + +Version 4.1.5.5 (06. May 2021 - rev6050) + Changed: Protocol18 ReadCustomType to no longer throw an exception when a unknown custom type arrives. Instead pass UnknownType. + Added: Type "UnknownType", which is used to pass not-deserialized custom types to the game logic. + Changed: Protocol18 ReadCustomType to use a "boundedSize" to read remaining data if the incoming size value is > remaining message data. + Internal: StreamBuffer.Available to get remaining bytes in buffer. Returns value >= 0. + Fixed: Protocol18 WriteArraySegmentByte to not write any bytes, if count is 0. + Fixed: Potential deserialization issues in protocol 1.6, which could be exposed to crash clients. + Changed: String deserialization in protocol 1.6 avoids some memory allocation. + Changed: String deserialization in protocol 1.6 throws a NotSupportedException if the length is negative (was never supported but now it's a clear error). + Changed: DeserializeDictionary in Protocol16, will no longer allow key- or value-types Dictionary or Array. Both were read incorrectly. + Changed: DeserializeDictionaryType in Protocol16, will no longer allow key- or value-types Dictionary or Array. Both were read incorrectly. + Fixed: Error message in 1.8 GetAllowedDictionaryKeyTypes. + +Version 4.1.5.2 (12. March 2021 - rev6017) + Fixed: A compatibility issue of the Native Encryptor API v2 and Unity UWP exports using the .Net Runtime as runtime. The incompatibility made the reference rewriter fail. Introduced in the Metro build of the library in v4.1.5.0. + Fixed: EncryptorNative will not use the new "native-to-managed callback API" when compiling for NETFX_CORE (UWP Metro). + +Version 4.1.5.1 (01. March 2021 - rev5999) + Note: Release as SDK. + +Version 4.1.5.1 (25. February 2021 - rev5991) + Fixed: Handling of IPv6 addresses (bug was introduced in v4.1.5.0). + +Version 4.1.5.0 (23. February 2021 - rev5986) + Added: Optional parameter CustomInitData to Connect() methods. This data/object is used in InitV3. + Changed: Parameter name for photonToken (was "customdata" or similar). + Internal: PrepareInitV3ConnectData(...) is now WriteInitRequest() without the bunch of parameters which were all coming from the PeerBase anyways. + Fixed: NonAllocDictionary.Remove will also remove the value-reference by setting a default. + Changed: Handling of server IP addresses. IpAddress.TryParse has a few false positives. Those are double-checked and may cause ServerAddressInvalid. + Changed: Failed DNS lookup uses status code: DnsExceptionOnConnect. This clarifies a set of runtime errors, which were hard to identify. + Added: StatusCode ServerAddressInvalid = 1050 and DnsExceptionOnConnect = 1051. Used in IPhotonPeerListener.OnStatusChanged(). + Changed: Native Encryptor API v2 is now required. This affects native Datagram Encryption plugins (and allows GCM / Mode 13). + Changed: PDB files are now in "portable" format. This should be equivalent to "full" (previously used) but compatible with latest Unity versions. + +Version 4.1.4.9 (11. January 2021 - rev5966) + Fixed: EnetPeer to reset values for unsequenced commands on connect. Before, clients would send illegal command numbers after switching connections. + Added: Special handling for StructWrapper in DictionaryToString(). Pretty-prints type and value. + Added: StructWrapper.ToString() and and ParameterDictionary.ToStringFull() to simplify logging. + Fixed: Add overload in ParameterDictionary was sending ints to byte, rather than object. + Changed: NonAllocDictionary now implements IDictionary. + Changed: ValueIterator and KeyIterator now implement IEnumerator, System.Collections.IEnumerator. + Changed: PairIterator now implement IEnumerator>. + +Version 4.1.4.8 (03. December 2020 - rev5915) + Fixed: Issue with DisconnectTimeout, which could be set to int.MaxValue and force a timeout. + Note: The maximum value that is applied as DisconnectTimeout is now 65535 (ushort.MaxValue). Negative values set to default timeout. + Added: PhotonPeer.RemoveAppIdFromWebSocketPath option to skip the appid in websocket connects. Defaults to false. + Internal: PeerBase.PepareWebSocketUrl to skip the appid when RemoveAppIdFromWebSocketPath is true. Changed order of parameters. + Note: The AppId is not mandatory on the PhotonCloud but might be needed to connect to Photon OnPremise when the port does not define the app type. + +Version 4.1.4.7 (26. November 2020 - rev5893) + Fixed: PhotonClientWebSocket handling of messages > MTU. Incoming messages may be incomplete and are now reassembled. Before the fix, common result was that deserialization failed with incorrect / incomplete data. + Added: ExitGames.Client.Photon.Hashtable Add() and Remove() for byte keys. This makes sure ht.Add(0, obj) and ht.Remove(0) match how the access via ht[0] works. + +Version 4.1.4.6 (17. November 2020 - rev5865) + Changed: The EventData.Paramters dictionary is now readonly. It does not need to be settable and can no longer be null. So null-checks are removed. + Changed: If the ByteArrayPool is used for reading events, the sender's actorNumber will not longer be in the EventData parameter-table. Instead, only the Sender property is used. + Note: This makes receiving EventData events non-alloc by not adding the Sender key/value to the Parameters (which would box the int value). + Note: The EventData.Sender property was available for a while and was a better way to access the event-sender's actornumber. + Changed: PhotonPeer.SendOperation() and PhotonPeer.SendMessage() will now check a few conditions (is connected, etc) and call PeerBase.SerializeOperationToMessage(). + Internal: Changed the code path for operations and messages serialization. There are lots of internal changes for this but no externals. + Internal: Message header length is set in EnqueueMessageAsPayload along with other updates (was in TPeer.SerializeOperationToMessage()). + Removed: PhotonPeer.OpCustom(), which was already obsolete for a long time. + Added: PeerBase.IsTransportEncrypted() to help figure out if Payload Encryption should be skipped. + Changed: PeerBase.SerializeMessageToMessage no longer updates the header for TCP. Done in EnqueueMessageAsPayload, used by EnqueuePhotonMessage. + Changed: SerializeMessageToMessage to check existing value isRawMessage instead of re-checking the condition when applying message type. + Fixed: Detection of Generic-Dictionary-typed arrays. Was lacking a test if the element was of type Dictionary. + Changed: Connect(string serverAddress, string applicationName) to call variant with proxy-support directly. + Updated: Connect methods and their docs. + Removed: More of the RHTTP support. + Added: DisconnectMessage deserialization and an event for this case: PhotonPeer.OnDisconnectMessage. This does not break the dll's compatibility. + Changed: Connect() workflow. This simplified the TPeer and EnetPeer somewhat and unifies some actions that were scattered in the classes hierarchy. + Changed: PhotonPeer to create the SocketImplementationConfig in the constructor. Connect() will use this to create the actual IPhotonSocket. + Changed: TPeer.Connect and EnetPeer.Connect are a bit simpler now (don't have to create the actual IPhotonSocket). + Removed: PeerBase.SocketImplementation member. + Changed: Setter and handling for the Version.clientVersion (conditional for SERVERSDK). + Changed: Version.clientVersion is readonly, unless SERVERSDK is defined. + Changed: CreatePeerBase() will only create a new EnetPeer or TPeer on demand (if the protocol changed or there was no PeerBase implementation). + Added: Special treatment for byte-typed keys to the Photon Hashtable to avoid boxing. Pre-boxed items are stored in a static array. + Added: NonAllocDictionary support in SupportClass (DictionaryToString()). + Added: ParameterDictionary as type and support to de/serialize it. + Note: This is a WiP commit to get LoadBalancing non-allow (or less alloc). + Added: PhotonPeer.WrapIncomingStructs to control how to de-serialize responses and events. Setting this requires adjusted code. + Note: As long as WrapIncomingStructs is not set, code changes are not mandatory. + Fixed: Error message in SerializeDictionaryHeader to include the type of the value which failed (it was logging the key's type). + Changed: The PhotonClientWebSocket does a DNS request before connecting, to figure out if IPv6 should be requested by Photon servers. + Added: PhotonPeer.TargetFramework to identify the target framework that the dll was built-for. Might help identify the used variant. + Changed: Code order a little, to get obsolete fields up in the range for them. + Removed: RhttpMinConnections and RhttpMaxConnections, which were entirely obsolete. + Changed: OperationResponse indexer is not obsolete. This was used to detect where it's used. + Changed: ParameterDictionary now has a Get() method, so the API is analog to Unwrap() in this case. + Changed: EnetChannel to use NonAllocDictionary instead of Dictionary. Our replacement is internal and non-allocating for iteration. + Changed: the logging level of events possibly happening on each Service() call changed from INFO to ALL + Changed: EnqueueMessageAsPayload stats-keeping no longer throws an exception, when the delivery mode is "out of range". + Internal: Changed how the client writes acknowledgements. + Internal: TPeer.ReceiveIncomingCommands() will only log "Wrong MagicNumber", if the received length is more than 0. It also logs the length then. + Changed: PhotonClientWebSocket (in .Net Standard 2.0 dll) now checks if the clientWebSocket state is still "Open" before handling incoming data. + Changed: PhotonClientWebSocket logs INFO, when the received data has length 0 but the socket is still Open. + + +Version 4.1.4.5 (04. September 2020 - rev5733) + Fixed: Serialization of a ByteArraySlice will now release it, as described in the docs. So you can pass a ByteArraySlice to RaiseEvent and control of it is passed over to the Photon API. + Fixed: Potential issues with SupportLogger.StartBackgroundCalls() when there were 255 threads running (and or stopped). + Changed: When PhotonClientWebSocket fails to connect, this will now correctly result in a disconnect with code/reason ExceptionOnConnect. + Changed: The socket reading-thread not call it's disconnect method, if already disconnecting/disconnected. This avoids a surplus call (and callback). + Internal: The thread locking object in SocketNative is no longers static. + Note: The next release will change the native datagram encryptor api. Most likely this is the last one compatible with the old native plugin. + +Version 4.1.4.4 (29. June 2020 - rev5625) + Fixed: NonAllocDict. Capacity change was broken. This is a critical fix. + Added: Indexer to NonAllocDict to allow Dict[key] get and set. + Added: NonAllocDict.Clear() method. + +Version 4.1.4.3 (24. June 2020 - rev5622) + Added: PhotonPeer.UseByteArraySlicePoolForEvents and PhotonPeer.ByteArraySlicePool to avoid memory garbage when events consist only of a byte-array. See comments / doc. + Added: Class NonAllocDictionary as replacement for the suboptimal implementation in Mono. Some of our code relies on Dictionaries and can be optimized with this. Note: This is neither documented well yet and considered "beta" until we got feedback on some use cases. + Added: PhotonPeer.TrafficRecorder and ITrafficRecorder. This can be used to capture traffic on UDP connections. + Changed: IPhotonSocket.ConnectAddress is now a local field. Before, it accessed the peer which might already connect to another server. + Changed: Debug log for DNS resolution (INFO level). This includes a count of IPs that were returned. + Added: Static PhotonPeer.NoNativeCallbacks to disable callbacks from native code to C#. SocketNative is the first class to use NoNativeCallbacks in DnsAndConnect(). + Changed: SocketUdp and SocketTcp are now public, like other implementations. This means you can assign them to the SocketImplementationConfig and select these deliberately. + Internal: Datagram Encryption was updated and optimized. (rev5548+rev5550). Added PhotonPeer.NativeEncryptorApiVersion constant to be able to check which native encryptor API is being compiled into the assembly. + Changed: EnetPeer.ReceiveIncomingCommands(): bytesIn counter simplified: now it simply incremented by input data size. + Removed: "CommandLog" feature. It was replaced by Traffic Recording. Remaining setting values for this (in PhotonPeer) are now Obsolete and without function. + Changed: WebSocket init workflow to make it compatible with AuthOnce / AuthOnceWss. It can send an init request now. + Internal: WebSockets need to use the ConnectAddress, as the TPeer will setup the address and path as required. + Changed: The IPhotonSocket does not set the ConnectAddress again, so the Peer can set this address before connecting. This is used for WebSocket connects. + Fixed: NETFX_CORE variant of payload encryption initialization. + Added: PreserveAttribute and used it on IPhotonSocket constructors. This keeps Unity's code stripping from constructors which we need. + Fixed: A rare threading issue for UDP connections. + Internal: SupportClass.StartBackgroundCalls() and related methods are now locked. A proper cleanup of StartBackgroundCalls is pending. + Changed: SupportClass.StopBackgroundCalls(id) will now clear the reference to the used thread but not remove the entry in the list. + Changed: SupportClass.StopAllBackgroundCalls() will now clear all referenced threads. Could be used when disconnected / before another connect to reduce number of Threads. + Removed: SupportClass.CallInBackground, which was replaced by StartBackgroundCalls a while ago. + + +Version 4.1.4.2 (08. May 2020 - rev5519) + Updated: DotNet SDK (net35, UWP and .Net Standard 2.0) and Unity SDK. +Version 4.1.4.1 (30. April 2020 - rev5482) + Fixed: String serialization for characters with more bytes. UTF8.GetBytes was called with byte-count instead of character-count as parameter. +Version 4.1.4.0 (28. April 2020 - rev5474) + Internal: Changed serialization of strings to produce no memory garbage. Strings that exceed a UTF8-encoded byte-length of 32767 (short.MaxValue) are not supported in either protocol (now throwing an exception). + Internal: Improved the serialization of float, double, float-array and double-array to produce less memory garbage with better performance. + Fixed: Cross platform serialization of float and double values in protocol 1.8 (which is used by default by PUN 2). Undiscovered, the C# client was using a wrong byte-order for network. The server and native clients were receiving a wrong value. Between C# clients, everything was fine. + Added: WebSocket support built-in to the Metro / UWP assembly. The PhotonMessageWebSocket is automatically used, unless some override is defined externally. + Changed: The socket implementations now disconnect when an exception happens due to sending. This uses the StatusCode.SendError, reported via OnStatusChanged callback. The LoadBalancingClient will handle this and report a DisconnectCause.Exception. Careful when updating only the library: Handle this in LoadBalancingClient. + Changed: The internal exception logging is now in log level INFO. This means the handled exception is no longer logged as if it's not. + Internal: Reverted to using the datagram encryption API v1. The new one is not fully implemented and should not be used yet (but was in v4.1.3.0). + Fixed: If a timeout message arrives after the client disconnected locally, the timeout is neither reported (via OnStatusChanged) nor does it trigger (another) disconnect. This avoids rare issues where clients would get stuck in Disconnecting state. + Added: Initial changes to support proxies for WSS. The PhotonPeer got a Connect overload which sets the proxy address and the IPhotonSocket got ProxyServerAddress. Higher level changes are upcoming. + Fixed: When the StreamBuffer for a large message got fragmented, this goes back to the StreamBuffer pool. + Changed: ExecuteCommand() for fragments. We now use a StreamBuffer from the pool and only adjust the buffered size if needed. + Added: Static PhotonPeer.MessageBufferPoolSize() to have access to the current StreamBuffer pool size. + +Version 4.1.3.0 (23. March 2020 - rev5399) + Internal: Changed Visual Studio projects which build the C# Photon libraries. Added a .Net Standard 2.0 assembly, which has built-in WebSocket support. + Added: IPhotonSocket.SerializationProtocol property. This provides the protocol of the current PhotonPeer.SerializationProtocolType as string. + Note: Some WebSocket implementations use a static value of the same name and need to be updated! Some projects contain SocketWebTcp.cs, SocketWebTcpThread.cs or similar files. + Changed: It is now possible to signal "on demand encryption" (known as Payload Encryption within Photon terms) even on secure connections (WSS / Datagram Encryption). This is important (only) for mixed connection types. A server update is required. The Photon Cloud is updated. + Added: PhotonPeer.SendInCreationOrder. This defaults to true, enabling the new behaviour explained below. + Changed: The send order of reliable and unreliable commands (UDP). This improves throughput of unreliable commands, when there are multiple datagrams of data. Before, a datagram got filled with the reliable commands first. Only when those were gone, unreliable commands were sent. The server was discarding some messages are too late. + Updated: Pool to be used in higher level APIs. Pool.Count is now also locked. + Internal: EnetPeer ocal variable fragmentLength to currentFragmentSize. It no longer hides EnetPeer.fragmentLength, which was probably causing issues in the Native Toolchain (Master builds for HoloLens 2). + Internal: Datagram Encryption now has a new mode: GCM. It can be used seamlessly with the Photon Cloud. + Internal: Native Datagram Encryption plugins and APIs are now more efficient. + Removed: rHttp support. + +Version 4.1.2.20 (12. December 2019 - rev5296) + Changed: DiffieHellmanCryptoProviderNative is now always compiled into the assembly using dynamic linking. If the payload encryption native library is present, it will be used automatically. + Changed: Extern methods with the DllImport attribute are now public. This allows a PrelinkAll() check if the dll is available and can be loaded. + Internal: EncryptorNative will no longer use native extern methods in static fields. This causes execptions in unexpected context. + Added: PhotonPeer NativePayloadEncryptionLibAvailable, NativeDatagramEncryptionLibAvailable and NativeSocketLibAvailable values. + Changed: SocketNative is autmatically used if the native socket lib is available, based on the checks above. + Changed: The wrappers for the native libraries (sockets and datagram encryption) will now setup a debug logging callback with debug level. This requires new native libraries. + +Version 4.1.2.19 (13. November 2019 - rev5271) + Internal: Handling for DllNotFoundException in the SocketNative.DnsAndConnect(). + Internal: Added support for setting the serialization protocol when using native websocket. + Internal: Added logging to native plugins for sockets and encryptor. + Internal: Changed the access level of PeerBase.debugOut from 'internal' to 'public'. + Changed: UDP socket classes now trigger a disconnect in the send methods, should the lower-level socket be disconnected. This gives us a quicker "disconnected" state in some error cases. So far, only reading-errors triggered disconnects in UDP. + Changed: Logging in case of send errors. + Added: Exception handling to deserialization. This adds a safety layer for receiving messages, which can be skipped. Important: It will be unknown, what message got discarded and if it's important or if it was fake / surplus. + Fixed: Internal class EncryptorNet, which was not thread safe, so DatagramEncryption was failing sooner or later. This affected only v4.1.2.18, which was an internal release. + +Version 4.1.2.18 (1. October 2019 - rev5229) + Removed: Surplus logging "Deserializing OpResponse Ping." for TCP/WebSocket connections. + Changed: Datagram Encryption to use an interface for the encryptor. + Changed: PhotonPeer.Encryptor is now public and can be set before connect. Applications can switch to new Datagram Encryption implementations. InitDatagramEncryption will create an instance and use that or falls back to the managed encryptor. + Note: PhotonPeer.Encryptor replaces DgramEncryptor. Setting the class before connect is likely what the LoadBalancingClient will do anyways. + Changed: The EncryptorNative class is for dynamic linked libs. It can be present in all Unity projects. + Changed: PhotonPeer.NativeDatagramEncrypt is obsolete, as it's always true now. On demand, an alternative Encryptor can be set. + Internal: ClientSdkIdShifted is simplified (because it was causing an error in another compiler / in Unity). + Changed: Reading Hashtables and Dictionaries will now check if the read key is null. If so, the key-value pair will be dropped silently. This applies to Protocol 1.6 and 1.8 alike. + Changed: All built-in socket implementations which are now more alike to one another. + Changed: When DNS resolution provides more than one IP, the socket will use the IPv6 ones first. If there is no route to the remote IP, the next in list is used. It does not resolve strings with IPv4 (e.g. a local address). + +Version 4.1.2.17 (9. August 2019 - rev5187) + Removed: Obsolete StatusCode values: DisconnectByServer is now DisconnectByServerTimeout. Other values are simply removed. + Updated: Used StatusCode.DisconnectByServer to DisconnectByServerTimeout. A follow up to renaming and removing the code. Some implementations of IPhotonSocket can be in existing projects and may still use the old naming. Simply rename. + Changed: PeerBase.timeInt is now a property which calculates the current time: SupportClass.GetTickCount() - this.timeBase. + Fixed: The change for timeInt fixes a problem with TCP connections which go entirely silent and would no longer update the PhotonPeer.ConnectionTime. + +Version 4.1.2.16 (28. June 2019 - rev5168) + Changed: TrafficStats Stopwatch initialization. This keeps the Stopwatch but resets it. Also it's started on initialization, if trafficStatsEnabled. + Changed: VitalStatsToString() can now return a useful stats string, even if TrafficStatsGameLevel is null (which is never the case currently). + Added: VitalStatsToString() now logs the "Longest send" time. This is a new value to debug connections. + Changed: NETFX_CORE now also uses Activator.CreateInstance, if a SocketImplementation is set (as in other implementations). + +Version 4.1.2.16 (24. June 2019 - rev5154) + Fixed: DispatchIncomingCommands() for reliable UDP. In some cases, an unreliable command/message could dispatch before the related reliable command (which was written and sent earlier). This issue was introduced in v4.1.2.11, 15. April 2019. + Fixed: Each thread created via SupportClass.StartBackgroundCalls will now try-catch a ThreadAbortException. Apparently the handling of a ThreadAbortException changed with Unity 2019, which started to log the exceptions on Android, IL2CPP, 4.x runtime. + +Version 4.1.2.15 (07. June 2019 - rev5137) + Changed: Tcp messages with magic byte 0xF4 are no longer accepted (this was used on a deprecated platform). + Changed: An Internal Operation Response for OpPing is now executed right away, fixing timing problems when using Op Ping (in WSS, e.g. on Xbox). + Added: PhotonPeer.InitialResentTimeMax. It guarantees resends for commands, despite bad lag (which may be temporary). + Changed: NCommand.ToString() for slightly better organization and naming of values. This is mostly a debugging help for the Photonians. + +Version 4.1.2.14 (16. May 2019 - rev5128) + Changed: The single "release history" file is now split. There is one "changes" file per api/level. For example: changes-realtime.txt lists the LoadBalancing / Realtime API changes. Find them in the respective subfolders. + Changed: The structure of the Unity SDK. As in PUN 2, there is a "Photon" folder, which wraps up all apis and libraries within a Unity project. + Updated: The readme txt. +Version 4.1.2.14 (6. May 2019 - rev5097) + Changed: EventData now has Sender or CustomData properties as well as SenderKey and CustomDataKey. The keys can be defines according to API. The default are the Realtime API values. Sender and CustomData access is cached. If an EventData instance is reused (PhotonPeer.ReuseEventInstance), the values will reset. + +Version 4.1.2.13 (3. May 2019 - rev5086) + Changed: EventData to not contain the Sender or CustomData properties anymore. They have been specific for the Realtime API and confusing for APIs like Chat. + Added: PhotonPeer.ReuseEventInstance as option to use a single EventData instance for all incoming events. This reduces memory garbage. The drawback is: An event provided via OnEvent(EventData photonEvent) is invalidated right after the callback finished. That event's content will get modified. Typically this is not a problem as events are rarely cached. + Internal: Added caching for a EventData instance (on demand). + +Version 4.1.2.12 (26. April 2019 - rev5046) + Fixed: A problem with the new EventData code of v4.1.2.11. It optimistically assumed that events with codes below 200 were always just containing Sender and CustomData. Photon Chat uses different codes, however. + Added: Access the Sender and CustomData via the EventData.Parameters dictionary, even if that is null (due to the new rules). In that case, the Sender and CustomData is accessed via the properties instead. + +Version 4.1.2.11 (15. April 2019 - rev5043) + Note: This version has a few changes that optimize memory usage mostly. + Internal: Changed EventData class (custom and Photon-defined events). Custom events set the Sender and Code and the CustomData but not Parameters (dict) now. EventData indexer checks if the "Parameters" are non-null. If the indexer is used to access non-existing Parameters, the result is null. Custom events are NOT setting Parameters anymore, saving the Dictionary<> use. + Internal: StreamBuffer.ReadByte() to return a byte or throw an exception when the end of the stream was reached. The -1 value was never used though. + Changed: EnetPeer.DispatchIncomingCommands() as optimization. When dispatching unreliable commands, the code now checks if the next command is available. If so, this is dispatched, without going through the keys of the dictionary (which was creating garbage to clean up). + +Version 4.1.2.10 (11. March 2019 - rev5023) + Fixed: A timeout issue for WS / WSS transport protocols by sending a ping. + +Version 4.1.2.9 (20. February 2019 - rev5003) + Note: Release .Net and Unity SDK from our page. + +Version 4.1.2.9 (13. February 2019 - rev4985) + Removed: Setting of Thread.Name for internally used threads. This was used for debugging only (since a while) but caused issues on a console platform (due to an issue in Unity IL2CPP exports). + +Version 4.1.2.8 (31. January 2019 - rev4975) + Fixed: The cap for the lastRoundtripTime. When the current roundTripTime was very low this meant the rtt wasn't going up again. This was a problem with local servers. + Fixed: Setting the roundTripTime when the connect-command gets acknowledged. This sets a minimum of 15ms now. Before, a local server could get a 0ms roundTripTime (due to using Environment.TickCount). + +Version 4.1.2.7 (25. January 2019 - rev4954) + Removed: CommandBufferSize and LimitOfUnreliableCommands. Technically, those are "Obsolete" but no longer used anywhere. See comments. + Fixed: MessageBufferPoolTrim() was not using a lock(), so this wasn't thread safe. + + +Older Versions + +*** Version 4.1.2.6 (12. December 2018 - rev4935) + Changed: Unsequenced delivery is now available in all builds. Internally, this was wrapped in compile conditions. + +*** Version 4.1.2.5 (10. December 2018 - rev4911) + Added: PhotonPeer.SentReliableCommandsCount - reliable commands sent but not yet acknowledged. This can be used as indicator for the quality of service, aslong with ResentReliableCommands. + Note: This build include the "Best Region Ping" classes, to minimize changes when released with Quantum. + +*** Version 4.1.2.4 (23. October 2018 - rev4868) + Fixed: Fragment bug introduced in 4557 (12. March 2018). This kept one command per reassembled message in the incoming queue (without payload). + Added: An upper limit for the calculated lastRoundtripTime (per ack) to prevent uncontrolled rtt changes due to garbled incoming messages. Protecting the rtt timer improves resending commands if needed. INFO level logging starts with: "LastRoundtripTime is suspicious". + Added: SocketTcpAsync class, which is receiving with BeginReceive and EndReceive in callback. This should work for Unity's 4.x Runtime, which has a bug that blocks Socket.Receive() indefinitely. + Changed: TPeer.ReceiveIncomingCommands() will now use dataLength as indicator how much was received. + Internal: CalculateCrc() now uses a cached lookup table, which gives it much better performance (most obviously for longer datagrams). + +*** Version 4.1.2.3 (17. October 2018 - rev4847) + Fixed: Fragment handling. When a duplicate fragment arrived after the "original" was reassembled, this led to an exception and disconnect. + +*** Version 4.1.2.2 (16. October 2018 - rev4843) + Changed: Enum StatusCode values. DisconnectByServer is obsolete and replaced with DisconnectByServerTimeout. + Added: StatusCode.DisconnectByServerReasonUnknown, as a generic case (unknown reason). + Added: You can now send List, which gets de/serialized as object[]. + Internal: PeerBase.MessageBufferPool is now a Queue, which means the usage changed minimally. This is faster than before. + Internal: When a client detects a timeout disconnect, it will enqueue the status-change, to allow the Disconnect()-call to finish first. This provides a clearer order of actions happening internally for a timeout. + Internal: Simplified the ExecuteCommand for a received Disconnect command. The StatusCallback is now enqueued, as Disconnect() also enqueues changes. + Updated: SocketUdpAsync class is now sending in blocking mode but receiving with BeginReceive and EndReceive in callback. This should work for Unity's 4.x Runtime, which has issues with blocking socket receive. + +*** Version 4.1.2.1 (31. July 2018 - rev4787) + Fixed: OutOfMemory exception while building 'Master' version on UWP with .Net Native Toolchain. This is a workaround for an alleged bug in ilc.exe. + Added: EventData.Sender to simplify access to this info. If there is no sender or if the server sent the event, the actorNumber is 0. +LoadBalancing: + Changed: OnStateChangeAction is now named StateChanged and provides a "previous state" value. State changes only trigger the event-call when the value actually changes. + Renamed: OnEventAction to EventReceived and OnOpResponseAction to OpResponseReceived. + Added: LoadBalancingClient now has AddCallbackTarget and RemoveCallbackTarget to simplify registering for various callbacks. +Chat: + Changed: Namespace from "ExitGames.Client.Photon.Chat" to "Photon.Chat". + Added: ConnectAndSetStatus method. + +*** Version 4.1.2.0 (3. May 2018 - rev4660) + Added: Unsequenced delivery for messages (reliable and unreliable). This way, operations and resulting responses and events can be sent as unsequenced, which is good for use cases like streaming voice, input and such. + Internal: The Photon library now re-uses memory for messages and commands. This avoids garbage collection in Unity, which can be a performance problem. + Changed: There is a new method to send operations: PhotonPeer.SendOperation. It uses the SendOptions struct to define the delivery mode, encryption, etc. This replaces the obsolete OpCustom(). + Added: Optionally, the Serialization Protocol v1.8 can now be used. Consider it a pre-release/test version for which we could use feedback. Set PhotonPeer.SerializationProtocolType. + Internal: Replaced foreach with while in SerializeParameterTable, SerializeHashTable and SerializeDictionaryElements in SerializationProtocol v1.6. + Internal: Changed EnetPeer to have a CommandQueue. Instead of queueing ExecuteCommand(cmd) delegates, we now queue the commands. This is leaner. + Internal: The SendOptions.DeliveryMode enum's order changed. + Added: PeerBase.MessageBufferPool, MessageBufferPoolGet() and MessageBufferPoolPut(StreamBuffer buff) as a simple memory pool implementation. + Added: PhotonPeer.MessageBufferPoolTrim() to give an option to limit the amount of memory buffered. + Added: PhotonPeer.MessageBufferPoolGet() as a (brute force) way to externally debug the memory buffer. This is temporary. Don't rely on this. + Added: For TCP, a client-side timeout. This is based on checking timestampOfLastReceive in the DispatchIncomingCommands method (timeouts only get called when you dispatch). + Fixed: The TCP implementation only checks for a TimeoutDisconnect, while in ConnectionStateValue.Connected. Otherwise, no timeout can happen. + Internal: Renamed various values for more fitting names. + Internal: Added special handling of Arrays of int and byte to DeserializeArray() in Protocol 1.6, using DeserializeByteArray and DeserializeIntArray respectively. +LoadBalancing: + Changed: The namespace to the simpler "Photon.Realtime". + Added: Various callbacks to signal specific situations / events. To get those, a class must implement the interface and be added to a list of "Targets". See: ILoadBalancingCallbacks. + Added: RegionHandler, which provides methods to ping a list of regions and to find the one with best ping. This moves PUN's "Best Region" feature to the LoadBalancing API. + Moved: The PhotonPing was part of the dll but is now part of LoadBalancing. + Added: LoadBalancingClient.UseAlternativeUdpPorts. This way, UDP may use ports of the Steam Multiplayer port-range by simply replacing existing port strings in addresses. + Changed: RaiseEvent now has an overload, that uses RaiseEventOptions and SendOptions. The old variant is obsolete but will still work. + Changed: CheckUserOnJoin is now set by default. The RoomOptions.CheckUserOnJoin got removed. + Added: Client-side checks and limits for OpFindFriends. + Added: Optional parameter sendAuthCookie to OpLeaveRoom. The app can control what's passed from Photon to a server via WebHook. + Changes: The room list for lobbies is no longer part of the LoadBalancingClient. Instead, implement the callback for the changed room list. + Added: AppSettings, a base class to host AppId and some settings for a title. This will help make it available across products. Right now, the LoadBalancingClient is not using it yet. + Changed: Player.ID is now .ActorNumber, which mirrors the server's naming. + Fixed: Unity compile defines to support Unity 2018, + +*** Version 4.1.1.19 (9. April 2018 - rev4590) + Fixed: WSS now sends an operation "ping" in SendAcksOnly() (and SendOutgoingCommands()) to avoid getting timed out. This happens right away, while other commands/operations stay in queue. + +*** Version 4.1.1.18 (19. December 2017 - rev4540) + Fixed: Exception in QueueOutgoingAcknowledgement() with a lock(). This avoids threading issues between Send- and Receive-thread. This was an issue mostly seen in Photon Voice, which uses the library multi-threaded. + Changed: Network Simulation now only stores the actual data of messages, instead of storing an action/delegate. + Note: The delegate IL code for Network Simulation was instantiating a "display class" instance and caused GC garbage, even if no simulation was running. +LoadBalancing: + Changed: FriendInfo.Name is now "UserId", which is up to date with it's usage. + Changed: CheckUserOnJoin is now set by default. The RoomOptions.CheckUserOnJoin got removed. + +*** Version 4.1.1.17 (11. October 2017 - rev4465) + Fixed: Fetching the server timestamp now works for the "No C# Sockets"-library build for WebSockets. This affected libraries used for XB1 and as a result, the serve time was not synced. E.g. PhotonNetwork.time was locked at 0. + Changed: XML doc of PhotonPeer.DisconnectTimeout. This is no longer a UDP exclusive setting and clearly states that the unit is milliseconds. + Updated: Several documentation comments for the PhotonPeer. Using shorter summaries and remarks for the details. +LoadBalancing: + Changed: OperationCode const byte Join = 255 is now marked obsolete. We use "JoinGame" instead. + Added: DisconnectCause.AuthenticationTicketExpired. + Fixed: DebugReturn call in Unity WebGL. +Chat: + Fixed: Unity "6" compile define is now UNITY_2017. + +*** Version 4.1.1.16 (1. September 2017 - rev4349) + Fixed: The native implementations for "PhotonPing", which are used for PUN's "Best Region" selection in the "No Sockets" dlls (Android and iOS for Unity 4.x). Disposing the "dynamic" version was causing an exception (due to an attempted fix of a less-obvious memory leak). This caused the Best Region selection to stall. + +*** Version 4.1.1.15 (17. July 2017 - rev4232) +WebSocket: + Changed: WebSockets are now treated like "addons", as their implementations are complex and potentially not running on some platforms (be it UWP or some Unity export). The Readme-Photon-WebSocket.txt tells you how to add them to your project and set them up. + Added: Folder "PhotonWebSocket" with SocketWebTcpCoroutine and SocketWebTcpThread classes, to support platforms with and without Thread API. + Added: PingHttp.cs for Unity WebGL exports, to support "Best Region" selection in PUN. +LoadBalancing: + Added: LoadBalancingClient.TransportProtocol as shortcut to the use PhotonPeer's TransportProtocol value. This enables setting the protocol easily while not connected. + Added: LoadBalancingClient.SocketImplementationConfig as shortcut to modify PhotonPeer's SocketImplementationConfig. This enables you to setup which IPhotonSocket implementation to use for which network protocol. + Changed: LoadBalancingPeer.ConfigUnitySockets() to try to find our websocket implementations in the assembly, making the SocketWebTcpCoroutine and SocketWebTcpThread classes optional. + Removed: Class "SocketWebTcp" is no longer found by ConfigUnitySockets(). +Chat: + Added: ChatClient.TransportProtocol as shortcut to the use PhotonPeer's TransportProtocol value. This enables setting the protocol easily while not connected. + Added: ChatClient.SocketImplementationConfig as shortcut to modify PhotonPeer's SocketImplementationConfig. This enables you to setup which IPhotonSocket implementation to use for which network protocol. + Changed: ChatPeer.ConfigUnitySockets() to try to find our websocket implementations in the assembly, making the SocketWebTcpCoroutine and SocketWebTcpThread classes optional. + Removed: Class "SocketWebTcp" is no longer found by ConfigUnitySockets(). + +*** Version 4.1.1.14 (5. July 2017 - rev4191) + Changed: SupportClass StartBackgroundCalls method now assigns an ID to each thread/task and enables you to cancel it explicitly via StopBackgroundCalls. CallInBackground is now obsolete due to renaming. +LoadBalancing + Changed: The ClientState "Uninitialized" is now "PeerCreated". This is the initial state. ConnectedToMaster is now ConnectedToMasterserver (both use the same value). + Updated: ClientState values descriptions. + Internal: GameEnteredOnGameServer() first sets the local player's actorNumber, then updates the player lists. +Chat: + Added: ChatClient can optionally run a thread to call SendOutgoingCommands in intervals. This makes sure the connection doesn't fail easily (e.g. when Unity is loading scenes, etc.). You still have to call Service to dispatch received messages. + Added: ChatClient.UseBackgroundWorkerForSending. Set this to true, to use the new background thread. Note: Do not use this in WebGL exports from Unity cause Threads are unavailable in them. +WebSocket: + Changed: Updated enabled protocols for WebSocket.cs. Now it is posible to connect to a server which supports only either TLS 1.1 or TLS 1.2 or TLS 1.0 or any combination of them. + +*** Version 4.1.1.13 (2. June 2017 - rev4112) + Internal: Fixed StreamBuffer.Seek() which was throwing an exception when seeking position 0 in an empty stream. + Removed: The queue-length checks which were using OnStatusChanged to warn about the amount of data queued for sending. Several StatusCode values are now obsolete accordingly. + Removed: StatusCode InternalReceiveException = 1039 and TcpRouter* which were obsolete for a longer time. + +*** Version 4.1.1.12 (24. May 2017 - rev4086) + Fixed: Peer.timeLastSendOutgoing was set in SendAcksOnly and ReceiveIncomingCommands. This should not happen. In PUN, this led to an issue with the fallback thread, which could not keep the connection. + Fixed: DNS resolution in the UDP socket is no longer inside a lock. Even if it takes longer (without network), the socket can now be closed before it's even opened properly, avoiding a freeze. + Fixed: UWP clients can set a SocketImplementationConfig. This opens up the use of WebSockets or any IPhotonSocket implementation in UWP, too. + Internal: Acknowledgements are no longer created as "commands" and serialized on send. Instead, they are directly written to a byte[] buffer, which is using less memory. + Added: PhotonPeer.EnableServerTracing to enable UDP Datagram Tracing on server side. We might ask you to use this when debugging connection issues. Otherwise, don't use this, as you can't access the server's logs anyways. + +*** Version 4.1.1.11 (13. April 2017 - rev3922) + Fixed: Bug of v4.1.1.10, which caused disconnects after a short time. + +*** Version 4.1.1.10 (11. April 2017 - rev3916) + Internal: Memory Allocation for nCommand and EnetPeer within SendoutGoingCommand, and AreReliableCommandInTransit + Internal: Refactoring of SerializeToBuffer to prevent memory allocation and access udpBuffer directly instead of using BlockCopy. + Internal: EnetPeer.channels got removed and replaced by a simple array and a GetChannel() method to access all channels. + +*** Version 4.1.1.9 (15. March 2017 - rev3884) + Fixed: Size calculation for Datagram Encryption (used on one console only, so far), when the MTU was changed from default. In some cases, an Exception was thrown: ArgumentException: Offset and length were out of bounds [...] at System.Buffer.BlockCopy. Adjusted GetFragmentLength() and CalculateBufferLen() internally. + +*** Version 4.1.1.8 (24. February 2017 - rev3873) + Fixed: Handling of non-fatal "WouldBlock" SocketExceptions when receiving data via TCP. This led to disconnects before. This affects only TCP connections in libs other than Windows Store. + Changed: Memory usage for TCP socket usage. Less memory is being allocated per receive. As we have to queue incoming data, it still has to get copied once. + Changed: Memory usage for encrypting messages. + Changed: SupportClass.DictionaryToString() now logs the length of a byte-array (not the content). + Changed: Deserializing unknown "Custom Types" returns the sent byte[]. There is no warning/error log yet but deserialization won't fail entirely. + Changed: PeerBase.SerializeMessageToMessage() to use less memory for encryption. Also, Raw-Messages can now be de/encrypted successfully. + Internal: Added StreamBuffer.ToArrayFromPos(), enabling you to get a buffer copy, starting with some offset (position of the buffer). + Internal: Removed some NETFX special build cases (obsolete due to using our own SteamBuffer). +LoadBalancing: + Added: Player.UserId field and code to read published UserIds from the player properties in CacheProperties(). When publishing the UserId in a room (RoomOptions.PublishUserId = true), the UserId becomes available for all players in a room. Good to find/make friends or follow a team player into another room. + Added: New matchmaking operation: OpGetGameList(typedLobby, sqlFilter). This fetches a list of rooms that match the filter. You can show lists of rooms with specific properties, if needed (or still use OpJoinRandom). + Fixed: WebFlags properties setters. + +*** Version 4.1.1.7 (16. December 2016) + Note: No new lib version. Just updated demos for the SDK. +Demos: + Fixed: Demos with persistent (Turnbased) games. The Memory Demo was not setting up rooms correctly (which led to errors joining them) and used a "join" rather than a "rejoin" to get into saved games (getting added to the room once more). + +*** Version 4.1.1.6 (9. December 2016 - rev3801) + Changed: Default SentCountAllowance = 7. +Loadbalancing: + Added: OpJoinRandom will now "remember" to send ExpectedUsers to the Game Server (by caching the value). + Added: AuthEvent and it's handling. This (internally sent) event can now update the client's token anytime (before that expires). + Added: LoadBalancingClient.OpChangeGroups(). + Changed: LoadBalancingClient.Disconnect() no longer sets it's own State to Disconnected. It waits till the state-change callback gets called by the lib. + +*** Version 4.1.1.5 (18. November 2016 - rev3775) +Internal: + Fixed: Photon-init request is now created by the factory method CreateAndEnqueueCommand to make sure we fragment the init if needed (for small MTU and more init-data). + Fixed: Bug in TrafficStatsReset method, which caused the averaged stats to go up infinitely after a reset. + +*** Version 4.1.1.4 (21. October 2016 - rev3737) + Internal: Added ArraySegment support for byte[]. This way, we can internally reuse more memory. + Changed: Implementations of PeerBase Disconnect() are now using EnqueueStatusCallback(StatusCode.Disconnect) to delay the "callback". That enables any thread to call Disconnect() while the status change correctly gets called in the main thread via DispatchIncomingCommands(). + Changed: When a SocketImplementationConfig for UDP is set, this will be used via Activator.CreateInstance(socketImplementation, this). + +*** Version 4.1.1.3 (20. September 2016 - rev3673) + Changed: NETFX_CORE implementation for UDP. This no longer attempts to detach the stream after every single Send, which caused issues when connecting and getting a lot of messages. + +*** Version 4.1.1.2 (13. September 2016 - rev3652) + Changed: There are less variants of the Photon library now, which makes it easier to integrate, run and test. There is a general "DotNet" version and a Windows Store (universal) dll. These two also come as Unity build and in Debug and Release. UWP can use the Universal/WSA library. + Added: PhotonPeer.SocketImplementationConfig. This allows easier configuration of the IPhotonSocket type to use per protocol, so that switching protocols is easier (compared to having to set a SocketImplementation before connecting). + Changed: PhotonPeer.SocketImplementation can't be set public. Use the SocketImplementationConfig instead. + Internal: This release summarizes a lot of internal refactorings. It's easy now to switch protocols (internally), to set socket implementations (platform dependent) if needed, etc. +LoadBalancing: + Removed: LoadBalancingClient.PlayerName and Player.Name. Were obsolete for more than a year. There is a NickName and the UserId can be set in the AuthValues. + Removed: OpJoinRoom() overload with actorNumber. This was obsolete. To enable clients to return to a room, set AuthValues and a userID. + Changed: LoadBalancingClient no longer overrides the protocol for Unity WebGL. This is done in the LoadBalancingPeer.ConfigUnitySockets(). + Changed: GetNameServerAddress() is the same in Chat and LoadBalancing APIs now. + Added: DisconnectCause.DisconnectByServerLogic and handling for this case. You can check this DisconnectedCause when the LoadBalancingClient.State is ClientState.Disconnected. + Added: Hashtable definition to use Photon's own implementation for Windows Store builds (NETFX_CORE). This must be used but it means you to use the same Hashtable definition in all builds (no matter if 8.1 or 10). + Added: Support for WebGL export in Unity. + Changed: OnStateChangeAction, OnEventAction and OnOpResponseAction are now events. To register a method in one of those, use += and to deregister you need to use -=. This prevents assigning a new method and de-registering any previously registered ones. +Chat: + Changed: ChatPeer now has ConfigUnitySockets(), which defines the SocketImplementationConfig. It's only used in Unity (using UNITY define). + Changed: ChatClient is not setting socket implementations anymore. + Added: Hashtable definition to use Photon's own implementation for Windows Store builds (NETFX_CORE). This must be used but it means you to use the same Hashtable definition in all builds (no matter if 8.1 or 10). + Added: Support for WebGL export in Unity. + +*** Version 4.1.1.1 (22. August 2016 - rev3549) + Fixed: IPv6 support. The init-message was missing it in 4.1.1.0. + Fixed: UWP build for Unity now has (Photon-)Hashtable class once more. Unlike Windows RT, UWP does support Hashtable (again). But in Unity, we always use ours. + +*** Version 4.1.1.0 (15. August 2016 - rev3536) + Changed: Unity exports now need to set the API Compatibility to ".Net 2.0". The ".Net 2.0 Subset" won't work anymore, due to need of some features for a new encryption mode. + Fixed: Windows Store implementation of TCP Socket. This is now compatible with 8.1 and 10 and the locally configured timeout is also applied while connecting initially. + Fixed: OpWebRPC documentation. +LoadBalancing: + Fixed: Room.ClearExpectedUsers() is now sending it's current, local "expected users" to update the server with "CAS" (Check and Swap). This gives the client an update when the values become valid (which updates the local cache after the roundtrip). + Added: Support for the 'Server Side Master Client' feature. The Room will read master client updates from the server accordingly. Room.SetMasterClient() enables you to override the server's selection (provided it did not change before your operation gets executed). + Changed: Option for bool WebForward into the new "WebFlags". This allows fine control of which data is being sent to WebHooks. This affects all SetProperties, OpWebRPC and the RaiseEventOptions. + Added: WebRPC.cs to the LoadBalancing API folder (was available separately before). It contains WebFlags and WebRpcResponse. +Internal: + Changed: Instead of Windows Phone 8.0 support, we now have a Windows 8.1 Universal library ("Metro") and one for Windows 10 Universal ("UWP"). + Changed: Changed initialization of PhotonPeer and related classes. + Changed: Workflow to send Init command. + Added: Option for "Datagram Encryption" and a new Authentication Workflow ("AuthOnce" and "AuthOnceWss"). This is part of the LoadBalancing API. + Added: ClientSdkId, which is used internally for reference. + +*** Version 4.1.0.6 (30. June 2016 - rev3400) + Changed: ExchangeKeysForEncryption() and internally called DeriveSharedKey() can now be executed in their own Thread. + Added: static PhotonPeer.AsyncKeyExchange to define if encryption calculations are done in parallel. +Internal: + Changed: NetworkSimulationSet.IsSimulationEnabled only does any work, if the value gets changed (saving some surplus work). + +*** Version 4.1.0.6 (21. June 2016 - rev3376) +Internal: + Removed: The wrapper for the optional "native encryption library" from most assemblies. It didn't get certified for the Windows Store and caused problems in the Unity WebPlayer. This will be provided on demand instead. + Removed: Our upcoming protocol implementation until it's fully compatible with all supported platforms. Despite not being used, it also caused some issues on some Unity exports. + Changed: Usage of MemoryStream is being replaced with a StreamBuffer. This is our own implementation and always grants access to the underlying byte[] (which is not possible in Windows Phone / Store API in some cases). For your Custom Type serialization, replace MemoryStream with StreamBuffer. That's all. + Internal: Commands are now split into header and payload byte-arrays, instead of copying them into yet another buffer before sending them. + Added: Support for IPv6 adresses in Photon Ping classes. This supports "Best Region" usage in PUN. + Fixed: After DNS resolution, IPv6 adresses are preferred over IPv4 ones. +LoadBalancing: + Fixed: LoadBalancingPeer.OpRaiseEvent(...) to send operations (and events) unencrypted again. + +*** Version 4.1.0.4 (19. May 2016 - rev3322) +Internal: + Updated: For Unity, the usage of the optional "native sockets" library is now compatible with IPv6 addresses, as required by Apple. + +*** Version 4.1.0.3 (28. April 2016) +Internal: + Added: An optional native library for encryption. This speeds up the (rarely used) key-exchange and encryption of messages. The usual case is to use the C# variant, as before. + +*** Version 4.1.0.2 (21. April 2016 - rev3283) +Internal: + Changed: PeerBase: ipv6 flag set in INIT_BYTES[5] after dns lookup, when ip address type already known + Changed: PeerBase: INIT_BYTES[4] contains clientlibid and release flag (15) + Changed: PeerBase: client version packed in INIT_BYTES[5,7,6] bytes + Changed: pProtocol prefix and path parsed (and trimmed) in IPhotonSocket.TryParseAddress to support websocket urls + Changed: Client version moved to separate version.cs + Changed: Protocol static methods reworked to instance methods, IProtocol interface extracted + Changed: PeerBase.DeserializeMessageAndCallback() to use a variant of new MemoryStream that exists in Windows 10 Universal APIs, too. +LoadBalancing: + Added: Expected Users. This affects the Room, LoadBalancingClient, JoinRoom, JoinOrCreateRoom and CreateRoom. + Added: null check in Extensions.StripToStringKeys(). + Fixed: FriendInfo.IsInRoom, which returned the opposite of it's naming! Also changed FriendInfo ToString() according to PUN's. + Added: RoomInfo expectedUsersField, which is updated with room properties (well known ones). + Added: Room.ExpectedUsers and ClearExpectedUsers() to expose the list of expected players. + Added: RoomInfo.serverSideMasterClient and masterClientIdField (also updated with well known properties). + Changed: OpRaiseEvent now re-uses a Dictionary in the LoadBalancingPeer. It uses Clear(), rather than creating a new Dict each time. + Changed: AuthenticationValues to also use C# properties and and backup-fields. This is guaranteed to work in Unity. + Updated: EventCode ErrorInfo reference with a link to "WebHooks" doc online. + Changed: Disconnect handling in the LoadBalancingClient. The client should reset correctly and log info, if it's in a State where a disconnect is a proper error. Note: In some cases like "switching server", a disconnect is expected, so it's not an error then. + Fixed: PlayerProperties sent to game server will now include well-known properties again. This fixes the "NickName missing" bug. + Fixed: LoadBalancingClient.State value when the client fails to join or create a game on the Master Server. The state is correctly re-set to ClientState.JoinedLobby or ClientState.ConnectedToMaster. + Internal: Added private inLobby value, to store if the client was/is in a lobby on the Master Server. + Fixed: DemoClient (in demo-loadbalancing) now makes use of the Name Server by using: ConnectToRegionMaster("eu"). + Added: DemoClient now has debug output when the connection times out or can't be established. + + +*** Version 4.0.5.1 (18. January 2016 - rev3187) + Fixed: EnetPeer.ExecuteCommand(). Fixed: Receiving a Disconnect-command didn't clear the receive-buffers. However, it has to be the last command executed. + Note: The bug related to the Disconnect-command happened, when an app paused, queued incoming commands and executed the Disconnect while incoming commands were queued. + Fixed: Setting of DisconnectTimeout for TCP connections (ReceiveTimeout and SendTimeout). + Changed: Our protocol serializes Enums implicitly as their underlying Type. This means you can easily send them but will lose the Type info (they don't arrive as your Enum type). This is now also working in Windows Store libraries (NETFX_CORE). +LoadBalancing: + Added: OpSetCustomPropertiesOfActor() and OpSetCustomPropertiesOfRoom() now check locally, if the client is currently in a room. It must be, to be able to set these properties. An exception exists for setting properties for the local player's actorNumber, but those are better set via LoadBalancingClient.LocalPlayer. +Unity SDK: + Changed: The Unity condition which defines "using Hashtable = ExitGames.Client.Photon.Hashtable;". All versions of Unity 4 and up now define that Photon's Hashtable is needed. This is only in the LoadBalancing API, not in the demos. + Added: WebGL support + +*** Version 4.0.5.0 (3. December 2015 - rev3144) + Changed: Signature of SetCustomProperties methods. All overloads now include a final, optional "webForward" parameter. This enables you to update a WebHook when properties change. This is intended for turnbased games, not for high-frequency updates - use with care. + Internal: Added more debug output to error messages from the socket usage. This should now always include the ServerAddress to make things easier to debug server-side, if needed. + Added: Serveral new ErrorCode values, which will be used by v4RC5 and newer servers. See ErrorCode.JoinFailed***, HttpLimitReached and ExternalHttpCallFailed. + Fixed: LoadBalancing API now reads the correct "NickName" key from the server's authentication response. So far, it was reading a key that is never used. Note: This means you can set a user's NickName server-side to override the client's nickname. +Chat + Added: A MessageLimit field for ChatClient and ChatChannel to limit the number of messages the client keeps locally. It might be useful to limit memory usage in long running chats. Set ChatClient.MessageLimit to apply the limit to any channel subscribed afterwards or apply a limit individually. + +*** Version 4.0.0.12 (3. November 2015 - rev3112) + Added: Support for IPv6. Note: IPv6 does not work in Unity yet. It has issues with IPv6. (Case 740910) + Note: Host name resolution will prefer IPv4 over IPv6, if both IPs should be available. IPv6 Addresses must use brackets! Example: [::1]:5055. This separates the port from the address. + Added: Error logging when Addresses can not be resolved to IPs. + Changed: LoadBalancingClient OpJoinOrCreateRoom() no longer allows you to re-join a room. Simply remove the ActorNumber from the parameters. To re-join, use OpJoin with actorNumber (Player.ID that was assigned in the room). + Added: Support for PS4 in Unity LoadBalancing SDK. Note: The demos are not all updated with controller support, as we use the old UI, still. To test export, use the Particle Demo. + +*** Version 4.0.0.11 (28. October 2015 - rev3093) + Changed: Sending a generic Dictionary (with specific types) will now throw an Exception, if any key or value is null. This limitation does not include Dictionaries which use object as type. Those Exceptions are one of the few, which are not catched and turned into a debug message. Catch them by wrapping Operation calls, where needed (OpRaiseEvent()). + Changed: TrafficStatsGameLevel public properties are now settable. This enables you to reset individual values to (e.g.) show "LongestDeltaBetweenSending of the past second". + Added: CommandLog debugging option. This can be used to get a list of sent reliable commands and their ACKs (from the server). Default is 0 size ("off"). + Added: CommandLogSize and CommandLogToString() to PhotonPeer. This is part of a LoadBalancingClient.loadBalancingPeer. + Added: Several PhotonPeer values to analyze connections: ConnectionTime, LastSendAckTime and LastSendOutgoingTime. PacketLossByChallenge is probably a tempoary addition to check if we have to drop corrupted packages due to bad "challenge" value. + Added: Log for incoming reliable commands. The most recent 200 entries will be logged with the CommandLogToString(). This is probably temporary. + Changed: Timing for resending reliable commands in RUDP. The peer will check the sent-queue more frequently now, no matter at what time some random command would have to be repeated. Repeats should be more timely, based on their dynamic resend-timing. + Changed: PhotonPeer.MaximumTransferUnit minimum is now 576 (was 520, which was lower than on the server). + Internal: Channels in the EnetPeer are now stored in an array, so we can replace some foreach-loops with for-loops. +LoadBalancing (Realtime and Turnbased API) + Added: LeaveLobby handling in OnOperationResponse(), which sets the client's state correctly. + Changed: Order of execution for Ev Join. If user is known (inactive user rejoins), the player's props are read. The actor list is used, if available. + Changed: RoomOptions to use properties with backup-fields to avoid issues in Unity which has issues with Object Initializer (curly brackets). + Changed: JoinMode 2 is now "JoinOrRejoin". Was: "Rejoin". + Added: ErrorCode constant AuthenticationTicketExpired. + Internal: OpJoinRoom, OpCreateRoom and OpJoinRandomRoom no longer use a (growing) list of properties. Instead, classes were created to "sum up" their parameters. The api for games didn't change. + Internal: Related to the refactoring of Join/Create, the LoadBalancingClient now creates a Room instance when the client arrived on the GameServer (before, it got created in the initial "create" call). +Chat + Added: More sanity checks on operations (empty userId or secret, max friends). + Added: Special debug logging when the server returns an error for "Operation Unknown". In this case, it's highly likely that you don't use a Chat AppId. + Added: More helpful error logging. + +*** Version 4.0.0.10 (14. July 2015 - rev2988) + Removed: LitePeer class and complete "Lite" namespace. It's highly recommended to use the LoadBalancing API (LoadBalancingClient, etc). The (few) operations that were defined in Lite are no longer required really. + Refactored: Some "base" enumerations that were provided by the Lite peer. They are now in LoadBalancingPeer. + Added: support for RoomOptions.Plugins. Which we need now since we support multiple plugins per plugin dll - for testing purposes for instance. + Fixed: The wrapper classes for the native sockets now do a Sleep(15) when there's nothing to receive. This reduces CPU load considerably. + Fixed: Unity library SocketWebTcp class for websocket support. It requires a coroutine on a new GameObject which is now marked as DontDestroyOnLoad(go) and survives scene loading. + Fixed: The Windows 8 SDKs now include the release assemblies. This makes sure you can submit your app to the Windows Store. + Added: ConnectionProtocol WebSocket and WebSocketSecure. It's simply a different protocol, compared to UDP and TCP, so it should be separated. + Internal: DoFraming is now a part of TPeer (was in IPhotonSocket). It's set by the ConnectionProtocol which avoids misconfiguration. + Changed: SendPing can now send a ping binary message or enqueue the Ping Operation (when DoFraming is false). + Added: A null-check for TrafficStatsStopwatch to avoid NullReferenceExceptions. + Added: Compile condition for Ping result handling. It's only used when the client uses Sockets as well (for the time being). + Added: Unity WebGL export also sets a "using" Hashtable definition. + Fixed: An exception in Photon.Hashtable.ToString() if a value was null. The assumption was that there are no null-values. + Changed: SocketUdp and SocketTcp now implement IDisposable, which seems to help with infrequent freezes in the Unity Editor. + Added: PhotonPeer.QuickResendAttempts. Sets how many resend attempts for a reliable command will be done in quick succession (after RTT+4*Variance), before the time between repeats will be increased. Use with care and with low values. + Added: IP/Hostname to logged exceptions when Connect fails. This is easier to support (e.g. DNS lookup fails). + Fixed: Library for PUN+ export to WebGL. Originally, we forced UDP as protocol for PUN+ export, as the native sockets library doesn't support TCP. However, WebGL export introduced a new use case. + Added: LoadBalancingClient.EnableLobbyStatistics and .LobbyStatistics. They provide an overview which lobbies your game uses and how busy they are. + Fixed: The LB Demo should set CustomProperties instead of directly setting (any) properties. + Fixed: SocketWebTcp is completely empty, unless WEBSOCKET is defined. Before the file still contained the "using" part of the class. +LoadBalancing (Realtime and Turnbased API) + Updated: Description for IsConnectedAndReady. + Changed: NameServerAddress to return a fitting address depending on protocol (including WebSocket but not yet RHTTP). + Updated: The only name server host is now "ns.exitgames.com", which gets turned into a proper address by protocol. + Changed: LoadBalancingClient.CustomAuthenticationValues is now .AuthValues. You can use those values to identify a user, even if you don't setup an external, custom authentication service. + Changed: LoadBalancingClient.UserId no longer directly stores the id but puts it into AuthValues. This means, the UserId could also be set via setting AuthValues. + Changed: The API of AuthenticationValues. There is now the UserId and AddAuthParameter() replaces the less general SetAuthParameters() (which only set specific key/values). + Changed: PlayerName gets renamed to NickName, so PhotonPlayer.Name becomes .NickName and LoadBalancingClient.Name becomes .NickName, too. The old naming is marked as obsolete. + Changed: Particle Demo now connects to the Cloud by default (because it's easier to setup and try). You can define your own Master Server (Photon OnPremise) of course. + Added: GamePropertyKey.MasterClientId (248) and ParameterCode.MasterClientId (203) + Added: ParameterCode.ExpectedValues (231) + Added: ParameterCode.SuppressRoomEvents (237) +Chat API: + Added: A Unity 4.6 demo with uGUI. It's missing a few features but should give you a good start to making your own. + Added: Unity/WebGL support (merged from PUN). + Added: Breaking! IChatClientListener.DebugReturn(). Photon lib and chat client log via this method (no logging to console by default). + Changed: ChatClient.CustomAuthenticationValues is now .AuthValues. You can use those values to identify a user, even if you don't setup an external, custom authentication service. + Changed: ChatClient.UserId no longer directly stores the id but puts it into AuthValues. This means, the UserId could also be set via setting AuthValues. + Changed: The API of AuthenticationValues. There is now the UserId and AddAuthParameter() replaces the less general SetAuthParameters() (which only set specific key/values). + Note: All users should have a UserId. You can set chatClient.UserId before you connect, or you can set the AuthenticationValues in Connect(..., authValues) to set a UserId. + Added: ChatChannel.ToStringMessages(), which gets all messages in a single string, line by line. The format is "Sender:Message". + Added: ChatClient.TryGetChannel() to find a channel only by name, no matter if public or private. +Photon Unity SDK + Changed: Organization of APIs and Assemblies in SDK. Now you can copy the content of folder "PhotonAssets" into your project's Assets folder and you have all APIs. + Added: PhotonAssets-U5 folder which includes only the Windows Universal DLL. + +*** Version 4.0.0.8 (14. January 2015 - rev2765) + Fixed: Serialization of custom types with nested Serialize-calls. In this case, re-using a specific memory stream breaks it. + +*** Version 4.0.0.7 (12. January 2015 - rev2763) + Fixed: Serialization of arrays of custom-types. +Chat API + Internal: Changed code for UserID from 7 to 225. The latter is used in LoadBalancing, too, so we want to re-use the code here. + +*** Version 4.0.0.6 (05. December 2014 - rev2758) + Added: ChatApi and LoadBalancingApi folders to Unity SDK. They are needed in any Photon project with Unity. When updating, copy and paste the files over existing ones and make sure to replace the assembly-files, too. + Changed: Protocol to save more memory or re-use it. The idea is to have less Garbage Collection (primarily for Unity/PUN and custom types). + Added: New CustomType de/serialization methods which provide the MemoryStream, instead of a byte[] COPY from the stream. + Changed: Now using one method to identify a Type. This was duplicated code before. + Changed: De/Serialization of some types. + Note: The drawback is now, that there are more places with: lock(). This is far from optimal but the alternative would be to make Protocol instances per thread. As most is static at the moment, this would not be an easy task. + Added: position check for DeserializeStreamFunction() call. Stream position must be "previous + customtype length". It gets corrected but at the moment no error is thrown. + Changed: DispatchIncomingCommands() no longer instantiates the commandsToRemove each call. This is reused and thus saves memory. + Changed: net_fx build will now check IsConstructedGenericType to detect if something is a dictionary +LoadBalancing + Added: LoadBalancingClient.OpJoinOrCreateRoom overload which has lobby as parameter. If a room gets created, this defines in which lobby it belongs. + Changed: LoadBalancingPeer: Added new error code PluginMismatch, documentation for Plugins parameter code. + +*** Version 4.0.0.5 (23. September 2014 - rev2738) + Updated: AddFriends and RemoveFriends doc. + Changed: Logging level for two cases. Dropping a package due to failed CRC-check is now logged for INFO. It's expected and certainly not an error. Dropping a package when the incoming challenge does not match is also not an ERROR. It is expected when you switch servers and packages arrive late. This is now debug level ALL. + +*** Version 4.0.0.4 (19. September 2014 - rev2736) + Fixed: Fragmentation when CRC checks are enabled. This kept clients from sending fragmented commands when the additional 4 bytes CRC were included later on. + Fixed: An issue in the ChatClient which was referring to a class from Photon Unity networking. This caused compile issues in the Unity Chat Demo. + Updated: Reference doc generation. + +*** Version 4.0.0.3 (15. September 2014 - rev2731) + Updated: Doc generation settings and style. + Note: This version has no code changes to rev2728 described below. That version is already released in the Unity Asset Store in PUN. + +*** Version 4.0.0.3 (11. September 2014 - rev2728) + Fixed: A simple "order of things" issue when detecting a timeout (due to resends). We first have to set "Zombie" state so that any Disconnect() call created a disconnect-command with reserved byte = 2 = "due to timeout". + Fixed: Chat to be compatible with native sockets of PUN+ (iOS and Android exports from Unity). + Fixed: Access to native sockets (in classes SocketUdpNativeDynamic and SocketUdpNativeStatic) is now using a lock(). The native methods are not thread safe but we need more than one socket for PUN+ and Chat (with native sockets, too). + Changed: Logging for the case "Ignoring received package due to wrong challenge". This got logged on log-level ERROR but maybe is better as WARNING only. Now this should log less often. + Internal: Updated to a newer native-sockets interface. + Internal: Updated to a newer native-sockets interface (affects PUN+ only). Cleaned up precompile defines and #if usage. + +*** Version 4.0.0.2 (01. August 2014 - rev2715) + Added: PhotonPing class and subclasses per platform. Allows clients to use regular UDP messages to ping our servers and find the best region. + Added: Native and Win8 support for PhotonPing. + Known Issue: Native ping has to be done "one by one" and without any other connection in Unity. It's not yet thread safe (but that is ok as we don't want to ping most of the time but only rarely and out of game). + Added: PhotonPing class/file to Win8 platforms. + Changed: The extern static methods for the native libs are now internal (instead of private). Pings are using them, too. + Changed: WebRpcResponse.ReturnCode comment to include fail code. + Changed: OpWebRpc doc is now much more complete and helpful. + Updated: Unity SDK Particle Demo (more) and LoadBalancing Demo (just a bit). + +*** Version 4.0.0.1 (17. June 2014 - rev2663) + Fixed: DotNet assembly no longer contains classes that try to include our Unity native socket libs. This was causing issues in some cases. + Added: PhotonPeer.CommandInfoCurrentDispatch. This property gives you the debug string of the currently dispatched command (events or responses). Only useful for UDP. +LoadBalancing: + Added: LoadBalancingClient.OpRaiseEvent(). Now that LoadBalancingClient USES a loadBalancingPeer (and doesn't extend it), things are much easier by offering this method, too! + Added: LoadBalancingClient.IsConnected and .IsConnectedAndReady to LB API. Going to be part of the API from now on. + Removed: Unused fields clientId and clientCount. + Changed: Field for internal use "lastJoinActorNumber" is now private as intended. + Changed: LoadBalancingClient.Disconnect is now setting it's own state to Disconnected if the connection got closed (as expected). +Chat: + Changed: How the server responds to Subscribe and Unsubscribe. Events will now contain success/failure of those. This allows us to send the answer after calling a WebHook if needed and we can even send it to multiple clients (which authenticated with the same userID). + Changed: Handling of subscription responsed. This is done to allow web services to subscribe a client remotely and to be able to prevent joining some channel that a user should not join (the channel of some guild or another team, e.g.). + Changed: Debug loggging. In Unity we can't use Debug.Assert, etc. So we have to log more cleanly. This works in Editor and several platforms (but not all). + Changed: Folder for Chat API. It now begins with "Photon" which provides some context no matter where you copy the files. Easier to find in Unity projects. + Changed: Operation FriendList and method SendFriendList renamed to AddFriends + Added: Operation RemoveFriends and corresponding method in ChatClient.cs + Added: Console Demo has new command 'fr' to remove friends + +*** Version 4.0.0.0 (23. May 2014 - rev2614) + Changed: This version contains a few features that are not compatible with the Photon Server SDK v3.x. Notable features that are not in the Server SDK are: NameServer, WebHooks and Turnbased API features. + Changed: This SDK is the first that contains all current APIs for Realtime, Turnbased and Chat. + Fixed: Release build of the Unity assembly now also excludes native-socket using code, fixing a Unity Free export issue. We only use the debug assembly in our demos though and suggest you do the same. +LoadBalancing: + Changed: LoadBalancingClient.FriendList creation/update is delayed until the server's response is available. This avoids cases where the friends are offline for the moment between requesting the update and getting it. Initially, it is null as before. + Added: some methods to Player to find next player, etc. Useful for turnbased games to find an opponent. + Added: LoadBalancingClient.UserId, which is the ID of a user(account). This is used in FindFriends and when you fetch account-related data (like save-games for Turnbased games). Set it before Connect*(). As fallback when empty during connect, the PlayerName is used instead. + Removed: LoadBalancingPeer.OpSetCustomPropertiesOfActor and OpSetPropertyOfRoom which were too special to be so low level. Could be implemented to LBClient. +Turnbased: + Fixed: OpJoinRandomRoom and OpCreateRoom which didn't reset the ActorNr to claim when entering the room. Depending on previous actions, some calls of those methods did fail when the actorNumber wasn't available. + Changed: OperationCode.Rpc is now called OperationCode.WebRpc. It's simply much cleaner (considering PUN has RPCs as well but in a different context). + Changed: WebRpcResponse reading to be able to handle additional data. + Added: Parameter webForward to: OpSetCustomPropertiesOfRoom and OpSetPropertiesOfRoom. The "old" overloads of these methods are still there, too. If webForward is true, the properties are sent to the WebHooks. +Chat: + Added: SendPrivateMessage() overload that has option to encrypt private messages. Public messages don't need encryption. + Removed: lastId and LastMessageIndex from channels. Those were impractical and should be removed from the API. + Changed: UserStatus class to ChatUserStatus. + Changed: Most classes are defined in their own file now. + Removed: Folders "Shared" and their subfolders. This gives a much better overview. + Added: Handling for event SubscribeResponse. This is not actually a response but gives you info about channels that got subscribed (e.g. when you re-login quickly or when a user is logged in in multiple clients). + Added: HandleSubscriptionResults() method to handle the event and proper responses. + +*** Version 3.2.2.6 (13. May - rev2575) + Fixed: Windows Store and Windows Phone libraries now only send the bytes they should send. This means we have to copy the payload from the "complete package buffer" in order to send it. + Fixed: SocketTcp now handles all exceptions during reading. Still, abort-by-server is treated as ServerDisconnect. Everything else as client side disconnect. This fix is especially for iOS exports from Unity. The iOS Power-button will immediately cut any connection. The Home-button allows us to keep the connection if we return the app to focus within a few seconds. + Fixed: TPeer.StopConnection() now clears the incoming queue when it disconnects. This avoids getting any more (already received) commands. + Changed: TPeer.Disconnect() now uses StopConnection instead of implementing the same code again. + +*** Version 3.2.2.5 (30. April - rev2566) +LoadBalancing: + Added: TypedLobby class to replace lobby name/type pair. + Added: LoadbalancingClient.CurrentLobby property. CurrentLobbyName and CurrentLobbyType are obsolete. + Added: New overloads in LoadbalancingClient with TypedLobby parameter instead of separate lobby name and type: OpJoinLobby, OpJoinRandomRoom. Old methods marked obsolete. + Added: New overloads in LoadbalancingClient for OpJoinOrCreateRoom, OpCreateRoom, CreateRoom with parameters packed in RoomOptions class. Old methods marked obsolete. + Breaking: LoadbalancingClient.CreateRoom parameters changed to (string roomName, RoomOptions opt). + Internal: Removed obsolete LoadBalancingPeer overloads of OpCreateRoom and OpJoinRoom + Internal: Added 'onGameServer' parameter to LoadBalancingPeer OpCreateRoom, OpJoinRoom; used to avoid sending extra data to master (player- and room-props) + Internal: Loadbalancing Room constructor(string roomName, RoomOptions opt). + Internal: Added use of the "JoinMode" parameter which is used in context of Turnbased games. + Fixed: Bug in OpLeaveLobby which joined the default lobby instead of leaving any lobby. +General: + Fixed: Server ports were read as short, which was wrong. We now use the correct unsigned short to convert from the address string). + Fixed: A minor issue in the SupportClass ToString conversion which used a Hashtable's key type instead of the value's type in one place. + +*** Version 3.2.2.4 (21. March 2014 - rev2519) + Internal: For Unity, the classes that handle native sockets can now be compiled in a variant that does not actually reference the native-socket-libs. The dll for PUN+ uses native sockets and need the native libs. Any regular dll will have the (not used and empty) classes for build-compatibility reasons. + Added: Values to enum EventCaching: SliceIncreaseIndex, SliceSetIndex, SlicePurgeIndex and SlicePurgeUpToIndex. They are in Lite but used in LoadBalancing. This is likely to be cleaned up in the next major release. + Changed: EventCaching MergeCache, ReplaceCache and RemoveCache as they belong to an outdated form of caching. The "RoomCache" is the better option in any case. +LoadBalancing: + Added: RaiseEventOptions class. It's used for OpRaiseEvent to avoid further parameter- and overload-clutter for this operation. While it's still not optimal for all cases, the fields in the RaiseEventOptions class are hopefully more clear how to use. Maybe some constructors will be added soon. + Changed: All OpRaiseEvent variants, except the one with RaiseEventOptions is now obsolete. + Added: Event Cache Slicing. Cached events can now be organized into "slices" which allows you to handle them in chunks. You can purge events in specific slices (e.g. get rid of the previous game-rounds events). +Turnbased: + Added: RaiseEventOptions.ForwardToWebhook which allows you to forward an event to a webhook (to be defined in Dashboard). Use this rarely, as it has an impact on (server) performance! + +*** Version 3.2.2.3 (18. February 2013 - rev2493) + Added: const PhotonPeer.NoSocket, so programs using our assemblies can detect if they must provide an external SocketImplementation. Some builds avoid using the Socket class (cause Unity Free doesn't support it on all platforms). + Added: PhotonPeer.SendMessage method in order to send any serializable object to server. + Added: IPhotonPeerListener.OnMessage in order to be notified about getting message from server. + Added: new 'Connect' method, which accepts as third parameter any serializable object. You may use this object on server before creating peer. + Added: OnMessage callback to samples + Changed: TCP and UDP both set the socket to null explicitly in Disconnect(). Hopefully this fixes a misbehaviour in Unity Editor which locked up often. + Changed: SocketTCP now has a syncer object and locks in Disconnect(), so only one Disconnect call can be made anytime. + Fixed: Nullreference when calling DispatchIncomingCommands() before Connect(). This was due to a (new) use of the socket wrapper. Commented this out until needed. + Fixed: Nullreference when calling SendAcksOnly() before Connect() with a new non-null check. + Fixed: breaking issue in Hashtable replacement class. Enumerators used in 2 "foreach" loops were breaking with a nullreference. Fix: No caching of enumerator. + Changed: AutoJoinLobby now uses this.CurrentLobbyName and this.CurrentLobbyType to join a specified lobby. + Changed: EnetPeer.StopConnection will always reset the state to be able to re-connect. + Changed: Disconnect() in SocketTcp and SocketUdp sets this.socket = null, even if socket.Close() caused an exception. This is what was expected. + Added: SocketUdpNativeDynamic and SocketUdpNativeStatic to "regular" Unity Lib, to improve compatibility in Unity for different export platforms (with and without native sockets). + +*** Version 3.2.2.1 (17. October 2013 - rev2335) + Note: This lib contains a lot of breaking changes and socket handling has been refactored. By now, this is well tested and confirmed working. + Changed: The way sockets are handled and added native-socket-lib support. There is a new IPhotonSocket interface which even allows to use external classes as socket wrapper. + Added: SocketImplementation property to set a class as socket implementation (Unity. sets native implementation or c# socket at compile time) + Changed: Win 8 RT and Phone now use fewer separate classes and files. Instead, more files from the (regular) DotNet client are used. RT and Phone are now part of the trunk folder in our SVN. + Added: TrafficStats.TimestampOfLastAck and .TimestampOfLastReliableCommand + Changed: Handling of server-side shutdown (a form of TCP disconnect) is now handled specifically as server-side-disconnect (was: generic receive exception) + Added: If a UDP connection times out in a client, it sends a special flag in it's disconnect command (to the server). This enables us to detect which side is triggering timeouts more often (and we can improve things). +LoadBalancing API + Fixed: issue where we called a virtual member from a constructor (http://confluence.jetbrains.com/display/ReSharper/Virtual+method+call+in+constructor) + Changed: LocalPlayer is now a property which checks null and returns a new Player (via virtual CreatePlayer) on demand. + Added: OpJoinRoom now optionally creates a room if parameter "createIfNotExists" is set to true and the room didn't exist. Room properties can't be set "on create" this way. LocalPlayer.IsMasterClient will be true. + Added: When OpJoinRoom can create a room, it also won't define which properties go into the lobby. You can use the new Room.SetPropertiesListedInLobby(). + Added: You can pass a actorNumber to OpJoinRoom when you re-enter a room and want to reclaim a specific actorNumber in that room. In best case, the client can re-join after a disconnect/crash and seamlessly go on playing. + +*** Version 3.2.1.6 (15. August 2013 - rev2272) + Changed: The library for Unity now contains a ExitGames.Client.Photon.Hashtable to be compatible with Win 8 exports. This must be used from now on! + Note: In Unity, the compiler will complain about ambiguous Hashtable definitions. To solve this, add this to the "using" part of your code: using Hashtable = ExitGames.Client.Photon.Hashtable; + Removed: Builds for Silverlight and Windows Phone 7.1 (this is not affecting Windows 8 RT and Windows 8 Phone SDKs which are of course supported) + Fixed: A null-reference check for a TCP connection's SendAcksOnly(). + +*** Version 3.2.1.5 (06.08.2013 - rev2242) + Added: Steam and Facebook entries to CustomAuthenticationType enum. + Fixed: Potential nullreference exception in TCP SendAcksOnly() code. If called before Connect(), this always failed. + Updated: Replacement classes for datatypes not supported on some platforms (Hashtable mostly). + Added: Hashtable got a new GetEnumerator that returns a IDictionary just like the standard Hashtable does. + Changed: Constructor with int InitialSize now calls the matching base constructor. + Removed: Synchronized() method which didn't do much and is not used. + Changed: ToString is now an override instead a "new" method. + Changed: DataTypes.cs: the Stopwatch is only implemented for Silverlight (non Windows Phone 8) + Updated: Description. + Changed: Protocol to expect Hashtable always providing a DictionaryEntry. Related to change in DataTypes.cs. + Changed: Protocol now has conditional "Dictionary" detection. In WP8 the API is different for that. Uses #if WINDOWS_PHONE. same file now works in W8 and WP8. + Changed: Removed PRPCAttribute from SupportClass.cs. This is used only in PUN and needs conditional per-platform compilation anyways, so it gets implemented there. + Removed: surplus debug output in ReceiveIncomingCommands(). + Fixed: Debug output in FetchServerTimestamp() depended on the Thread calling the method. Correct: The output is enqueued and dispatched later on. + Fixed: FetchServerTimestamp no longer fails with a SendError when the state is not "Connected". + Internal: Metro-Alike project now uses DataTypes.cs of Silverlight (like everyone else). Removed surplus copy. + Internal: DataTypes.cs and Protocol.cs files can now be used in DotNet 3.5, Windows Store and Windows 8 Phone. + Internal: outdated compiler-definitions "Photon" and "METROALIKE". + +*** Version 3.2.1.4 (10.07.2013 - rev2209) + Added: "Typed Lobby" API. Photon Cloud and Loadbalancing now support multiple lobbies per app/title. Also, different types of lobbies are now possible, each can have different options and different rules for matchmaking. + Added: enum LobbyType with "Default" and "SqlLobby". The SqlLobby is a new type of lobby that uses up to 10 room properties with sql-like filters. The filter being written like the "where" part of a sql query. + Changed: FetchServerTimestamp now enqueues callbacks (can be called by socket-receive-thread). also no longer causes a disconnect callback if offline + Changed: RemoveSentReliableCommand now enqueues callbacks (can be called by socket-receive-thread) + Internal: SendAcksOnly override in TCP's TPeer class. This now sends pings but nothing else. That resets the server's timeout for this peer +LoadBalancing API + Updated: LoadBalancing API in the Unity demos (now gets copied over at build time, making sure it's identical to the DotNet "original") + Fixed: LoadBalancingClient now handles SecurityException and InternalReceiveExceptions and disconnects correctly. Before, especially Unity web clients would get stuck in "Disconnecting" state. + Fixed: LoadBalancingClient state on disconnect (no matter what caused the disconnect). + +*** Version 3.2.1.3 (19.06.2013 - rev2170) + Fixed: surplus conversion of incoming data to string, which was used in debugging. + +*** Version 3.2.1.2 (17.06.2013 - rev2160) + Fixed: custom auth will send custom auth parameters if any authentication params are set + +*** Version 3.2.1.1 (10.06.2013 - rev2148) + Added: new POST value for Custom Authentication. POST can carry more data than GET (usually used). AuthenticationValues has a setter for this. +LoadBalancing API + Changed: LoadBalancingClient.AuthValues is renamed to CustomAuthenticationValues property (sets the custom authentication values). + Changed: Player class now compares by ActorNumer (assigned by server) instead of comparing the instance. + Internal: SupportClass.GetMethods() now returns type.GetRuntimeMethods(), filtered by attribute (if at all needed). This is used by Photon Unity Networking (PUN) internally. It also returns inherited methods now, not only Declared. + +*** Version 3.2.1.0 (24.05.2013 - rev2112) + Added: Feature "Custom Authentication" which lets you authorize players/users in the Photon Cloud with an external account/user service. More on that online: http://doc.photonengine.com/photon-cloud/CustomAuthentication + Added: LoadBalancing API Feature "Friend Finding" which enables a client to find friends in rooms by userId. If an external service provides a userID per player and a friend list, this can be used to find a friend's room (game) and join it (unless closed or full). + Added: CustomAuthenticationType enum to enable differnt types of custom auth later on (only one actually useful value so far). + Added: Class AuthenticationValues as container for authentication values. + Added: LoadBalancingClient.Connect overload which takes a AuthenticationValues parameter. + Added: LoadBalancingPeer.AuthValues property to set the custom authentication values. + Added: Parameter authValues to OpAuthenticate. This is used to provide the authentication parameters and or the secret/ticket provided by Photon. + Added: ErrorCode.CustomAuthenticationFailed to be used in switches for OperationResponse.ErrorCode (for OpAuthenticate). + Changed: LoadBalancingClient.PlayerName can be set before connecting to get a UserId which is "findable" by OpFindFriends(). Find friends does NOT use any values set for custom authentication! + Added: Class FriendInfo to contain a friend's name, online state and room name (if available and after using OpFindFriends()). + Added: OpFindFriends() to actually find the friends. Use on the Master Server only, not on a room. + Added: LoadBalancingClient.FriendList, a List of FriendInfo entries. Filled by using OpFindFriends (don't modify this list directly!). + Added: LoadBalancingClient.FriendListAge, to let you know how old the FriendList is. Only get updates when the list gets "old". + Fixed: OpRaiseEvent will no longer send "group" if it's 0 (which is the default). + Added: OpRaiseEvent overload to send object instead of Hashtable. This overload uses another parameter order to not mix up with the older implementation. You can send any serializable datatype now but must be aware if the event is Hashtable or something else. + Changed: Several variants of OpAuthenticate(), Connect() and ConnectToMaster() are now obsolete or removed. Use the alternative implementations (which should be cleaner). + Internal: Added several (operation) parameters to enum ParameterCode: ClientAuthenticationType, ClientAuthenticationParams, FindFriendsRequestList, FindFriendsResponseOnlineList, FindFriendsResponseRoomIdList. + Added: PhotonPeer.ResentReliableCommands to get count of re-sent commands (might be higher than out command count (as that counts created commands only) + Internal: Address (string) handling now uses string.Split instead of IndexOf to separate port from address and short.TryParse instead of short.Parse + Added: TrafficStatsGameLevel.ResetMaximumCounters() to reset those values that could max-out easily. Allows to get "longest delta between SendOutgoingCommands()-calls since last query". + +*** Version 3.2.0.2 (21.02.2013 - rev2066) + Fixed: Potential lock-up during sending. This could cause infinite blocking and thus a crash in some apps. (Win8 / Win Store api only) + +*** Version 3.2.0.1 (15.02.2013 - rev2060) + Fixed: Issue with delayed sending of operations in udp. The data could become overwritten before being sent. The bug was leading to high disconnect rates for clients using Windows Phone 7 and 8 and Silverlight or any client that used Network Simulation. + +*** Version 3.2.0.0 (13.02.2013 - rev2053) + Note: This release only changed the version, matching the new Server SDK v3.2.0.0 + Updated: readme.txt + Fixed: Reference for Windows 8 RT and Windows Phone 8 SDKs. + Added: Particle Demo to Unity Client SDK. + +*** Version 3.0.1.18 (11.02.2013 - rev1998) + Added: Optional per package CRC checksums to filter out compromised packages (avoiding more issues, compared to reading bad values). + Added: PhotonPeer .CrcEnabled and .PacketLossByCrc to handle CRC and get the count of affected (incoming) packages. + Note: Checking packages with CRC will take some computation time. Consider this an option to detect if/why someone's connection is bad. It's likely not good to be enabled by default. +Windows 8 RT & Windows 8 Phone: + Fixed: Serialization of foat and double values. These caused exceptions when used in object-arrays. + +*** Version 3.0.1.17 (19.12.2012 - rev1946) + Added: New Platform: Mono 4 Android. Please check the Readme.txt for hints how to build the demo in Mono 4 Android. + Changed: The referenced DotNet assemblies used by our libraries, which makes ours compatible with Mono 4 Android and others. + Changed: The Particle Demo Logic to also handle events sent by JavaScript clients. In case these are used, the types used in event differ from what DotNet or other clients send. + Changed: PhotonPeer.LocalTimeInMilliSeconds property now uses SupportClass.GetTickCount(). That method is using Environment.TickCount (which can be replaced if needed). + Changed: Any place that directly used Environment.TickCount (as the way SupportClass.GetTickCount() gets the value can be replaced). + Renamed: GetLocalMsTimestampDelegate is now: SupportClass.IntegerMillisecondsDelegate (rarely used if at all). + +*** Version 3.0.1.16 (29.11.2012 - rev1923) + Internal: A client timeout now internally sets connectionState to Zombie and then calls Disconnect() instead of stopping the connection right away. + Changed: Disconnect() sends a disconnect-command in any case (except not connected or disconnecting). If the connection is not in state connected anymore, said command is unsequenced (unreliable) and the disconnect is locally executed immediately as call to StopThread(). As before, disconnecting and disconnected clients won't send this. + Changed: Ping creation is now more strict and checks also if any reliable commands are outgoing AreReliableCommandsInTransit(). this avoids a few pings. + Fixed: NullReference exception in StopConnection() if it's called before being connected for the first time (late object creation made this fail). + Changed: PhotonPeer.LocalTimeInMilliSeconds property now uses SupportClass.GetTickCount(). That method is using Environment.TickCount (which can be replaced if needed). + Changed: Any place that directly used Environment.TickCount (as the way SupportClass.GetTickCount() gets the value can be replaced). + Renamed: GetLocalMsTimestampDelegate is now: SupportClass.IntegerMillisecondsDelegate (rarely used if at all). + + +*** Version 3.0.1.15 (27.11.2012 - rev1917) + Note: Silverlight SDK release only! + Updated: Silverlight projects with proper references (hopefully). In case you wonder: Some projects are included even though only their (source) files are linked in Silverlight. We can't reference DotNet projects directly, so we use the (shared) files instead. + Updated: Silverlight Particle Demo now has a basic gui and hopefully helps with your first steps. + +*** Version 3.0.1.14 (16.11.2012 - rev1891) + Added: Interest Groups! In rooms, you might send events to an interest group, identified by a byte (255 groups are currently allowed). OpChangeGroups lets you add or remove groups you're interested in. + Added: New platform! Welcome Windows 8 RT and Windows Phone 8. Both are "preview" releases but based on the stable DotNet basis we have. + Note: The Windows Phone 8 SDK does not yet have a LoadBalancing demo but the API is available (Windows Phone 8 is separate from the still existing Windows Phone 7.1 SDK). + Added: Another new platform: Playstation Mobile! This is Sony's SDK for mobile platforms. Find out more about it: www.playstation.com/psm + Added: Silverlight 4 SDK is back. Now with LoadBalancing API (the demo implementation is not finished but the basic "logic" is running). + Fixed: Windows Phone 7 and Silverlight TCP error handling while connecting to the server. This should fix issues with failing connects due to missing policy settings. + Internal: Windows Phone 7 and Silverlight TCP connections now set their state a bit differently (this doesn't affect the workflow though). + Internal: Http implementation now checks if a Proxy was set deliberately. Check is: (WebRequest.DefaultWebProxy as WebProxy != null). + Internal: DispatchIncomingCommands() now avoids copying lists when checking for commands that need a repeat. + Internal: SendOutgoingCommands() now re-uses a buffer to create UDP packages in before sending. This should save a lot of memory allocation. +LoadBalancing API: + Added: New demo "Particle". You will notice it's similar to the "Realtime Demo" but LoadBalancing- and Cloud-compatible and it makes better use of the default features. Check out Particle "Logic". + Added: LoadBalancingClient.DisconnectedCause to track certain disconnect causes (no matter if the connection or an operation caused the disconnect). + Added: DisconnectCause enum to enumerate those disconnection causes. + Changed: LoadBalancing OnOperationResponse() and OnStatusChanged() to track most disconnect reasons (in DisconnectedCause). + Removed: LoadBalancing Connect() variants that essentially were duplicates of others. + Changed: LoadBalancingClient debug output now goes to: Debug.WriteLine (which is available in debugger, while Console is not always). + Changed: CacheProperties method is now virtual for Room and Player. This allows you to override it and use this as callback to update props. + Added: Player.Tag to store arbitrary (game) data with the Player. Put in (e.g.) a player's representation/avatar or similar. + Added: ErrorCode constants MaxCcuReached and InvalidRegion. These are important for the Photon Cloud. + Added: Handling for DisconnectedByUserLimit. This is a status of OnStatusChanged when a Photon Server License's CCU limit is reached. This no longer will try to connect to a Game Server (where it gets rejected, too). + Changed: Debug output of loadBalancingClient now goes to Debug.WriteLine (which is available in debugger). + Changed: API now uses a factory method to create Room instances (this makes it possible to extend the Room class and instantiate the new class instead). + Changed: The Player constructor now has an "actorProperties" parameter and will cache the provided properties. This makes sure actor-props are available locally. +Windows Phone 8: + Added: Demo for Cloud / LoadBalancing. The Particle Demo only has a special WP8 GUI and links it's logic from a separate project (read: folder). +Windows 8 RT: + Added: Demo "Phong", which is a simplified, basic multiplayer game. It's focus is to show how to sync data, not to make it super smooth and error free. Let us know any issues but bear with us as it isn't fully featured. + +*** Version 3.0.1.13 (26.09.2012 - rev1731) + Fixed: Internals of method DispatchIncomingCommands() for UDP. In some cases this removed commands from a dictionary inside a foreach loop (which causes an Exception due to changing the dictionary) + Added: Support for Dictionary<,>[]. This is not a very lean way to send data (especially when using ) but if needed, it now works + Changed: Replaced several foreach loops with for loops (it doesn't change usage but in Unity exports to iOS, foreach uses more memory than for) + Added: Doc for public methods in Protocol class (they are useful to quickly write values into an existing byte-array) + Fixed: Unity UDP send code: iOS 5 devices will kill a socket when the power button is pressed (screen locked). This case was not detectable by checking socket.Connected. + Added: Unity UDP send code: Now tries to open another socket to refresh/keep the connection. This is affected by timeouts still, of course (as are all connections). + Internal: locked usage of UDP / enet channels + +*** Version 3.0.1.12 (26.07.2012 - rev1683) + Changed: The DotNet client libraries are now Thread safe! You could start a background Thread to keep calling SendOutgoingCommands in intervals and still call it from a game loop, too + Changed: Due to the thread safety, the demos no longer use excessive locks. This is now solved by the lib, more streamlined and hidden. One Thread is used instead of Timers (which could fire concurrently if execution was longer then their interval) + Changed: Moved the enable/disable property fro NetworkSimulationSettings to PhotonPeer.IsSimulationEnabled (this should now be thread safe) + Changed: NetworkSimulation will create and keep one thread when you first enable it in a (debug) client. Disabling it, will execute any delayed action immediately (in IsSimulationEnabled!) and pause the simulation thread + Changed: All demos are updated. We assigned new event codes (starting at 0, like any developer's code should) and extended the comments. Check them out + Changed: All Loadbalancing demos are now using the same DemoBasisCode linked in, so it can be changed in one position. Where needed an extension is made + Updated: comments / documentation for LoadBalancing API, Lite API and basic Photon API (basically anything public) + Changed: SupportClass.NumberToByteArray is now obsolete. It can be replaced with Protocol.Serialize() easily and that is performing better + Fixed: Windows Phone UDP socket was sending a full package of zeros on connect. It didn't break anything but is not needed, of course. + Fixed: SupportClass.StripKeysWithNullValues method was prone to throw an exception +LoadBalancing API: + Changed: LoadBalancingClient.OpLeaveRoom() skips execution when the room is null or the server is not GameServer or the client is disconnecting from GS already + Note: LoadBalancingClient.OpLeaveRoom() returns false in those cases and won't change the state, so check return of this method + Fixed: workflow for authentication (which should be called only once per connection, instead of "any time we establish encryption) + +*** Version 3.0.1.11 (05.06.2012 - rev1569) + Fixed: Udp issue with channels and unreliable commands. Unreliable commands of one channel were discarded, when another channel had unreliable commands, too + +*** Version 3.0.1.10 (04.06.2012 - rev1561) + Fixed: TCP connection issues for DotNet and Unity (Silverlight and WindowsPhone are different) + Fixed: DotNet+Unity TCP send calls with 0 bytes to send (this was ignored by the socket but useless anyways) + Moved: DNS resolution and socket.Connect() are now handled in the connection thread (TCP in DotNet and Unity) + Fixed: Issue with (TCP) socket connections being closed directly while connecting. in this case, socket.Receive() might receive 0 bytes instead of blocking until more bytes are available. without sending anything, the socket never updates its .Connected state and never throws a Exception. now we send a ping and thus trigger a exception + Fixed: Some documentation errors (due to changed API, etc) +Loadbalancing API: + Changed: LoadBalancingClient.OnEvent() now uses a join-event's actornumber-list to create Player instances for anyone who wasn't created as Player before + Fixed: LoadBalancingClient.OnEvent() handling for join-event does not expect any actor/player properties anymore (which fixes a potential null-reference exception when not even a name is set) + +*** Version 3.0.1.9 (10.05.2012 - rev1512) + Fixed: Reference to project in Windows Phone SDK + +*** Version 3.0.1.8 (09.05.2012 - rev1508) + Fixed: The OpJoinRandom of the LoadBalancingAPI failed to filter rooms for their custom room properties. Instead, any room matched. This is fixed now. + Added: New Demo for Windows Phone: Cloud Basics + Changed: The loadbalancing / cloud-based demos are refactored to share a similar codebase + +*** Version 3.0.1.6 (07.05.2012 - rev1489) + Note: This is a "stable" release, containing only a few updates. The bulk of changes are in the "odd" numbered releases. Read those updates carefully. + +*** Version 3.0.1.5 + Changed: adopted the even/odd version numbering system. versions ending on a odd number = intermediate/in-development version, even number = released (that makes 3.0.1.5 a intermediate) + Fixed: When NetworkSimulation is disabled, all remaining packages are sent/received immediately (ignoring the former delays) + Note: NetworkSimulation should be working nicely now. Be aware that sudden, additional lag might (!) lead to a disconnect. Play with the settings to find out which ones work for you + Changed: Protocol class now has a few methods to (effectively) serialize some datatypes to arrays (and into existing arrays) + Removed: Surplus public methods from Protocol that were "type-named" like SerializeFloat. The functionality is in still with overloaded methods + Added: count of packages (requests) outgoing if TrafficStatsEnabled +Demo Realtime: + Changed: The commandline arguments are now server:port, protocol (udp,tcp,http), reliable sending, interval dispatch, interval send, interval move. Example: localhost:5055 Udp false 15 25 15 + Changed: Demo Realtime: If the commandline sets an unknown protocol, the client shows a message and closes gracefully + Changed: Demo Realtime: The demo now starts in the grid view (showing something). Local player and player list are created with the Game instance. Player startpoint is randomized. +Loadbalancing API: + Renamed: LoadBalancingClient.lbPeer to .loadBalancingPeer + Fixed: LocalPlayer.SetCustomProperties() usage + Added: Service() method, which calls the LoadBalancingClient's Service simply + Changed: LoadBalancingClient is no longer extending LoadBalancingPeer but instead using one + Changed: the many overloads of Operations are gone in LoadBalancingPeer to streamline the api + Changed: ActorProperties are no longer set via JoinRoom, JoinRandomRoom or CreateRoom. instead, set the properties in the LocalPlayer and let the LoadBalancingClient send and sync them where necessary + Fixed: MasterClientId is now 0 when there are no more players in the room (it was set to int.max before) +Internal: + Changed: all DispatchIncomingCommands now use a while loop to dispatch the ActionQueue (in the hope this is the fastest way to do it) + Changed: DispatchIncomingCommands now looks for the received unreliable command with lowest unreliable seqNr to dispatch this + Changed: DispatchIncomingCommands discards commands if the reliable OR unreliable sequence is beyond the command's sequences + Changed: DispatchIncomingCommands now truncates the incoming unreliable commands to limitOfUnreliableCommands (if that's > 0) + Changed: the next reliable command to dispatch is now fetched with Dictionary.TryGetValue() (for being faster) + Changed: no longer using BinaryReader streams anywhere (this should improve speed and reduce mem usage) + Changed: PeerBase accordingly + Changed: Unit test MyType de/serialization now supports null-references (as 1 byte being 0) + Changed: Protocol.SerializeOperationRequest is now used in the same way, no matter if request is "top level" or inside some other datatype + Changed: the peer bases accordingly to use only one SerializeMemStream and lock it + Changed: how encryption fits in to the new serialization (it is a special case, as only the operation bytes get encrypted) + Added: Protocol.SerializeParameterTable() as requests, events and responses all use the same way to write their parameters + Changed: SerializeOperationToMessage parameter order + Changed: Order of Protocol methods to make more sense (from byte to more complex types for serialization) + New: PhotonDotNet library prototype for windows 8 metro + +*** Version 3.0.1.3 (13.04.2012 - rev1430) + Known issues: The Network Simulation is currently not guaranteed to work properly. Please bear with us. + Note: the following change might be a breaking one: + Changed: When dispatching a server's disconnect-command, the state is changed to ConnectionStateValue.Disconnecting BEFORE any callback due to state change is called. This should disallow game-code from calling any operations immediately. + Changed: Many internals. This should result in better performance + Changed: Service() now calls SendOutgoingCommands() until send-queues are empty. This might take more time but gets important commands out. If you need more control, Service() can be replaced with DispatchIncomingCommands and SendOutgoingCommands! + Added: null check to GetEndpoint() to avoid issues when the host address is null + Fixed: queueIncomingCommand() debug out message when a command is being received AND in in-queue (the list it accesses is now a dict) + Added: new "vital" stats to TrafficStats + Added: LongestOpResponseCallback and LongestOpResponseCallbackOpCode (opcode and time of longest callback) + Added: LongestEventCallback and LongestEventCallbackCode (event code and time of longest callback) + Added: LongestDeltaBetweenDispatching and LongestDeltaBetweenSending to detect "gaps" between subsequent calls of those + Added: DispatchCalls and SendOutgoingCommandsCalls to measure average call-rate + Fixed: PeerBase.TrafficStatsEnabledTime now checks if a stopwatch is set, else it returns 0 + Fixed: TrafficStatsReset() now works as intended (starting a new stopwatch, too) +Internal: + Changed: name of variable timeLastReceive. is now: timeLastAckReceive (better fit with what it does) + Internal: queueOutgoingReliableCommand() to use a lock on the channel it accesses + Internal: SerializeOperationRequest() now locks the MemoryStream while using it (avoids threading-issues with calling OPs) + Internal: SendUdpPackage() now checks if socket is obsolete (and disconnected for a reason) or not. only if not, a error is logged + Internal: EnetChannel now uses Dictionary and Queue for commands (should be faster to access) + Internal: simplified access methods in EnetChannel according to changes + Internal: outgoingAcknowledgementsList is now a Queue + Internal: receiveIncomingCommands() no longer has a local variable sentTime. instead using this.serverSentTime directly + Internal: UDP sending is now done with a synchronous socket call (profiling told us: this is cheaper) + Internal: re-using the socket arguments for receiving packages (saves some buffer allocation) + Internal: socket to non-blocking (maybe not possible on all devices) + Removed: initial-HTTP-protocol support (HTTP support not public yet) + Added: support for encryption with HTTP protocol + +*** Version 3.0.1.2 +- Added: Rooms now have a "well known" property to list the custom properties that should be available in the lobby. This can be set per room (but most likely makes sense per title/application). +- Added: LoadBalancingClient.OpCreateRoom() has a new parameter "propsListedInLobby" and Room.PropsListedInLobby is available to check this list (if needed at all). +- Added: GameProperties.PropsListedInLobby as "well known property" key +- Changed: LoadBalancingPeer.OpCreateRoom now sets ParameterCode.CleanupCacheOnLeave to true by default. This makes the server clean a player's event cache on leave. +- Added: SupportClass.DictionaryToString() will now print values of string[] and optionally leaves out type information. +- Note: 3.0.1.1 didn't get it's own SDK, so read that version's changes, too + +*** Version 3.0.1.1 +- Added: PhotonPeer.TrafficStatsElapsedMs, which gives you the milliseconds that the traffic stats are enabled. This internally uses a stopwatch (for now) which might not be available on all platforms. Please report if this new SDK causes issues. +- Added: PhotonPeer.TrafficStatsReset() to reset the traffic stats and the timer. This could be useful to get stats of "in game" versus "out of game". Note: Loadbalancing includes frequent server-switching and each disconnect/reconnect causes a reset. +- Changed: In LoadBalancingPeer EventCode.SetProperties is obsolete and replaced with EventCode.PropertiesChanged. Please switch to new constant. +- Added: Support in LoadBalancingAPI for Player.IsMasterClient. For this, the Players now get a RoomReference set (when added). The active player with the lowest ID is the master (per room). +- Added: Room.MasterClientId, which is updated when new players are added or the current master is removed. +- Added: SupportClass.DictionaryToString() has an overload which doesn't "print" the Type per key/value. +- Added: Loadbalancing API overload for OpJoinRandomRoom(...) taking additional parameter 'playerProperties' +- Added: Loadbalancing API CacheProperties() and Room.GetPlayer() are public now +- Added: LoadBalancingClient will now handle ExceptionOnConnect and keep clients from re-connecting if establishing a connection fails +- Note: The following changes affect only HTTP, which is an upcoming option for connections. So far, the public server SDKs don't support this. Feel free to contact us about it. +- Added: setter for PhotonPeer.ServerAddress to allow setting a http url (even while connected) +- Added: PhotonPeer.HttpUrlParameters setting parameters to be added to end of url (must begin with '&') +- Added: HttpUrlParameters to PeerBase +- Added: HttpUrlParameters is now attached to the end of a URL in http usecase +- Added: "Http2" support to Unity library +- Internal: method HttpBase.ConnectAsync is no longer needed and Request() is now directly passed to thread + +*** Version 3.0.1.0 +- Added: Loadbalancing (Cloud) Features +- Added: Project with the Loadbalancing sourcecode for DotNet, WindowsPhone and Unity3d (usable without PUN) +- Added: Initial, simple Loadbalancing demos for each platform (will update and extend those) +- Note: The API of the client libraries didn't change. The new features were added on top of the known API +- Added: VS2010 solutions for DotNet and Windows Phone SDKs containing the demos and APIs in the package +- Added: readme.txt with initial help to setup the Cloud/Loadbalancing demos +- Added: default appId for Loadblanacing demos: "" + +*** Version 3.0.0.10 +- Added: When UDP StartConnection (internal method) fails, callbacks to OnStatusChanged(StatusCode.Disconnect) are now done additionally to the SecurityExceptionOnConnect and ExceptionOnConnect calls. This happens direcly inside PhotonPeer.Connect()! +- Changed: When Unity UDP implementation fails to connect due to missing DNS resolution, it now also calls OnStatusChanged(StatusCode.ExceptionOnConnect) +- Removed: StatusCode.Exception_Connect value (obsolete, replaced by ExceptionOnConnect, same value) +- Fixed: Http connections (DotNet & Unity) now skip results while in disconnected state +- Fixed: Http connections (DotNet & Unity) now ignore results after a disconnect and reconnect was done (this applies only to HttpBase, not HttpBase2) +- Fixed: misleading debug out (pointing to WindowsPhone while the class is now in general use) +- Changed: DotNet UDP connection now only logs socket errors if the connection isn't obsolete (disconnected) already + +*** Version 3.0.0.9 +- Fixed: issue with HTTP connections and EstablishEncryption() +- Changed: ActionQueue is now a Queue, allowing Dequeue in a while loop instead of foreach(i in list) and clear() +- Changed: Unity HttpBase DispatchIncomingCommands() to make use of the queue +- Fixed: init byte[] length (internal. did not have consequences) +- Fixed: LitePeer OpRaiseEvent() was sending encrypted +- Internal: ContainsUnreliableSequenceNumber() check if reliable list needed sorting +- Fixed: Unity/Silverlight bug with encryption. Their implementation of BigInteger.GetBytes() failed when the 2nd, 3rd or 4th of the first 4 bytes was 0 but the previous wasnt. This led to incompatible secrets. +- Changed: TCP socket sending debug output now checks debuglevel (when send is skipped, cause the sender is obsolete already) +- Added: caching option RemoveFromRoomCacheForActorsLeft = 7 +- Internal: Added another http-based communication protocol. Please note: The fitting server's are not yet publicly released. This does not affect UDP or TCP protocols. + +*** Version 3.0.0.8 +- Fixed: Udp fragment reassembly in case fragments are received out of order and incoming queue was not yet sorted +- Fixed: Handling of incoming reliable commands (udp) which were skipped in some cases, if not received in order +- Fixed: Network simulation issue which caused lost incoming commands +- Fixed: Demo Realtime. protocol is now again Udp, fitting the default server address "localhost:5055" (make sure to build the demo with your server's address if Photon is not on the same machine) + +*** Version 3.0.0.7 +- Changed: Udp socket usage for Unity 3d lib. Both threads (send (in game loop) and receive (separate)) now have minimal locks while using the socket +- Fixed: SendOutgoingCommands now returns true if anything didn't make it into the outgoing UDP package +- Internal: TCP connections also skip network simulation when it's turned off + +*** Version 3.0.0.6 +- Fixed: SendOutgoingCommands now returns true if commands are remaining in outgoing queues (UDP only sends one package per call, TCP will send anything outgoing). +- Added: New "RoomCache" for Events. The EventCaching enum allows you to use it. Events in this cache will keep the order in which they arrived in the server. A filter makes deleting them very flexible. +- Internal: Ability to make lib send only ACKs and nothing else. This is probably a temp solution as it might be better to make sending and calling ops completely thread safe. +- Internal: PhotonPeer.IsSendingOnlyAcks, which is locked with the sending (not changing while sending). This makes SendOutgoingCommands() thread safe, which is good if you need a separate thread to keep connection. You could call operations while sending. +- Internal: Unity3d's connection now also syncs socket usage + +*** Version 3.0.0.5 +- Fixed: ObjectDisposedException in DotNet UDP workflow. This was caused by disconnecting while incoming data was processed (and before the next datagram was accepted) +- Added: PhotonPeer.LimitOfUnreliableCommands property. This helps you skip potentially "outdated" unreliable commands (events), which helps if you couldn't dispatch for a while +- Internal: Minor performance improvements. Example: The check if network simulation is turned on is done earlier in the workflow, which avoids a bit of overhead + +*** Version 3.0.0.4 +- Fixed: Tcp connections have been throwing ArgumentNullException in DispatchIncomgingCommands() if they were not connected yet +- Internal: Adjusted Http client to server rev2360 + +*** Version 3.0.0.3 RC2 +- Internal: Communication with HTTP server is WIP (Work In Progress - not a publicly available feature) + +*** Version 3.0.0.2 +- Fixed: OpRaiseEvent overload with EventCaching and ReceiverGroup parameters was not sending the customEventContent as expected. This was always null. +- Fixed: Time fetching case where no time was accepted. Servertime is now accepted, if the fetch-time-command was less or equal as the current roundtrip time. Avoids issues if rtt is exceptionally low immediately. +- Internal: When using multiple channels, dispatching incoming commands now will continue with the next channel, if one doesn't yet have the next reliable command (reliable sequence of one channel does not affect others) +- Internal: Changed protocol for TCP and message headers. This will support bigger message sizes. Also changed debug out related to unknown headers. +- Internal: Changed handling of TCP receive-callbacks for unfinished messages in Silverlight and WP. This should fix handling of very big data that's received in multiple "chunks" +- Internal: Http messages are now deserialized the same way that content in tcp or udp is handled + +*** Version 3.0.0.1 RC1 +- Fixed: Packaging of SDK now includes all files in demo folders, except a list of ignored file-endings (xaml and jpg files were missing in previous Silverlight and WindowsPhone SDKs) + +*** Version 3.0.0.0 RC1 +- Changed: Filenames! Now include a '3' for Photon v3. Update your references! Also, Silverlight libraries now use "Silverlight" in the filename (was: SL) +- Changed: Versioning. A dll's version has now 4 digits. The first 2 match Major and Minor number of the Server SDK. The latter 2 are Release and Build respectively +- Changed: Silverlight DataTypes (like Hashtable) are now in namespace ExitGames.Client.Photon. This is easier to include (as that namespace is in "using" in most cases) + +*** Version 6.4.5 +- Changed: Parameters for OpCustom are now of type Dictionary, making sure that only byte-codes are used for parameters +- Changed: Most IPhotonPeer names (to match those in server code): EventAction -> OnEvent, OperationResult -> OnOperationResponse, PeerStatusCallback -> OnStatusChanged +- Added: SupportClass.DictionaryToString(), which converts the content to string (includes support for Hashtables) +- Moved: Definitions of Lite and Lite Lobby specific codes for Parameters, operations and events are now in LitePeer. Will be available as source and could be replaced +- Changed: Usage of codes in Lite and Lite Lobby. Now pre-defined codes are starting at 255 and go down. Your events, operations and operation-parameters can now start at 0 and go up without clashing with pre-defined ones +- Changed: Constants that are non-exclusive (like event codes and OpKeys, which can be extended) are no longer "defined" as enums but as class of const byte values. Less casting but also less convenient "name" representation in debug output +- Added: LiteEventKey.CustomContent as key to access the content you sent via OpRaiseEvent ("Data" seems a bit misleading but is also available) +- Changed: Namespace of LitePeer to ExitGames.Client.Photon.Lite (the Lite-specific class is still compiled into the library for convenience but can be ignored quite easily this way) +- Added: Property MaximumTransferUnit. The default is 1200 bytes. Usually this is ok. In few cases, it might make sense to lower this value to ~520, which is commonly assumed the minimum MTU. Don't change this, if you don't know why. +- Added: New classes to wrap up op-requests (OperationRequest), op-results (OperationResponse) and events (EventData). Those new classes are now used in callback methods OnEvent and OnOperationResponse +- Changed: by using the new classes (note above), the client is a bit more like the server in its naming. We didn't want to change every last bit though. +- Internal: Changed protocol (to 1.6) so that it does not require any parameter codes internally. Any application can now define any operation, parameter and event codes it wants to. +- Changed: Encryption is now triggered by you and resolved by the library. You don't have to look out for the result of EstablishEncryption and use it. Instead: wait for OnPeerStateChanged call with either EncryptionEstablished or EncryptionFailedToEstablish +- Removed: InvocationId. This concept was very rarely used but confusing. It's easy to implement, if needed. If you don't know what this means: Nevermind. +- Changed: Operation calls now return bool: if they could be enqueued or not. If enqueued (cause you are connected and the data was serializable), then SendOutgoingCommands will send those operations (as before). +- Added: Support to de/serialize Dictionary. If the types are more specific than object, the serialization writes the type-code only once (lean byte usage in protocol) +- Added: Support to de/serialize null. Enables you to send a null value, e.g. in a Hashtable +- Added: ReceiverGroup enum to select a range of players that get an event via Operation Raise Event +- Added: Event Caching. Any event sent via RaiseEvent can now be buffered on the server side and is "repeated" when a new player is joining a room. This is similar to Properties but lets you categorize your info better and works just like regular events, too. +- Added: EventCaching enum to select if an event is to be cached and how it's cached: either "not at all" (default), replacing anything cached so far (fast) or "merge" (which will add new and replace old keys with new values). Optionally, a event can be raise with option "remove". +- Added: new overload of OpRaiseEvent() with the two new parameters noted above +- Added: Support for custom de/serializer methods. By writing 2 methods to convert a object into a byte-array (and back from that), Photon now supports any custom object type (standard datatypes are still supported out of the box) +- Added: PhotonPeer.RegisterType() to register serializer and deserialize methods for a certain type. Per object, a length and one byte 'type code' are added to the serialized data +- Added: Support for non-strict object[]. Unlike strictly-typed array, here each element will carry its own type. +- Note: If you want to use the new Custom Types or the object[], you have to update your server! Older Servers don't support the new features. As long as you don't use these features, the library is compatible with previous servers. +- Added: ByteCountCurrentDispatch and ByteCountLastOperation properties to PhotonPeer (the ancestor of LiteGame, etc). A game can now access the size of operation-results and events as well as operation-call size. +- Added: Traffic statistic set: PhotonPeer.TrafficStatsGameLevel as "high level" game-related traffic statistic. Counts bytes used by operations, their results and events. This includes overhead for these types of messages, but excludes connection-related overhead +- Added: Traffic statistic set: PhotonPeer.TrafficStatsIncoming and PhotonPeer.TrafficStatsOutgoing as low level statistics of the traffic +- Added: PhotonPeer.TrafficStatsEnabled which enables two sets of traffic statistics. By default, statistics are turned off. +- Added: Classes TrafficStats and TrafficStatsGameLevel for the two statistic cases metioned above +- Changed: NetworkSimulation now starts a Thread when it becomes enabled and the thread ends on simulation disable. Disable the NetworkSimulation to stop the thread, as Disconnect does not change the simulation settings! +- Internal: Cleanup and renaming of several properties +- Internal: Each new peer increases the PeerCount but it is no longer reduced on disconnect (it is existing still, after all) +- Internal: Udp commands will be buffered when serialized. This saves some work when re-sending a reliable command +- Added: TCP Routing code (not in Silverlight). To be used when running Photon on Azure (can be ignored in regular use) +- Added: to StatusCode: TcpRouterResponseOk = 1044, TcpRouterResponseNodeIdUnknown = 1045, TcpRouterResponseEndpointUnknown = 1046 and TcpRouterResponseNodeNotReady = 1047, +- Added: override for PhotonPeer.Connect() with node +- Internal: DotNet now reads the 2 bytes routing response, if a routing request was made (also, not in Silverlight) +- Internal: If TConnect sent a routing request, nothing else will be sent until 2 bytes response are read. +- Internal: If the routing-response does not start with ProxyResponseMarkerByte = 0xF1, a debug message is enqueued and TCP will disconnect +- Internal: Init request for TCP is now always enqueued instead sent directly. This way, it can be delayed if a routing node is selected +- Internal: TPeer EnqueueInit() and SendProxyInit() now create init and routing request respectively +- Internal: TConnect.sendTcp() checks isRunning before it tries to send (the socket might close before the NetSim does). This won't be an issue anytime, still INFO-level callback to DebugReturn is done. +- Removed: debug out for "send package" situation (even on ALL-level, this is more or less spam) +- Internal: updated version numbers of init to 6.4.5 +- Changed: SupportClass HashtableToString() returns "null" if parameter is null +- Internal: Removed SortedCommandList and CommandList classes. Replaced by List and a Sort() where necessary +- Internal: EnetPeer.channels is now a Dictionary instead of a SortedList +- Internal: the channels are initialized with channel 0xff first - this makes 0xff high prio in all foreach usaged +- Internal: NCommand class is now IComparable for usage in Sort() + + +*** Version 6.4.4 +- Added: PhotonPeer.TimestampOfLastSocketReceive now provides the time when something was received. Can be used warn players of bad communication-timing even before the disconnect timeout will be happening +- Fixed: OpGetPropertiesOfActor did use the actorNrList correctly, which always got you all properties of all players + +*** Version 6.4.3 +- Changed: A udp connection timeout in Unity will now end the socket-handling thread correctly +- Changed: The thread for Network simulation is now stopped when the client disconnects and started on connection (instead of keeping it per peer) +- Fixed: Exceptions in network simulation, when Disconnect() was called soon after Connect() but before the connection was established. + +*** Version 6.4.2 +- Fixed: It was possible to send PhotonPeer.FetchServerTimestamp() before being connected properly. Now the method triggers debug output (INFO level) and the callback PeerStatusCallback(StatusCode.SendError) +- Internal: Added a lock in the UDP version of SendOutgoingCommands(). It's still illegal to access a peer from multiple threads but the follow-up issues this lock avoids are very difficult to track. +- Internal: to stay compatible with all exports of Unity, the use of System.Threading.Interlocked.Exchange was replaced by simply replacing the list's reference instead + +*** Version 6.4.1 +- Changed: The Unity library now uses the WWW class for Http based requests. Results are checked within DispatchIncomingCommands(). Important: Unity allows handling WWW requests only on the MainThread, so dispatch must be called from this context! +- Note: Photon does not support Http requests out of the box. Customers get access to a fitting server on demand +- Changed: outgoing list is now replaced on send, instead of calling remove(0) repeatedly (which takes longer). Internal: this uses System.Threading.Interlocked.Exchange to switch to a new outgoing list in one step + +*** Version 6.4.0 +- Fixed: TCP handling of incoming data. This avoids loss of data (operation-results or events) when a lot of data is incoming. +- Changed: PeerStatusCallback() is less often called for queue-length warnings (e.g.: StatusCode.QueueIncomingReliableWarning). Only if a queue has a multiple of PhotonPeer.WarningSize items. +- Changed: WarningSize is now 100 by default +- Changed: Description of PhotonPeer.WarningSize and PhotonPeer.CommandBufferSize, which really is just the initial size of any buffer. The warnings are there to avoid situations where all heap is used up. +- Changed: Naming: StatusCode.Exception_Connect is now Obsolete and replaced with StatusCode.ExceptionOnConnect +- Added: Missing summary for StatusCode.SecurityExceptionOnConnect +- Added: NetworkSimulationSet.ToString override to provide a better overview +- Added: Support for arrays of Hashtables + +*** Version 6.3.1 +- Fixed: Network simulation now delays incoming packages by IncomingLag and IncomingJitter as expected (it was using the outgoing values, too) + +*** Version 6.3.0 +- Added: Network simulation (lag, jitter and drop rate) to debug builds +- Added: class NetworkSimulationSet with properties to control network simulation +- Added: NetworkSimulationSettings.NetworkSimulationSettings property to get current simulation settings +- Changed: only the first peerId of a VerifyConnect is accepted in client (avoids surplus peerID changes) +- Internal: added PeerBase.SendNetworkSimulated() and PeerBase.ReceiveNetworkSimulated() and a Thread to run delay simulation +Siverlight: +- Updated: to Silverlight v4.0 +- Added: Encryption to Silverlight library +- Internal: updated internal BigInteger class for Silverlight +- Internal: DiffieHellmanCryptoProvider in Silverlight, so it uses AesManaged instead of Rijndael (which is not part of Silverlight 3) +- Added: Stopwatch class to DataTypes.cs (for Silverlight only) + +*** Version 6.2.0 +- Added: "Demo LiteLobby Chatroom" to Unity SDK +- Updated: Demo Realtime in Unity client SDK. It's still compatible with the demo on other platforms but cleaned up and much better commented +- Updated: Documentation is now clearer on where the Lite logic is used (it runs on Photon but is not the only application logic) +- Updated: Documentation for the enumerations in IPhotonListener. The Lite application based ones are better described and it's now clear which ones are essential to the Photon client (not only in Lite) +- Updated: Documentation in several other places +- Added: StatusCode.SecurityExceptionOnConnect which is thrown if a security exception keeps a socket from connecting (happens in Unity when it's missing a policy file) +- Added: PhotonEventKey and PhotonOpParameterKey which contain the fixed byte keys that cannot be re-assigned by applications at will (as these keys are used in the clients and server in their respective context) +- Change: PhotonPeer.PeerState is no longer a byte but of type PhotonPeer.PeerStateValue, which makes checking the state simpler. The PeerStateCallback() for state changes is still called as before. +- Changed: Property PhotonPeer.PeerState. It now converts the low level ConnectionStateValue to a PeerStateValue, which now includes a state InitializingApplication. See reference for PeerStateValue. +- Changed: PeerStateValue enum is now part of the ExitGames.Client.Photon namespace, making it more accessible +- Internal: NConnect in DotNet and Unity to catch security exceptions +- Internal: from using var to explicit type usage in DiffieHellmanCryptoProvider.cs (Mono Develop friendly) +- Internal: made const: ENET_PEER_PACKET_LOSS_SCALE, ENET_PEER_DEFAULT_ROUND_TRIP_TIME and ENET_PEER_PACKET_THROTTLE_INTERVAL +- Internal: PeerBase "PeerStateValue peerState" is now: "ConnectionStateValue peerConnectionState" (holding the low level connection state, nothing more) +- Internal: added PeerBase.ApplicationIsInitialized, which stores if the init command was answered by Photon (reset on connect/disconnect) +- Removed: PhotonDemoServerUrlPort and PhotonDemoServerIpPort of PhotonPeer. All demos now use "localhost:5055" and you should run your own server. +- Added: enum ConnectionProtocol to get rid of the "useTcp" parameter in the PhotonPeer constructor (which was less clear than the explicit enum now in use) +- Added: overload of PhotonPeer constructor, which is still compatible with the "useTcp" bool parameter (to avoid a breaking change for the time being) +- Added: PhotonPeer.UsedProtocol property to find out this peer's protcol +- Added: LitePeer.OpLeave() overload without the gameName parameter. That name is not checked in the Lite application (on the server), so it's not really needed + +*** Version 6.1.0 +- Added: Encryption for Unity and DotNet. Operations (and their responses) can be encrypted after exchanging the public keys with the server +- Added: OpExchangeKeysForEncryption(), DeriveSharedKey() and IsEncryptionAvailable to PhotonPeer (and LitePeer inherits these) +- Added: OpCustom() will throw an ArgumentException if the operation should be encrypted but keys are not yet exchanged (exchange keys first) +- Added: LiteOpCode.ExchangeKeysForEncryption = (byte)95 +- Added: Overloaded PhotonPeer.OpCustom() with new "encrypt" parameter +- Added: property PhotonPeer.IsEncryptionAvailable is true if public-keys are exchanged and the secret is compiled from them +- Added: Encryption demo to Realtime Demo. Press E to exchange keys and R to toggle encrypted sending for the move data (even though events are never encrypted) +- Changed: PeerBase methods: sendOperation()->EnqueueOperation(...,encrypt), updateRoundTripTimeAndVariance()->UpdateRoundTripTimeAndVariance() +- Updated: the Unity client is now a Unity v3.1 project. Make sure to change the server address before you build for iPhone (localhost:5055 won't work on the mobile) +- Removed: the outdated, separate iPhone demo (was: Unity v1.7 for iPhone) +- Updated: PhotonPeer documentation for Service(), DispatchIncomingCommands() and SendOutgoingCommands() +- Added: OpRaiseEvent() overload with parameter TargetActors. Sends optional list of actors that will receive the event (if null, all *other* actors will receive the event, as default) +- Internal: Added source BigInteger.cs, DiffieHellmanCryptoProvider.cs and OakleyGroups.cs +- Internal: PeerBase.CryptoProvider, PeerBase.ExchangeKeysForEncryption() and PeerBase.DeriveSharedKey() +- Internal: EnetPeer.initPhotonPeer() and TPeer.initPhotonPeer() are setting PeerBase.isEncryptionAvailable = false +- Internal: De/Serialization methods (and some variables for it) are moved from NConnect to PeerBase and renamed to: SerializeOperationToMessage() and DeserializeMessageAndCallback() +- Internal: switched project to allow "unsafe" functions (used by BigInteger) +- Internal: renamed PhotonPeer.sendOperation()->EnqueueOperation +- Internal: changed assembly version to 6.1.0 and "client version" in init-byte-block to 6,1,0 +- Internal: moved protocol handling to EnetPeer and TPeer classes (where encryption is added) +- Internal: moved InitBlock to (shared) PeerBase (same for UDP/TCP) +- Internal: serialization is now done by Protocol.SerializeOpParameters(), which excludes the message header. this makes encryption simpler + +*** Version 6.0.0 +- Changed: This library requires Photon v2.2.0 and up! (in other words: the libraries are not compatible with older Photon servers, due to servertime changes) +- Added: Support for arrays in arrays. Any serializable datatype can now be used in nested arrays. Even arrays of Hashtables are possible. +- Added: Realtime Demo optional command line arguments for game config. set all or none: serverAddress, useTcp (true/false), useReliable (true/false), int intervalDispatch, intervalSend (ms), intervalMove (ms) +- Note: Realtime Demo commandline might look like this: start demo-realtime.exe localhost:5055 false true 5 25 100 +- Changed: renamed GetLocalMsTimestamp property to LocalMsTimestampDelegate (it does not have a getter, despite the old name's implication) +- Added: PhotonPeer.LocalTimeInMilliSeconds property to use the timestamp delegate to get the current client milliseconds (by default this is Environment.TickCount) +- Changed: UDP: The default value for PhotonPeer.RoundTripTime (300ms, used before connecting) is now replaced with the turnaround time of connect. This should lead to accurate RTT values much sooner +- Changed: PhotonPeer.ServerTimeInMilliSeconds is no longer updated all the time. Instead it's fetched soon after connect (when initialization won't affect rountrips anymore) and extrapolated. It should be better to be off by a constant value than by a changing value +- Changed: PhotonPeer.ServerTimeInMilliSeconds now returns 0 until the server's timestamp is fetched. Updated the documentation with some internals for this. +- Added: PhotonPeer.FetchServerTimestamp() to send the time fetch command (this is done automatically as well. this method is here for completeness) +- Fixed: roundtrip time calculation is no longer affected by long intervals between Service() or DispatchIncomingCommands() calls (bug of v5.9.0, caused by internal action queues) +- Added: internally for UDP, we use a new command to fetch the timestamp which minimizes the latency for that roundtrip. this one is excluded in roundtrip time measuring +- Changed: internal: ACKs by the server are again directly executed (other commands which are put into the action queue and dispatched) +- Fixed: Peers with TCP as protocol will no longer try to disconnect while not being connected (does not do anything of disconnected or disconnecting) +- Changed: Peers with TCP as protocol will clear the outgoing queue when disconnect() is called (while connected. see fix above) +- Updated: Silverlight Realtime Demo slightly +- Added: PhotonPeer.Listener property to give subclasses access to the IPhotonPeerListener (set in constructor). Can be useful to call Listener.DebugReturn() +- Added: LitePeer-Source.cs to demo-realtime. This is the source of a LitePeer and could be used as sample to create custom operations on the client side + +*** Version 5.9.0 +- Release: of changes in 5.7.6 and 5.7.5 + +*** Version 5.7.6 +- Fixed: a debug output line for TCP connections which did not heed the debug-level. +- Changed: PhotonPeer uses less locking internally and will handle incoming data in the game thread (inside DispatchIncomingCommands() or Service()). +- Changed: Internally, all commands are put into a (locked) queue which is processed within DispatchIncomingCommands(). Your dispatch interval affects local lag but not the PhotonPeer.RoundTripTime value. +- Note: Don't use a peer from multiple threads! It's not thread safe. All callbacks to IPhotonPeerListener methods are happening in your game thread (again: inside DispatchIncomingCommands()). +- Changed: removed locks inside the callbacks (according to above change). +- Changed: DNS resolution is now done in Connect() unless you provide a valid IP address (if IPAddress.Parse(address) is successful, the IP is used directly). +- Fixed: PhotonPeer.Connect() should fail if the IP is unknown or unavailable. Exception: using a localhost might succeed but fail when we try to receive anything. +- Updated: Game.cs now initialized the timing intervals. This avoids issues if the client system is having a negative TickCount. +- Added: ServerAddress property to PhotonPeer, which might help while developing with several servers and peers. +- Changed: This version includes GetLocalMsTimestampDelegate and the PhotonPeer property GetLocalMsTimestamp to set the delegate for local timestamp. + +*** Version 5.7.5 +- Changed: All precompiled demos now connect to localhost! From now on, you need to run Photon before trying any of the demos (as we don't guarantee that udp.exitgames.com is online anyways) +- Changed: OpCustom() now accepts null as parameter Hashtable, which is a shortcut to "no parameters" for simple operations (an empty hashtable is sent though, it does not reduce bandwidth) +- Added: new feature: UDP timeout definition by setting PhotonPeer.DisconnectTimeout (individual per command, set in milliseconds, checked when a command is repeated) +- Renamed: enum ReturnCode to StatusCode. The StatusCode values are only used for status callbacks (not as operation results) +- Changed: parameter type of PeerStatusCallback() from int to StatusCode (to differentiate them from operation ReturnCodes, which are customizable) +- Removed: StatusCode.Ok (as it was actually an Operation ReturnCode) +- Added: new StatusCallback value: StatusCode.SendError. Used for sending error cases: "not connected" and "channel not available" +- Changed: sendOperation() (Udp and Tcp) does not throw an exception while disconnected or for wrong channel (using StatusCode.SendError instead) +- Changed: callback DebugReturn() now has the additional parameter (DebugLevel)level, analog to logging +- Changed: UDP connection is disconnected when a read exception happens (before we tried to read despite this until a timeout ended it) +- Changed: EnetPeer.Disconnect() now ignores calls when peer is disconnected or disconnecting already +- Fixed: TCP code tried to detect socket issues by checking for IOExceptions but now checks SocketException instead +- Changed: internal threading: Callbacks due to incoming packages and commands are now queued and triggered by dispatch (in game loop) +- Changed: dispatch of action-queue as added to DispatchIncomingCommands (in EnetPeer and TPeer) +- Changed: internally, there is no locking for outgoing reliable and unreliable command lists anymore +- Changed: Realtime Demo timer usage to avoid nullref on form-close +- Changed: Realtime Demo propety isReliable is now in the Player class +- Changed: Game.cs and Player.cs for all realtime demos. There is now something like a gameloop (Update()) which must be called regularly and makes (pretty) sure just one thread accesses the peer +- Changed: all realtime demos to use the new Update() method and use more similar Game and Player classes (cleanup for less differences) +- Fixed: RoundtripTimeVariance is now also reset on connect / init, so the resend-timing of reliable udp does not suffer when a peer connects after a disconnect +- Fixed: typo in ExitGames.Client.Photon.StatusCode.QueueIncomingUnreliableWarning (was QueueIncomingUneliableWarning) + +*** Version 5.7.4 RC3 +- Changed: Unity3D lib again has it's own UDP handling (the DotNet one causes browser crashes on web-player exit) + +*** Version 5.7.3 RC3 +- Changed: Unity3D lib is now identical to DotNet lib (Unity iPhone is compatible with DotNet 2.0 now and this got tested) +- Fixed: DNS resolution (did not work for "localhost", which gave two results (IPv4 and IPv6), mixing up things + +*** Version 5.7.2 RC3 +- Changed: Unity3D lib: the receive thread will now receive until no data is available, then sleep 5ms and check again +- Changed: serverTime is now a signed int (as on server) and adds averaged rountripTime/2 when it gets an update +- Changed: ServerTimeInMilliSeconds doc (more concrete, explains how server time works) +- Added: support for serverTime, RountripTime and RoundtripTimeVariance when using TCP (Silverlight does not allow UDP) +- Added: Silverlight supports either URL:Port and IP:Port as server url string + +*** Version 5.7.1 RC2 +- Added: DotNet "Lobby Demo" which uses the "LiteLobby" application of the server SDK to show running games and their player-count +- Changed: the realtime demos to use the more similar Game and Player classes + +*** Version 5.7.0 RC1 +- Added: documentation: project for Silverlight Hashtable and ArrayList substitutes. +- Changed: RealtimeDemo uses same classes Game and Player for Unity3 + Silverlight +- Changed: Silverlight: Hashtable and ArrayList are now a separate project / lib +- Internal: Silverlight: listener interfaces (Photon and Neutron) now conditionally use ExitGames.Client datatypes from lib +- Changed: Photon: connect callback is now deferred to on-init-response (instead of enet-connect) which ensures "no ops before init" +- Changed: Unity Realtime demo: using game and player classes merged over from silverlight and re-wrote sample code to display players +- Internal: photon projects now have a pre-compile setting "Photon" +- Changed: SupportClass Namespace is now compiling into either ExitGames.Client .Photon or .Neutron (to avoid ambiguation) +- Added: LitePeer as Lite Application specific peer (with OpJoin and the rest) +- Changed: demos accordingly +- Changed: case of PhotonPeer methods to first-letter-is-uppercase (as usual in C#) +- Removed: nNet-prefix (Connect and Disconnect are self-explanatory) +- Renamed: PropertyTypes are now LitePropertyTypes (as they belong to the Lite application) +- Changed: Peer state constants with PS_* converted into enum "PeerStateValue" +- Removed: URL_RT_SERVER, URL_RT_SERVER_DEV, IP_RT_SERVER and IP_RT_SERVER_DEV +- Added: PhotonDemoServerUrlPort and PhotonDemoServerIpPort +- Renamed: NPeer to PhotonPeer +- Renamed: PhotonPeerListener to IPhotonListener (class and file) +- Changed: namespace from Com.ExitGames to ExitGames and ExitGames.Client, ExitGames.Client.Photon and ExitGames.Client.Neutron +- Removed: QueueOutgoingUnreliableError, QueueOutgoingAcksError, QueueIncomingReliableError, QueueIncomingUneliableError, QueueSentError (no errors, only warnings) +- Removed: error "report" when TCP incoming queue getts fuller +- Internal: updates Neutron part to run with Protocol.cs de/serialization (added a serializeParametersNeutron() as there are multiple differences to UDP part) +- Changed: projects and scripts to build documentation xml in debug builds +- Renamed: demo-photon-SL to demo-realtime-SL (according to other demo realtime implementations) +- Changed: many classes and properties are now internal. e.g. Protocol, EnetChannel, EnetPeer (and inner classes), TPeer, SuppportClass.ReadInput() +- Updated: AssemblyInfo.cs for photon dotnet and silverlight +- Internal: projects to have precompile-flags also in release builds +- Updated: build scripts for SDK building +- Removed: Compact Framework support + +*** Version 5.6.1 +- Fixed: 0 element arrays caused bugs +- Fixed: double type was cast incorrectly after being read + +*** Version 5.6.0 +- Added: more supported datatypes: float, double and arrays of all basic datatypes (no arrays of hashtable or arrays) +- Internal: changed Photon protocol internally to 1.5. (needs a server update to Photon Server SDK 1.6.1+)! +- Changed: Channels for Photon UDP are now priorized (from low to high) getting the lower channels out first +- Internal: switched de/serialization at several places from manual shifting to a support function, which should provide endian-correctness (Photon Unity PPC compatibility) +- Added: Unity info about "Application.runInBackground = true;" to Unity Appendix in doc +- Changed: Photon return values are put into a NEW hashtable on receive. not just a cleared one which was not reference-safe (no more need to deep-copy the data of events) +- Added: Photon support for "disconnect-reason" which is sent by server in the enet "reserved" byte +- Added: Photon ReturnCode.DisconnectByServerUserLimit and .DisconnectByServerLogic +- Removed: NPeer.IncomingReliableCommands (was more or less useless) +- Added: QueuedIncomingCommands and QueuedOutgoingCommands as metric for how effective send and dispatch is done +- Changed: now throwing exceptions when trying to set init-values at runtime (to be fixed at development-time) +- Added: doc for sequencing and updated channel doc, (too) short chapter on custom operations, topic "opCodes: byte versus short", doc for property-related functions +- Added: overloaded functions for opGetProperties*() for byte-keys +- Fixed: Realtime Demo keypress in input-fields have been used as in-game actions, too +- Changed: Realtime Demo game-name is now that of the native samples ("play" with other platform SDKs) +- Changed: Silverlight SDK has a different port in the constants NPeer.URL_RT_SERVER* and .IP_RT_SERVER* (as Silverlight uses TCP port 4350) + +*** Version 5.4.1 +- Added: missing documentation in Unity3d SDK + +*** Version 5.4.0 +- Change: The timespan until a sent and unacknowledged reliable command is considered lost, is now calculated by + current roundTripTime + 4 * roundTripTimeVariance + The result of this calculation is doubled with every following resend. The maximum number of retries can still be defined by calling SetSentCountAllowance. +- Change: Removed TimeAllowanceInt +- Change: removed surplus debug out, adjusted levels for other, output of command sent-time from hex to decimal +- Added: fragmentation support: bigger data is now placed into multiple packages and reassembled +- Internal: command-buffers are replaced with CommandList and SortedCommandList (major change, but fully internal) +- Fixed: possibility of command buffer overflow. now everything is stored and warnings are used as hint for temporary problems +- Added: property NPeer.IncomingReliableCommands, which returns the count of reliable commands currently queued +- Added: callback on NCommand.CT_DISCONNECT to inform the NPeerListener about a disconnect from server (see above) +- Added: disconnect command will be sent by server in case of timeout, connection-limitations or other issues +- Added: NPeer ReturnCode.DisconnectByServer is called on server-side disconnect (see description) +- Added: call to StopConnection() on disconnect (by server) +- Added: NPeer.PeerID property to get ENet's peerID (useful while debugging) +- Internal: SupportClass.WriteIntToByteArray() to ease writing ints to byte[] +- Internal: added several values to NCommand to store fragments +- Added: support for channels. read more about this in the documentation +- Added: NPeer.ChannelCount which sets the number of channels while not connected (default: 2) +- Changed: opRaiseEvent() and opCustom() now optionally have a channel parameter +- Added: Photon properties functions to NPeer (available with Photon Server SDK v1.5.0) and doc +- Added: LiteEventKey.SetProperties = 92 for broadcasted property set +- Added: LiteOpKey.Broadcast = 13 and .Properties = 12 +- Added: LiteEventKey.TargetActorNr = 10 (actorNr the properties are attached to) and .Properties = 12 (changed properties) + + +*** Version 5.3.11 +- Change: all bytes sent to and from server are treated as unsigned bytes (standard for c#). same for byte-arrays +- Change: updated realtime demo to use int for posx,posy but still sending just a byte-value (the field is 16x16, after all) + +*** Version 5.3.10 +- Change: switched from sbyte-array to byte-array in de/serialization! important: bytes (ev-keys etc) are sbyte. arrays of bytes are unsigned (on client and server) +- Change: NeutronListener functions getShadowReturn() and HasbadwordsReturn() now have byte-array return values. please adjust, even if you don't use those +- Internal: changed SupportClass for Compact Framework +- Internal: getting ticks sitched from expensive "System.DateTime.Now.Ticks / 10000" to cheap "Environment.TickCount" +- Change: Unity lib will now give more debug out if serialisation fails + +*** Version 5.3.9 +- Fixed: result-queue, timeouts and customOps work also fine for Unity build again (were broken due to Neutron Unity webplayer compatibility changes in 5.3.8 for Unity) +- Fixed: if the browser is closed and the unity webplayer immediatly can't use http anymore, Neutron now informs the application via NetworkStatusReturn() + +*** Version 5.3.8 +- Fixed: Neutron Unity now also works fine in webplayer -> Neutron and Photon now both support all platforms of Unity und Unity iPhone +- Fixed: default value for parameter encrypt of NeutronGame::RaiseEvent() now is false like for all other RaiseEvent methods and like on all other platforms, instead of true, as it was before + +*** Version 5.3.7 +- Fixed: .Net UDP issue, where standard MTU settings caused dropped UDP packages +- Internal: refactored ACK queue to arraylist + +*** Version 5.3.6 +- Fixed: NPeer issue with ACKs for repeated commands. this enhances handling of lost packages +- Changed: NPeer.opJoin() no longer needs the SID + +*** Version 5.3.5 +- Known issues: to use Photon on iPhone device, you do need Unity iPhone 1.0.2b1 or higher (current official release is 1.0.1, so please ask for a prerelease or wait until next official release), but of course you can use Photon with Unity iPhone 1.0.1 IDE +- Merged: renamed .NET 1.1 NeutronUnity3DiPhone into NeutronUnity3D to replace the old .NET 2.0 lib of that name, which means, that you can use the same .NET 1.1 based lib for Unity and for Unity iPhone now, since 1.1 cpmpatibility fixes are all done now +- Fixed: photon is fully compatible to .NET 1.1 now +- Internal: optimized UDP package size in Unity3D library (was sending surplus bytes, which were ignored) +- Fixed: NPeer.opCustom() now sends the operation given by parameter +- Changed: IP_RT_SERVER points to new server IP of udp.exitgames.com +- Changed: a new NeutronSession now clears the NetworkLoss state and the sendQueue +- Changed: timeout of a HTTP request to 10 seconds. it triggers + +*** Version 5.3.4 +- Added: prealpha Unity3DiPhone version of Neutron .NET: core lib already functional, but realtime part not usable on device yet +- Internal: there are 4 different versions of Neutron.NET now: + - Full .NET: .NET 2.0 based, with asnyc realtime part + - Compact Framework: .NET 2.0 based, with threaded realtime part + - Unity3D: .NET 2.0 based, with Unity www-class based http part and threaded realtime part + - Unity3DiPhone: .NET 1.1 based, with Unity www-class based http part and threaded realtime part + +*** Version 5.3.3 +- New: ReturnCode.RC_RT_EXCEPTION_CONNECT, which covers the cases where a server is not running +- New: NPeer can now be created with UDP or TCP (by new bool parameter) +- Change: renamed most of the constants for NPeer (in INPeerListener structs) +- Note: TCP does not offer ServerTime or RoundTripTime jet + +*** Version 5.3.2 +- Internal: reverted to threaded model in NConnect (as async UDP is not supported by Unity3D) + +*** Version 5.3.1 +- New: login(), register(), customOperation() and raiseEvent() (all variants) can be encrypted with additional parameter "encrypt" (overloaded function) +- New: encryption uses HTTPs as transfer, by changing the "http:" url to a "https:" url +- New: returnCode for failure of encrypted HTTPs requests: RC_SSL_AUTHENTICATION_FAILED (if certificate is not found, valid or expired) +- Fixed: Realtime Demo using the older Realtime Server + +*** Version 5.3.0 +- New: separated libraries into "Compact Framework" (CF) and "Regular Framework" (no name postfix) +- Change: libraries are now in "libs" folder as debug/release and in libs/CompactFramework debug/release +- Change: libs default URL set to EU/Test. use setServerURL() with Neutron.URL_NEUTRON_* for other Neutron instances +- Internal: lib now uses async UDP communication now with "regular" framework +- Added: properties serverTimeInMilliSeconds, serverTimeAsTimeSpan and serverTimeAsDateTime for getting the current server time +- Removed: serverTimeOffset is now internal only and removed from the API (was only needed to calculate the servertime by yourself, before neutron could do this for you) +- Change: debug out for realtime classes is now layered +- Change: debug level NPeer.DebugOut is now a NPeer.DebugLevel enum and will include all lower levels in output, default: DebugLevel.ERROR +- Fixed: size of realtime demo board +- Change: NPeer constructor now always throws an exception if listener is null +- Change: EventAction() parameter eventCode is now of type sbyte (was int), which corresponds to type of RaiseEvent (and server-side used type) +- Internal: NPeer.opRaiseEvent() now treats eventCode as parameter of operation RaiseEvent (as changed in latest RT server) +- Change: NPeer has its own listener (INPeerListener) and several (better named) structs for the constants used with NPeer / realtime +- Added: LiteOpKey and LiteOpKey.ActorNumber to have a constant for the only OP key of interest +- Change: EventAction() always returns the complete event, which contains a code, the ActorNumber (if any given) and data from raiseEvent (see below) +- Change: in custom events, the data from opRaiseEvent() is in placed as value of key: LiteEventKey.EV_RT_KEY_DATA. to get the data use: Hashtable data = (Hashtable)neutronEvent[LiteEventKey.EV_RT_KEY_DATA]; + +*** Version 5.2.0 +- changed library filename to neutron-lib__.dll with server "test" and "run" (no debug out) and billing "dummy" and "none" +- removed US build of library. please use NeutronSession.SetServerUrl() and the constants: Neutron.URL_NEUTRON_SERVER_*. + +*** Version 5.1.0 +- added realtime classes to DotNet library: ported NPeer (and internal: NCommand and NConnect) classes +- added nPeerReturn() to NeutronListener interface +- added constants for realtime returnCodes (RC_RT_*): RC_RT_CONNECT, RC_RT_DISCONNECT and RC_RT_EXCEPTION +- added constants for realtime eventCodes (EV_RT_*) +- added constants for Neutron servers to Neutron class: URL_NEUTRON_* +- added Reamtime Demo +- updated samples +- added test for UDP to NUnit + +*** Version 5.0.1 +- New: operation Spectate (including new SpectateReturn) to get events from any game (as admin) +- New: SetServerUrl and SetCustomServerUrl now return the URL to debugReturn +- Internal: constant "DEBUG_- InternalS" to be used for intern debugging output + +*** Version 5.0.0 +- New: hasBadwords() as OP and return. Server side check of strings for badwords + +*** Version 5.0.0 RC3 +- Internal: changed constant values: EV_KEY_PROPERTIES = "Data", EV_KEY_REVISION = "Rev" +- New: EV_KEY_CHANNELTYPE for channel-type in property-change events +- New: constants for default channels, CHANNEL_APPLICATION_LONGLIFE, CHANNEL_ACTOR_SHORTLIFE, CHANNEL_ACTOR_LONGLIFE and CHANNEL_APPINSTANCE +- Change: operations that fail due to missing moderation-rights now return RC_MODERATION_DENIED instead of RC_COMMAND_ACCESS_DENIED +- Change: actor-properties can no longer be broadcasted in any way - removed "broadcast" parameter from setActorProperties() +- Change: properties now have a revision which is increased on each change. this way outdated updates might be skipped +- Change: parameters of GetPropertiesReturn(). property-type is replaced by channel. added revision +- Change: EV_PROPERTIES_CHANGE now has a key EV_KEY_OWNERNR if it's a "player property" (the key is missing if it's a game-property) +- Internal: changed setProperties and getProperties to new operation-codes using different parameters (with similar results) +- New: parameter "textMessage" for NeutronGame.invite() adds personal message to invited players (in EV_INV and gameListInvitations()) +- New: key EV_KEY_INFO will be added to EV_INV if "textMessage" was used in NeutronGame.invite() (it's not there otherwise) +- New: gameListInvitations() has new value parameter {t} to get "textMessage" from NeutronGame.invite() +- New: RC_APPINSTANCE_NOT_OPEN is now used for "singleton namebased pools" where a game is full (not able to join / instanciate) +- New: gameCreate() with invitations will fail if the chosen game-name is already taken in a "singleton namebased pool" +- New: RC_APPINSTANCE_ALREADY_EXISTS for the case above + +*** Version 5.0.0 RC2 +- Change: gameCreateReturn() now returns RC_APPINSTANCE_NOT_OPEN (instead of RC_AI_TOO_MANY_ACTORSESSIONS) for full games in "singleton" pools +- Change: obsolete events EV_TURN, EV_TXTMSG and EV_DATA which could be sent by raiseEvent*() and still handled +- Change: switched Neutron URLs to "[..].neutron5.[..]" for test/run libs +- Fix: Polling (getEvents operation) again calls sendGameDataReturn() for all errors (as intended for v4.9.2 already) +- New: constant NeutronListener.EV_KEY_TYPE as part of event EV_BUDDNOTICE + +*** Version 5.0.0 RC1 +- New: RaiseEvent (all functions of this name) now has a "filter" parameter. If filter is true, all String-typed values in an event are badword filtered +- Change: signature of NeutronGame.raiseEvent(), NeutronGame.raiseEventInChannel(), NeutronSession.raiseEventInChannel(), NeutronSession.raiseEventForActor() start with: byte eventCode, Hashtable event, boolean filter +- Change: signature of NeutronSession.raiseEventForActor() is changed to "byte eventCode, Hashtable eventData, boolean filter, String userID, int minutesValid, byte maxTypeCount, byte channel" +- Change: NeutronGame.doModerate() is now isModerator() +- Change: moved GpOperation.SerializeData() and GpOperation.DeserializeData() to Neutron.SerializeData() and Neutron.DeserializeData(). +- New: errorCode RC_INVALID_TARGET and RC_PARAMETER_NOT_SUPPLIED added as constant. + +*** Version 4.9.3 +- New: Errors constants in NeutronListener: RC_FATAL_LOGIC, RC_MATCHMAKING_NOT_COMPLETED, RC_CHANNEL_ACCESS_VIOLATION +- New: for game-creation you can now reserve "spots", which are not filled up by Neutron matchmaking. players can be invited to fill the spots, or they can be deblocked later on +- New: Parameter reservedSpots in NeutronSession.gameCreate() +- New: NeutronGame.setReservedSpots() to modify the number of reserved slots (to make them available to matchmaking again, or block/reserve them) +- New: event EV_RESERVED_SPOTS will update the NeutronGame.reservedSpots value after a call to NeutronGame.setReservedSpots() +- New: NeutronSession.listBannedPlayers() gives you the list of banned players for a known game - only usable by "admin" users +- New: NeutronSession.unbanPlayer() is a modified "kick" operation which allows the respective user to join a certain game again - only usable by "admin" users +- New: the event invitation includes now the game's name (in the new key EV_KEY_NAME) +- New: NeutronSession.gameListPerPool() has now three options to sort the results: by game-name, player-count or "persistent games first" +- Removed: NeutronGame: handoverTurn(), sendData(), sendTextMsg(), getEventHistory() and getEventHistoryReturn(). Obsolete events: EV_TURN, EV_TXTMSG, EV_DATA. Session: getScorePosition()+getScorePositionReturn() +- Update: release_history.txt was updated from v4.0. All changes up to v4.0.4 are added to v4.9.3 + +*** Version 4.9.2 +- New: Players can be admins (by list of logins on server) or moderator (by being the first active player of a game) +- New: Players may request and become moderator for game: NeutronSession.gameCanModerate(boolean), NeutronSession.canModerate, NeutronGame.doModerate() and NeutronGame.moderatorActorNr +- Change: the new value NeutronSession.canModerate will be sent with gameCreate() operations (if set to true) +- New: Event key NeutronListener.EV_KEY_MODERATOR to get moderator's actorNr from events +- Change: EV_QUIT and EV_KICKED now carry the new key EV_KEY_MODERATOR which tells all players who is the current moderator (by actorNr); this is stored into NeutronGame.moderatorActorNr +- New: Players in NeutronGame can have new state PLAYER_KICKED (player-data is updated with EV_KICKED) +- New: NeutronGame.kickPlayer() (for moderators) and NeutronSession.kickPlayer() (for admin's who are not active in the game to shutdown) +- New: NeutronSession.shutdownGame() can be used by admin-players (for others, this operation will fail) +- New: Namebased pools can now be defined as "singleton": only one instance per pool and name will be created; if such a game is full players get an error instead of a new game +- New: Errors constants in NeutronListener: RC_ACTORSESSION_KICKED, RC_ACTORSESSION_BANNED, RC_APPINSTANCE_CLOSED, RC_ACTORSESSION_ALREADY_JOINED +- Change: NeutronGame.raiseEvent() accepts a "targetActorNr" which defines a single player to get the raised event; leave 0 to target "all players in game" (as before) +- New: NeutronGame.quitLocally() to release a NeutronGame instance locally (without having to quit()); used after a player was kicked or game shutdown +- Update: NeutronGame.playerGetCount() is updated to simply count all active or inactive players (excluding quit and kicked ones) +- Internal: NeutronGame constructor reads new parameter: P_MODERATOR +- Change: Polling (getEvents operation) now calls sendGameDataReturn() for all errors (not just RC_ACTORSESSION_EXPIRED and RC_ACTORSESSION_NOT_FOUND); takes care of kicked/banned errors +- Fix: Fatal server errors cause a returnCode of NeutronListener.RC_OP_SERVER again; debug test-server libs print out debug text! (during development fatal errors could happen in case of not matching client/server setups) +- Change: removed (already deprecated) NeutronListener.gameListPerPoolReturn() +- Change / Internal: canModerate is sent as Byte (not bool) as in definition; Code: if ( canModerate ) op.addParameter(Neutron.P_MODERATOR , new Byte((byte)1)); +- Add: NeutronGame.PLAYER_KICKED is now listed in JavaDoc for NeutronGame.playerGetStatus() +- Update: JavaDoc package.html, gameCreateReturn(), gamesListReturn(), EV_DEACTIVATE, kickPlayer(), quitLocally(), RC_ACTORSESSION_KICKED, RC_ACTORSESSION_BANNED, RC_APPINSTANCE_CLOSED, RC_ACTORSESSION_ALREADY_JOINED +- Added: Event EV_STATUS (50) includes a key EV_KEY_ISADMIN if the current player has administrator rights; the value is (byte)1 in that case. The key does not exist in any other case (normal users) +- Update: JavaDoc gameCreateReturn; +- New: Added constant RC_APPINSTANCE_NOT_FOUND = 137 for shutdownGameReturn() +- Fix: serializable datatypes are now completely listed in NeutronSession JavaDoc +- New: Constant for property-change events: EV_PROPERTIES_CHANGE including new keys: EV_KEY_PROPERTY_TYPE, EV_KEY_PROPERTIES, EV_KEY_ISDIFF +- Update: JavaDoc for properties in NeutronSession + +*** Version 4.1.1 +- Fix: gameListPerPool() defaults to 10 games and no offset if the values are less than 1 +- Fix: gamesListReturn() JavaDoc description for "listType" is now: 0 = open games; 1 = invitations; 2 = pool's open games list +- Update: gameListPerPool() sends "{gn}" as values-parameter if it's null +- Update: getPropertiesReturn() gets new parameters: actorNr, userID. These are optional and are available in certain situations only. See JavaDoc +- Update: gameListPerPoolReturn() is now deprecated and merged into gamesListReturn() which in turn got a "type" to identify the list-type +- New: getListBuddyIgnore() got one more value: 't'. This requests the type of relation to users. useful when getting lists of type "both". this is buddies and ignores. +- Change: renamed returned parameters to: count and countOnline. These values are referring to the number in the returned list +- Internal: parameter P_USERID = 85; used in getProperties +- New: made methods nullpointer resistant: getListBuddyIgnore, buddySet, get/set PlayerProperties, get/set ActorProperties, get/set GameProperties; some methods throw exceptions in debug version + +*** Version 4.1.0 +- New: Properties. NeutronSession: setActorProperties(), getActorProperties(). NeutronGame: setLocalPlayerProperties(), getPlayerProperties(), getGameProperties(), setGameProperties() +- New: Buddylist and Ignorelist in NeutronSession: listBuddies(), listIgnored(), getListBuddyIgnore(), buddySet() +- New: Listing of games per pool in NeutronSession: NeutronSession gameListPerPool() +- New: Games with password (only usable for named games) +- Internal: Changed parameter in buddySet from P_STATUS to P_TYPE + +*** Version 4.0.4 +- Change: NeutronGame.handoverTurn() and NeutronGame.sendData() are now getting a Hashtable parameter instead of Object +- New: RC_ACTORSESSION_BUSY (121) constant to help identify common development error! check in gameCreateReturn() + +*** Version 4.0.3 +- New: RC_INVALID_CONNECTIONSTRING (74) constant to help identify a common error! check in loginReturn() +- Update: list of serializable datatypes in NeutronSession JavaDoc +- Fix: Fatal server errors cause a returnCode of NeutronListener.RC_OP_SERVER again; debug test-server libs print out debug text! (during development fatal errors could happen in case of not matching client/server setups) + +*** Version 4.0.2 +- Internal: Neutron.deserializeData() now returns either the P_DATA part of the deserialized data (if available / serialized by serializeData()) or the resulting hashtable itself + +*** Version 4.0.1 +- New: NConnectSE connects to server defined by parameter: ipPort (before: fixed host) +- New: SE version is now completely independent from Java ME classes (were not used, but had to be present) +- Fix: Changed versioning for "ClientLibVersion" in Login/CC +*** Version 4.0.0.0 + +- Removed methods: + - NeutronSession.BuggyGetList - replaced by new GetListBuddyIgnore method; + - NeutronSession.ReSubscribe; + - NeutrinSession.ConfirmBilling; + - NeutronListener.ResubscribeReturn; + +- Added methods: + - NeutronSession.GameCreateNamed with password parameter; + - NeutronSession.GameListPerPool; + - NeutronSession.GetActorProperties; + - NeutronSession.SetActorProperties; + - NeutronSession.GetListBuddyIgnore - replaces removed BuggyGetList; + - NeutronSession.ListBuddies; + - NeutronSession.ListIgnore; + - NeutronSession.BillingInitPayment; + - NeutronSession.BillingProcessPayment; + - NeutronGame.Invite; + - NeutronGame.GetGameProperties; + - NeutronGame.SetGameProperties; + - NeutronGame.GetPlayerProperties; + - NeutronGame.SetLocatPlayerProperties; + - NeutronListener.GameInviteReturn; + - NeutronListener.GetPropertiesReturn; + - NeutronListener.SetPropertiesReturn; + +- Changed argument list: + - NeutronSession.GameCreate - added password parameter; + - NeutronListener.GamesListReturn added listType parameter; + - NeutronListener.BuddyGetListReturn all buddy related info now in passing in one strings array parrameter; + - NeutronListener.BuddySetReturn added type parameter; + - NeutronListener.BillingInitPaymentReturn; + + +- added constants: + - OPC_INVITE + - OPC_TELLAFRIEND + - OPC_LISTGAMES + - OPC_SETPROPERTIES + - OPC_GETPROPERTIES + - P_USERID + - P_RESERVE + - P_RESULT + - P_PROPERTIES + - P_BROADCAST + - P_ISDIFF + - RCB_CHARGING_ERROR + - RCB_POST_CHARGING_ERROR + - RCB_TIMEOUT + - RCB_PRICE_- Changed + - RCB_PRICE_INVALID + - RCB_FATAL_SERVER_ERROR + - RCB_FATAL_LOGIC_ERROR + - RCB_NOT_INCLUDED + - RCB_WMA_UNAVAILABLE + +*** Version 3.0.2.2 +- CLS-specifications largely corrected + +*** Version 3.0.1.1 +- changes in neutron-java-lib integrated + +*** +- Removed: NeutronGame: playerNames, playerIDs, playerLobbies, playerStats +- Change: removed GpOperation.roundtripTime, now using public Neutron.roundtripTime + to be sent in operation headers (GpOperation.serializeParameters(), internal) +- Change: channelRaiseEvent() is now raiseEventInChannel() and gets the eventCode + as seperate parameter value - analog to raiseEventForActor() +- Fix: renamed EV_KEY_M_MIPLAYERS to EV_KEY_M_MINPLAYERS (number of min players of game, before start) +- Fix: values for EV_KEY_M_MINPLAYERS and EV_KEY_M_MAXPLAYERS corrected (wrong case so far) +- Changed: Neutron.millisecondsToWait (current value of polling-interval) is now + set in Neutron.receiveResponse() for login, register and alike \ No newline at end of file diff --git a/Assets/Photon/PhotonLibs/changes-library.txt.meta b/Assets/Photon/PhotonLibs/changes-library.txt.meta new file mode 100644 index 00000000..800c31a7 --- /dev/null +++ b/Assets/Photon/PhotonLibs/changes-library.txt.meta @@ -0,0 +1,6 @@ +fileFormatVersion: 2 +guid: d8040d310df77714a90a561261bfb2cb +DefaultImporter: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/PhotonLibs/netstandard2.0.meta b/Assets/Photon/PhotonLibs/netstandard2.0.meta new file mode 100644 index 00000000..a5019beb --- /dev/null +++ b/Assets/Photon/PhotonLibs/netstandard2.0.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7bffb42c242be434cae63f6d3a210a6d +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/PhotonLibs/netstandard2.0/Photon3Unity3D.dll b/Assets/Photon/PhotonLibs/netstandard2.0/Photon3Unity3D.dll new file mode 100644 index 00000000..31b17ce4 Binary files /dev/null and b/Assets/Photon/PhotonLibs/netstandard2.0/Photon3Unity3D.dll differ diff --git a/Assets/Photon/PhotonLibs/netstandard2.0/Photon3Unity3D.dll.meta b/Assets/Photon/PhotonLibs/netstandard2.0/Photon3Unity3D.dll.meta new file mode 100644 index 00000000..e507b85b --- /dev/null +++ b/Assets/Photon/PhotonLibs/netstandard2.0/Photon3Unity3D.dll.meta @@ -0,0 +1,165 @@ +fileFormatVersion: 2 +guid: 6a558ae793155af4b9b9ab945fc64a0f +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + '': Any + second: + enabled: 0 + settings: + Exclude Android: 0 + Exclude Editor: 0 + Exclude GameCoreScarlett: 0 + Exclude GameCoreXboxOne: 0 + Exclude Linux: 0 + Exclude Linux64: 0 + Exclude LinuxUniversal: 0 + Exclude Lumin: 0 + Exclude OSXUniversal: 0 + Exclude PS4: 0 + Exclude PS5: 0 + Exclude Switch: 0 + Exclude WebGL: 0 + Exclude Win: 0 + Exclude Win64: 0 + Exclude WindowsStoreApps: 0 + Exclude XboxOne: 0 + Exclude iOS: 0 + Exclude tvOS: 0 + - first: + Android: Android + second: + enabled: 1 + settings: + CPU: ARMv7 + - first: + Any: + second: + enabled: 1 + settings: {} + - first: + Editor: Editor + second: + enabled: 1 + settings: + CPU: AnyCPU + DefaultValueInitialized: true + OS: AnyOS + - first: + Facebook: Win + second: + enabled: 0 + settings: + CPU: AnyCPU + - first: + Facebook: Win64 + second: + enabled: 0 + settings: + CPU: AnyCPU + - first: + Standalone: Linux + second: + enabled: 1 + settings: + CPU: x86 + - first: + Standalone: Linux64 + second: + enabled: 1 + settings: + CPU: AnyCPU + - first: + Standalone: LinuxUniversal + second: + enabled: 1 + settings: + CPU: AnyCPU + - first: + Standalone: OSXUniversal + second: + enabled: 1 + settings: + CPU: AnyCPU + - first: + Standalone: Win + second: + enabled: 1 + settings: + CPU: AnyCPU + - first: + Standalone: Win64 + second: + enabled: 1 + settings: + CPU: AnyCPU + - first: + WebGL: WebGL + second: + enabled: 1 + settings: {} + - first: + Windows Store Apps: WindowsStoreApps + second: + enabled: 1 + settings: + CPU: AnyCPU + DontProcess: false + PlaceholderPath: + SDK: AnySDK + ScriptingBackend: Il2Cpp + - first: + iPhone: iOS + second: + enabled: 1 + settings: + AddToEmbeddedBinaries: false + CompileFlags: + FrameworkDependencies: + - first: + PS4: PS4 + second: + enabled: 1 + settings: {} + - first: + PS5: PS5 + second: + enabled: 1 + settings: {} + - first: + Nintendo Switch: Switch + second: + enabled: 1 + settings: {} + - first: + XboxOne: XboxOne + second: + enabled: 1 + settings: {} + - first: + GameCoreScarlett: GameCoreScarlett + second: + enabled: 1 + settings: {} + - first: + GameCoreXboxOne: GameCoreXboxOne + second: + enabled: 1 + settings: {} + - first: + Lumin: Lumin + second: + enabled: 1 + settings: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Photon/PhotonLibs/netstandard2.0/Photon3Unity3D.xml b/Assets/Photon/PhotonLibs/netstandard2.0/Photon3Unity3D.xml new file mode 100644 index 00000000..569a3d2c --- /dev/null +++ b/Assets/Photon/PhotonLibs/netstandard2.0/Photon3Unity3D.xml @@ -0,0 +1,2818 @@ + + + + Photon3Unity3D + + + + + Initializes a new instance of the class. + + + + + Gets the public key that can be used by another DiffieHellmanCryptoProvider object + to generate a shared secret agreement. + + + + + Derives the shared key is generated from the secret agreement between two parties, + given a byte array that contains the second party's public key. + + + The second party's public key. + + + + Interface for Photon's DiffieHellman/Payload Encryption. + + + + Provides classical Diffie-Hellman Modular Exponentiation Groups defined by the + OAKLEY Key Determination Protocol (RFC 2412). + + + + + Gets the genrator (N) used by the the well known groups 1,2 and 5. + + + + + Gets the 768 bit prime for the well known group 1. + + + + + Gets the 1024 bit prime for the well known group 2. + + + + + Gets the 1536 bit prime for the well known group 5. + + + + A slice of memory that should be pooled and reused. Wraps a byte-array. + + This is a serializable datatype for the .Net clients. It will serialize and transfer as byte[]. + If PhotonPeer.UseByteArraySlicePoolForEvents is enabled, byte-arrays in (incoming) events will be deserialized as + ByteArraySlice. + + Adjust your OnEvent code accordingly. + + + + The buffer for the slice. + + + The position where the content starts in Buffer. + + + The length of the data in the Buffer. + + + + Internal constructor - these instances will be part of the pooling system. + + The pool to return to. + The index to return to (in the related returnPool). + + + + Create a new ByteArraySlice. The buffer supplied will be used. Usage is similar to ArraySegment. + + Not part of pooling. + + + + Creates a ByteArraySlice, which is not pooled. It has no Buffer. + + Not part of pooling. + + + + If this item was fetched from a ByteArraySlicePool, this will return it. + + + True if this was a pooled item and it successfully was returned. + If it does not belong to a pool nothing will happen, and false will be returned. + + + + Resets Count and Offset to 0 each. + + + Tiered pool for ByteArraySlices. Reduces the allocations once enough slices are available. + + + + Requests for buffers smaller than 2^minStackIndex will use 2^minStackIndex. This value avoids allocations of smaller rarely used buffers. + Set this to a lower value if you will never need sizes larger than byte[2^minStackIndex] + + + + Count of allocations this pool did. + + + Creates a new pool. + + + + Get a ByteArraySlice from pool. This overload handles user supplied byte[] and byte count and can be used as a non-boxing alternative to ArraySegment<byte>. + + + This effectively pools the ByteArraySlice instances but not their data. + ByteArraySlice.Release() will return the slice itself to the pool but delete the reference to the buffer supplied here. + + + + + Get byte[] wrapper from pool. This overload accepts a bytecount and will return a wrapper with a byte[] that size or greater. + + + + Pops a slice from the stack or creates a new slice for that stack. + The stack to use. Lock that stack before calling PopOrCreate for thread safety. + + A slice. + + + + Releasing a ByteArraySlice, will put it back into the pool, if it was acquired from one. + + The ByteArraySlice to return to the pool. + The stackIndex for this slice. + True if this slice was returned to some pool. False if not (null or stackIndex < 0. + + + + Clears all pool items with byte array sizes between lower and upper inclusively. + + + Use this if you sent some unusually large RaiseEvents and believe the buffers of that size + will not be needed again, and you would like to free up the buffer memory. + + + + + Replacement for Dictionary<K,V> which does not allocate memory during usage. + + Key type. + Value type. + + + + This is a substitute for the Hashtable class, missing in: Win8RT and Windows Phone. It uses a Dictionary<object,object> as base. + + + Please be aware that this class might act differently than the Hashtable equivalent. + As far as Photon is concerned, the substitution is sufficiently precise. + + + + + Translates the byte key into the pre-boxed byte before doing the lookup. + + + + + + + Creates a shallow copy of the Hashtable. + + + A shallow copy of a collection copies only the elements of the collection, whether they are + reference types or value types, but it does not copy the objects that the references refer + to. The references in the new collection point to the same objects that the references in + the original collection point to. + + Shallow copy of the Hashtable. + + + + Used as container for unknown types the client could not deserialize. + + + + + The type code which was read for this type. + + + + + The size/length value that was read for this type. + + May be larger than Data.Length, if the Size exceeded the remaining message content. + + + + Container for the data that arrived. + + If the Size exceeded the remaining message length, only the remaining data is read. This may be null, if the size was somehow less than 1. + + + Interface for DatagramEncryptor implementations. + + + Initialize the encryptor. + + + + Encryption/decryption algorithm implementation + + + + + Packet authentication algorithm impelmenation + + + + Number for reliable unsequenced commands (separate from "standard" reliable sequenced). Used to avoid duplicates. + + + The highest number of reliable unsequenced commands that arrived (and all commands before). + + + Any reliable unsequenced number that's been received, which is higher than the current highest in complete sequence (reliableUnsequencedNumbersCompletelyReceived). + + + To store the highest acknowledged sequence number (and get some impression what the server already received and stored). + + + Count of reliable commands sent and not yet acknowledged. + + + The lowest sequence number sent but not acknowledged yet. This defines the sequence delta "window" for sending subsequent reliable commands. + + + Checks and queues incoming reliable unsequenced commands ("send" or "fragment"), if they haven't been received yet. + The command to check and queue. + True if the command is new and got queued (or could be executed/dispatched). + + + Quick Resends are suspended if the sent queue is this size or larger. + + + One list for all channels keeps sent commands (for re-sending). + + + Thread safe. + + + One pool of ACK byte arrays ( 20 bytes each) for all channels to keep acknowledgements. + + + Gets enabled by "request" from server (not by client). + + + Initial PeerId as used in Connect command. If EnableServerTracing is false. + + + Initial PeerId to enable Photon Tracing, as used in Connect command. See: EnableServerTracing. + + + + Checks the incoming queue and Dispatches received data if possible. + + If a Dispatch happened or not, which shows if more Dispatches might be needed. + + + Gets the target size for fragments. + + Caches the result for a specific MTU value. + Fragment length is different, when datagram encryption is used (so this caches two values in fact). + + + + + + gathers acks until udp-packet is full and sends it! + + + + Queue of received commands. ReceiveIncomingCommands will queue commands, except ACKs which Execute immediately. + + + + gathers commands from all (out)queues until udp-packet is full and sends it! + + + + Used to store temporary values in UpdateSendWindow(). + + + + Checks connected state and channel before operation is serialized and enqueued for sending. + + if operation could be enqueued + + + reliable-udp-level function to send some byte[] to the server via un/reliable command + only called when a custom operation should be send + the invocation ID for this operation (the payload) + + + reads incoming udp-packages to create and queue incoming commands* + + + Queues incoming commands in the correct order as either unreliable, reliable or unsequenced. + If queued or not. + + + removes commands which are acknowledged + + + + Enumeration of situations that change the peers internal status. + Used in calls to OnStatusChanged to inform your application of various situations that might happen. + + + Most of these codes are referenced somewhere else in the documentation when they are relevant to methods. + + + + the PhotonPeer is connected.
See {@link PhotonListener#OnStatusChanged}*
+
+ + the PhotonPeer just disconnected.
See {@link PhotonListener#OnStatusChanged}*
+
+ + the PhotonPeer encountered an exception and will disconnect, too.
See {@link PhotonListener#OnStatusChanged}*
+
+ + Exception while opening the incoming connection to the server. Followed by Disconnect. + The server could be down / not running or the client has no network or a misconfigured DNS.
See {@link PhotonListener#OnStatusChanged}*
+
+ + Used on platforms that throw a security exception on connect. Unity3d does this, e.g., if a webplayer build could not fetch a policy-file from a remote server. + + + Sending command failed. Either not connected, or the requested channel is bigger than the number of initialized channels. + + + Exception, if a server cannot be connected. Followed by Disconnect. + Most likely, the server is not responding. Ask user to try again later. + + + Disconnection due to a timeout (client did no longer receive ACKs from server). Followed by Disconnect. + + + Timeout disconnect by server. The server didn't receive necessary ACKs in time. Followed by Disconnect. + + + Disconnect by server due to concurrent user limit reached (received a disconnect command). + + + (1043) Disconnect by server due to server's logic. Followed by Disconnect. + + + Disconnect by server due to unspecified reason. Followed by Disconnect. + + + (1048) Value for OnStatusChanged()-call, when the encryption-setup for secure communication finished successfully. + + + (1049) Value for OnStatusChanged()-call, when the encryption-setup failed for some reason. Check debug logs. + + + + Callback interface for the Photon client side. Must be provided to a new PhotonPeer in its constructor. + + + These methods are used by your PhotonPeer instance to keep your app updated. Read each method's + description and check out the samples to see how to use them. + + + + + Provides textual descriptions for various error conditions and noteworthy situations. + In cases where the application needs to react, a call to OnStatusChanged is used. + OnStatusChanged gives "feedback" to the game, DebugReturn provies human readable messages + on the background. + + + All debug output of the library will be reported through this method. Print it or put it in a + buffer to use it on-screen. Use PhotonPeer.DebugOut to select how verbose the output is. + + DebugLevel (severity) of the message. + Debug text. Print to System.Console or screen. + + + + Callback method which gives you (async) responses for called operations. + + + Similar to method-calling, operations can have a result. + Because operation-calls are non-blocking and executed on the server, responses are provided + after a roundtrip as call to this method. + + Example: Trying to create a room usually succeeds but can fail if the room's name is already + in use (room names are their IDs). + + This method is used as general callback for all operations. Each response corresponds to a certain + "type" of operation by its OperationCode. + + + + When you join a room, the server will assign a consecutive number to each client: the + "actorNr" or "player number". This is sent back in the operation result. + + Fetch your actorNr of a Join response like this: + int actorNr = (int)operationResponse[(byte)OperationCode.ActorNr]; + + The response to an operation\-call. + + + + OnStatusChanged is called to let the game know when asynchronous actions finished or when errors happen. + + + Not all of the many StatusCode values will apply to your game. Example: If you don't use encryption, + the respective status changes are never made. + + The values are all part of the StatusCode enumeration and described value-by-value. + + A code to identify the situation. + + + + Called whenever an event from the Photon Server is dispatched. + + + Events are used for communication between clients and allow the server to update clients anytime. + The creation of an event is often triggered by an operation (called by this client or an other). + + Each event carries a Code plus optional content in its Parameters. + Your application should identify which content to expect by the event's Code. + + Events can be defined and modified server-side. + + If you use the LoadBalancing api as basis, several events like EvJoin and EvLeave are pre-defined. + The LoadBalancing api provides the EventCode and ParameterCode classes for pre-defined events. + + Photon also allows you to come up with custom events on the fly, purely client-side. + To do so, use OpRaiseEvent. + + Events are incoming messages and as such buffered in the peer. + Calling PhotonPeer.DispatchIncomingCommands will call IPhotonPeerListener.OnEvent, to hand over received events. + + PhotonPeer.ReuseEventInstance is an option to optimize memory usage by reusing one EventData instance. + + The event currently being dispatched. + + + Data wasn't sent yet. + + + Data is being sent async (still in use). + + + The protocol for this socket, defined in constructor. + + + Socket implementations should store the SocketErrorCode here, if anything happens. + + + Address, as defined via a Connect() call. Including protocol, port and or path. + This is set in the constructor and in Connect() again. Typically the address does not change after the IPhotonSocket is instantiated. + + + Contains only the server's hostname (stripped protocol, port and or path). Set in IPhotonSocket.Connect(). + + + Contains the IP address of the previously resolved ServerAddress (or empty, if GetIpAddress wasn't used). + + + Contains only the server's port address (as string). Set in IphotonSocket.Connect(). + + + Where available, this exposes if the server's address was resolved into an IPv6 address or not. + + + + Provides the protocol string, of the current PhotonPeer.SerializationProtocolType to be used for WebSocket connections. + + + Any WebSocket wrapper could access this to get the desired binary protocol for the connection. + Some WebSocket implementations use a static value of the same name and need to be updated. + + The value is not cached and each call will create the needed string on the fly. + + + + + Separates the given address into host (name or IP), port, scheme and path. This is more about splitting the parts, than detecting invalid cases. + + + The out scheme may be empty for IP addresses (UDP and TCP). For these protocols, the url should include a port. + IPv6 addresses must use brackets to separate address from port. + + Examples: + ns.exitgames.com:5058 + http://[2001:db8:1f70::999:de8:7648:6e8]:100/ + [2001:db8:1f70::999:de8:7648:6e8]:100 + See: + http://serverfault.com/questions/205793/how-can-one-distinguish-the-host-and-the-port-in-an-ipv6-url + + + + Wraps a DNS call to provide an array of addresses, sorted to have the IPv6 ones first. + + This skips a DNS lookup, if the hostname is an IPv4 address. Then only this address is used as is. + The DNS lookup may take a while, so it is recommended to do this in a thread. Also, it may fail entirely. + + + IPAddress array for hostname, sorted to put any IPv6 addresses first.
+ If the DNS lookup fails, HandleException(StatusCode.ExceptionOnConnect) gets called and null returned. + Then the socket should not attempt to connect. +
+
+ + + Returns null or the IPAddress representing the address, doing Dns resolution if needed. + + Only returns IPv4 or IPv6 adresses, no others. + The string address of a server (hostname or IP). + IPAddress for the string address or null, if the address is neither IPv4, IPv6 or some hostname that could be resolved. + + + Variants of the Photon specific serialization protocol used for operations, responses, events and data. + + + Version 1.6 (outdated). + + + Version 1.8. + + + + + + + Serialize creates a byte-array from the given object and returns it. + + The object to serialize + The serialized byte-array + + + + Deserialize returns an object reassembled from the given StreamBuffer. + + The buffer to be Deserialized + The Deserialized object + + + + Deserialize returns an object reassembled from the given byte-array. + + The byte-array to be Deserialized + The Deserialized object + + + + Interface for (UDP) traffic capturing. + + + + Indicates if the PhotonPeer should call Record or not. + + + Implement to record network traffic. Called by PhotonPeer for each UDP message sent and received. + + The buffer will not contain Ethernet Header, IP, UDP level data. Only the payload received by the client. + + It is advised to not use NetworkSimulation when recording traffic. + The recording is done on the timing of actual receive- and send-calls and internal simulation would offset the timing. + + Buffer to be sent or received. Check length value for actual content length. + Length of the network data. + Indicates incoming (true) or outgoing (false) traffic. + The local peerId for the connection. Defaults to 0xFFFF until assigned by the Server. + The currently used IPhotonSocket of this Peer. Enables you to track the connection endpoint. + + + Internal class for "commands" - the package in which operations are sent. + + + Size of the Payload, which may be null. + + + Checks commandFlags & FV_UNRELIABLE_UNSEQUENCED. + + + Checks commandFlags & FV_RELIABLE. + + + + ACKs should never be created as NCommand. use CreateACK to wrtie the serialized ACK right away... + + + + + + + + + this variant does only create outgoing commands and increments . incoming ones are created from a DataInputStream + + + this variant does only create outgoing commands and increments . incoming ones are created from a DataInputStream + + + reads the command values (commandHeader and command-values) from incoming bytestream and populates the incoming command* + + + + A simulation item is an action that can be queued to simulate network lag. + + + + With this, the actual delay can be measured, compared to the intended lag. + + + Timestamp after which this item must be executed. + + + Action to execute when the lag-time passed. + + + Starts a new Stopwatch + + + + A set of network simulation settings, enabled (and disabled) by PhotonPeer.IsSimulationEnabled. + + + For performance reasons, the lag and jitter settings can't be produced exactly. + In some cases, the resulting lag will be up to 20ms bigger than the lag settings. + Even if all settings are 0, simulation will be used. Set PhotonPeer.IsSimulationEnabled + to false to disable it if no longer needed. + + All lag, jitter and loss is additional to the current, real network conditions. + If the network is slow in reality, this will add even more lag. + The jitter values will affect the lag positive and negative, so the lag settings + describe the medium lag even with jitter. The jitter influence is: [-jitter..+jitter]. + Packets "lost" due to OutgoingLossPercentage count for BytesOut and LostPackagesOut. + Packets "lost" due to IncomingLossPercentage count for BytesIn and LostPackagesIn. + + + + internal + + + internal + + + internal + + + internal + + + internal + + + internal + + + internal + + + This setting overrides all other settings and turns simulation on/off. Default: false. + + + Outgoing packages delay in ms. Default: 100. + + + Randomizes OutgoingLag by [-OutgoingJitter..+OutgoingJitter]. Default: 0. + + + Percentage of outgoing packets that should be lost. Between 0..100. Default: 1. TCP ignores this setting. + + + Incoming packages delay in ms. Default: 100. + + + Randomizes IncomingLag by [-IncomingJitter..+IncomingJitter]. Default: 0. + + + Percentage of incoming packets that should be lost. Between 0..100. Default: 1. TCP ignores this setting. + + + Counts how many outgoing packages actually got lost. TCP connections ignore loss and this stays 0. + + + Counts how many incoming packages actually got lost. TCP connections ignore loss and this stays 0. + + + Provides an overview of the current values in form of a string. + String summary. + + + + The pool this wrapper should return to when released/disposed. + + + + + Gets value and if it belongs to the static pool, returns the wrapper to pool. + + + + + + Boxes the value and returns boxed object. Releases the wrapper. + + + + + + Removes this WrapperStruct from pooling. + + + + Returns a String which represents the value of this instance. + String which represents the value of this instance. + + + Returns a String which represents the type (in brackets and value of this instance. + String which represents the type (in brackets) and value of this instance. + + + + staticPool is used for implicit casting. This is not threadsafe, so casting between T and StructWrapper should only be done on the Unity main thread. + + + + + Replacement for object.GetType() that first checks to see if object is a WrappedStruct. + If so returns the StructWrapper T type, otherwise just returns object.GetType(). + + + + + + + Wrap a struct in a pooled StructWrapper. + + + + + Wrap a struct in a pooled StructWrapper. Pulls wrapper from the static pool. Wrapper is returned to pool when Unwrapped. + Slighty faster version of Wrap() that is hard wired to pull from the static pool. Use the persistant bool argument to make a permanent unpooled wrapper. + + + + + Tests if object is either a cast T, or a wrapped T + + + + + Remove all wrappers in hashtable from pooling, so they can remain cached and used later. + + + + + + Unwraps any WrapperStructs, boxes their value, releases hashtable entry with the boxed value. Releases the wrappers. + + + + + If object is a StructWrapper, the value will be extracted. If not, the object will be cast to T. + Wrapper is returned to the wrapper pool if applicable, so it is not considered safe to Unwrap multiple times, as the wrapper may be recycled. + + + + + If object is a StructWrapper, the value will be extracted. If not, the object will be cast to T. + Wrapper is will not be returned to its pool until it is Unwrapped, or the pool is cleared. + + + + + If object is a StructWrapper, the value will be extracted. If not, the object will be cast to T. + Wrapper is returned to the wrapper pool if applicable, so it is not considered safe to Unwrap multiple times, as the wrapper may be recycled. + + + + + If object is a StructWrapper, the value will be extracted. If not, the object will be cast to T. + Wrapper is returned to the wrapper pool if applicable, so it is not considered safe to Unwrap multiple times, as the wrapper may be recycled. + + + + + If object is a StructWrapper, the value will be extracted. If not, the object will be cast to T. + + + + + If object is a StructWrapper, the value will be extracted. If not, the object will be cast to T. + + + + + If object is a StructWrapper, the value will be extracted. If not, the object will be cast to T. + Wrapper is returned to the wrapper pool if applicable, so it is not considered safe to Unwrap multiple times, as the wrapper may be recycled. + + + + + If object is a StructWrapper, the value will be extracted. If not, the object will be cast to T. + Wrapper is returned to the wrapper pool if applicable, so it is not considered safe to Unwrap multiple times, as the wrapper may be recycled. + + + + + If object is a StructWrapper, the value will be extracted. If not, the object will be cast to T. + Wrapper is will not be returned to its pool until it is Unwrapped, or the pool is cleared. + + + + + + + + + + + Will get the object using the key. If the key is invalid, will return null. + + + + + + Dictionary content as string. + If true, type-info is also included. + Full content of dictionary as string. + + + Param code. Used in internal op: InitEncryption. + + + Encryption-Mode code. Used in internal op: InitEncryption. + + + Param code. Used in internal op: InitEncryption. + + + Code of internal op: InitEncryption. + + + TODO: Code of internal op: Ping (used in PUN binary websockets). + + + Result code for any (internal) operation. + + + + This is the replacement for the const values used in eNet like: PS_DISCONNECTED, PS_CONNECTED, etc. + + + + No connection is available. Use connect. + + + Establishing a connection already. The app should wait for a status callback. + + + + The low level connection with Photon is established. On connect, the library will automatically + send an Init package to select the application it connects to (see also PhotonPeer.Connect()). + When the Init is done, IPhotonPeerListener.OnStatusChanged() is called with connect. + + Please note that calling operations is only possible after the OnStatusChanged() with StatusCode.Connect. + + + Connection going to be ended. Wait for status callback. + + + Acknowledging a disconnect from Photon. Wait for status callback. + + + Connection not properly disconnected. + + + The server's address, as set by a Connect() call, including any protocol, ports and or path. + If rHTTP is used, this can be set directly. + + + + This is the (low level) connection state of the peer. It's internal and based on eNet's states. + + Applications can read the "high level" state as PhotonPeer.PeerState, which uses a different enum. + + + Byte count of last sent operation (set during serialization). + + + Byte count of last dispatched message (set during dispatch/deserialization). + + + The command that's currently being dispatched. + + + This ID is assigned by the Realtime Server upon connection. + The application does not have to care about this, but it is useful in debugging. + + + + The serverTimeOffset is serverTimestamp - localTime. Used to approximate the serverTimestamp with help of localTime + + + + + Count of all bytes going out (including headers) + + + + + Count of all bytes coming in (including headers) + + + + Set via Connect(..., customObject) and sent in Init-Request. + + + Sent on connect in an Init Request. + + + Temporary cache of AppId. Used in Connect() to keep the AppId until we send the Init-Request (after the network-level (and Enet) connect). + + + Set to timeInt, whenever SendOutgoingCommands actually checks outgoing queues to send them. Must be connected. + + + Maximum Transfer Unit to be used for UDP+TCP + + + If IPhotonSocket.Connected is true, this value shows if the server's address resolved as IPv6 address. + + You must check the socket's IsConnected state. Otherwise, this value is not initialized. + Sent to server in Init-Request. + + + + + Writes and "Init Request", which initializes the connection / application used server-side. + + Uses this.ServerAddress, this.AppId, this.PhotonToken and CustomInitData and some more values. + Bytes of the init request. + + + Called when the server's Init Response arrived. + + + Serializes an operation into our binary messages (magic number, msg-type byte and message). Optionally encrypts. + This method is mostly the same in EnetPeer, TPeer and HttpPeerBase. Also, for raw messages, we have another variant. + + + Serializes an operation into our binary messages (magic number, msg-type byte and message). Optionally encrypts. + This method is mostly the same in EnetPeer, TPeer and HttpPeerBase. Also, for raw messages, we have another variant. + + + Returns the UDP Payload starting with Magic Number for binary protocol + + + + Checks outgoing queues for commands to send and puts them on their way. + This creates one package per go in UDP. + + If commands are not sent, cause they didn't fit into the package that's sent. + + + + Checks the incoming queue and Dispatches received data if possible. + + If a Dispatch happened or not, which shows if more Dispatches might be needed. + + + + Internally uses an operation to exchange encryption keys with the server. + + If the op could be sent. + + + + Gets the currently used settings for the built-in network simulation. + Please check the description of NetworkSimulationSet for more details. + + + + + Core of the Network Simulation, which is available in Debug builds. + Called by a timer in intervals. + + + + EnetPeer will set this value, so trafficstats can use it. TCP has 0 bytes per package extra + + + See PhotonPeer value. + + + See PhotonPeer value. + + + See PhotonPeer value. + + + See PhotonPeer value. + + + + Value range for a Peer's connection and initialization state, as returned by the PeerState property. + + + While this is not the same as the StatusCode of IPhotonPeerListener.OnStatusChanged(), it directly relates to it. + In most cases, it makes more sense to build a game's state on top of the OnStatusChanged() as you get changes. + + + + The peer is disconnected and can't call Operations. Call Connect(). + + + The peer is establishing the connection: opening a socket, exchanging packages with Photon. + + + The connection is established and now sends the application name to Photon. + You set the "application name" by calling PhotonPeer.Connect(). + + + The peer is connected and initialized (selected an application). You can now use operations. + + + The peer is disconnecting. It sent a disconnect to the server, which will acknowledge closing the connection. + + + + These are the options that can be used as underlying transport protocol. + + + + Use UDP to connect to Photon, which allows you to send operations reliable or unreliable on demand. + + + Use TCP to connect to Photon. + + + A TCP-based protocol commonly supported by browsers.For WebGL games mostly. Note: No WebSocket IPhotonSocket implementation is in this Assembly. + This protocol is only available in Unity exports to WebGL. + + + A TCP-based, encrypted protocol commonly supported by browsers. For WebGL games mostly. Note: No WebSocket IPhotonSocket implementation is in this Assembly. + This protocol is only available in Unity exports to WebGL. + + + + Level / amount of DebugReturn callbacks. Each debug level includes output for lower ones: OFF, ERROR, WARNING, INFO, ALL. + + + + No debug out. + + + Only error descriptions. + + + Warnings and errors. + + + Information about internal workflows, warnings and errors. + + + Most complete workflow description (but lots of debug output), info, warnings and errors. + + + Build target framework supported by this dll. + + + + Instances of the PhotonPeer class are used to connect to a Photon server and communicate with it. + + + A PhotonPeer instance allows communication with the Photon Server, which in turn distributes messages + to other PhotonPeer clients. + An application can use more than one PhotonPeer instance, which are treated as separate users on the + server. Each should have its own listener instance, to separate the operations, callbacks and events. + + + + + No effect anymore. Removed without replacement. + + + This value was used to get/set the initial capacities of command-lists. + These grow on demand but knowing their capacity is of very limited use. + Also, various command-lists grow their capacity independent from one another. + + + + + No effect anymore. Removed without replacement. + + + This was used to skip some received (and buffered) unreliable commands, to avoid situations + where the peer has aggregated a lot of (old) messages. + + + + + The WarningSize was used test all message queues for congestion. + + + + + Gets a local timestamp in milliseconds by calling SupportClass.GetTickCount(). + See LocalMsTimestampDelegate. + + + + Where dynamic linking is available, this library will attempt to load a native Photon "Encryptor" plugin library for "Datagram Encryption". + Fallback to a managed implementation. This value is always true. + + + Obsolete and ignored. Size of CommandLog. Default is 0, no logging. + + + Obsolete and ignored. Converts the CommandLog into a readable table-like string with summary. + + + False if this library build contains C# Socket code. If true, you must set some type as SocketImplementation before connecting. + + + True if the library was compiled with DEBUG setting. + + + Version of the Native Encryptor API compiled into this assembly. Defines which PhotonEncryptorPlugin needs to be used. + + + Target framework this dll was built for. + + + Global toggle to avoid callbacks from native plugins. Defaults to false, meaning: "callbacks enabled". + Callbacks from native code will fail on some platforms, which is why you can disable them. + + + Can be used to remove/hide the AppId from websocket connect paths. + + + A simplified identifier for client SDKs. Helps debugging. Only Photon APIs should set this. + + + For the Init-request, we shift the ClientId by one and the last bit signals a "debug" (0) or "release" build (1). + + + Version of this library as string. + + + Version of this library as string. + + + A Native Socket implementation is no longer part of this DLL but delivered in a separate add-on. This value always returns false. + + + Native Payload Encryption is no longer part of this DLL but delivered in a separate add-on. This value always returns false. + + + Native Datagram Encryption is no longer part of this DLL but delivered in a separate add-on. This value always returns false. + + + Enables selection of a (Photon-)serialization protocol. Used in Connect methods. + Defaults to SerializationProtocol.GpBinaryV16; + + + Optional definition of IPhotonSocket type per ConnectionProtocol. + + Several platforms have special Socket implementations and slightly different APIs. + Customizing the SocketImplementationConfig helps to accomodate this. + By default, UDP and TCP have socket implementations assigned. + + If a native socket plugin is available set the SocketNativeSource class as Type definition here. + + You only need to set the SocketImplementationConfig once, after creating a PhotonPeer + and before connecting. If you switch the TransportProtocol, the correct implementation is being used. + + + + + Can be used to read the IPhotonSocket implementation at runtime (before connecting). + + + Use the SocketImplementationConfig to define which IPhotonSocket is used per ConnectionProtocol. + + + + Provides access to a SocketError code (if available) after a call to OnStatusChanged with a Socket-level exception. + Useful for debugging send / receive error cases. + + + + Sets the level (and amount) of debug output provided by the library. + + + This affects the callbacks to IPhotonPeerListener.DebugReturn. + Default Level: Error. + + + + + Gets the IPhotonPeerListener of this instance (set in constructor). + Can be used in derived classes for Listener.DebugReturn(). + + + + + Called when the client received a Disconnect Message from the server. Signals an error and provides a message to debug the case. + + + + + Option to make the PhotonPeer reuse a single EventData instance for all incoming events. + + + This reduces memory garbage. + If enabled, the event provided via OnEvent(EventData photonEvent) is invalid once the callback finished. + That event's content will get modified. Typically this is not a problem as events are rarely cached. + + Changing this value acquires the same lock that DispatchIncomingCommands() uses. + + + + + Enables a deserialization optimization for incoming events. Defaults to false. + + + When enabled, byte-arrays in incoming Photon events are deserialized into pooled ByteArraySlice instances (wrappers for byte[]). + This improves the memory footprint for receiving byte-arrays in events. + + When used, you have to release the (pooled) ByteArraySlice instances. + + Adjust your handling of EventData accordingly: + + The ByteArraySlice.Buffer will usually be bigger than the send/received byte-array. + Check the ByteArraySlice.Count and read only the actually received bytes. + The Buffer is reused and not cleared. The Offset will be 0 for incoming events. + + Important: + While the peer will acquire the ByteArraySlice and passes it to OnEvent, the game code has to call ByteArraySlice.Release() + when the slice is no longer needed. + + Send either byte[], ArraySegment or use the ByteArraySlicePool to acquire ByteArraySlices to send. + + + + + Incoming struct types are wrapped in a pooled IWrapperStruct, rather than being cast to object. This eliminated allocations and garbage collection from boxing, + however object that are wrapped structs will need to be cast to WrapperStruct<T> and their values extracted with (obj as WrapperStruct<T>).Value. + + + + Instance of a ByteArraySlicePool. UseByteArraySlicePoolForEvents defines if this PhotonPeer is using the pool for deserialization of byte[] in Photon events. + + ByteArraySlice is a serializable datatype of the Photon .Net client library. + It helps avoid allocations by being pooled and (optionally) used in incoming Photon events (see: UseByteArraySlicePoolForEvents). + + You can also use the pool to acquire ByteArraySlice instances for serialization. + RaiseEvent will auto-release all ByteArraySlice instances passed in. + + + + + This debug setting enables a new send-ordering for commands. Defaults to true and commands are sent in the order they are created. Set to false to use Enet ordering. + + + + Defines how far ahead the client can go sending commands (on UDP connections), compared to the acknowledged sequence number. + + This avoids spamming the receiver and puts focus on resending commands that are older and are needed on the receiver side to dispatch commands. + + When acks arrive, the SendWindow will move forward and newer commands get sent. + This indirectly affects unreliable commands as they are by default sent in creation order. + It queues more on the client side than on the server. + + + + + Gets count of all bytes coming in (including headers, excluding UDP/TCP overhead) + + + + + Gets count of all bytes going out (including headers, excluding UDP/TCP overhead) + + + + + Gets the size of the dispatched event or operation-result in bytes. + This value is set before OnEvent() or OnOperationResponse() is called (within DispatchIncomingCommands()). + + + Get this value directly in OnEvent() or OnOperationResponse(). Example: + void OnEvent(...) { + int eventSizeInBytes = this.peer.ByteCountCurrentDispatch; + //... + + void OnOperationResponse(...) { + int resultSizeInBytes = this.peer.ByteCountCurrentDispatch; + //... + + + + Returns the debug string of the event or operation-response currently being dispatched or string. Empty if none. + In a release build of the lib, this will always be empty. + + + + Gets the size of the last serialized operation call in bytes. + The value includes all headers for this single operation but excludes those of UDP, Enet Package Headers and TCP. + + + Get this value immediately after calling an operation. + Example: + + this.loadbalancingClient.OpJoinRoom("myroom"); + int opjoinByteCount = this.loadbalancingClient.ByteCountLastOperation; + + + + If set, the TrafficRecorder will be used to capture all traffic. + + If null or not Enabled, the recorder is not being used. + Release builds of this library will never record traffic for performance reasons. + + See ITrafficRecorder docs. + + + + + Debugging option to tell the Photon Server to log all datagrams. + + + + + Up to 4 resend attempts for a reliable command can be done in quick succession (after RTT+4*Variance). + + + By default 0. Any later resend attempt will then double the time before the next resend. + Max value = 4; + Make sure to adjust SentCountAllowance to a slightly higher value, as more repeats will get done. + + + + + This is the (low level) state of the connection to the server of a PhotonPeer. Managed internally and read-only. + + + Don't mix this up with the StatusCode provided in IPhotonListener.OnStatusChanged(). + Applications should use the StatusCode of OnStatusChanged() to track their state, as + it also covers the higher level initialization between a client and Photon. + + + + + This peer's ID as assigned by the server or 0 if not using UDP. Will be 0xFFFF before the client connects. + + Used for debugging only. This value is not useful in everyday Photon usage. + + + + Count of all currently received but not-yet-Dispatched reliable commands + (events and operation results) from all channels. + + + + + Count of all commands currently queued as outgoing, including all channels and reliable, unreliable. + + + + + Sets a new (temporary) size of the MessageBufferPool to reuse memory where possible. + + + The MessageBufferPool is a Queue<StreamBuffer> for performance reasons. + This methods dequeues from the MessageBufferPool to get the Count equal to countOfBuffers, + then it calls MessageBufferPool.TrimExcess(). + + New size of the pool. Clears the pool if <= 0. + + + + Gets / sets the number of channels available in UDP connections with Photon. + Photon Channels are only supported for UDP. + The default ChannelCount is 2. Channel IDs start with 0 and 255 is a internal channel. + + + + + Enables the client so send the "encrypted" flag on secure connections. Incompatible with Server SDK 4.x. + + + + + While not connected, this controls if the next connection(s) should use a per-package CRC checksum. + + + While turned on, the client and server will add a CRC checksum to every sent package. + The checksum enables both sides to detect and ignore packages that were corrupted during transfer. + Corrupted packages have the same impact as lost packages: They require a re-send, adding a delay + and could lead to timeouts. + + Building the checksum has a low processing overhead but increases integrity of sent and received data. + Packages discarded due to failed CRC cecks are counted in PhotonPeer.PacketLossByCrc. + + + + + Count of packages dropped due to failed CRC checks for this connection. + + + + + + Count of packages dropped due to wrong challenge for this connection. + + + + + Gets the count of sent but not yet acknowledged commands (for UDP connections). + + + + + Count of commands that got repeated (due to local repeat-timing before an ACK was received). + + + + + Number of send retries before a peer is considered lost/disconnected. Default: 7. + + + The initial timeout countdown of a command is calculated by the current roundTripTime + 4 * roundTripTimeVariance. + Please note that the timeout span until a command will be resent is not constant, but based on + the roundtrip time at the initial sending, which will be doubled with every failed retry. + + DisconnectTimeout and SentCountAllowance are competing settings: either might trigger a disconnect on the + client first, depending on the values and Roundtrip Time. + + + + + Caps the initial timing for repeats of reliable commands. In milliseconds. Default: 400ms. + + + Unless acknowledged, reliable commands are repeated initially after: current roundTripTime + 4 * roundTripTimeVariance. + + As this value can be very high when there was exceptional lag, InitialResendTimeMax makes sure that commands + get repeated several times before they may trigger a timeout. + + + + + Sets the time between pings being sent automatically. They measure the roundtrip time and keep connections from closing. Default: 1000. + + + For Photon's reliable UDP connections, pings are skipped if any reliable command was sent during the specified TimePingInterval. + Any reliable command is used to update the RoundTripTime and RoundTripTimeVariance. + + When using TCP and WebSockets, the ping is of interest to measure the roundtrip and to keep a connection open, should nothing else + With those two protocols, the ping is used to update the RoundTripTime and RoundTripTimeVariance. + + + + + Time in milliseconds before any sent reliable command triggers a timeout disconnect, unless acknowledged by the receiver. Default: 10000. + + + DisconnectTimeout is not an exact value for a timeout. The exact timing of the timeout depends on the frequency + of Service() calls and the roundtrip time. Commands sent with long roundtrip-times and variance are checked less + often for re-sending. + + DisconnectTimeout and SentCountAllowance are competing settings: either might trigger a disconnect on the + client first, depending on the values and Roundtrip Time. + + Default: 10000 ms. + Setting a negative value will apply the default timeout. + + + + + Approximated Environment.TickCount value of server (while connected). + + + UDP: The server's timestamp is automatically fetched after connecting (once). This is done + internally by a command which is acknowledged immediately by the server. + TCP: The server's timestamp fetched with each ping but set only after connecting (once). + + The approximation will be off by +/- 10ms in most cases. Per peer/client and connection, the + offset will be constant (unless FetchServerTimestamp() is used). A constant offset should be + better to adjust for. Unfortunately there is no way to find out how much the local value + differs from the original. + + The approximation adds RoundtripTime / 2 and uses this.LocalTimeInMilliSeconds to calculate + in-between values (this property returns a new value per tick). + + The value sent by Photon equals Environment.TickCount in the logic layer. + + + 0 until connected. + While connected, the value is an approximation of the server's current timestamp. + + + + + This setter for the (local-) timestamp delegate replaces the default Environment.TickCount with any equal function. + + + + The internally used per PhotonPeer time value. + + Returns the integer part of a Stopwatch ElapsedMilliseconds value. + If the PhotonPeer runs continuously the ClientTime will increment from zero to Int32..::.MaxValue + for approximately 24.9 days, then jump to Int32..::.MinValue (a negative number), then increment + back to zero during the next 24.9 days. + + It is recommended to use this int only for delta times, to avoid handling the overflow. + + + + The last ConnectionTime value, when some ACKs were sent out by this client. + Only applicable to UDP connections. + + + The last ConnectionTime value, when SendOutgoingCommands actually checked outgoing queues to send them. Must be connected. + Available for UDP and TCP connections. + + + Measures the maximum milliseconds spent in PhotonSocket.Send(). + + + Time until a reliable command is acknowledged by the server. + + The value measures network latency and for UDP it includes the server's ACK-delay (setting in config). + In TCP, there is no ACK-delay, so the value is slightly lower (if you use default settings for Photon). + + RoundTripTime is updated constantly. Every reliable command will contribute a fraction to this value. + + This is also the approximate time until a raised event reaches another client or until an operation + result is available. + + + + + Changes of the roundtriptime as variance value. Gives a hint about how much the time is changing. + + + + The last measured roundtrip time for this connection. + + + + Timestamp of the last time anything (!) was received from the server (including low level Ping, ACKs, events and operation-returns). + + + This is not the time when something was dispatched. If you enable NetworkSimulation, this value is affected as well. + + + + + The server address which was used in PhotonPeer.Connect() or null (before Connect() was called). + + + + Contains the IP address of the previously resolved ServerAddress (or empty, if address wasn't resolved with the internal methods). + + + The protocol this peer is currently connected/connecting with (or 0). + + + This is the transport protocol to be used for next connect (see remarks). + The TransportProtocol can be changed anytime but it will not change the + currently active connection. Instead, TransportProtocol will be applied on next Connect. + + + + + Gets or sets the network simulation "enabled" setting. + Changing this value also locks this peer's sending and when setting false, + the internally used queues are executed (so setting to false can take some cycles). + + + + + Gets the settings for built-in Network Simulation for this peer instance + while IsSimulationEnabled will enable or disable them. + Once obtained, the settings can be modified by changing the properties. + + + + + Defines the initial size of an internally used StreamBuffer for Tcp. + The StreamBuffer is used to aggregate operation into (less) send calls, + which uses less resources. + + + The size is not restricting the buffer and does not affect when outgoing data is actually sent. + + + + + The Maximum Transfer Unit (MTU) defines the (network-level) packet-content size that is + guaranteed to arrive at the server in one piece. The Photon Protocol uses this + size to split larger data into packets and for receive-buffers of packets. + + + This value affects the Packet-content. The resulting UDP packages will have additional + headers that also count against the package size (so it's bigger than this limit in the end) + Setting this value while being connected is not allowed and will throw an Exception. + Minimum is 576. Huge values won't speed up connections in most cases! + + + + + This property is set internally, when OpExchangeKeysForEncryption successfully finished. + While it's true, encryption can be used for operations. + + + + + While true, the peer will not send any other commands except ACKs (used in UDP connections). + + + + Defines if Key Exchange for Encryption is done asynchronously in another thread. + + + Indicates if sequence numbers should be randomized. + + + Initialization array, used to modify the sequence numbers of channels. + + + If GCM is used for DatagramEncryption. + If true, the randomization-value gets added to the current value, else (CBC/old style) the randomization-value replaces the current value. + + + + Gets the byte-count of incoming "low level" messages, which are either Enet Commands or Tcp Messages. + These include all headers, except those of the underlying internet protocol Udp or Tcp. + + + + + Gets the byte-count of outgoing "low level" messages, which are either Enet Commands or Tcp Messages. + These include all headers, except those of the underlying internet protocol Udp or Tcp. + + + + + Gets a statistic of incoming and outgoing traffic, split by operation, operation-result and event. + + + Operations are outgoing traffic, results and events are incoming. + Includes the per-command header sizes (Udp: Enet Command Header or Tcp: Message Header). + + + + + Returns the count of milliseconds the stats are enabled for tracking. + + + + + Enables or disables collection of statistics in TrafficStatsIncoming, TrafficStatsOutgoing and TrafficstatsGameLevel. + + + Setting this to true, also starts the stopwatch to measure the timespan the stats are collected. + Enables the traffic statistics of a peer: TrafficStatsIncoming, TrafficStatsOutgoing and TrafficstatsGameLevel (nothing else). + Default value: false (disabled). + + + + + Creates new instances of TrafficStats and starts a new timer for those. + + + + + Creates new TrafficStats values and the related Stopwatch instance. To be called when the peer is created / reset. + + + + + Returns a string of the most interesting connection statistics. + When you have issues on the client side, these might contain hints about the issue's cause. + + If true, Incoming and Outgoing low-level stats are included in the string. + Stats as string. + + + Implements the message-protocol, based on the underlying network protocol (udp, tcp, http). + + + Setter for the Payload Encryptor type. Used for next connection. + + If null, the PhotonPeer will create a DiffieHellmanCryptoProvider, which is the default. + This is only needed in rare cases, where using native payload encryption makes sense. + + Get in touch about this, if you got questions: developer@photonengine.com + + + + PayloadEncryption Secret. Message payloads get encrypted with it individually and on demand. + + + Setter for the Datagram Encryptor type. Used at next connect. + + If null, the PhotonPeer will create a default datagram encryptor instance. + + + + The datagram encryptor used for the current connection. Applied internally in InitDatagramEncryption. + + + Count of unreliable commands being discarded in case this client already dispatched a command that was newer (higher sequence number). + + + Set per dispatch in DispatchIncomingCommands to: commandUnreliableSequenceNumber - channel.incomingUnreliableSequenceNumber. Indicates how big the (sequence)gap is, compared to the last dispatched unreliable command. + + + Creates a new PhotonPeer with specified transport protocol (without a IPhotonPeerListener). + Make sure to set the Listener, before using the peer. + + + + Creates a new PhotonPeer instance to communicate with Photon and selects the transport protocol. We recommend UDP. + + a IPhotonPeerListener implementation + Protocol to use to connect to Photon. + + + + Starts connecting to the given Photon server. Non-blocking. + + + Connecting to the Photon server is done asynchronous. + Unless an error happens right away (and this returns false), wait for the call of IPhotonPeerListener.OnStatusChanged. + + + Address of a Photon server as IP:port or hostname. WebSocket connections must contain a scheme (ws:// or wss://). + + + The ID of the app to use. Typically this is a guid (for the Photon Cloud). Max 32 characters. + + + Optional custom data to be used by server during peer creation. + If used for authentication, the server is able to reject a client without creating a peer. + Must be a serializable data type of Photon. + + Custom data to send to the server in the Init request. Might be used to identify a client / user. + + True if a connection attempt will be made. False if some error could be detected early-on. + + + + + Starts connecting to the given Photon server. Non-blocking. + + + Connecting to the Photon server is done asynchronous. + Unless an error happens right away (and this returns false), wait for the call of IPhotonPeerListener.OnStatusChanged. + + + Address of a Photon server as IP:port or hostname. WebSocket connections must contain a scheme (ws:// or wss://). + + + Optional address of a proxy server. Only used by WebSocket connections. Set null to use none. + + + The ID of the app to use. Typically this is a guid (for the Photon Cloud). Max 32 characters. + + + Optional Photon token data to be used by server during peer creation. + If used for authentication, the server is able to reject a client without creating a peer. + Must be of type string or byte[] (as provided by server). + + Custom data to send to the server in the Init request. Might be used to identify a client / user. + + True if a connection attempt will be made. False if some error could be detected early-on. + + + + + This method initiates a mutual disconnect between this client and the server. + + + Calling this method does not immediately close a connection. Disconnect lets the server + know that this client is no longer listening. For the server, this is a much faster way + to detect that the client is gone but it requires the client to send a few final messages. + + On completion, OnStatusChanged is called with the StatusCode.Disconnect. + + If the client is disconnected already or the connection thread is stopped, then there is no callback. + + The default server logic will leave any joined game and trigger the respective event. + + + + + This method immediately closes a connection (pure client side) and ends related listening Threads. + + + Unlike Disconnect, this method will simply stop to listen to the server. Udp connections will timeout. + If the connections was open, this will trigger a callback to OnStatusChanged with code StatusCode.Disconnect. + + + + + This will fetch the server's timestamp and update the approximation for property ServerTimeInMilliseconds. + + + The server time approximation will NOT become more accurate by repeated calls. Accuracy currently depends + on a single roundtrip which is done as fast as possible. + + The command used for this is immediately acknowledged by the server. This makes sure the roundtrip time is + low and the timestamp + rountriptime / 2 is close to the original value. + + + + + This method creates a public key for this client and exchanges it with the server. + + + Encryption is not instantly available but calls OnStatusChanged when it finishes. + Check for StatusCode EncryptionEstablished and EncryptionFailedToEstablish. + + Calling this method sets IsEncryptionAvailable to false. + This method must be called before the "encrypt" parameter of OpCustom can be used. + + If operation could be enqueued for sending + + + + Initializes Datagram Encryption. Optionally, the EncryptorType is being used, if set. + + Secret used to cipher udp packets. + Secret used for authentication of udp packets. + Sets if enet Sequence Numbers will be randomized or not. Preferably should be true. + Sets if the chaining mode should be CBC (false, default) or GCM (true). GCM mode is only available with a native encryption plugin. + + + + Photon's Payload Encryption secret may be set by a response from the server. + + The secret in form of a byte[]. + + + + This method excutes DispatchIncomingCommands and SendOutgoingCommands in your application Thread-context. + + + The Photon client libraries are designed to fit easily into a game or application. The application + is in control of the context (thread) in which incoming events and responses are executed and has + full control of the creation of UDP/TCP packages. + + Sending packages and dispatching received messages are two separate tasks. Service combines them + into one method at the cost of control. It calls DispatchIncomingCommands and SendOutgoingCommands. + + Call this method regularly (2..20 times a second). + + This will Dispatch ANY remaining buffered responses and events AND will send queued outgoing commands. + Fewer calls might be more effective if a device cannot send many packets per second, as multiple + operations might be combined into one package. + + + You could replace Service by: + + while (DispatchIncomingCommands()); //Dispatch until everything is Dispatched... + SendOutgoingCommands(); //Send a UDP/TCP package with outgoing messages + + + + + + + Creates and sends a UDP/TCP package with outgoing commands (operations and acknowledgements). Also called by Service(). + + + As the Photon library does not create any UDP/TCP packages by itself. Instead, the application + fully controls how many packages are sent and when. A tradeoff, an application will + lose connection, if it is no longer calling SendOutgoingCommands or Service. + + If multiple operations and ACKs are waiting to be sent, they will be aggregated into one + package. The package fills in this order: + ACKs for received commands + A "Ping" - only if no reliable data was sent for a while + Starting with the lowest Channel-Nr: + Reliable Commands in channel + Unreliable Commands in channel + + This gives a higher priority to lower channels. + + A longer interval between sends will lower the overhead per sent operation but + increase the internal delay (which adds "lag"). + + Call this 2..20 times per second (depending on your target platform). + + The if commands are not yet sent. Udp limits it's package size, Tcp doesnt. + + + + Dispatching received messages (commands), causes callbacks for events, responses and state changes within a IPhotonPeerListener. + + + DispatchIncomingCommands only executes a single received + command per call. If a command was dispatched, the return value is true and the method + should be called again. + + This method is called by Service() until currently available commands are dispatched. + In general, this method should be called until it returns false. In a few cases, it might + make sense to pause dispatching (if a certain state is reached and the app needs to load + data, before it should handle new events). + + The callbacks to the peer's IPhotonPeerListener are executed in the same thread that is + calling DispatchIncomingCommands. This makes things easier in a game loop: Event execution + won't clash with painting objects or the game logic. + + + + + Prepares your operation (code and parameters) to be sent to the Photon Server with specified SendOptions. + + + This method serializes and enqueues the operation right away while the actual sending happens later. + To be able to aggregate operations/messages, the Photon client sends packages only when you call SendOutgoingCommands(). + + The sendOptions specify how the operation gets sent exactly. + Keep in mind that some transport protocols don't support unreliable or unsequenced transport. + In that case, the sendOptions might be ignored. + + The operationCode must be known by the server's logic or won't be processed. + In almost all cases, sending an operation will result in a OperationResponse (see: IPhotonPeerListener.OnOperationResponse). + + Operations are handled by their byte\-typed code. The codes are defined in the Realtime API (a.k.a. LoadBalancing API). + Containing parameters as key\-value pair. The key is byte\-typed, while the value is any serializable datatype. + Wraps up DeliveryMode (reliability), Encryption and Channel values for sending. + If operation could be enqueued for sending. + + + + Registers new types/classes for de/serialization and the fitting methods to call for this type. + + + SerializeMethod and DeserializeMethod are complementary: Feed the product of serializeMethod to + the constructor, to get a comparable instance of the object. + + After registering a Type, it can be used in events and operations and will be serialized like + built-in types. + + Type (class) to register. + A byte-code used as shortcut during transfer of this Type. + Method delegate to create a byte[] from a customType instance. + Method delegate to create instances of customType's from byte[]. + If the Type was registered successfully. + + + + Container for an Operation request, which is a code and parameters. + + + On the lowest level, Photon only allows byte-typed keys for operation parameters. + The values of each such parameter can be any serializable datatype: byte, int, hashtable and many more. + + + + Byte-typed code for an operation - the short identifier for the server's method to call. + + + The parameters of the operation - each identified by a byte-typed code in Photon. + + + + Contains the server's response for an operation called by this peer. + The indexer of this class actually provides access to the Parameters Dictionary. + + + The OperationCode defines the type of operation called on Photon and in turn also the Parameters that + are set in the request. Those are provided as Dictionary with byte-keys. + There are pre-defined constants for various codes defined in the LoadBalancing application. + Check: OperationCode, ParameterCode, etc. + + An operation's request is summarized by the ReturnCode: a short typed code for "Ok" or + some different result. The code's meaning is specific per operation. An optional DebugMessage can be + provided to simplify debugging. + + Each call of an operation gets an ID, called the "invocID". This can be matched to the IDs + returned with any operation calls. This way, an application could track if a certain OpRaiseEvent + call was successful. + + + + The code for the operation called initially (by this peer). + Use enums or constants to be able to handle those codes, like OperationCode does. + + + A code that "summarizes" the operation's success or failure. Specific per operation. 0 usually means "ok". + + + An optional string sent by the server to provide readable feedback in error-cases. Might be null. + + + A Dictionary of values returned by an operation, using byte-typed keys per value. + + + + Alternative access to the Parameters, which wraps up a TryGetValue() call on the Parameters Dictionary. + + The byte-code of a returned value. + The value returned by the server, or null if the key does not exist in Parameters. + + + ToString() override. + Relatively short output of OpCode and returnCode. + + + Extensive output of operation results. + To be used in debug situations only, as it returns a string for each value. + + + A Photon Event consists of a Code value and a Parameters Dictionary with the event's content (if any). + + The indexer of this class provides access to the values in Parameters. + It wraps the null check for Parameters and uses TryGetValue() for the provided key. + + Photon servers use events to send information which is not triggered by a client's operation requests (those get responses). + The Realtime API allows you to send custom events with any Code and content via OpRaiseEvent. + + + + The event code identifies the type of event. + + + The Parameters of an event is a Dictionary<byte, object>. + + + + Access to the Parameters of a Photon-defined event. Custom Events only use Code, Sender and CustomData. + + The key byte-code of a Photon event value. + The Parameters value, or null if the key does not exist in Parameters. + + + + Defines the event key containing the Sender of the event. + + + Defaults to Sender key of Realtime API events (RaiseEvent): 254. + Can be set to Chat API's ChatParameterCode.Sender: 5. + + + + + Accesses the Sender of the event via the indexer and SenderKey. The result is cached. + + + Accesses this event's Parameters[CustomDataKey], which may be null. + In that case, this returns 0 (identifying the server as sender). + + + + + Defines the event key containing the Custom Data of the event. + + + Defaults to Data key of Realtime API events (RaiseEvent): 245. + Can be set to any other value on demand. + + + + + Accesses the Custom Data of the event via the indexer and CustomDataKey. The result is cached. + + + Accesses this event's Parameters[CustomDataKey], which may be null. + + + + ToString() override. + Short output of "Event" and it's Code. + + + Extensive output of the event content. + To be used in debug situations only, as it returns a string for each value. + + + + Type of serialization methods to add custom type support. + Use PhotonPeer.ReisterType() to register new types with serialization and deserialization methods. + + The method will get objects passed that were registered with it in RegisterType(). + Return a byte[] that resembles the object passed in. The framework will surround it with length and type info, so don't include it. + + + Serialization method delegate. StreamBuffer based custom serialization methods must use this form. + + + + Type of deserialization methods to add custom type support. + Use PhotonPeer.RegisterType() to register new types with serialization and deserialization methods. + + The framwork passes in the data it got by the associated SerializeMethod. The type code and length are stripped and applied before a DeserializeMethod is called. + Return a object of the type that was associated with this method through RegisterType(). + + + Deserialization method delegate. StreamBuffer based custom deserialization methods must use this form. + + + + Provides tools for the Exit Games Protocol + + + + + Serialize creates a byte-array from the given object and returns it. + + The object to serialize + The serialized byte-array + + + + Deserialize returns an object reassembled from the given byte-array. + + The byte-array to be Deserialized + The Deserialized object + + + + Serializes a short typed value into a byte-array (target) starting at the also given targetOffset. + The altered offset is known to the caller, because it is given via a referenced parameter. + + The short value to be serialized + The byte-array to serialize the short to + The offset in the byte-array + + + + Serializes an int typed value into a byte-array (target) starting at the also given targetOffset. + The altered offset is known to the caller, because it is given via a referenced parameter. + + The int value to be serialized + The byte-array to serialize the short to + The offset in the byte-array + + + + Serializes an float typed value into a byte-array (target) starting at the also given targetOffset. + The altered offset is known to the caller, because it is given via a referenced parameter. + + The float value to be serialized + The byte-array to serialize the short to + The offset in the byte-array + + + + Deserialize fills the given int typed value with the given byte-array (source) starting at the also given offset. + The result is placed in a variable (value). There is no need to return a value because the parameter value is given by reference. + The altered offset is this way also known to the caller. + + The int value to deserialize into + The byte-array to deserialize from + The offset in the byte-array + + + + Deserialize fills the given short typed value with the given byte-array (source) starting at the also given offset. + The result is placed in a variable (value). There is no need to return a value because the parameter value is given by reference. + The altered offset is this way also known to the caller. + + The short value to deserialized into + The byte-array to deserialize from + The offset in the byte-array + + + + Deserialize fills the given float typed value with the given byte-array (source) starting at the also given offset. + The result is placed in a variable (value). There is no need to return a value because the parameter value is given by reference. + The altered offset is this way also known to the caller. + + The float value to deserialize + The byte-array to deserialize from + The offset in the byte-array + + + + Exit Games GpBinaryV16 protocol implementation + + + + + The gp type. + + + + + Unkown type. + + + + + An array of objects. + + + This type is new in version 1.5. + + + + + A boolean Value. + + + + + A byte value. + + + + + An array of bytes. + + + + + An array of objects. + + + + + A 16-bit integer value. + + + + + A 32-bit floating-point value. + + + This type is new in version 1.5. + + + + + A dictionary + + + This type is new in version 1.6. + + + + + A 64-bit floating-point value. + + + This type is new in version 1.5. + + + + + A Hashtable. + + + + + A 32-bit integer value. + + + + + An array of 32-bit integer values. + + + + + A 64-bit integer value. + + + + + A string value. + + + + + An array of string values. + + + + + A custom type. 0x63 + + + + + Null value don't have types. + + + + + Calls the correct serialization method for the passed object. + + + + + DeserializeInteger returns an Integer typed value from the given stream. + + + + Exception type for de/serialization issues. Used in Protocol 1.8. + + + Constructor for the exception. + + + Unkown. GpType: 0. + + + Boolean. GpType: 2. See: BooleanFalse, BooleanTrue. + + + Byte. GpType: 3. + + + Short. GpType: 4. + + + 32-bit floating-point value. GpType: 5. + + + 64-bit floating-point value. GpType: 6. + + + String. GpType: 7. + + + Null value don't have types. GpType: 8. + + + CompressedInt. GpType: 9. + + + CompressedLong. GpType: 10. + + + Int1. GpType: 11. + + + Int1_. GpType: 12. + + + Int2. GpType: 13. + + + Int2_. GpType: 14. + + + L1. GpType: 15. + + + L1_. GpType: 16. + + + L2. GpType: 17. + + + L2_. GpType: 18. + + + Custom Type. GpType: 19. + + + Custom Type Slim. GpType: 128 (0x80) and up. + + + Dictionary. GpType: 20. + + + Hashtable. GpType: 21. + + + ObjectArray. GpType: 23. + + + OperationRequest. GpType: 24. + + + OperationResponse. GpType: 25. + + + EventData. GpType: 26. + + + Boolean False. GpType: 27. + + + Boolean True. GpType: 28. + + + ShortZero. GpType: 29. + + + IntZero. GpType: 30. + + + LongZero. GpType: 3. + + + FloatZero. GpType: 32. + + + DoubleZero. GpType: 33. + + + ByteZero. GpType: 34. + + + Array for nested Arrays. GpType: 64 (0x40). Element count and type follows. + + + + Writes integers as compressed. Either directly as zigzag-encoded or (when a type is written for this value) it can use an optimized sub-type. + + + + Enum of the three options for reliability and sequencing in Photon's reliable-UDP. + + + The operation/message gets sent just once without acknowledgement or repeat. The sequence (order) of messages is guaranteed. + + + The operation/message asks for an acknowledgment. It's resent until an ACK arrived. The sequence (order) of messages is guaranteed. + + + The operation/message gets sent once (unreliable) and might arrive out of order. Best for your own sequencing (e.g. for streams). + + + The operation/message asks for an acknowledgment. It's resent until an ACK arrived and might arrive out of order. Best for your own sequencing (e.g. for streams). + + + Wraps up DeliveryMode, Encryption and Channel values for sending operations and messages. + + + Default SendOptions instance for reliable sending. + + + Default SendOptions instance for unreliable sending. + + + Chose the DeliveryMode for this operation/message. Defaults to Unreliable. + + + If true the operation/message gets encrypted before it's sent. Defaults to false. + Before encryption can be used, it must be established. Check PhotonPeer.IsEncryptionAvailable is true. + + + The Enet channel to send in. Defaults to 0. + Channels in Photon relate to "message channels". Each channel is a sequence of messages. + + + Sets the DeliveryMode either to true: Reliable or false: Unreliable, overriding any current value. + Use this to conveniently select reliable/unreliable delivery. + + + Encapsulates the network i/o functionality for the realtime library. + + + used by PhotonPeer* + + + Endless loop, run in Receive Thread. + + + Internal class to encapsulate the network i/o functionality for the realtime libary. + + + used by PhotonPeer* + + + Encapsulates the network i/o functionality for the realtime library. + + + used by PhotonPeer* + + + Endless loop, run in Receive Thread. + + + Internal class to encapsulate the network i/o functionality for the realtime libary. + + + used by PhotonPeer* + + + Encapsulates the network i/o functionality for the realtime library. + + + used by PhotonPeer* + + + Endless loop, run in Receive Thread. + + + + Allocates a new byte[] that is the exact used length. Use GetBuffer for nonalloc operations. + + + + + Allocates a new byte[] that is the exact used length. Use GetBuffer for nonalloc operations. + + + + + The bytes between Position and Length are copied to the beginning of the buffer. Length decreased by Position. Position set to 0. + + + + + Brings StreamBuffer to the state as after writing of 'length' bytes. Returned buffer and offset can be used to actually fill "written" segment with data. + + + + + Remaining bytes in this StreamBuffer. Returns 0 if len - pos is less than 0. + + + + + Sets stream length. If current position is greater than specified value, it's set to the value. + + + SetLength(0) resets the stream to initial state but preserves underlying byte[] buffer. + + + + + Guarantees that the buffer is at least neededSize bytes. + + + + + Contains several (more or less) useful static methods, mostly used for debugging. + + + + + Gets the local machine's "milliseconds since start" value (precision is described in remarks). + + + This method uses Environment.TickCount (cheap but with only 16ms precision). + PhotonPeer.LocalMsTimestampDelegate is available to set the delegate (unless already connected). + + Fraction of the current time in Milliseconds (this is not a proper datetime timestamp). + + + + Creates a background thread that calls the passed function in intervals, as long as that returns true. + + + With StopBackgroundCalls, you can stop threads started with this method. + The resulting ThreadAbortException is caught and discarded. + + The function to call. Must return true, if it should be called again. Returning false ends the thread. + Milliseconds to sleep between calls of myThread. Default: 100ms. + An optional name for the task to help debugging. Null or empty won't set the thread.Name. + + + + Calls Abort on the thread with the given id (= index of the thread list) + + + The resulting ThreadAbortException is caught and discarded. + + The unique ID of the thread. + True if the thread is canceled and false otherwise, e.g. if the thread with the given ID does not exist. + + + + Calls Abort on all threads that were started via StartBackgroundCalls. + + + The resulting ThreadAbortException is caught and discarded. + + True if any thread got aborted. + + + + Writes the exception's stack trace to the received stream. + + Exception to obtain information from. + Output sream used to write to. + + + + Writes the exception's stack trace to the received stream. Writes to: System.Diagnostics.Debug. + + Exception to obtain information from. + + + + This method returns a string, representing the content of the given IDictionary. + Returns "null" if parameter is null. + + IDictionary to return as string. + + + + + Converts a byte-array to string (useful as debugging output). + Uses BitConverter.ToString(list) internally after a null-check of list. + + Byte-array to convert to string. + Length of bytes to convert to string. If negative, list.Length is converted. Optional. Default: -1. + + List of bytes as string. + + + + + Class to wrap static access to the random.Next() call in a thread safe manner. + + + + + An Attribute named "Preserve" tells Unity to not strip the code. + + + + TCP "Package" header: 7 bytes + + + TCP "Message" header: 2 bytes + + + TCP header combined: 9 bytes + + + Timestamp of last ping activity (updated when sending to avoid sending again immediately). + + + Re-used binary message used in the TCP protocol (WSS uses a Ping Operation due to lack of framing). + + + Re-used ping-Operation parameter-dict used in the WS/WSS protocol (TCP uses pingRequest). + + + Defines if the (TCP) socket implementation needs to do "framing". + The WebSocket protocol (e.g.) includes framing, so when that is used, we set DoFraming to false. + + + + Helper value to track if this TPeer sends an init request and has to wait for the response. Defaults to true. + + + For WebSockets, the init request is usually skipped (unless there is an init-token) + Setting this false allows WS/WSS to send pings even before the init-response is back. this fixes a bug in some platforms which do not provide the init-response message (for unknown reasons) + + + + + Checks the incoming queue and Dispatches received data if possible. Returns if a Dispatch happened or + not, which shows if more Dispatches might be needed. + + + + + gathers commands from all (out)queues until udp-packet is full and sends it! + + + + Sends a ping in intervals to keep connection alive (server will timeout connection if nothing is sent). + Always false in this case (local queues are ignored. true would be: "call again to send remaining data"). + + + enqueues serialized operations to be sent as tcp stream / package + + + Checks conditions. Immediately sends a ping as operation or "message". Updates this.lastPingActivity to avoid another ping for a while. + + + reads incoming tcp-packages to create and queue incoming commands* + + + + Only in use as long as PhotonPeer.TrafficStatsEnabled = true; + + + + Gets sum of outgoing operations in bytes. + + + Gets count of outgoing operations. + + + Gets sum of byte-cost of incoming operation-results. + + + Gets count of incoming operation-results. + + + Gets sum of byte-cost of incoming events. + + + Gets count of incoming events. + + + + Gets longest time it took to complete a call to OnOperationResponse (in your code). + If such a callback takes long, it will lower the network performance and might lead to timeouts. + + + + Gets OperationCode that causes the LongestOpResponseCallback. See that description. + + + + Gets longest time a call to OnEvent (in your code) took. + If such a callback takes long, it will lower the network performance and might lead to timeouts. + + + + Gets EventCode that caused the LongestEventCallback. See that description. + + + + Gets longest time between subsequent calls to DispatchIncomgingCommands in milliseconds. + Note: This is not a crucial timing for the networking. Long gaps just add "local lag" to events that are available already. + + + + + Gets longest time between subsequent calls to SendOutgoingCommands in milliseconds. + Note: This is a crucial value for network stability. Without calling SendOutgoingCommands, + nothing will be sent to the server, who might time out this client. + + + + + Gets number of calls of DispatchIncomingCommands. + + + + + Gets number of calls of DispatchIncomingCommands. + + + + + Gets number of calls of SendOutgoingCommands. + + + + Gets sum of byte-cost of all "logic level" messages. + + + Gets sum of counted "logic level" messages. + + + Gets sum of byte-cost of all incoming "logic level" messages. + + + Gets sum of counted incoming "logic level" messages. + + + Gets sum of byte-cost of all outgoing "logic level" messages (= OperationByteCount). + + + Gets sum of counted outgoing "logic level" messages (= OperationCount). + + + + Resets the values that can be maxed out, like LongestDeltaBetweenDispatching. See remarks. + + + Set to 0: LongestDeltaBetweenDispatching, LongestDeltaBetweenSending, LongestEventCallback, LongestEventCallbackCode, LongestOpResponseCallback, LongestOpResponseCallbackOpCode. + Also resets internal values: timeOfLastDispatchCall and timeOfLastSendCall (so intervals are tracked correctly). + + + + Gets the byte-size of per-package headers. + + + + Counts commands created/received by this client, ignoring repeats (out command count can be higher due to repeats). + + + + Gets count of bytes as traffic, excluding UDP/TCP headers (42 bytes / x bytes). + + + Timestamp of the last incoming ACK that has been read (every PhotonPeer.TimePingInterval milliseconds this client sends a PING which must be ACKd). + + + Timestamp of last incoming reliable command (every second we expect a PING). + +
+
diff --git a/Assets/Photon/PhotonLibs/netstandard2.0/Photon3Unity3D.xml.meta b/Assets/Photon/PhotonLibs/netstandard2.0/Photon3Unity3D.xml.meta new file mode 100644 index 00000000..62127689 --- /dev/null +++ b/Assets/Photon/PhotonLibs/netstandard2.0/Photon3Unity3D.xml.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 7015e500cd5b71244af56448dfb59804 +labels: +- FusionCodeDoc +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Prefabs/Player.prefab b/Assets/Prefabs/Player.prefab index e364cdd8..43488f0e 100644 --- a/Assets/Prefabs/Player.prefab +++ b/Assets/Prefabs/Player.prefab @@ -18,6 +18,8 @@ GameObject: - component: {fileID: 3043298118541876184} - component: {fileID: 3866929919288054183} - component: {fileID: 5773292363125757170} + - component: {fileID: 504047695906043424} + - component: {fileID: -5076913349690967641} m_Layer: 0 m_Name: Player m_TagString: Player @@ -61,6 +63,8 @@ MonoBehaviour: serializedVersion: 2 m_Bits: 128 heightRayLength: 0 + interactionOffset: {x: 0, y: 1.5, z: 0} + interactionRadius: 0.5 --- !u!143 &8915611492738107882 CharacterController: m_ObjectHideFlags: 0 @@ -96,7 +100,7 @@ Animator: m_GameObject: {fileID: 1054594849095937263} m_Enabled: 1 m_Avatar: {fileID: 9000000, guid: 5847774ba45dc754598435b50d4a0247, type: 3} - m_Controller: {fileID: 0} + m_Controller: {fileID: 9100000, guid: 09e31034ca0f14f42b3aa81e50326f87, type: 2} m_CullingMode: 1 m_UpdateMode: 0 m_ApplyRootMotion: 1 @@ -450,6 +454,43 @@ MonoBehaviour: shrineRiseHeight: 5 shrineFloorOffset: 0.5 camMoveSpeed: 4 +--- !u!114 &504047695906043424 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1054594849095937263} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 158639473, guid: e725a070cec140c4caffb81624c8c787, type: 3} + m_Name: + m_EditorClassIdentifier: Fusion.Runtime.dll::Fusion.NetworkTransform + _stateAuthorityChangeErrorCorrectionDelta: 0 + SyncScale: 0 + SyncParent: 0 + _autoAOIOverride: 1 + DisableSharedModeInterpolation: 0 +--- !u!114 &-5076913349690967641 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1054594849095937263} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: -1552182283, guid: e725a070cec140c4caffb81624c8c787, type: 3} + m_Name: + m_EditorClassIdentifier: Fusion.Runtime.dll::Fusion.NetworkObject + SortKey: 129023362 + ObjectInterest: 1 + Flags: 262145 + NestedObjects: [] + NetworkedBehaviours: + - {fileID: 830356296960548640} + - {fileID: 504047695906043424} + ForceRemoteRenderTimeframe: 0 --- !u!1 &3751838835891881608 GameObject: m_ObjectHideFlags: 0 diff --git a/Assets/Prefabs/Player.prefab.meta b/Assets/Prefabs/Player.prefab.meta index 01057a44..2205a199 100644 --- a/Assets/Prefabs/Player.prefab.meta +++ b/Assets/Prefabs/Player.prefab.meta @@ -1,5 +1,7 @@ fileFormatVersion: 2 guid: 761bdf2e5c0cff4488527355acb975e5 +labels: +- FusionPrefab PrefabImporter: externalObjects: {} userData: diff --git a/Assets/Prefabs/Stress Test/Dummy Green.prefab.meta b/Assets/Prefabs/Stress Test/Dummy Green.prefab.meta index 103e46d2..575f39e3 100644 --- a/Assets/Prefabs/Stress Test/Dummy Green.prefab.meta +++ b/Assets/Prefabs/Stress Test/Dummy Green.prefab.meta @@ -1,5 +1,7 @@ fileFormatVersion: 2 guid: 24ec9fe667c3dc744ab5837f102d0df9 +labels: +- FusionPrefab PrefabImporter: externalObjects: {} userData: diff --git a/Assets/Prefabs/Stress Test/Dummy Red.prefab.meta b/Assets/Prefabs/Stress Test/Dummy Red.prefab.meta index 9ff0d55a..18870772 100644 --- a/Assets/Prefabs/Stress Test/Dummy Red.prefab.meta +++ b/Assets/Prefabs/Stress Test/Dummy Red.prefab.meta @@ -1,5 +1,7 @@ fileFormatVersion: 2 guid: b2daf89439724f94e80963048b9c7bbc +labels: +- FusionPrefab PrefabImporter: externalObjects: {} userData: diff --git a/Assets/Resources/Player Variant.prefab.meta b/Assets/Resources/Player Variant.prefab.meta index 692936e5..ac41f49b 100644 --- a/Assets/Resources/Player Variant.prefab.meta +++ b/Assets/Resources/Player Variant.prefab.meta @@ -1,5 +1,7 @@ fileFormatVersion: 2 guid: ad9b5a9cb340cca4dad3bfc1a55ac1bc +labels: +- FusionPrefab PrefabImporter: externalObjects: {} userData: diff --git a/Assets/Scove/DEMO FUSION.unity b/Assets/Scove/DEMO FUSION.unity new file mode 100644 index 00000000..129740ec --- /dev/null +++ b/Assets/Scove/DEMO FUSION.unity @@ -0,0 +1,696 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!29 &1 +OcclusionCullingSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_OcclusionBakeSettings: + smallestOccluder: 5 + smallestHole: 0.25 + backfaceThreshold: 100 + m_SceneGUID: 00000000000000000000000000000000 + m_OcclusionCullingData: {fileID: 0} +--- !u!104 &2 +RenderSettings: + m_ObjectHideFlags: 0 + serializedVersion: 10 + m_Fog: 0 + m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} + m_FogMode: 3 + m_FogDensity: 0.01 + m_LinearFogStart: 0 + m_LinearFogEnd: 300 + m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} + m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} + m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} + m_AmbientIntensity: 1 + m_AmbientMode: 0 + m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} + m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0} + m_HaloStrength: 0.5 + m_FlareStrength: 1 + m_FlareFadeSpeed: 3 + m_HaloTexture: {fileID: 0} + m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} + m_DefaultReflectionMode: 0 + m_DefaultReflectionResolution: 128 + m_ReflectionBounces: 1 + m_ReflectionIntensity: 1 + m_CustomReflection: {fileID: 0} + m_Sun: {fileID: 0} + m_UseRadianceAmbientProbe: 0 +--- !u!157 &3 +LightmapSettings: + m_ObjectHideFlags: 0 + serializedVersion: 13 + m_BakeOnSceneLoad: 0 + m_GISettings: + serializedVersion: 2 + m_BounceScale: 1 + m_IndirectOutputScale: 1 + m_AlbedoBoost: 1 + m_EnvironmentLightingMode: 0 + m_EnableBakedLightmaps: 1 + m_EnableRealtimeLightmaps: 0 + m_LightmapEditorSettings: + serializedVersion: 12 + m_Resolution: 2 + m_BakeResolution: 40 + m_AtlasSize: 1024 + m_AO: 0 + m_AOMaxDistance: 1 + m_CompAOExponent: 1 + m_CompAOExponentDirect: 0 + m_ExtractAmbientOcclusion: 0 + m_Padding: 2 + m_LightmapParameters: {fileID: 0} + m_LightmapsBakeMode: 1 + m_TextureCompression: 1 + m_ReflectionCompression: 2 + m_MixedBakeMode: 2 + m_BakeBackend: 2 + m_PVRSampling: 1 + m_PVRDirectSampleCount: 32 + m_PVRSampleCount: 512 + m_PVRBounces: 2 + m_PVREnvironmentSampleCount: 256 + m_PVREnvironmentReferencePointCount: 2048 + m_PVRFilteringMode: 1 + m_PVRDenoiserTypeDirect: 1 + m_PVRDenoiserTypeIndirect: 1 + m_PVRDenoiserTypeAO: 1 + m_PVRFilterTypeDirect: 0 + m_PVRFilterTypeIndirect: 0 + m_PVRFilterTypeAO: 0 + m_PVREnvironmentMIS: 1 + m_PVRCulling: 1 + m_PVRFilteringGaussRadiusDirect: 1 + m_PVRFilteringGaussRadiusIndirect: 1 + m_PVRFilteringGaussRadiusAO: 1 + m_PVRFilteringAtrousPositionSigmaDirect: 0.5 + m_PVRFilteringAtrousPositionSigmaIndirect: 2 + m_PVRFilteringAtrousPositionSigmaAO: 1 + m_ExportTrainingData: 0 + m_TrainingDataDestination: TrainingData + m_LightProbeSampleCountMultiplier: 4 + m_LightingDataAsset: {fileID: 20201, guid: 0000000000000000f000000000000000, type: 0} + m_LightingSettings: {fileID: 0} +--- !u!196 &4 +NavMeshSettings: + serializedVersion: 2 + m_ObjectHideFlags: 0 + m_BuildSettings: + serializedVersion: 3 + agentTypeID: 0 + agentRadius: 0.5 + agentHeight: 2 + agentSlope: 45 + agentClimb: 0.4 + ledgeDropHeight: 0 + maxJumpAcrossDistance: 0 + minRegionArea: 2 + manualCellSize: 0 + cellSize: 0.16666667 + manualTileSize: 0 + tileSize: 256 + buildHeightMesh: 0 + maxJobWorkers: 0 + preserveTilesOutsideBounds: 0 + debug: + m_Flags: 0 + m_NavMeshData: {fileID: 0} +--- !u!1 &134723959 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 134723962} + - component: {fileID: 134723961} + - component: {fileID: 134723960} + - component: {fileID: 134723963} + m_Layer: 0 + m_Name: Main Camera + m_TagString: MainCamera + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!81 &134723960 +AudioListener: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 134723959} + m_Enabled: 1 +--- !u!20 &134723961 +Camera: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 134723959} + m_Enabled: 1 + serializedVersion: 2 + m_ClearFlags: 1 + m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0} + m_projectionMatrixMode: 1 + m_GateFitMode: 2 + m_FOVAxisMode: 0 + m_Iso: 200 + m_ShutterSpeed: 0.005 + m_Aperture: 16 + m_FocusDistance: 10 + m_FocalLength: 50 + m_BladeCount: 5 + m_Curvature: {x: 2, y: 11} + m_BarrelClipping: 0.25 + m_Anamorphism: 0 + m_SensorSize: {x: 36, y: 24} + m_LensShift: {x: 0, y: 0} + m_NormalizedViewPortRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + near clip plane: 0.3 + far clip plane: 1000 + field of view: 60 + orthographic: 0 + orthographic size: 5 + m_Depth: -1 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingPath: -1 + m_TargetTexture: {fileID: 0} + m_TargetDisplay: 0 + m_TargetEye: 3 + m_HDR: 1 + m_AllowMSAA: 1 + m_AllowDynamicResolution: 0 + m_ForceIntoRT: 0 + m_OcclusionCulling: 1 + m_StereoConvergence: 10 + m_StereoSeparation: 0.022 +--- !u!4 &134723962 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 134723959} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 1, z: -10} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &134723963 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 134723959} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: a79441f348de89743a2939f4d699eac1, type: 3} + m_Name: + m_EditorClassIdentifier: Unity.RenderPipelines.Universal.Runtime::UnityEngine.Rendering.Universal.UniversalAdditionalCameraData + m_RenderShadows: 1 + m_RequiresDepthTextureOption: 2 + m_RequiresOpaqueTextureOption: 2 + m_CameraType: 0 + m_Cameras: [] + m_RendererIndex: -1 + m_VolumeLayerMask: + serializedVersion: 2 + m_Bits: 1 + m_VolumeTrigger: {fileID: 0} + m_VolumeFrameworkUpdateModeOption: 2 + m_RenderPostProcessing: 0 + m_Antialiasing: 0 + m_AntialiasingQuality: 2 + m_StopNaN: 0 + m_Dithering: 0 + m_ClearDepth: 1 + m_AllowXRRendering: 1 + m_AllowHDROutput: 1 + m_UseScreenCoordOverride: 0 + m_ScreenSizeOverride: {x: 0, y: 0, z: 0, w: 0} + m_ScreenCoordScaleBias: {x: 0, y: 0, z: 0, w: 0} + m_RequiresDepthTexture: 0 + m_RequiresColorTexture: 0 + m_TaaSettings: + m_Quality: 3 + m_FrameInfluence: 0.1 + m_JitterScale: 1 + m_MipBias: 0 + m_VarianceClampScale: 0.9 + m_ContrastAdaptiveSharpening: 0 + m_Version: 2 +--- !u!1 &884186905 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 884186906} + - component: {fileID: 884186908} + - component: {fileID: 884186907} + m_Layer: 5 + m_Name: Image + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &884186906 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 884186905} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1956301527} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.5} + m_AnchoredPosition: {x: -910, y: -490} + m_SizeDelta: {x: 100, y: 100} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &884186907 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 884186905} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Image + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 0} + m_Type: 0 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!222 &884186908 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 884186905} + m_CullTransparentMesh: 1 +--- !u!1 &966137136 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 966137138} + - component: {fileID: 966137137} + m_Layer: 0 + m_Name: NetworkManager + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &966137137 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 966137136} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 44bfaa339c82069418e72a14479a0212, type: 3} + m_Name: + m_EditorClassIdentifier: Assembly-CSharp::BasicSpawner + _playerPrefab: + RawGuidValue: 761bdf2e5c0cff4488527355acb975e5 +--- !u!4 &966137138 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 966137136} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: -81.98698, y: 0, z: 37.26562} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1016072950 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1016072953} + - component: {fileID: 1016072952} + - component: {fileID: 1016072951} + m_Layer: 0 + m_Name: EventSystem + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1016072951 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1016072950} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 01614664b831546d2ae94a42149d80ac, type: 3} + m_Name: + m_EditorClassIdentifier: Unity.InputSystem::UnityEngine.InputSystem.UI.InputSystemUIInputModule + m_SendPointerHoverToParent: 1 + m_MoveRepeatDelay: 0.5 + m_MoveRepeatRate: 0.1 + m_XRTrackingOrigin: {fileID: 0} + m_ActionsAsset: {fileID: -944628639613478452, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3} + m_PointAction: {fileID: -1654692200621890270, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3} + m_MoveAction: {fileID: -8784545083839296357, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3} + m_SubmitAction: {fileID: 392368643174621059, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3} + m_CancelAction: {fileID: 7727032971491509709, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3} + m_LeftClickAction: {fileID: 3001919216989983466, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3} + m_MiddleClickAction: {fileID: -2185481485913320682, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3} + m_RightClickAction: {fileID: -4090225696740746782, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3} + m_ScrollWheelAction: {fileID: 6240969308177333660, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3} + m_TrackedDevicePositionAction: {fileID: 6564999863303420839, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3} + m_TrackedDeviceOrientationAction: {fileID: 7970375526676320489, guid: ca9f5fa95ffab41fb9a615ab714db018, type: 3} + m_DeselectOnBackgroundClick: 1 + m_PointerBehavior: 0 + m_CursorLockBehavior: 0 + m_ScrollDeltaPerTick: 6 +--- !u!114 &1016072952 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1016072950} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 76c392e42b5098c458856cdf6ecaaaa1, type: 3} + m_Name: + m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.EventSystems.EventSystem + m_FirstSelected: {fileID: 0} + m_sendNavigationEvents: 1 + m_DragThreshold: 10 +--- !u!4 &1016072953 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1016072950} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1392427387 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1392427389} + - component: {fileID: 1392427388} + - component: {fileID: 1392427390} + m_Layer: 0 + m_Name: Directional Light + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!108 &1392427388 +Light: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1392427387} + m_Enabled: 1 + serializedVersion: 12 + m_Type: 1 + m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1} + m_Intensity: 1 + m_Range: 10 + m_SpotAngle: 30 + m_InnerSpotAngle: 21.80208 + m_CookieSize2D: {x: 0.5, y: 0.5} + m_Shadows: + m_Type: 2 + m_Resolution: -1 + m_CustomResolution: -1 + m_Strength: 1 + m_Bias: 0.05 + m_NormalBias: 0.4 + m_NearPlane: 0.2 + m_CullingMatrixOverride: + e00: 1 + e01: 0 + e02: 0 + e03: 0 + e10: 0 + e11: 1 + e12: 0 + e13: 0 + e20: 0 + e21: 0 + e22: 1 + e23: 0 + e30: 0 + e31: 0 + e32: 0 + e33: 1 + m_UseCullingMatrixOverride: 0 + m_Cookie: {fileID: 0} + m_DrawHalo: 0 + m_Flare: {fileID: 0} + m_RenderMode: 0 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingLayerMask: 1 + m_Lightmapping: 4 + m_LightShadowCasterMode: 0 + m_AreaSize: {x: 1, y: 1} + m_BounceIntensity: 1 + m_ColorTemperature: 6570 + m_UseColorTemperature: 0 + m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0} + m_UseBoundingSphereOverride: 0 + m_UseViewFrustumForShadowCasterCull: 1 + m_ForceVisible: 0 + m_ShadowRadius: 0 + m_ShadowAngle: 0 + m_LightUnit: 1 + m_LuxAtDistance: 1 + m_EnableSpotReflector: 1 +--- !u!4 &1392427389 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1392427387} + serializedVersion: 2 + m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261} + m_LocalPosition: {x: 0, y: 3, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0} +--- !u!114 &1392427390 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1392427387} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 474bcb49853aa07438625e644c072ee6, type: 3} + m_Name: + m_EditorClassIdentifier: Unity.RenderPipelines.Universal.Runtime::UnityEngine.Rendering.Universal.UniversalAdditionalLightData + m_UsePipelineSettings: 1 + m_AdditionalLightsShadowResolutionTier: 2 + m_CustomShadowLayers: 0 + m_LightCookieSize: {x: 1, y: 1} + m_LightCookieOffset: {x: 0, y: 0} + m_SoftShadowQuality: 0 + m_RenderingLayersMask: + serializedVersion: 0 + m_Bits: 1 + m_ShadowRenderingLayersMask: + serializedVersion: 0 + m_Bits: 1 + m_Version: 4 + m_LightLayerMask: 1 + m_ShadowLayerMask: 1 + m_RenderingLayers: 1 + m_ShadowRenderingLayers: 1 +--- !u!1 &1956301523 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1956301527} + - component: {fileID: 1956301526} + - component: {fileID: 1956301525} + - component: {fileID: 1956301524} + m_Layer: 5 + m_Name: Canvas + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1956301524 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1956301523} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: dc42784cf147c0c48a680349fa168899, type: 3} + m_Name: + m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.GraphicRaycaster + m_IgnoreReversedGraphics: 1 + m_BlockingObjects: 0 + m_BlockingMask: + serializedVersion: 2 + m_Bits: 4294967295 +--- !u!114 &1956301525 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1956301523} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3} + m_Name: + m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.CanvasScaler + m_UiScaleMode: 0 + m_ReferencePixelsPerUnit: 100 + m_ScaleFactor: 1 + m_ReferenceResolution: {x: 800, y: 600} + m_ScreenMatchMode: 0 + m_MatchWidthOrHeight: 0 + m_PhysicalUnit: 3 + m_FallbackScreenDPI: 96 + m_DefaultSpriteDPI: 96 + m_DynamicPixelsPerUnit: 1 + m_PresetInfoIsWorld: 0 +--- !u!223 &1956301526 +Canvas: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1956301523} + m_Enabled: 1 + serializedVersion: 3 + m_RenderMode: 1 + m_Camera: {fileID: 134723961} + m_PlaneDistance: 100 + m_PixelPerfect: 0 + m_ReceivesEvents: 1 + m_OverrideSorting: 0 + m_OverridePixelPerfect: 0 + m_SortingBucketNormalizedSize: 0 + m_VertexColorAlwaysGammaSpace: 0 + m_AdditionalShaderChannelsFlag: 0 + m_UpdateRectTransformForStandalone: 0 + m_SortingLayerID: 0 + m_SortingOrder: 0 + m_TargetDisplay: 0 +--- !u!224 &1956301527 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1956301523} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 0, y: 0, z: 0} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 884186906} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0, y: 0} +--- !u!1660057539 &9223372036854775807 +SceneRoots: + m_ObjectHideFlags: 0 + m_Roots: + - {fileID: 134723962} + - {fileID: 1392427389} + - {fileID: 1956301527} + - {fileID: 1016072953} + - {fileID: 966137138} diff --git a/Assets/Scove/DEMO FUSION.unity.meta b/Assets/Scove/DEMO FUSION.unity.meta new file mode 100644 index 00000000..4c13ed83 --- /dev/null +++ b/Assets/Scove/DEMO FUSION.unity.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: c3d4d808eec835545b53364265c55e97 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scove/Player Movement.unity b/Assets/Scove/Player Movement.unity index df3757ea..cc046699 100644 --- a/Assets/Scove/Player Movement.unity +++ b/Assets/Scove/Player Movement.unity @@ -261,7 +261,7 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 4970741399400872, guid: b5a5a17dc3a6d9e47bd468828a2dec34, type: 3} propertyPath: m_LocalPosition.x - value: -7.701748 + value: -5.49 objectReference: {fileID: 0} - target: {fileID: 4970741399400872, guid: b5a5a17dc3a6d9e47bd468828a2dec34, type: 3} propertyPath: m_LocalPosition.y @@ -269,7 +269,7 @@ PrefabInstance: objectReference: {fileID: 0} - target: {fileID: 4970741399400872, guid: b5a5a17dc3a6d9e47bd468828a2dec34, type: 3} propertyPath: m_LocalPosition.z - value: -2.4509988 + value: -4.2 objectReference: {fileID: 0} - target: {fileID: 4970741399400872, guid: b5a5a17dc3a6d9e47bd468828a2dec34, type: 3} propertyPath: m_LocalRotation.w @@ -439,11 +439,6 @@ Transform: m_Children: [] m_Father: {fileID: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} ---- !u!137 &166789747 stripped -SkinnedMeshRenderer: - m_CorrespondingSourceObject: {fileID: 1058696422167757239, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - m_PrefabInstance: {fileID: 8240317044381527393} - m_PrefabAsset: {fileID: 0} --- !u!1 &200732282 GameObject: m_ObjectHideFlags: 0 @@ -571,27 +566,6 @@ MonoBehaviour: m_ShadowLayerMask: 1 m_RenderingLayers: 1 m_ShadowRenderingLayers: 1 ---- !u!4 &216247148 stripped -Transform: - m_CorrespondingSourceObject: {fileID: 3154409663696148700, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - m_PrefabInstance: {fileID: 8240317044381527393} - m_PrefabAsset: {fileID: 0} ---- !u!114 &216247156 stripped -MonoBehaviour: - m_CorrespondingSourceObject: {fileID: 5600577104145922999, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - m_PrefabInstance: {fileID: 8240317044381527393} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1055459067} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 5962d8f2c8e40e240a4a4907c7b539fa, type: 3} - m_Name: - m_EditorClassIdentifier: Assembly-CSharp::OnlyScove.Scripts.InputReader ---- !u!20 &442028704 stripped -Camera: - m_CorrespondingSourceObject: {fileID: 452500236988029996, guid: fb7874830b9e56341bf88f2a1123c677, type: 3} - m_PrefabInstance: {fileID: 3886963620680427248} - m_PrefabAsset: {fileID: 0} --- !u!1 &731807201 GameObject: m_ObjectHideFlags: 0 @@ -891,53 +865,6 @@ Transform: m_Children: [] m_Father: {fileID: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} ---- !u!1 &1055459067 stripped -GameObject: - m_CorrespondingSourceObject: {fileID: 1054594849095937263, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - m_PrefabInstance: {fileID: 8240317044381527393} - m_PrefabAsset: {fileID: 0} ---- !u!114 &1055459068 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1055459067} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 33ce7e871a1d2b445a1dbbb4fe07ba48, type: 3} - m_Name: - m_EditorClassIdentifier: Assembly-CSharp::OnlyScove.Scripts.Health - maxHealth: 100 - currentHealth: 0 ---- !u!114 &1055459072 stripped -MonoBehaviour: - m_CorrespondingSourceObject: {fileID: 830356296960548640, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - m_PrefabInstance: {fileID: 8240317044381527393} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1055459067} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 848ad6fdeb60b254497391392419b063, type: 3} - m_Name: - m_EditorClassIdentifier: Assembly-CSharp::OnlyScove.Scripts.PlayerStateMachine ---- !u!114 &1055459077 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1055459067} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: bf6aff0b7e11d41439ac80f4963a0795, type: 3} - m_Name: - m_EditorClassIdentifier: Assembly-CSharp::OnlyScove.Scripts.PlayerDebugProvider - stateMachine: {fileID: 1055459072} - debugCanvas: {fileID: 1579856151} - followOffset: {x: 1.5, y: 2, z: 0} - smoothTime: 0.15 - lookAtCamera: 1 --- !u!1 &1100930032 GameObject: m_ObjectHideFlags: 0 @@ -1291,7 +1218,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: cd13c5c96000414397dd7d41a73edd62, type: 3} m_Name: m_EditorClassIdentifier: Assembly-CSharp::UI.MyUIDisplay - playerDebugProvider: {fileID: 1055459077} + playerDebugProvider: {fileID: 0} interactionPromptContainer: {fileID: 1222728863} interactionPromptText: {fileID: 0} --- !u!114 &1579856153 @@ -1377,11 +1304,6 @@ RectTransform: m_AnchoredPosition: {x: 0, y: 0} m_SizeDelta: {x: 0, y: 0} m_Pivot: {x: 0, y: 0} ---- !u!4 &1631120432 stripped -Transform: - m_CorrespondingSourceObject: {fileID: 5188652905305800431, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - m_PrefabInstance: {fileID: 8240317044381527393} - m_PrefabAsset: {fileID: 0} --- !u!1 &1667007657 GameObject: m_ObjectHideFlags: 0 @@ -1511,11 +1433,6 @@ Transform: m_Children: [] m_Father: {fileID: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} ---- !u!4 &1732205146 stripped -Transform: - m_CorrespondingSourceObject: {fileID: 8004958684693924044, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - m_PrefabInstance: {fileID: 8240317044381527393} - m_PrefabAsset: {fileID: 0} --- !u!1 &1738061767 GameObject: m_ObjectHideFlags: 0 @@ -1795,6 +1712,22 @@ PrefabInstance: propertyPath: m_Layer value: 9 objectReference: {fileID: 0} + - target: {fileID: 3593740734441989150, guid: 120d7631bf5b4b94f8271411b1868e06, type: 3} + propertyPath: m_Range + value: 5 + objectReference: {fileID: 0} + - target: {fileID: 3593740734441989150, guid: 120d7631bf5b4b94f8271411b1868e06, type: 3} + propertyPath: m_Intensity + value: 5 + objectReference: {fileID: 0} + - target: {fileID: 3593740734441989150, guid: 120d7631bf5b4b94f8271411b1868e06, type: 3} + propertyPath: m_InnerSpotAngle + value: 40 + objectReference: {fileID: 0} + - target: {fileID: 3593740734441989150, guid: 120d7631bf5b4b94f8271411b1868e06, type: 3} + propertyPath: m_BounceIntensity + value: 1 + objectReference: {fileID: 0} - target: {fileID: 4300131081491030878, guid: 120d7631bf5b4b94f8271411b1868e06, type: 3} propertyPath: m_Layer value: 9 @@ -1861,37 +1794,6 @@ PrefabInstance: insertIndex: -1 addedObject: {fileID: 830381277} m_SourcePrefab: {fileID: 100100000, guid: 120d7631bf5b4b94f8271411b1868e06, type: 3} ---- !u!1 &2101138892 -GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 2101138893} - m_Layer: 3 - m_Name: Campoint 1 - m_TagString: Untagged - m_Icon: {fileID: 0} - m_NavMeshLayer: 0 - m_StaticEditorFlags: 0 - m_IsActive: 1 ---- !u!4 &2101138893 -Transform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 2101138892} - serializedVersion: 2 - m_LocalRotation: {x: -0, y: 0.9727903, z: -0, w: 0.23168753} - m_LocalPosition: {x: -1.8390989, y: -1.7589655, z: 7.503214} - m_LocalScale: {x: 1, y: 1, z: 1} - m_ConstrainProportionsScale: 0 - m_Children: [] - m_Father: {fileID: 1732205146} - m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1001 &3886963620680427248 PrefabInstance: m_ObjectHideFlags: 0 @@ -1951,11 +1853,11 @@ PrefabInstance: - target: {fileID: 3657229949309460766, guid: fb7874830b9e56341bf88f2a1123c677, type: 3} propertyPath: fpvTarget value: - objectReference: {fileID: 1631120432} + objectReference: {fileID: 0} - target: {fileID: 3657229949309460766, guid: fb7874830b9e56341bf88f2a1123c677, type: 3} propertyPath: inputReader value: - objectReference: {fileID: 216247156} + objectReference: {fileID: 0} - target: {fileID: 3657229949309460766, guid: fb7874830b9e56341bf88f2a1123c677, type: 3} propertyPath: sensitivity value: 10 @@ -1963,7 +1865,7 @@ PrefabInstance: - target: {fileID: 3657229949309460766, guid: fb7874830b9e56341bf88f2a1123c677, type: 3} propertyPath: followTarget value: - objectReference: {fileID: 216247148} + objectReference: {fileID: 0} - target: {fileID: 3657229949309460766, guid: fb7874830b9e56341bf88f2a1123c677, type: 3} propertyPath: zoomHandler.distance value: 7.602974 @@ -1983,11 +1885,11 @@ PrefabInstance: - target: {fileID: 3657229949309460766, guid: fb7874830b9e56341bf88f2a1123c677, type: 3} propertyPath: 'characterRenderers.Array.data[0]' value: - objectReference: {fileID: 166789747} + objectReference: {fileID: 0} - target: {fileID: 3657229949309460766, guid: fb7874830b9e56341bf88f2a1123c677, type: 3} propertyPath: 'characterFading.characterRenderers.Array.data[0]' value: - objectReference: {fileID: 166789747} + objectReference: {fileID: 0} - target: {fileID: 8391577239842762580, guid: fb7874830b9e56341bf88f2a1123c677, type: 3} propertyPath: m_RenderPostProcessing value: 1 @@ -1997,439 +1899,6 @@ PrefabInstance: m_AddedGameObjects: [] m_AddedComponents: [] m_SourcePrefab: {fileID: 100100000, guid: fb7874830b9e56341bf88f2a1123c677, type: 3} ---- !u!1001 &8240317044381527393 -PrefabInstance: - m_ObjectHideFlags: 0 - serializedVersion: 2 - m_Modification: - serializedVersion: 3 - m_TransformParent: {fileID: 0} - m_Modifications: - - target: {fileID: 24604759064366179, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 428631968399211893, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 628293800709747807, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 698367172708192476, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 830356296960548640, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: k__BackingField - value: 7 - objectReference: {fileID: 0} - - target: {fileID: 830356296960548640, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: k__BackingField - value: 20 - objectReference: {fileID: 0} - - target: {fileID: 830356296960548640, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: k__BackingField.m_Bits - value: 512 - objectReference: {fileID: 0} - - target: {fileID: 953676275580348474, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 994175005038401079, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 1054594849095937263, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Name - value: Player - objectReference: {fileID: 0} - - target: {fileID: 1054594849095937263, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 1054594849095937263, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_TagString - value: Player - objectReference: {fileID: 0} - - target: {fileID: 1058696422167757239, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: 'm_Materials.Array.data[0]' - value: - objectReference: {fileID: 3297912226980038121, guid: 8290c8e8479e3b744b22042adbe32801, type: 3} - - target: {fileID: 1058696422167757239, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: 'm_Materials.Array.data[1]' - value: - objectReference: {fileID: 2100000, guid: 29d8aeef71b97864a9ad6317a4738f26, type: 2} - - target: {fileID: 1099729003880524104, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 1187902911135133637, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 1342363478191964358, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 1365767707156522953, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 1417209934070135885, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 1629062039960760911, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 1706908724520692566, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 1941222964578247006, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 2066572381289880962, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 2078373902343722426, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 2089802539415559148, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 2161548823967223948, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 2372096890453441496, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 2583915459421688893, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 2602872181561755417, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 2663176581072049613, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 3010251870038942475, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Camera - value: - objectReference: {fileID: 442028704} - - target: {fileID: 3010251870038942475, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_ActionEvents.Array.size - value: 21 - objectReference: {fileID: 0} - - target: {fileID: 3010251870038942475, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_ActionEvents.Array.data[20].m_ActionId - value: 7e8b9416-0a2d-4652-98d8-e7368560ede9 - objectReference: {fileID: 0} - - target: {fileID: 3010251870038942475, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_ActionEvents.Array.data[20].m_ActionName - value: 'Player/Change View[/Keyboard/f2]' - objectReference: {fileID: 0} - - target: {fileID: 3010251870038942475, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_ActionEvents.Array.data[20].m_PersistentCalls.m_Calls.Array.size - value: 1 - objectReference: {fileID: 0} - - target: {fileID: 3010251870038942475, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_ActionEvents.Array.data[20].m_PersistentCalls.m_Calls.Array.data[0].m_Mode - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 3010251870038942475, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_ActionEvents.Array.data[20].m_PersistentCalls.m_Calls.Array.data[0].m_Target - value: - objectReference: {fileID: 216247156} - - target: {fileID: 3010251870038942475, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_ActionEvents.Array.data[20].m_PersistentCalls.m_Calls.Array.data[0].m_CallState - value: 2 - objectReference: {fileID: 0} - - target: {fileID: 3010251870038942475, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_ActionEvents.Array.data[20].m_PersistentCalls.m_Calls.Array.data[0].m_MethodName - value: OnToggleView - objectReference: {fileID: 0} - - target: {fileID: 3010251870038942475, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_ActionEvents.Array.data[20].m_PersistentCalls.m_Calls.Array.data[0].m_TargetAssemblyTypeName - value: OnlyScove.Scripts.InputReader, Assembly-CSharp - objectReference: {fileID: 0} - - target: {fileID: 3010251870038942475, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_ActionEvents.Array.data[20].m_PersistentCalls.m_Calls.Array.data[0].m_Arguments.m_ObjectArgumentAssemblyTypeName - value: UnityEngine.Object, UnityEngine - objectReference: {fileID: 0} - - target: {fileID: 3012726806270232475, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 3034023431910074942, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 3075112423820774093, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 3154409663696148700, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_LocalPosition.x - value: -3.68 - objectReference: {fileID: 0} - - target: {fileID: 3154409663696148700, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_LocalPosition.y - value: 5.166 - objectReference: {fileID: 0} - - target: {fileID: 3154409663696148700, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_LocalPosition.z - value: -4.537 - objectReference: {fileID: 0} - - target: {fileID: 3154409663696148700, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_LocalRotation.w - value: 1 - objectReference: {fileID: 0} - - target: {fileID: 3154409663696148700, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_LocalRotation.x - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 3154409663696148700, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_LocalRotation.y - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 3154409663696148700, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_LocalRotation.z - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 3154409663696148700, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_LocalEulerAnglesHint.x - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 3154409663696148700, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_LocalEulerAnglesHint.y - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 3154409663696148700, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_LocalEulerAnglesHint.z - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 3169088727455081001, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 3208943355414805193, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 3260655485705870215, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 3372620539050622430, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 3585659656605431361, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 3632511961091267724, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 3751838835891881608, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 3819107769984714904, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 3866929919288054183, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: redSlashPrefab - value: - objectReference: {fileID: 1113287330716207023, guid: 03163717f6c5cad409e7e7f079f06ea5, type: 3} - - target: {fileID: 3866929919288054183, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: blackSlashPrefab - value: - objectReference: {fileID: 7925862234553078923, guid: a9db8dc0d7288b8418ab54e786fbffa7, type: 3} - - target: {fileID: 4156966651666679023, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 4262004705141875331, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 4423969535931836863, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 4487397454122334117, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 4535094625508263680, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 4725799393607847019, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 4783305068539476411, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 5043773522115702614, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 5073031060995569267, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: autoDetectOnStart - value: 0 - objectReference: {fileID: 0} - - target: {fileID: 5220717330053178882, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 5287269061604265618, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 5773292363125757170, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: cinematicCameraPoint - value: - objectReference: {fileID: 2101138893} - - target: {fileID: 5811177247042239962, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: interactionRadius - value: 0.2 - objectReference: {fileID: 0} - - target: {fileID: 5811177247042239962, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: interactionOffset.y - value: 1.5 - objectReference: {fileID: 0} - - target: {fileID: 5811177247042239962, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: obstacleLayer.m_Bits - value: 640 - objectReference: {fileID: 0} - - target: {fileID: 5939497822486129397, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 6010937357954156926, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 6732799055399666404, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 6756785572177215345, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 6802363043976308623, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 7081828259010238464, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 7120464003553950939, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 7130820906669969123, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 7325607502380417214, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 7584350699754940592, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 8038276856359068568, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 8042183671600357484, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 8289300213039820077, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 8335303598418656312, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 8520321464314555498, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 8532001592693726814, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 8601218555146693774, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 8648256310726498631, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 8705788873716680917, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 8899165731977409877, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 9039092688197800535, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Layer - value: 3 - objectReference: {fileID: 0} - - target: {fileID: 9098752589608501196, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_Controller - value: - objectReference: {fileID: 9100000, guid: 09e31034ca0f14f42b3aa81e50326f87, type: 2} - - target: {fileID: 9098752589608501196, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - propertyPath: m_ApplyRootMotion - value: 1 - objectReference: {fileID: 0} - m_RemovedComponents: - - {fileID: 6587788942094262397, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - - {fileID: 5294322338071205561, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - - {fileID: 8541105841172983867, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - m_RemovedGameObjects: [] - m_AddedGameObjects: - - targetCorrespondingSourceObject: {fileID: 8004958684693924044, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - insertIndex: -1 - addedObject: {fileID: 2101138893} - m_AddedComponents: - - targetCorrespondingSourceObject: {fileID: 1054594849095937263, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - insertIndex: -1 - addedObject: {fileID: 1055459068} - - targetCorrespondingSourceObject: {fileID: 1054594849095937263, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} - insertIndex: -1 - addedObject: {fileID: 1055459077} - m_SourcePrefab: {fileID: 100100000, guid: 761bdf2e5c0cff4488527355acb975e5, type: 3} --- !u!1660057539 &9223372036854775807 SceneRoots: m_ObjectHideFlags: 0 @@ -2438,7 +1907,6 @@ SceneRoots: - {fileID: 200732284} - {fileID: 1333385348} - {fileID: 1437922952} - - {fileID: 8240317044381527393} - {fileID: 731807205} - {fileID: 75539245} - {fileID: 1738061771} diff --git a/Assets/Scripts/Camera Controller/CameraController.cs b/Assets/Scripts/Camera Controller/CameraController.cs index da7690c9..15b9f55b 100644 --- a/Assets/Scripts/Camera Controller/CameraController.cs +++ b/Assets/Scripts/Camera Controller/CameraController.cs @@ -7,8 +7,8 @@ namespace OnlyScove.Scripts { public enum CameraViewMode { ThirdPerson, FirstPerson } - [SerializeField] InputReader inputReader; - [SerializeField] Transform followTarget; // Player's root for TPV + public InputReader inputReader; // Đổi từ [SerializeField] thành public + public Transform followTarget; // Player's root for TPV [SerializeField] float positionSmoothTime = 0.12f; [SerializeField] float rotationSmoothTime = 5f; [SerializeField] Vector2 framingOffset; @@ -72,6 +72,7 @@ namespace OnlyScove.Scripts private void Update() { + if (followTarget == null) return; HandleViewTransition(); if (inputReader != null) diff --git a/Assets/Scripts/Fusion.meta b/Assets/Scripts/Fusion.meta new file mode 100644 index 00000000..13389f28 --- /dev/null +++ b/Assets/Scripts/Fusion.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 6aa851b2c7ee553439ee0065f77665cb +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Fusion/BasicSpawner.cs b/Assets/Scripts/Fusion/BasicSpawner.cs new file mode 100644 index 00000000..9296e8ec --- /dev/null +++ b/Assets/Scripts/Fusion/BasicSpawner.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; +using Fusion; +using Fusion.Sockets; +using UnityEngine; +using UnityEngine.SceneManagement; + +// ĐỊNH NGHĨA DỮ LIỆU GỬI QUA MẠNG +public struct NetworkInputData : INetworkInput +{ + public Vector2 move; + public Quaternion rot; + public bool sprint; +} + +public class BasicSpawner : MonoBehaviour, INetworkRunnerCallbacks +{ + private NetworkRunner _runner; + + [SerializeField] private NetworkPrefabRef _playerPrefab; + private Dictionary _spawnedCharacters = new Dictionary(); + + async void StartGame(GameMode mode) + { + if (_runner != null) return; + _runner = gameObject.AddComponent(); + _runner.ProvideInput = true; + DontDestroyOnLoad(gameObject); + await _runner.StartGame(new StartGameArgs() + { + GameMode = mode, + SessionName = "TestRoom", + Scene = SceneRef.FromIndex(1), + SceneManager = gameObject.AddComponent() + }); + } + + private void OnGUI() + { + if (_runner == null) + { + if (GUI.Button(new Rect(10, 10, 200, 40), "Host (Tạo phòng)")) StartGame(GameMode.AutoHostOrClient); + if (GUI.Button(new Rect(10, 60, 200, 40), "Join (Vào phòng)")) StartGame(GameMode.Client); + } + } + + public void OnPlayerJoined(NetworkRunner runner, PlayerRef player) + { + if (runner.IsServer) + { + Vector3 spawnPosition = new Vector3((player.RawEncoded % 10) * 2, 1, 0); + NetworkObject networkPlayerObject = runner.Spawn(_playerPrefab, spawnPosition, Quaternion.identity, player); + runner.SetPlayerObject(player, networkPlayerObject); + _spawnedCharacters.Add(player, networkPlayerObject); + } + } + + public void OnInput(NetworkRunner runner, NetworkInput input) + { + var data = new NetworkInputData(); + + // Lấy dữ liệu từ nhân vật local của chính mình + if (OnlyScove.Scripts.PlayerStateMachine.Local != null) + { + var sm = OnlyScove.Scripts.PlayerStateMachine.Local; + data.move = sm.Input.MoveInput; + data.sprint = sm.Input.IsSprintHeld; + + // Lấy hướng xoay từ Camera (nếu có) + if (sm.Cam != null) + data.rot = sm.Cam.PlanarRotation; + else + data.rot = sm.NetworkedCameraRotation; // Fallback + } + + input.Set(data); + } + + public void OnPlayerLeft(NetworkRunner runner, PlayerRef player) + { + if (_spawnedCharacters.TryGetValue(player, out NetworkObject networkObject)) + { + if (networkObject != null) runner.Despawn(networkObject); + _spawnedCharacters.Remove(player); + } + } + + public void OnInputMissing(NetworkRunner runner, PlayerRef player, NetworkInput input) { } + public void OnShutdown(NetworkRunner runner, ShutdownReason shutdownReason) { } + public void OnConnectedToServer(NetworkRunner runner) { } + public void OnDisconnectedFromServer(NetworkRunner runner, NetDisconnectReason reason) { } + public void OnConnectRequest(NetworkRunner runner, NetworkRunnerCallbackArgs.ConnectRequest request, byte[] token) { } + public void OnConnectFailed(NetworkRunner runner, NetAddress remoteAddress, NetConnectFailedReason reason) { } + public void OnUserSimulationMessage(NetworkRunner runner, SimulationMessagePtr message) { } + public void OnSessionListUpdated(NetworkRunner runner, List sessionList) { } + public void OnCustomAuthenticationResponse(NetworkRunner runner, Dictionary data) { } + public void OnHostMigration(NetworkRunner runner, HostMigrationToken hostMigrationToken) { } + public void OnSceneLoadDone(NetworkRunner runner) { } + public void OnSceneLoadStart(NetworkRunner runner) { } + public void OnObjectExitAOI(NetworkRunner runner, NetworkObject obj, PlayerRef player) { } + public void OnObjectEnterAOI(NetworkRunner runner, NetworkObject obj, PlayerRef player) { } + public void OnReliableDataReceived(NetworkRunner runner, PlayerRef player, ReliableKey key, ArraySegment data) { } + public void OnReliableDataProgress(NetworkRunner runner, PlayerRef player, ReliableKey key, float progress) { } +} diff --git a/Assets/Scripts/Fusion/BasicSpawner.cs.meta b/Assets/Scripts/Fusion/BasicSpawner.cs.meta new file mode 100644 index 00000000..cf96aa18 --- /dev/null +++ b/Assets/Scripts/Fusion/BasicSpawner.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 44bfaa339c82069418e72a14479a0212 \ No newline at end of file diff --git a/Assets/Scripts/Player Controller/InputReader.cs b/Assets/Scripts/Player Controller/InputReader.cs index 8224d3c8..e9bee96d 100644 --- a/Assets/Scripts/Player Controller/InputReader.cs +++ b/Assets/Scripts/Player Controller/InputReader.cs @@ -12,6 +12,13 @@ namespace OnlyScove.Scripts public virtual Vector2 ScrollInput { get; protected set; } public virtual bool IsSprintHeld { get; protected set; } // Left Shift public virtual bool IsAttackHeld { get; protected set; } // Left Mouse Button + + // THÊM HÀM NÀY ĐỂ ĐỒNG BỘ MẠNG + public void ApplyNetworkInput(Vector2 move, bool isSprint) + { + MoveInput = move; + IsSprintHeld = isSprint; + } // One-shot Events public event Action OnJumpEvent; // Space diff --git a/Assets/Scripts/Player Controller/PlayerMoveState.cs b/Assets/Scripts/Player Controller/PlayerMoveState.cs index 04880b00..38024e87 100644 --- a/Assets/Scripts/Player Controller/PlayerMoveState.cs +++ b/Assets/Scripts/Player Controller/PlayerMoveState.cs @@ -36,11 +36,16 @@ namespace OnlyScove.Scripts } Vector3 inputDir = new Vector3(input.x, 0, input.y).normalized; - Vector3 moveDirection = stateMachine.Cam != null ? stateMachine.Cam.PlanarRotation * inputDir : inputDir; + + // DÙNG HƯỚNG CAMERA TỪ MẠNG ĐỂ ĐỒNG BỘ MÁY CHỦ + Vector3 moveDirection = stateMachine.NetworkedCameraRotation * inputDir; + moveDirection.y = 0; // Đảm bảo không bay lên trời + moveDirection.Normalize(); - if (stateMachine.Cam != null) + if (stateMachine.Cam != null && stateMachine.Object.HasInputAuthority) { - Debug.Log($"[PlayerMoveState] View: {(stateMachine.Cam.PlanarRotation.eulerAngles.y)}, InputDir: {inputDir}, MoveDir: {moveDirection}, PlayerRot: {stateMachine.transform.rotation.eulerAngles.y}"); + // Log chỉ hiện ở máy khách để debug hướng xoay + // Debug.Log($"[Move] Input: {inputDir}, MoveDir: {moveDirection}"); } Vector3 velocity = moveDirection * stateMachine.WalkSpeed; diff --git a/Assets/Scripts/Player Controller/PlayerStateMachine.cs b/Assets/Scripts/Player Controller/PlayerStateMachine.cs index 3f13158a..0694a900 100644 --- a/Assets/Scripts/Player Controller/PlayerStateMachine.cs +++ b/Assets/Scripts/Player Controller/PlayerStateMachine.cs @@ -1,10 +1,11 @@ using System.Collections.Generic; using UnityEngine; +using Fusion; namespace OnlyScove.Scripts { [RequireComponent(typeof(CharacterController), typeof(InputReader), typeof(Animator))] - public class PlayerStateMachine : MonoBehaviour + public class PlayerStateMachine : NetworkBehaviour { [field: Header("References")] [field: SerializeField] public CharacterController Controller { get; private set; } @@ -36,16 +37,19 @@ namespace OnlyScove.Scripts [field: SerializeField] public float InteractionRange { get; private set; } = 2f; [field: SerializeField] public LayerMask InteractionMask { get; private set; } + [Networked] public Quaternion NetworkedCameraRotation { get; set; } + public float VelocityY { get; set; } public bool IsGrounded { get; private set; } public bool WasGrounded { get; private set; } - // Interaction system variables private List interactablesNearby = new List(); private int currentInteractableIndex = 0; public string CurrentStateName => currentState != null ? currentState.GetType().Name : "None"; + public static PlayerStateMachine Local { get; private set; } // THÊM DÒNG NÀY + private PlayerBaseState currentState; private bool hasControl = true; @@ -55,43 +59,56 @@ namespace OnlyScove.Scripts Input = GetComponent(); Anim = GetComponentInChildren(); Scanner = GetComponent(); - Cam = Camera.main?.GetComponent(); } - private void Start() + public override void Spawned() { + // BẮT BUỘC: Mọi máy (Server và Client) đều phải khởi tạo trạng thái ban đầu SwitchState(new PlayerIdleState(this)); - - // Subscribe to cycle events - Input.OnNextInteractEvent += OnNextInteract; - Input.OnPreviousInteractEvent += OnPreviousInteract; - } - private void OnDestroy() - { - if (Input != null) + if (Object.HasInputAuthority) { - Input.OnNextInteractEvent -= OnNextInteract; - Input.OnPreviousInteractEvent -= OnPreviousInteract; + Local = this; + + CameraController cameraController = GameObject.FindAnyObjectByType(); + if (cameraController != null) + { + Cam = cameraController; + Cam.followTarget = this.transform; + Cam.inputReader = this.Input; + } + + Input.OnNextInteractEvent += OnNextInteract; + Input.OnPreviousInteractEvent += OnPreviousInteract; } } - protected virtual void Update() + public override void FixedUpdateNetwork() { + if (Object == null) return; + + // 1. NHẬN DỮ LIỆU TỪ MẠNG + if (GetInput(out NetworkInputData data)) + { + // Gán phím bấm vào InputReader để các State (Move, Jump...) sử dụng + Input.ApplyNetworkInput(data.move, data.sprint); + + // CẬP NHẬT HƯỚNG CAMERA (Cho cả Server và Client) + // Đây là mấu chốt để Server tính toán hướng chạy đúng + NetworkedCameraRotation = data.rot; + } + + // 2. CHẶN MÁY KHÁCH KHÁC, NHƯNG CHO PHÉP SERVER VÀ LOCAL PLAYER CHẠY LOGIC + if (!Object.HasInputAuthority && !Runner.IsServer) return; if (!hasControl) return; WasGrounded = IsGrounded; CheckGround(); UpdateInteractablesList(); - - currentState?.Tick(Time.deltaTime); + currentState?.Tick(Runner.DeltaTime); } - private void FixedUpdate() - { - if (!hasControl) return; - currentState?.PhysicsTick(Time.fixedDeltaTime); - } + protected virtual void Update() { } private void CheckGround() { @@ -101,16 +118,8 @@ namespace OnlyScove.Scripts private void UpdateInteractablesList() { interactablesNearby.Clear(); - - // Sử dụng Scanner để tìm vật thể người chơi đang nhìn vào IInteractable target = Scanner.ScanForInteractable(InteractionRange, InteractionMask); - - if (target != null) - { - interactablesNearby.Add(target); - } - - // Reset index vì hiện tại Scanner trả về 1 kết quả chính xác nhất + if (target != null) interactablesNearby.Add(target); currentInteractableIndex = 0; } @@ -118,7 +127,6 @@ namespace OnlyScove.Scripts { if (interactablesNearby.Count <= 1) return; currentInteractableIndex = (currentInteractableIndex + 1) % interactablesNearby.Count; - Debug.Log($"[Interaction] Switched to: {interactablesNearby[currentInteractableIndex].InteractionPrompt}"); } private void OnPreviousInteract() @@ -126,7 +134,6 @@ namespace OnlyScove.Scripts if (interactablesNearby.Count <= 1) return; currentInteractableIndex--; if (currentInteractableIndex < 0) currentInteractableIndex = interactablesNearby.Count - 1; - Debug.Log($"[Interaction] Switched to: {interactablesNearby[currentInteractableIndex].InteractionPrompt}"); } public IInteractable GetInteractable() @@ -152,19 +159,13 @@ namespace OnlyScove.Scripts { hasControl = control; Controller.enabled = control; - if (!control) - { - Anim.SetFloat("Speed", 0f); - } + if (!control) Anim.SetFloat("Speed", 0f); } private void OnDrawGizmosSelected() { Gizmos.color = new Color(0, 1, 0, 0.5f); Gizmos.DrawSphere(transform.TransformPoint(GroundCheckOffset), GroundCheckRadius); - - Gizmos.color = Color.blue; - Gizmos.DrawWireSphere(transform.position + transform.forward * (InteractionRange / 2), InteractionRange); } } -} \ No newline at end of file +} diff --git a/Assets/Settings/PC_RPAsset.asset b/Assets/Settings/PC_RPAsset.asset index 010c4ffd..6e73de78 100644 --- a/Assets/Settings/PC_RPAsset.asset +++ b/Assets/Settings/PC_RPAsset.asset @@ -101,16 +101,16 @@ MonoBehaviour: m_Keys: [] m_Values: m_PrefilteringModeMainLightShadows: 3 - m_PrefilteringModeAdditionalLight: 4 - m_PrefilteringModeAdditionalLightShadows: 0 + m_PrefilteringModeAdditionalLight: 0 + m_PrefilteringModeAdditionalLightShadows: 2 m_PrefilterXRKeywords: 1 - m_PrefilteringModeForwardPlus: 1 + m_PrefilteringModeForwardPlus: 2 m_PrefilteringModeDeferredRendering: 0 - m_PrefilteringModeScreenSpaceOcclusion: 1 + m_PrefilteringModeScreenSpaceOcclusion: 2 m_PrefilterDebugKeywords: 1 - m_PrefilterWriteRenderingLayers: 0 + m_PrefilterWriteRenderingLayers: 1 m_PrefilterHDROutput: 1 - m_PrefilterAlphaOutput: 0 + m_PrefilterAlphaOutput: 1 m_PrefilterSSAODepthNormals: 0 m_PrefilterSSAOSourceDepthLow: 1 m_PrefilterSSAOSourceDepthMedium: 1 @@ -122,17 +122,17 @@ MonoBehaviour: m_PrefilterSSAOSampleCountHigh: 1 m_PrefilterDBufferMRT1: 1 m_PrefilterDBufferMRT2: 1 - m_PrefilterDBufferMRT3: 0 - m_PrefilterSoftShadowsQualityLow: 0 - m_PrefilterSoftShadowsQualityMedium: 0 - m_PrefilterSoftShadowsQualityHigh: 0 + m_PrefilterDBufferMRT3: 1 + m_PrefilterSoftShadowsQualityLow: 1 + m_PrefilterSoftShadowsQualityMedium: 1 + m_PrefilterSoftShadowsQualityHigh: 1 m_PrefilterSoftShadows: 0 m_PrefilterScreenCoord: 1 - m_PrefilterScreenSpaceIrradiance: 0 + m_PrefilterScreenSpaceIrradiance: 1 m_PrefilterNativeRenderPass: 1 m_PrefilterUseLegacyLightmaps: 0 - m_PrefilterBicubicLightmapSampling: 0 - m_PrefilterReflectionProbeRotation: 0 + m_PrefilterBicubicLightmapSampling: 1 + m_PrefilterReflectionProbeRotation: 1 m_PrefilterReflectionProbeBlending: 0 m_PrefilterReflectionProbeBoxProjection: 0 m_PrefilterReflectionProbeAtlas: 0 diff --git a/Assets/Settings/UniversalRenderPipelineGlobalSettings.asset b/Assets/Settings/UniversalRenderPipelineGlobalSettings.asset index 17db4432..b7271adb 100644 --- a/Assets/Settings/UniversalRenderPipelineGlobalSettings.asset +++ b/Assets/Settings/UniversalRenderPipelineGlobalSettings.asset @@ -68,7 +68,22 @@ MonoBehaviour: - rid: 5521631447830495244 - rid: 5521631447830495245 m_RuntimeSettings: - m_List: [] + m_List: + - rid: 6852985685364965378 + - rid: 6852985685364965379 + - rid: 6852985685364965380 + - rid: 6852985685364965381 + - rid: 6852985685364965384 + - rid: 6852985685364965385 + - rid: 6852985685364965392 + - rid: 6852985685364965394 + - rid: 8712630790384254976 + - rid: 5521631447830495233 + - rid: 5521631447830495234 + - rid: 5521631447830495237 + - rid: 5521631447830495238 + - rid: 5521631447830495243 + - rid: 5521631447830495245 m_AssetVersion: 10 m_ObsoleteDefaultVolumeProfile: {fileID: 0} m_RenderingLayerNames: diff --git a/Assets/TEST CUA TUAN/DEMO MAP.unity b/Assets/TEST CUA TUAN/DEMO MAP.unity index 60c3653a..344d7c08 100644 --- a/Assets/TEST CUA TUAN/DEMO MAP.unity +++ b/Assets/TEST CUA TUAN/DEMO MAP.unity @@ -130,6 +130,7 @@ GameObject: - component: {fileID: 60907914} - component: {fileID: 60907913} - component: {fileID: 60907912} + - component: {fileID: 60907915} m_Layer: 0 m_Name: Main Camera m_TagString: MainCamera @@ -211,6 +212,50 @@ Transform: m_Children: [] m_Father: {fileID: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &60907915 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 60907911} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: a79441f348de89743a2939f4d699eac1, type: 3} + m_Name: + m_EditorClassIdentifier: Unity.RenderPipelines.Universal.Runtime::UnityEngine.Rendering.Universal.UniversalAdditionalCameraData + m_RenderShadows: 1 + m_RequiresDepthTextureOption: 2 + m_RequiresOpaqueTextureOption: 2 + m_CameraType: 0 + m_Cameras: [] + m_RendererIndex: -1 + m_VolumeLayerMask: + serializedVersion: 2 + m_Bits: 1 + m_VolumeTrigger: {fileID: 0} + m_VolumeFrameworkUpdateModeOption: 2 + m_RenderPostProcessing: 0 + m_Antialiasing: 0 + m_AntialiasingQuality: 2 + m_StopNaN: 0 + m_Dithering: 0 + m_ClearDepth: 1 + m_AllowXRRendering: 1 + m_AllowHDROutput: 1 + m_UseScreenCoordOverride: 0 + m_ScreenSizeOverride: {x: 0, y: 0, z: 0, w: 0} + m_ScreenCoordScaleBias: {x: 0, y: 0, z: 0, w: 0} + m_RequiresDepthTexture: 0 + m_RequiresColorTexture: 0 + m_TaaSettings: + m_Quality: 3 + m_FrameInfluence: 0.1 + m_JitterScale: 1 + m_MipBias: 0 + m_VarianceClampScale: 0.9 + m_ContrastAdaptiveSharpening: 0 + m_Version: 2 --- !u!1 &1343192606 GameObject: m_ObjectHideFlags: 0 @@ -221,6 +266,7 @@ GameObject: m_Component: - component: {fileID: 1343192608} - component: {fileID: 1343192607} + - component: {fileID: 1343192609} m_Layer: 0 m_Name: Directional Light m_TagString: Untagged @@ -308,6 +354,35 @@ Transform: m_Children: [] m_Father: {fileID: 0} m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0} +--- !u!114 &1343192609 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1343192606} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 474bcb49853aa07438625e644c072ee6, type: 3} + m_Name: + m_EditorClassIdentifier: Unity.RenderPipelines.Universal.Runtime::UnityEngine.Rendering.Universal.UniversalAdditionalLightData + m_UsePipelineSettings: 1 + m_AdditionalLightsShadowResolutionTier: 2 + m_CustomShadowLayers: 0 + m_LightCookieSize: {x: 1, y: 1} + m_LightCookieOffset: {x: 0, y: 0} + m_SoftShadowQuality: 0 + m_RenderingLayersMask: + serializedVersion: 0 + m_Bits: 1 + m_ShadowRenderingLayersMask: + serializedVersion: 0 + m_Bits: 1 + m_Version: 4 + m_LightLayerMask: 1 + m_ShadowLayerMask: 1 + m_RenderingLayers: 1 + m_ShadowRenderingLayers: 1 --- !u!1660057539 &9223372036854775807 SceneRoots: m_ObjectHideFlags: 0 diff --git a/Packages/manifest.json b/Packages/manifest.json index 7af4181a..7aa97163 100644 --- a/Packages/manifest.json +++ b/Packages/manifest.json @@ -6,6 +6,7 @@ "com.unity.ide.visualstudio": "2.0.26", "com.unity.inputsystem": "1.18.0", "com.unity.multiplayer.center": "1.0.1", + "com.unity.nuget.mono-cecil": "1.10.2", "com.unity.render-pipelines.universal": "17.3.0", "com.unity.test-framework": "1.6.0", "com.unity.timeline": "1.8.10", diff --git a/ProjectSettings/EditorBuildSettings.asset b/ProjectSettings/EditorBuildSettings.asset index d057ba3d..aea1940a 100644 --- a/ProjectSettings/EditorBuildSettings.asset +++ b/ProjectSettings/EditorBuildSettings.asset @@ -6,6 +6,12 @@ EditorBuildSettings: serializedVersion: 2 m_Scenes: - enabled: 1 + path: Assets/Scove/DEMO FUSION.unity + guid: c3d4d808eec835545b53364265c55e97 + - enabled: 1 + path: Assets/Scove/Player Movement.unity + guid: 18e8e6bc4985a3b46a52116295088fce + - enabled: 0 path: Assets/Scenes/SampleScene.unity guid: 99c9720ab356a0642a771bea13969a05 m_configObjects: diff --git a/ProjectSettings/PackageManagerSettings.asset b/ProjectSettings/PackageManagerSettings.asset index be4a7974..6306c875 100644 --- a/ProjectSettings/PackageManagerSettings.asset +++ b/ProjectSettings/PackageManagerSettings.asset @@ -2,7 +2,7 @@ %TAG !u! tag:unity3d.com,2011: --- !u!114 &1 MonoBehaviour: - m_ObjectHideFlags: 61 + m_ObjectHideFlags: 53 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} @@ -11,12 +11,14 @@ MonoBehaviour: m_EditorHideFlags: 0 m_Script: {fileID: 13964, guid: 0000000000000000e000000000000000, type: 0} m_Name: - m_EditorClassIdentifier: - m_EnablePreviewPackages: 0 - m_EnablePackageDependencies: 0 + m_EditorClassIdentifier: UnityEditor.dll::UnityEditor.PackageManager.UI.Internal.PackageManagerProjectSettings + m_EnablePreReleasePackages: 0 m_AdvancedSettingsExpanded: 1 m_ScopedRegistriesSettingsExpanded: 1 + m_SeeAllPackageVersions: 0 + m_DismissPreviewPackagesInUse: 0 oneTimeWarningShown: 0 + oneTimePackageErrorsPopUpShown: 0 m_Registries: - m_Id: main m_Name: @@ -24,20 +26,15 @@ MonoBehaviour: m_Scopes: [] m_IsDefault: 1 m_Capabilities: 7 + m_ConfigSource: 0 + m_Compliance: + m_Status: 0 + m_Violations: [] m_UserSelectedRegistryName: m_UserAddingNewScopedRegistry: 0 m_RegistryInfoDraft: - m_ErrorMessage: - m_Original: - m_Id: - m_Name: - m_Url: - m_Scopes: [] - m_IsDefault: 0 - m_Capabilities: 0 m_Modified: 0 - m_Name: - m_Url: - m_Scopes: - - - m_SelectedScopeIndex: 0 + m_ErrorMessage: + m_UserModificationsInstanceId: -902 + m_OriginalInstanceId: -904 + m_LoadAssets: 0 diff --git a/ProjectSettings/ProjectSettings.asset b/ProjectSettings/ProjectSettings.asset index 89aa5a5c..4a2ee3a7 100644 --- a/ProjectSettings/ProjectSettings.asset +++ b/ProjectSettings/ProjectSettings.asset @@ -83,7 +83,7 @@ PlayerSettings: androidApplicationEntry: 2 defaultIsNativeResolution: 1 macRetinaSupport: 1 - runInBackground: 0 + runInBackground: 1 muteOtherAudioSources: 0 Prepare IOS For Recording: 0 Force IOS Speakers When Recording: 0 @@ -524,7 +524,10 @@ PlayerSettings: m_Height: 720 m_Kind: 1 m_SubKind: - m_BuildTargetBatching: [] + m_BuildTargetBatching: + - m_BuildTarget: Standalone + m_StaticBatching: 1 + m_DynamicBatching: 0 m_BuildTargetShaderSettings: [] m_BuildTargetGraphicsJobs: [] m_BuildTargetGraphicsJobMode: [] @@ -825,7 +828,8 @@ PlayerSettings: webGLCloseOnQuit: 0 webWasm2023: 0 webEnableSubmoduleStrippingCompatibility: 0 - scriptingDefineSymbols: {} + scriptingDefineSymbols: + Standalone: FUSION_LOGLEVEL_INFO;FUSION_WEAVER;FUSION2;FUSION_2;FUSION_2_0;FUSION_2_0_11;FUSION_2_OR_NEWER;FUSION_2_0_OR_NEWER additionalCompilerArguments: {} platformArchitecture: {} scriptingBackend: @@ -836,7 +840,7 @@ PlayerSettings: managedStrippingLevel: {} incrementalIl2cppBuild: {} suppressCommonWarnings: 1 - allowUnsafeCode: 0 + allowUnsafeCode: 1 useDeterministicCompilation: 1 additionalIl2CppArgs: scriptingRuntimeVersion: 1 diff --git a/ProjectSettings/UnityConnectSettings.asset b/ProjectSettings/UnityConnectSettings.asset index 029ad8b9..7a17e8f7 100644 --- a/ProjectSettings/UnityConnectSettings.asset +++ b/ProjectSettings/UnityConnectSettings.asset @@ -4,7 +4,7 @@ UnityConnectSettings: m_ObjectHideFlags: 0 serializedVersion: 1 - m_Enabled: 0 + m_Enabled: 1 m_TestMode: 0 m_EventOldUrl: https://api.uca.cloud.unity3d.com/v1/events m_EventUrl: https://cdp.cloud.unity3d.com/v1/events