diff --git a/Assets/Mirage.Profiler.meta b/Assets/Mirage.Profiler.meta new file mode 100644 index 000000000..146d54dd0 --- /dev/null +++ b/Assets/Mirage.Profiler.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: faf041ce44aaa4df69ab8e633c2b21ec +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirage.Profiler/CHANGELOG.md b/Assets/Mirage.Profiler/CHANGELOG.md new file mode 100755 index 000000000..588a1ffa6 --- /dev/null +++ b/Assets/Mirage.Profiler/CHANGELOG.md @@ -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)) diff --git a/Assets/Mirage.Profiler/CHANGELOG.md.meta b/Assets/Mirage.Profiler/CHANGELOG.md.meta new file mode 100755 index 000000000..27b2aef97 --- /dev/null +++ b/Assets/Mirage.Profiler/CHANGELOG.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 1b2c5211880da6c4f83e37f1222441e1 +TextScriptImporter: + externalObjects: {} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirage.Profiler/Editor.meta b/Assets/Mirage.Profiler/Editor.meta new file mode 100755 index 000000000..1bbf5195f --- /dev/null +++ b/Assets/Mirage.Profiler/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e9eb58b4bf960c1439443c3160ce8371 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirage.Profiler/Editor/AssemblyInfo.cs b/Assets/Mirage.Profiler/Editor/AssemblyInfo.cs new file mode 100755 index 000000000..b9e3b5066 --- /dev/null +++ b/Assets/Mirage.Profiler/Editor/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using System.Reflection; + +[assembly: AssemblyVersion("1.1.0.11")] diff --git a/Assets/Mirage.Profiler/Editor/AssemblyInfo.cs.meta b/Assets/Mirage.Profiler/Editor/AssemblyInfo.cs.meta new file mode 100755 index 000000000..c3f13ce2d --- /dev/null +++ b/Assets/Mirage.Profiler/Editor/AssemblyInfo.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6efabd9b23eecd0459622d955c6a2b4e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirage.Profiler/Editor/ICountRecorderProvider.cs b/Assets/Mirage.Profiler/Editor/ICountRecorderProvider.cs new file mode 100755 index 000000000..6ca8f3f18 --- /dev/null +++ b/Assets/Mirage.Profiler/Editor/ICountRecorderProvider.cs @@ -0,0 +1,7 @@ +namespace Mirage.NetworkProfiler.ModuleGUI +{ + internal interface ICountRecorderProvider + { + CountRecorder GetCountRecorder(); + } +} diff --git a/Assets/Mirage.Profiler/Editor/ICountRecorderProvider.cs.meta b/Assets/Mirage.Profiler/Editor/ICountRecorderProvider.cs.meta new file mode 100755 index 000000000..2e368a43b --- /dev/null +++ b/Assets/Mirage.Profiler/Editor/ICountRecorderProvider.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e7ff307d04db0b64690d8c9692e40d8c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirage.Profiler/Editor/Messages.meta b/Assets/Mirage.Profiler/Editor/Messages.meta new file mode 100755 index 000000000..7e63c6122 --- /dev/null +++ b/Assets/Mirage.Profiler/Editor/Messages.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 667ec5fd9bee70844a2b458410782b0a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirage.Profiler/Editor/Messages/Columns.cs b/Assets/Mirage.Profiler/Editor/Messages/Columns.cs new file mode 100755 index 000000000..3703a1ce0 --- /dev/null +++ b/Assets/Mirage.Profiler/Editor/Messages/Columns.cs @@ -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 + { + 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 GetEnumerator() => ((IEnumerable)_columns).GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => _columns.GetEnumerator(); + } +} diff --git a/Assets/Mirage.Profiler/Editor/Messages/Columns.cs.meta b/Assets/Mirage.Profiler/Editor/Messages/Columns.cs.meta new file mode 100755 index 000000000..c0c3244fb --- /dev/null +++ b/Assets/Mirage.Profiler/Editor/Messages/Columns.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 36dff191a3209c444844bbdc74f60cd4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirage.Profiler/Editor/Messages/CounterNames.cs b/Assets/Mirage.Profiler/Editor/Messages/CounterNames.cs new file mode 100755 index 000000000..3784aaac3 --- /dev/null +++ b/Assets/Mirage.Profiler/Editor/Messages/CounterNames.cs @@ -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; + } + } +} diff --git a/Assets/Mirage.Profiler/Editor/Messages/CounterNames.cs.meta b/Assets/Mirage.Profiler/Editor/Messages/CounterNames.cs.meta new file mode 100755 index 000000000..fa8df0bef --- /dev/null +++ b/Assets/Mirage.Profiler/Editor/Messages/CounterNames.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2422d6a68fe533e4c9da13ae30767fcf +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirage.Profiler/Editor/Messages/DrawnMessage.cs b/Assets/Mirage.Profiler/Editor/Messages/DrawnMessage.cs new file mode 100755 index 000000000..ed8f12c01 --- /dev/null +++ b/Assets/Mirage.Profiler/Editor/Messages/DrawnMessage.cs @@ -0,0 +1,10 @@ +using Mirage.NetworkProfiler.ModuleGUI.UITable; + +namespace Mirage.NetworkProfiler.ModuleGUI.Messages +{ + internal class DrawnMessage + { + public MessageInfo Info; + public Row Row; + } +} diff --git a/Assets/Mirage.Profiler/Editor/Messages/DrawnMessage.cs.meta b/Assets/Mirage.Profiler/Editor/Messages/DrawnMessage.cs.meta new file mode 100755 index 000000000..038b7987f --- /dev/null +++ b/Assets/Mirage.Profiler/Editor/Messages/DrawnMessage.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: df1ae3a59995b1a43b6b76e16d2b8f3a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirage.Profiler/Editor/Messages/Group.cs b/Assets/Mirage.Profiler/Editor/Messages/Group.cs new file mode 100755 index 000000000..bd3319390 --- /dev/null +++ b/Assets/Mirage.Profiler/Editor/Messages/Group.cs @@ -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 Rows = new List(); + public readonly string Name; + public readonly List Messages = new List(); + + 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(); + } + + + /// Messages to add to table + /// list to add rows to once created, Can be null + 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; + } + } +} diff --git a/Assets/Mirage.Profiler/Editor/Messages/Group.cs.meta b/Assets/Mirage.Profiler/Editor/Messages/Group.cs.meta new file mode 100755 index 000000000..67f17c25d --- /dev/null +++ b/Assets/Mirage.Profiler/Editor/Messages/Group.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0d73a0b136989084c8caed7ae1349219 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirage.Profiler/Editor/Messages/GroupSorter.cs b/Assets/Mirage.Profiler/Editor/Messages/GroupSorter.cs new file mode 100755 index 000000000..97eb3081c --- /dev/null +++ b/Assets/Mirage.Profiler/Editor/Messages/GroupSorter.cs @@ -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 _grouped; + private readonly Func _sortGroupFunc; + private readonly Func _sortMessageFunc; + + private readonly SortMode _sortMode; + + public GroupSorter(Dictionary 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(_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(Group x, Group y, Func func) where T : IComparable + { + var xValue = func.Invoke(x); + var yValue = func.Invoke(y); + return xValue.CompareTo(yValue); + } + public static int Compare(MessageInfo x, MessageInfo y, Func func) where T : IComparable + { + 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); + } + } +} diff --git a/Assets/Mirage.Profiler/Editor/Messages/GroupSorter.cs.meta b/Assets/Mirage.Profiler/Editor/Messages/GroupSorter.cs.meta new file mode 100755 index 000000000..96a036206 --- /dev/null +++ b/Assets/Mirage.Profiler/Editor/Messages/GroupSorter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b13bef8f87b23684bae6fc2453c090b0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirage.Profiler/Editor/Messages/MessageView.cs b/Assets/Mirage.Profiler/Editor/Messages/MessageView.cs new file mode 100755 index 000000000..e53fcc42d --- /dev/null +++ b/Assets/Mirage.Profiler/Editor/Messages/MessageView.cs @@ -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; + + /// + /// cache list used to gather messages + /// + private readonly List _messages = new List(); + private readonly Dictionary _grouped = new Dictionary(); + + public event Action OnGroupExpanded; + + public MessageView(Columns columns, TableSorter sorter, VisualElement parent) + { + _columns = columns; + _table = new Table(columns, sorter); + parent.Add(_table.VisualElement); + } + + public void Draw(IEnumerable 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 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 expanded) + { + foreach (var group in _grouped.Values) + { + if (expanded.Contains(group.Name)) + { + group.Expand(true); + } + } + } + } +} diff --git a/Assets/Mirage.Profiler/Editor/Messages/MessageView.cs.meta b/Assets/Mirage.Profiler/Editor/Messages/MessageView.cs.meta new file mode 100755 index 000000000..62f99481c --- /dev/null +++ b/Assets/Mirage.Profiler/Editor/Messages/MessageView.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6f417eeb8a30cca4eb1a9301a73dbf10 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirage.Profiler/Editor/Messages/MessageViewController.cs b/Assets/Mirage.Profiler/Editor/Messages/MessageViewController.cs new file mode 100755 index 000000000..acef0ffae --- /dev/null +++ b/Assets/Mirage.Profiler/Editor/Messages/MessageViewController.cs @@ -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.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 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 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 GenerateDebugMessages() + { + var messages = new List(); + 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; + } + } +} diff --git a/Assets/Mirage.Profiler/Editor/Messages/MessageViewController.cs.meta b/Assets/Mirage.Profiler/Editor/Messages/MessageViewController.cs.meta new file mode 100755 index 000000000..31bcd88c6 --- /dev/null +++ b/Assets/Mirage.Profiler/Editor/Messages/MessageViewController.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b210a69331a49414ca5f6436163f61d5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirage.Profiler/Editor/Messages/TableSorter.cs b/Assets/Mirage.Profiler/Editor/Messages/TableSorter.cs new file mode 100755 index 000000000..db0caccc1 --- /dev/null +++ b/Assets/Mirage.Profiler/Editor/Messages/TableSorter.cs @@ -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); + } + } +} diff --git a/Assets/Mirage.Profiler/Editor/Messages/TableSorter.cs.meta b/Assets/Mirage.Profiler/Editor/Messages/TableSorter.cs.meta new file mode 100755 index 000000000..da160ebfe --- /dev/null +++ b/Assets/Mirage.Profiler/Editor/Messages/TableSorter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 658b759dbe63e9c44a14d785a15c862e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirage.Profiler/Editor/Mirage.Profiler.Editor.asmdef b/Assets/Mirage.Profiler/Editor/Mirage.Profiler.Editor.asmdef new file mode 100755 index 000000000..dae99d112 --- /dev/null +++ b/Assets/Mirage.Profiler/Editor/Mirage.Profiler.Editor.asmdef @@ -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 +} \ No newline at end of file diff --git a/Assets/Mirage.Profiler/Editor/Mirage.Profiler.Editor.asmdef.meta b/Assets/Mirage.Profiler/Editor/Mirage.Profiler.Editor.asmdef.meta new file mode 100755 index 000000000..ce9e1e78f --- /dev/null +++ b/Assets/Mirage.Profiler/Editor/Mirage.Profiler.Editor.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: ff97fe2e55c0fd44c91fb1242f9bff3e +AssemblyDefinitionImporter: + externalObjects: {} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirage.Profiler/Editor/ModuleNames.cs b/Assets/Mirage.Profiler/Editor/ModuleNames.cs new file mode 100755 index 000000000..7ec76c13e --- /dev/null +++ b/Assets/Mirage.Profiler/Editor/ModuleNames.cs @@ -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"; + } +} diff --git a/Assets/Mirage.Profiler/Editor/ModuleNames.cs.meta b/Assets/Mirage.Profiler/Editor/ModuleNames.cs.meta new file mode 100755 index 000000000..162642847 --- /dev/null +++ b/Assets/Mirage.Profiler/Editor/ModuleNames.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1a65943c66af2cb4cb0433f5f3bd73b7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirage.Profiler/Editor/ReceivedModule.cs b/Assets/Mirage.Profiler/Editor/ReceivedModule.cs new file mode 100755 index 000000000..33b6f862a --- /dev/null +++ b/Assets/Mirage.Profiler/Editor/ReceivedModule.cs @@ -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; + } + } +} diff --git a/Assets/Mirage.Profiler/Editor/ReceivedModule.cs.meta b/Assets/Mirage.Profiler/Editor/ReceivedModule.cs.meta new file mode 100755 index 000000000..3292d94fb --- /dev/null +++ b/Assets/Mirage.Profiler/Editor/ReceivedModule.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 677abdcefedd3144b9784f7a09a647da +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirage.Profiler/Editor/SavedData.cs b/Assets/Mirage.Profiler/Editor/SavedData.cs new file mode 100755 index 000000000..bb1217c15 --- /dev/null +++ b/Assets/Mirage.Profiler/Editor/SavedData.cs @@ -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 + { + /// + /// Message from each frame so they can survive domain reload + /// + public Frames Frames; + + /// + /// Active sort header + /// + public string SortHeader; + + public SortMode SortMode; + + /// + /// Which Message groups are expanded + /// + public List Expanded; + + public bool GroupMessages = true; + + public SavedData() + { + Frames = new Frames(); + + Expanded = new List(); + } + + 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(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); + } + } +} diff --git a/Assets/Mirage.Profiler/Editor/SavedData.cs.meta b/Assets/Mirage.Profiler/Editor/SavedData.cs.meta new file mode 100755 index 000000000..99d229831 --- /dev/null +++ b/Assets/Mirage.Profiler/Editor/SavedData.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6e3608e18fbb66346bc8d15f628ad4f1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirage.Profiler/Editor/SentModule.cs b/Assets/Mirage.Profiler/Editor/SentModule.cs new file mode 100755 index 000000000..4cf2203ef --- /dev/null +++ b/Assets/Mirage.Profiler/Editor/SentModule.cs @@ -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; + } + } +} diff --git a/Assets/Mirage.Profiler/Editor/SentModule.cs.meta b/Assets/Mirage.Profiler/Editor/SentModule.cs.meta new file mode 100755 index 000000000..c8bb173ed --- /dev/null +++ b/Assets/Mirage.Profiler/Editor/SentModule.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5447799e8b54b3044b4fac717c450b46 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirage.Profiler/Editor/ServerModule.cs b/Assets/Mirage.Profiler/Editor/ServerModule.cs new file mode 100755 index 000000000..d8e29d7cf --- /dev/null +++ b/Assets/Mirage.Profiler/Editor/ServerModule.cs @@ -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); + } + } +} diff --git a/Assets/Mirage.Profiler/Editor/ServerModule.cs.meta b/Assets/Mirage.Profiler/Editor/ServerModule.cs.meta new file mode 100755 index 000000000..0fca973ce --- /dev/null +++ b/Assets/Mirage.Profiler/Editor/ServerModule.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b8113ff8f56cc644bb23d2da87f23eab +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirage.Profiler/Editor/Table.meta b/Assets/Mirage.Profiler/Editor/Table.meta new file mode 100755 index 000000000..071fe66d7 --- /dev/null +++ b/Assets/Mirage.Profiler/Editor/Table.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: cc264da8a8fc533418223d921f2f00c8 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirage.Profiler/Editor/Table/ColumnInfo.cs b/Assets/Mirage.Profiler/Editor/Table/ColumnInfo.cs new file mode 100755 index 000000000..40841949a --- /dev/null +++ b/Assets/Mirage.Profiler/Editor/Table/ColumnInfo.cs @@ -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 SortGroup { get; private set; } + public Func SortMessages { get; private set; } + + public Func TextGetter { get; private set; } + + public bool HasToolTip { get; private set; } + public Func ToolTipGetter { get; private set; } + + public ColumnInfo(string header, int width, Func textGetter) + { + Header = header; + Width = width; + TextGetter = textGetter; + } + + /// + /// Enables sorting for column. If sort functions are null they will use default sort from + /// + /// + /// + /// + public void AddSort(Func sortGroup, Func sortMessages) + { + AllowSort = true; + SortGroup = sortGroup ?? GroupSorter.DefaultGroupSort; + SortMessages = sortMessages ?? GroupSorter.DefaultMessageSort; + } + + /// + /// Enables sorting for column. If sort functions are null they will use default sort from + /// Uses member getting to sort via that member + /// + /// + /// + /// + public void AddSort(Func groupGetter, Func messageGetter) where T : IComparable + { + Func sortGroup = groupGetter != null + ? (x, y) => GroupSorter.Compare(x, y, groupGetter) + : null; + + Func sortMessages = messageGetter != null + ? (x, y) => GroupSorter.Compare(x, y, messageGetter) + : null; + + AddSort(sortGroup, sortMessages); + } + + public void AddToolTip(Func getter) + { + HasToolTip = true; + ToolTipGetter = getter; + } + } +} diff --git a/Assets/Mirage.Profiler/Editor/Table/ColumnInfo.cs.meta b/Assets/Mirage.Profiler/Editor/Table/ColumnInfo.cs.meta new file mode 100755 index 000000000..9a09d715c --- /dev/null +++ b/Assets/Mirage.Profiler/Editor/Table/ColumnInfo.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7b017b2728f784842bc8f62460ab9fbe +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirage.Profiler/Editor/Table/EmptyRow.cs b/Assets/Mirage.Profiler/Editor/Table/EmptyRow.cs new file mode 100755 index 000000000..b5b492a10 --- /dev/null +++ b/Assets/Mirage.Profiler/Editor/Table/EmptyRow.cs @@ -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 GetChildren() + { + return Enumerable.Empty(); + } + } +} diff --git a/Assets/Mirage.Profiler/Editor/Table/EmptyRow.cs.meta b/Assets/Mirage.Profiler/Editor/Table/EmptyRow.cs.meta new file mode 100755 index 000000000..3eeac6db5 --- /dev/null +++ b/Assets/Mirage.Profiler/Editor/Table/EmptyRow.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8129d33c5f799524fa10e84110131e69 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirage.Profiler/Editor/Table/ITableSorter.cs b/Assets/Mirage.Profiler/Editor/Table/ITableSorter.cs new file mode 100755 index 000000000..a549ccb1f --- /dev/null +++ b/Assets/Mirage.Profiler/Editor/Table/ITableSorter.cs @@ -0,0 +1,7 @@ +namespace Mirage.NetworkProfiler.ModuleGUI.UITable +{ + internal interface ITableSorter + { + void Sort(Table table, SortHeader header); + } +} diff --git a/Assets/Mirage.Profiler/Editor/Table/ITableSorter.cs.meta b/Assets/Mirage.Profiler/Editor/Table/ITableSorter.cs.meta new file mode 100755 index 000000000..8cf369ff3 --- /dev/null +++ b/Assets/Mirage.Profiler/Editor/Table/ITableSorter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 05c177a4f382419409a2f55de20e046c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirage.Profiler/Editor/Table/LabelRow.cs b/Assets/Mirage.Profiler/Editor/Table/LabelRow.cs new file mode 100755 index 000000000..429128aac --- /dev/null +++ b/Assets/Mirage.Profiler/Editor/Table/LabelRow.cs @@ -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 _elements = new Dictionary(); + + 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 GetChildren() + { + return _elements.Values; + } + } +} diff --git a/Assets/Mirage.Profiler/Editor/Table/LabelRow.cs.meta b/Assets/Mirage.Profiler/Editor/Table/LabelRow.cs.meta new file mode 100755 index 000000000..7fb113a2f --- /dev/null +++ b/Assets/Mirage.Profiler/Editor/Table/LabelRow.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e463bd968d1bb0e4cbae5efd09ed24fc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirage.Profiler/Editor/Table/Row.cs b/Assets/Mirage.Profiler/Editor/Table/Row.cs new file mode 100755 index 000000000..74f7ba7fa --- /dev/null +++ b/Assets/Mirage.Profiler/Editor/Table/Row.cs @@ -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 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; + } + } +} diff --git a/Assets/Mirage.Profiler/Editor/Table/Row.cs.meta b/Assets/Mirage.Profiler/Editor/Table/Row.cs.meta new file mode 100755 index 000000000..b3e23121a --- /dev/null +++ b/Assets/Mirage.Profiler/Editor/Table/Row.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ac8d50f233b70db42a649ca6b7d69010 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirage.Profiler/Editor/Table/SortHeader.cs b/Assets/Mirage.Profiler/Editor/Table/SortHeader.cs new file mode 100755 index 000000000..0e95c641e --- /dev/null +++ b/Assets/Mirage.Profiler/Editor/Table/SortHeader.cs @@ -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; + } + } + } +} diff --git a/Assets/Mirage.Profiler/Editor/Table/SortHeader.cs.meta b/Assets/Mirage.Profiler/Editor/Table/SortHeader.cs.meta new file mode 100755 index 000000000..67764880a --- /dev/null +++ b/Assets/Mirage.Profiler/Editor/Table/SortHeader.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ab266974aca81aa4abbaefba1e934073 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirage.Profiler/Editor/Table/SortHeaderRow.cs b/Assets/Mirage.Profiler/Editor/Table/SortHeaderRow.cs new file mode 100755 index 000000000..4e0d99f00 --- /dev/null +++ b/Assets/Mirage.Profiler/Editor/Table/SortHeaderRow.cs @@ -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; + } + + /// + /// Update names of header based on sort + /// + /// + public void SetSortHeader(SortHeader sortHeader) + { + // not null or current + if (_currentSort != null && _currentSort != sortHeader) + { + _currentSort.SortMode = SortMode.None; + _currentSort.UpdateName(); + } + + _currentSort = sortHeader; + _currentSort.UpdateName(); + } + + /// + /// Updates names and applies sort to table + /// + /// + public void ApplySort(SortHeader sortHeader) + { + SetSortHeader(sortHeader); + + _sorter.Sort(Table, sortHeader); + } + } +} diff --git a/Assets/Mirage.Profiler/Editor/Table/SortHeaderRow.cs.meta b/Assets/Mirage.Profiler/Editor/Table/SortHeaderRow.cs.meta new file mode 100755 index 000000000..1283a0479 --- /dev/null +++ b/Assets/Mirage.Profiler/Editor/Table/SortHeaderRow.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4b4301d9972553c4ab486e9b8915f784 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirage.Profiler/Editor/Table/SortMode.cs b/Assets/Mirage.Profiler/Editor/Table/SortMode.cs new file mode 100755 index 000000000..2c768e524 --- /dev/null +++ b/Assets/Mirage.Profiler/Editor/Table/SortMode.cs @@ -0,0 +1,9 @@ +namespace Mirage.NetworkProfiler.ModuleGUI.UITable +{ + internal enum SortMode + { + None, + Accending, + Descending, + } +} diff --git a/Assets/Mirage.Profiler/Editor/Table/SortMode.cs.meta b/Assets/Mirage.Profiler/Editor/Table/SortMode.cs.meta new file mode 100755 index 000000000..92b1e7fe7 --- /dev/null +++ b/Assets/Mirage.Profiler/Editor/Table/SortMode.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f0d80f37bc0b1a44cbad7667bba04c79 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirage.Profiler/Editor/Table/Table.cs b/Assets/Mirage.Profiler/Editor/Table/Table.cs new file mode 100755 index 000000000..fdd6a47af --- /dev/null +++ b/Assets/Mirage.Profiler/Editor/Table/Table.cs @@ -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 Rows = new List(); + public readonly IReadOnlyList HeaderInfo; + + public bool ContainsEmptyRows { get; private set; } + + public Table(IEnumerable columns, ITableSorter sorter) + { + // create readonly list from given Enumerable + HeaderInfo = new List(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; + } + } + + /// + /// Removes all rows expect header + /// + public void Clear() + { + ScrollView.Clear(); + Rows.Clear(); + Rows.Add(Header); + ContainsEmptyRows = false; + } + + /// + /// Updates names of sort header + /// + public void SetSortHeader(ColumnInfo info, SortMode mode) + { + var sortBy = (SortHeader)Header.GetLabel(info); + sortBy.SortMode = mode; + Header.SetSortHeader(sortBy); + } + } +} diff --git a/Assets/Mirage.Profiler/Editor/Table/Table.cs.meta b/Assets/Mirage.Profiler/Editor/Table/Table.cs.meta new file mode 100755 index 000000000..095fe62c5 --- /dev/null +++ b/Assets/Mirage.Profiler/Editor/Table/Table.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2a7346de93910944c965b68a39d93082 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirage.Profiler/LICENSE b/Assets/Mirage.Profiler/LICENSE new file mode 100755 index 000000000..8a221041c --- /dev/null +++ b/Assets/Mirage.Profiler/LICENSE @@ -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. \ No newline at end of file diff --git a/Assets/Mirage.Profiler/LICENSE.meta b/Assets/Mirage.Profiler/LICENSE.meta new file mode 100755 index 000000000..89e9becc5 --- /dev/null +++ b/Assets/Mirage.Profiler/LICENSE.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: d4452ce1fed4a2641879ef852c413104 +DefaultImporter: + externalObjects: {} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirage.Profiler/Readme.txt b/Assets/Mirage.Profiler/Readme.txt new file mode 100755 index 000000000..3b74699e3 --- /dev/null +++ b/Assets/Mirage.Profiler/Readme.txt @@ -0,0 +1 @@ +See full readme here https://github.com/James-Frowen/Mirage.Profiler diff --git a/Assets/Mirage.Profiler/Readme.txt.meta b/Assets/Mirage.Profiler/Readme.txt.meta new file mode 100755 index 000000000..5868223fa --- /dev/null +++ b/Assets/Mirage.Profiler/Readme.txt.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: cc3de9d50be43ed438d71e49f728567c +TextScriptImporter: + externalObjects: {} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirage.Profiler/Runtime.meta b/Assets/Mirage.Profiler/Runtime.meta new file mode 100755 index 000000000..17a722e6f --- /dev/null +++ b/Assets/Mirage.Profiler/Runtime.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 82fa6b4931bac2f41b95862e74371e17 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirage.Profiler/Runtime/AssemblyInfo.cs b/Assets/Mirage.Profiler/Runtime/AssemblyInfo.cs new file mode 100755 index 000000000..921b02040 --- /dev/null +++ b/Assets/Mirage.Profiler/Runtime/AssemblyInfo.cs @@ -0,0 +1,6 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +[assembly: AssemblyVersion("1.1.0.11")] + +[assembly: InternalsVisibleTo("Mirage.Profiler.Editor")] diff --git a/Assets/Mirage.Profiler/Runtime/AssemblyInfo.cs.meta b/Assets/Mirage.Profiler/Runtime/AssemblyInfo.cs.meta new file mode 100755 index 000000000..caea44e17 --- /dev/null +++ b/Assets/Mirage.Profiler/Runtime/AssemblyInfo.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b0ddded0f98d71546aeae6e4c52aef54 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirage.Profiler/Runtime/CountRecorder.cs b/Assets/Mirage.Profiler/Runtime/CountRecorder.cs new file mode 100755 index 000000000..4bc727f7b --- /dev/null +++ b/Assets/Mirage.Profiler/Runtime/CountRecorder.cs @@ -0,0 +1,87 @@ +using System.Collections.Generic; +using Mirror; +using Unity.Profiling; +using UnityEngine; + +namespace Mirage.NetworkProfiler +{ + internal class CountRecorder + { + private readonly ProfilerCounter _profilerCount; + private readonly ProfilerCounter _profilerBytes; + private readonly ProfilerCounter _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 profilerCount, ProfilerCounter profilerBytes, ProfilerCounter 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); + } + } +} diff --git a/Assets/Mirage.Profiler/Runtime/CountRecorder.cs.meta b/Assets/Mirage.Profiler/Runtime/CountRecorder.cs.meta new file mode 100755 index 000000000..a2105fb93 --- /dev/null +++ b/Assets/Mirage.Profiler/Runtime/CountRecorder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 16575ee3be2c5e046af4438787d215f0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirage.Profiler/Runtime/Counters.cs b/Assets/Mirage.Profiler/Runtime/Counters.cs new file mode 100755 index 000000000..43376c592 --- /dev/null +++ b/Assets/Mirage.Profiler/Runtime/Counters.cs @@ -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 PlayerCount = new ProfilerCounter(Category, Names.PLAYER_COUNT, COUNT); + public static readonly ProfilerCounter CharCount = new ProfilerCounter(Category, Names.CHARACTER_COUNT, COUNT); + public static readonly ProfilerCounter ObjectCount = new ProfilerCounter(Category, Names.OBJECT_COUNT, COUNT); + + public static readonly ProfilerCounter SentCount = new ProfilerCounter(Category, Names.SENT_COUNT, COUNT); + public static readonly ProfilerCounter SentBytes = new ProfilerCounter(Category, Names.SENT_BYTES, BYTES); + public static readonly ProfilerCounter SentPerSecond = new ProfilerCounter(Category, Names.SENT_PER_SECOND, BYTES); + + public static readonly ProfilerCounter ReceiveCount = new ProfilerCounter(Category, Names.RECEIVED_COUNT, COUNT); + public static readonly ProfilerCounter ReceiveBytes = new ProfilerCounter(Category, Names.RECEIVED_BYTES, BYTES); + public static readonly ProfilerCounter ReceivePerSecond = new ProfilerCounter(Category, Names.RECEIVED_PER_SECOND, BYTES); + } +} diff --git a/Assets/Mirage.Profiler/Runtime/Counters.cs.meta b/Assets/Mirage.Profiler/Runtime/Counters.cs.meta new file mode 100755 index 000000000..0b15cc735 --- /dev/null +++ b/Assets/Mirage.Profiler/Runtime/Counters.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 40d8d596129601f4d90f0664cb2557c4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirage.Profiler/Runtime/Frame.cs b/Assets/Mirage.Profiler/Runtime/Frame.cs new file mode 100755 index 000000000..57ebd30da --- /dev/null +++ b/Assets/Mirage.Profiler/Runtime/Frame.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +namespace Mirage.NetworkProfiler +{ + [System.Serializable] + public class Frames : IEnumerable + { + [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 GetEnumerator() => ((IEnumerable)_frames).GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)_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 Messages = new List(); + public int Bytes; + } +} diff --git a/Assets/Mirage.Profiler/Runtime/Frame.cs.meta b/Assets/Mirage.Profiler/Runtime/Frame.cs.meta new file mode 100755 index 000000000..b9e76417c --- /dev/null +++ b/Assets/Mirage.Profiler/Runtime/Frame.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b59754fd175ff2345a6788891dddbfcf +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirage.Profiler/Runtime/MessageInfo.cs b/Assets/Mirage.Profiler/Runtime/MessageInfo.cs new file mode 100755 index 000000000..080df6fd1 --- /dev/null +++ b/Assets/Mirage.Profiler/Runtime/MessageInfo.cs @@ -0,0 +1,45 @@ +using Mirror; +using UnityEngine; + +namespace Mirage.NetworkProfiler +{ + [System.Serializable] + public class MessageInfo + { + /// + /// Order message was sent/received in frame + /// + [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); + } + } +} diff --git a/Assets/Mirage.Profiler/Runtime/MessageInfo.cs.meta b/Assets/Mirage.Profiler/Runtime/MessageInfo.cs.meta new file mode 100755 index 000000000..434947da4 --- /dev/null +++ b/Assets/Mirage.Profiler/Runtime/MessageInfo.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 668dc7c434784214794807fcfecfe185 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirage.Profiler/Runtime/Mirage.Profiler.asmdef b/Assets/Mirage.Profiler/Runtime/Mirage.Profiler.asmdef new file mode 100755 index 000000000..34407c0d2 --- /dev/null +++ b/Assets/Mirage.Profiler/Runtime/Mirage.Profiler.asmdef @@ -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 +} \ No newline at end of file diff --git a/Assets/Mirage.Profiler/Runtime/Mirage.Profiler.asmdef.meta b/Assets/Mirage.Profiler/Runtime/Mirage.Profiler.asmdef.meta new file mode 100755 index 000000000..5bb9df45f --- /dev/null +++ b/Assets/Mirage.Profiler/Runtime/Mirage.Profiler.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 6ff9f1c8e2ed4034baf80c43db7a3b6a +AssemblyDefinitionImporter: + externalObjects: {} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirage.Profiler/Runtime/Names.cs b/Assets/Mirage.Profiler/Runtime/Names.cs new file mode 100755 index 000000000..13b3384ea --- /dev/null +++ b/Assets/Mirage.Profiler/Runtime/Names.cs @@ -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"; + } +} diff --git a/Assets/Mirage.Profiler/Runtime/Names.cs.meta b/Assets/Mirage.Profiler/Runtime/Names.cs.meta new file mode 100755 index 000000000..77e0cdaf4 --- /dev/null +++ b/Assets/Mirage.Profiler/Runtime/Names.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 05fd6c7133d9b2e42a8f282465a445d8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirage.Profiler/Runtime/NetworkInfoProvider.cs b/Assets/Mirage.Profiler/Runtime/NetworkInfoProvider.cs new file mode 100644 index 000000000..a24997630 --- /dev/null +++ b/Assets/Mirage.Profiler/Runtime/NetworkInfoProvider.cs @@ -0,0 +1,105 @@ +using System.Text.RegularExpressions; +using Mirror; +using Mirror.RemoteCalls; +using UnityEngine; +using UnityEngine.Assertions; + +namespace Mirage.NetworkProfiler +{ + /// + /// Returns information about NetworkMessage + /// + 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; + } + + + /// + /// Some stuff from Mirror.Weaver + /// + 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; + } + } + } +} diff --git a/Assets/Mirage.Profiler/Runtime/NetworkInfoProvider.cs.meta b/Assets/Mirage.Profiler/Runtime/NetworkInfoProvider.cs.meta new file mode 100755 index 000000000..f931865f0 --- /dev/null +++ b/Assets/Mirage.Profiler/Runtime/NetworkInfoProvider.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8192a6dbec7e3b446bea79c4b0f172be +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirage.Profiler/Runtime/NetworkProfilerRecorder.cs b/Assets/Mirage.Profiler/Runtime/NetworkProfilerRecorder.cs new file mode 100755 index 000000000..6040fceb5 --- /dev/null +++ b/Assets/Mirage.Profiler/Runtime/NetworkProfilerRecorder.cs @@ -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 + } + + /// + /// call this every frame to sample number of players and objects + /// + 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); + } + + /// + /// call this when ProfilerDriver shows it is next frame + /// + /// + private void SampleMessages(int frame) + { + _sentCounter.EndFrame(frame); + _receivedCounter.EndFrame(frame); + AfterSample?.Invoke(frame); + } + } +} diff --git a/Assets/Mirage.Profiler/Runtime/NetworkProfilerRecorder.cs.meta b/Assets/Mirage.Profiler/Runtime/NetworkProfilerRecorder.cs.meta new file mode 100755 index 000000000..f61ba4190 --- /dev/null +++ b/Assets/Mirage.Profiler/Runtime/NetworkProfilerRecorder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bc5b42cbfc38b1c4d86fd25905b19e29 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirage.Profiler/package.json b/Assets/Mirage.Profiler/package.json new file mode 100755 index 000000000..2ad246c76 --- /dev/null +++ b/Assets/Mirage.Profiler/package.json @@ -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": [] +} diff --git a/Assets/Mirage.Profiler/package.json.meta b/Assets/Mirage.Profiler/package.json.meta new file mode 100755 index 000000000..c78114823 --- /dev/null +++ b/Assets/Mirage.Profiler/package.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 2c236d885d1e08b41870190b5e9f0d37 +TextScriptImporter: + externalObjects: {} + userData: '' + assetBundleName: '' + assetBundleVariant: '' diff --git a/Assets/Mirror/Components/NetworkTransform/HybridNetworkTransform.cs b/Assets/Mirror/Components/NetworkTransform/HybridNetworkTransform.cs new file mode 100644 index 000000000..df5ba7643 --- /dev/null +++ b/Assets/Mirror/Components/NetworkTransform/HybridNetworkTransform.cs @@ -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++; + } + } +} \ No newline at end of file diff --git a/Assets/Mirror/Components/NetworkTransform/HybridNetworkTransform.cs.meta b/Assets/Mirror/Components/NetworkTransform/HybridNetworkTransform.cs.meta new file mode 100644 index 000000000..505c762dc --- /dev/null +++ b/Assets/Mirror/Components/NetworkTransform/HybridNetworkTransform.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fb1619f96225342ac97095bd7e87d3e6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Components/NetworkTransform/SyncData.cs b/Assets/Mirror/Components/NetworkTransform/SyncData.cs new file mode 100644 index 000000000..baf855b4f --- /dev/null +++ b/Assets/Mirror/Components/NetworkTransform/SyncData.cs @@ -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); + } + } +} diff --git a/Assets/Mirror/Components/NetworkTransform/SyncData.cs.meta b/Assets/Mirror/Components/NetworkTransform/SyncData.cs.meta new file mode 100644 index 000000000..49e835be3 --- /dev/null +++ b/Assets/Mirror/Components/NetworkTransform/SyncData.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2c363936c3e954543824fa48e6534ed9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Examples/Tanks/Prefabs/Tank.prefab b/Assets/Mirror/Examples/Tanks/Prefabs/Tank.prefab index 114f12879..08bcf38bd 100644 --- a/Assets/Mirror/Examples/Tanks/Prefabs/Tank.prefab +++ b/Assets/Mirror/Examples/Tanks/Prefabs/Tank.prefab @@ -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 diff --git a/Assets/Mirror/Examples/Tanks/Scenes/MirrorTanks.unity b/Assets/Mirror/Examples/Tanks/Scenes/MirrorTanks.unity index b737fcea6..21aefd424 100644 --- a/Assets/Mirror/Examples/Tanks/Scenes/MirrorTanks.unity +++ b/Assets/Mirror/Examples/Tanks/Scenes/MirrorTanks.unity @@ -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} diff --git a/Assets/ParrelSync.meta b/Assets/ParrelSync.meta new file mode 100755 index 000000000..737c5bd88 --- /dev/null +++ b/Assets/ParrelSync.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: bd07209623118fc46a32850135be3f32 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ParrelSync/Examples.meta b/Assets/ParrelSync/Examples.meta new file mode 100755 index 000000000..9d4eb9a43 --- /dev/null +++ b/Assets/ParrelSync/Examples.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 654be33bd44a76a4c8c180d1da6ad066 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ParrelSync/Examples/CustomArgumentExample.cs b/Assets/ParrelSync/Examples/CustomArgumentExample.cs new file mode 100755 index 000000000..3d9b1a6c5 --- /dev/null +++ b/Assets/ParrelSync/Examples/CustomArgumentExample.cs @@ -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 \ No newline at end of file diff --git a/Assets/ParrelSync/Examples/CustomArgumentExample.cs.meta b/Assets/ParrelSync/Examples/CustomArgumentExample.cs.meta new file mode 100755 index 000000000..a7043c34c --- /dev/null +++ b/Assets/ParrelSync/Examples/CustomArgumentExample.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 346d302ecc25a9a41b48b857ce51d873 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ParrelSync/LICENSE.md b/Assets/ParrelSync/LICENSE.md new file mode 100755 index 000000000..5324dda00 --- /dev/null +++ b/Assets/ParrelSync/LICENSE.md @@ -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. diff --git a/Assets/ParrelSync/LICENSE.md.meta b/Assets/ParrelSync/LICENSE.md.meta new file mode 100755 index 000000000..e0f1a8b05 --- /dev/null +++ b/Assets/ParrelSync/LICENSE.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: f4b327eab8d866e4087e166da8cafc09 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ParrelSync/ParrelSync.meta b/Assets/ParrelSync/ParrelSync.meta new file mode 100755 index 000000000..890e8df38 --- /dev/null +++ b/Assets/ParrelSync/ParrelSync.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8f5fec620d3bc9546a41a5b67cb9f8b6 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ParrelSync/ParrelSync/Editor.meta b/Assets/ParrelSync/ParrelSync/Editor.meta new file mode 100755 index 000000000..56fd13122 --- /dev/null +++ b/Assets/ParrelSync/ParrelSync/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a31ea7d0315594440839cdb0db6bc411 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ParrelSync/ParrelSync/Editor/AssetModBlock.meta b/Assets/ParrelSync/ParrelSync/Editor/AssetModBlock.meta new file mode 100755 index 000000000..3bb4f7033 --- /dev/null +++ b/Assets/ParrelSync/ParrelSync/Editor/AssetModBlock.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8b14e706b1e7cb044b23837e8a70cad9 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ParrelSync/ParrelSync/Editor/AssetModBlock/EditorQuit.cs b/Assets/ParrelSync/ParrelSync/Editor/AssetModBlock/EditorQuit.cs new file mode 100755 index 000000000..dc181d19b --- /dev/null +++ b/Assets/ParrelSync/ParrelSync/Editor/AssetModBlock/EditorQuit.cs @@ -0,0 +1,22 @@ +using UnityEditor; +namespace ParrelSync +{ + [InitializeOnLoad] + public class EditorQuit + { + /// + /// Is editor being closed + /// + static public bool IsQuiting { get; private set; } + static void Quit() + { + IsQuiting = true; + } + + static EditorQuit() + { + IsQuiting = false; + EditorApplication.quitting += Quit; + } + } +} \ No newline at end of file diff --git a/Assets/ParrelSync/ParrelSync/Editor/AssetModBlock/EditorQuit.cs.meta b/Assets/ParrelSync/ParrelSync/Editor/AssetModBlock/EditorQuit.cs.meta new file mode 100755 index 000000000..2296dacf3 --- /dev/null +++ b/Assets/ParrelSync/ParrelSync/Editor/AssetModBlock/EditorQuit.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bf2888ff90706904abc2d851c3e59e00 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ParrelSync/ParrelSync/Editor/AssetModBlock/ParrelSyncAssetModificationProcessor.cs b/Assets/ParrelSync/ParrelSync/Editor/AssetModBlock/ParrelSyncAssetModificationProcessor.cs new file mode 100755 index 000000000..6587482ad --- /dev/null +++ b/Assets/ParrelSync/ParrelSync/Editor/AssetModBlock/ParrelSyncAssetModificationProcessor.cs @@ -0,0 +1,34 @@ +using UnityEditor; +using UnityEngine; +namespace ParrelSync +{ + /// + /// For preventing assets being modified from the clone instance. + /// + 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; + } + } +} \ No newline at end of file diff --git a/Assets/ParrelSync/ParrelSync/Editor/AssetModBlock/ParrelSyncAssetModificationProcessor.cs.meta b/Assets/ParrelSync/ParrelSync/Editor/AssetModBlock/ParrelSyncAssetModificationProcessor.cs.meta new file mode 100755 index 000000000..71581750f --- /dev/null +++ b/Assets/ParrelSync/ParrelSync/Editor/AssetModBlock/ParrelSyncAssetModificationProcessor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 755e570bd21b39440a923056e60f1450 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ParrelSync/ParrelSync/Editor/ClonesManager.cs b/Assets/ParrelSync/ParrelSync/Editor/ClonesManager.cs new file mode 100755 index 000000000..9fd125ac8 --- /dev/null +++ b/Assets/ParrelSync/ParrelSync/Editor/ClonesManager.cs @@ -0,0 +1,668 @@ +using System.Collections.Generic; +using System.Diagnostics; +using UnityEngine; +using UnityEditor; +using System.Linq; +using System.IO; +using Debug = UnityEngine.Debug; + +namespace ParrelSync +{ + /// + /// Contains all required methods for creating a linked clone of the Unity project. + /// + public class ClonesManager + { + /// + /// Name used for an identifying file created in the clone project directory. + /// + /// + /// (!) Do not change this after the clone was created, because then connection will be lost. + /// + public const string CloneFileName = ".clone"; + + /// + /// Suffix added to the end of the project clone name when it is created. + /// + /// + /// (!) Do not change this after the clone was created, because then connection will be lost. + /// + public const string CloneNameSuffix = "_clone"; + + public const string ProjectName = "ParrelSync"; + + /// + /// The maximum number of clones + /// + public const int MaxCloneProjectCount = 10; + + /// + /// Name of the file for storing clone's argument. + /// + public const string ArgumentFileName = ".parrelsyncarg"; + + /// + /// Default argument of the new clone + /// + public const string DefaultArgument = "client"; + + #region Managing clones + + /// + /// Creates clone from the project currently open in Unity Editor. + /// + /// + public static Project CreateCloneFromCurrent() + { + if (IsClone()) + { + Debug.LogError("This project is already a clone. Cannot clone it."); + return null; + } + + string currentProjectPath = ClonesManager.GetCurrentProjectPath(); + return ClonesManager.CreateCloneFromPath(currentProjectPath); + } + + /// + /// Creates clone of the project located at the given path. + /// + /// + /// + public static Project CreateCloneFromPath(string sourceProjectPath) + { + Project sourceProject = new Project(sourceProjectPath); + + string cloneProjectPath = null; + + //Find available clone suffix id + for (int i = 0; i < MaxCloneProjectCount; i++) + { + string originalProjectPath = ClonesManager.GetCurrentProject().projectPath; + string possibleCloneProjectPath = originalProjectPath + ClonesManager.CloneNameSuffix + "_" + i; + + if (!Directory.Exists(possibleCloneProjectPath)) + { + cloneProjectPath = possibleCloneProjectPath; + break; + } + } + + if (string.IsNullOrEmpty(cloneProjectPath)) + { + Debug.LogError("The number of cloned projects has reach its limit. Limit: " + MaxCloneProjectCount); + return null; + } + + Project cloneProject = new Project(cloneProjectPath); + + Debug.Log("Start cloning project, original project: " + sourceProject + ", clone project: " + cloneProject); + + ClonesManager.CreateProjectFolder(cloneProject); + + //Copy Folders + Debug.Log("Library copy: " + cloneProject.libraryPath); + ClonesManager.CopyDirectoryWithProgressBar(sourceProject.libraryPath, cloneProject.libraryPath, + "Cloning Project Library '" + sourceProject.name + "'. "); + Debug.Log("Packages copy: " + cloneProject.libraryPath); + ClonesManager.CopyDirectoryWithProgressBar(sourceProject.packagesPath, cloneProject.packagesPath, + "Cloning Project Packages '" + sourceProject.name + "'. "); + + + //Link Folders + ClonesManager.LinkFolders(sourceProject.assetPath, cloneProject.assetPath); + ClonesManager.LinkFolders(sourceProject.projectSettingsPath, cloneProject.projectSettingsPath); + ClonesManager.LinkFolders(sourceProject.autoBuildPath, cloneProject.autoBuildPath); + ClonesManager.LinkFolders(sourceProject.localPackages, cloneProject.localPackages); + + ClonesManager.RegisterClone(cloneProject); + + return cloneProject; + } + + /// + /// Registers a clone by placing an identifying ".clone" file in its root directory. + /// + /// + private static void RegisterClone(Project cloneProject) + { + /// Add clone identifier file. + string identifierFile = Path.Combine(cloneProject.projectPath, ClonesManager.CloneFileName); + File.Create(identifierFile).Dispose(); + + //Add argument file with default argument + string argumentFilePath = Path.Combine(cloneProject.projectPath, ClonesManager.ArgumentFileName); + File.WriteAllText(argumentFilePath, DefaultArgument, System.Text.Encoding.UTF8); + + /// Add collabignore.txt to stop the clone from messing with Unity Collaborate if it's enabled. Just in case. + string collabignoreFile = Path.Combine(cloneProject.projectPath, "collabignore.txt"); + File.WriteAllText(collabignoreFile, "*"); /// Make it ignore ALL files in the clone. + } + + /// + /// Opens a project located at the given path (if one exists). + /// + /// + public static void OpenProject(string projectPath) + { + if (!Directory.Exists(projectPath)) + { + Debug.LogError("Cannot open the project - provided folder (" + projectPath + ") does not exist."); + return; + } + + if (projectPath == ClonesManager.GetCurrentProjectPath()) + { + Debug.LogError("Cannot open the project - it is already open."); + return; + } + + //Validate (and update if needed) the "Packages" folder before opening clone project to ensure the clone project will have the + //same "compiling environment" as the original project + ValidateCopiedFoldersIntegrity.ValidateFolder(projectPath, GetOriginalProjectPath(), "Packages"); + + string fileName = GetApplicationPath(); + string args = "-projectPath \"" + projectPath + "\""; + Debug.Log("Opening project \"" + fileName + " " + args + "\""); + ClonesManager.StartHiddenConsoleProcess(fileName, args); + } + + private static string GetApplicationPath() + { + switch (Application.platform) + { + case RuntimePlatform.WindowsEditor: + return EditorApplication.applicationPath; + case RuntimePlatform.OSXEditor: + return EditorApplication.applicationPath + "/Contents/MacOS/Unity"; + case RuntimePlatform.LinuxEditor: + return EditorApplication.applicationPath; + default: + throw new System.NotImplementedException("Platform has not supported yet ;("); + } + } + + /// + /// Is this project being opened by an Unity editor? + /// + /// + /// + public static bool IsCloneProjectRunning(string projectPath) + { + + //Determine whether it is opened in another instance by checking the UnityLockFile + string UnityLockFilePath = new string[] { projectPath, "Temp", "UnityLockfile" } + .Aggregate(Path.Combine); + + switch (Application.platform) + { + case (RuntimePlatform.WindowsEditor): + //Windows editor will lock "UnityLockfile" file when project is being opened. + //Sometime, for instance: windows editor crash, the "UnityLockfile" will not be deleted even the project + //isn't being opened, so a check to the "UnityLockfile" lock status may be necessary. + if (Preferences.AlsoCheckUnityLockFileStaPref.Value) + return File.Exists(UnityLockFilePath) && FileUtilities.IsFileLocked(UnityLockFilePath); + else + return File.Exists(UnityLockFilePath); + case (RuntimePlatform.OSXEditor): + //Mac editor won't lock "UnityLockfile" file when project is being opened + return File.Exists(UnityLockFilePath); + case (RuntimePlatform.LinuxEditor): + return File.Exists(UnityLockFilePath); + default: + throw new System.NotImplementedException("IsCloneProjectRunning: Unsupport Platfrom: " + Application.platform); + } + } + + /// + /// Deletes the clone of the currently open project, if such exists. + /// + public static void DeleteClone(string cloneProjectPath) + { + /// Clone won't be able to delete itself. + if (ClonesManager.IsClone()) return; + + ///Extra precautions. + if (cloneProjectPath == string.Empty) return; + if (cloneProjectPath == ClonesManager.GetOriginalProjectPath()) return; + + //Check what OS is + string identifierFile; + string args; + switch (Application.platform) + { + case (RuntimePlatform.WindowsEditor): + Debug.Log("Attempting to delete folder \"" + cloneProjectPath + "\""); + + //The argument file will be deleted first at the beginning of the project deletion process + //to prevent any further reading and writing to it(There's a File.Exist() check at the (file)editor windows.) + //If there's any file in the directory being write/read during the deletion process, the directory can't be fully removed. + identifierFile = Path.Combine(cloneProjectPath, ClonesManager.ArgumentFileName); + File.Delete(identifierFile); + + args = "/c " + @"rmdir /s/q " + string.Format("\"{0}\"", cloneProjectPath); + StartHiddenConsoleProcess("cmd.exe", args); + + break; + case (RuntimePlatform.OSXEditor): + Debug.Log("Attempting to delete folder \"" + cloneProjectPath + "\""); + + //The argument file will be deleted first at the beginning of the project deletion process + //to prevent any further reading and writing to it(There's a File.Exist() check at the (file)editor windows.) + //If there's any file in the directory being write/read during the deletion process, the directory can't be fully removed. + identifierFile = Path.Combine(cloneProjectPath, ClonesManager.ArgumentFileName); + File.Delete(identifierFile); + + FileUtil.DeleteFileOrDirectory(cloneProjectPath); + + break; + case (RuntimePlatform.LinuxEditor): + Debug.Log("Attempting to delete folder \"" + cloneProjectPath + "\""); + identifierFile = Path.Combine(cloneProjectPath, ClonesManager.ArgumentFileName); + File.Delete(identifierFile); + + FileUtil.DeleteFileOrDirectory(cloneProjectPath); + + break; + default: + Debug.LogWarning("Not in a known editor. Where are you!?"); + break; + } + } + + #endregion + + #region Creating project folders + + /// + /// Creates an empty folder using data in the given Project object + /// + /// + public static void CreateProjectFolder(Project project) + { + string path = project.projectPath; + Debug.Log("Creating new empty folder at: " + path); + Directory.CreateDirectory(path); + } + + /// + /// Copies the full contents of the unity library. We want to do this to avoid the lengthy re-serialization of the whole project when it opens up the clone. + /// + /// + /// + [System.Obsolete] + public static void CopyLibraryFolder(Project sourceProject, Project destinationProject) + { + if (Directory.Exists(destinationProject.libraryPath)) + { + Debug.LogWarning("Library copy: destination path already exists! "); + return; + } + + Debug.Log("Library copy: " + destinationProject.libraryPath); + ClonesManager.CopyDirectoryWithProgressBar(sourceProject.libraryPath, destinationProject.libraryPath, + "Cloning project '" + sourceProject.name + "'. "); + } + + #endregion + + #region Creating symlinks + + /// + /// Creates a symlink between destinationPath and sourcePath (Mac version). + /// + /// + /// + private static void CreateLinkMac(string sourcePath, string destinationPath) + { + sourcePath = sourcePath.Replace(" ", "\\ "); + destinationPath = destinationPath.Replace(" ", "\\ "); + var command = string.Format("ln -s {0} {1}", sourcePath, destinationPath); + + Debug.Log("Mac hard link " + command); + + ClonesManager.ExecuteBashCommand(command); + } + + /// + /// Creates a symlink between destinationPath and sourcePath (Linux version). + /// + /// + /// + private static void CreateLinkLinux(string sourcePath, string destinationPath) + { + sourcePath = sourcePath.Replace(" ", "\\ "); + destinationPath = destinationPath.Replace(" ", "\\ "); + var command = string.Format("ln -s {0} {1}", sourcePath, destinationPath); + + Debug.Log("Linux Symlink " + command); + + ClonesManager.ExecuteBashCommand(command); + } + + /// + /// Creates a symlink between destinationPath and sourcePath (Windows version). + /// + /// + /// + private static void CreateLinkWin(string sourcePath, string destinationPath) + { + string cmd = "/C mklink /J " + string.Format("\"{0}\" \"{1}\"", destinationPath, sourcePath); + Debug.Log("Windows junction: " + cmd); + ClonesManager.StartHiddenConsoleProcess("cmd.exe", cmd); + } + + //TODO(?) avoid terminal calls and use proper api stuff. See below for windows! + ////https://docs.microsoft.com/en-us/windows/desktop/api/ioapiset/nf-ioapiset-deviceiocontrol + //[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] + //private static extern bool DeviceIoControl(System.IntPtr hDevice, uint dwIoControlCode, + // System.IntPtr InBuffer, int nInBufferSize, + // System.IntPtr OutBuffer, int nOutBufferSize, + // out int pBytesReturned, System.IntPtr lpOverlapped); + + /// + /// Create a link / junction from the original project to it's clone. + /// + /// + /// + public static void LinkFolders(string sourcePath, string destinationPath) + { + if ((Directory.Exists(destinationPath) == false) && (Directory.Exists(sourcePath) == true)) + { + switch (Application.platform) + { + case (RuntimePlatform.WindowsEditor): + CreateLinkWin(sourcePath, destinationPath); + break; + case (RuntimePlatform.OSXEditor): + CreateLinkMac(sourcePath, destinationPath); + break; + case (RuntimePlatform.LinuxEditor): + CreateLinkLinux(sourcePath, destinationPath); + break; + default: + Debug.LogWarning("Not in a known editor. Application.platform: " + Application.platform); + break; + } + } + else + { + Debug.LogWarning("Skipping Asset link, it already exists: " + destinationPath); + } + } + + #endregion + + #region Utility methods + + private static bool? isCloneFileExistCache = null; + + /// + /// Returns true if the project currently open in Unity Editor is a clone. + /// + /// + public static bool IsClone() + { + if (isCloneFileExistCache == null) + { + /// The project is a clone if its root directory contains an empty file named ".clone". + string cloneFilePath = Path.Combine(ClonesManager.GetCurrentProjectPath(), ClonesManager.CloneFileName); + isCloneFileExistCache = File.Exists(cloneFilePath); + } + + return (bool)isCloneFileExistCache; + } + + /// + /// Get the path to the current unityEditor project folder's info + /// + /// + public static string GetCurrentProjectPath() + { + return Application.dataPath.Replace("/Assets", ""); + } + + /// + /// Return a project object that describes all the paths we need to clone it. + /// + /// + public static Project GetCurrentProject() + { + string pathString = ClonesManager.GetCurrentProjectPath(); + return new Project(pathString); + } + + /// + /// Get the argument of this clone project. + /// If this is the original project, will return an empty string. + /// + /// + public static string GetArgument() + { + string argument = ""; + if (IsClone()) + { + string argumentFilePath = Path.Combine(GetCurrentProjectPath(), ClonesManager.ArgumentFileName); + if (File.Exists(argumentFilePath)) + { + argument = File.ReadAllText(argumentFilePath, System.Text.Encoding.UTF8); + } + } + + return argument; + } + + /// + /// Returns the path to the original project. + /// If currently open project is the original, returns its own path. + /// If the original project folder cannot be found, retuns an empty string. + /// + /// + public static string GetOriginalProjectPath() + { + if (IsClone()) + { + /// If this is a clone... + /// Original project path can be deduced by removing the suffix from the clone's path. + string cloneProjectPath = ClonesManager.GetCurrentProject().projectPath; + + int index = cloneProjectPath.LastIndexOf(ClonesManager.CloneNameSuffix); + if (index > 0) + { + string originalProjectPath = cloneProjectPath.Substring(0, index); + if (Directory.Exists(originalProjectPath)) return originalProjectPath; + } + + return string.Empty; + } + else + { + /// If this is the original, we return its own path. + return ClonesManager.GetCurrentProjectPath(); + } + } + + /// + /// Returns all clone projects path. + /// + /// + public static List GetCloneProjectsPath() + { + List projectsPath = new List(); + for (int i = 0; i < MaxCloneProjectCount; i++) + { + string originalProjectPath = ClonesManager.GetCurrentProject().projectPath; + string cloneProjectPath = originalProjectPath + ClonesManager.CloneNameSuffix + "_" + i; + + if (Directory.Exists(cloneProjectPath)) + projectsPath.Add(cloneProjectPath); + } + + return projectsPath; + } + + /// + /// Copies directory located at sourcePath to destinationPath. Displays a progress bar. + /// + /// Directory to be copied. + /// Destination directory (created automatically if needed). + /// Optional string added to the beginning of the progress bar window header. + public static void CopyDirectoryWithProgressBar(string sourcePath, string destinationPath, + string progressBarPrefix = "") + { + var source = new DirectoryInfo(sourcePath); + var destination = new DirectoryInfo(destinationPath); + + long totalBytes = 0; + long copiedBytes = 0; + + ClonesManager.CopyDirectoryWithProgressBarRecursive(source, destination, ref totalBytes, ref copiedBytes, + progressBarPrefix); + EditorUtility.ClearProgressBar(); + } + + /// + /// Copies directory located at sourcePath to destinationPath. Displays a progress bar. + /// Same as the previous method, but uses recursion to copy all nested folders as well. + /// + /// Directory to be copied. + /// Destination directory (created automatically if needed). + /// Total bytes to be copied. Calculated automatically, initialize at 0. + /// To track already copied bytes. Calculated automatically, initialize at 0. + /// Optional string added to the beginning of the progress bar window header. + private static void CopyDirectoryWithProgressBarRecursive(DirectoryInfo source, DirectoryInfo destination, + ref long totalBytes, ref long copiedBytes, string progressBarPrefix = "") + { + /// Directory cannot be copied into itself. + if (source.FullName.ToLower() == destination.FullName.ToLower()) + { + Debug.LogError("Cannot copy directory into itself."); + return; + } + + /// Calculate total bytes, if required. + if (totalBytes == 0) + { + totalBytes = ClonesManager.GetDirectorySize(source, true, progressBarPrefix); + } + + /// Create destination directory, if required. + if (!Directory.Exists(destination.FullName)) + { + Directory.CreateDirectory(destination.FullName); + } + + /// Copy all files from the source. + foreach (FileInfo file in source.GetFiles()) + { + try + { + file.CopyTo(Path.Combine(destination.ToString(), file.Name), true); + } + catch (IOException) + { + /// Some files may throw IOException if they are currently open in Unity editor. + /// Just ignore them in such case. + } + + /// Account the copied file size. + copiedBytes += file.Length; + + /// Display the progress bar. + float progress = (float)copiedBytes / (float)totalBytes; + bool cancelCopy = EditorUtility.DisplayCancelableProgressBar( + progressBarPrefix + "Copying '" + source.FullName + "' to '" + destination.FullName + "'...", + "(" + (progress * 100f).ToString("F2") + "%) Copying file '" + file.Name + "'...", + progress); + if (cancelCopy) return; + } + + /// Copy all nested directories from the source. + foreach (DirectoryInfo sourceNestedDir in source.GetDirectories()) + { + DirectoryInfo nextDestingationNestedDir = destination.CreateSubdirectory(sourceNestedDir.Name); + ClonesManager.CopyDirectoryWithProgressBarRecursive(sourceNestedDir, nextDestingationNestedDir, + ref totalBytes, ref copiedBytes, progressBarPrefix); + } + } + + /// + /// Calculates the size of the given directory. Displays a progress bar. + /// + /// Directory, which size has to be calculated. + /// If true, size will include all nested directories. + /// Optional string added to the beginning of the progress bar window header. + /// Size of the directory in bytes. + private static long GetDirectorySize(DirectoryInfo directory, bool includeNested = false, + string progressBarPrefix = "") + { + EditorUtility.DisplayProgressBar(progressBarPrefix + "Calculating size of directories...", + "Scanning '" + directory.FullName + "'...", 0f); + + /// Calculate size of all files in directory. + long filesSize = directory.GetFiles().Sum((FileInfo file) => file.Length); + + /// Calculate size of all nested directories. + long directoriesSize = 0; + if (includeNested) + { + IEnumerable nestedDirectories = directory.GetDirectories(); + foreach (DirectoryInfo nestedDir in nestedDirectories) + { + directoriesSize += ClonesManager.GetDirectorySize(nestedDir, true, progressBarPrefix); + } + } + + return filesSize + directoriesSize; + } + + /// + /// Starts process in the system console, taking the given fileName and args. + /// + /// + /// + private static void StartHiddenConsoleProcess(string fileName, string args) + { + System.Diagnostics.Process.Start(fileName, args); + } + + /// + /// Thanks to https://github.com/karl-/unity-symlink-utility/blob/master/SymlinkUtility.cs + /// + /// + private static void ExecuteBashCommand(string command) + { + command = command.Replace("\"", "\"\""); + + var proc = new Process() + { + StartInfo = new ProcessStartInfo + { + FileName = "/bin/bash", + Arguments = "-c \"" + command + "\"", + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true + } + }; + + using (proc) + { + proc.Start(); + proc.WaitForExit(); + + if (!proc.StandardError.EndOfStream) + { + UnityEngine.Debug.LogError(proc.StandardError.ReadToEnd()); + } + } + } + + public static void OpenProjectInFileExplorer(string path) + { + System.Diagnostics.Process.Start(@path); + } + #endregion + } +} diff --git a/Assets/ParrelSync/ParrelSync/Editor/ClonesManager.cs.meta b/Assets/ParrelSync/ParrelSync/Editor/ClonesManager.cs.meta new file mode 100755 index 000000000..5800cf851 --- /dev/null +++ b/Assets/ParrelSync/ParrelSync/Editor/ClonesManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6148e48ed6b61d748b187d06d3687b83 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ParrelSync/ParrelSync/Editor/ClonesManagerWindow.cs b/Assets/ParrelSync/ParrelSync/Editor/ClonesManagerWindow.cs new file mode 100755 index 000000000..ad9619e28 --- /dev/null +++ b/Assets/ParrelSync/ParrelSync/Editor/ClonesManagerWindow.cs @@ -0,0 +1,198 @@ +using UnityEngine; +using UnityEditor; +using System.IO; + +namespace ParrelSync +{ + /// + ///Clones manager Unity editor window + /// + public class ClonesManagerWindow : EditorWindow + { + /// + /// Returns true if project clone exists. + /// + public bool isCloneCreated + { + get { return ClonesManager.GetCloneProjectsPath().Count >= 1; } + } + + [MenuItem("ParrelSync/Clones Manager", priority = 0)] + private static void InitWindow() + { + ClonesManagerWindow window = (ClonesManagerWindow)EditorWindow.GetWindow(typeof(ClonesManagerWindow)); + window.titleContent = new GUIContent("Clones Manager"); + window.Show(); + } + + /// + /// For storing the scroll position of clones list + /// + Vector2 clonesScrollPos; + + private void OnGUI() + { + /// If it is a clone project... + if (ClonesManager.IsClone()) + { + //Find out the original project name and show the help box + string originalProjectPath = ClonesManager.GetOriginalProjectPath(); + if (originalProjectPath == string.Empty) + { + /// If original project cannot be found, display warning message. + EditorGUILayout.HelpBox( + "This project is a clone, but the link to the original seems lost.\nYou have to manually open the original and create a new clone instead of this one.\n", + MessageType.Warning); + } + else + { + /// If original project is present, display some usage info. + EditorGUILayout.HelpBox( + "This project is a clone of the project '" + Path.GetFileName(originalProjectPath) + "'.\nIf you want to make changes the project files or manage clones, please open the original project through Unity Hub.", + MessageType.Info); + } + + //Clone project custom argument. + GUILayout.BeginHorizontal(); + EditorGUILayout.LabelField("Arguments", GUILayout.Width(70)); + if (GUILayout.Button("?", GUILayout.Width(20))) + { + Application.OpenURL(ExternalLinks.CustomArgumentHelpLink); + } + GUILayout.EndHorizontal(); + + string argumentFilePath = Path.Combine(ClonesManager.GetCurrentProjectPath(), ClonesManager.ArgumentFileName); + //Need to be careful with file reading / writing since it will effect the deletion of + // the clone project(The directory won't be fully deleted if there's still file inside being read or write). + //The argument file will be deleted first at the beginning of the project deletion process + //to prevent any further being read and write. + //Will need to take some extra cautious if want to change the design of how file editing is handled. + if (File.Exists(argumentFilePath)) + { + string argument = File.ReadAllText(argumentFilePath, System.Text.Encoding.UTF8); + string argumentTextAreaInput = EditorGUILayout.TextArea(argument, + GUILayout.Height(50), + GUILayout.MaxWidth(300) + ); + File.WriteAllText(argumentFilePath, argumentTextAreaInput, System.Text.Encoding.UTF8); + } + else + { + EditorGUILayout.LabelField("No argument file found."); + } + } + else// If it is an original project... + { + if (isCloneCreated) + { + GUILayout.BeginVertical("HelpBox"); + GUILayout.Label("Clones of this Project"); + + //List all clones + clonesScrollPos = + EditorGUILayout.BeginScrollView(clonesScrollPos); + var cloneProjectsPath = ClonesManager.GetCloneProjectsPath(); + for (int i = 0; i < cloneProjectsPath.Count; i++) + { + + GUILayout.BeginVertical("GroupBox"); + string cloneProjectPath = cloneProjectsPath[i]; + + bool isOpenInAnotherInstance = ClonesManager.IsCloneProjectRunning(cloneProjectPath); + + if (isOpenInAnotherInstance == true) + EditorGUILayout.LabelField("Clone " + i + " (Running)", EditorStyles.boldLabel); + else + EditorGUILayout.LabelField("Clone " + i); + + + GUILayout.BeginHorizontal(); + EditorGUILayout.TextField("Clone project path", cloneProjectPath, EditorStyles.textField); + if (GUILayout.Button("View Folder", GUILayout.Width(80))) + { + ClonesManager.OpenProjectInFileExplorer(cloneProjectPath); + } + GUILayout.EndHorizontal(); + + GUILayout.BeginHorizontal(); + EditorGUILayout.LabelField("Arguments", GUILayout.Width(70)); + if (GUILayout.Button("?", GUILayout.Width(20))) + { + Application.OpenURL(ExternalLinks.CustomArgumentHelpLink); + } + GUILayout.EndHorizontal(); + + string argumentFilePath = Path.Combine(cloneProjectPath, ClonesManager.ArgumentFileName); + //Need to be careful with file reading/writing since it will effect the deletion of + //the clone project(The directory won't be fully deleted if there's still file inside being read or write). + //The argument file will be deleted first at the beginning of the project deletion process + //to prevent any further being read and write. + //Will need to take some extra cautious if want to change the design of how file editing is handled. + if (File.Exists(argumentFilePath)) + { + string argument = File.ReadAllText(argumentFilePath, System.Text.Encoding.UTF8); + string argumentTextAreaInput = EditorGUILayout.TextArea(argument, + GUILayout.Height(50), + GUILayout.MaxWidth(300) + ); + File.WriteAllText(argumentFilePath, argumentTextAreaInput, System.Text.Encoding.UTF8); + } + else + { + EditorGUILayout.LabelField("No argument file found."); + } + + EditorGUILayout.Space(); + EditorGUILayout.Space(); + EditorGUILayout.Space(); + + + EditorGUI.BeginDisabledGroup(isOpenInAnotherInstance); + + if (GUILayout.Button("Open in New Editor")) + { + ClonesManager.OpenProject(cloneProjectPath); + } + + GUILayout.BeginHorizontal(); + if (GUILayout.Button("Delete")) + { + bool delete = EditorUtility.DisplayDialog( + "Delete the clone?", + "Are you sure you want to delete the clone project '" + ClonesManager.GetCurrentProject().name + "_clone'?", + "Delete", + "Cancel"); + if (delete) + { + ClonesManager.DeleteClone(cloneProjectPath); + } + } + + GUILayout.EndHorizontal(); + EditorGUI.EndDisabledGroup(); + GUILayout.EndVertical(); + + } + EditorGUILayout.EndScrollView(); + + if (GUILayout.Button("Add new clone")) + { + ClonesManager.CreateCloneFromCurrent(); + } + + GUILayout.EndVertical(); + GUILayout.FlexibleSpace(); + } + else + { + /// If no clone created yet, we must create it. + EditorGUILayout.HelpBox("No project clones found. Create a new one!", MessageType.Info); + if (GUILayout.Button("Create new clone")) + { + ClonesManager.CreateCloneFromCurrent(); + } + } + } + } + } +} diff --git a/Assets/ParrelSync/ParrelSync/Editor/ClonesManagerWindow.cs.meta b/Assets/ParrelSync/ParrelSync/Editor/ClonesManagerWindow.cs.meta new file mode 100755 index 000000000..ac75a0455 --- /dev/null +++ b/Assets/ParrelSync/ParrelSync/Editor/ClonesManagerWindow.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a041d83486c20b84bbf5077ddfbbca37 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ParrelSync/ParrelSync/Editor/ExternalLinks.cs b/Assets/ParrelSync/ParrelSync/Editor/ExternalLinks.cs new file mode 100755 index 000000000..84809bccc --- /dev/null +++ b/Assets/ParrelSync/ParrelSync/Editor/ExternalLinks.cs @@ -0,0 +1,13 @@ +namespace ParrelSync +{ + public class ExternalLinks + { + public const string RemoteVersionURL = "https://raw.githubusercontent.com/VeriorPies/ParrelSync/master/VERSION.txt"; + public const string Releases = "https://github.com/VeriorPies/ParrelSync/releases"; + public const string CustomArgumentHelpLink = "https://github.com/VeriorPies/ParrelSync/wiki/Argument"; + + public const string GitHubHome = "https://github.com/VeriorPies/ParrelSync/"; + public const string GitHubIssue = "https://github.com/VeriorPies/ParrelSync/issues"; + public const string FAQ = "https://github.com/VeriorPies/ParrelSync/wiki/Troubleshooting-&-FAQs"; + } +} \ No newline at end of file diff --git a/Assets/ParrelSync/ParrelSync/Editor/ExternalLinks.cs.meta b/Assets/ParrelSync/ParrelSync/Editor/ExternalLinks.cs.meta new file mode 100755 index 000000000..c238b5c9f --- /dev/null +++ b/Assets/ParrelSync/ParrelSync/Editor/ExternalLinks.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 65daf17fbe5101b41977305639f30c65 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ParrelSync/ParrelSync/Editor/FileUtilities.cs b/Assets/ParrelSync/ParrelSync/Editor/FileUtilities.cs new file mode 100755 index 000000000..4d0a23925 --- /dev/null +++ b/Assets/ParrelSync/ParrelSync/Editor/FileUtilities.cs @@ -0,0 +1,31 @@ +using System.IO; +using UnityEngine; + +namespace ParrelSync +{ + public class FileUtilities : MonoBehaviour + { + public static bool IsFileLocked(string path) + { + FileInfo file = new FileInfo(path); + try + { + using (FileStream stream = file.Open(FileMode.Open, FileAccess.Read, FileShare.None)) + { + stream.Close(); + } + } + catch (IOException) + { + //the file is unavailable because it is: + //still being written to + //or being processed by another thread + //or does not exist (has already been processed) + return true; + } + + //file is not locked + return false; + } + } +} \ No newline at end of file diff --git a/Assets/ParrelSync/ParrelSync/Editor/FileUtilities.cs.meta b/Assets/ParrelSync/ParrelSync/Editor/FileUtilities.cs.meta new file mode 100755 index 000000000..273394425 --- /dev/null +++ b/Assets/ParrelSync/ParrelSync/Editor/FileUtilities.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 11fdc6f78f8c965499a870ca06dca6bc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ParrelSync/ParrelSync/Editor/NonCore.meta b/Assets/ParrelSync/ParrelSync/Editor/NonCore.meta new file mode 100755 index 000000000..5b4e19296 --- /dev/null +++ b/Assets/ParrelSync/ParrelSync/Editor/NonCore.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 74a7aa389726f964ab34c52e208c2a43 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ParrelSync/ParrelSync/Editor/NonCore/AskFeedbackDialog.cs b/Assets/ParrelSync/ParrelSync/Editor/NonCore/AskFeedbackDialog.cs new file mode 100755 index 000000000..2bb988ae1 --- /dev/null +++ b/Assets/ParrelSync/ParrelSync/Editor/NonCore/AskFeedbackDialog.cs @@ -0,0 +1,78 @@ +namespace ParrelSync.NonCore +{ + using UnityEditor; + using UnityEngine; + + /// + /// A simple script to display feedback/star dialog after certain time of project being opened/re-compiled. + /// Will only pop-up once unless "Remind me next time" are chosen. + /// Removing this file from project wont effect any other functions. + /// + [InitializeOnLoad] + public class AskFeedbackDialog + { + const string InitializeOnLoadCountKey = "ParrelSync_InitOnLoadCount", StopShowingKey = "ParrelSync_StopShowFeedBack"; + static AskFeedbackDialog() + { + if (EditorPrefs.HasKey(StopShowingKey)) { return; } + + int InitializeOnLoadCount = EditorPrefs.GetInt(InitializeOnLoadCountKey, 0); + if (InitializeOnLoadCount > 20) + { + ShowDialog(); + } + else + { + EditorPrefs.SetInt(InitializeOnLoadCountKey, InitializeOnLoadCount + 1); + } + } + + //[MenuItem("ParrelSync/(Debug)Show AskFeedbackDialog ")] + private static void ShowDialog() + { + int option = EditorUtility.DisplayDialogComplex("Do you like " + ParrelSync.ClonesManager.ProjectName + "?", + "Do you like " + ParrelSync.ClonesManager.ProjectName + "?\n" + + "If so, please don't hesitate to star it on GitHub and contribute to the project!", + "Star on GitHub", + "Close", + "Remind me next time" + ); + + switch (option) + { + // First parameter. + case 0: + Debug.Log("AskFeedbackDialog: Star on GitHub selected"); + EditorPrefs.SetBool(StopShowingKey, true); + EditorPrefs.DeleteKey(InitializeOnLoadCountKey); + Application.OpenURL(ExternalLinks.GitHubHome); + break; + // Second parameter. + case 1: + Debug.Log("AskFeedbackDialog: Close and never show again."); + EditorPrefs.SetBool(StopShowingKey, true); + EditorPrefs.DeleteKey(InitializeOnLoadCountKey); + break; + // Third parameter. + case 2: + Debug.Log("AskFeedbackDialog: Remind me next time"); + EditorPrefs.SetInt(InitializeOnLoadCountKey, 0); + break; + default: + //Debug.Log("Close windows."); + break; + } + } + + ///// + ///// For debug purpose + ///// + //[MenuItem("ParrelSync/(Debug)Delete AskFeedbackDialog keys")] + //private static void DebugDeleteAllKeys() + //{ + // EditorPrefs.DeleteKey(InitializeOnLoadCountKey); + // EditorPrefs.DeleteKey(StopShowingKey); + // Debug.Log("AskFeedbackDialog keys deleted"); + //} + } +} \ No newline at end of file diff --git a/Assets/ParrelSync/ParrelSync/Editor/NonCore/AskFeedbackDialog.cs.meta b/Assets/ParrelSync/ParrelSync/Editor/NonCore/AskFeedbackDialog.cs.meta new file mode 100755 index 000000000..20a2a0b81 --- /dev/null +++ b/Assets/ParrelSync/ParrelSync/Editor/NonCore/AskFeedbackDialog.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 894412a5b602e6c4ba2cf2d01f4f92b5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ParrelSync/ParrelSync/Editor/NonCore/OtherMenuItem.cs b/Assets/ParrelSync/ParrelSync/Editor/NonCore/OtherMenuItem.cs new file mode 100755 index 000000000..0f42af96f --- /dev/null +++ b/Assets/ParrelSync/ParrelSync/Editor/NonCore/OtherMenuItem.cs @@ -0,0 +1,26 @@ +namespace ParrelSync.NonCore +{ + using UnityEditor; + using UnityEngine; + + public class OtherMenuItem + { + [MenuItem("ParrelSync/GitHub/View this project on GitHub", priority = 10)] + private static void OpenGitHub() + { + Application.OpenURL(ExternalLinks.GitHubHome); + } + + [MenuItem("ParrelSync/GitHub/View FAQ", priority = 11)] + private static void OpenFAQ() + { + Application.OpenURL(ExternalLinks.FAQ); + } + + [MenuItem("ParrelSync/GitHub/View Issues", priority = 12)] + private static void OpenGitHubIssues() + { + Application.OpenURL(ExternalLinks.GitHubIssue); + } + } +} \ No newline at end of file diff --git a/Assets/ParrelSync/ParrelSync/Editor/NonCore/OtherMenuItem.cs.meta b/Assets/ParrelSync/ParrelSync/Editor/NonCore/OtherMenuItem.cs.meta new file mode 100755 index 000000000..563d7a2a3 --- /dev/null +++ b/Assets/ParrelSync/ParrelSync/Editor/NonCore/OtherMenuItem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7191fa4bfa12ae749b27f73ed292eaf1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ParrelSync/ParrelSync/Editor/Preferences.cs b/Assets/ParrelSync/ParrelSync/Editor/Preferences.cs new file mode 100755 index 000000000..3ca653d09 --- /dev/null +++ b/Assets/ParrelSync/ParrelSync/Editor/Preferences.cs @@ -0,0 +1,110 @@ +using UnityEngine; +using UnityEditor; + +namespace ParrelSync +{ + /// + /// To add value caching for functions + /// + public class BoolPreference + { + public string key { get; private set; } + public bool defaultValue { get; private set; } + public BoolPreference(string key, bool defaultValue) + { + this.key = key; + this.defaultValue = defaultValue; + } + + private bool? valueCache = null; + + public bool Value + { + get + { + if (valueCache == null) + valueCache = EditorPrefs.GetBool(key, defaultValue); + + return (bool)valueCache; + } + set + { + if (valueCache == value) + return; + + EditorPrefs.SetBool(key, value); + valueCache = value; + Debug.Log("Editor preference updated. key: " + key + ", value: " + value); + } + } + + public void ClearValue() + { + EditorPrefs.DeleteKey(key); + valueCache = null; + } + } + + public class Preferences : EditorWindow + { + [MenuItem("ParrelSync/Preferences", priority = 1)] + private static void InitWindow() + { + Preferences window = (Preferences)EditorWindow.GetWindow(typeof(Preferences)); + window.titleContent = new GUIContent(ClonesManager.ProjectName + " Preferences"); + window.Show(); + } + + /// + /// Disable asset saving in clone editors? + /// + public static BoolPreference AssetModPref = new BoolPreference("ParrelSync_DisableClonesAssetSaving", true); + + /// + /// In addition of checking the existence of UnityLockFile, + /// also check is the is the UnityLockFile being opened. + /// + public static BoolPreference AlsoCheckUnityLockFileStaPref = new BoolPreference("ParrelSync_CheckUnityLockFileOpenStatus", true); + + private void OnGUI() + { + if (ClonesManager.IsClone()) + { + EditorGUILayout.HelpBox( + "This is a clone project. Please use the original project editor to change preferences.", + MessageType.Info); + return; + } + + GUILayout.BeginVertical("HelpBox"); + GUILayout.Label("Preferences"); + GUILayout.BeginVertical("GroupBox"); + + AssetModPref.Value = EditorGUILayout.ToggleLeft( + new GUIContent( + "(recommended) Disable asset saving in clone editors- require re-open clone editors", + "Disable asset saving in clone editors so all assets can only be modified from the original project editor" + ), + AssetModPref.Value); + + if (Application.platform == RuntimePlatform.WindowsEditor) + { + AlsoCheckUnityLockFileStaPref.Value = EditorGUILayout.ToggleLeft( + new GUIContent( + "Also check UnityLockFile lock status while checking clone projects running status", + "Disable this can slightly increase Clones Manager window performance, but will lead to in-correct clone project running status" + + "(the Clones Manager window show the clone project is still running even it's not) if the clone editor crashed" + ), + AlsoCheckUnityLockFileStaPref.Value); + } + GUILayout.EndVertical(); + if (GUILayout.Button("Reset to default")) + { + AssetModPref.ClearValue(); + AlsoCheckUnityLockFileStaPref.ClearValue(); + Debug.Log("Editor preferences cleared"); + } + GUILayout.EndVertical(); + } + } +} \ No newline at end of file diff --git a/Assets/ParrelSync/ParrelSync/Editor/Preferences.cs.meta b/Assets/ParrelSync/ParrelSync/Editor/Preferences.cs.meta new file mode 100755 index 000000000..0166f9a5b --- /dev/null +++ b/Assets/ParrelSync/ParrelSync/Editor/Preferences.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 24641be1c0410a745b529e61b508679f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ParrelSync/ParrelSync/Editor/Project.cs b/Assets/ParrelSync/ParrelSync/Editor/Project.cs new file mode 100755 index 000000000..7e7c3874a --- /dev/null +++ b/Assets/ParrelSync/ParrelSync/Editor/Project.cs @@ -0,0 +1,112 @@ +using System.Collections.Generic; +using System.Linq; + +namespace ParrelSync +{ + public class Project : System.ICloneable + { + public string name; + public string projectPath; + string rootPath; + public string assetPath; + public string projectSettingsPath; + public string libraryPath; + public string packagesPath; + public string autoBuildPath; + public string localPackages; + + char[] separator = new char[1] { '/' }; + + + /// + /// Default constructor + /// + public Project() + { + + } + + + /// + /// Initialize the project object by parsing its full path returned by Unity into a bunch of individual folder names and paths. + /// + /// + public Project(string path) + { + ParsePath(path); + } + + + /// + /// Create a new object with the same settings + /// + /// + public object Clone() + { + Project newProject = new Project(); + newProject.rootPath = rootPath; + newProject.projectPath = projectPath; + newProject.assetPath = assetPath; + newProject.projectSettingsPath = projectSettingsPath; + newProject.libraryPath = libraryPath; + newProject.name = name; + newProject.separator = separator; + newProject.packagesPath = packagesPath; + newProject.autoBuildPath = autoBuildPath; + newProject.localPackages = localPackages; + + + return newProject; + } + + + /// + /// Update the project object by renaming and reparsing it. Pass in the new name of a project, and it'll update the other member variables to match. + /// + /// + public void updateNewName(string newName) + { + name = newName; + ParsePath(rootPath + "/" + name + "/Assets"); + } + + + /// + /// Debug override so we can quickly print out the project info. + /// + /// + public override string ToString() + { + string printString = name + "\n" + + rootPath + "\n" + + projectPath + "\n" + + assetPath + "\n" + + projectSettingsPath + "\n" + + packagesPath + "\n" + + autoBuildPath + "\n" + + localPackages + "\n" + + libraryPath; + return (printString); + } + + private void ParsePath(string path) + { + //Unity's Application functions return the Assets path in the Editor. + projectPath = path; + + //pop off the last part of the path for the project name, keep the rest for the root path + List pathArray = projectPath.Split(separator).ToList(); + name = pathArray.Last(); + + pathArray.RemoveAt(pathArray.Count() - 1); + rootPath = string.Join(separator[0].ToString(), pathArray.ToArray()); + + assetPath = projectPath + "/Assets"; + projectSettingsPath = projectPath + "/ProjectSettings"; + libraryPath = projectPath + "/Library"; + packagesPath = projectPath + "/Packages"; + autoBuildPath = projectPath + "/AutoBuild"; + localPackages = projectPath + "/LocalPackages"; + } + } +} \ No newline at end of file diff --git a/Assets/ParrelSync/ParrelSync/Editor/Project.cs.meta b/Assets/ParrelSync/ParrelSync/Editor/Project.cs.meta new file mode 100755 index 000000000..84d9855c1 --- /dev/null +++ b/Assets/ParrelSync/ParrelSync/Editor/Project.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ec8d3a1577179ef44815739178cf75b4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ParrelSync/ParrelSync/Editor/UpdateChecker.cs b/Assets/ParrelSync/ParrelSync/Editor/UpdateChecker.cs new file mode 100755 index 000000000..85dbf9649 --- /dev/null +++ b/Assets/ParrelSync/ParrelSync/Editor/UpdateChecker.cs @@ -0,0 +1,60 @@ +using System; +using UnityEditor; +using UnityEngine; +namespace ParrelSync.Update +{ + /// + /// A simple update checker + /// + public class UpdateChecker + { + //const string LocalVersionFilePath = "Assets/ParrelSync/VERSION.txt"; + public const string LocalVersion = "1.5.1"; + [MenuItem("ParrelSync/Check for update", priority = 20)] + static void CheckForUpdate() + { + using (System.Net.WebClient client = new System.Net.WebClient()) + { + try + { + //This won't work with UPM packages + //string localVersionText = AssetDatabase.LoadAssetAtPath(LocalVersionFilePath).text; + + string localVersionText = LocalVersion; + Debug.Log("Local version text : " + LocalVersion); + + string latesteVersionText = client.DownloadString(ExternalLinks.RemoteVersionURL); + Debug.Log("latest version text got: " + latesteVersionText); + string messageBody = "Current Version: " + localVersionText +"\n" + +"Latest Version: " + latesteVersionText + "\n"; + var latestVersion = new Version(latesteVersionText); + var localVersion = new Version(localVersionText); + + if (latestVersion > localVersion) + { + Debug.Log("There's a newer version"); + messageBody += "There's a newer version available"; + if(EditorUtility.DisplayDialog("Check for update.", messageBody, "Get latest release", "Close")) + { + Application.OpenURL(ExternalLinks.Releases); + } + } + else + { + Debug.Log("Current version is up-to-date."); + messageBody += "Current version is up-to-date."; + EditorUtility.DisplayDialog("Check for update.", messageBody,"OK"); + } + + } + catch (Exception exp) + { + Debug.LogError("Error with checking update. Exception: " + exp); + EditorUtility.DisplayDialog("Update Error","Error with checking update. \nSee console for more details.", + "OK" + ); + } + } + } + } +} diff --git a/Assets/ParrelSync/ParrelSync/Editor/UpdateChecker.cs.meta b/Assets/ParrelSync/ParrelSync/Editor/UpdateChecker.cs.meta new file mode 100755 index 000000000..8dcd73339 --- /dev/null +++ b/Assets/ParrelSync/ParrelSync/Editor/UpdateChecker.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d3453b3f1a20ea148b5028f8556a7be5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ParrelSync/ParrelSync/Editor/ValidateCopiedFoldersIntegrity.cs b/Assets/ParrelSync/ParrelSync/Editor/ValidateCopiedFoldersIntegrity.cs new file mode 100755 index 000000000..1ee73bc7e --- /dev/null +++ b/Assets/ParrelSync/ParrelSync/Editor/ValidateCopiedFoldersIntegrity.cs @@ -0,0 +1,73 @@ +namespace ParrelSync +{ + using UnityEditor; + using UnityEngine; + using System; + using System.Text; + using System.Security.Cryptography; + using System.IO; + using System.Linq; + + [InitializeOnLoad] + public class ValidateCopiedFoldersIntegrity + { + const string SessionStateKey = "ValidateCopiedFoldersIntegrity_Init"; + /// + /// Called once on editor startup. + /// Validate copied folders integrity in clone project + /// + static ValidateCopiedFoldersIntegrity() + { + if (!SessionState.GetBool(SessionStateKey, false)) + { + SessionState.SetBool(SessionStateKey, true); + if (!ClonesManager.IsClone()) { return; } + + ValidateFolder(ClonesManager.GetCurrentProjectPath(), ClonesManager.GetOriginalProjectPath(), "Packages"); + } + } + + public static void ValidateFolder(string targetRoot, string originalRoot, string folderName) + { + var targetFolderPath = Path.Combine(targetRoot, folderName); + var targetFolderHash = CreateMd5ForFolder(targetFolderPath); + + var originalFolderPath = Path.Combine(originalRoot, folderName); + var originalFolderHash = CreateMd5ForFolder(originalFolderPath); + + if (targetFolderHash != originalFolderHash) + { + Debug.Log("ParrelSync: Detected changes in '" + folderName + "' directory. Updating cloned project..."); + FileUtil.ReplaceDirectory(originalFolderPath, targetFolderPath); + } + } + + static string CreateMd5ForFolder(string path) + { + // assuming you want to include nested folders + var files = Directory.GetFiles(path, "*.*", SearchOption.AllDirectories) + .OrderBy(p => p).ToList(); + + MD5 md5 = MD5.Create(); + + for (int i = 0; i < files.Count; i++) + { + string file = files[i]; + + // hash path + string relativePath = file.Substring(path.Length + 1); + byte[] pathBytes = Encoding.UTF8.GetBytes(relativePath.ToLower()); + md5.TransformBlock(pathBytes, 0, pathBytes.Length, pathBytes, 0); + + // hash contents + byte[] contentBytes = File.ReadAllBytes(file); + if (i == files.Count - 1) + md5.TransformFinalBlock(contentBytes, 0, contentBytes.Length); + else + md5.TransformBlock(contentBytes, 0, contentBytes.Length, contentBytes, 0); + } + + return BitConverter.ToString(md5.Hash).Replace("-", "").ToLower(); + } + } +} \ No newline at end of file diff --git a/Assets/ParrelSync/ParrelSync/Editor/ValidateCopiedFoldersIntegrity.cs.meta b/Assets/ParrelSync/ParrelSync/Editor/ValidateCopiedFoldersIntegrity.cs.meta new file mode 100755 index 000000000..daab5229e --- /dev/null +++ b/Assets/ParrelSync/ParrelSync/Editor/ValidateCopiedFoldersIntegrity.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d8fb344b9abf5274abd744833474b087 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ParrelSync/ParrelSync/package.json b/Assets/ParrelSync/ParrelSync/package.json new file mode 100755 index 000000000..29776fb78 --- /dev/null +++ b/Assets/ParrelSync/ParrelSync/package.json @@ -0,0 +1,10 @@ +{ + "name": "com.veriorpies.parrelsync", + "displayName": "ParrelSync", + "version": "1.5.1", + "unity": "2018.4", + "description": "ParrelSync is a Unity editor extension that allows users to test multiplayer gameplay without building the project by having another Unity editor window opened and mirror the changes from the original project.", + "license": "MIT", + "keywords": [ "Networking", "Utils", "Editor", "Extensions" ], + "dependencies": {} +} \ No newline at end of file diff --git a/Assets/ParrelSync/ParrelSync/package.json.meta b/Assets/ParrelSync/ParrelSync/package.json.meta new file mode 100755 index 000000000..4ced74079 --- /dev/null +++ b/Assets/ParrelSync/ParrelSync/package.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: a2a889c264e34b47a7349cbcb2cbedd7 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ParrelSync/ParrelSync/projectCloner.asmdef b/Assets/ParrelSync/ParrelSync/projectCloner.asmdef new file mode 100755 index 000000000..1e56b06f5 --- /dev/null +++ b/Assets/ParrelSync/ParrelSync/projectCloner.asmdef @@ -0,0 +1,15 @@ +{ + "name": "ParrelSync", + "references": [], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Assets/ParrelSync/ParrelSync/projectCloner.asmdef.meta b/Assets/ParrelSync/ParrelSync/projectCloner.asmdef.meta new file mode 100755 index 000000000..3aa8857fb --- /dev/null +++ b/Assets/ParrelSync/ParrelSync/projectCloner.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 894a6cc6ed5cd2645bb542978cbed6a9 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ParrelSync/README.md b/Assets/ParrelSync/README.md new file mode 100755 index 000000000..18ec606a1 --- /dev/null +++ b/Assets/ParrelSync/README.md @@ -0,0 +1,91 @@ +# ParrelSync +[![Release](https://img.shields.io/github/v/release/VeriorPies/ParrelSync?include_prereleases)](https://github.com/VeriorPies/ParrelSync/releases) [![Documentation](https://img.shields.io/badge/documentation-brightgreen.svg)](https://github.com/VeriorPies/ParrelSync/wiki) [![License](https://img.shields.io/badge/license-MIT-green)](https://github.com/VeriorPies/ParrelSync/blob/master/LICENSE.md) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-blue.svg)](https://github.com/VeriorPies/ParrelSync/pulls) [![Chats](https://img.shields.io/discord/710688100996743200)](https://discord.gg/TmQk2qG) + +ParrelSync is a Unity editor extension that allows users to test multiplayer gameplay without building the project by having another Unity editor window opened and mirror the changes from the original project. + +
+ +![ShortGif](https://raw.githubusercontent.com/VeriorPies/ParrelSync/master/Images/Showcase%201.gif) +

+Test project changes on clients and server within seconds - both in editor + +
+

+ +## Features +1. Test multiplayer gameplay without building the project +2. GUI tools for managing all project clones +3. Protected assets from being modified by other clone instances +4. Handy APIs to speed up testing workflows +## Installation + +1. Backup your project folder or use a version control system such as [Git](https://git-scm.com/) or [SVN](https://subversion.apache.org/) +2. Download .unitypackage from the [latest release](https://github.com/VeriorPies/ParrelSync/releases) and import it to your project. +3. ParrelSync should appreared in the menu item bar after imported +![UpdateButtonInMenu](https://github.com/VeriorPies/ParrelSync/raw/master/Images/AfterImported.png) + +Check out the [Installation-and-Update](https://github.com/VeriorPies/ParrelSync/wiki/Installation-and-Update) page for more details. + +### UPM Package +ParrelSync can also be installed via UPM package. +After Unity 2019.3.4f1, Unity 2020.1a21, which support path query parameter of git package. You can install ParrelSync by adding the following to Package Manager. + +``` +https://github.com/VeriorPies/ParrelSync.git?path=/ParrelSync +``` + + +![UPM_Image](https://github.com/VeriorPies/ParrelSync/raw/master/Images/UPM_1.png?raw=true) ![UPM_Image2](https://github.com/VeriorPies/ParrelSync/raw/master/Images/UPM_2.png?raw=true) + +or by adding + +``` +"com.veriorpies.parrelsync": "https://github.com/VeriorPies/ParrelSync.git?path=/ParrelSync" +``` + +to the `Packages/manifest.json` file + + +## Supported Platform +Currently, ParrelSync supports Windows, macOS and Linux editors. + +ParrelSync has been tested with the following Unity version. However, it should also work with other versions as well. +* *2020.3.1f1* +* *2019.3.0f6* +* *2018.4.22f1* + + +## APIs +There's some useful APIs for speeding up the multiplayer testing workflow. +Here's a basic example: +``` +if (ClonesManager.IsClone()) { + // Automatically connect to local host if this is the clone editor +}else{ + // Automatically start server if this is the original editor +} +``` +Check out [the doc](https://github.com/VeriorPies/ParrelSync/wiki/List-of-APIs) to view the complete API list. + +## How does it work? +For each clone instance, ParrelSync will make a copy of the original project folder and reference the ```Asset```, ```Packages``` and ```ProjectSettings``` folder back to the original project with [symbolic link](https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/mklink). Other folders such as ```Library```, ```Temp```, and ```obj``` will remain independent for each clone project. + +All clones are placed right next to the original project with suffix *```_clone_x```*, which will be something like this in the folder hierarchy. +``` +/ProjectName +/ProjectName_clone_0 +/ProjectName_clone_1 +... +``` +## Discord Server +We have a [Discord server](https://discord.gg/TmQk2qG). + +## Need Help? +Some common questions and troubleshooting can be found under the [Troubleshooting & FAQs](https://github.com/VeriorPies/ParrelSync/wiki/Troubleshooting-&-FAQs) page. +You can also [create a question post](https://github.com/VeriorPies/ParrelSync/issues/new/choose), or ask on [Discord](https://discord.gg/TmQk2qG) if you prefer to have a real-time conversation. + +## Support this project +A star will be appreciated :) + +## Credits +This project is originated from hwaet's [UnityProjectCloner](https://github.com/hwaet/UnityProjectCloner) diff --git a/Assets/ParrelSync/README.md.meta b/Assets/ParrelSync/README.md.meta new file mode 100755 index 000000000..5bc8ca582 --- /dev/null +++ b/Assets/ParrelSync/README.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: fa3f2fa6aced9b54b970c8996957df3b +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ParrelSync/VERSION.txt b/Assets/ParrelSync/VERSION.txt new file mode 100755 index 000000000..8e03717dc --- /dev/null +++ b/Assets/ParrelSync/VERSION.txt @@ -0,0 +1 @@ +1.5.1 \ No newline at end of file diff --git a/Assets/ParrelSync/VERSION.txt.meta b/Assets/ParrelSync/VERSION.txt.meta new file mode 100755 index 000000000..52f8b6bde --- /dev/null +++ b/Assets/ParrelSync/VERSION.txt.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 9833b240625d4e14995c296b87e34b96 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/manifest.json b/Packages/manifest.json index 04e067792..1033e1c1b 100644 --- a/Packages/manifest.json +++ b/Packages/manifest.json @@ -2,9 +2,12 @@ "dependencies": { "com.unity.2d.sprite": "1.0.0", "com.unity.2d.tilemap": "1.0.0", - "com.unity.ide.rider": "3.0.24", - "com.unity.ide.visualstudio": "2.0.18", + "com.unity.ai.navigation": "1.1.5", + "com.unity.ide.rider": "3.0.26", + "com.unity.ide.visualstudio": "2.0.22", "com.unity.ide.vscode": "1.2.5", + "com.unity.nuget.newtonsoft-json": "3.2.1", + "com.unity.profiling.core": "1.0.2", "com.unity.test-framework": "1.1.33", "com.unity.testtools.codecoverage": "1.2.4", "com.unity.toolchain.macos-arm64-linux-x86_64": "1.0.1", @@ -40,8 +43,7 @@ "com.unity.modules.video": "1.0.0", "com.unity.modules.vr": "1.0.0", "com.unity.modules.wind": "1.0.0", - "com.unity.modules.xr": "1.0.0", - "com.unity.nuget.newtonsoft-json": "3.2.1" + "com.unity.modules.xr": "1.0.0" }, "testables": [ "com.unity.test-framework.performance" diff --git a/Packages/packages-lock.json b/Packages/packages-lock.json index d28d9a02e..89446013f 100644 --- a/Packages/packages-lock.json +++ b/Packages/packages-lock.json @@ -10,7 +10,19 @@ "version": "1.0.0", "depth": 0, "source": "builtin", - "dependencies": {} + "dependencies": { + "com.unity.modules.tilemap": "1.0.0", + "com.unity.modules.uielements": "1.0.0" + } + }, + "com.unity.ai.navigation": { + "version": "1.1.5", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.modules.ai": "1.0.0" + }, + "url": "https://packages.unity.com" }, "com.unity.ext.nunit": { "version": "1.0.6", @@ -20,7 +32,7 @@ "url": "https://packages.unity.com" }, "com.unity.ide.rider": { - "version": "3.0.24", + "version": "3.0.26", "depth": 0, "source": "registry", "dependencies": { @@ -29,7 +41,7 @@ "url": "https://packages.unity.com" }, "com.unity.ide.visualstudio": { - "version": "2.0.18", + "version": "2.0.22", "depth": 0, "source": "registry", "dependencies": { @@ -51,8 +63,15 @@ "dependencies": {}, "url": "https://packages.unity.com" }, + "com.unity.profiling.core": { + "version": "1.0.2", + "depth": 0, + "source": "registry", + "dependencies": {}, + "url": "https://packages.unity.com" + }, "com.unity.settings-manager": { - "version": "1.0.3", + "version": "2.0.1", "depth": 1, "source": "registry", "dependencies": {}, @@ -256,17 +275,6 @@ "version": "1.0.0", "depth": 0, "source": "builtin", - "dependencies": { - "com.unity.modules.ui": "1.0.0", - "com.unity.modules.imgui": "1.0.0", - "com.unity.modules.jsonserialize": "1.0.0", - "com.unity.modules.uielementsnative": "1.0.0" - } - }, - "com.unity.modules.uielementsnative": { - "version": "1.0.0", - "depth": 1, - "source": "builtin", "dependencies": { "com.unity.modules.ui": "1.0.0", "com.unity.modules.imgui": "1.0.0", diff --git a/ProjectSettings/EditorBuildSettings.asset b/ProjectSettings/EditorBuildSettings.asset index 0147887ef..581715eca 100644 --- a/ProjectSettings/EditorBuildSettings.asset +++ b/ProjectSettings/EditorBuildSettings.asset @@ -4,5 +4,14 @@ EditorBuildSettings: m_ObjectHideFlags: 0 serializedVersion: 2 - m_Scenes: [] + m_Scenes: + - enabled: 1 + path: Assets/Mirror/Examples/CharacterSelection/MirrorCharacterSelectionPreScene.unity + guid: 7d4f83306961f4c03ab43038a1801ab2 + - enabled: 1 + path: Assets/Mirror/Examples/CharacterSelection/MirrorCharacterSelectionNoCharacter.unity + guid: a8a73128beb4248aca152ab0364c5abc + - enabled: 1 + path: Assets/Mirror/Examples/CharacterSelection/MirrorCharacterSelection.unity + guid: 9619a16fe963d4deb9b5821f4475c606 m_configObjects: {} diff --git a/ProjectSettings/ProjectSettings.asset b/ProjectSettings/ProjectSettings.asset index 0a2d4cfea..8d93c5758 100644 --- a/ProjectSettings/ProjectSettings.asset +++ b/ProjectSettings/ProjectSettings.asset @@ -3,7 +3,7 @@ --- !u!129 &1 PlayerSettings: m_ObjectHideFlags: 0 - serializedVersion: 24 + serializedVersion: 20 productGUID: bb0f7d422b91e4aab8e0fe9838a26db0 AndroidProfiler: 0 AndroidFilterTouchesWhenObscured: 0 @@ -49,8 +49,6 @@ PlayerSettings: m_StereoRenderingPath: 0 m_ActiveColorSpace: 0 m_MTRendering: 1 - mipStripping: 0 - numberOfMipsStripped: 0 m_StackTraceTypes: 010000000100000001000000010000000100000001000000 iosShowActivityIndicatorOnLoading: -1 androidShowActivityIndicatorOnLoading: -1 @@ -125,9 +123,7 @@ PlayerSettings: stadiaTargetFramerate: 0 vulkanNumSwapchainBuffers: 3 vulkanEnableSetSRGBWrite: 0 - vulkanEnablePreTransform: 0 vulkanEnableLateAcquireNextImage: 0 - vulkanEnableCommandBufferRecycling: 1 m_SupportedAspectRatios: 4:3: 1 5:4: 1 @@ -142,25 +138,48 @@ PlayerSettings: xboxOneDisableKinectGpuReservation: 0 xboxOneEnable7thCore: 0 vrSettings: + cardboard: + depthFormat: 0 + enableTransitionView: 0 + daydream: + depthFormat: 0 + useSustainedPerformanceMode: 0 + enableVideoLayer: 0 + useProtectedVideoMemory: 0 + minimumSupportedHeadTracking: 0 + maximumSupportedHeadTracking: 1 + hololens: + depthFormat: 1 + depthBufferSharingEnabled: 1 + lumin: + depthFormat: 0 + frameTiming: 2 + enableGLCache: 0 + glCacheMaxBlobSize: 524288 + glCacheMaxFileSize: 8388608 + oculus: + sharedDepthBuffer: 1 + dashSupport: 1 + lowOverheadMode: 0 + protectedContext: 0 + v2Signing: 1 enable360StereoCapture: 0 isWsaHolographicRemotingEnabled: 0 enableFrameTimingStats: 0 - enableOpenGLProfilerGPURecorders: 1 useHDRDisplay: 0 D3DHDRBitDepth: 0 m_ColorGamuts: 00000000 targetPixelDensity: 30 resolutionScalingMode: 0 - resetResolutionOnWindowResize: 0 androidSupportedAspectRatio: 1 androidMaxAspectRatio: 2.1 applicationIdentifier: Standalone: com.Mirror.Mirror buildNumber: Standalone: 0 + VisionOS: 0 iPhone: 0 tvOS: 0 - overrideDefaultApplicationIdentifier: 0 AndroidBundleVersionCode: 1 AndroidMinSdkVersion: 22 AndroidTargetSdkVersion: 0 @@ -214,11 +233,10 @@ PlayerSettings: iOSLaunchScreeniPadFillPct: 100 iOSLaunchScreeniPadSize: 100 iOSLaunchScreeniPadCustomXibPath: + iOSUseLaunchScreenStoryboard: 0 iOSLaunchScreenCustomStoryboardPath: - iOSLaunchScreeniPadCustomStoryboardPath: iOSDeviceRequirements: [] iOSURLSchemes: [] - macOSURLSchemes: [] iOSBackgroundModes: 0 iOSMetalForceHardShadows: 0 metalEditorSupport: 1 @@ -234,17 +252,9 @@ PlayerSettings: iOSRequireARKit: 0 iOSAutomaticallyDetectAndAddCapabilities: 1 appleEnableProMotion: 0 - shaderPrecisionModel: 0 clonedFromGUID: 00000000000000000000000000000000 templatePackageId: templateDefaultScene: - useCustomMainManifest: 0 - useCustomLauncherManifest: 0 - useCustomMainGradleTemplate: 0 - useCustomLauncherGradleManifest: 0 - useCustomBaseGradleTemplate: 0 - useCustomGradlePropertiesTemplate: 0 - useCustomProguardFile: 0 AndroidTargetArchitectures: 5 AndroidTargetDevices: 0 AndroidSplashScreenScale: 0 @@ -264,15 +274,200 @@ PlayerSettings: banner: {fileID: 0} androidGamepadSupportLevel: 0 chromeosInputEmulation: 1 - AndroidMinifyWithR8: 0 - AndroidMinifyRelease: 0 - AndroidMinifyDebug: 0 AndroidValidateAppBundleSize: 1 AndroidAppBundleSizeToValidate: 150 m_BuildTargetIcons: [] - m_BuildTargetPlatformIcons: [] + m_BuildTargetPlatformIcons: + - m_BuildTarget: iPhone + m_Icons: + - m_Textures: [] + m_Width: 180 + m_Height: 180 + m_Kind: 0 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 120 + m_Height: 120 + m_Kind: 0 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 167 + m_Height: 167 + m_Kind: 0 + m_SubKind: iPad + - m_Textures: [] + m_Width: 152 + m_Height: 152 + m_Kind: 0 + m_SubKind: iPad + - m_Textures: [] + m_Width: 76 + m_Height: 76 + m_Kind: 0 + m_SubKind: iPad + - m_Textures: [] + m_Width: 120 + m_Height: 120 + m_Kind: 3 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 80 + m_Height: 80 + m_Kind: 3 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 80 + m_Height: 80 + m_Kind: 3 + m_SubKind: iPad + - m_Textures: [] + m_Width: 40 + m_Height: 40 + m_Kind: 3 + m_SubKind: iPad + - m_Textures: [] + m_Width: 87 + m_Height: 87 + m_Kind: 1 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 58 + m_Height: 58 + m_Kind: 1 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 29 + m_Height: 29 + m_Kind: 1 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 58 + m_Height: 58 + m_Kind: 1 + m_SubKind: iPad + - m_Textures: [] + m_Width: 29 + m_Height: 29 + m_Kind: 1 + m_SubKind: iPad + - m_Textures: [] + m_Width: 60 + m_Height: 60 + m_Kind: 2 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 40 + m_Height: 40 + m_Kind: 2 + m_SubKind: iPhone + - m_Textures: [] + m_Width: 40 + m_Height: 40 + m_Kind: 2 + m_SubKind: iPad + - m_Textures: [] + m_Width: 20 + m_Height: 20 + m_Kind: 2 + m_SubKind: iPad + - m_Textures: [] + m_Width: 1024 + m_Height: 1024 + m_Kind: 4 + m_SubKind: App Store + - m_BuildTarget: Android + m_Icons: + - m_Textures: [] + m_Width: 432 + m_Height: 432 + m_Kind: 2 + m_SubKind: + - m_Textures: [] + m_Width: 324 + m_Height: 324 + m_Kind: 2 + m_SubKind: + - m_Textures: [] + m_Width: 216 + m_Height: 216 + m_Kind: 2 + m_SubKind: + - m_Textures: [] + m_Width: 162 + m_Height: 162 + m_Kind: 2 + m_SubKind: + - m_Textures: [] + m_Width: 108 + m_Height: 108 + m_Kind: 2 + m_SubKind: + - m_Textures: [] + m_Width: 81 + m_Height: 81 + m_Kind: 2 + m_SubKind: + - m_Textures: [] + m_Width: 192 + m_Height: 192 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 144 + m_Height: 144 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 96 + m_Height: 96 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 72 + m_Height: 72 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 48 + m_Height: 48 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 36 + m_Height: 36 + m_Kind: 1 + m_SubKind: + - m_Textures: [] + m_Width: 192 + m_Height: 192 + m_Kind: 0 + m_SubKind: + - m_Textures: [] + m_Width: 144 + m_Height: 144 + m_Kind: 0 + m_SubKind: + - m_Textures: [] + m_Width: 96 + m_Height: 96 + m_Kind: 0 + m_SubKind: + - m_Textures: [] + m_Width: 72 + m_Height: 72 + m_Kind: 0 + m_SubKind: + - m_Textures: [] + m_Width: 48 + m_Height: 48 + m_Kind: 0 + m_SubKind: + - m_Textures: [] + m_Width: 36 + m_Height: 36 + m_Kind: 0 + m_SubKind: m_BuildTargetBatching: [] - m_BuildTargetShaderSettings: [] m_BuildTargetGraphicsJobs: - m_BuildTarget: MacStandaloneSupport m_GraphicsJobs: 0 @@ -315,8 +510,6 @@ PlayerSettings: m_APIs: 0b00000008000000 m_Automatic: 0 m_BuildTargetVRSettings: [] - m_DefaultShaderChunkSizeInMB: 16 - m_DefaultShaderChunkCount: 0 openGLRequireES31: 0 openGLRequireES31AEP: 0 openGLRequireES32: 0 @@ -327,8 +520,6 @@ PlayerSettings: tvOS: 1 m_BuildTargetGroupLightmapEncodingQuality: [] m_BuildTargetGroupLightmapSettings: [] - m_BuildTargetNormalMapEncoding: [] - m_BuildTargetDefaultTextureCompressionFormat: [] playModeTestRunnerEnabled: 0 runPlayModeTestAsEditModeTest: 0 actionOnDotNetUnhandledException: 1 @@ -338,16 +529,12 @@ PlayerSettings: cameraUsageDescription: locationUsageDescription: microphoneUsageDescription: - bluetoothUsageDescription: - switchNMETAOverride: switchNetLibKey: switchSocketMemoryPoolSize: 6144 switchSocketAllocatorPoolSize: 128 switchSocketConcurrencyLimit: 14 switchScreenResolutionBehavior: 2 switchUseCPUProfiler: 0 - switchUseGOLDLinker: 0 - switchLTOSetting: 0 switchApplicationID: 0x01004b9000490000 switchNSODependencies: switchTitleNames_0: @@ -423,6 +610,7 @@ PlayerSettings: switchReleaseVersion: 0 switchDisplayVersion: 1.0.0 switchStartupUserAccount: 0 + switchTouchScreenUsage: 0 switchSupportedLanguagesMask: 0 switchLogoType: 0 switchApplicationErrorCodeCategory: @@ -464,7 +652,6 @@ PlayerSettings: switchNativeFsCacheSize: 32 switchIsHoldTypeHorizontal: 0 switchSupportedNpadCount: 8 - switchEnableTouchScreen: 1 switchSocketConfigEnabled: 0 switchTcpInitialSendBufferSize: 32 switchTcpInitialReceiveBufferSize: 64 @@ -476,8 +663,6 @@ PlayerSettings: switchSocketInitializeEnabled: 1 switchNetworkInterfaceManagerInitializeEnabled: 1 switchPlayerConnectionEnabled: 1 - switchUseNewStyleFilepaths: 0 - switchUseLegacyFmodPriorities: 1 switchUseMicroSleepForYield: 1 switchEnableRamDiskSupport: 0 switchMicroSleepForYieldTime: 25 @@ -557,6 +742,32 @@ PlayerSettings: ps4attribEyeToEyeDistanceSettingVR: 0 ps4IncludedModules: [] ps4attribVROutputEnabled: 0 + ps5ParamFilePath: + ps5VideoOutPixelFormat: 0 + ps5VideoOutInitialWidth: 1920 + ps5VideoOutOutputMode: 1 + ps5BackgroundImagePath: + ps5StartupImagePath: + ps5Pic2Path: + ps5StartupImagesFolder: + ps5IconImagesFolder: + ps5SaveDataImagePath: + ps5SdkOverride: + ps5BGMPath: + ps5ShareOverlayImagePath: + ps5NPConfigZipPath: + ps5Passcode: N2qmWqBlQ9wQj99nsQzldVI5ZuGXbEWR + ps5UseResolutionFallback: 0 + ps5UseAudio3dBackend: 0 + ps5ScriptOptimizationLevel: 2 + ps5Audio3dVirtualSpeakerCount: 14 + ps5VrrSupport: 0 + ps5UpdateReferencePackage: + ps5disableAutoHideSplash: 0 + ps5OperatingSystemCanDisableSplashScreen: 0 + ps5IncludedModules: [] + ps5SharedBinaryContentLabels: [] + ps5SharedBinarySystemFolders: [] monoEnv: splashScreenBackgroundSourceLandscape: {fileID: 0} splashScreenBackgroundSourcePortrait: {fileID: 0} @@ -573,16 +784,12 @@ PlayerSettings: webGLAnalyzeBuildSize: 0 webGLUseEmbeddedResources: 0 webGLCompressionFormat: 1 - webGLWasmArithmeticExceptions: 0 webGLLinkerTarget: 1 webGLThreadsSupport: 0 - webGLDecompressionFallback: 0 - webGLPowerPreference: 2 + webGLWasmStreaming: 0 scriptingDefineSymbols: - Server: MIRROR;MIRROR_70_OR_NEWER;MIRROR_71_OR_NEWER;MIRROR_73_OR_NEWER;MIRROR_78_OR_NEWER;MIRROR_79_OR_NEWER;MIRROR_81_OR_NEWER;MIRROR_82_OR_NEWER;MIRROR_83_OR_NEWER;MIRROR_84_OR_NEWER;MIRROR_85_OR_NEWER - Standalone: MIRROR;MIRROR_70_OR_NEWER;MIRROR_71_OR_NEWER;MIRROR_73_OR_NEWER;MIRROR_78_OR_NEWER;MIRROR_79_OR_NEWER;MIRROR_81_OR_NEWER;MIRROR_82_OR_NEWER;MIRROR_83_OR_NEWER;MIRROR_84_OR_NEWER;MIRROR_85_OR_NEWER;MIRROR_86_OR_NEWER - WebGL: MIRROR;MIRROR_70_OR_NEWER;MIRROR_71_OR_NEWER;MIRROR_73_OR_NEWER;MIRROR_78_OR_NEWER;MIRROR_79_OR_NEWER;MIRROR_81_OR_NEWER;MIRROR_82_OR_NEWER;MIRROR_83_OR_NEWER;MIRROR_84_OR_NEWER;MIRROR_85_OR_NEWER - additionalCompilerArguments: {} + 0: MIRROR;MIRROR_70_OR_NEWER;MIRROR_71_OR_NEWER;MIRROR_73_OR_NEWER;MIRROR_78_OR_NEWER;MIRROR_79_OR_NEWER;MIRROR_81_OR_NEWER;MIRROR_82_OR_NEWER;MIRROR_83_OR_NEWER;MIRROR_84_OR_NEWER;MIRROR_85_OR_NEWER + 1: MIRROR;MIRROR_70_OR_NEWER;MIRROR_71_OR_NEWER;MIRROR_73_OR_NEWER;MIRROR_78_OR_NEWER;MIRROR_79_OR_NEWER;MIRROR_81_OR_NEWER;MIRROR_82_OR_NEWER;MIRROR_83_OR_NEWER;MIRROR_84_OR_NEWER;MIRROR_85_OR_NEWER;MIRROR_86_OR_NEWER platformArchitecture: {} scriptingBackend: Standalone: 1 @@ -605,8 +812,6 @@ PlayerSettings: incrementalIl2cppBuild: {} suppressCommonWarnings: 1 allowUnsafeCode: 0 - useDeterministicCompilation: 1 - enableRoslynAnalyzers: 1 additionalIl2CppArgs: scriptingRuntimeVersion: 1 gcIncremental: 0 @@ -643,7 +848,6 @@ PlayerSettings: metroFTAName: metroFTAFileTypes: [] metroProtocolName: - vcxProjDefaultLanguage: XboxOneProductId: XboxOneUpdateKey: XboxOneSandboxId: @@ -671,7 +875,10 @@ PlayerSettings: XboxOneXTitleMemory: 8 XboxOneOverrideIdentityName: XboxOneOverrideIdentityPublisher: - vrEditorSettings: {} + vrEditorSettings: + daydream: + daydreamIconForeground: {fileID: 0} + daydreamIconBackground: {fileID: 0} cloudServicesEnabled: {} luminIcon: m_Name: @@ -685,15 +892,11 @@ PlayerSettings: m_VersionCode: 1 m_VersionName: apiCompatibilityLevel: 3 - activeInputHandler: 0 - windowsGamepadBackendHint: 0 cloudProjectId: framebufferDepthMemorylessMode: 0 - qualitySettingsNames: [] projectName: organizationId: cloudEnabled: 0 + enableNativePlatformBackendsForNewInputSystem: 0 + disableOldInputManagerSupport: 0 legacyClampBlendShapeWeights: 1 - playerDataPath: - forceSRGBBlit: 1 - virtualTexturingSupportEnabled: 0 diff --git a/ProjectSettings/ProjectVersion.txt b/ProjectSettings/ProjectVersion.txt index db3863a92..91206fdc4 100644 --- a/ProjectSettings/ProjectVersion.txt +++ b/ProjectSettings/ProjectVersion.txt @@ -1,2 +1,2 @@ -m_EditorVersion: 2021.3.29f1 -m_EditorVersionWithRevision: 2021.3.29f1 (204d6dc9ae1c) +m_EditorVersion: 2022.3.13f1 +m_EditorVersionWithRevision: 2022.3.13f1 (5f90a5ebde0f) diff --git a/ProjectSettings/boot.config b/ProjectSettings/boot.config deleted file mode 100644 index e69de29bb..000000000