Initial Push

This commit is contained in:
JesusLuvsYooh 2024-01-16 08:58:48 +00:00
parent 98061c0f59
commit 854de5644d
135 changed files with 6127 additions and 143 deletions

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: faf041ce44aaa4df69ab8e633c2b21ec
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,366 @@
# [1.1.0-mirror.11](https://github.com/James-Frowen/Mirage.Profiler/compare/v1.1.0-mirror.10...v1.1.0-mirror.11) (2023-08-13)
### Bug Fixes
* adding define to support older versions of mirror ([86c29cd](https://github.com/James-Frowen/Mirage.Profiler/commit/86c29cd70ca57913d7ec5678cb126abd3737beac))
# [1.1.0-mirror.10](https://github.com/James-Frowen/Mirage.Profiler/compare/v1.1.0-mirror.9...v1.1.0-mirror.10) (2022-09-23)
### Features
* adding support for counter in built player ([506e904](https://github.com/James-Frowen/Mirage.Profiler/commit/506e9048571ba3e75ec9561ca1144db5685245c5))
# [1.1.0-mirror.9](https://github.com/James-Frowen/Mirage.Profiler/compare/v1.1.0-mirror.8...v1.1.0-mirror.9) (2022-09-08)
### Features
* names for mirror rpcs ([#5](https://github.com/James-Frowen/Mirage.Profiler/issues/5)) ([6bd5fa5](https://github.com/James-Frowen/Mirage.Profiler/commit/6bd5fa5b24084fc8a7a0f043e1c5f083ce1a00e2))
# [1.1.0-mirror.8](https://github.com/James-Frowen/Mirage.Profiler/compare/v1.1.0-mirror.7...v1.1.0-mirror.8) (2022-09-08)
### Bug Fixes
* fixes null ref when sort header is null ([9848b81](https://github.com/James-Frowen/Mirage.Profiler/commit/9848b818713228f3b3784fe66fda58b55de9d095)), closes [#4](https://github.com/James-Frowen/Mirage.Profiler/issues/4)
* fixing header being squashed if vertical scroll is full ([89e651e](https://github.com/James-Frowen/Mirage.Profiler/commit/89e651e26e3c55415648e6fa6f1002c32132ddfb))
* fixing null ref when group message is false ([6d3dfbf](https://github.com/James-Frowen/Mirage.Profiler/commit/6d3dfbfe365196529ba040ad533773e7f5b9aa03)), closes [#6](https://github.com/James-Frowen/Mirage.Profiler/issues/6)
* stopping exception if rpc name does not contain dot ([#3](https://github.com/James-Frowen/Mirage.Profiler/issues/3)) ([4f7a1bd](https://github.com/James-Frowen/Mirage.Profiler/commit/4f7a1bd40333449dfb82969d7ef76b7c62801167))
### Features
* adding group message to save data ([12c3313](https://github.com/James-Frowen/Mirage.Profiler/commit/12c3313e6b557d6afb25844783287a38c22c6878))
# [1.1.0-mirror.7](https://github.com/James-Frowen/Mirage.Profiler/compare/v1.1.0-mirror.6...v1.1.0-mirror.7) (2022-09-07)
### Bug Fixes
* only add NetworkDiagnostics events for first static instance ([#2](https://github.com/James-Frowen/Mirage.Profiler/issues/2)) ([d827c4d](https://github.com/James-Frowen/Mirage.Profiler/commit/d827c4d4296915ac237d10540b7daf4ece5442fd))
# [1.1.0-mirror.6](https://github.com/James-Frowen/Mirage.Profiler/compare/v1.1.0-mirror.5...v1.1.0-mirror.6) (2022-08-19)
### Bug Fixes
* removing mirage from paackage.json ([0b0032b](https://github.com/James-Frowen/Mirage.Profiler/commit/0b0032b8b449214c20b1836ecb21e70b3696eebb))
# [1.1.0-mirror.5](https://github.com/James-Frowen/Mirage.Profiler/compare/v1.1.0-mirror.4...v1.1.0-mirror.5) (2022-08-08)
### Bug Fixes
* movng files into package ([b1d50bf](https://github.com/James-Frowen/Mirage.Profiler/commit/b1d50bf7137083e0c071277f6880b67455ef1761))
# [1.1.0-mirror.4](https://github.com/James-Frowen/Mirage.Profiler/compare/v1.1.0-mirror.3...v1.1.0-mirror.4) (2022-08-08)
### Bug Fixes
* fixing version not being added by CI ([08d48cf](https://github.com/James-Frowen/Mirage.Profiler/commit/08d48cfae980798cda5e8334f0b225c3cc640dcc))
# [1.1.0-mirror.3](https://github.com/James-Frowen/Mirage.Profiler/compare/v1.1.0-mirror.2...v1.1.0-mirror.3) (2022-08-07)
### Bug Fixes
* adding missing meta file ([318e209](https://github.com/James-Frowen/Mirage.Profiler/commit/318e20989a7690ea886ab442e15141cdea96261b))
# [1.1.0-mirror.2](https://github.com/James-Frowen/Mirage.Profiler/compare/v1.1.0-mirror.1...v1.1.0-mirror.2) (2022-08-07)
### Bug Fixes
* adding runtime folder to fix package layout ([e606a0f](https://github.com/James-Frowen/Mirage.Profiler/commit/e606a0fa356c8a837dff5809dfa0fa85f0267d6f))
# [1.1.0-mirror.1](https://github.com/James-Frowen/Mirage.Profiler/compare/v1.0.0...v1.1.0-mirror.1) (2022-08-07)
### Bug Fixes
* fixing error when rpc name is empty ([2502325](https://github.com/James-Frowen/Mirage.Profiler/commit/25023256734cd730cc95b06d1ebad8f2677324e7))
* fixing module names for mirror ([94d77b1](https://github.com/James-Frowen/Mirage.Profiler/commit/94d77b129964559a1f8e68b49d9b345768c81532))
### Features
* example scene for mirror ([c707be8](https://github.com/James-Frowen/Mirage.Profiler/commit/c707be8e9d2d5c71cf609a8f11d1174d4cd0c1af))
* Mirror Support ([1675f77](https://github.com/James-Frowen/Mirage.Profiler/commit/1675f776264c10421802028f1a30b422fdb81215))
# 1.0.0 (2022-08-07)
### Bug Fixes
* adding note about maybe missing NetworkProfilerBehaviour ([91b2420](https://github.com/James-Frowen/Mirage.Profiler/commit/91b242030cf64cc7a80bd0b5e8f3e38317e6a246))
* changing order to be int.max ([4c40e61](https://github.com/James-Frowen/Mirage.Profiler/commit/4c40e61bc23884dff13de35db84dd8ed4abc0724))
* clearing byte count at end of frame ([f2270a8](https://github.com/James-Frowen/Mirage.Profiler/commit/f2270a8960543a8bfd70fbc2a8fc628e788a1a43))
* disabling debug message by default ([7f16136](https://github.com/James-Frowen/Mirage.Profiler/commit/7f161365b0f54d32e34773b3eb5645046b12bed4))
* enabling example bullet on server ([ea15bf6](https://github.com/James-Frowen/Mirage.Profiler/commit/ea15bf6301b736a328f51edadf5f127cdf704729))
* fixing client spawn in example ([5ff2c0a](https://github.com/James-Frowen/Mirage.Profiler/commit/5ff2c0ae7ed0a3c89baff1aed7316761d7342338))
* fixing crash when closing unity ([cf2513a](https://github.com/James-Frowen/Mirage.Profiler/commit/cf2513ac150fc67533d6c41e9e44ec894d81ca0e))
* fixing define to hide debug toggle ([8d22bf7](https://github.com/James-Frowen/Mirage.Profiler/commit/8d22bf7cd6ccf32b0af0866235587c136d09072b))
* fixing example auto connect ([6b5163e](https://github.com/James-Frowen/Mirage.Profiler/commit/6b5163e9577e0b8304d78a5ec87df7650f63fe9d))
* fixing example where field was not being used ([4417cdc](https://github.com/James-Frowen/Mirage.Profiler/commit/4417cdcf0504723ba25e8e342c1c91757fd11743))
* fixing frame index for saved messages ([49d9e70](https://github.com/James-Frowen/Mirage.Profiler/commit/49d9e705b7db99e05b3089eed502553369752522))
* fixing groups not un-expanding ([196feae](https://github.com/James-Frowen/Mirage.Profiler/commit/196feae6e7e49f54478f575c7c3668160b01e0e3))
* fixing index out of range on first frame ([036f104](https://github.com/James-Frowen/Mirage.Profiler/commit/036f10487e469c4cf39fe6b431c2f22499157b6d))
* fixing player count sample being skipped ([740b4f8](https://github.com/James-Frowen/Mirage.Profiler/commit/740b4f8816dfd783e7465856f0380c7fc8b9b2f2))
* fixing profier behaviour ([53f0614](https://github.com/James-Frowen/Mirage.Profiler/commit/53f06143befb02caa8c415a05fb10d53bf484906))
* fixing ProfilerCounter ([81344fe](https://github.com/James-Frowen/Mirage.Profiler/commit/81344fe871224a773d014b9bb76f028ff4aba21d))
* fixing sort arrow not updated ([3ced312](https://github.com/James-Frowen/Mirage.Profiler/commit/3ced312f20f5c24657bd635fc93ced5b40ffca02))
* fixing unity skipping profiler frames becuase unity is buggy ([f452ac4](https://github.com/James-Frowen/Mirage.Profiler/commit/f452ac4a1eb45bf4713d1769895ec431c24550ba))
* init array with non-null elements ([b3c3276](https://github.com/James-Frowen/Mirage.Profiler/commit/b3c32761b0e6a62ff4180fde276131709d1956d6))
* making example clients auto reconnect ([cbf27b7](https://github.com/James-Frowen/Mirage.Profiler/commit/cbf27b7abdb0ee955b77c9d7f977241b1ad3e7bc))
* making header scroll Horizontally ([5eac0f7](https://github.com/James-Frowen/Mirage.Profiler/commit/5eac0f71a8569c580bc03587d63b9fea418be326))
* making message info fields serializable ([81fb20d](https://github.com/James-Frowen/Mirage.Profiler/commit/81fb20da066d271fb72c56a763ccd0fe106cbc1a))
* naming debug name shorter ([36f2d6a](https://github.com/James-Frowen/Mirage.Profiler/commit/36f2d6a5c76e6391ba16d44d8f09d44625be9d11))
* removing debug logs ([0bb90ef](https://github.com/James-Frowen/Mirage.Profiler/commit/0bb90ef9dc674e5596ef79994259f96fd378f2d7))
* removing prefix from summary labels ([3841a9e](https://github.com/James-Frowen/Mirage.Profiler/commit/3841a9e3c65ee7432a4d2020ed03a4f7557f14d6))
* removing verbose log ([0cfd3bf](https://github.com/James-Frowen/Mirage.Profiler/commit/0cfd3bf4d9364871da49dcbf9a084179e67f4a20))
* scroll bar for table ([af7c83b](https://github.com/James-Frowen/Mirage.Profiler/commit/af7c83b13fb13aff81f7fc1648eb48597849525b))
* setting max connections in example to 1000 ([94d06ea](https://github.com/James-Frowen/Mirage.Profiler/commit/94d06ea9723cadf1214b0a92ece59f2788389305))
* stoping sort mode going back to none ([7745700](https://github.com/James-Frowen/Mirage.Profiler/commit/77457001775bc6f6889c7c8f4f86bacdadbd90e1))
* updating mirage for fix ([e367c4a](https://github.com/James-Frowen/Mirage.Profiler/commit/e367c4a3c60e065effc80d4896998d5ef6c310c9))
### Features
* adding basic column sort ([39d4519](https://github.com/James-Frowen/Mirage.Profiler/commit/39d4519ce8a2fe086189bf12cdb34ce3e1820fe2))
* adding object name column ([239e069](https://github.com/James-Frowen/Mirage.Profiler/commit/239e069dd0aecc080413325eb98edf7ccc3b1eb1))
* adding option to group messages by type ([2e5fc27](https://github.com/James-Frowen/Mirage.Profiler/commit/2e5fc276bacc93f45c444a3b637d0d716bc1db1b))
* adding option to run example outside of editor ([5a75b65](https://github.com/James-Frowen/Mirage.Profiler/commit/5a75b65b0f3f3075d21c2cdd0d821655bf2a47e3))
* adding rpc name to message info ([d90d842](https://github.com/James-Frowen/Mirage.Profiler/commit/d90d842bc57defe10b7af8b1b2b6c2bd70566df1))
* adding rpc name to table ([63722e2](https://github.com/James-Frowen/Mirage.Profiler/commit/63722e29458a522d10afa96d6a75831a5d084495))
* adding shooting to example ([6a86354](https://github.com/James-Frowen/Mirage.Profiler/commit/6a86354895f2effe3689b4638f195d8536107a19))
* adding sorting for groups ([2dec9a5](https://github.com/James-Frowen/Mirage.Profiler/commit/2dec9a5bfef76c34dff68a4aa904590c0924dc86))
* expandable groups for message types ([001de86](https://github.com/James-Frowen/Mirage.Profiler/commit/001de86db446769d12880b35e64147bfda84f755))
* first prerelease ([7fbd8ce](https://github.com/James-Frowen/Mirage.Profiler/commit/7fbd8ceb9223321abc514a3178d7703fe92f9497))
* first release ([55b2b39](https://github.com/James-Frowen/Mirage.Profiler/commit/55b2b395e10da952101ae101f2cf2ab1acefe664))
* only showing rpc short name, full name in tooltip ([9f71ae7](https://github.com/James-Frowen/Mirage.Profiler/commit/9f71ae78a546b0c8543d4295e8c2944a3ec458bd))
* putting table header above scrollview ([a7d52ff](https://github.com/James-Frowen/Mirage.Profiler/commit/a7d52fff785b0f625a8af5abd920cc06d0a38053))
* returning saved message instead of ones from counter ([6e894fa](https://github.com/James-Frowen/Mirage.Profiler/commit/6e894faec6701346f738925057cf360554e74860))
* saving group expanded in save data ([70e4da9](https://github.com/James-Frowen/Mirage.Profiler/commit/70e4da92c1d8e35518eaa4656ff87d5276843a62))
* saving message for frames ([131257a](https://github.com/James-Frowen/Mirage.Profiler/commit/131257aa7c603da085e10c8d5103a6776c7a8a38))
* using sort from save data ([6bded42](https://github.com/James-Frowen/Mirage.Profiler/commit/6bded42b36bc8f7767cce355de220157c630093f))
* working save data for message table ([0ab9197](https://github.com/James-Frowen/Mirage.Profiler/commit/0ab9197870fd581f68a1b405f3572c7b04c9f39e))
### Performance Improvements
* replacing FindObjectOfType with reference given in setup ([71e6712](https://github.com/James-Frowen/Mirage.Profiler/commit/71e671247dd5394d806ca1017b91aa2faa68eea8))
# [1.0.0-beta.24](https://github.com/James-Frowen/Mirage.Profiler/compare/v1.0.0-beta.23...v1.0.0-beta.24) (2022-08-07)
### Bug Fixes
* making header scroll Horizontally ([7ba9d26](https://github.com/James-Frowen/Mirage.Profiler/commit/7ba9d2667afcf32269c979f3df4769e3e86a3d6c))
### Features
* adding rpc name to message info ([52a7bb4](https://github.com/James-Frowen/Mirage.Profiler/commit/52a7bb4927e009eec5839e30c94bee1345e4a4b9))
* adding rpc name to table ([fd1e762](https://github.com/James-Frowen/Mirage.Profiler/commit/fd1e762a22e9ce5ec10f92105613f8f11f5aad6d))
* only showing rpc short name, full name in tooltip ([e346a6f](https://github.com/James-Frowen/Mirage.Profiler/commit/e346a6f7d403113057fd067e89cb470e594fd4c3))
# [1.0.0-beta.23](https://github.com/James-Frowen/Mirage.Profiler/compare/v1.0.0-beta.22...v1.0.0-beta.23) (2022-08-07)
### Bug Fixes
* fixing sort arrow not updated ([bf8cd9a](https://github.com/James-Frowen/Mirage.Profiler/commit/bf8cd9adeec12ca2898918e40c611e040e58013e))
# [1.0.0-beta.22](https://github.com/James-Frowen/Mirage.Profiler/compare/v1.0.0-beta.21...v1.0.0-beta.22) (2022-08-07)
### Features
* saving group expanded in save data ([557538c](https://github.com/James-Frowen/Mirage.Profiler/commit/557538c6cf9d06b456def4e34c0b8add6683bd7b))
# [1.0.0-beta.21](https://github.com/James-Frowen/Mirage.Profiler/compare/v1.0.0-beta.20...v1.0.0-beta.21) (2022-08-07)
### Bug Fixes
* fixing index out of range on first frame ([b1a4a6b](https://github.com/James-Frowen/Mirage.Profiler/commit/b1a4a6b7ca21154d32758fcb577ccd8c22c55d7e))
* fixing player count sample being skipped ([04d7341](https://github.com/James-Frowen/Mirage.Profiler/commit/04d7341d0e3cbf7a91208a0b5f0df567644d7030))
* setting max connections in example to 1000 ([94dc143](https://github.com/James-Frowen/Mirage.Profiler/commit/94dc14329c3c1799f85c210f11ece1879bb6fa47))
### Features
* adding object name column ([d3de7e3](https://github.com/James-Frowen/Mirage.Profiler/commit/d3de7e334530068cc7776fb000afbaaf21dd30b4))
### Performance Improvements
* replacing FindObjectOfType with reference given in setup ([df9141d](https://github.com/James-Frowen/Mirage.Profiler/commit/df9141df18139a1020222c60402c4fedd3259a38))
# [1.0.0-beta.20](https://github.com/James-Frowen/Mirage.Profiler/compare/v1.0.0-beta.19...v1.0.0-beta.20) (2022-08-06)
### Bug Fixes
* fixing example where field was not being used ([1e6f605](https://github.com/James-Frowen/Mirage.Profiler/commit/1e6f605e8cd9953daf6f9482c72dabe069c7b300))
# [1.0.0-beta.19](https://github.com/James-Frowen/Mirage.Profiler/compare/v1.0.0-beta.18...v1.0.0-beta.19) (2022-08-06)
### Bug Fixes
* removing verbose log ([fe0541a](https://github.com/James-Frowen/Mirage.Profiler/commit/fe0541a7859eadd5ce390c6449dd83694c058d08))
# [1.0.0-beta.18](https://github.com/James-Frowen/Mirage.Profiler/compare/v1.0.0-beta.17...v1.0.0-beta.18) (2022-08-06)
### Bug Fixes
* fixing crash when closing unity ([eacc82c](https://github.com/James-Frowen/Mirage.Profiler/commit/eacc82ca37b4bce169ed03ae2336474af1936760))
* fixing unity skipping profiler frames becuase unity is buggy ([4e23dff](https://github.com/James-Frowen/Mirage.Profiler/commit/4e23dffc07ed2d7f48dd9a0d4257806c93ba7c3f))
# [1.0.0-beta.17](https://github.com/James-Frowen/Mirage.Profiler/compare/v1.0.0-beta.16...v1.0.0-beta.17) (2022-08-06)
### Bug Fixes
* removing debug logs ([3c0d019](https://github.com/James-Frowen/Mirage.Profiler/commit/3c0d01905c1c3b659a4e5453c79d63a3ba506bfe))
# [1.0.0-beta.16](https://github.com/James-Frowen/Mirage.Profiler/compare/v1.0.0-beta.15...v1.0.0-beta.16) (2022-08-06)
### Bug Fixes
* clearing byte count at end of frame ([d185ad4](https://github.com/James-Frowen/Mirage.Profiler/commit/d185ad43c9d716aa44bbb715b3eed89b16d19706))
* enabling example bullet on server ([bc58f72](https://github.com/James-Frowen/Mirage.Profiler/commit/bc58f72ff2946b1f4a96821d36e3dfc88fed0f82))
* fixing client spawn in example ([348e98b](https://github.com/James-Frowen/Mirage.Profiler/commit/348e98b4de4c36be9c118cfa6242827126422cd9))
* fixing example auto connect ([65968de](https://github.com/James-Frowen/Mirage.Profiler/commit/65968ded4aa7ea2fd0c04739cb0e272709ff7d67))
* fixing frame index for saved messages ([1204a1f](https://github.com/James-Frowen/Mirage.Profiler/commit/1204a1f4f32d497a260a4720937edde67d6cd9ed))
* fixing profier behaviour ([bc91dc7](https://github.com/James-Frowen/Mirage.Profiler/commit/bc91dc78b71d9893adb48dac991433631fb7a34f))
* fixing ProfilerCounter ([9a7f62a](https://github.com/James-Frowen/Mirage.Profiler/commit/9a7f62a4d4ce2ff42bfb1cd0ad0b712ac7b46ac7))
* init array with non-null elements ([f69c268](https://github.com/James-Frowen/Mirage.Profiler/commit/f69c2684de2b242fe337b051d274d84b48577ebc))
* making example clients auto reconnect ([8ca5eee](https://github.com/James-Frowen/Mirage.Profiler/commit/8ca5eeeb102e54e12a33490c878fd35156cb6ff0))
* making message info fields serializable ([4cbc905](https://github.com/James-Frowen/Mirage.Profiler/commit/4cbc905e513a62ee17ddf660d524363464e080c8))
### Features
* adding option to run example outside of editor ([5de8073](https://github.com/James-Frowen/Mirage.Profiler/commit/5de80731e41513564ccf20ba67fb29eb4e01adf1))
* adding shooting to example ([543374a](https://github.com/James-Frowen/Mirage.Profiler/commit/543374a94c0a65362fcabaf0ca190d49163e372d))
* returning saved message instead of ones from counter ([541ec66](https://github.com/James-Frowen/Mirage.Profiler/commit/541ec663cda04fb2b10c73822a6e28bd221ac6b7))
* saving message for frames ([519f256](https://github.com/James-Frowen/Mirage.Profiler/commit/519f25695ad0ca7b113a65de14c7feb93c42d4f0))
* working save data for message table ([ffaaef9](https://github.com/James-Frowen/Mirage.Profiler/commit/ffaaef97e4ee05751be419e642f61b923857486f))
# [1.0.0-beta.15](https://github.com/James-Frowen/Mirage.Profiler/compare/v1.0.0-beta.14...v1.0.0-beta.15) (2022-08-05)
### Bug Fixes
* stoping sort mode going back to none ([5c85668](https://github.com/James-Frowen/Mirage.Profiler/commit/5c856680631869769e41206c72fb024e90e74cd9))
### Features
* using sort from save data ([cdc65ad](https://github.com/James-Frowen/Mirage.Profiler/commit/cdc65ad3d72791e74d51d76df700eb3993a4576d))
# [1.0.0-beta.14](https://github.com/James-Frowen/Mirage.Profiler/compare/v1.0.0-beta.13...v1.0.0-beta.14) (2022-08-03)
### Bug Fixes
* adding note about maybe missing NetworkProfilerBehaviour ([5120029](https://github.com/James-Frowen/Mirage.Profiler/commit/5120029c3d30f5a141d0a86e28501dc52a6d7a7d))
# [1.0.0-beta.13](https://github.com/James-Frowen/Mirage.Profiler/compare/v1.0.0-beta.12...v1.0.0-beta.13) (2022-08-03)
### Bug Fixes
* changing order to be int.max ([31cbd2f](https://github.com/James-Frowen/Mirage.Profiler/commit/31cbd2ff5b9b9a476d9c9562a19c7d3aec894eb1))
# [1.0.0-beta.12](https://github.com/James-Frowen/Mirage.Profiler/compare/v1.0.0-beta.11...v1.0.0-beta.12) (2022-07-28)
### Bug Fixes
* fixing groups not un-expanding ([17c27c2](https://github.com/James-Frowen/Mirage.Profiler/commit/17c27c24a22f40a9e240e9f0a68933ad531bd192))
# [1.0.0-beta.11](https://github.com/James-Frowen/Mirage.Profiler/compare/v1.0.0-beta.10...v1.0.0-beta.11) (2022-07-28)
### Features
* putting table header above scrollview ([6d52a00](https://github.com/James-Frowen/Mirage.Profiler/commit/6d52a004e8859d8930063b4610f24512d3016113))
# [1.0.0-beta.10](https://github.com/James-Frowen/Mirage.Profiler/compare/v1.0.0-beta.9...v1.0.0-beta.10) (2022-07-28)
### Features
* adding sorting for groups ([bd655e8](https://github.com/James-Frowen/Mirage.Profiler/commit/bd655e89338f1f03b32f600288be6dd06edc6a8f))
# [1.0.0-beta.9](https://github.com/James-Frowen/Mirage.Profiler/compare/v1.0.0-beta.8...v1.0.0-beta.9) (2022-07-27)
### Features
* adding basic column sort ([1ce56a8](https://github.com/James-Frowen/Mirage.Profiler/commit/1ce56a878444597bf4946bf573264c9ebd156581))
# [1.0.0-beta.8](https://github.com/James-Frowen/Mirage.Profiler/compare/v1.0.0-beta.7...v1.0.0-beta.8) (2022-07-26)
### Bug Fixes
* disabling debug message by default ([ad1e082](https://github.com/James-Frowen/Mirage.Profiler/commit/ad1e08243d1f4e443468b32682eb44ede8c3ffa4))
* removing prefix from summary labels ([6836ff5](https://github.com/James-Frowen/Mirage.Profiler/commit/6836ff547c7f2340baca8a68064c7556ef0ccfe6))
# [1.0.0-beta.7](https://github.com/James-Frowen/Mirage.Profiler/compare/v1.0.0-beta.6...v1.0.0-beta.7) (2022-07-26)
### Features
* expandable groups for message types ([20f949c](https://github.com/James-Frowen/Mirage.Profiler/commit/20f949ceee10951cf4215451afbbea33d3f0d63f))
# [1.0.0-beta.6](https://github.com/James-Frowen/Mirage.Profiler/compare/v1.0.0-beta.5...v1.0.0-beta.6) (2022-07-25)
### Bug Fixes
* fixing define to hide debug toggle ([5798c5c](https://github.com/James-Frowen/Mirage.Profiler/commit/5798c5c0f13c5614c8ce507736820ac6bfbfa04a))
# [1.0.0-beta.5](https://github.com/James-Frowen/Mirage.Profiler/compare/v1.0.0-beta.4...v1.0.0-beta.5) (2022-07-25)
### Features
* adding option to group messages by type ([9392ebb](https://github.com/James-Frowen/Mirage.Profiler/commit/9392ebb6d324db1cc4fb09f28909acde371ba198))
# [1.0.0-beta.4](https://github.com/James-Frowen/Mirage.Profiler/compare/v1.0.0-beta.3...v1.0.0-beta.4) (2022-07-23)
### Bug Fixes
* naming debug name shorter ([1a2ad87](https://github.com/James-Frowen/Mirage.Profiler/commit/1a2ad8765b1cfbd9a4033da6de7a18e1c38d3632))
# [1.0.0-beta.3](https://github.com/James-Frowen/Mirage.Profiler/compare/v1.0.0-beta.2...v1.0.0-beta.3) (2022-07-23)
### Bug Fixes
* scroll bar for table ([d7b697f](https://github.com/James-Frowen/Mirage.Profiler/commit/d7b697f1556300a22773f4f9e04718c7d228203e))
# [1.0.0-beta.2](https://github.com/James-Frowen/Mirage.Profiler/compare/v1.0.0-beta.1...v1.0.0-beta.2) (2022-07-23)
### Bug Fixes
* updating mirage for fix ([69ded5e](https://github.com/James-Frowen/Mirage.Profiler/commit/69ded5e7ac44130226362da61bddde2f275d0b98))
# 1.0.0-beta.1 (2022-07-23)
### Features
* first prerelease ([e253142](https://github.com/James-Frowen/Mirage.Profiler/commit/e25314230a8e322e351912ac9f960f6f29821be5))

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 1b2c5211880da6c4f83e37f1222441e1
TextScriptImporter:
externalObjects: {}
userData: ''
assetBundleName: ''
assetBundleVariant: ''

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: e9eb58b4bf960c1439443c3160ce8371
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData: ''
assetBundleName: ''
assetBundleVariant: ''

View File

@ -0,0 +1,3 @@
using System.Reflection;
[assembly: AssemblyVersion("1.1.0.11")]

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6efabd9b23eecd0459622d955c6a2b4e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData: ''
assetBundleName: ''
assetBundleVariant: ''

View File

@ -0,0 +1,7 @@
namespace Mirage.NetworkProfiler.ModuleGUI
{
internal interface ICountRecorderProvider
{
CountRecorder GetCountRecorder();
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e7ff307d04db0b64690d8c9692e40d8c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData: ''
assetBundleName: ''
assetBundleVariant: ''

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 667ec5fd9bee70844a2b458410782b0a
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData: ''
assetBundleName: ''
assetBundleVariant: ''

View File

@ -0,0 +1,89 @@
using System.Collections;
using System.Collections.Generic;
using Mirage.NetworkProfiler.ModuleGUI.UITable;
namespace Mirage.NetworkProfiler.ModuleGUI.Messages
{
internal sealed class Columns : IEnumerable<ColumnInfo>
{
private const int EXPAND_WIDTH = 25;
private const int FULL_NAME_WIDTH = 300;
private const int NAME_WIDTH = 150;
private const int OTHER_WIDTH = 100;
private readonly ColumnInfo[] _columns;
public readonly ColumnInfo Expand;
public readonly ColumnInfo FullName;
public readonly ColumnInfo TotalBytes;
public readonly ColumnInfo Count;
public readonly ColumnInfo BytesPerMessage;
public readonly ColumnInfo NetId;
public readonly ColumnInfo ObjectName;
public readonly ColumnInfo RpcName;
public Columns()
{
Expand = new ColumnInfo("+", EXPAND_WIDTH, x => "");
FullName = new ColumnInfo("Message", FULL_NAME_WIDTH, x => x.Name);
FullName.AddSort(m => m.Name, m => m.Name);
TotalBytes = new ColumnInfo("Total Bytes", OTHER_WIDTH, x => x.TotalBytes.ToString());
TotalBytes.AddSort(m => m.TotalBytes, m => m.TotalBytes);
Count = new ColumnInfo("Count", OTHER_WIDTH, x => x.Count.ToString());
Count.AddSort(m => m.TotalCount, m => m.Count);
BytesPerMessage = new ColumnInfo("Bytes", OTHER_WIDTH, x => x.Bytes.ToString());
BytesPerMessage.AddSort(null, m => m.Bytes);
NetId = new ColumnInfo("Net id", OTHER_WIDTH, x => x.NetId.HasValue ? x.NetId.ToString() : "");
NetId.AddSort(null, m => m.NetId.GetValueOrDefault());
ObjectName = new ColumnInfo("GameObject Name", NAME_WIDTH, x => x.ObjectName);
ObjectName.AddSort(null, m => m.ObjectName);
RpcName = new ColumnInfo("RPC Name (hover for full name)", FULL_NAME_WIDTH, x => RpcShortName(x.RpcName));
RpcName.AddSort(null, m => m.RpcName);
// full name in tooltip
RpcName.AddToolTip(m => m.RpcName);
_columns = new ColumnInfo[] {
Expand,
FullName,
TotalBytes,
Count,
BytesPerMessage,
NetId,
ObjectName,
RpcName,
};
}
private string RpcShortName(string fullName)
{
if (string.IsNullOrEmpty(fullName))
return string.Empty;
const char separator = '.';
if (fullName.Contains(separator))
{
var split = fullName.Split(separator);
var count = split.Length;
if (count >= 2)
{
return $"{split[count - 2]}.{split[count - 1]}";
}
}
return fullName;
}
public IEnumerator<ColumnInfo> GetEnumerator() => ((IEnumerable<ColumnInfo>)_columns).GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => _columns.GetEnumerator();
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 36dff191a3209c444844bbdc74f60cd4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData: ''
assetBundleName: ''
assetBundleVariant: ''

View File

@ -0,0 +1,16 @@
namespace Mirage.NetworkProfiler.ModuleGUI.Messages
{
internal struct CounterNames
{
public readonly string Count;
public readonly string Bytes;
public readonly string PerSecond;
public CounterNames(string count, string bytes, string perSecond)
{
Count = count;
Bytes = bytes;
PerSecond = perSecond;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2422d6a68fe533e4c9da13ae30767fcf
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData: ''
assetBundleName: ''
assetBundleVariant: ''

View File

@ -0,0 +1,10 @@
using Mirage.NetworkProfiler.ModuleGUI.UITable;
namespace Mirage.NetworkProfiler.ModuleGUI.Messages
{
internal class DrawnMessage
{
public MessageInfo Info;
public Row Row;
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: df1ae3a59995b1a43b6b76e16d2b8f3a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData: ''
assetBundleName: ''
assetBundleVariant: ''

View File

@ -0,0 +1,123 @@
using System;
using System.Collections.Generic;
using Mirage.NetworkProfiler.ModuleGUI.UITable;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
namespace Mirage.NetworkProfiler.ModuleGUI.Messages
{
internal class Group
{
public readonly List<Row> Rows = new List<Row>();
public readonly string Name;
public readonly List<DrawnMessage> Messages = new List<DrawnMessage>();
private readonly Table _table;
private readonly Columns _columns;
public Row Head;
public int TotalBytes { get; private set; }
public int TotalCount { get; private set; }
public int Order { get; private set; }
public bool Expanded { get; private set; }
public Group(string name, Table table, Columns columns)
{
Name = name;
_table = table;
_columns = columns;
// start at max, then take min each time message is added
Order = int.MaxValue;
}
public void AddMessage(MessageInfo msg)
{
Messages.Add(new DrawnMessage { Info = msg });
TotalBytes += msg.TotalBytes;
TotalCount += msg.Count;
Order = Math.Min(Order, msg.Order);
}
public void ToggleExpand()
{
Expand(!Expanded);
}
public void Expand(bool expanded)
{
Expanded = expanded;
// create rows if needed
LazyCreateRows();
foreach (var row in Rows)
{
row.VisualElement.style.display = expanded ? DisplayStyle.Flex : DisplayStyle.None;
}
// head can be null if ungrouped
Head?.SetText(_columns.Expand, Expanded ? "-" : "+");
}
public void LazyCreateRows()
{
// not visible, do nothing till row is expanded
if (!Expanded)
return;
// already created
if (Rows.Count > 0)
return;
DrawMessages();
}
/// <param name="messages">Messages to add to table</param>
/// <param name="createdRows">list to add rows to once created, Can be null</param>
private void DrawMessages()
{
var previous = Head;
var backgroundColor = GetBackgroundColor();
foreach (var drawn in Messages)
{
var row = _table.AddRow(previous);
Rows.Add(row);
// set previous to be new row, so that message are added in order after previous
previous = row;
drawn.Row = row;
var info = drawn.Info;
DrawMessage(row, info);
// set color of labels not whole row, otherwise color will be outside of table as well
foreach (var ele in row.GetChildren())
ele.style.backgroundColor = backgroundColor;
}
}
private void DrawMessage(Row row, MessageInfo info)
{
foreach (var column in _columns)
{
row.SetText(column, column.TextGetter.Invoke(info));
if (column.HasToolTip)
{
var label = row.GetLabel(column);
label.tooltip = column.ToolTipGetter.Invoke(info);
}
}
}
private static Color GetBackgroundColor()
{
// pick color that is lighter/darker than default editor background
// todo check if there is a way to get the real color, or do we have to use `isProSkin`?
return EditorGUIUtility.isProSkin
? (Color)new Color32(56, 56, 56, 255) / 0.8f
: (Color)new Color32(194, 194, 194, 255) * .8f;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0d73a0b136989084c8caed7ae1349219
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData: ''
assetBundleName: ''
assetBundleVariant: ''

View File

@ -0,0 +1,94 @@
using System;
using System.Collections.Generic;
using Mirage.NetworkProfiler.ModuleGUI.UITable;
namespace Mirage.NetworkProfiler.ModuleGUI.Messages
{
internal struct GroupSorter
{
private readonly Dictionary<string, Group> _grouped;
private readonly Func<Group, Group, int> _sortGroupFunc;
private readonly Func<MessageInfo, MessageInfo, int> _sortMessageFunc;
private readonly SortMode _sortMode;
public GroupSorter(Dictionary<string, Group> grouped, ColumnInfo sortHeader, SortMode sortMode)
{
_grouped = grouped;
_sortMode = sortMode;
// if header or sort is null, use default
_sortGroupFunc = sortHeader?.SortGroup ?? DefaultGroupSort;
_sortMessageFunc = sortHeader?.SortMessages ?? DefaultMessageSort;
}
public void Sort()
{
var groups = new List<Group>(_grouped.Values);
// sort all groups and their messages
groups.Sort(CompareGroupSortMode);
foreach (var group in groups)
{
group.Messages.Sort(CompareDrawnSortMode);
}
// apply sort to table
foreach (var group in groups)
{
// use BringToFront so that each new element is placed after the last one, bring them all to their correct position
// head might be null if messages are ungrouped
group.Head?.VisualElement.BringToFront();
foreach (var msg in group.Messages)
{
// row might be null before it is drawn for first time
msg.Row?.VisualElement.BringToFront();
}
}
}
private int CompareGroupSortMode(Group x, Group y)
{
var sort = _sortGroupFunc.Invoke(x, y);
return CheckSortMode(sort);
}
private int CompareDrawnSortMode(DrawnMessage x, DrawnMessage y)
{
var sort = _sortMessageFunc.Invoke(x.Info, y.Info);
return CheckSortMode(sort);
}
private int CheckSortMode(int sort)
{
// flip order if Descending
if (_sortMode == SortMode.Descending)
return -sort;
return sort;
}
public static int Compare<T>(Group x, Group y, Func<Group, T> func) where T : IComparable<T>
{
var xValue = func.Invoke(x);
var yValue = func.Invoke(y);
return xValue.CompareTo(yValue);
}
public static int Compare<T>(MessageInfo x, MessageInfo y, Func<MessageInfo, T> func) where T : IComparable<T>
{
var xValue = func.Invoke(x);
var yValue = func.Invoke(y);
return xValue.CompareTo(yValue);
}
public static int DefaultGroupSort(Group x, Group y)
{
return x.Order.CompareTo(y.Order);
}
public static int DefaultMessageSort(MessageInfo x, MessageInfo y)
{
return x.Order.CompareTo(y.Order);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b13bef8f87b23684bae6fc2453c090b0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData: ''
assetBundleName: ''
assetBundleVariant: ''

View File

@ -0,0 +1,141 @@
using System;
using System.Collections.Generic;
using Mirage.NetworkProfiler.ModuleGUI.UITable;
using UnityEngine.UIElements;
namespace Mirage.NetworkProfiler.ModuleGUI.Messages
{
internal class MessageView
{
private readonly Columns _columns;
private readonly Table _table;
/// <summary>
/// cache list used to gather messages
/// </summary>
private readonly List<MessageInfo> _messages = new List<MessageInfo>();
private readonly Dictionary<string, Group> _grouped = new Dictionary<string, Group>();
public event Action<Group, bool> OnGroupExpanded;
public MessageView(Columns columns, TableSorter sorter, VisualElement parent)
{
_columns = columns;
_table = new Table(columns, sorter);
parent.Add(_table.VisualElement);
}
public void Draw(IEnumerable<Frame> frames, bool groupMessages)
{
CollectMessages(frames);
GroupMessages(groupMessages);
DrawGroups(groupMessages);
var expandColumn = _columns.Expand;
var defaultWidth = expandColumn.Width;
var width = groupMessages ? defaultWidth : 0;
_table.ChangeWidth(expandColumn, width, true);
}
private void CollectMessages(IEnumerable<Frame> frames)
{
_messages.Clear();
foreach (var frame in frames)
{
_messages.AddRange(frame.Messages);
}
}
private void GroupMessages(bool asGroups)
{
_grouped.Clear();
foreach (var message in _messages)
{
string name;
if (asGroups)
name = message.Name;
else
name = "all_messages";
if (!_grouped.TryGetValue(name, out var group))
{
group = new Group(name, _table, _columns);
_grouped[name] = group;
}
group.AddMessage(message);
}
}
private void DrawGroups(bool withHeader)
{
foreach (var group in _grouped.Values)
{
if (withHeader)
{
DrawGroupHeader(group);
}
else
{
group.Expand(true);
}
}
}
private void DrawGroupHeader(Group group)
{
// draw header
var head = _table.AddRow();
head.SetText(_columns.Expand, group.Expanded ? "-" : "+");
head.SetText(_columns.FullName, group.Name);
head.SetText(_columns.TotalBytes, group.TotalBytes);
head.SetText(_columns.Count, group.TotalCount);
group.Head = head;
var expand = head.GetLabel(_columns.Expand);
expand.AddManipulator(new Clickable((evt) =>
{
group.ToggleExpand();
OnGroupExpanded?.Invoke(group, group.Expanded);
}));
// will lazy create message if expanded
group.Expand(group.Expanded);
}
public void Sort(ColumnInfo sortHeader, SortMode sortMode)
{
var sorter = new GroupSorter(_grouped, sortHeader, sortMode);
sorter.Sort();
if (sortHeader != null)
{
// also set table names,
_table.SetSortHeader(sortHeader, sortMode);
}
}
public void Clear()
{
_table.Clear();
}
public VisualElement AddEmptyRow()
{
var row = _table.AddEmptyRow();
return row.VisualElement;
}
public void ExpandMany(List<string> expanded)
{
foreach (var group in _grouped.Values)
{
if (expanded.Contains(group.Name))
{
group.Expand(true);
}
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6f417eeb8a30cca4eb1a9301a73dbf10
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData: ''
assetBundleName: ''
assetBundleVariant: ''

View File

@ -0,0 +1,284 @@
using System;
using System.Collections.Generic;
using Mirage.NetworkProfiler.ModuleGUI.UITable;
using Unity.Profiling;
using Unity.Profiling.Editor;
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;
using UnityEngine.UIElements;
namespace Mirage.NetworkProfiler.ModuleGUI.Messages
{
internal sealed class MessageViewController : ProfilerModuleViewController
{
private readonly CounterNames _names;
private readonly Columns _columns = new Columns();
private Label _countLabel;
private Label _bytesLabel;
private Label _perSecondLabel;
private VisualElement _toggleBox;
private Toggle _debugToggle;
private Toggle _groupMsgToggle;
private MessageView _messageView;
private readonly SavedData _savedData;
public MessageViewController(ProfilerWindow profilerWindow, CounterNames names, SavedData savedData)
: base(profilerWindow)
{
_names = names;
_savedData = savedData;
}
protected override VisualElement CreateView()
{
// unity doesn't catch errors here so we have to wrap in try/catch
try
{
return CreateViewInternal();
}
catch (Exception ex)
{
Debug.LogException(ex);
return null;
}
}
protected override void Dispose(bool disposing)
{
if (!disposing)
return;
// Unsubscribe from the Profiler window event that we previously subscribed to.
ProfilerWindow.SelectedFrameIndexChanged -= FrameIndexChanged;
base.Dispose(disposing);
}
private VisualElement CreateViewInternal()
{
var root = new VisualElement();
root.style.flexDirection = new StyleEnum<FlexDirection>(FlexDirection.Row);
root.style.height = Length.Percent(100);
root.style.overflow = Overflow.Hidden;
var summary = new VisualElement();
_countLabel = AddLabelWithPadding(summary);
_bytesLabel = AddLabelWithPadding(summary);
_perSecondLabel = AddLabelWithPadding(summary);
_perSecondLabel.tooltip = Names.PER_SECOND_TOOLTIP;
root.Add(summary);
summary.style.height = Length.Percent(100);
summary.style.width = 180;
summary.style.minWidth = 180;
summary.style.maxWidth = 180;
summary.style.borderRightColor = Color.white * .4f;//dark grey
summary.style.borderRightWidth = 3;
_toggleBox = new VisualElement();
_toggleBox.style.position = Position.Absolute;
_toggleBox.style.bottom = 5;
_toggleBox.style.left = 5;
_toggleBox.style.unityTextAlign = TextAnchor.LowerLeft;
summary.Add(_toggleBox);
_groupMsgToggle = new Toggle
{
text = "Group Messages",
tooltip = "Groups Message by type",
value = _savedData.GroupMessages,
};
_groupMsgToggle.RegisterValueChangedCallback(GroupToggled);
_toggleBox.Add(_groupMsgToggle);
// todo allow selection of multiple frames
//var frameSlider = new MinMaxSlider();
//frameSlider.highLimit = 300;
//frameSlider.lowLimit = 1;
//frameSlider.value = Vector2.one;
//frameSlider.RegisterValueChangedCallback(_ => Debug.Log(frameSlider.value));
//_toggleBox.Add(frameSlider);
_debugToggle = new Toggle
{
text = "Show Fake Messages",
tooltip = "Adds fakes message to table to debug layout of table",
value = false
};
_debugToggle.RegisterValueChangedCallback(_ => ReloadData());
_toggleBox.Add(_debugToggle);
#if MIRAGE_PROFILER_DEBUG
_debugToggle.style.display = DisplayStyle.Flex;
#else
_debugToggle.style.display = DisplayStyle.None;
#endif
var sorter = new TableSorter(this);
_messageView = new MessageView(_columns, sorter, root);
_messageView.OnGroupExpanded += OnGroupExpanded;
// Populate the label with the current data for the selected frame.
ReloadData();
// Be notified when the selected frame index in the Profiler Window changes, so we can update the label.
ProfilerWindow.SelectedFrameIndexChanged += FrameIndexChanged;
return root;
}
private void GroupToggled(ChangeEvent<bool> evt)
{
_savedData.GroupMessages = evt.newValue;
ReloadData();
}
private void OnGroupExpanded(Group group, bool expanded)
{
if (expanded)
_savedData.Expanded.Add(group.Name);
else
_savedData.Expanded.Remove(group.Name);
}
private void FrameIndexChanged(long selectedFrameIndex) => ReloadData();
private static Label AddLabelWithPadding(VisualElement parent)
{
var label = new Label() { style = { paddingTop = 8, paddingLeft = 8 } };
parent.Add(label);
return label;
}
internal void Sort(SortHeader header)
{
_savedData.SetSortHeader(header);
SortFromSaveData();
}
private void SortFromSaveData()
{
var (sortHeader, sortMode) = _savedData.GetSortHeader(_columns);
_messageView.Sort(sortHeader, sortMode);
}
private void ReloadData()
{
SetSummary(_countLabel, _names.Count);
SetSummary(_bytesLabel, _names.Bytes);
SetSummary(_perSecondLabel, _names.PerSecond);
ReloadMessages();
}
private void SetSummary(Label label, string counterName)
{
var frame = (int)ProfilerWindow.selectedFrameIndex;
var category = ProfilerCategory.Network.Name;
var value = ProfilerDriver.GetFormattedCounterValue(frame, category, counterName);
// replace prefix
var display = counterName.Replace("Received", "").Replace("Sent", "").Trim();
label.text = $"{display}: {value}";
}
private void ReloadMessages()
{
const int EditorID = -1;
_messageView.Clear();
var frameIndex = (int)ProfilerWindow.selectedFrameIndex;
// Debug.Log($"ReloadMessages [selected {(int)ProfilerWindow.selectedFrameIndex}]");
if (ProfilerDriver.connectedProfiler != EditorID)
{
AddErrorLabel("Can't read message from player");
return;
}
if (!TryGetMessages(frameIndex, out var messages))
{
AddErrorLabel("Can not load messages! (Message list only visible in play mode)\nIMPORTANT: make sure NetworkProfilerBehaviour is setup in starting scene");
return;
}
if (messages.Count == 0)
{
AddInfoLabel("No Messages");
return;
}
var frame = new Frame[1] {
new Frame{ Messages = messages },
};
_messageView.Draw(frame, _groupMsgToggle.value);
_messageView.ExpandMany(_savedData.Expanded);
SortFromSaveData();
}
private bool TryGetMessages(int frameIndex, out List<MessageInfo> messages)
{
#if MIRAGE_PROFILER_DEBUG
if (_debugToggle.value)
{
messages = GenerateDebugMessages();
return true;
}
#endif
if (frameIndex == -1)
{
messages = null;
return false;
}
messages = _savedData.Frames.GetFrame(frameIndex).Messages;
return true;
}
#if MIRAGE_PROFILER_DEBUG
private static List<MessageInfo> GenerateDebugMessages()
{
var messages = new List<MessageInfo>();
var order = 0;
for (var i = 0; i < 5; i++)
{
messages.Add(NewInfo(order++, new RpcMessage { netId = (uint)i }, 20 + i, 5));
messages.Add(NewInfo(order++, new SpawnMessage { netId = (uint)i }, 80 + i, 1));
messages.Add(NewInfo(order++, new SpawnMessage { netId = (uint)i }, 60 + i, 4));
messages.Add(NewInfo(order++, new NetworkPingMessage { }, 4, 1));
static MessageInfo NewInfo(int order, object msg, int bytes, int count)
{
#if MIRAGE_DIAGNOSTIC_INSTANCE
return new MessageInfo(new NetworkDiagnostics.MessageInfo(null, msg, bytes, count), provider, order);
#else
return new MessageInfo(new NetworkDiagnostics.MessageInfo(msg, bytes, count), new NetworkInfoProvider(null), order);
#endif
}
}
return messages;
}
#endif
private void AddErrorLabel(string message)
{
var parent = _messageView.AddEmptyRow();
var ele = AddLabelWithPadding(parent);
ele.style.color = Color.red;
ele.text = message;
}
private void AddInfoLabel(string message)
{
var parent = _messageView.AddEmptyRow();
var ele = AddLabelWithPadding(parent);
ele.text = message;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b210a69331a49414ca5f6436163f61d5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData: ''
assetBundleName: ''
assetBundleVariant: ''

View File

@ -0,0 +1,27 @@
using Mirage.NetworkProfiler.ModuleGUI.UITable;
using UnityEngine;
namespace Mirage.NetworkProfiler.ModuleGUI.Messages
{
internal class TableSorter : ITableSorter
{
private readonly MessageViewController _controller;
public TableSorter(MessageViewController controller)
{
_controller = controller;
}
public void Sort(Table table, SortHeader header)
{
if (table.ContainsEmptyRows)
{
Debug.LogWarning("Can't sort when there are empty rows");
return;
}
_controller.Sort(header);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 658b759dbe63e9c44a14d785a15c862e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData: ''
assetBundleName: ''
assetBundleVariant: ''

View File

@ -0,0 +1,20 @@
{
"name": "Mirage.Profiler.Editor",
"rootNamespace": "",
"references": [
"GUID:b46779583a009f04ba9f5f31d0e7e6ac",
"GUID:6ff9f1c8e2ed4034baf80c43db7a3b6a",
"GUID:30817c1a0e6d646d99c048fc403f5979"
],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: ff97fe2e55c0fd44c91fb1242f9bff3e
AssemblyDefinitionImporter:
externalObjects: {}
userData: ''
assetBundleName: ''
assetBundleVariant: ''

View File

@ -0,0 +1,12 @@
namespace Mirage.NetworkProfiler.ModuleGUI
{
internal static class ModuleNames
{
// change this based on netcode lib
private const string PREFIX = "Mirror";
public const string SERVER = PREFIX + " Server";
public const string SENT = PREFIX + " Sent";
public const string RECEIVED = PREFIX + " Received";
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1a65943c66af2cb4cb0433f5f3bd73b7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData: ''
assetBundleName: ''
assetBundleVariant: ''

View File

@ -0,0 +1,35 @@
using Mirage.NetworkProfiler.ModuleGUI.Messages;
using Unity.Profiling.Editor;
namespace Mirage.NetworkProfiler.ModuleGUI
{
[System.Serializable]
[ProfilerModuleMetadata(ModuleNames.RECEIVED)]
public class ReceivedModule : ProfilerModule, ICountRecorderProvider
{
private static readonly ProfilerCounterDescriptor[] counters = new ProfilerCounterDescriptor[]
{
new ProfilerCounterDescriptor(Names.RECEIVED_COUNT, Counters.Category),
new ProfilerCounterDescriptor(Names.RECEIVED_BYTES, Counters.Category),
new ProfilerCounterDescriptor(Names.RECEIVED_PER_SECOND, Counters.Category),
};
public ReceivedModule() : base(counters) { }
public override ProfilerModuleViewController CreateDetailsViewController()
{
var names = new CounterNames(
Names.RECEIVED_COUNT,
Names.RECEIVED_BYTES,
Names.RECEIVED_PER_SECOND
);
return new MessageViewController(ProfilerWindow, names, SaveDataLoader.ReceiveData);
}
CountRecorder ICountRecorderProvider.GetCountRecorder()
{
return NetworkProfilerRecorder._receivedCounter;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 677abdcefedd3144b9784f7a09a647da
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData: ''
assetBundleName: ''
assetBundleVariant: ''

View File

@ -0,0 +1,231 @@
using System;
using System.Collections.Generic;
using System.IO;
using Mirage.NetworkProfiler.ModuleGUI.Messages;
using Mirage.NetworkProfiler.ModuleGUI.UITable;
using UnityEditor;
using UnityEngine;
namespace Mirage.NetworkProfiler.ModuleGUI
{
[Serializable]
internal class SavedData
{
/// <summary>
/// Message from each frame so they can survive domain reload
/// </summary>
public Frames Frames;
/// <summary>
/// Active sort header
/// </summary>
public string SortHeader;
public SortMode SortMode;
/// <summary>
/// Which Message groups are expanded
/// </summary>
public List<string> Expanded;
public bool GroupMessages = true;
public SavedData()
{
Frames = new Frames();
Expanded = new List<string>();
}
public (ColumnInfo, SortMode) GetSortHeader(Columns columns)
{
foreach (var c in columns)
{
if (SortHeader == c.Header)
{
return (c, SortMode);
}
}
return (null, SortMode.None);
}
public void SetSortHeader(SortHeader header)
{
if (header == null)
{
SortHeader = "";
}
else
{
SortHeader = header.Info.Header;
SortMode = header.SortMode;
}
}
public void Clear()
{
foreach (var frame in Frames)
{
frame.Bytes = 0;
frame.Messages.Clear();
}
}
}
internal class SaveDataLoader
{
private SavedData _receiveData;
private SavedData _sentData;
private static SaveDataLoader instance;
private static bool isQuitting;
// private so only we can create one
private SaveDataLoader()
{
NetworkProfilerRecorder.AfterSample += AfterSample;
EditorApplication.quitting += Quitting;
}
private void Quitting()
{
Console.WriteLine("[Mirage.Profiler] quitting");
// save and clear references when quitting,
// this is needed because finialize is called after unity dll unloads so causes crash
SaveBoth();
_receiveData = null;
_sentData = null;
isQuitting = true;
}
~SaveDataLoader()
{
NetworkProfilerRecorder.AfterSample -= AfterSample;
// dont save after quitting, unity might unload their dll and cause crash
if (isQuitting)
return;
SaveBoth();
}
private void SaveBoth()
{
if (_receiveData != null)
Save(GetFullPath("Receive"), _receiveData);
if (_sentData != null)
Save(GetFullPath("Sent"), _sentData);
}
private static void AfterSample(int tick)
{
SetFrame(tick, ReceiveData, NetworkProfilerRecorder._receivedCounter);
SetFrame(tick, SentData, NetworkProfilerRecorder._sentCounter);
}
private static void SetFrame(int tick, SavedData data, CountRecorder counter)
{
var saveFrame = data.Frames.GetFrame(tick);
// clear old data
saveFrame.Bytes = 0;
saveFrame.Messages.Clear();
if (counter == null)
return;
var counterFrame = counter._frames.GetFrame(tick);
saveFrame.Bytes = counterFrame.Bytes;
saveFrame.Messages.AddRange(counterFrame.Messages);
}
public static SavedData ReceiveData
{
get
{
// dont load on quit, it might cause crash if unity dll unloads while savedata is save/loading
if (isQuitting)
return null;
if (instance == null)
instance = new SaveDataLoader();
if (instance._receiveData == null)
{
instance._receiveData = Load(GetFullPath("Receive"));
}
return instance._receiveData;
}
}
public static SavedData SentData
{
get
{
// dont load on quit, it might cause crash if unity dll unloads while savedata is save/loading
if (isQuitting)
return null;
if (instance == null)
instance = new SaveDataLoader();
if (instance._sentData == null)
{
instance._sentData = Load(GetFullPath("Sent"));
}
return instance._sentData;
}
}
public static string GetFullPath(string name)
{
var userSettingsFolder = Path.GetFullPath("UserSettings");
if (string.IsNullOrEmpty(name))
throw new ArgumentNullException(nameof(name));
return Path.Join(userSettingsFolder, "Mirage.Profiler", $"{name}.json");
}
public static void Save(string path, SavedData data)
{
Console.WriteLine($"[Mirage.Profiler] Save {path}");
CheckDir(path);
var text = JsonUtility.ToJson(data);
File.WriteAllText(path, text);
}
public static SavedData Load(string path)
{
Console.WriteLine($"[Mirage.Profiler] Load {path}");
CheckDir(path);
if (File.Exists(path))
{
var text = File.ReadAllText(path);
var data = JsonUtility.FromJson<SavedData>(text);
Validate(data);
return data;
}
else
{
return new SavedData();
}
}
private static void Validate(SavedData data)
{
data.Frames.ValidateSize();
}
private static void CheckDir(string path)
{
// check dir exists
var dir = Path.GetDirectoryName(path);
if (!Directory.Exists(dir))
Directory.CreateDirectory(dir);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6e3608e18fbb66346bc8d15f628ad4f1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData: ''
assetBundleName: ''
assetBundleVariant: ''

View File

@ -0,0 +1,35 @@
using Mirage.NetworkProfiler.ModuleGUI.Messages;
using Unity.Profiling.Editor;
namespace Mirage.NetworkProfiler.ModuleGUI
{
[System.Serializable]
[ProfilerModuleMetadata(ModuleNames.SENT)]
public class SentModule : ProfilerModule, ICountRecorderProvider
{
private static readonly ProfilerCounterDescriptor[] counters = new ProfilerCounterDescriptor[]
{
new ProfilerCounterDescriptor(Names.SENT_COUNT, Counters.Category),
new ProfilerCounterDescriptor(Names.SENT_BYTES, Counters.Category),
new ProfilerCounterDescriptor(Names.SENT_PER_SECOND, Counters.Category),
};
public SentModule() : base(counters) { }
public override ProfilerModuleViewController CreateDetailsViewController()
{
var names = new CounterNames(
Names.SENT_COUNT,
Names.SENT_BYTES,
Names.SENT_PER_SECOND
);
return new MessageViewController(ProfilerWindow, names, SaveDataLoader.SentData);
}
CountRecorder ICountRecorderProvider.GetCountRecorder()
{
return NetworkProfilerRecorder._sentCounter;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5447799e8b54b3044b4fac717c450b46
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData: ''
assetBundleName: ''
assetBundleVariant: ''

View File

@ -0,0 +1,103 @@
using Unity.Profiling;
using Unity.Profiling.Editor;
using UnityEditor;
using UnityEditorInternal;
using UnityEngine.UIElements;
namespace Mirage.NetworkProfiler.ModuleGUI
{
[System.Serializable]
[ProfilerModuleMetadata(ModuleNames.SERVER)]
public class ServerModule : ProfilerModule
{
private static readonly ProfilerCounterDescriptor[] counters = new ProfilerCounterDescriptor[]
{
new ProfilerCounterDescriptor(Names.PLAYER_COUNT, Counters.Category),
new ProfilerCounterDescriptor(Names.CHARACTER_COUNT, Counters.Category),
new ProfilerCounterDescriptor(Names.OBJECT_COUNT, Counters.Category),
};
public ServerModule() : base(counters) { }
public override ProfilerModuleViewController CreateDetailsViewController()
{
return new ServerViewController(ProfilerWindow);
}
}
public abstract class BaseViewController : ProfilerModuleViewController
{
public BaseViewController(ProfilerWindow profilerWindow) : base(profilerWindow) { }
protected static Label AddLabelWithPadding(VisualElement parent)
{
var label = new Label() { style = { paddingTop = 8, paddingLeft = 8 } };
parent.Add(label);
return label;
}
protected void SetText(Label label, string name)
{
var frame = (int)ProfilerWindow.selectedFrameIndex;
var category = ProfilerCategory.Network.Name;
var value = ProfilerDriver.GetFormattedCounterValue(frame, category, name);
label.text = $"{name}: {value}";
}
}
public sealed class ServerViewController : BaseViewController
{
private Label PlayerLabel;
private Label CharacterLabel;
private Label ObjectLabel;
public ServerViewController(ProfilerWindow profilerWindow) : base(profilerWindow) { }
protected override VisualElement CreateView()
{
var root = new VisualElement();
PlayerLabel = AddLabelWithPadding(root);
CharacterLabel = AddLabelWithPadding(root);
ObjectLabel = AddLabelWithPadding(root);
PlayerLabel.tooltip = Names.PLAYER_COUNT_TOOLTIP;
CharacterLabel.tooltip = Names.CHARACTER_COUNT_TOOLTIP;
ObjectLabel.tooltip = Names.OBJECT_COUNT_TOOLTIP;
// Populate the label with the current data for the selected frame.
ReloadData();
// Be notified when the selected frame index in the Profiler Window changes, so we can update the label.
ProfilerWindow.SelectedFrameIndexChanged += OnSelectedFrameIndexChanged;
return root;
}
private void OnSelectedFrameIndexChanged(long selectedFrameIndex)
{
// Update the label with the current data for the newly selected frame.
ReloadData();
}
protected override void Dispose(bool disposing)
{
if (!disposing)
return;
// Unsubscribe from the Profiler window event that we previously subscribed to.
ProfilerWindow.SelectedFrameIndexChanged -= OnSelectedFrameIndexChanged;
base.Dispose(disposing);
}
private void ReloadData()
{
SetText(PlayerLabel, Names.PLAYER_COUNT);
SetText(CharacterLabel, Names.CHARACTER_COUNT);
SetText(ObjectLabel, Names.OBJECT_COUNT);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b8113ff8f56cc644bb23d2da87f23eab
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData: ''
assetBundleName: ''
assetBundleVariant: ''

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: cc264da8a8fc533418223d921f2f00c8
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData: ''
assetBundleName: ''
assetBundleVariant: ''

View File

@ -0,0 +1,66 @@
using System;
using Mirage.NetworkProfiler.ModuleGUI.Messages;
namespace Mirage.NetworkProfiler.ModuleGUI.UITable
{
internal class ColumnInfo
{
public string Header { get; private set; }
public int Width { get; private set; }
public bool AllowSort { get; private set; }
public Func<Group, Group, int> SortGroup { get; private set; }
public Func<MessageInfo, MessageInfo, int> SortMessages { get; private set; }
public Func<MessageInfo, string> TextGetter { get; private set; }
public bool HasToolTip { get; private set; }
public Func<MessageInfo, string> ToolTipGetter { get; private set; }
public ColumnInfo(string header, int width, Func<MessageInfo, string> textGetter)
{
Header = header;
Width = width;
TextGetter = textGetter;
}
/// <summary>
/// Enables sorting for column. If sort functions are null they will use default sort from <see cref="GroupSorter"/>
/// </summary>
/// <param name="sortGroup"></param>
/// <param name="sortMessages"></param>
/// <exception cref="ArgumentNullException"></exception>
public void AddSort(Func<Group, Group, int> sortGroup, Func<MessageInfo, MessageInfo, int> sortMessages)
{
AllowSort = true;
SortGroup = sortGroup ?? GroupSorter.DefaultGroupSort;
SortMessages = sortMessages ?? GroupSorter.DefaultMessageSort;
}
/// <summary>
/// Enables sorting for column. If sort functions are null they will use default sort from <see cref="GroupSorter"/>
/// <para>Uses member getting to sort via that member</para>
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="groupGetter"></param>
/// <param name="messageGetter"></param>
public void AddSort<T>(Func<Group, T> groupGetter, Func<MessageInfo, T> messageGetter) where T : IComparable<T>
{
Func<Group, Group, int> sortGroup = groupGetter != null
? (x, y) => GroupSorter.Compare(x, y, groupGetter)
: null;
Func<MessageInfo, MessageInfo, int> sortMessages = messageGetter != null
? (x, y) => GroupSorter.Compare(x, y, messageGetter)
: null;
AddSort(sortGroup, sortMessages);
}
public void AddToolTip(Func<MessageInfo, string> getter)
{
HasToolTip = true;
ToolTipGetter = getter;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7b017b2728f784842bc8f62460ab9fbe
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData: ''
assetBundleName: ''
assetBundleVariant: ''

View File

@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine.UIElements;
namespace Mirage.NetworkProfiler.ModuleGUI.UITable
{
internal class EmptyRow : Row
{
public EmptyRow(Table table, Row previous = null) : base(table, previous) { }
public override Label GetLabel(ColumnInfo column)
{
throw new NotSupportedException("Empty row does not have any columns");
}
public override IEnumerable<VisualElement> GetChildren()
{
return Enumerable.Empty<VisualElement>();
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8129d33c5f799524fa10e84110131e69
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData: ''
assetBundleName: ''
assetBundleVariant: ''

View File

@ -0,0 +1,7 @@
namespace Mirage.NetworkProfiler.ModuleGUI.UITable
{
internal interface ITableSorter
{
void Sort(Table table, SortHeader header);
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 05c177a4f382419409a2f55de20e046c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData: ''
assetBundleName: ''
assetBundleVariant: ''

View File

@ -0,0 +1,53 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;
namespace Mirage.NetworkProfiler.ModuleGUI.UITable
{
internal class LabelRow : Row
{
private readonly Dictionary<ColumnInfo, Label> _elements = new Dictionary<ColumnInfo, Label>();
public LabelRow(Table table, Row previous = null) : base(table, previous)
{
foreach (var header in table.HeaderInfo)
{
var label = CreateLabel(header);
VisualElement.Add(label);
_elements[header] = label;
}
}
protected virtual Label CreateLabel(ColumnInfo column)
{
var label = new Label();
SetLabelStyle(column, label);
return label;
}
protected static void SetLabelStyle(ColumnInfo column, Label label)
{
var style = label.style;
style.width = column.Width;
style.paddingLeft = 5;
style.paddingRight = 5;
style.paddingTop = 5;
style.paddingBottom = 5;
style.borderRightColor = Color.white * .4f;
style.borderBottomColor = Color.white * .4f;
style.borderBottomWidth = 1;
style.borderRightWidth = 2;
}
public override Label GetLabel(ColumnInfo column)
{
return _elements[column];
}
public override IEnumerable<VisualElement> GetChildren()
{
return _elements.Values;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e463bd968d1bb0e4cbae5efd09ed24fc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData: ''
assetBundleName: ''
assetBundleVariant: ''

View File

@ -0,0 +1,45 @@
using System.Collections.Generic;
using UnityEngine.UIElements;
namespace Mirage.NetworkProfiler.ModuleGUI.UITable
{
internal abstract class Row
{
public Table Table { get; }
public VisualElement VisualElement { get; }
public Row(Table table, Row previous = null)
{
Table = table;
VisualElement = new VisualElement();
VisualElement.style.flexDirection = FlexDirection.Row;
var parent = table.ScrollView;
if (previous != null)
{
var index = parent.IndexOf(previous.VisualElement);
// insert after previous
parent.Insert(index + 1, VisualElement);
}
else
{
// just add at end
parent.Add(VisualElement);
}
}
public abstract Label GetLabel(ColumnInfo column);
public abstract IEnumerable<VisualElement> GetChildren();
public void SetText(ColumnInfo column, object obj)
{
SetText(column, obj.ToString());
}
public void SetText(ColumnInfo column, string text)
{
var label = GetLabel(column);
label.text = text;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ac8d50f233b70db42a649ca6b7d69010
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData: ''
assetBundleName: ''
assetBundleVariant: ''

View File

@ -0,0 +1,63 @@
using UnityEngine.UIElements;
namespace Mirage.NetworkProfiler.ModuleGUI.UITable
{
internal class SortHeader : Label
{
public const string ARROW_UP = "\u2191";
public const string ARROW_DOWN = "\u2193";
public SortMode SortMode;
private readonly SortHeaderRow _row;
private string _nameWithoutSort;
public string NameWithoutSort
{
get
{
// this get should be called before any modifications are made
// so lazy property should be safe here
if (_nameWithoutSort == null)
_nameWithoutSort = text;
return _nameWithoutSort;
}
}
public ColumnInfo Info { get; internal set; }
public SortHeader(SortHeaderRow row) : base()
{
_row = row;
this.AddManipulator(new Clickable(OnClick));
}
private void OnClick(EventBase evt)
{
// just flip between Accending and Descending, no going back to none
if (SortMode == SortMode.Accending)
SortMode = SortMode.Descending;
else
SortMode = SortMode.Accending;
_row.ApplySort(this);
}
public void UpdateName()
{
switch (SortMode)
{
case SortMode.None:
text = NameWithoutSort;
break;
case SortMode.Accending:
text = ARROW_UP + NameWithoutSort;
break;
case SortMode.Descending:
text = ARROW_DOWN + NameWithoutSort;
break;
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ab266974aca81aa4abbaefba1e934073
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData: ''
assetBundleName: ''
assetBundleVariant: ''

View File

@ -0,0 +1,54 @@
using System;
using UnityEngine.UIElements;
namespace Mirage.NetworkProfiler.ModuleGUI.UITable
{
internal class SortHeaderRow : LabelRow
{
private readonly ITableSorter _sorter;
private SortHeader _currentSort;
public SortHeaderRow(Table table, ITableSorter sorter) : base(table, null)
{
_sorter = sorter ?? throw new ArgumentNullException(nameof(sorter));
}
protected override Label CreateLabel(ColumnInfo column)
{
var label = column.AllowSort
? new SortHeader(this)
: new Label();
SetLabelStyle(column, label);
return label;
}
/// <summary>
/// Update names of header based on sort
/// </summary>
/// <param name="sortHeader"></param>
public void SetSortHeader(SortHeader sortHeader)
{
// not null or current
if (_currentSort != null && _currentSort != sortHeader)
{
_currentSort.SortMode = SortMode.None;
_currentSort.UpdateName();
}
_currentSort = sortHeader;
_currentSort.UpdateName();
}
/// <summary>
/// Updates names and applies sort to table
/// </summary>
/// <param name="sortHeader"></param>
public void ApplySort(SortHeader sortHeader)
{
SetSortHeader(sortHeader);
_sorter.Sort(Table, sortHeader);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4b4301d9972553c4ab486e9b8915f784
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData: ''
assetBundleName: ''
assetBundleVariant: ''

View File

@ -0,0 +1,9 @@
namespace Mirage.NetworkProfiler.ModuleGUI.UITable
{
internal enum SortMode
{
None,
Accending,
Descending,
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f0d80f37bc0b1a44cbad7667bba04c79
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData: ''
assetBundleName: ''
assetBundleVariant: ''

View File

@ -0,0 +1,113 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;
namespace Mirage.NetworkProfiler.ModuleGUI.UITable
{
internal class Table
{
public readonly VisualElement VisualElement;
public readonly ScrollView ScrollView;
public readonly SortHeaderRow Header;
public readonly List<Row> Rows = new List<Row>();
public readonly IReadOnlyList<ColumnInfo> HeaderInfo;
public bool ContainsEmptyRows { get; private set; }
public Table(IEnumerable<ColumnInfo> columns, ITableSorter sorter)
{
// create readonly list from given Enumerable
HeaderInfo = new List<ColumnInfo>(columns);
// create table root and scroll view
// root can be Horizontal scroll
// Horizontal will also move header, but Vertical keeps header at top
VisualElement = new ScrollView(ScrollViewMode.Horizontal);
// seperate root for contents, so that Horizontal from scroll isn't applied
var root = new VisualElement();
VisualElement.Add(root);
// using VerticalAndHorizontal fixes header being squashed, not sure why, or if it'll cause future problems
ScrollView = new ScrollView(ScrollViewMode.VerticalAndHorizontal);
// add header to table root
// header will initialize labels, but we need to set text
Header = new SortHeaderRow(this, sorter);
// add header and scroll to root
root.Add(Header.VisualElement);
root.Add(ScrollView);
// add headers
foreach (var c in columns)
{
var ele = Header.GetLabel(c);
ele.text = c.Header;
if (c.AllowSort)
{
var sortHeader = (SortHeader)ele;
sortHeader.Info = c;
}
// make header element thicker
var eleStyle = ele.style;
eleStyle.unityFontStyleAndWeight = FontStyle.Bold;
eleStyle.borderBottomWidth = 3;
eleStyle.borderRightWidth = 3;
}
}
public Row AddRow(Row previous = null)
{
var row = new LabelRow(this, previous);
Rows.Add(row);
return row;
}
public Row AddEmptyRow(Row previous = null)
{
var row = new EmptyRow(this, previous);
ContainsEmptyRows = true;
Rows.Add(row);
return row;
}
public void ChangeWidth(ColumnInfo column, int newWidth, bool setVisibility)
{
foreach (var row in Rows)
{
if (row is EmptyRow)
continue;
var label = row.GetLabel(column);
var style = label.style;
style.width = newWidth;
if (setVisibility)
style.display = newWidth > 0 ? DisplayStyle.Flex : DisplayStyle.None;
}
}
/// <summary>
/// Removes all rows expect header
/// </summary>
public void Clear()
{
ScrollView.Clear();
Rows.Clear();
Rows.Add(Header);
ContainsEmptyRows = false;
}
/// <summary>
/// Updates names of sort header
/// </summary>
public void SetSortHeader(ColumnInfo info, SortMode mode)
{
var sortBy = (SortHeader)Header.GetLabel(info);
sortBy.SortMode = mode;
Header.SetSortHeader(sortBy);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2a7346de93910944c965b68a39d93082
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData: ''
assetBundleName: ''
assetBundleVariant: ''

21
Assets/Mirage.Profiler/LICENSE Executable file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 James Frowen
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.

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: d4452ce1fed4a2641879ef852c413104
DefaultImporter:
externalObjects: {}
userData: ''
assetBundleName: ''
assetBundleVariant: ''

View File

@ -0,0 +1 @@
See full readme here https://github.com/James-Frowen/Mirage.Profiler

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: cc3de9d50be43ed438d71e49f728567c
TextScriptImporter:
externalObjects: {}
userData: ''
assetBundleName: ''
assetBundleVariant: ''

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 82fa6b4931bac2f41b95862e74371e17
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData: ''
assetBundleName: ''
assetBundleVariant: ''

View File

@ -0,0 +1,6 @@
using System.Reflection;
using System.Runtime.CompilerServices;
[assembly: AssemblyVersion("1.1.0.11")]
[assembly: InternalsVisibleTo("Mirage.Profiler.Editor")]

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b0ddded0f98d71546aeae6e4c52aef54
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData: ''
assetBundleName: ''
assetBundleVariant: ''

View File

@ -0,0 +1,87 @@
using System.Collections.Generic;
using Mirror;
using Unity.Profiling;
using UnityEngine;
namespace Mirage.NetworkProfiler
{
internal class CountRecorder
{
private readonly ProfilerCounter<int> _profilerCount;
private readonly ProfilerCounter<int> _profilerBytes;
private readonly ProfilerCounter<int> _profilerPerSecond;
private readonly object _instance;
private readonly INetworkInfoProvider _provider;
internal readonly Frames _frames;
private int _count;
private int _bytes;
private int _perSecond;
private readonly Queue<(float time, int bytes)> _perSecondQueue = new Queue<(float time, int bytes)>();
private int _frameIndex = -1;
public CountRecorder(object instance, INetworkInfoProvider provider, ProfilerCounter<int> profilerCount, ProfilerCounter<int> profilerBytes, ProfilerCounter<int> profilerPerSecond)
{
_provider = provider;
_instance = instance;
_profilerCount = profilerCount;
_profilerBytes = profilerBytes;
_profilerPerSecond = profilerPerSecond;
_frames = new Frames();
}
public void OnMessage(NetworkDiagnostics.MessageInfo obj)
{
// using the profiler-window branch of mirage to allow NetworkDiagnostics to say which server/client is sent the event
#if MIRAGE_DIAGNOSTIC_INSTANCE
if (obj.instance != _instance)
return;
#endif
// dont record anything if frame index is -1
// this normally means the profiler has not been activated yet
if (_frameIndex == -1)
return;
_count += obj.count;
_bytes += obj.bytes * obj.count;
var frame = _frames.GetFrame(_frameIndex);
frame.Messages.Add(new MessageInfo(obj, _provider, frame.Messages.Count));
frame.Bytes++;
}
public void EndFrame(int frameIndex)
{
CaclulatePerSecond(Time.time, _bytes);
_profilerCount.Sample(_count);
_profilerBytes.Sample(_bytes);
_count = 0;
_bytes = 0;
// +1 so that we set next frame
// otherwise we clear the frame that the savedata wants to grab
_frameIndex = frameIndex + 1;
var frame = _frames.GetFrame(_frameIndex);
frame.Messages.Clear();
frame.Bytes = 0;
}
private void CaclulatePerSecond(float now, int bytes)
{
// add new values to sum
_perSecond += bytes;
_perSecondQueue.Enqueue((now, bytes));
// remove old bytes from sum
var removeTime = now - 1;
while (_perSecondQueue.Peek().time < removeTime)
{
var removed = _perSecondQueue.Dequeue();
_perSecond -= removed.bytes;
}
// record sample after adding/removing value
_profilerPerSecond.Sample(_perSecond);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 16575ee3be2c5e046af4438787d215f0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData: ''
assetBundleName: ''
assetBundleVariant: ''

View File

@ -0,0 +1,23 @@
using Unity.Profiling;
namespace Mirage.NetworkProfiler
{
internal static class Counters
{
public static readonly ProfilerCategory Category = ProfilerCategory.Network;
private const ProfilerMarkerDataUnit COUNT = ProfilerMarkerDataUnit.Count;
private const ProfilerMarkerDataUnit BYTES = ProfilerMarkerDataUnit.Bytes;
public static readonly ProfilerCounter<int> PlayerCount = new ProfilerCounter<int>(Category, Names.PLAYER_COUNT, COUNT);
public static readonly ProfilerCounter<int> CharCount = new ProfilerCounter<int>(Category, Names.CHARACTER_COUNT, COUNT);
public static readonly ProfilerCounter<int> ObjectCount = new ProfilerCounter<int>(Category, Names.OBJECT_COUNT, COUNT);
public static readonly ProfilerCounter<int> SentCount = new ProfilerCounter<int>(Category, Names.SENT_COUNT, COUNT);
public static readonly ProfilerCounter<int> SentBytes = new ProfilerCounter<int>(Category, Names.SENT_BYTES, BYTES);
public static readonly ProfilerCounter<int> SentPerSecond = new ProfilerCounter<int>(Category, Names.SENT_PER_SECOND, BYTES);
public static readonly ProfilerCounter<int> ReceiveCount = new ProfilerCounter<int>(Category, Names.RECEIVED_COUNT, COUNT);
public static readonly ProfilerCounter<int> ReceiveBytes = new ProfilerCounter<int>(Category, Names.RECEIVED_BYTES, BYTES);
public static readonly ProfilerCounter<int> ReceivePerSecond = new ProfilerCounter<int>(Category, Names.RECEIVED_PER_SECOND, BYTES);
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 40d8d596129601f4d90f0664cb2557c4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData: ''
assetBundleName: ''
assetBundleVariant: ''

View File

@ -0,0 +1,44 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Mirage.NetworkProfiler
{
[System.Serializable]
public class Frames : IEnumerable<Frame>
{
[SerializeField]
private Frame[] _frames;
public Frames()
{
_frames = new Frame[NetworkProfilerRecorder.FRAME_COUNT];
for (var i = 0; i < _frames.Length; i++)
_frames[i] = new Frame();
}
public Frame GetFrame(int frameIndex)
{
return _frames[frameIndex % _frames.Length];
}
public IEnumerator<Frame> GetEnumerator() => ((IEnumerable<Frame>)_frames).GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable<Frame>)_frames).GetEnumerator();
internal void ValidateSize()
{
if (_frames.Length != NetworkProfilerRecorder.FRAME_COUNT)
{
Array.Resize(ref _frames, NetworkProfilerRecorder.FRAME_COUNT);
}
}
}
[System.Serializable]
public class Frame
{
public List<MessageInfo> Messages = new List<MessageInfo>();
public int Bytes;
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b59754fd175ff2345a6788891dddbfcf
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData: ''
assetBundleName: ''
assetBundleVariant: ''

View File

@ -0,0 +1,45 @@
using Mirror;
using UnityEngine;
namespace Mirage.NetworkProfiler
{
[System.Serializable]
public class MessageInfo
{
/// <summary>
/// Order message was sent/received in frame
/// </summary>
[SerializeField] private int _order;
[SerializeField] private int _bytes;
[SerializeField] private int _count;
[SerializeField] private string _messageName;
// unity can't serialize nullable so store as 2 fields
[SerializeField] private bool _hasNetId;
[SerializeField] private uint _netId;
[SerializeField] private string _objectName;
[SerializeField] private string _rpcName;
public int Order => _order;
public string Name => _messageName;
public int Bytes => _bytes;
public int Count => _count;
public int TotalBytes => Bytes * Count;
public uint? NetId => _hasNetId ? _netId : default;
public string ObjectName => _objectName;
public string RpcName => _rpcName;
public MessageInfo(NetworkDiagnostics.MessageInfo msg, INetworkInfoProvider provider, int order)
{
_order = order;
_bytes = msg.bytes;
_count = msg.count;
_messageName = msg.message.GetType().FullName;
var id = provider.GetNetId(msg);
_hasNetId = id.HasValue;
_netId = id.GetValueOrDefault();
var obj = provider.GetNetworkIdentity(id);
_objectName = obj != null ? obj.name : null;
_rpcName = provider.GetRpcName(msg);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 668dc7c434784214794807fcfecfe185
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData: ''
assetBundleName: ''
assetBundleVariant: ''

View File

@ -0,0 +1,17 @@
{
"name": "Mirage.Profiler",
"rootNamespace": "",
"references": [
"GUID:b46779583a009f04ba9f5f31d0e7e6ac",
"GUID:30817c1a0e6d646d99c048fc403f5979"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 6ff9f1c8e2ed4034baf80c43db7a3b6a
AssemblyDefinitionImporter:
externalObjects: {}
userData: ''
assetBundleName: ''
assetBundleVariant: ''

View File

@ -0,0 +1,23 @@
namespace Mirage.NetworkProfiler
{
public static class Names
{
public const string PLAYER_COUNT = "Player Count";
public const string PLAYER_COUNT_TOOLTIP = "Number of players connected to the server";
public const string CHARACTER_COUNT = "Character Count";
public const string CHARACTER_COUNT_TOOLTIP = "Number of players with spawned GameObjects";
public const string OBJECT_COUNT = "Object Count";
public const string OBJECT_COUNT_TOOLTIP = "Number of NetworkIdentities spawned on the server";
public const string SENT_COUNT = "Sent Messages";
public const string SENT_BYTES = "Sent Bytes";
public const string SENT_PER_SECOND = "Sent Per Second";
public const string RECEIVED_COUNT = "Received Messages";
public const string RECEIVED_BYTES = "Received Bytes";
public const string RECEIVED_PER_SECOND = "Received Per Second";
public const string PER_SECOND_TOOLTIP = "Sum of Bytes over the previous second";
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 05fd6c7133d9b2e42a8f282465a445d8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData: ''
assetBundleName: ''
assetBundleVariant: ''

View File

@ -0,0 +1,105 @@
using System.Text.RegularExpressions;
using Mirror;
using Mirror.RemoteCalls;
using UnityEngine;
using UnityEngine.Assertions;
namespace Mirage.NetworkProfiler
{
/// <summary>
/// Returns information about NetworkMessage
/// </summary>
public interface INetworkInfoProvider
{
uint? GetNetId(NetworkDiagnostics.MessageInfo info);
NetworkIdentity GetNetworkIdentity(uint? netId);
string GetRpcName(NetworkDiagnostics.MessageInfo info);
}
public class NetworkInfoProvider : INetworkInfoProvider
{
public uint? GetNetId(NetworkDiagnostics.MessageInfo info)
{
switch (info.message)
{
case CommandMessage msg: return msg.netId;
case RpcMessage msg: return msg.netId;
case SpawnMessage msg: return msg.netId;
case ChangeOwnerMessage msg: return msg.netId;
case ObjectDestroyMessage msg: return msg.netId;
case ObjectHideMessage msg: return msg.netId;
case EntityStateMessage msg: return msg.netId;
default: return default;
}
}
public NetworkIdentity GetNetworkIdentity(uint? netId)
{
if (!netId.HasValue)
return null;
if (NetworkServer.active)
{
NetworkServer.spawned.TryGetValue(netId.Value, out var identity);
return identity;
}
if (NetworkClient.active)
{
NetworkClient.spawned.TryGetValue(netId.Value, out var identity);
return identity;
}
return null;
}
public string GetRpcName(NetworkDiagnostics.MessageInfo info)
{
switch (info.message)
{
case CommandMessage msg:
return GetRpcName(msg.netId, msg.componentIndex, msg.functionHash);
case RpcMessage msg:
return GetRpcName(msg.netId, msg.componentIndex, msg.functionHash);
default: return string.Empty;
}
}
#if MIRROR_2022_9_OR_NEWER
private string GetRpcName(uint netId, int componentIndex, int functionIndex)
#else
private string GetRpcName(uint netId, int componentIndex, ushort functionIndex)
#endif
{
var hash = functionIndex;
var remoteCallDelegate = RemoteProcedureCalls.GetDelegate(hash);
if (remoteCallDelegate != null)
{
string fixedMethodName = MirrorMethodNameTrimmer.FixMethodName(remoteCallDelegate.Method.Name);
string methodFullName = $"{remoteCallDelegate.Method.DeclaringType?.FullName}.{fixedMethodName}";
return methodFullName;
}
// todo some error maybe
return string.Empty;
}
/// <summary>
/// Some stuff from Mirror.Weaver
/// </summary>
private static class MirrorMethodNameTrimmer
{
private static readonly Regex regex1 = new Regex($"^InvokeUserCode_", RegexOptions.Compiled);
private static readonly Regex regex2 = new Regex("__[A-Z][\\w`]*$", RegexOptions.Compiled);
public static string FixMethodName(string methodName)
{
methodName = regex1.Replace(methodName, "");
methodName = regex2.Replace(methodName, "");
return methodName;
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8192a6dbec7e3b446bea79c4b0f172be
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData: ''
assetBundleName: ''
assetBundleVariant: ''

View File

@ -0,0 +1,112 @@
using UnityEngine;
using Mirror;
#if UNITY_EDITOR
using UnityEditorInternal;
#endif
namespace Mirage.NetworkProfiler
{
[DefaultExecutionOrder(int.MaxValue)] // last
public class NetworkProfilerRecorder : MonoBehaviour
{
// singleton because unity only has 1 profiler
public static NetworkProfilerRecorder Instance { get; private set; }
internal static CountRecorder _sentCounter;
internal static CountRecorder _receivedCounter;
internal const int FRAME_COUNT = 300; // todo find a way to get real frame count
public delegate void FrameUpdate(int tick);
public static event FrameUpdate AfterSample;
private int _lastProcessedFrame = -1;
private void Start()
{
if (Instance == null)
{
#if UNITY_EDITOR
_lastProcessedFrame = ProfilerDriver.lastFrameIndex;
#endif
var provider = new NetworkInfoProvider();
_sentCounter = new CountRecorder(null, provider, Counters.SentCount, Counters.SentBytes, Counters.SentPerSecond);
_receivedCounter = new CountRecorder(null, provider, Counters.ReceiveCount, Counters.ReceiveBytes, Counters.ReceivePerSecond);
NetworkDiagnostics.InMessageEvent += _receivedCounter.OnMessage;
NetworkDiagnostics.OutMessageEvent += _sentCounter.OnMessage;
Instance = this;
DontDestroyOnLoad(this);
}
}
private void OnDestroy()
{
if (Instance == this)
{
if (_receivedCounter != null)
NetworkDiagnostics.InMessageEvent -= _receivedCounter.OnMessage;
if (_sentCounter != null)
NetworkDiagnostics.OutMessageEvent -= _sentCounter.OnMessage;
Instance = null;
}
}
private void LateUpdate()
{
if (!NetworkServer.active && !NetworkClient.active)
return;
#if UNITY_EDITOR
if (!ProfilerDriver.enabled)
return;
// once a frame, ever frame, no matter what lastFrameIndex is
SampleCounts();
// unity sometimes skips a profiler frame, because unity
// so we have to check if that happens and then sample the missing frame
while (_lastProcessedFrame < ProfilerDriver.lastFrameIndex)
{
_lastProcessedFrame++;
//Debug.Log($"Sample: [LateUpdate, enabled { ProfilerDriver.enabled}, first {ProfilerDriver.firstFrameIndex}, last {ProfilerDriver.lastFrameIndex}, lastProcessed {lastProcessedFrame}]");
var lastFrame = _lastProcessedFrame;
// not sure why frame is offset, but +2 fixes it
SampleMessages(lastFrame + 2);
}
#else
// in player, just use ProfilerCounter (frameCount only used by messages)
SampleCounts();
SampleMessages(0);
#endif
}
/// <summary>
/// call this every frame to sample number of players and objects
/// </summary>
private void SampleCounts()
{
if (!NetworkServer.active)
return;
Counters.PlayerCount.Sample(NetworkServer.connections.Count);
Counters.PlayerCount.Sample(NetworkManager.singleton.numPlayers);
Counters.ObjectCount.Sample(NetworkServer.spawned.Count);
}
/// <summary>
/// call this when ProfilerDriver shows it is next frame
/// </summary>
/// <param name="frame"></param>
private void SampleMessages(int frame)
{
_sentCounter.EndFrame(frame);
_receivedCounter.EndFrame(frame);
AfterSample?.Invoke(frame);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bc5b42cbfc38b1c4d86fd25905b19e29
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData: ''
assetBundleName: ''
assetBundleVariant: ''

View File

@ -0,0 +1,16 @@
{
"name": "com.james-frowen.mirage-profiler",
"displayName": "Mirage Profiler",
"version": "1.1.0-mirror.11",
"unity": "2021.3",
"description": "Network profiler for Mirage",
"author": "James Frowen",
"repository": {
"type": "git",
"url": "https://github.com/James-Frowen/Mirage.Profiler"
},
"dependencies": {
"com.unity.profiling.core": "1.0.2"
},
"samples": []
}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 2c236d885d1e08b41870190b5e9f0d37
TextScriptImporter:
externalObjects: {}
userData: ''
assetBundleName: ''
assetBundleVariant: ''

View File

@ -0,0 +1,590 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
namespace Mirror
{
[AddComponentMenu("Network/Network Transform (Unreliable)")]
public class HybridNetworkTransform : NetworkTransformBase
{
[Header("Full Send Interval Multiplier")]
[Tooltip("Check/Sync every multiple of Network Manager send interval (= 1 / NM Send Rate), instead of every send interval.\n(30 NM send rate, and 3 interval, is a send every 0.1 seconds)\nA larger interval means less network sends, which has a variety of upsides. The drawbacks are delays and lower accuracy, you should find a nice balance between not sending too much, but the results looking good for your particular scenario.")]
[Range(1, 120)]
public uint fullSendIntervalMultiplier = 30;
private uint fullSendIntervalCounter = 0;
double lastFullSendIntervalTime = double.MinValue;
private byte lastSentFullSyncIndex = 0;
private SyncDataFull lastSentFullSyncData;
private QuantizedSnapshot lastSentFullQuantized;
private SyncDataFull lastReceivedFullSyncData;
private QuantizedSnapshot lastReceivedFullQuantized;
[Header("Delta Send Interval Multiplier")]
[Tooltip("Check/Sync every multiple of Network Manager send interval (= 1 / NM Send Rate), instead of every send interval.\n(30 NM send rate, and 3 interval, is a send every 0.1 seconds)\nA larger interval means less network sends, which has a variety of upsides. The drawbacks are delays and lower accuracy, you should find a nice balance between not sending too much, but the results looking good for your particular scenario.")]
[Range(1, 120)]
public uint deltaSendIntervalMultiplier = 1;
private uint deltaSendIntervalCounter = 0;
double lastDeltaSendIntervalTime = double.MinValue;
[Header("Rotation")]
[Tooltip("Sensitivity of changes needed before an updated state is sent over the network")]
public float rotationSensitivity = 0.01f;
[Header("Precision")]
[Tooltip("Position is rounded in order to drastically minimize bandwidth.\n\nFor example, a precision of 0.01 rounds to a centimeter. In other words, sub-centimeter movements aren't synced until they eventually exceeded an actual centimeter.\n\nDepending on how important the object is, a precision of 0.01-0.10 (1-10 cm) is recommended.\n\nFor example, even a 1cm precision combined with delta compression cuts the Benchmark demo's bandwidth in half, compared to sending every tiny change.")]
[Range(0.00_01f, 1f)] // disallow 0 division. 1mm to 1m precision is enough range.
public float positionPrecision = 0.01f; // 1 cm
[Range(0.00_01f, 1f)] // disallow 0 division. 1mm to 1m precision is enough range.
public float scalePrecision = 0.01f; // 1 cm
[SerializeField] protected SyncSettings syncSettings;
protected override void OnEnable()
{
base.OnEnable();
// NTBase has options to sync pos/rot/scale. Sync Settings has the same ability
// If sync settings is not set, we use NTBase's settings.
if (syncSettings != SyncSettings.None)
syncSettings = InitSyncSettings();
}
protected virtual SyncSettings InitSyncSettings()
{
SyncSettings syncSettings = SyncSettings.None;
if (syncPosition) syncSettings |= (SyncSettings.SyncPosX | SyncSettings.SyncPosY | SyncSettings.SyncPosZ);
if (syncRotation) syncSettings |= SyncSettings.SyncRot;
if (syncScale) syncSettings |= SyncSettings.SyncScale;
if (compressRotation) syncSettings |= SyncSettings.CompressRot;
return syncSettings;
}
protected override void OnValidate()
{
base.OnValidate();
if ((syncSettings & (SyncSettings.CompressRot | SyncSettings.UseEulerAngles)) > 0) syncSettings &= ~SyncSettings.CompressRot;
fullSendIntervalMultiplier = Math.Max(deltaSendIntervalMultiplier, fullSendIntervalMultiplier) +1;
}
#region Apply Interpolation
void Update()
{
if (isServer) UpdateServerInterpolation();
// for all other clients (and for local player if !authority),
// we need to apply snapshots from the buffer.
// 'else if' because host mode shouldn't interpolate client
else if (isClient && !IsClientWithAuthority) UpdateClientInterpolation();
}
void UpdateServerInterpolation()
{
// apply buffered snapshots IF client authority
// -> in server authority, server moves the object
// so no need to apply any snapshots there.
// -> don't apply for host mode player objects either, even if in
// client authority mode. if it doesn't go over the network,
// then we don't need to do anything.
// -> connectionToClient is briefly null after scene changes:
// https://github.com/MirrorNetworking/Mirror/issues/3329
if (syncDirection == SyncDirection.ClientToServer &&
connectionToClient != null &&
!isOwned)
{
if (serverSnapshots.Count == 0) return;
// step the transform interpolation without touching time.
// NetworkClient is responsible for time globally.
SnapshotInterpolation.StepInterpolation(
serverSnapshots,
connectionToClient.remoteTimeline,
out TransformSnapshot from,
out TransformSnapshot to,
out double t);
// interpolate & apply
TransformSnapshot computed = TransformSnapshot.Interpolate(from, to, t);
Apply(computed, to);
}
}
void UpdateClientInterpolation()
{
// only while we have snapshots
if (clientSnapshots.Count == 0) return;
// step the interpolation without touching time.
// NetworkClient is responsible for time globally.
SnapshotInterpolation.StepInterpolation(
clientSnapshots,
NetworkTime.time, // == NetworkClient.localTimeline from snapshot interpolation
out TransformSnapshot from,
out TransformSnapshot to,
out double t);
// interpolate & apply
TransformSnapshot computed = TransformSnapshot.Interpolate(from, to, t);
Apply(computed, to);
}
#endregion
#region Initial State Serialization
public override void OnSerialize(NetworkWriter writer, bool initialState)
{
// sync target component's position on spawn.
// fixes https://github.com/vis2k/Mirror/pull/3051/
// (Spawn message wouldn't sync NTChild positions either)
if (initialState)
{
if (syncPosition) writer.WriteVector3(GetPosition());
if (syncRotation) writer.WriteQuaternion(GetRotation());
if (syncScale) writer.WriteVector3(GetScale());
}
}
public override void OnDeserialize(NetworkReader reader, bool initialState)
{
// sync target component's position on spawn.
// fixes https://github.com/vis2k/Mirror/pull/3051/
// (Spawn message wouldn't sync NTChild positions either)
if (initialState)
{
if (syncPosition) SetPosition(reader.ReadVector3());
if (syncRotation) SetRotation(reader.ReadQuaternion());
if (syncScale) SetScale(reader.ReadVector3());
}
}
#endregion
void LateUpdate()
{
// if server then always sync to others.
if (isServer) UpdateServerBroadcast();
// client authority, and local player (= allowed to move myself)?
// 'else if' because host mode shouldn't send anything to server.
// it is the server. don't overwrite anything there.
else if (isClient && IsClientWithAuthority) UpdateClientBroadcast();
}
void UpdateServerBroadcast()
{
// broadcast to all clients each 'sendInterval'
// (client with authority will drop the rpc)
// NetworkTime.localTime for double precision until Unity has it too
//
// IMPORTANT:
// snapshot interpolation requires constant sending.
// DO NOT only send if position changed. for example:
// ---
// * client sends first position at t=0
// * ... 10s later ...
// * client moves again, sends second position at t=10
// ---
// * server gets first position at t=0
// * server gets second position at t=10
// * server moves from first to second within a time of 10s
// => would be a super slow move, instead of a wait & move.
//
// IMPORTANT:
// DO NOT send nulls if not changed 'since last send' either. we
// send unreliable and don't know which 'last send' the other end
// received successfully.
//
// Checks to ensure server only sends snapshots if object is
// on server authority(!clientAuthority) mode because on client
// authority mode snapshots are broadcasted right after the authoritative
// client updates server in the command function(see above), OR,
// since host does not send anything to update the server, any client
// authoritative movement done by the host will have to be broadcasted
// here by checking IsClientWithAuthority.
// TODO send same time that NetworkServer sends time snapshot?
CheckLastSendTime();
if (syncDirection == SyncDirection.ServerToClient || IsClientWithAuthority)
{
if (fullSendIntervalCounter == fullSendIntervalMultiplier) ServerBroadcastFull();
else if (deltaSendIntervalCounter == deltaSendIntervalMultiplier) ServerBroadcastDelta();
}
}
#region Server Broadcast Full
protected virtual void ServerBroadcastFull()
{
lastSentFullSyncData = ConstructFullSyncData(true);
lastSentFullQuantized = ConstructQuantizedSnapshot(lastSentFullSyncData.position, lastSentFullSyncData.rotation, lastSentFullSyncData.scale);
RpcServerToClientSyncFull(lastSentFullSyncData);
}
private byte NextFullSyncIndex()
{
if (lastSentFullSyncIndex == 255) lastSentFullSyncIndex = 0;
else lastSentFullSyncIndex += 1;
return lastSentFullSyncIndex;
}
[ClientRpc]
void RpcServerToClientSyncFull(SyncDataFull syncData) =>
OnServerToClientSyncFull(syncData);
protected virtual void OnServerToClientSyncFull(SyncDataFull syncData)
{
// in host mode, the server sends rpcs to all clients.
// the host client itself will receive them too.
// -> host server is always the source of truth
// -> we can ignore any rpc on the host client
// => otherwise host objects would have ever growing clientBuffers
// (rpc goes to clients. if isServer is true too then we are host)
if (isServer) return;
// don't apply for local player with authority
if (IsClientWithAuthority) return;
double timestamp = NetworkClient.connection.remoteTimeStamp;
// TODO, if we are syncing full by pos axis, we need to maybe
// use current non-synced axis instead of giving it a 0.
lastReceivedFullSyncData = syncData;
lastReceivedFullQuantized = ConstructQuantizedSnapshot(syncData.position, syncData.rotation, syncData.scale);
// We don't care if we are adding 'default' to any field because
// syncing is checked again in Apply before applying the changes.
AddSnapshot(clientSnapshots, timestamp + timeStampAdjustment + offset, syncData.position, syncData.rotation, syncData.scale);
}
#endregion
#region Server Broadcast Delta
protected virtual void ServerBroadcastDelta()
{
SyncDataFull currentFull = ConstructFullSyncData(false);
QuantizedSnapshot currentQuantized = ConstructQuantizedSnapshot(currentFull.position, currentFull.rotation, currentFull.scale);
SyncDataDelta syncDataDelta = DeriveDelta(currentQuantized);
RpcServerToClientSyncDelta(syncDataDelta);
}
[ClientRpc (channel = Channels.Unreliable)]
void RpcServerToClientSyncDelta(SyncDataDelta syncData) =>
OnServerToClientSyncDelta(syncData);
protected virtual void OnServerToClientSyncDelta(SyncDataDelta delta)
{
// in host mode, the server sends rpcs to all clients.
// the host client itself will receive them too.
// -> host server is always the source of truth
// -> we can ignore any rpc on the host client
// => otherwise host objects would have ever growing clientBuffers
// (rpc goes to clients. if isServer is true too then we are host)
if (isServer) return;
// don't apply for local player with authority
if (IsClientWithAuthority) return;
double timestamp = NetworkClient.connection.remoteTimeStamp;
// If the delta syncdata is not based on the last received full sync, we discard.
if (delta.fullSyncDataIndex != lastReceivedFullSyncData.fullSyncDataIndex) return;
ApplyDelta(delta, out Vector3 position, out Quaternion rotation, out Vector3 scale);
// We don't care if we are adding 'default' to any field because
// syncing is checked again in Apply before applying the changes.
AddSnapshot(clientSnapshots, timestamp + timeStampAdjustment + offset, position, rotation, scale);
}
#endregion
/*
if (sendIntervalCounter == sendIntervalMultiplier && // same interval as time interpolation!
(syncDirection == SyncDirection.ServerToClient || IsClientWithAuthority))
{
// send snapshot without timestamp.
// receiver gets it from batch timestamp to save bandwidth.
TransformSnapshot snapshot = Construct();
cachedSnapshotComparison = CompareSnapshots(snapshot);
if (cachedSnapshotComparison && hasSentUnchangedPosition && onlySyncOnChange) { return; }
if (compressRotation)
{
RpcServerToClientSyncCompressRotation(
// only sync what the user wants to sync
syncPosition && positionChanged ? snapshot.position : default(Vector3?),
syncRotation && rotationChanged ? Compression.CompressQuaternion(snapshot.rotation) : default(uint?),
syncScale && scaleChanged ? snapshot.scale : default(Vector3?)
);
}
else
{
RpcServerToClientSync(
// only sync what the user wants to sync
syncPosition && positionChanged ? snapshot.position : default(Vector3?),
syncRotation && rotationChanged ? snapshot.rotation : default(Quaternion?),
syncScale && scaleChanged ? snapshot.scale : default(Vector3?)
);
}
if (cachedSnapshotComparison)
{
hasSentUnchangedPosition = true;
}
else
{
hasSentUnchangedPosition = false;
lastSnapshot = snapshot;
}
}
}*/
void UpdateClientBroadcast()
{
// https://github.com/vis2k/Mirror/pull/2992/
if (!NetworkClient.ready) return;
// send to server each 'sendInterval'
// NetworkTime.localTime for double precision until Unity has it too
//
// IMPORTANT:
// snapshot interpolation requires constant sending.
// DO NOT only send if position changed. for example:
// ---
// * client sends first position at t=0
// * ... 10s later ...
// * client moves again, sends second position at t=10
// ---
// * server gets first position at t=0
// * server gets second position at t=10
// * server moves from first to second within a time of 10s
// => would be a super slow move, instead of a wait & move.
//
// IMPORTANT:
// DO NOT send nulls if not changed 'since last send' either. we
// send unreliable and don't know which 'last send' the other end
// received successfully.
CheckLastSendTime();
if (syncDirection == SyncDirection.ServerToClient) return;
if (fullSendIntervalCounter == fullSendIntervalMultiplier) ClientBroadcastFull();
else if (deltaSendIntervalCounter == deltaSendIntervalMultiplier) ClientBroadcastDelta();
}
#region Client Broadcast Full
protected virtual void ClientBroadcastFull()
{
lastSentFullSyncData = ConstructFullSyncData(true);
lastSentFullQuantized = ConstructQuantizedSnapshot(lastSentFullSyncData.position, lastSentFullSyncData.rotation, lastSentFullSyncData.scale);
CmdClientToServerSyncFull(lastSentFullSyncData);
}
[Command]
void CmdClientToServerSyncFull(SyncDataFull syncData)
{
OnClientToServerSyncFull(syncData);
if (syncDirection == SyncDirection.ClientToServer)
RpcServerToClientSyncFull(syncData);
}
protected virtual void OnClientToServerSyncFull(SyncDataFull syncData)
{
// in host mode, the server sends rpcs to all clients.
// the host client itself will receive them too.
// -> host server is always the source of truth
// -> we can ignore any rpc on the host client
// => otherwise host objects would have ever growing clientBuffers
// (rpc goes to clients. if isServer is true too then we are host)
if (syncDirection != SyncDirection.ClientToServer) return;
double timestamp = connectionToClient.remoteTimeStamp;
// See Server's issue
lastReceivedFullSyncData = syncData;
lastReceivedFullQuantized = ConstructQuantizedSnapshot(syncData.position, syncData.rotation, syncData.scale);
// We don't care if we are adding 'default' to any field because
// syncing is checked again in Apply before applying the changes.
AddSnapshot(serverSnapshots, timestamp + timeStampAdjustment + offset, syncData.position, syncData.rotation, syncData.scale);
}
#endregion
#region Client Broadcast Delta
protected virtual void ClientBroadcastDelta()
{
SyncDataFull currentFull = ConstructFullSyncData(false);
QuantizedSnapshot currentQuantized = ConstructQuantizedSnapshot(currentFull.position, currentFull.rotation, currentFull.scale);
SyncDataDelta syncDataDelta = DeriveDelta(currentQuantized);
Debug.Log($"Client sending sync data delta index: {syncDataDelta.fullSyncDataIndex}");
CmdClientToServerSyncDelta(syncDataDelta);
}
[Command(channel = Channels.Unreliable)]
void CmdClientToServerSyncDelta(SyncDataDelta delta)
{
OnClientToServerSyncDelta(delta);
if (syncDirection == SyncDirection.ClientToServer)
RpcServerToClientSyncDelta(delta);
}
protected virtual void OnClientToServerSyncDelta(SyncDataDelta delta)
{
// only apply if in client authority mode
if (syncDirection != SyncDirection.ClientToServer) return;
// protect against ever growing buffer size attacks
if (serverSnapshots.Count >= connectionToClient.snapshotBufferSizeLimit) return;
//if (!isLocalPlayer) Debug.Log($"delta index received: {delta.fullSyncDataIndex}, last received {lastReceivedFullSyncData.fullSyncDataIndex}");
if (delta.fullSyncDataIndex != lastReceivedFullSyncData.fullSyncDataIndex) return;
double timestamp = connectionToClient.remoteTimeStamp;
ApplyDelta(delta, out Vector3 position, out Quaternion rotation, out Vector3 scale);
// We don't care if we are adding 'default' to any field because
// syncing is checked again in Apply before applying the changes.
AddSnapshot(serverSnapshots, timestamp + timeStampAdjustment + offset, position, rotation, scale);
}
#endregion
protected virtual SyncDataFull ConstructFullSyncData(bool updateIndex)
{
return new SyncDataFull(
updateIndex? NextFullSyncIndex() : lastSentFullSyncIndex,
syncSettings,
GetPosition(),
GetRotation(),
GetScale()
);
}
protected virtual QuantizedSnapshot ConstructQuantizedSnapshot(Vector3 position, Quaternion rotation, Vector3 scale)
{
Compression.ScaleToLong(position, positionPrecision, out Vector3Long positionQuantized);
Compression.ScaleToLong(scale, scalePrecision, out Vector3Long scaleQuantized);
return new QuantizedSnapshot(
positionQuantized,
rotation,
scaleQuantized
);
}
protected virtual SyncDataDelta DeriveDelta(QuantizedSnapshot current)
{
SyncDataDelta syncDataDelta = new SyncDataDelta();
syncDataDelta.fullSyncDataIndex = lastSentFullSyncIndex;
syncDataDelta.deltaHeader = DeltaHeader.None;
syncDataDelta.position = current.position - lastSentFullQuantized.position;
if ((syncSettings & SyncSettings.SyncPosX) > 0 && syncDataDelta.position.x != 0)
syncDataDelta.deltaHeader |= DeltaHeader.PosX;
if ((syncSettings & SyncSettings.SyncPosY) > 0 && syncDataDelta.position.y != 0)
syncDataDelta.deltaHeader |= DeltaHeader.PosY;
if ((syncSettings & SyncSettings.SyncPosZ) > 0 && syncDataDelta.position.z != 0)
{
syncDataDelta.deltaHeader |= DeltaHeader.PosZ;
}
// Rotation: We have 3 options:
// 1) Send compressed Quaternion
// 2) Send uncompressed Quaternion
// 3) Send Euler Angles
// If user only ever rotates 1, 2 axes then option 3 may save more bandwidth since
// we delta each axis.
// 1 and 2 prevents gimbal lock etc, and 2 if user requires absolute precision.
// We use 4 bits to express rotation type and change (NonEulerAngles, RotX, RotY, RotZ):
// 1) If nothing has changed or < rotationSensitivity, all 4 will be false. Doesn't matter which method
// we are treating rotation. We are not reading anything on the receiving side.
// 2) If NonEulerAngles is false, we check the next 3 for each individual axis.
// 3) If NonEulerAngles is true, we are sending Quaternion. We piggyback on the RotX bit to tell us
// if it is compressed Quat or uncompressed Quat.
if ((syncSettings & SyncSettings.SyncRot) > 0)
{
if ((syncSettings & SyncSettings.UseEulerAngles) > 0)
{
Compression.ScaleToLong(lastSentFullQuantized.rotation.eulerAngles, rotationSensitivity, out Vector3Long lastRotationEuler);
Compression.ScaleToLong(current.rotation.eulerAngles, rotationSensitivity, out Vector3Long currentRotationEuler);
syncDataDelta.eulRotation = currentRotationEuler - lastRotationEuler;
if (syncDataDelta.eulRotation.x != 0) syncDataDelta.deltaHeader |= DeltaHeader.RotX;
if (syncDataDelta.eulRotation.y != 0) syncDataDelta.deltaHeader |= DeltaHeader.RotY;
if (syncDataDelta.eulRotation.z != 0) syncDataDelta.deltaHeader |= DeltaHeader.RotZ;
}
else
{
if (Quaternion.Angle(lastSentFullQuantized.rotation, current.rotation) > rotationSensitivity)
{
syncDataDelta.quatRotation = current.rotation;
syncDataDelta.deltaHeader |= DeltaHeader.SendQuat;
if ((syncSettings & SyncSettings.CompressRot) > 0)
{
syncDataDelta.deltaHeader |= DeltaHeader.RotX;
}
}
}
}
if ((syncSettings & SyncSettings.SyncScale) > 0)
{
syncDataDelta.scale = current.scale - lastSentFullQuantized.scale;
if (syncDataDelta.scale != Vector3Long.zero)
{
syncDataDelta.deltaHeader |= DeltaHeader.Scale;
}
}
return syncDataDelta;
}
protected virtual void ApplyDelta(SyncDataDelta delta, out Vector3 position, out Quaternion rotation, out Vector3 scale)
{
position = Compression.ScaleToFloat(lastReceivedFullQuantized.position + delta.position, positionPrecision);
if ((lastReceivedFullSyncData.syncSettings & SyncSettings.UseEulerAngles) > 0)
{
Vector3 eulRotation = Compression.ScaleToFloat(lastReceivedFullQuantized.rotationEuler + delta.eulRotation, rotationSensitivity);
rotation = Quaternion.Euler(eulRotation);
}
else
{
if ((delta.deltaHeader & DeltaHeader.SendQuat) > 0)
rotation = delta.quatRotation;
else
rotation = lastReceivedFullSyncData.rotation;
}
scale = Compression.ScaleToFloat(lastReceivedFullQuantized.scale + delta.scale, scalePrecision);
}
protected virtual void CheckLastSendTime()
{
// We check interval every frame, and then send if interval is reached.
// So by the time sendIntervalCounter == sendIntervalMultiplier, data is sent,
// thus we reset the counter here.
// This fixes previous issue of, if sendIntervalMultiplier = 1, we send every frame,
// because intervalCounter is always = 1 in the previous version.
if (fullSendIntervalCounter == fullSendIntervalMultiplier) fullSendIntervalCounter = 0;
if (deltaSendIntervalCounter == deltaSendIntervalMultiplier) deltaSendIntervalCounter = 0;
// timeAsDouble not available in older Unity versions.
if (AccurateInterval.Elapsed(NetworkTime.localTime, NetworkServer.sendInterval, ref lastFullSendIntervalTime))
fullSendIntervalCounter++;
if (AccurateInterval.Elapsed(NetworkTime.localTime, NetworkServer.sendInterval, ref lastDeltaSendIntervalTime))
deltaSendIntervalCounter++;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fb1619f96225342ac97095bd7e87d3e6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,210 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
//using Unity.VisualScripting;
namespace Mirror
{
public struct SyncDataFull
{
public byte fullSyncDataIndex;
public SyncSettings syncSettings;
public Vector3 position;
public Quaternion rotation;
public Vector3 scale;
public SyncDataFull(byte fullSyncDataIndex, SyncSettings syncSettings, Vector3 position, Quaternion rotation, Vector3 scale)
{
this.fullSyncDataIndex = fullSyncDataIndex;
this.syncSettings = syncSettings;
this.position = position;
this.rotation = rotation;
this.scale = scale;
}
}
public struct SyncDataDelta
{
public byte fullSyncDataIndex;
public DeltaHeader deltaHeader;
public Vector3Long position;
public Quaternion quatRotation;
public Vector3Long eulRotation;
public Vector3Long scale;
public SyncDataDelta(byte fullSyncDataIndex, DeltaHeader deltaHeader, Vector3Long position, Quaternion rotation,Vector3Long eulRotation, Vector3Long scale)
{
this.fullSyncDataIndex = fullSyncDataIndex;
this.deltaHeader = deltaHeader;
this.position = position;
this.quatRotation = rotation;
this.eulRotation = eulRotation;
this.scale = scale;
}
}
public struct QuantizedSnapshot
{
public Vector3Long position;
public Quaternion rotation;
public Vector3Long rotationEuler;
public Vector3Long scale;
public QuantizedSnapshot(Vector3Long position, Quaternion rotation, Vector3Long scale)
{
this.position = position;
this.rotation = rotation;
this.rotationEuler = new Vector3Long();
this.scale = scale;
}
}
[Flags]
public enum DeltaHeader : byte
{
None = 0,
PosX = 1 << 0,
PosY = 1 << 1,
PosZ = 1 << 2,
SendQuat = 1 << 3,
RotX = 1 << 4,
RotY = 1 << 5,
RotZ = 1 << 6,
Scale = 1 << 7,
}
[Flags]
public enum SyncSettings : byte
{
None = 0,
SyncPosX = 1 << 0,
SyncPosY = 1 << 1,
SyncPosZ = 1 << 2,
SyncRot = 1 << 3,
SyncScale = 1 << 4,
CompressRot = 1 << 5,
UseEulerAngles = 1 << 6,
}
public static class SyncDataReaderWriter
{
public static void WriteSyncDataFull(this NetworkWriter writer, SyncDataFull syncData)
{
writer.WriteByte(syncData.fullSyncDataIndex);
writer.WriteByte((byte)syncData.syncSettings);
if ((syncData.syncSettings & SyncSettings.SyncPosX) > 0) writer.WriteFloat(syncData.position.x);
if ((syncData.syncSettings & SyncSettings.SyncPosY) > 0) writer.WriteFloat(syncData.position.y);
if ((syncData.syncSettings & SyncSettings.SyncPosZ) > 0) writer.WriteFloat(syncData.position.z);
if ((syncData.syncSettings & SyncSettings.SyncRot) > 0)
{
if ((syncData.syncSettings & SyncSettings.CompressRot) > 0) writer.WriteUInt(Compression.CompressQuaternion(syncData.rotation));
else writer.WriteQuaternion(syncData.rotation);
}
if ((syncData.syncSettings & SyncSettings.SyncScale) > 0) writer.WriteVector3(syncData.scale);
}
public static SyncDataFull ReadSyncDataFull(this NetworkReader reader)
{
byte index = reader.ReadByte();
SyncSettings syncSettings = (SyncSettings)reader.ReadByte();
// If we have nothing to read here, let's say because posX is unchanged, then we can write anything
// for now, but in the NT, we will need to check changedData again, to put the right values of the axis
// back. We don't have it here.
Vector3 position = new Vector3(
(syncSettings & SyncSettings.SyncPosX) > 0 ? reader.ReadFloat() : default,
(syncSettings & SyncSettings.SyncPosY) > 0 ? reader.ReadFloat() : default,
(syncSettings & SyncSettings.SyncPosZ) > 0 ? reader.ReadFloat() : default
);
Quaternion rotation = new Quaternion();
if ((syncSettings & SyncSettings.SyncRot) > 0)
rotation = (syncSettings & SyncSettings.CompressRot) > 0 ? Compression.DecompressQuaternion(reader.ReadUInt()) : reader.ReadQuaternion();
else
rotation = new Quaternion();
Vector3 scale = (syncSettings & SyncSettings.SyncScale) > 0 ? reader.ReadVector3() : default;
return new SyncDataFull(index, syncSettings, position, rotation, scale);
}
public static void WriteSyncDataDelta(this NetworkWriter writer, SyncDataDelta syncData)
{
writer.WriteByte(syncData.fullSyncDataIndex);
writer.WriteByte((byte)syncData.deltaHeader);
if ((syncData.deltaHeader & DeltaHeader.PosX) > 0) Compression.CompressVarInt(writer, syncData.position.x);
if ((syncData.deltaHeader & DeltaHeader.PosY) > 0) Compression.CompressVarInt(writer, syncData.position.y);
if ((syncData.deltaHeader & DeltaHeader.PosZ) > 0) Compression.CompressVarInt(writer, syncData.position.z);
if ((syncData.deltaHeader & DeltaHeader.SendQuat) > 0)
{
if ((syncData.deltaHeader & DeltaHeader.RotX) > 0) writer.WriteUInt(Compression.CompressQuaternion(syncData.quatRotation));
else writer.WriteQuaternion(syncData.quatRotation);
}
else
{
if ((syncData.deltaHeader & DeltaHeader.RotX) > 0) Compression.CompressVarInt(writer, syncData.eulRotation.x);
if ((syncData.deltaHeader & DeltaHeader.RotY) > 0) Compression.CompressVarInt(writer, syncData.eulRotation.y);
if ((syncData.deltaHeader & DeltaHeader.RotZ) > 0) Compression.CompressVarInt(writer, syncData.eulRotation.z);
}
if ((syncData.deltaHeader & DeltaHeader.Scale) > 0)
{
Compression.CompressVarInt(writer, syncData.scale.x);
Compression.CompressVarInt(writer, syncData.scale.y);
Compression.CompressVarInt(writer, syncData.scale.z);
}
}
public static SyncDataDelta ReadSyncDataDelta(this NetworkReader reader)
{
byte index = reader.ReadByte();
DeltaHeader header = (DeltaHeader)reader.ReadByte();
Vector3Long position = new Vector3Long(
(header & DeltaHeader.PosX) > 0 ? Compression.DecompressVarInt(reader) : 0,
(header & DeltaHeader.PosY) > 0 ? Compression.DecompressVarInt(reader) : 0,
(header & DeltaHeader.PosZ) > 0 ? Compression.DecompressVarInt(reader) : 0
);
Quaternion quatRotation = new Quaternion();
Vector3Long eulRotation = new Vector3Long();
if ((header & DeltaHeader.SendQuat) > 0)
{
if ((header & DeltaHeader.RotX) > 0) quatRotation = Compression.DecompressQuaternion(reader.ReadUInt());
else quatRotation = reader.ReadQuaternion();
}
else
{
eulRotation = new Vector3Long(
(header & DeltaHeader.RotX) > 0 ? Compression.DecompressVarInt(reader) : 0,
(header & DeltaHeader.RotY) > 0 ? Compression.DecompressVarInt(reader) : 0,
(header & DeltaHeader.RotZ) > 0 ? Compression.DecompressVarInt(reader) : 0
);
}
Vector3Long scale = new Vector3Long();
if ((header & DeltaHeader.Scale) > 0)
{
scale = new Vector3Long(
Compression.DecompressVarInt(reader),
Compression.DecompressVarInt(reader),
Compression.DecompressVarInt(reader)
);
}
return new SyncDataDelta(index, header, position, quatRotation, eulRotation, scale);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2c363936c3e954543824fa48e6534ed9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -15,6 +15,7 @@ GameObject:
- component: {fileID: 2240606817507776182}
- component: {fileID: 6900008319038825817}
- component: {fileID: 5194388907919410155}
- component: {fileID: 6104086894243768031}
m_Layer: 0
m_Name: Tank
m_TagString: Untagged
@ -29,14 +30,15 @@ Transform:
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1916082411674582}
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:
- {fileID: 7831918942946891954}
- {fileID: 4116800716706440423}
m_Father: {fileID: 0}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &114118589361100106
MonoBehaviour:
@ -50,9 +52,8 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 9b91ecbcc199f4492b9a91e820070131, type: 3}
m_Name:
m_EditorClassIdentifier:
clientStarted: 0
sceneId: 0
_assetId: 3454335836
_assetId: 2638947628
serverOnly: 0
visible: 0
hasSpawned: 0
@ -63,7 +64,7 @@ MonoBehaviour:
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1916082411674582}
m_Enabled: 1
m_Enabled: 0
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: a553cb17010b2403e8523b558bffbc14, type: 3}
m_Name:
@ -75,6 +76,8 @@ MonoBehaviour:
syncPosition: 1
syncRotation: 1
syncScale: 0
onlySyncOnChange: 1
compressRotation: 1
interpolatePosition: 1
interpolateRotation: 1
interpolateScale: 0
@ -84,8 +87,6 @@ MonoBehaviour:
showGizmos: 0
showOverlay: 0
overlayColor: {r: 0, g: 0, b: 0, a: 0.5}
onlySyncOnChange: 1
compressRotation: 1
bufferResetMultiplier: 5
positionSensitivity: 0.01
rotationSensitivity: 0.01
@ -117,7 +118,7 @@ MonoBehaviour:
health: 4
--- !u!95 &2240606817507776182
Animator:
serializedVersion: 3
serializedVersion: 5
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
@ -130,10 +131,12 @@ Animator:
m_UpdateMode: 0
m_ApplyRootMotion: 0
m_LinearVelocityBlending: 0
m_StabilizeFeet: 0
m_WarningMessage:
m_HasTransformHierarchy: 1
m_AllowConstantClipSamplingOptimization: 1
m_KeepAnimatorControllerStateOnDisable: 0
m_KeepAnimatorStateOnDisable: 0
m_WriteDefaultValuesOnDisable: 0
--- !u!195 &6900008319038825817
NavMeshAgent:
m_ObjectHideFlags: 0
@ -164,11 +167,55 @@ SphereCollider:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1916082411674582}
m_Material: {fileID: 0}
m_IncludeLayers:
serializedVersion: 2
m_Bits: 0
m_ExcludeLayers:
serializedVersion: 2
m_Bits: 0
m_LayerOverridePriority: 0
m_IsTrigger: 1
m_ProvidesContacts: 0
m_Enabled: 1
serializedVersion: 2
serializedVersion: 3
m_Radius: 0.5
m_Center: {x: 0, y: 0.25, z: 0}
--- !u!114 &6104086894243768031
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1916082411674582}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fb1619f96225342ac97095bd7e87d3e6, type: 3}
m_Name:
m_EditorClassIdentifier:
syncDirection: 1
syncMode: 0
syncInterval: 0
target: {fileID: 4492442352427800}
syncPosition: 1
syncRotation: 1
syncScale: 1
onlySyncOnChange: 1
compressRotation: 0
interpolatePosition: 1
interpolateRotation: 1
interpolateScale: 0
coordinateSpace: 0
sendIntervalMultiplier: 1
timelineOffset: 0
showGizmos: 0
showOverlay: 0
overlayColor: {r: 0, g: 0, b: 0, a: 0.5}
fullSendIntervalMultiplier: 13
deltaSendIntervalMultiplier: 13
rotationSensitivity: 0.01
positionPrecision: 0.01
scalePrecision: 0.01
syncSettings: 95
--- !u!1 &4730779867780281009
GameObject:
m_ObjectHideFlags: 0
@ -192,12 +239,13 @@ Transform:
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 4730779867780281009}
serializedVersion: 2
m_LocalRotation: {x: -0, y: 0.000000119209275, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0.0015906466, z: 0.009359999}
m_LocalScale: {x: 0.01, y: 0.010000003, z: 0.010000002}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 7831918942946891958}
m_RootOrder: 1
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &6425560216547760105
GameObject:
@ -225,12 +273,13 @@ Transform:
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 6425560216547760105}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0.8, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 4492442352427800}
m_RootOrder: 1
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!23 &1346539668293290578
MeshRenderer:
@ -243,10 +292,12 @@ MeshRenderer:
m_CastShadows: 1
m_ReceiveShadows: 1
m_DynamicOccludee: 1
m_StaticShadowCaster: 0
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 2
m_RayTraceProcedural: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
@ -271,6 +322,7 @@ MeshRenderer:
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
m_AdditionalVertexStreams: {fileID: 0}
--- !u!102 &1985504562751981867
TextMesh:
serializedVersion: 3
@ -305,45 +357,12 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: afa2d590c474413d9fc183551385ed85, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!114 &9196118806080746389
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7831918942947312790}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: a553cb17010b2403e8523b558bffbc14, type: 3}
m_Name:
m_EditorClassIdentifier:
syncDirection: 1
syncMode: 0
syncInterval: 0
target: {fileID: 7831918942946891958}
syncPosition: 0
syncRotation: 1
syncScale: 0
interpolatePosition: 0
interpolateRotation: 1
interpolateScale: 0
coordinateSpace: 0
sendIntervalMultiplier: 3
timelineOffset: 1
showGizmos: 0
showOverlay: 0
overlayColor: {r: 0, g: 0, b: 0, a: 0.5}
onlySyncOnChange: 1
compressRotation: 1
bufferResetMultiplier: 5
positionSensitivity: 0.01
rotationSensitivity: 0.01
scaleSensitivity: 0.01
--- !u!1001 &7831918942947279416
PrefabInstance:
m_ObjectHideFlags: 0
serializedVersion: 2
m_Modification:
serializedVersion: 3
m_TransformParent: {fileID: 4492442352427800}
m_Modifications:
- target: {fileID: 100010, guid: 38b49695fc0a4418bbc350f2366660c5, type: 3}
@ -400,6 +419,21 @@ PrefabInstance:
objectReference: {fileID: 2100000, guid: 2e67e42170aa64aa9a33424f8045ac89, type: 2}
m_RemovedComponents:
- {fileID: 9500000, guid: 38b49695fc0a4418bbc350f2366660c5, type: 3}
m_RemovedGameObjects: []
m_AddedGameObjects:
- targetCorrespondingSourceObject: {fileID: 400014, guid: 38b49695fc0a4418bbc350f2366660c5,
type: 3}
insertIndex: -1
addedObject: {fileID: 5718089106632469514}
m_AddedComponents:
- targetCorrespondingSourceObject: {fileID: 100014, guid: 38b49695fc0a4418bbc350f2366660c5,
type: 3}
insertIndex: -1
addedObject: {fileID: 9196118806080746389}
- targetCorrespondingSourceObject: {fileID: 100014, guid: 38b49695fc0a4418bbc350f2366660c5,
type: 3}
insertIndex: -1
addedObject: {fileID: 5018727074361621677}
m_SourcePrefab: {fileID: 100100000, guid: 38b49695fc0a4418bbc350f2366660c5, type: 3}
--- !u!4 &7831918942946891954 stripped
Transform:
@ -407,15 +441,85 @@ Transform:
type: 3}
m_PrefabInstance: {fileID: 7831918942947279416}
m_PrefabAsset: {fileID: 0}
--- !u!1 &7831918942947312790 stripped
GameObject:
m_CorrespondingSourceObject: {fileID: 100014, guid: 38b49695fc0a4418bbc350f2366660c5,
type: 3}
m_PrefabInstance: {fileID: 7831918942947279416}
m_PrefabAsset: {fileID: 0}
--- !u!4 &7831918942946891958 stripped
Transform:
m_CorrespondingSourceObject: {fileID: 400014, guid: 38b49695fc0a4418bbc350f2366660c5,
type: 3}
m_PrefabInstance: {fileID: 7831918942947279416}
m_PrefabAsset: {fileID: 0}
--- !u!1 &7831918942947312790 stripped
GameObject:
m_CorrespondingSourceObject: {fileID: 100014, guid: 38b49695fc0a4418bbc350f2366660c5,
type: 3}
m_PrefabInstance: {fileID: 7831918942947279416}
m_PrefabAsset: {fileID: 0}
--- !u!114 &9196118806080746389
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7831918942947312790}
m_Enabled: 0
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: a553cb17010b2403e8523b558bffbc14, type: 3}
m_Name:
m_EditorClassIdentifier:
syncDirection: 1
syncMode: 0
syncInterval: 0
target: {fileID: 7831918942946891958}
syncPosition: 0
syncRotation: 1
syncScale: 0
onlySyncOnChange: 1
compressRotation: 1
interpolatePosition: 0
interpolateRotation: 1
interpolateScale: 0
coordinateSpace: 0
sendIntervalMultiplier: 3
timelineOffset: 1
showGizmos: 0
showOverlay: 0
overlayColor: {r: 0, g: 0, b: 0, a: 0.5}
bufferResetMultiplier: 5
positionSensitivity: 0.01
rotationSensitivity: 0.01
scaleSensitivity: 0.01
--- !u!114 &5018727074361621677
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 7831918942947312790}
m_Enabled: 0
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fb1619f96225342ac97095bd7e87d3e6, type: 3}
m_Name:
m_EditorClassIdentifier:
syncDirection: 0
syncMode: 0
syncInterval: 0
target: {fileID: 7831918942946891958}
syncPosition: 1
syncRotation: 1
syncScale: 0
onlySyncOnChange: 1
compressRotation: 1
interpolatePosition: 1
interpolateRotation: 1
interpolateScale: 1
coordinateSpace: 0
sendIntervalMultiplier: 1
timelineOffset: 0
showGizmos: 0
showOverlay: 0
overlayColor: {r: 0, g: 0, b: 0, a: 0.5}
fullSendIntervalMultiplier: 30
deltaSendIntervalMultiplier: 30
rotationSensitivity: 0.01
positionPrecision: 0.01
scalePrecision: 0.01
syncSettings: 0

View File

@ -98,13 +98,14 @@ LightmapSettings:
m_TrainingDataDestination: TrainingData
m_LightProbeSampleCountMultiplier: 4
m_LightingDataAsset: {fileID: 0}
m_LightingSettings: {fileID: 4890085278179872738, guid: 1cb229a9b0b434acf9cb6b263057a2a0, type: 2}
m_LightingSettings: {fileID: 4890085278179872738, guid: 1cb229a9b0b434acf9cb6b263057a2a0,
type: 2}
--- !u!196 &4
NavMeshSettings:
serializedVersion: 2
m_ObjectHideFlags: 0
m_BuildSettings:
serializedVersion: 2
serializedVersion: 3
agentTypeID: 0
agentRadius: 0.5
agentHeight: 2
@ -117,7 +118,7 @@ NavMeshSettings:
cellSize: 0.16666667
manualTileSize: 0
tileSize: 256
accuratePlacement: 0
buildHeightMesh: 0
maxJobWorkers: 0
preserveTilesOutsideBounds: 0
debug:
@ -155,9 +156,17 @@ Camera:
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_FocalLength: 50
m_NormalizedViewPortRect:
serializedVersion: 2
x: 0
@ -191,13 +200,13 @@ Transform:
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 88936773}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0.92387956, z: -0.38268343, w: 0}
m_LocalPosition: {x: 0, y: 6.5, z: 8}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 45, y: 180, z: 0}
--- !u!114 &88936778
MonoBehaviour:
@ -212,7 +221,9 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier:
height: 150
offsetY: 40
maxLogCount: 50
showInEditor: 0
hotKey: 293
--- !u!1 &251893064
GameObject:
@ -238,13 +249,13 @@ Transform:
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 251893064}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 3, y: 0, z: 3}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_RootOrder: 6
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &251893066
MonoBehaviour:
@ -282,13 +293,13 @@ Transform:
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 535739935}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 3, y: 0, z: -3}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_RootOrder: 4
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &535739937
MonoBehaviour:
@ -371,9 +382,17 @@ MeshCollider:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1107091652}
m_Material: {fileID: 0}
m_IncludeLayers:
serializedVersion: 2
m_Bits: 0
m_ExcludeLayers:
serializedVersion: 2
m_Bits: 0
m_LayerOverridePriority: 0
m_IsTrigger: 0
m_ProvidesContacts: 0
m_Enabled: 1
serializedVersion: 4
serializedVersion: 5
m_Convex: 0
m_CookingOptions: 30
m_Mesh: {fileID: 10209, guid: 0000000000000000e000000000000000, type: 0}
@ -392,13 +411,13 @@ Transform:
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1107091652}
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_RootOrder: 1
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &1282001517
GameObject:
@ -413,6 +432,7 @@ GameObject:
- component: {fileID: 1282001519}
- component: {fileID: 1282001521}
- component: {fileID: 1282001522}
- component: {fileID: 1282001523}
m_Layer: 0
m_Name: NetworkManager
m_TagString: Untagged
@ -427,13 +447,13 @@ Transform:
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1282001517}
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_RootOrder: 3
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &1282001519
MonoBehaviour:
@ -463,22 +483,29 @@ MonoBehaviour:
m_EditorClassIdentifier:
dontDestroyOnLoad: 1
runInBackground: 1
autoStartServerBuild: 1
headlessStartMode: 1
editorAutoStart: 0
sendRate: 30
autoStartServerBuild: 0
autoConnectClientBuild: 0
sendRate: 120
offlineScene:
onlineScene:
transport: {fileID: 1282001521}
networkAddress: localhost
maxConnections: 100
disconnectInactiveConnections: 0
disconnectInactiveTimeout: 60
authenticator: {fileID: 0}
playerPrefab: {fileID: 1916082411674582, guid: 6f43bf5488a7443d19ab2a83c6b91f35, type: 3}
playerPrefab: {fileID: 1916082411674582, guid: 6f43bf5488a7443d19ab2a83c6b91f35,
type: 3}
autoCreatePlayer: 1
playerSpawnMethod: 1
spawnPrefabs:
- {fileID: 5890560936853567077, guid: b7dd46dbf38c643f09e206f9fa4be008, type: 3}
exceptionsDisconnect: 1
snapshotSettings:
bufferTimeMultiplier: 2
bufferLimit: 32
catchupNegativeThreshold: -1
catchupPositiveThreshold: 1
catchupSpeed: 0.019999999552965164
@ -487,6 +514,8 @@ MonoBehaviour:
dynamicAdjustment: 1
dynamicAdjustmentTolerance: 1
deliveryTimeEmaDuration: 2
evaluationMethod: 0
evaluationInterval: 3
timeInterpolationGui: 1
--- !u!114 &1282001521
MonoBehaviour:
@ -500,7 +529,7 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 6b0fecffa3f624585964b0d0eb21b18e, type: 3}
m_Name:
m_EditorClassIdentifier:
Port: 7777
port: 7777
DualMode: 1
NoDelay: 1
Interval: 10
@ -512,8 +541,8 @@ MonoBehaviour:
SendWindowSize: 4096
MaxRetransmit: 40
MaximizeSocketBuffers: 1
ReliableMaxMessageSize: 298449
UnreliableMaxMessageSize: 1199
ReliableMaxMessageSize: 297433
UnreliableMaxMessageSize: 1194
debugLog: 0
statisticsGUI: 0
statisticsLog: 0
@ -533,6 +562,18 @@ MonoBehaviour:
padding: 2
width: 180
height: 25
--- !u!114 &1282001523
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1282001517}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: bc5b42cbfc38b1c4d86fd25905b19e29, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!1 &1458789072
GameObject:
m_ObjectHideFlags: 0
@ -557,13 +598,13 @@ Transform:
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1458789072}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: -3, y: 0, z: 3}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_RootOrder: 7
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &1458789074
MonoBehaviour:
@ -601,13 +642,13 @@ Transform:
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1501912662}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: -3, y: 0, z: -3}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_RootOrder: 5
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &1501912664
MonoBehaviour:
@ -707,11 +748,23 @@ Transform:
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2054208274}
serializedVersion: 2
m_LocalRotation: {x: 0.10938167, y: 0.8754261, z: -0.40821788, w: 0.23456976}
m_LocalPosition: {x: 0, y: 10, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_RootOrder: 2
m_LocalEulerAnglesHint: {x: 50, y: 150, z: 0}
--- !u!1660057539 &9223372036854775807
SceneRoots:
m_ObjectHideFlags: 0
m_Roots:
- {fileID: 88936777}
- {fileID: 1107091656}
- {fileID: 2054208276}
- {fileID: 1282001518}
- {fileID: 535739936}
- {fileID: 1501912663}
- {fileID: 251893065}
- {fileID: 1458789073}

8
Assets/ParrelSync.meta Executable file
View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: bd07209623118fc46a32850135be3f32
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 654be33bd44a76a4c8c180d1da6ad066
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,31 @@
// This should be editor only
#if UNITY_EDITOR
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace ParrelSync.Example
{
public class CustomArgumentExample : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
// Is this editor instance running a clone project?
if (ClonesManager.IsClone())
{
Debug.Log("This is a clone project.");
//Argument can be set from the clones manager window.
string customArgument = ClonesManager.GetArgument();
Debug.Log("The custom argument of this clone project is: " + customArgument);
// Do what ever you need with the argument string.
}
else
{
Debug.Log("This is the original project.");
}
}
}
}
#endif

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 346d302ecc25a9a41b48b857ce51d873
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

22
Assets/ParrelSync/LICENSE.md Executable file
View File

@ -0,0 +1,22 @@
MIT License
Copyright (c) 2018 Greg M
Copyright (c) 2020 Ian and Contributors
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.

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: f4b327eab8d866e4087e166da8cafc09
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 8f5fec620d3bc9546a41a5b67cb9f8b6
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a31ea7d0315594440839cdb0db6bc411
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 8b14e706b1e7cb044b23837e8a70cad9
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,22 @@
using UnityEditor;
namespace ParrelSync
{
[InitializeOnLoad]
public class EditorQuit
{
/// <summary>
/// Is editor being closed
/// </summary>
static public bool IsQuiting { get; private set; }
static void Quit()
{
IsQuiting = true;
}
static EditorQuit()
{
IsQuiting = false;
EditorApplication.quitting += Quit;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bf2888ff90706904abc2d851c3e59e00
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,34 @@
using UnityEditor;
using UnityEngine;
namespace ParrelSync
{
/// <summary>
/// For preventing assets being modified from the clone instance.
/// </summary>
public class ParrelSyncAssetModificationProcessor : UnityEditor.AssetModificationProcessor
{
public static string[] OnWillSaveAssets(string[] paths)
{
if (ClonesManager.IsClone() && Preferences.AssetModPref.Value)
{
if (paths != null && paths.Length > 0 && !EditorQuit.IsQuiting)
{
EditorUtility.DisplayDialog(
ClonesManager.ProjectName + ": Asset modifications saving detected and blocked",
"Asset modifications saving are blocked in the clone instance. \n\n" +
"This is a clone of the original project. \n" +
"Making changes to asset files via the clone editor is not recommended. \n" +
"Please use the original editor window if you want to make changes to the project files.",
"ok"
);
foreach (var path in paths)
{
Debug.Log("Attempting to save " + path + " are blocked.");
}
}
return new string[0] { };
}
return paths;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 755e570bd21b39440a923056e60f1450
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

Some files were not shown because too many files have changed in this diff Show More