├── EntitiesExtensions.meta ├── EntitiesExtensions ├── EntitiesReference.asmref ├── EntitiesReference.asmref.meta ├── UntypedAccessExtensionMethods.cs └── UntypedAccessExtensionMethods.cs.meta ├── LICENSE.MD ├── LICENSE.MD.meta ├── PackageManual.md ├── PackageManual.md.meta ├── QuickSave.Baking.meta ├── QuickSave.Baking ├── QuickSaveBaker.cs ├── QuickSaveBaker.cs.meta ├── QuickSaveBakingSystem.cs ├── QuickSaveBakingSystem.cs.meta ├── com.studioaurelius.quicksave.baking.asmdef └── com.studioaurelius.quicksave.baking.asmdef.meta ├── QuickSave.Containers.meta ├── QuickSave.Containers ├── TypeHandleArray.cs ├── TypeHandleArray.cs.meta ├── com.studioaurelius.quicksave.containers.asmdef └── com.studioaurelius.quicksave.containers.asmdef.meta ├── QuickSave.Editor.meta ├── QuickSave.Editor ├── FindTypeWindow.cs ├── FindTypeWindow.cs.meta ├── QuickSaveArchetypeCollectionInspector.cs ├── QuickSaveArchetypeCollectionInspector.cs.meta ├── QuickSaveAuthoringInspector.cs ├── QuickSaveAuthoringInspector.cs.meta ├── QuickSaveSettingsAssetInspector.cs ├── QuickSaveSettingsAssetInspector.cs.meta ├── QuickSaveSubSceneInspector.cs ├── QuickSaveSubSceneInspector.cs.meta ├── com.studioaurelius.quicksave.editor.asmdef └── com.studioaurelius.quicksave.editor.asmdef.meta ├── QuickSave.Tests.meta ├── QuickSave.Tests ├── BufferDataTests.cs ├── BufferDataTests.cs.meta ├── ComponentDataTests.cs ├── ComponentDataTests.cs.meta ├── ECSTestsFixture.cs ├── ECSTestsFixture.cs.meta ├── EnableDataTests.cs ├── EnableDataTests.cs.meta ├── SystemTests.cs ├── SystemTests.cs.meta ├── com.studioaurelius.quicksave.tests.asmdef └── com.studioaurelius.quicksave.tests.asmdef.meta ├── QuickSave.meta ├── QuickSave ├── DataLayoutHelpers.cs ├── DataLayoutHelpers.cs.meta ├── DefaultQuickSaveSerialization.cs ├── DefaultQuickSaveSerialization.cs.meta ├── IndexInContainer.cs ├── IndexInContainer.cs.meta ├── QuickSaveAPI.cs ├── QuickSaveAPI.cs.meta ├── QuickSaveArchetypeCollection.cs ├── QuickSaveArchetypeCollection.cs.meta ├── QuickSaveAuthoring.cs ├── QuickSaveAuthoring.cs.meta ├── QuickSaveBeginFrameSystem.cs ├── QuickSaveBeginFrameSystem.cs.meta ├── QuickSaveDataContainer.cs ├── QuickSaveDataContainer.cs.meta ├── QuickSaveEndFrameSystem.cs ├── QuickSaveEndFrameSystem.cs.meta ├── QuickSaveGroupedJobs.cs ├── QuickSaveGroupedJobs.cs.meta ├── QuickSaveJobs.cs ├── QuickSaveJobs.cs.meta ├── QuickSaveSceneComponents.cs ├── QuickSaveSceneComponents.cs.meta ├── QuickSaveSceneSystem.cs ├── QuickSaveSceneSystem.cs.meta ├── QuickSaveSettings.cs ├── QuickSaveSettings.cs.meta ├── QuickSaveSettingsAsset.cs ├── QuickSaveSettingsAsset.cs.meta ├── QuickSaveSubScene.cs ├── QuickSaveSubScene.cs.meta ├── QuickSaveSystemBase.cs ├── QuickSaveSystemBase.cs.meta ├── QuickSaveTypeHandle.cs ├── QuickSaveTypeHandle.cs.meta ├── QuickSaveUnloadSceneSystem.cs ├── QuickSaveUnloadSceneSystem.cs.meta ├── com.studioaurelius.quicksave.asmdef └── com.studioaurelius.quicksave.asmdef.meta ├── README.md ├── README.md.meta ├── package.json └── package.json.meta /EntitiesExtensions.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: df0431d6f7262da4b992770e21bb81cf 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /EntitiesExtensions/EntitiesReference.asmref: -------------------------------------------------------------------------------- 1 | { 2 | "reference": "GUID:734d92eba21c94caba915361bd5ac177" 3 | } -------------------------------------------------------------------------------- /EntitiesExtensions/EntitiesReference.asmref.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f441bc71952ae524eb4cec9af0c167b5 3 | AssemblyDefinitionReferenceImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /EntitiesExtensions/UntypedAccessExtensionMethods.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Runtime.CompilerServices; 4 | using Unity.Burst.CompilerServices; 5 | using Unity.Collections; 6 | using Unity.Collections.LowLevel.Unsafe; 7 | using Unity.Entities; 8 | 9 | namespace QuickSave 10 | { 11 | public static class UntypedAccessExtensionMethods 12 | { 13 | [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] 14 | public static void DisableSafetyChecks(ref DynamicComponentTypeHandle chunkComponentTypeHandle) 15 | { 16 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 17 | chunkComponentTypeHandle.m_Safety0 = AtomicSafetyHandle.Create(); 18 | chunkComponentTypeHandle.m_Safety1 = AtomicSafetyHandle.Create(); 19 | #endif 20 | } 21 | 22 | [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] 23 | private static void CheckZeroSizedComponentData(DynamicComponentTypeHandle chunkComponentTypeHandle) 24 | { 25 | if (Hint.Unlikely(chunkComponentTypeHandle.IsZeroSized)) 26 | throw new ArgumentException($"ArchetypeChunk.GetComponentDataAsByteArray cannot be called on zero-sized IComponentData"); 27 | } 28 | 29 | // based on ArchetypeChunk::GetEnabledMask 30 | public static unsafe EnabledMask GetEnabledMask(this in ArchetypeChunk archetypeChunk, ref DynamicComponentTypeHandle chunkComponentTypeHandle) 31 | { 32 | var m_Chunk = archetypeChunk.m_Chunk; 33 | ChunkDataUtility.GetIndexInTypeArray(archetypeChunk.Archetype.Archetype, chunkComponentTypeHandle.m_TypeIndex, ref chunkComponentTypeHandle.m_TypeLookupCache); 34 | var indexInArchetypeTypeArray = chunkComponentTypeHandle.m_TypeLookupCache; 35 | 36 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 37 | AtomicSafetyHandle.CheckReadAndThrow(chunkComponentTypeHandle.m_Safety0); 38 | #endif 39 | // if (Hint.Unlikely(chunkComponentTypeHandle.m_LookupCache.Archetype != m_Chunk->Archetype)) 40 | // { 41 | // chunkComponentTypeHandle.m_LookupCache.Update(m_Chunk->Archetype, chunkComponentTypeHandle.m_TypeIndex); 42 | // } 43 | 44 | // In case the chunk does not contains the component type (or the internal TypeIndex lookup fails to find a 45 | // match), the LookupCache.Update will invalidate the IndexInArchetype. 46 | // In such a case, we return an empty EnabledMask. 47 | if (Hint.Unlikely(indexInArchetypeTypeArray == -1)) 48 | { 49 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 50 | return new EnabledMask(new SafeBitRef(null, 0, chunkComponentTypeHandle.m_Safety0), null); 51 | #else 52 | return new EnabledMask(SafeBitRef.Null, null); 53 | #endif 54 | } 55 | int* ptrChunkDisabledCount = default; 56 | var ptr = (chunkComponentTypeHandle.IsReadOnly) 57 | ? ChunkDataUtility.GetEnabledRefRO(m_Chunk, indexInArchetypeTypeArray).Ptr 58 | : ChunkDataUtility.GetEnabledRefRW(m_Chunk, indexInArchetypeTypeArray, 59 | chunkComponentTypeHandle.GlobalSystemVersion, out ptrChunkDisabledCount).Ptr; 60 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 61 | var result = new EnabledMask(new SafeBitRef(ptr, 0, chunkComponentTypeHandle.m_Safety0), ptrChunkDisabledCount); 62 | #else 63 | var result = new EnabledMask(new SafeBitRef(ptr, 0), ptrChunkDisabledCount); 64 | #endif 65 | return result; 66 | } 67 | 68 | // based on ArchetypeChunk::GetDynamicComponentDataArrayReinterpret 69 | public static unsafe NativeArray GetComponentDataAsByteArray(this in ArchetypeChunk archetypeChunk, ref DynamicComponentTypeHandle chunkComponentType) 70 | { 71 | CheckZeroSizedComponentData(chunkComponentType); 72 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 73 | AtomicSafetyHandle.CheckReadAndThrow(chunkComponentType.m_Safety0); 74 | #endif 75 | var chunk = archetypeChunk.m_Chunk; 76 | var archetype = chunk->Archetype; 77 | ChunkDataUtility.GetIndexInTypeArray(chunk->Archetype, chunkComponentType.m_TypeIndex, ref chunkComponentType.m_TypeLookupCache); 78 | var typeIndexInArchetype = chunkComponentType.m_TypeLookupCache; 79 | if (typeIndexInArchetype == -1) 80 | { 81 | var emptyResult = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray(null, 0, 0); 82 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 83 | NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref emptyResult, chunkComponentType.m_Safety0); 84 | #endif 85 | return emptyResult; 86 | } 87 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 88 | if (archetype->Types[typeIndexInArchetype].IsBuffer) 89 | throw new ArgumentException($"ArchetypeChunk.GetComponentDataAsByteArray cannot be called for IBufferElementData {TypeManager.GetType(chunkComponentType.m_TypeIndex)}"); 90 | #endif 91 | 92 | var typeSize = archetype->SizeOfs[typeIndexInArchetype]; 93 | var length = archetypeChunk.Count; 94 | var byteLen = length * typeSize; 95 | // var outTypeSize = 1; 96 | // var outLength = byteLen / outTypeSize; 97 | 98 | byte* ptr = (chunkComponentType.IsReadOnly) 99 | ? ChunkDataUtility.GetComponentDataRO(chunk, 0, typeIndexInArchetype) 100 | : ChunkDataUtility.GetComponentDataRW(chunk, 0, typeIndexInArchetype, chunkComponentType.GlobalSystemVersion); 101 | 102 | var result = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray(ptr, byteLen, Allocator.None); 103 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 104 | NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref result, chunkComponentType.m_Safety0); 105 | #endif 106 | 107 | #if (UNITY_EDITOR || DEVELOPMENT_BUILD) && !DISABLE_ENTITIES_JOURNALING 108 | if (Hint.Unlikely(archetypeChunk.m_EntityComponentStore->m_RecordToJournal != 0) && !chunkComponentType.IsReadOnly) 109 | JournalAddRecordGetComponentDataRW(archetypeChunk, ref chunkComponentType, ptr, byteLen); 110 | #endif 111 | return result; 112 | } 113 | 114 | #if (UNITY_EDITOR || DEVELOPMENT_BUILD) && !DISABLE_ENTITIES_JOURNALING 115 | [MethodImpl(MethodImplOptions.NoInlining)] 116 | private static unsafe void JournalAddRecord(in ArchetypeChunk thisArchetypeChunk, EntitiesJournaling.RecordType recordType, TypeIndex typeIndex, uint globalSystemVersion, void* data = null, int dataLength = 0) 117 | { 118 | fixed (ArchetypeChunk* archetypeChunk = &thisArchetypeChunk) 119 | { 120 | EntitiesJournaling.AddRecord( 121 | recordType: recordType, 122 | entityComponentStore: archetypeChunk->m_EntityComponentStore, 123 | globalSystemVersion: globalSystemVersion, 124 | chunks: archetypeChunk, 125 | chunkCount: 1, 126 | types: &typeIndex, 127 | typeCount: 1, 128 | data: data, 129 | dataLength: dataLength); 130 | } 131 | } 132 | 133 | [MethodImpl(MethodImplOptions.NoInlining)] 134 | private static unsafe void JournalAddRecordGetComponentDataRW(in ArchetypeChunk thisArchetypeChunk, ref DynamicComponentTypeHandle typeHandle, void* data, int dataLength) => 135 | JournalAddRecord(in thisArchetypeChunk, EntitiesJournaling.RecordType.GetComponentDataRW, typeHandle.m_TypeIndex, typeHandle.m_GlobalSystemVersion, data, dataLength); 136 | #endif 137 | 138 | public static TypeIndex GetTypeIndex(this ref DynamicComponentTypeHandle componentTypeHandle) 139 | { 140 | return componentTypeHandle.m_TypeIndex; 141 | } 142 | 143 | public static unsafe void AddComponent(ref this EntityCommandBuffer.ParallelWriter ecb, int jobIndex, Entity e, ComponentType cType, int typeSize, NativeArray byteArray) 144 | { 145 | ecb.UnsafeAddComponent(jobIndex, e, cType.TypeIndex, typeSize, byteArray.GetUnsafeReadOnlyPtr()); 146 | } 147 | 148 | public static ref UnsafeList GetPendingBuffersRef(this EntityCommandBufferSystem ecbSystem) 149 | { 150 | return ref ecbSystem.PendingBuffers; 151 | } 152 | } 153 | } -------------------------------------------------------------------------------- /EntitiesExtensions/UntypedAccessExtensionMethods.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 29f16881c57694246b6f647e8486e6d9 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /LICENSE.MD: -------------------------------------------------------------------------------- 1 | 2 | ## Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International Public License 3 | 4 | By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International Public License ("Public License"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions. 5 | 6 | ### Section 1 – Definitions. 7 | 8 | a. __Adapted Material__ means material subject to Copyright and Similar Rights that is derived from or based upon the Licensed Material and in which the Licensed Material is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the Copyright and Similar Rights held by the Licensor. For purposes of this Public License, where the Licensed Material is a musical work, performance, or sound recording, Adapted Material is always produced where the Licensed Material is synched in timed relation with a moving image. 9 | 10 | b. __Copyright and Similar Rights__ means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights. 11 | 12 | e. __Effective Technological Measures__ means those measures that, in the absence of proper authority, may not be circumvented under laws fulfilling obligations under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, and/or similar international agreements. 13 | 14 | f. __Exceptions and Limitations__ means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Material. 15 | 16 | h. __Licensed Material__ means the artistic or literary work, database, or other material to which the Licensor applied this Public License. 17 | 18 | i. __Licensed Rights__ means the rights granted to You subject to the terms and conditions of this Public License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license. 19 | 20 | h. __Licensor__ means the individual(s) or entity(ies) granting rights under this Public License. 21 | 22 | i. __NonCommercial__ means not primarily intended for or directed towards commercial advantage or monetary compensation. For purposes of this Public License, the exchange of the Licensed Material for other material subject to Copyright and Similar Rights by digital file-sharing or similar means is NonCommercial provided there is no payment of monetary compensation in connection with the exchange. 23 | 24 | j. __Share__ means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them. 25 | 26 | k. __Sui Generis Database Rights__ means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world. 27 | 28 | l. __You__ means the individual or entity exercising the Licensed Rights under this Public License. Your has a corresponding meaning. 29 | 30 | ### Section 2 – Scope. 31 | 32 | a. ___License grant.___ 33 | 34 | 1. Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to: 35 | 36 | A. reproduce and Share the Licensed Material, in whole or in part, for NonCommercial purposes only; and 37 | 38 | B. produce and reproduce, but not Share, Adapted Material for NonCommercial purposes only. 39 | 40 | 2. __Exceptions and Limitations.__ For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions. 41 | 42 | 3. __Term.__ The term of this Public License is specified in Section 6(a). 43 | 44 | 4. __Media and formats; technical modifications allowed.__ The Licensor authorizes You to exercise the Licensed Rights in all media and formats whether now known or hereafter created, and to make technical modifications necessary to do so. The Licensor waives and/or agrees not to assert any right or authority to forbid You from making technical modifications necessary to exercise the Licensed Rights, including technical modifications necessary to circumvent Effective Technological Measures. For purposes of this Public License, simply making modifications authorized by this Section 2(a)(4) never produces Adapted Material. 45 | 46 | 5. __Downstream recipients.__ 47 | 48 | A. __Offer from the Licensor – Licensed Material.__ Every recipient of the Licensed Material automatically receives an offer from the Licensor to exercise the Licensed Rights under the terms and conditions of this Public License. 49 | 50 | B. __No downstream restrictions.__ You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, the Licensed Material if doing so restricts exercise of the Licensed Rights by any recipient of the Licensed Material. 51 | 52 | 6. __No endorsement.__ Nothing in this Public License constitutes or may be construed as permission to assert or imply that You are, or that Your use of the Licensed Material is, connected with, or sponsored, endorsed, or granted official status by, the Licensor or others designated to receive attribution as provided in Section 3(a)(1)(A)(i). 53 | 54 | b. ___Other rights.___ 55 | 56 | 1. Moral rights, such as the right of integrity, are not licensed under this Public License, nor are publicity, privacy, and/or other similar personality rights; however, to the extent possible, the Licensor waives and/or agrees not to assert any such rights held by the Licensor to the limited extent necessary to allow You to exercise the Licensed Rights, but not otherwise. 57 | 58 | 2. Patent and trademark rights are not licensed under this Public License. 59 | 60 | 3. To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties, including when the Licensed Material is used other than for NonCommercial purposes. 61 | 62 | ### Section 3 – License Conditions. 63 | 64 | Your exercise of the Licensed Rights is expressly made subject to the following conditions. 65 | 66 | a. ___Attribution.___ 67 | 68 | 1. If You Share the Licensed Material, You must: 69 | 70 | A. retain the following if it is supplied by the Licensor with the Licensed Material: 71 | 72 | i. identification of the creator(s) of the Licensed Material and any others designated to receive attribution, in any reasonable manner requested by the Licensor (including by pseudonym if designated); 73 | 74 | ii. a copyright notice; 75 | 76 | iii. a notice that refers to this Public License; 77 | 78 | iv. a notice that refers to the disclaimer of warranties; 79 | 80 | v. a URI or hyperlink to the Licensed Material to the extent reasonably practicable; 81 | 82 | B. indicate if You modified the Licensed Material and retain an indication of any previous modifications; and 83 | 84 | C. indicate the Licensed Material is licensed under this Public License, and include the text of, or the URI or hyperlink to, this Public License. 85 | 86 | For the avoidance of doubt, You do not have permission under this Public License to Share Adapted Material. 87 | 88 | 2. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner based on the medium, means, and context in which You Share the Licensed Material. For example, it may be reasonable to satisfy the conditions by providing a URI or hyperlink to a resource that includes the required information. 89 | 90 | 3. If requested by the Licensor, You must remove any of the information required by Section 3(a)(1)(A) to the extent reasonably practicable. 91 | 92 | ### Section 4 – Sui Generis Database Rights. 93 | 94 | Where the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material: 95 | 96 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database for NonCommercial purposes only and provided You do not Share Adapted Material; 97 | 98 | b. if You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Material; and 99 | 100 | c. You must comply with the conditions in Section 3(a) if You Share all or a substantial portion of the contents of the database. 101 | 102 | For the avoidance of doubt, this Section 4 supplements and does not replace Your obligations under this Public License where the Licensed Rights include other Copyright and Similar Rights. 103 | 104 | ### Section 5 – Disclaimer of Warranties and Limitation of Liability. 105 | 106 | a. __Unless otherwise separately undertaken by the Licensor, to the extent possible, the Licensor offers the Licensed Material as-is and as-available, and makes no representations or warranties of any kind concerning the Licensed Material, whether express, implied, statutory, or other. This includes, without limitation, warranties of title, merchantability, fitness for a particular purpose, non-infringement, absence of latent or other defects, accuracy, or the presence or absence of errors, whether or not known or discoverable. Where disclaimers of warranties are not allowed in full or in part, this disclaimer may not apply to You.__ 107 | 108 | b. __To the extent possible, in no event will the Licensor be liable to You on any legal theory (including, without limitation, negligence) or otherwise for any direct, special, indirect, incidental, consequential, punitive, exemplary, or other losses, costs, expenses, or damages arising out of this Public License or use of the Licensed Material, even if the Licensor has been advised of the possibility of such losses, costs, expenses, or damages. Where a limitation of liability is not allowed in full or in part, this limitation may not apply to You.__ 109 | 110 | c. The disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability. 111 | 112 | ### Section 6 – Term and Termination. 113 | 114 | a. This Public License applies for the term of the Copyright and Similar Rights licensed here. However, if You fail to comply with this Public License, then Your rights under this Public License terminate automatically. 115 | 116 | b. Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates: 117 | 118 | 1. automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or 119 | 120 | 2. upon express reinstatement by the Licensor. 121 | 122 | For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License. 123 | 124 | c. For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License. 125 | 126 | d. Sections 1, 5, 6, 7, and 8 survive termination of this Public License. 127 | 128 | ### Section 7 – Other Terms and Conditions. 129 | 130 | a. The Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed. 131 | 132 | b. Any arrangements, understandings, or agreements regarding the Licensed Material not stated herein are separate from and independent of the terms and conditions of this Public License. 133 | 134 | ### Section 8 – Interpretation. 135 | 136 | a. For the avoidance of doubt, this Public License does not, and shall not be interpreted to, reduce, limit, restrict, or impose conditions on any use of the Licensed Material that could lawfully be made without permission under this Public License. 137 | 138 | b. To the extent possible, if any provision of this Public License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this Public License without affecting the enforceability of the remaining terms and conditions. 139 | 140 | c. No term or condition of this Public License will be waived and no failure to comply consented to unless expressly agreed to by the Licensor. 141 | 142 | d. Nothing in this Public License constitutes or may be interpreted as a limitation upon, or waiver of, any privileges and immunities that apply to the Licensor or You, including from the legal processes of any jurisdiction or authority. 143 | -------------------------------------------------------------------------------- /LICENSE.MD.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 17a25e1d8724c524784f92c29ccbdce9 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /PackageManual.md: -------------------------------------------------------------------------------- 1 | # QuickSave Package Manual 2 | This is a comprehensive guide on how to use the package in your own project. 3 | You can use it together with the [Demo Project](https://github.com/JonasDeM/QuickSaveDemo) to learn how to use the package. 4 | 5 | ## Setup 6 | To install the package you can just put it in your projects Packages folder. 7 | An alternative is to install it via the Unity Package Manager via the git URL. (It's possible to specify an exact commit in the URL.) 8 | 9 | Add the QuickSaveAuthoring component to a gameobject in a SubScene and click the 'Create QuickSave Settings'. 10 | 11 | ## Getting Started 12 | ### Defining what **CAN** get saved 13 | If the Initial Setup was followed there will now be 2 assets in the 'Assets/QuickSave/Resources/QuickSave' folder. 14 | On the [QuickSaveSettingsAsset](QuickSave/QuickSaveSettingsAsset.cs) you will define every ECS component that you might want to save & load. 15 | For BufferElementData you'll also have to define the max amount of elements that can be saved. 16 | On the [QuickSaveArchetypeCollection](QuickSave/QuickSaveArchetypeCollection.cs) asset you can optionally define named combinations of types. 17 | These definitions are useful to reduce authoring work, add consistency and make later modifications easier. 18 | 19 | 20 | ![image](https://github.com/JonasDeM/QuickSave/assets/23634827/3d5cec80-6525-4b72-9e63-cfac7e1db354) 21 | 22 | 23 | QuickSave also tracks if the component is enabled or not (see IEnableableComponent). 24 | For IComponentData it tracks whether the component is on the entity or not (especially useful for TagComponents). 25 | For IBufferElementData there's no tracking for the component being added or not, BUT it tracks the amount of entries and their data. You'll have to empty you buffers instead of removing them! 26 | The reason for this is performance related. 27 | 28 | 29 | _There are restrictions to what can get saved. ComponentData & BufferElementData are supported as long as they don't container pointers, blobassetreferences or references to other entities. 30 | The restrictions are there because it doesn't make sense to persist any of these things in their raw form. 31 | If for example your pointer field is part of the state of your entity, it's up to the user to quicksave some kind of serializable field like a GUID and quicksave that instead. 32 | The user can then make a system that automatically fixes up the pointer based on whatever the GUID is._ 33 | 34 | ### Defining what **WILL** get saved 35 | With QuickSave you can decide per Authoring Gameobject (and thus per Entity) what state gets saved specifically. 36 | This doesn't mean that there should be lots of authoring work though. 37 | The authoring component [QuickSaveAuthoring](QuickSave/QuickSaveAuthoring.cs) can get added to Prefabs & multi-object editing is fully supported. 38 | 39 | 40 | [QuickSaveAuthoring](QuickSave/QuickSaveAuthoring.cs) should get added to any authoring gameobject for which their resulting Entity you want to save & load their state. 41 | The options in the inspector are what you defined in the previous step 'Defining what can get saved'. You can select one of the presets you made (Preset is the same as a QuickSaveArchetype). 42 | Or you can make a custom combination of types directly on the component. 43 | 44 | 45 | ![image](https://github.com/JonasDeM/QuickSave/assets/23634827/97a5b53f-e292-4cae-afd6-9a7a74826406) 46 | 47 | 48 | ### Testing it out 49 | To make this first test simple I recommend using a physics object & saving at least the LocalTransform + PhysicsVelocity. 50 | 51 | 52 | When you added a [QuickSaveAuthoring](QuickSave/QuickSaveAuthoring.cs) component with some definition of what to save, you can close the subscene. 53 | QuickSave has a useful utility component [QuickSaveSubScene](QuickSave/QuickSaveSubScene.cs), add this to your subscene gameobject. 54 | 55 | You can now enter playmode and use the [QuickSaveSubScene inspector](QuickSave.Editor/QuickSaveSubSceneInspector.cs) on the subscene to 'Reset To Initial State', you can also use it to load & unload your subscene. 56 | Resetting a SubScene to its initial state is the only feature that works out of the box with 0 coding. 57 | 58 | 59 | ![resetGif](https://github.com/JonasDeM/QuickSave/assets/23634827/ddae8948-0eae-4af5-90e9-3c78bcbf8752) 60 | 61 | 62 | ### What's next 63 | 64 | In the next parts we delve into more detail on how QuickSave works and how it can be used. 65 | The [Demo Project](https://github.com/JonasDeM/QuickSaveDemo) also shows these things and how to: 66 | * Save/Load the state of a subscene at any time. 67 | * Save the state to a file & load it from a file. 68 | * Create a Replay System 69 | 70 | There's even more than that you can do, but that's up to the creativity of the developer! 71 | Once you learn the API, there's little limits to it. 72 | 73 | ## QuickSave Containers 74 | When QuickSave saves the state of your entities it copies the data to a single array per SubScene. 75 | It just looks at the SceneSection component on the entities to decide in which container the data ends up. 76 | 77 | 78 | Per SubScene, QuickSave creates only 1 container automatically, the 'Initial Container', this is where the initial state of the subscene gets stored. 79 | This gets done by the [QuickSaveSceneSystem](QuickSave/QuickSaveSceneSystem.cs) & [QuickSaveBeginFrameSystem](QuickSave/QuickSaveBeginFrameSystem.cs). 80 | 81 | A container is just an entity that stores all its data in a buffer component of type [QuickSaveDataContainer.Data](QuickSave/QuickSaveDataContainer.cs). 82 | It also has the [QuickSaveDataContainer](QuickSave/QuickSaveDataContainer.cs) component with some info about the container. 83 | 84 | 85 | You can create new containers for a subscene simply by instantiating an existing valid container entity. 86 | When a subscene is loaded the first time, QuickSave automatically creates 1 valid container with the intial state of the subscene. 87 | So to create your first own container you'll need to duplicate/instantiate that entity. 88 | The [QuickSaveAPI](QuickSave/QuickSaveAPI.cs) class has some handy methods to do this, but feel free to work with the container entities directly. 89 | Getting the initial container for a subscene can be done by grabbing the [QuickSaveSceneSection](QuickSave/QuickSaveSceneComponents.cs) component on the SceneSection entity. 90 | 91 | 92 | To apply the data from a container to the entities (or the reverse) you use the [DataTransferRequest](QuickSave/QuickSaveDataContainer.cs) component on a container. 93 | 94 | ## QuickSave Systems 95 | There are 2 default systems that will execute your [DataTransferRequest](QuickSave/QuickSaveDataContainer.cs): [QuickSaveBeginFrameSystem](QuickSave/QuickSaveBeginFrameSystem.cs) & [QuickSaveEndFrameSystem](QuickSave/QuickSaveEndFrameSystem.cs). 96 | 97 | 98 | For common usage these can be enough, but if you want your requests to be executed at another time in the frame you can create your own QuickSaveSystem by simply inheriting from [QuickSaveSystemBase](QuickSave/QuickSaveSystemBase.cs). 99 | 100 | ## Subscene Loading & Unloading 101 | You can use the Unity component RequestSceneLoaded on SceneSection entities just as you would normally. 102 | [QuickSaveSceneSystem](QuickSave/QuickSaveSceneSystem.cs) will auto-detect when scenes are loaded that use QuickSave. 103 | 104 | QuickSave does provide 2 handy components to simplify auto-saving & auto-loading of state. 105 | [AutoApplyOnLoad](QuickSave/QuickSaveSceneComponents.cs) & [AutoPersistOnUnload](QuickSave/QuickSaveSceneComponents.cs). 106 | If you decide to use [AutoPersistOnUnload](QuickSave/QuickSaveSceneComponents.cs) you will need to unload your subscene by adding the [RequestSceneUnloaded](QuickSave/QuickSaveSceneComponents.cs) component. 107 | This is to give QuickSave time to save the state before the scene gets unloaded. 108 | 109 | ## Serialization 110 | QuickSave comes with serialization support, so you can write your containers to disk. 111 | The [RequestSerialization](QuickSave/DefaultQuickSaveSerialization.cs) & [RequestDeserialization](QuickSave/DefaultQuickSaveSerialization.cs) components can be added/enabled on a quicksave container for this purpose. 112 | 113 | 114 | If you need custom serialization you can disable the [DefaultQuickSaveSerializationSystem](QuickSave/DefaultQuickSaveSerialization.cs) & create your own system based on it. 115 | 116 | 117 | # API Reference 118 | [QuickSaveAuthoring](QuickSave/QuickSaveAuthoring.cs): The authoring component to put on gameobjects in subscenes. 119 | 120 | 121 | [QuickSaveBaker](QuickSave.Baking/QuickSaveBaker.cs): Baker for QuickSaveAuthoring 122 | 123 | 124 | [QuickSaveBakingSystem](QuickSave.Baking/QuickSaveBakingSystem.cs): Finishes the baking from QuickSaveBaker. 125 | 126 | 127 | ------ 128 | 129 | 130 | [QuickSaveDataContainer](QuickSave/QuickSaveDataContainer.cs): ComponentData on container entities that describes the container. 131 | 132 | 133 | [QuickSaveDataContainer.Data](QuickSave/QuickSaveDataContainer.cs): BufferData on container entities that contains all the raw container data. 134 | 135 | 136 | [QuickSaveArchetypeDataLayout](QuickSave/QuickSaveDataContainer.cs): BufferData on container entities that described the data layout of the raw data. 137 | 138 | 139 | [DataTransferRequest](QuickSave/QuickSaveDataContainer.cs): BufferData that can be added to a container to request a QuickSaveSystemBase to apply or save the data. 140 | 141 | 142 | [QuickSaveAPI.IsInitialContainer](QuickSave/QuickSaveAPI.cs): Utility function to check whether the container is the auto-created initial container for a subscene. 143 | 144 | 145 | [QuickSaveAPI.InstantiateContainer](QuickSave/QuickSaveAPI.cs): Utility function to duplicate a container. 146 | 147 | 148 | ------ 149 | 150 | 151 | [QuickSaveSystemBase](QuickSave/QuickSaveSystemBase.cs): Abstract class to inherit from when in need of a custom QuickSaveSystem. 152 | 153 | 154 | [QuickSaveSceneSystem](QuickSave/QuickSaveSceneSystem.cs): System that detects subscene loads, creates the initial container & does other crucial work on subscene load. 155 | 156 | 157 | [QuickSaveSceneSection](QuickSave/QuickSaveSceneComponents.cs) : ComponentData on SceneSections that holds a reference to the initial container entity. 158 | 159 | 160 | ------ 161 | 162 | 163 | [QuickSaveArchetypeIndexInContainer](QuickSave/IndexInContainer.cs): ComponentData on user entities that holds a primary index into the containerdata array. 164 | 165 | 166 | [LocalIndexInContainer](QuickSave/IndexInContainer.cs): ComponentData on user entities that holds a secondary index into the containerdata array. 167 | 168 | 169 | [QuickSaveMetaData](QuickSave/IndexInContainer.cs): Small struct that lives in front of every piece of data in the containerdata array. 170 | 171 | 172 | ------ 173 | 174 | 175 | [QuickSaveSettings](QuickSave/QuickSaveSettings.cs): Static runtime equivalent to the QuickSaveSettingsAsset. 176 | 177 | 178 | [QuickSaveSettingsAsset](QuickSave/QuickSaveSettingsAsset.cs): Asset which contains the user defined types & some options. 179 | 180 | 181 | [QuickSaveArchetypeCollection](QuickSave/QuickSaveArchetypeCollection.cs): Asset which contains the user defined type combination presets. 182 | -------------------------------------------------------------------------------- /PackageManual.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 979b134fd31469c4dad618d4d589eac3 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /QuickSave.Baking.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0b75e538e298f3e4bb4a92e2ad72e691 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /QuickSave.Baking/QuickSaveBaker.cs: -------------------------------------------------------------------------------- 1 | // Author: Jonas De Maeseneer 2 | 3 | using System.Collections.Generic; 4 | using Unity.Collections; 5 | using Unity.Entities; 6 | 7 | namespace QuickSave.Baking 8 | { 9 | [BakingType] 10 | internal struct QuickSaveTypeHandlesBakingOnly : IBufferElementData 11 | { 12 | public QuickSaveTypeHandle QuickSaveTypeHandle; 13 | } 14 | 15 | [BakingType] 16 | internal struct QuickSaveDataLayoutHashBakingOnly : IComponentData 17 | { 18 | public ulong Value; 19 | } 20 | 21 | // Works in Tandem with PersistencyBakingSystem 22 | internal class QuickSaveBaker : Baker 23 | { 24 | public override void Bake(QuickSaveAuthoring authoring) 25 | { 26 | var settings = QuickSaveSettingsAsset.Get(); 27 | if (settings == null || settings.QuickSaveArchetypeCollection == null) 28 | return; 29 | 30 | DependsOn(settings); 31 | DependsOn(settings.QuickSaveArchetypeCollection); 32 | 33 | // Get the types from preset or type list 34 | List fullTypeNames = authoring.FullTypeNamesToPersist; 35 | if (authoring.QuickSaveArchetypeName != "") 36 | { 37 | var quickSaveArchetype = settings.QuickSaveArchetypeCollection.Definitions.Find(p => p.Name == authoring.QuickSaveArchetypeName); 38 | fullTypeNames = quickSaveArchetype != null ? quickSaveArchetype.FullTypeNames : new List(); 39 | } 40 | 41 | if (fullTypeNames.Count == 0) 42 | return; 43 | 44 | QuickSaveSettings.Initialize(); // This needs to be here because we're in baking & it's not guaranteed to be initialized 45 | 46 | var entity = GetEntity(TransformUsageFlags.None); 47 | // Add 2 uninitialized components that will get set by the baking system 48 | AddComponent(entity, new LocalIndexInContainer() 49 | { 50 | LocalIndex = -1 51 | }); 52 | AddSharedComponent(entity, new QuickSaveArchetypeIndexInContainer 53 | { 54 | IndexInContainer = ushort.MaxValue 55 | }); 56 | 57 | // Add the baking only components 58 | var bakingOnlyBuffer = AddBuffer(entity); 59 | foreach (var handle in QuickSaveSettings.GetTypeHandles(fullTypeNames, Allocator.Temp)) 60 | { 61 | bakingOnlyBuffer.Add(new QuickSaveTypeHandlesBakingOnly { QuickSaveTypeHandle = handle }); 62 | } 63 | QuickSaveDataLayoutHashBakingOnly dataLayoutHash = new QuickSaveDataLayoutHashBakingOnly(); 64 | foreach (QuickSaveTypeHandlesBakingOnly bufferElement in bakingOnlyBuffer) 65 | { 66 | var typeInfo = TypeManager.GetTypeInfo(QuickSaveSettings.GetTypeIndex(bufferElement.QuickSaveTypeHandle)); 67 | dataLayoutHash.Value = TypeHash.CombineFNV1A64(dataLayoutHash.Value, typeInfo.StableTypeHash); 68 | } 69 | AddComponent(entity, dataLayoutHash); 70 | } 71 | } 72 | 73 | } -------------------------------------------------------------------------------- /QuickSave.Baking/QuickSaveBaker.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ebd6abf65e63ea94e98eb74bc0027935 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /QuickSave.Baking/QuickSaveBakingSystem.cs: -------------------------------------------------------------------------------- 1 | // Author: Jonas De Maeseneer 2 | 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Runtime.CompilerServices; 6 | using System.Text; 7 | using Unity.Collections; 8 | using Unity.Entities; 9 | using Debug = UnityEngine.Debug; 10 | using Hash128 = Unity.Entities.Hash128; 11 | // ReSharper disable AccessToDisposedClosure 12 | 13 | [assembly:InternalsVisibleTo("com.studioaurelius.quicksave.editor")] 14 | [assembly:InternalsVisibleTo("com.studioaurelius.quicksave.tests")] 15 | 16 | namespace QuickSave.Baking 17 | { 18 | // Works in Tandem with PersistencyBaker 19 | [WorldSystemFilter(WorldSystemFilterFlags.BakingSystem)] 20 | internal partial class QuickSaveBakingSystem : SystemBase 21 | { 22 | private EntityQuery _allPersistenceEntities; 23 | private EntityQuery _validQuickSaveEntities; 24 | 25 | internal struct QuickSaveArchetypesInSceneCreationInfo 26 | { 27 | public int OffsetInQuickSaveTypeHandlesLookupList; 28 | public int AmountTypeHandles; 29 | public int AmountEntities; 30 | } 31 | 32 | protected override void OnCreate() 33 | { 34 | base.OnCreate(); 35 | 36 | NativeList types = new NativeList(5, Allocator.Temp); 37 | types.Add(typeof(LocalIndexInContainer)); 38 | types.Add(typeof(QuickSaveArchetypeIndexInContainer)); 39 | types.Add(typeof(QuickSaveTypeHandlesBakingOnly)); 40 | types.Add(typeof(QuickSaveDataLayoutHashBakingOnly)); 41 | _allPersistenceEntities = new EntityQueryBuilder(Allocator.Temp).WithAny(ref types).WithOptions(EntityQueryOptions.IncludeDisabledEntities).Build(this); 42 | _validQuickSaveEntities = new EntityQueryBuilder(Allocator.Temp).WithAll(ref types).WithOptions(EntityQueryOptions.IncludeDisabledEntities).Build(this); 43 | } 44 | 45 | protected override void OnUpdate() 46 | { 47 | BlobAssetStore blobAssetStore = World.GetExistingSystemManaged().BlobAssetStore; 48 | Update(blobAssetStore); 49 | } 50 | 51 | internal void Update(BlobAssetStore blobAssetStore) 52 | { 53 | QuickSaveSettings.Initialize(); // This needs to be here because we're in baking & it's not guaranteed to be initialized 54 | 55 | // Check for invalid entities 56 | if (_allPersistenceEntities.CalculateEntityCountWithoutFiltering() != _validQuickSaveEntities.CalculateEntityCount()) 57 | { 58 | Debug.LogError($"{nameof(QuickSaveBakingSystem)} encountered entities that were invalid for this system! The baking result is likely incomplete or incorrect."); 59 | } 60 | 61 | // Init Containers 62 | NativeReference containerDataLayoutHashRef = new NativeReference(Allocator.TempJob) {Value = 17}; 63 | 64 | NativeList blobCreationInfoList = new NativeList(16, Allocator.TempJob); 65 | NativeList quickSaveTypeHandlesLookup = new NativeList(128, Allocator.TempJob); 66 | NativeList indexCounters = new NativeList(Allocator.TempJob); 67 | 68 | NativeHashSet allUniqueTypeHandles = new NativeHashSet(16, Allocator.TempJob); 69 | NativeHashMap hashToArchetypeIndexInContainer = new NativeHashMap(16, Allocator.TempJob); 70 | EntityCommandBuffer ecb = new EntityCommandBuffer(Allocator.TempJob); 71 | 72 | // Iterate all persistable entities to get overview on indices & amounts 73 | Entities.WithEntityQueryOptions(EntityQueryOptions.IncludeDisabledEntities).ForEach( 74 | (Entity e, ref LocalIndexInContainer indexInContainer, 75 | in QuickSaveDataLayoutHashBakingOnly dataLayoutHash, in DynamicBuffer typeHandlesBuffer 76 | ) => 77 | { 78 | if (!hashToArchetypeIndexInContainer.ContainsKey(dataLayoutHash.Value)) 79 | { 80 | Debug.Assert(blobCreationInfoList.Length < ushort.MaxValue); 81 | indexCounters.Add(0); 82 | hashToArchetypeIndexInContainer.Add(dataLayoutHash.Value, (ushort)blobCreationInfoList.Length); 83 | blobCreationInfoList.Add(new QuickSaveArchetypesInSceneCreationInfo() 84 | { 85 | AmountEntities = 0, 86 | AmountTypeHandles = typeHandlesBuffer.Length, 87 | OffsetInQuickSaveTypeHandlesLookupList = quickSaveTypeHandlesLookup.Length 88 | }); 89 | 90 | for (var i = 0; i < typeHandlesBuffer.Length; i++) 91 | { 92 | var handle = typeHandlesBuffer[i].QuickSaveTypeHandle; 93 | allUniqueTypeHandles.Add(handle); 94 | quickSaveTypeHandlesLookup.Add(handle); 95 | } 96 | 97 | // Add the datalayouthash of the new QuickSaveArchetype to the datalayouthash of the container 98 | containerDataLayoutHashRef.Value = TypeHash.CombineFNV1A64(containerDataLayoutHashRef.Value, dataLayoutHash.Value); 99 | } 100 | 101 | // Set index 102 | ushort archetypeIndexInContainer = hashToArchetypeIndexInContainer[dataLayoutHash.Value]; 103 | ecb.SetSharedComponent(e, new QuickSaveArchetypeIndexInContainer { IndexInContainer = archetypeIndexInContainer }); 104 | int index = indexCounters[archetypeIndexInContainer]; 105 | indexInContainer.LocalIndex = index; 106 | indexCounters[archetypeIndexInContainer] = index + 1; 107 | 108 | // update amount for ScenePersistencyInfoEntity 109 | var tempToUpdate = blobCreationInfoList[archetypeIndexInContainer]; 110 | tempToUpdate.AmountEntities += 1; 111 | blobCreationInfoList[archetypeIndexInContainer] = tempToUpdate; 112 | }).WithBurst().Run(); 113 | 114 | // Check if all types are supported 115 | bool allSupported = true; 116 | foreach (var quickSaveTypeHandle in allUniqueTypeHandles) 117 | { 118 | if (QuickSaveSettings.IsSupported(TypeManager.GetTypeInfo(QuickSaveSettings.GetTypeIndex(quickSaveTypeHandle)), out string notSupportedReason)) 119 | continue; 120 | Debug.LogError($"{nameof(QuickSaveBakingSystem)}:: BAKE FAILED FOR {TryGetScenePath(GetSceneGuid())} (NonSupported Type found): {notSupportedReason}"); 121 | allSupported = false; 122 | } 123 | 124 | // Only do this work for scenes with persistent entities & when all types are valid 125 | if (blobCreationInfoList.Length > 0 && allSupported) 126 | { 127 | // Make the shared component changes 128 | ecb.Playback(EntityManager); 129 | 130 | // Add amount entities per QuickSaveArchetype to hash 131 | foreach (var creationInfo in blobCreationInfoList) 132 | { 133 | containerDataLayoutHashRef.Value = TypeHash.CombineFNV1A64(containerDataLayoutHashRef.Value, (ulong) creationInfo.AmountEntities); 134 | } 135 | 136 | // Create a single entity (per scene) that holds blob data which contains all the necessary info to be able to persist the whole subscene to a single container 137 | CreateOrUpdateScenePersistencyInfoEntity(allUniqueTypeHandles, blobCreationInfoList, quickSaveTypeHandlesLookup, 138 | containerDataLayoutHashRef.Value, GetSceneGuid(), blobAssetStore); 139 | } 140 | 141 | // Clean up 142 | containerDataLayoutHashRef.Dispose(); 143 | blobCreationInfoList.Dispose(); 144 | quickSaveTypeHandlesLookup.Dispose(); 145 | indexCounters.Dispose(); 146 | allUniqueTypeHandles.Dispose(); 147 | hashToArchetypeIndexInContainer.Dispose(); 148 | ecb.Dispose(); 149 | } 150 | 151 | protected override void OnDestroy() 152 | { 153 | QuickSaveSettings.CleanUp(); // This needs to be here because we're in baking & this system should be the last thing that uses it during baking 154 | base.OnDestroy(); 155 | } 156 | 157 | private void CreateOrUpdateScenePersistencyInfoEntity(NativeHashSet allUniqueTypeHandles, 158 | NativeList blobCreationInfoList, NativeList typeHandlesLookup, ulong dataLayoutHash, 159 | Hash128 sceneGUID, BlobAssetStore blobAssetStore) 160 | { 161 | // GetOrCreate Setting Entity & set the right data 162 | // Create the entity that contains all the info the PersistentSceneSystem needs for initializing a loaded scene 163 | NativeArray scenePersistencyInfoEntityArray = EntityManager.CreateEntityQuery(typeof(QuickSaveSceneInfoRef)).ToEntityArray(Allocator.Temp); 164 | Entity sceneInfoEntity; 165 | if (scenePersistencyInfoEntityArray.Length == 0) 166 | { 167 | sceneInfoEntity = EntityManager.CreateEntity(); 168 | EntityManager.SetName(sceneInfoEntity, nameof(QuickSaveSceneInfoRef)); 169 | } 170 | else 171 | { 172 | sceneInfoEntity = scenePersistencyInfoEntityArray[0]; 173 | if (scenePersistencyInfoEntityArray.Length > 1) 174 | { 175 | Debug.LogError($"{nameof(QuickSaveBakingSystem)} found more than 1 {nameof(QuickSaveSceneInfoRef)} entities, this indicates an invalid bake!"); 176 | } 177 | } 178 | List allUniqueTypeHandlesSorted = new List(allUniqueTypeHandles.Count); 179 | var hashSetEnumerator = allUniqueTypeHandles.GetEnumerator(); 180 | while (hashSetEnumerator.MoveNext()) 181 | { 182 | allUniqueTypeHandlesSorted.Add(hashSetEnumerator.Current); 183 | } 184 | allUniqueTypeHandlesSorted.Sort((left, right) => left.CompareTo(right)); 185 | 186 | QuickSaveSceneInfoRef quickSaveSceneInfoRef = CreateQuickSaveSceneInfoRef(allUniqueTypeHandlesSorted, blobCreationInfoList, 187 | typeHandlesLookup, sceneGUID, dataLayoutHash, blobAssetStore); 188 | EntityManager.AddComponentData(sceneInfoEntity, quickSaveSceneInfoRef); 189 | 190 | Debug.Assert(sceneGUID != default); 191 | EntityManager.AddSharedComponent(sceneInfoEntity, new SceneSection {SceneGUID = sceneGUID, Section = 0}); 192 | 193 | if (QuickSaveSettings.VerboseBakingLog) 194 | { 195 | VerboseLog(quickSaveSceneInfoRef, TryGetScenePath(sceneGUID)); 196 | } 197 | } 198 | 199 | internal static QuickSaveSceneInfoRef CreateQuickSaveSceneInfoRef(List allUniqueTypeHandlesSorted, 200 | NativeList blobCreationInfoList, NativeList typeHandlesLookup, Hash128 sceneGUID, ulong dataLayoutHash, 201 | BlobAssetStore blobAssetStore) 202 | { 203 | QuickSaveSceneInfoRef quickSaveSceneInfoRef = new QuickSaveSceneInfoRef(); 204 | using (BlobBuilder blobBuilder = new BlobBuilder(Allocator.Temp)) 205 | { 206 | ref QuickSaveSceneInfo quickSaveSceneInfo = ref blobBuilder.ConstructRoot(); 207 | 208 | BlobBuilderArray blobBuilderArray1 = blobBuilder.Allocate(ref quickSaveSceneInfo.AllUniqueTypeHandles, allUniqueTypeHandlesSorted.Count); 209 | for (int i = 0; i < blobBuilderArray1.Length; i++) 210 | { 211 | blobBuilderArray1[i] = allUniqueTypeHandlesSorted[i]; 212 | } 213 | 214 | BlobBuilderArray blobBuilderArray2 = blobBuilder.Allocate(ref quickSaveSceneInfo.QuickSaveArchetypesInScene, blobCreationInfoList.Length); 215 | for (int i = 0; i < blobCreationInfoList.Length; i++) 216 | { 217 | QuickSaveArchetypesInSceneCreationInfo info = blobCreationInfoList[i]; 218 | ref QuickSaveArchetypesInScene quickSaveArchetypesInScene = ref blobBuilderArray2[i]; 219 | quickSaveArchetypesInScene.AmountEntities = info.AmountEntities; 220 | BlobBuilderArray blobBuilderArray3 = blobBuilder.Allocate(ref quickSaveArchetypesInScene.QuickSaveTypeHandles, info.AmountTypeHandles); 221 | 222 | for (int j = 0; j < info.AmountTypeHandles; j++) 223 | { 224 | blobBuilderArray3[j] = typeHandlesLookup[info.OffsetInQuickSaveTypeHandlesLookupList + j]; 225 | } 226 | } 227 | 228 | quickSaveSceneInfo.SceneGUID = sceneGUID; 229 | quickSaveSceneInfo.DataLayoutHash = dataLayoutHash; 230 | 231 | BlobAssetReference blobAssetReference = blobBuilder.CreateBlobAssetReference(Allocator.Persistent); 232 | blobAssetStore.TryAdd(ref blobAssetReference); 233 | quickSaveSceneInfoRef.InfoRef = blobAssetReference; 234 | } 235 | return quickSaveSceneInfoRef; 236 | } 237 | 238 | private Hash128 GetSceneGuid() 239 | { 240 | EntityManager.GetAllUniqueSharedComponents(out NativeList allValues, Allocator.Temp); 241 | Hash128 sceneGUID = default; 242 | foreach (var sceneSection in allValues) 243 | { 244 | if (sceneSection.SceneGUID != default) 245 | sceneGUID = sceneSection.SceneGUID; 246 | } 247 | Debug.Assert(sceneGUID != default, $"Expected to find another SceneSection value than default!"); 248 | return sceneGUID; 249 | } 250 | 251 | private string TryGetScenePath(Hash128 sceneGUID) 252 | { 253 | string scenePath = ""; 254 | #if UNITY_EDITOR 255 | try 256 | { 257 | scenePath = UnityEditor.AssetDatabase.GUIDToAssetPath(sceneGUID); 258 | } 259 | catch { /*ignored*/ } 260 | #endif 261 | if (string.IsNullOrEmpty(scenePath)) 262 | { 263 | scenePath = "Scene Path Not Found"; 264 | } 265 | return scenePath; 266 | } 267 | 268 | [Conditional("UNITY_EDITOR")] 269 | private static void VerboseLog(QuickSaveSceneInfoRef quickSaveSceneInfoRef, string sceneName) 270 | { 271 | ref QuickSaveSceneInfo quickSaveSceneInfo = ref quickSaveSceneInfoRef.InfoRef.Value; 272 | StringBuilder stringBuilder = new StringBuilder($"PersistenceBaking Logs ({sceneName})\n", 128); 273 | stringBuilder.AppendLine("------DataLayoutHash------"); 274 | stringBuilder.AppendLine(quickSaveSceneInfoRef.InfoRef.Value.DataLayoutHash.ToString()); 275 | for (int i = 0; i < quickSaveSceneInfo.QuickSaveArchetypesInScene.Length; i++) 276 | { 277 | stringBuilder.AppendLine($"---Archetype{i.ToString()}---"); 278 | stringBuilder.AppendLine("Amount Entities: " + quickSaveSceneInfo.QuickSaveArchetypesInScene[i].AmountEntities.ToString()); 279 | for (int j = 0; j < quickSaveSceneInfo.QuickSaveArchetypesInScene[i].QuickSaveTypeHandles.Length; j++) 280 | { 281 | stringBuilder.AppendLine(ComponentType.FromTypeIndex(QuickSaveSettings.GetTypeIndex(quickSaveSceneInfo.QuickSaveArchetypesInScene[i].QuickSaveTypeHandles[j])).ToString()); 282 | } 283 | } 284 | stringBuilder.AppendLine("------AllUniqueTypes------"); 285 | for (int i = 0; i < quickSaveSceneInfo.AllUniqueTypeHandles.Length; i++) 286 | { 287 | stringBuilder.AppendLine(ComponentType.FromTypeIndex(QuickSaveSettings.GetTypeIndex(quickSaveSceneInfo.AllUniqueTypeHandles[i])).ToString()); 288 | } 289 | Debug.Log(stringBuilder.ToString()); 290 | } 291 | } 292 | } 293 | -------------------------------------------------------------------------------- /QuickSave.Baking/QuickSaveBakingSystem.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e67ecb6c9f9f080408917d87f2222067 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /QuickSave.Baking/com.studioaurelius.quicksave.baking.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.studioaurelius.quicksave.baking", 3 | "rootNamespace": "", 4 | "references": [ 5 | "GUID:42697df707fc47a4aa81e8cf52f99bfe", 6 | "GUID:e63a9f9b38516b1469a5055028a5560d", 7 | "GUID:2665a8d13d1b3f18800f46e256720795", 8 | "GUID:8819f35a0fc84499b990e90a4ca1911f", 9 | "GUID:734d92eba21c94caba915361bd5ac177", 10 | "GUID:e0cd26848372d4e5c891c569017e11f1", 11 | "GUID:a5baed0c9693541a5bd947d336ec7659", 12 | "GUID:d8b63aba1907145bea998dd612889d6b", 13 | "GUID:5f3cf485eb0554709a8abbeace890c86" 14 | ], 15 | "includePlatforms": [], 16 | "excludePlatforms": [], 17 | "allowUnsafeCode": false, 18 | "overrideReferences": false, 19 | "precompiledReferences": [], 20 | "autoReferenced": true, 21 | "defineConstraints": [], 22 | "versionDefines": [], 23 | "noEngineReferences": false 24 | } -------------------------------------------------------------------------------- /QuickSave.Baking/com.studioaurelius.quicksave.baking.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: aaa622c5ca1ba8f45adaf6d3b4809534 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /QuickSave.Containers.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 06f01782f99ff4045a50379251e4d6ef 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /QuickSave.Containers/TypeHandleArray.cs: -------------------------------------------------------------------------------- 1 | // Author: Jonas De Maeseneer 2 | 3 | using System; 4 | using System.Runtime.CompilerServices; 5 | using Unity.Collections; 6 | using Unity.Collections.LowLevel.Unsafe; 7 | using Unity.Entities; 8 | using Unity.Jobs; 9 | 10 | [assembly:InternalsVisibleTo("com.studioaurelius.quicksave")] 11 | 12 | // This container is intentionally super specific. 13 | // It should only be used to get around the fact that NativeContainer doesn't want to hold DynamicTypeHandles. 14 | 15 | namespace QuickSave.Containers 16 | { 17 | [NativeContainer] 18 | internal struct TypeHandleArrayDisposeJobData 19 | { 20 | [NativeDisableUnsafePtrRestriction] 21 | public unsafe void* m_Buffer; 22 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 23 | public AtomicSafetyHandle m_Safety; 24 | #endif 25 | public Allocator m_AllocatorLabel; 26 | 27 | public unsafe void Dispose() 28 | { 29 | UnsafeUtility.Free(m_Buffer, m_AllocatorLabel); 30 | } 31 | } 32 | 33 | internal struct TypeHandleArrayDisposeJob : IJob 34 | { 35 | public TypeHandleArrayDisposeJobData DisposeJobData; 36 | 37 | public void Execute() 38 | { 39 | DisposeJobData.Dispose(); 40 | } 41 | } 42 | 43 | [NativeContainer] 44 | [NativeContainerSupportsDeallocateOnJobCompletion] 45 | internal struct ComponentTypeHandleArray : IDisposable 46 | { 47 | [NativeDisableUnsafePtrRestriction] private unsafe void* m_Buffer; 48 | private int m_Length; 49 | 50 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 51 | private AtomicSafetyHandle m_Safety; 52 | [NativeSetClassTypeToNullOnSchedule] 53 | private DisposeSentinel m_DisposeSentinel; 54 | #endif 55 | private Allocator m_AllocatorLabel; 56 | 57 | private static readonly int ElementSize = UnsafeUtility.SizeOf(); 58 | private static readonly int Alignment = UnsafeUtility.AlignOf(); 59 | 60 | public unsafe ComponentTypeHandleArray(int length, Allocator allocator) 61 | { 62 | long size = ElementSize * (long) length; 63 | 64 | if (allocator <= Allocator.None) 65 | throw new ArgumentException("Allocator must be Temp, TempJob or Persistent", nameof (allocator)); 66 | if (length < 0) 67 | throw new ArgumentOutOfRangeException(nameof (length), "Length must be >= 0"); 68 | if (size > (long) int.MaxValue) 69 | throw new ArgumentOutOfRangeException(nameof (length), $"Length * sizeof(DynamicComponentTypeHandle) cannot exceed {int.MaxValue.ToString()} bytes"); 70 | 71 | m_Buffer = UnsafeUtility.Malloc(size, Alignment, allocator); 72 | m_Length = length; 73 | m_AllocatorLabel = allocator; 74 | 75 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 76 | DisposeSentinel.Create(out m_Safety, out m_DisposeSentinel, 1, allocator); 77 | #endif 78 | } 79 | 80 | public unsafe void Dispose() 81 | { 82 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 83 | if (!UnsafeUtility.IsValidAllocator(m_AllocatorLabel)) 84 | throw new InvalidOperationException("The ComponentTypeHandleArray can not be disposed because it was not allocated with a valid allocator."); 85 | DisposeSentinel.Dispose(ref this.m_Safety, ref this.m_DisposeSentinel); 86 | #endif 87 | UnsafeUtility.Free(m_Buffer, m_AllocatorLabel); 88 | m_Buffer = (void*) null; 89 | m_Length = 0; 90 | } 91 | 92 | public unsafe JobHandle Dispose(JobHandle inputDeps) 93 | { 94 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 95 | DisposeSentinel.Clear(ref m_DisposeSentinel); 96 | #endif 97 | JobHandle jobHandle = new TypeHandleArrayDisposeJob() 98 | { 99 | DisposeJobData = new TypeHandleArrayDisposeJobData() 100 | { 101 | m_Buffer = m_Buffer, 102 | m_AllocatorLabel = m_AllocatorLabel, 103 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 104 | m_Safety = m_Safety 105 | #endif 106 | } 107 | }.Schedule(inputDeps); 108 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 109 | AtomicSafetyHandle.Release(m_Safety); 110 | #endif 111 | m_Buffer = (void*) null; 112 | m_Length = 0; 113 | return jobHandle; 114 | } 115 | 116 | public unsafe DynamicComponentTypeHandle this[int index] 117 | { 118 | set 119 | { 120 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 121 | if (index < 0 || index >= m_Length) 122 | throw new IndexOutOfRangeException(); 123 | #endif 124 | UnsafeUtility.WriteArrayElement(m_Buffer, index, value); 125 | } 126 | } 127 | 128 | public unsafe bool GetByTypeIndex(TypeIndex typeIndex, out DynamicComponentTypeHandle typeHandle) 129 | { 130 | for (int i = 0; i < m_Length; i++) 131 | { 132 | DynamicComponentTypeHandle element = UnsafeUtility.ReadArrayElement(m_Buffer, i); 133 | if (element.GetTypeIndex() == typeIndex) 134 | { 135 | typeHandle = element; 136 | return true; 137 | } 138 | } 139 | typeHandle = default; 140 | return false; 141 | } 142 | } 143 | } -------------------------------------------------------------------------------- /QuickSave.Containers/TypeHandleArray.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b414f55bf7b807b41b600212cc1db888 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /QuickSave.Containers/com.studioaurelius.quicksave.containers.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.studioaurelius.quicksave.containers", 3 | "rootNamespace": "", 4 | "references": [ 5 | "GUID:e0cd26848372d4e5c891c569017e11f1", 6 | "GUID:734d92eba21c94caba915361bd5ac177" 7 | ], 8 | "includePlatforms": [], 9 | "excludePlatforms": [], 10 | "allowUnsafeCode": true, 11 | "overrideReferences": false, 12 | "precompiledReferences": [], 13 | "autoReferenced": false, 14 | "defineConstraints": [], 15 | "versionDefines": [], 16 | "noEngineReferences": false 17 | } -------------------------------------------------------------------------------- /QuickSave.Containers/com.studioaurelius.quicksave.containers.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e63a9f9b38516b1469a5055028a5560d 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /QuickSave.Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6b47261541e6bde49ac28a0ce3735b56 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /QuickSave.Editor/FindTypeWindow.cs: -------------------------------------------------------------------------------- 1 | // Author: Jonas De Maeseneer 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using Unity.Entities; 7 | using UnityEditor; 8 | using UnityEngine; 9 | 10 | namespace QuickSave.Editor 11 | { 12 | public class FindTypeWindow : EditorWindow 13 | { 14 | private List _allAvailableTypes; 15 | internal delegate void TypeSelectedDelegate(string fullTypeName); 16 | internal TypeSelectedDelegate OnTypeChosen = persistableTypeInfo => { }; 17 | 18 | [SerializeField] 19 | private Vector2 _scrollPos; 20 | [SerializeField] 21 | private string _currentFilter = ""; 22 | [SerializeField] 23 | private bool _showUnityTypes = false; 24 | [SerializeField] 25 | private bool _showTestTypes = false; 26 | [SerializeField] 27 | private bool _showNonSupportedTypes = false; 28 | 29 | private void OnEnable() 30 | { 31 | UpdateTypeList(); 32 | titleContent = new GUIContent("Choose a type"); 33 | } 34 | 35 | private void OnGUI() 36 | { 37 | _currentFilter = EditorGUILayout.TextField("Filter: ", _currentFilter); 38 | EditorGUI.BeginChangeCheck(); 39 | _showUnityTypes = EditorGUILayout.Toggle("Show Unity Types", _showUnityTypes); 40 | _showTestTypes = EditorGUILayout.Toggle("Show Test Types", _showTestTypes); 41 | _showNonSupportedTypes = EditorGUILayout.Toggle("Show Unsupported Types (Adding will fail but give you the explanation why.)", _showNonSupportedTypes); 42 | if (EditorGUI.EndChangeCheck()) 43 | { 44 | UpdateTypeList(); 45 | } 46 | List filterValues = _currentFilter.Split(' ').ToList(); 47 | 48 | _scrollPos = EditorGUILayout.BeginScrollView(_scrollPos); 49 | bool allHidden = true; 50 | foreach (Type ecsType in _allAvailableTypes) 51 | { 52 | string fullTypeName = ecsType.FullName.Replace("+", "."); 53 | bool hide = filterValues.Any(filterValue => fullTypeName.IndexOf(filterValue, StringComparison.InvariantCultureIgnoreCase) == -1); 54 | 55 | var oldAlignment = GUI.skin.button.alignment; 56 | GUI.skin.button.alignment = TextAnchor.MiddleLeft; 57 | if (!hide) 58 | { 59 | if (GUILayout.Button(fullTypeName)) 60 | { 61 | if (!QuickSaveSettingsAsset.IsSupported(TypeManager.GetTypeInfo(TypeManager.GetTypeIndex(ecsType)), out string notSupportedReason)) 62 | { 63 | EditorUtility.DisplayDialog(nameof(QuickSaveSettingsAsset), notSupportedReason, "Ok"); 64 | } 65 | else 66 | { 67 | OnTypeChosen(fullTypeName); 68 | Close(); 69 | } 70 | } 71 | } 72 | GUI.skin.button.alignment = oldAlignment; 73 | allHidden &= hide; 74 | } 75 | if (allHidden) 76 | { 77 | EditorGUILayout.HelpBox("No types found. Try changing the options above.", MessageType.Info); 78 | } 79 | EditorGUILayout.EndScrollView(); 80 | } 81 | 82 | private void UpdateTypeList() 83 | { 84 | _allAvailableTypes = TypeManager.GetAllTypes() 85 | .Where(info => info.Type != null && info.Type.FullName != null && info.Category != TypeManager.TypeCategory.UnityEngineObject) 86 | .Where(info => _showNonSupportedTypes || QuickSaveSettingsAsset.IsSupported(info, out _)) 87 | .Select(info => info.Type) 88 | .Where(type => type.Namespace == null || type.Namespace != typeof(LocalIndexInContainer).Namespace) // Don't show QuickSave Types 89 | .Where(type => (_showUnityTypes || !IsUnityType(type)) && (_showTestTypes || !IsTestType(type))) 90 | .ToList(); 91 | _allAvailableTypes.Sort((type, type1) => string.CompareOrdinal(type.FullName, type1.FullName)); 92 | } 93 | 94 | private static bool IsUnityType(Type type) 95 | { 96 | return type.Namespace != null && type.Namespace.Contains("Unity"); 97 | } 98 | 99 | private static bool IsTestType(Type type) 100 | { 101 | return type.Namespace != null && type.Namespace.Contains("Test"); 102 | } 103 | 104 | // Some code to ensure the window is closed when scripts are reloaded 105 | [UnityEditor.Callbacks.DidReloadScripts] 106 | private static void OnScriptsReloaded() 107 | { 108 | if (HasOpenInstances()) 109 | { 110 | GetWindow().Close(); 111 | } 112 | } 113 | } 114 | } -------------------------------------------------------------------------------- /QuickSave.Editor/FindTypeWindow.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b59e75d3bacd83f4aa64d731debe86be 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /QuickSave.Editor/QuickSaveArchetypeCollectionInspector.cs: -------------------------------------------------------------------------------- 1 | // Author: Jonas De Maeseneer 2 | 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using UnityEditor; 6 | using UnityEditorInternal; 7 | using UnityEngine; 8 | 9 | namespace QuickSave.Editor 10 | { 11 | [CustomEditor(typeof(QuickSaveArchetypeCollection))] 12 | public class QuickSaveArchetypeCollectionInspector : UnityEditor.Editor 13 | { 14 | private ReorderableList _reorderableList; 15 | private QuickSaveArchetypeCollection _archetypeCollection; 16 | private QuickSaveSettingsAsset _quickSaveSettingsAsset; 17 | List _cachedFullTypeNames; 18 | [SerializeField] 19 | private List _foldouts = new List(); 20 | 21 | public static readonly string[] InvalidNames = new string[3] {"None", "No Preset", "Custom Combination"}; 22 | private string _newEntryName = ""; 23 | 24 | private void OnEnable() 25 | { 26 | _archetypeCollection = (QuickSaveArchetypeCollection) target; 27 | _quickSaveSettingsAsset = QuickSaveSettingsAsset.Get(); 28 | _cachedFullTypeNames = _quickSaveSettingsAsset.AllQuickSaveTypeInfos.Select(info => info.FullTypeName).ToList(); 29 | _cachedFullTypeNames.Add(""); 30 | 31 | ResizeFoldoutList(); 32 | 33 | _reorderableList = new ReorderableList(_archetypeCollection.Definitions, typeof(QuickSaveArchetypeCollection.QuickSaveArchetypeDefinition)); 34 | _reorderableList.drawElementCallback = DrawElementCallback; 35 | _reorderableList.drawHeaderCallback = DrawHeaderCallback; 36 | _reorderableList.onAddCallback = OnAddCallback; 37 | _reorderableList.onRemoveCallback = OnRemoveCallback; 38 | _reorderableList.elementHeightCallback = ElementHeightCallback; 39 | _reorderableList.drawNoneElementCallback = DrawNoneCallback; 40 | } 41 | 42 | private float ElementHeightCallback(int index) 43 | { 44 | float height = EditorGUIUtility.singleLineHeight + 2; 45 | if (_foldouts[index]) 46 | height *= _archetypeCollection.Definitions[index].FullTypeNames.Count + 2; 47 | return height + 4; 48 | } 49 | 50 | public override void OnInspectorGUI() 51 | { 52 | GUI.enabled = !EditorApplication.isPlayingOrWillChangePlaymode; 53 | 54 | _reorderableList.DoLayoutList(); 55 | 56 | if (EditorUtility.IsDirty(_archetypeCollection) && _reorderableList.list.Count > 0) 57 | { 58 | EditorGUILayout.HelpBox("Save for the changes to go into effect! (Ctrl+S)", MessageType.Info); 59 | } 60 | } 61 | 62 | private void DrawElementCallback(Rect rect, int index, bool isactive, bool isfocused) 63 | { 64 | var entry = _archetypeCollection.Definitions[index]; 65 | rect.min += new Vector2(16, 1); 66 | rect.max -= new Vector2(16, 3); 67 | rect.height = EditorGUIUtility.singleLineHeight; 68 | // Name 69 | _foldouts[index] = EditorGUI.Foldout(rect, _foldouts[index], entry.Name); 70 | if (!_foldouts[index]) 71 | return; 72 | 73 | // Type List 74 | for (int i = 0; i < entry.FullTypeNames.Count; i++) 75 | { 76 | rect.y += EditorGUIUtility.singleLineHeight + 2; 77 | int selectedIndex = _cachedFullTypeNames.FindIndex(s => s == entry.FullTypeNames[i]); 78 | 79 | var popupRect = new Rect(rect); 80 | popupRect.xMax -= EditorGUIUtility.singleLineHeight + 2; 81 | int newSelectedIndex = EditorGUI.Popup(popupRect, selectedIndex, 82 | _cachedFullTypeNames.Select((s => 83 | string.IsNullOrEmpty(s) ? "None" : _quickSaveSettingsAsset.GetPrettyNameInEditor(s))).ToArray()); 84 | 85 | if (GUI.Button(new Rect(popupRect.xMax + 2, popupRect.y, EditorGUIUtility.singleLineHeight, EditorGUIUtility.singleLineHeight), "-")) 86 | { 87 | entry.FullTypeNames.RemoveAt(i); 88 | EditorUtility.SetDirty(_archetypeCollection); 89 | } 90 | 91 | if (newSelectedIndex != selectedIndex) 92 | { 93 | entry.FullTypeNames[i] = _cachedFullTypeNames[newSelectedIndex]; 94 | entry.FullTypeNames = entry.FullTypeNames.ToList(); 95 | entry.FullTypeNames.Sort(); 96 | EditorUtility.SetDirty(_archetypeCollection); 97 | } 98 | } 99 | 100 | // Add 101 | rect.y += EditorGUIUtility.singleLineHeight + 2; 102 | rect.xMin = rect.center.x; 103 | bool enabledBefore = GUI.enabled; 104 | GUI.enabled = !entry.FullTypeNames.Contains(""); 105 | string buttonText = GUI.enabled ? "Add Type" : "Add Type (\"None\" Value exists)"; 106 | if (GUI.Button(rect, buttonText)) 107 | { 108 | entry.FullTypeNames.Add(""); 109 | EditorUtility.SetDirty(_archetypeCollection); 110 | } 111 | GUI.enabled = enabledBefore; 112 | } 113 | 114 | private void DrawHeaderCallback(Rect rect) 115 | { 116 | if (_reorderableList.list.Count > 0) 117 | EditorGUI.LabelField(rect, "Presets"); 118 | else 119 | EditorGUI.LabelField(rect, "Presets (Adding)"); 120 | } 121 | 122 | private void OnRemoveCallback(ReorderableList list) 123 | { 124 | list.list.RemoveAt(list.index); 125 | ResizeFoldoutList(); 126 | EditorUtility.SetDirty(_archetypeCollection); 127 | } 128 | 129 | private void OnAddCallback(ReorderableList list) 130 | { 131 | list.list = new List(); 132 | } 133 | 134 | private void DrawNoneCallback(Rect rect) 135 | { 136 | bool enabledBefore = GUI.enabled; 137 | 138 | var drawRect = new Rect(rect); 139 | drawRect.xMin = rect.xMin; 140 | drawRect.xMax = rect.center.x; 141 | 142 | _newEntryName = EditorGUI.TextField(drawRect, _newEntryName); 143 | 144 | if (!IsNameValid(_newEntryName, out string invalidReason, out bool showInfoBox)) 145 | { 146 | if (showInfoBox) 147 | EditorGUILayout.HelpBox(invalidReason, MessageType.Warning); 148 | GUI.enabled = false; 149 | } 150 | 151 | drawRect.xMin = rect.center.x + 2; 152 | drawRect.xMax = rect.center.x + Mathf.Abs(rect.width / 4f); 153 | if (GUI.Button(drawRect, "Add")) 154 | { 155 | _archetypeCollection.Definitions.Add(new QuickSaveArchetypeCollection.QuickSaveArchetypeDefinition() { Name = _newEntryName}); 156 | _reorderableList.list = _archetypeCollection.Definitions; 157 | ResizeFoldoutList(); 158 | _foldouts[_reorderableList.count - 1] = true; 159 | EditorUtility.SetDirty(_archetypeCollection); 160 | } 161 | 162 | drawRect.xMin = rect.center.x + Mathf.Abs(rect.width / 4f) + 2; 163 | drawRect.xMax = rect.xMax; 164 | GUI.enabled = true; 165 | if (_archetypeCollection.Definitions.Count > 0 && GUI.Button(drawRect, "Cancel")) 166 | { 167 | _reorderableList.list = _archetypeCollection.Definitions; 168 | } 169 | 170 | GUI.enabled = enabledBefore; 171 | } 172 | 173 | private void ResizeFoldoutList() 174 | { 175 | if (_foldouts == null) 176 | _foldouts = new List(Enumerable.Repeat(false, _archetypeCollection.Definitions.Count)); 177 | if(_foldouts.Count > _archetypeCollection.Definitions.Count) 178 | _foldouts.RemoveRange(_archetypeCollection.Definitions.Count, _foldouts.Count - _archetypeCollection.Definitions.Count); 179 | if (_foldouts.Count < _archetypeCollection.Definitions.Count) 180 | _foldouts.AddRange(Enumerable.Repeat(false, _archetypeCollection.Definitions.Count - _foldouts.Count)); 181 | } 182 | 183 | private bool IsNameValid(string nameToValidate, out string invalidReason, out bool showInfoBox) 184 | { 185 | if (string.IsNullOrEmpty(nameToValidate)) 186 | { 187 | invalidReason = "The Name is invalid because an empty Name is not allowed."; 188 | showInfoBox = false; 189 | return false; 190 | } 191 | 192 | foreach (var invalidName in InvalidNames) 193 | { 194 | if (nameToValidate.Equals(invalidName)) 195 | { 196 | invalidReason = $"The Name can't be '{invalidName}'."; 197 | showInfoBox = true; 198 | return false; 199 | } 200 | } 201 | 202 | if (_archetypeCollection.Definitions.Any(d => d.Name == nameToValidate)) 203 | { 204 | invalidReason = $"An entry already exists with the Name '{nameToValidate}'."; 205 | showInfoBox = true; 206 | return false; 207 | } 208 | 209 | invalidReason = ""; 210 | showInfoBox = false; 211 | return true; 212 | } 213 | } 214 | } -------------------------------------------------------------------------------- /QuickSave.Editor/QuickSaveArchetypeCollectionInspector.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 945622c89175a344a8eaacbb7a6fda1a 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /QuickSave.Editor/QuickSaveAuthoringInspector.cs: -------------------------------------------------------------------------------- 1 | // Author: Jonas De Maeseneer 2 | 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using UnityEditor; 6 | using UnityEngine; 7 | using Object = UnityEngine.Object; 8 | 9 | namespace QuickSave.Editor 10 | { 11 | [CustomEditor(typeof(QuickSaveAuthoring)), CanEditMultipleObjects] 12 | public class QuickSaveAuthoringInspector : UnityEditor.Editor 13 | { 14 | List _cachedFullTypeNames; 15 | private QuickSaveSettingsAsset _quickSaveSettingsAsset; 16 | private List _allPresets; 17 | private static readonly string[] _mixedPresetDropDownList = new string[] { "--Mixed Presets--" }; 18 | 19 | private void OnEnable() 20 | { 21 | _quickSaveSettingsAsset = QuickSaveSettingsAsset.Get(); 22 | if (_quickSaveSettingsAsset) 23 | { 24 | _cachedFullTypeNames = _quickSaveSettingsAsset.AllQuickSaveTypeInfos.Select(info => info.FullTypeName).ToList(); 25 | _cachedFullTypeNames.Insert(0, ""); 26 | 27 | if (_quickSaveSettingsAsset.QuickSaveArchetypeCollection) 28 | { 29 | _allPresets = new List(_quickSaveSettingsAsset.QuickSaveArchetypeCollection.Definitions); 30 | _allPresets.Insert(0, new QuickSaveArchetypeCollection.QuickSaveArchetypeDefinition() {Name = ""}); 31 | _allPresets.Add(new QuickSaveArchetypeCollection.QuickSaveArchetypeDefinition() { Name = "Create New Preset"}); 32 | } 33 | } 34 | } 35 | 36 | public override void OnInspectorGUI() 37 | { 38 | // Handle missing settings asset 39 | if (_quickSaveSettingsAsset == null) 40 | { 41 | if (GUILayout.Button("Create QuickSaveSettings", GUILayout.Height(50))) 42 | { 43 | QuickSaveSettingsAsset.CreateInEditor(); 44 | _quickSaveSettingsAsset = QuickSaveSettingsAsset.Get(); 45 | _quickSaveSettingsAsset.QuickSaveArchetypeCollection = QuickSaveArchetypeCollection.CreateInEditor(); 46 | EditorUtility.SetDirty(_quickSaveSettingsAsset); 47 | AssetDatabase.SaveAssets(); 48 | OnEnable(); 49 | } 50 | return; 51 | } 52 | 53 | // Handle missing preset asset 54 | if (_quickSaveSettingsAsset.QuickSaveArchetypeCollection == null) 55 | { 56 | if (GUILayout.Button("Create QuickSaveArchetypeCollection", GUILayout.Height(50))) 57 | { 58 | _quickSaveSettingsAsset.QuickSaveArchetypeCollection = QuickSaveArchetypeCollection.CreateInEditor(); 59 | EditorUtility.SetDirty(_quickSaveSettingsAsset); 60 | AssetDatabase.SaveAssets(); 61 | OnEnable(); 62 | } 63 | return; 64 | } 65 | 66 | // Grab the info from the first selected target 67 | var singleTarget = ((QuickSaveAuthoring) target); 68 | List singleTargetTypeList = singleTarget.FullTypeNamesToPersist; 69 | if (singleTarget.QuickSaveArchetypeName != "") 70 | { 71 | var preset = _allPresets.Find(p => p.Name == singleTarget.QuickSaveArchetypeName); 72 | singleTargetTypeList = preset != null ? preset.FullTypeNames : new List(); 73 | } 74 | 75 | // Gather info on all targets as a whole 76 | bool samePresets = true; 77 | bool allNoPreset = true; 78 | bool sameHashes = true; 79 | foreach (Object o in targets) 80 | { 81 | var persistenceAuthoring = (QuickSaveAuthoring) o; 82 | List typeList = persistenceAuthoring.FullTypeNamesToPersist; 83 | if (persistenceAuthoring.QuickSaveArchetypeName != "") 84 | { 85 | var preset = _allPresets.Find(p => p.Name == persistenceAuthoring.QuickSaveArchetypeName); 86 | typeList = preset != null ? preset.FullTypeNames : new List(); 87 | } 88 | 89 | if (singleTarget.QuickSaveArchetypeName != persistenceAuthoring.QuickSaveArchetypeName) 90 | samePresets = false; 91 | 92 | if (persistenceAuthoring.QuickSaveArchetypeName != "") 93 | allNoPreset = false; 94 | 95 | if (singleTargetTypeList.Count != typeList.Count || singleTargetTypeList.Except(typeList).Any()) 96 | sameHashes = false; 97 | } 98 | 99 | // Select a Preset 100 | if (samePresets) 101 | { 102 | int selectedIndex = _allPresets.FindIndex(p => p.Name == singleTarget.QuickSaveArchetypeName); 103 | 104 | GUI.enabled = !(allNoPreset && singleTargetTypeList.Count > 0); 105 | string noPresetText = GUI.enabled ? "No Preset" : "Custom Combination"; 106 | int newSelectedIndex = EditorGUILayout.Popup($"Preset", selectedIndex, 107 | _allPresets.Select(p => string.IsNullOrEmpty(p.Name) ? noPresetText : p.Name).ToArray()); 108 | GUI.enabled = true; 109 | 110 | if (newSelectedIndex != selectedIndex) 111 | { 112 | if (newSelectedIndex == _allPresets.Count - 1) 113 | { 114 | Selection.activeObject = _quickSaveSettingsAsset.QuickSaveArchetypeCollection; 115 | return; 116 | } 117 | foreach (var o in targets) 118 | { 119 | var persistenceAuthoring = (QuickSaveAuthoring) o; 120 | persistenceAuthoring.QuickSaveArchetypeName = _allPresets[newSelectedIndex].Name; 121 | persistenceAuthoring.FullTypeNamesToPersist.Clear(); 122 | EditorUtility.SetDirty(persistenceAuthoring); 123 | } 124 | } 125 | } 126 | else 127 | { 128 | GUI.enabled = false; 129 | EditorGUILayout.Popup("Preset", 0, _mixedPresetDropDownList); 130 | GUI.enabled = true; 131 | } 132 | 133 | GUILayout.Space(8); 134 | EditorGUILayout.LabelField(allNoPreset ? "Type Combination (No Preset)" : "Type Combination"); 135 | 136 | // Select the Types 137 | if (sameHashes) 138 | { 139 | GUI.enabled = allNoPreset; 140 | 141 | for (int i = 0; i < singleTargetTypeList.Count; i++) 142 | { 143 | int selectedIndex = _cachedFullTypeNames.FindIndex(s => s == singleTargetTypeList[i]); 144 | 145 | EditorGUILayout.BeginHorizontal(); 146 | int newSelectedIndex = EditorGUILayout.Popup($"Type {i}", selectedIndex, 147 | _cachedFullTypeNames.Select(s => string.IsNullOrEmpty(s) ? "None" : _quickSaveSettingsAsset.GetPrettyNameInEditor(s)).ToArray()); 148 | 149 | if (GUI.enabled && GUILayout.Button("-", GUILayout.Width(18), GUILayout.Height(18))) 150 | { 151 | foreach (var o in targets) 152 | { 153 | var persistenceAuthoring = (QuickSaveAuthoring) o; 154 | persistenceAuthoring.FullTypeNamesToPersist.RemoveAt(i); 155 | EditorUtility.SetDirty(persistenceAuthoring); 156 | } 157 | } 158 | EditorGUILayout.EndHorizontal(); 159 | 160 | if (newSelectedIndex != selectedIndex) 161 | { 162 | foreach (var o in targets) 163 | { 164 | var persistenceAuthoring = (QuickSaveAuthoring) o; 165 | persistenceAuthoring.FullTypeNamesToPersist[i] = _cachedFullTypeNames[newSelectedIndex]; 166 | persistenceAuthoring.FullTypeNamesToPersist = persistenceAuthoring.FullTypeNamesToPersist.Distinct().ToList(); 167 | persistenceAuthoring.FullTypeNamesToPersist.Sort(); 168 | EditorUtility.SetDirty(persistenceAuthoring); 169 | } 170 | } 171 | } 172 | 173 | if (singleTargetTypeList.Count == 0 && !allNoPreset) 174 | { 175 | GUI.enabled = false; 176 | EditorGUILayout.TextField("--No Types in Preset--"); 177 | GUI.enabled = true; 178 | } 179 | 180 | GUI.enabled = true; 181 | } 182 | else 183 | { 184 | GUI.enabled = false; 185 | EditorGUILayout.TextField("--Mixed Type Combinations--"); 186 | GUI.enabled = true; 187 | } 188 | 189 | if (allNoPreset) 190 | { 191 | GUI.enabled = !singleTarget.FullTypeNamesToPersist.Contains(""); 192 | string buttonText = GUI.enabled ? "Add Type" : "Add Type (You still have a \"None\" Value)"; 193 | if (sameHashes && GUILayout.Button(buttonText)) 194 | { 195 | foreach (var o in targets) 196 | { 197 | var persistenceAuthoring = (QuickSaveAuthoring) o; 198 | persistenceAuthoring.FullTypeNamesToPersist.Add(""); 199 | EditorUtility.SetDirty(persistenceAuthoring); 200 | } 201 | } 202 | GUI.enabled = true; 203 | } 204 | 205 | GUILayout.Space(8); 206 | if (GUILayout.Button("Reset")) 207 | { 208 | foreach (var o in targets) 209 | { 210 | var persistenceAuthoring = (QuickSaveAuthoring) o; 211 | persistenceAuthoring.QuickSaveArchetypeName = ""; 212 | persistenceAuthoring.FullTypeNamesToPersist.Clear(); 213 | EditorUtility.SetDirty(persistenceAuthoring); 214 | } 215 | } 216 | 217 | if (allNoPreset && singleTargetTypeList.Count == 0) 218 | EditorGUILayout.HelpBox("Select a Preset or make a custom Type Combination with 'Add'", MessageType.Info); 219 | } 220 | } 221 | } -------------------------------------------------------------------------------- /QuickSave.Editor/QuickSaveAuthoringInspector.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b53433b123ec58741b1e4664d08d338a 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /QuickSave.Editor/QuickSaveSettingsAssetInspector.cs: -------------------------------------------------------------------------------- 1 | // Author: Jonas De Maeseneer 2 | 3 | using System.Collections.Generic; 4 | using Unity.Mathematics; 5 | using UnityEditor; 6 | using UnityEditorInternal; 7 | using UnityEngine; 8 | 9 | namespace QuickSave.Editor 10 | { 11 | [CustomEditor(typeof(QuickSaveSettingsAsset))] 12 | public class QuickSaveSettingsAssetInspector : UnityEditor.Editor 13 | { 14 | private List _allAvailableTypes; 15 | private ReorderableList _reorderableList; 16 | private QuickSaveSettingsAsset _quickSaveSettingsAsset; 17 | 18 | private bool _foldoutSettings = false; 19 | 20 | private void OnEnable() 21 | { 22 | _quickSaveSettingsAsset = (QuickSaveSettingsAsset) target; 23 | _reorderableList = new ReorderableList(_quickSaveSettingsAsset.AllQuickSaveTypeInfos, typeof(QuickSaveSettingsAsset.QuickSaveSettingsTypeInfo)); 24 | _reorderableList.drawHeaderCallback = DrawHeaderCallback; 25 | _reorderableList.draggable = false; 26 | _reorderableList.drawElementCallback = DrawElementCallback; 27 | _reorderableList.elementHeight = EditorGUIUtility.singleLineHeight + 2; 28 | _reorderableList.onAddCallback = OnAddCallback; 29 | _reorderableList.onRemoveCallback = OnRemoveCallback; 30 | } 31 | 32 | private void OnRemoveCallback(ReorderableList list) 33 | { 34 | list.list.RemoveAt(list.index); 35 | EditorUtility.SetDirty(_quickSaveSettingsAsset); 36 | } 37 | 38 | private void OnAddCallback(ReorderableList list) 39 | { 40 | FindTypeWindow window = EditorWindow.GetWindow(); 41 | window.OnTypeChosen = fullTypeName => 42 | { 43 | _quickSaveSettingsAsset.AddQuickSaveTypeInEditor(fullTypeName); 44 | // trigger the list to update 45 | _reorderableList.list = _quickSaveSettingsAsset.AllQuickSaveTypeInfos; 46 | Repaint(); 47 | }; 48 | } 49 | 50 | public override void OnInspectorGUI() 51 | { 52 | GUI.enabled = !EditorApplication.isPlayingOrWillChangePlaymode; 53 | 54 | _reorderableList.DoLayoutList(); 55 | if (GUILayout.Button("Force Update Type Data")) 56 | { 57 | var oldInfo = new List(_quickSaveSettingsAsset.AllQuickSaveTypeInfos); 58 | _quickSaveSettingsAsset.ClearPersistableTypesInEditor(); 59 | for (int i = oldInfo.Count - 1; i >= 0; i--) 60 | { 61 | var infoToRetain = oldInfo[i]; 62 | _quickSaveSettingsAsset.AddQuickSaveTypeInEditor(infoToRetain.FullTypeName, infoToRetain.MaxElements); 63 | } 64 | } 65 | 66 | EditorGUILayout.Space(); 67 | EditorGUI.BeginChangeCheck(); 68 | _quickSaveSettingsAsset.QuickSaveArchetypeCollection = EditorGUILayout.ObjectField("Preset Collection", _quickSaveSettingsAsset.QuickSaveArchetypeCollection, typeof(QuickSaveArchetypeCollection), false) as QuickSaveArchetypeCollection; 69 | if (EditorGUI.EndChangeCheck()) 70 | { 71 | EditorUtility.SetDirty(_quickSaveSettingsAsset); 72 | } 73 | _foldoutSettings = EditorGUILayout.Foldout(_foldoutSettings, "Advanced Options", true); 74 | EditorGUI.BeginChangeCheck(); 75 | if (_foldoutSettings) 76 | { 77 | _quickSaveSettingsAsset.VerboseBakingLog = EditorGUILayout.ToggleLeft("Verbose Baking Log", _quickSaveSettingsAsset.VerboseBakingLog); 78 | _quickSaveSettingsAsset.ForceUseGroupedJobsInEditor = EditorGUILayout.ToggleLeft("Force Grouped Jobs In Editor (Less Safety Checks)", _quickSaveSettingsAsset.ForceUseGroupedJobsInEditor); 79 | _quickSaveSettingsAsset.ForceUseNonGroupedJobsInBuild = EditorGUILayout.ToggleLeft("Force Non-Grouped Jobs In Build (Usually Slower)", _quickSaveSettingsAsset.ForceUseNonGroupedJobsInBuild); 80 | } 81 | if (EditorGUI.EndChangeCheck()) 82 | { 83 | EditorUtility.SetDirty(_quickSaveSettingsAsset); 84 | } 85 | 86 | if (EditorUtility.IsDirty(_quickSaveSettingsAsset)) 87 | { 88 | EditorGUILayout.HelpBox("Save for the changes to go into effect! (Ctrl+S)", MessageType.Info); 89 | } 90 | 91 | GUI.enabled = true; 92 | } 93 | 94 | private void DrawElementCallback(Rect rect, int index, bool isactive, bool isfocused) 95 | { 96 | var info = _quickSaveSettingsAsset.AllQuickSaveTypeInfos[index]; 97 | int maxElementBoxWidth = math.max(50, 10 * info.MaxElements.ToString().Length); 98 | string elementName = info.FullTypeName + (info.IsBuffer ? " [B]" : ""); 99 | rect.xMax -= maxElementBoxWidth + EditorGUIUtility.standardVerticalSpacing; 100 | EditorGUI.LabelField(rect, elementName); 101 | 102 | if (info.IsBuffer) 103 | { 104 | rect.xMax += maxElementBoxWidth + EditorGUIUtility.standardVerticalSpacing; 105 | rect.xMin = rect.xMax - (maxElementBoxWidth + EditorGUIUtility.standardVerticalSpacing); 106 | rect.height -= 1; 107 | 108 | EditorGUI.BeginChangeCheck(); 109 | info.MaxElements = math.clamp(EditorGUI.IntField(rect, info.MaxElements), 1, QuickSaveMetaData.MaxValueForAmount); 110 | if (EditorGUI.EndChangeCheck()) 111 | { 112 | _quickSaveSettingsAsset.AllQuickSaveTypeInfos[index] = info; 113 | EditorUtility.SetDirty(_quickSaveSettingsAsset); 114 | } 115 | } 116 | } 117 | 118 | private void DrawHeaderCallback(Rect rect) 119 | { 120 | EditorGUI.LabelField(rect, "Types"); 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /QuickSave.Editor/QuickSaveSettingsAssetInspector.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9cbacf77850a8254d8a9048faeefa486 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /QuickSave.Editor/QuickSaveSubSceneInspector.cs: -------------------------------------------------------------------------------- 1 | // Author: Jonas De Maeseneer 2 | 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using Unity.Collections; 6 | using Unity.Entities; 7 | using Unity.Scenes; 8 | using UnityEditor; 9 | using UnityEngine; 10 | using Hash128 = Unity.Entities.Hash128; 11 | 12 | namespace QuickSave.Editor 13 | { 14 | [CanEditMultipleObjects] 15 | [CustomEditor(typeof(QuickSaveSubScene))] 16 | public class QuickSaveSubSceneInspector : UnityEditor.Editor 17 | { 18 | public override void OnInspectorGUI() 19 | { 20 | List subSceneGUIDS = targets 21 | .Select(o => ((QuickSaveSubScene)o).GetComponent()) 22 | .Where(comp => comp != null) 23 | .Select(comp => comp.SceneGUID) 24 | .ToList(); 25 | if (subSceneGUIDS.Count == 0) 26 | return; 27 | 28 | string postFix = subSceneGUIDS.Count > 1 ? $" ({subSceneGUIDS.Count})" : ""; 29 | GUI.enabled = EditorApplication.isPlaying; 30 | 31 | EditorGUILayout.BeginHorizontal(); 32 | if (GUILayout.Button("Load" + postFix)) 33 | { 34 | LoadSections(subSceneGUIDS); 35 | } 36 | if (GUILayout.Button("Reset To Initial State" + postFix)) 37 | { 38 | ResetToInitialState(subSceneGUIDS); 39 | } 40 | if (GUILayout.Button("Unload" + postFix)) 41 | { 42 | UnloadSections(subSceneGUIDS); 43 | } 44 | EditorGUILayout.EndHorizontal(); 45 | 46 | GUI.enabled = true; 47 | } 48 | 49 | private static void LoadSections(List guids) 50 | { 51 | foreach (var world in World.All) 52 | { 53 | EntityManager entityManager = world.EntityManager; 54 | var query = entityManager.CreateEntityQuery(ComponentType.ReadOnly(), ComponentType.Exclude()); 55 | var entities = query.ToEntityArray(Allocator.Temp); 56 | foreach (Entity entity in entities) 57 | { 58 | Hash128 guid = entityManager.GetComponentData(entity).SceneGUID; 59 | if (guids.Contains(guid)) 60 | { 61 | entityManager.AddComponent(entity); 62 | } 63 | } 64 | entities.Dispose(); 65 | } 66 | } 67 | 68 | private static void ResetToInitialState(List guids) 69 | { 70 | foreach (var world in World.All) 71 | { 72 | EntityManager entityManager = world.EntityManager; 73 | var query = entityManager.CreateEntityQuery(ComponentType.ReadOnly(), ComponentType.ReadWrite()); 74 | var entities = query.ToEntityArray(Allocator.Temp); 75 | foreach (Entity entity in entities) 76 | { 77 | var container = entityManager.GetComponentData(entity); 78 | if (guids.Contains(container.GUID) && QuickSaveAPI.IsInitialContainer(entity, container)) 79 | { 80 | entityManager.GetBuffer(entity).Add(new DataTransferRequest() 81 | { 82 | ExecutingSystem = world.GetOrCreateSystem(), 83 | RequestType = DataTransferRequest.Type.FromDataContainerToEntities 84 | }); 85 | } 86 | } 87 | entities.Dispose(); 88 | } 89 | } 90 | 91 | private static void UnloadSections(List guids) 92 | { 93 | foreach (var world in World.All) 94 | { 95 | EntityManager entityManager = world.EntityManager; 96 | var query = entityManager.CreateEntityQuery(ComponentType.ReadOnly(), ComponentType.ReadOnly()); 97 | var entities = query.ToEntityArray(Allocator.Temp); 98 | foreach (Entity entity in entities) 99 | { 100 | Hash128 guid = entityManager.GetComponentData(entity).SceneGUID; 101 | if (guids.Contains(guid)) 102 | { 103 | entityManager.AddComponent(entity); 104 | } 105 | } 106 | 107 | entities.Dispose(); 108 | } 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /QuickSave.Editor/QuickSaveSubSceneInspector.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a5d65ac5abf828b4781a861356d00b8f 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /QuickSave.Editor/com.studioaurelius.quicksave.editor.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.studioaurelius.quicksave.editor", 3 | "rootNamespace": "", 4 | "references": [ 5 | "GUID:42697df707fc47a4aa81e8cf52f99bfe", 6 | "GUID:aaa622c5ca1ba8f45adaf6d3b4809534", 7 | "GUID:d41a1e45755de594b8709bf955fc2dcb", 8 | "GUID:e0cd26848372d4e5c891c569017e11f1", 9 | "GUID:734d92eba21c94caba915361bd5ac177", 10 | "GUID:c38fb62397af04c51b143415e1db6d90", 11 | "GUID:8819f35a0fc84499b990e90a4ca1911f", 12 | "GUID:d8b63aba1907145bea998dd612889d6b", 13 | "GUID:e04e6c86a9f3947eb95fded39f9e60cc", 14 | "GUID:5f3cf485eb0554709a8abbeace890c86" 15 | ], 16 | "includePlatforms": [ 17 | "Editor" 18 | ], 19 | "excludePlatforms": [], 20 | "allowUnsafeCode": false, 21 | "overrideReferences": false, 22 | "precompiledReferences": [], 23 | "autoReferenced": false, 24 | "defineConstraints": [], 25 | "versionDefines": [], 26 | "noEngineReferences": false 27 | } -------------------------------------------------------------------------------- /QuickSave.Editor/com.studioaurelius.quicksave.editor.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 72688ec49e10379479291ea0354d26a7 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /QuickSave.Tests.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: bfefbe10b4c34104596143a60a0d18bb 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /QuickSave.Tests/BufferDataTests.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f4993a02aa6f84c4bbdb11ec4f7cf55f 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /QuickSave.Tests/ComponentDataTests.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4931792a102ea5a4aa637c898e9bfadc 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /QuickSave.Tests/ECSTestsFixture.cs: -------------------------------------------------------------------------------- 1 | // Author: Jonas De Maeseneer 2 | 3 | using System.Collections.Generic; 4 | using NUnit.Framework; 5 | using QuickSave.Baking; 6 | using Unity.Collections; 7 | using Unity.Entities; 8 | using Unity.Entities.CodeGeneratedJobForEach; 9 | using UnityEngine; 10 | using UnityEngine.SceneManagement; 11 | using Hash128 = Unity.Entities.Hash128; 12 | using Object = UnityEngine.Object; 13 | 14 | namespace QuickSave.Tests 15 | { 16 | public abstract class EcsTestsFixture 17 | { 18 | protected World PreviousWorld; 19 | protected World World; 20 | protected EntityManager EntityManager; 21 | 22 | protected World BakingWorld; 23 | protected EntityManager BakingEntityManager; 24 | 25 | protected NativeList>> BlobAssetsToDisposeOnTearDown; 26 | protected BlobAssetStore TestBlobAssetStore; 27 | 28 | [SetUp] 29 | public virtual void Setup() 30 | { 31 | PreviousWorld = World.DefaultGameObjectInjectionWorld; 32 | World = World.DefaultGameObjectInjectionWorld = new World("Test World"); 33 | EntityManager = World.EntityManager; 34 | BakingWorld = new World("Test Baking World"); 35 | BakingEntityManager = BakingWorld.EntityManager; 36 | QuickSaveSettings.Initialize(); 37 | BlobAssetsToDisposeOnTearDown = new NativeList>>(16, Allocator.Persistent); 38 | TestBlobAssetStore = new BlobAssetStore(64); 39 | } 40 | 41 | [TearDown] 42 | public virtual void TearDown() 43 | { 44 | if (EntityManager != default && World.IsCreated) 45 | { 46 | // Clean up systems before calling CheckInternalConsistency because we might have filters etc 47 | // holding on SharedComponentData making checks fail 48 | while (World.Systems.Count > 0) 49 | { 50 | World.DestroySystemManaged(World.Systems[0]); 51 | } 52 | EntityManager.Debug.CheckInternalConsistency(); 53 | World.Dispose(); 54 | World = null; 55 | World.DefaultGameObjectInjectionWorld = PreviousWorld; 56 | PreviousWorld = null; 57 | EntityManager = default; 58 | } 59 | 60 | if (BakingEntityManager != default && BakingWorld.IsCreated) 61 | { 62 | while (BakingWorld.Systems.Count > 0) 63 | { 64 | BakingWorld.DestroySystemManaged(BakingWorld.Systems[0]); 65 | } 66 | BakingEntityManager.Debug.CheckInternalConsistency(); 67 | BakingWorld.Dispose(); 68 | BakingWorld = null; 69 | BakingEntityManager = default; 70 | } 71 | 72 | foreach (var rootGameObject in SceneManager.GetActiveScene().GetRootGameObjects()) 73 | { 74 | Object.DestroyImmediate(rootGameObject); 75 | } 76 | QuickSaveSettings.CleanUp(); 77 | 78 | for (int i = 0; i < BlobAssetsToDisposeOnTearDown.Length; i++) 79 | { 80 | BlobAssetsToDisposeOnTearDown[i].Dispose(); 81 | } 82 | BlobAssetsToDisposeOnTearDown.Dispose(); 83 | TestBlobAssetStore.Dispose(); 84 | } 85 | 86 | protected static QuickSaveSettingsAsset CreateTestSettings(bool groupedJobs = false, bool removeFirst = false, int maxBufferElements = -1) 87 | { 88 | QuickSaveSettingsAsset settingsAsset = ScriptableObject.CreateInstance(); 89 | settingsAsset.AddQuickSaveTypeInEditor(typeof(EcsTestData).FullName); 90 | settingsAsset.AddQuickSaveTypeInEditor(typeof(EcsTestFloatData2).FullName); 91 | settingsAsset.AddQuickSaveTypeInEditor(typeof(EcsTestData5).FullName); 92 | settingsAsset.AddQuickSaveTypeInEditor(typeof(DynamicBufferData1).FullName, maxBufferElements); 93 | settingsAsset.AddQuickSaveTypeInEditor(typeof(DynamicBufferData2).FullName, maxBufferElements); 94 | settingsAsset.AddQuickSaveTypeInEditor(typeof(DynamicBufferData3).FullName, maxBufferElements); 95 | settingsAsset.AddQuickSaveTypeInEditor(typeof(EmptyEcsTestData).FullName); 96 | settingsAsset.AddQuickSaveTypeInEditor(typeof(ComponentDataTests.EcsPersistingTestData).FullName); 97 | settingsAsset.AddQuickSaveTypeInEditor(typeof(ComponentDataTests.EcsPersistingFloatTestData2).FullName); 98 | settingsAsset.AddQuickSaveTypeInEditor(typeof(ComponentDataTests.EcsPersistingTestData5).FullName); 99 | settingsAsset.AddQuickSaveTypeInEditor(typeof(BufferDataTests.PersistentDynamicBufferData1).FullName, maxBufferElements); 100 | settingsAsset.AddQuickSaveTypeInEditor(typeof(BufferDataTests.PersistentDynamicBufferData2).FullName, maxBufferElements); 101 | settingsAsset.AddQuickSaveTypeInEditor(typeof(BufferDataTests.PersistentDynamicBufferData3).FullName, maxBufferElements); 102 | settingsAsset.AddQuickSaveTypeInEditor(typeof(EnableDataTests.TestComponent).FullName); 103 | settingsAsset.AddQuickSaveTypeInEditor(typeof(EnableDataTests.TestTagComponent).FullName); 104 | settingsAsset.AddQuickSaveTypeInEditor(typeof(EnableDataTests.TestBufferComponent).FullName, maxBufferElements); 105 | 106 | settingsAsset.ForceUseGroupedJobsInEditor = groupedJobs; 107 | settingsAsset.ForceUseNonGroupedJobsInBuild = !groupedJobs; 108 | if (removeFirst) 109 | { 110 | settingsAsset.AllQuickSaveTypeInfos.RemoveAt(0); 111 | } 112 | 113 | settingsAsset.QuickSaveArchetypeCollection = ScriptableObject.CreateInstance(); 114 | 115 | // Reset it so the new types are initialized 116 | QuickSaveSettings.CleanUp(); 117 | QuickSaveSettings.Initialize(settingsAsset); 118 | 119 | return settingsAsset; 120 | } 121 | 122 | // Creates a 'fake' SceneInfoRef for single QuickSaveArchetype with a single Type T to track 123 | internal QuickSaveSceneInfoRef CreateFakeSceneInfoRef(int amountEntities) where T : unmanaged 124 | { 125 | var typeHandle = QuickSaveSettings.GetTypeHandleFromTypeIndex(ComponentType.ReadWrite().TypeIndex); 126 | var typeHandleList = new NativeList(1, Allocator.Temp) {typeHandle}; 127 | var creationInfoList = new NativeList(Allocator.Temp) 128 | { 129 | new QuickSaveBakingSystem.QuickSaveArchetypesInSceneCreationInfo 130 | { 131 | AmountEntities = amountEntities, 132 | AmountTypeHandles = 1, 133 | OffsetInQuickSaveTypeHandlesLookupList = 0 134 | } 135 | }; 136 | Hash128 sceneGUID = UnityEngine.Hash128.Compute(typeof(T).FullName); 137 | return QuickSaveBakingSystem.CreateQuickSaveSceneInfoRef(new List {typeHandle}, creationInfoList, typeHandleList, 138 | sceneGUID, 0, TestBlobAssetStore); 139 | } 140 | 141 | [DisableAutoCreation] 142 | internal partial class TestSystem : SystemBase 143 | { 144 | protected override void OnUpdate() { } 145 | } 146 | } 147 | 148 | 149 | public struct EcsTestData : IComponentData 150 | { 151 | public int Value; 152 | 153 | public EcsTestData(int value) 154 | { 155 | this.Value = value; 156 | } 157 | } 158 | public struct EcsTestFloatData2 : IComponentData 159 | { 160 | public float Value0; 161 | public float Value1; 162 | 163 | public EcsTestFloatData2(float value) 164 | { 165 | this.Value0 = value; 166 | this.Value1 = value; 167 | } 168 | } 169 | public struct EcsTestData5 : IComponentData 170 | { 171 | public EcsTestData5(int value) 172 | { 173 | Value0 = value; 174 | Value1 = value; 175 | Value2 = value; 176 | Value3 = value; 177 | Value4 = value; 178 | } 179 | 180 | public int Value0; 181 | public int Value1; 182 | public int Value2; 183 | public int Value3; 184 | public int Value4; 185 | } 186 | 187 | 188 | [InternalBufferCapacity(2)] 189 | public struct DynamicBufferData1 : IBufferElementData 190 | { 191 | public int Value; 192 | 193 | public override string ToString() 194 | { 195 | return Value.ToString(); 196 | } 197 | } 198 | 199 | public struct DynamicBufferData2 : IBufferElementData 200 | { 201 | #pragma warning disable 649 202 | public float Value; 203 | #pragma warning restore 649 204 | 205 | public override string ToString() 206 | { 207 | return Value.ToString(); 208 | } 209 | } 210 | 211 | public struct DynamicBufferData3 : IBufferElementData, IEnableableComponent 212 | { 213 | public byte Value; 214 | 215 | public override string ToString() 216 | { 217 | return Value.ToString(); 218 | } 219 | } 220 | 221 | public struct EmptyEcsTestData : IComponentData 222 | { 223 | 224 | } 225 | } -------------------------------------------------------------------------------- /QuickSave.Tests/ECSTestsFixture.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ef4c475c675e2124cbc5327792977700 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /QuickSave.Tests/EnableDataTests.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ab8044bf784385445832126e68b59afd 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /QuickSave.Tests/SystemTests.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6b185f067d21aba4b8b28eb688b10669 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /QuickSave.Tests/com.studioaurelius.quicksave.tests.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.studioaurelius.quicksave.tests", 3 | "rootNamespace": "", 4 | "references": [ 5 | "GUID:aaa622c5ca1ba8f45adaf6d3b4809534", 6 | "GUID:42697df707fc47a4aa81e8cf52f99bfe", 7 | "GUID:27619889b8ba8c24980f49ee34dbb44a", 8 | "GUID:0acc523941302664db1f4e527237feb3", 9 | "GUID:e0cd26848372d4e5c891c569017e11f1", 10 | "GUID:734d92eba21c94caba915361bd5ac177", 11 | "GUID:8819f35a0fc84499b990e90a4ca1911f", 12 | "GUID:8e2f79f713f5a47dcb69903037d4bc0e", 13 | "GUID:d8b63aba1907145bea998dd612889d6b", 14 | "GUID:e04e6c86a9f3947eb95fded39f9e60cc", 15 | "GUID:2665a8d13d1b3f18800f46e256720795" 16 | ], 17 | "includePlatforms": [ 18 | "Editor" 19 | ], 20 | "excludePlatforms": [], 21 | "allowUnsafeCode": true, 22 | "overrideReferences": true, 23 | "precompiledReferences": [ 24 | "nunit.framework.dll" 25 | ], 26 | "autoReferenced": false, 27 | "defineConstraints": [ 28 | "UNITY_INCLUDE_TESTS" 29 | ], 30 | "versionDefines": [], 31 | "noEngineReferences": false 32 | } -------------------------------------------------------------------------------- /QuickSave.Tests/com.studioaurelius.quicksave.tests.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8063800b251bc6944a1bdefed1bb51e8 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /QuickSave.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6d01d7eeeae86e4439ef406bb31ac421 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /QuickSave/DataLayoutHelpers.cs: -------------------------------------------------------------------------------- 1 | // Author: Jonas De Maeseneer 2 | 3 | using System; 4 | using System.Diagnostics; 5 | using System.Diagnostics.Contracts; 6 | using Unity.Burst; 7 | using Unity.Collections; 8 | using Unity.Collections.LowLevel.Unsafe; 9 | using Unity.Entities; 10 | 11 | namespace QuickSave 12 | { 13 | public static class DataLayoutHelpers 14 | { 15 | [Pure] 16 | private static unsafe BlobAssetReference> BuildTypeInfoBlobAsset( 17 | ref QuickSaveArchetypesInScene quickSaveArchetypesInScene, int amountEntities, out int sizePerEntity) 18 | { 19 | NativeArray blobArrayAsNative = 20 | NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray(quickSaveArchetypesInScene.QuickSaveTypeHandles.GetUnsafePtr(), 21 | quickSaveArchetypesInScene.QuickSaveTypeHandles.Length, Allocator.None); 22 | 23 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 24 | var safety = AtomicSafetyHandle.Create(); 25 | NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref blobArrayAsNative, safety); 26 | #endif 27 | 28 | var blobAssetReference = BuildTypeInfoBlobAsset(blobArrayAsNative, amountEntities, out sizePerEntity); 29 | 30 | #if ENABLE_UNITY_COLLECTIONS_CHECKS 31 | AtomicSafetyHandle.Release(safety); 32 | #endif 33 | 34 | return blobAssetReference; 35 | } 36 | 37 | private static BlobAssetReference> BuildTypeInfoBlobAsset( 38 | NativeArray quickSaveTypeHandles, int amountEntities, out int sizePerEntity) 39 | { 40 | BlobAssetReference> blobAssetReference; 41 | int currentOffset = 0; 42 | sizePerEntity = 0; 43 | 44 | using (BlobBuilder blobBuilder = new BlobBuilder(Allocator.Temp)) 45 | { 46 | ref BlobArray blobArray = 47 | ref blobBuilder.ConstructRoot>(); 48 | 49 | BlobBuilderArray blobBuilderArray = blobBuilder.Allocate(ref blobArray, quickSaveTypeHandles.Length); 50 | 51 | for (int i = 0; i < blobBuilderArray.Length; i++) 52 | { 53 | QuickSaveTypeHandle quickSaveTypeHandle = quickSaveTypeHandles[i]; 54 | TypeManager.TypeInfo typeInfo = TypeManager.GetTypeInfo(QuickSaveSettings.GetTypeIndex(quickSaveTypeHandle)); 55 | int maxElements = QuickSaveSettings.GetMaxElements(quickSaveTypeHandle); 56 | ValidateType(typeInfo); 57 | 58 | blobBuilderArray[i] = new QuickSaveArchetypeDataLayout.TypeInfo() 59 | { 60 | QuickSaveTypeHandle = quickSaveTypeHandles[i], 61 | ElementSize = typeInfo.ElementSize, 62 | IsBuffer = typeInfo.Category == TypeManager.TypeCategory.BufferData, 63 | MaxElements = maxElements, 64 | Offset = currentOffset 65 | }; 66 | int sizeForComponent = (typeInfo.ElementSize * maxElements) + QuickSaveMetaData.SizeOfStruct; 67 | sizePerEntity += sizeForComponent; 68 | currentOffset += sizeForComponent * amountEntities; 69 | } 70 | 71 | blobAssetReference = blobBuilder.CreateBlobAssetReference>(Allocator.Persistent); 72 | } 73 | 74 | return blobAssetReference; 75 | } 76 | 77 | internal static void BuildDataLayouts(ref QuickSaveSceneInfo quickSaveSceneInfo, DynamicBuffer layoutsToFill) 78 | { 79 | int offset = 0; 80 | layoutsToFill.ResizeUninitialized(quickSaveSceneInfo.QuickSaveArchetypesInScene.Length); 81 | 82 | for (var i = 0; i < quickSaveSceneInfo.QuickSaveArchetypesInScene.Length; i++) 83 | { 84 | ref QuickSaveArchetypesInScene quickSaveArchetypesInScene = ref quickSaveSceneInfo.QuickSaveArchetypesInScene[i]; 85 | 86 | var dataLayout = new QuickSaveArchetypeDataLayout() 87 | { 88 | Amount = quickSaveArchetypesInScene.AmountEntities, 89 | TypeInfoArrayRef = BuildTypeInfoBlobAsset(ref quickSaveArchetypesInScene, quickSaveArchetypesInScene.AmountEntities, out int sizePerEntity), 90 | SizePerEntity = sizePerEntity, 91 | Offset = offset 92 | }; 93 | offset += quickSaveArchetypesInScene.AmountEntities * sizePerEntity; 94 | 95 | layoutsToFill[i] = dataLayout; 96 | } 97 | } 98 | 99 | private static unsafe bool CheckResize(NativeArray dataLayouts, NativeArray newAmounts, out int oldSize, out int newSize) 100 | { 101 | CheckNewAmountsArray(dataLayouts, newAmounts); 102 | 103 | oldSize = 0; 104 | newSize = 0; 105 | bool difference = false; 106 | 107 | for (var i = 0; i < dataLayouts.Length; i++) 108 | { 109 | var dataLayout = dataLayouts[i]; 110 | int newAmount = newAmounts[i]; 111 | 112 | oldSize += dataLayout.Amount * dataLayout.SizePerEntity; 113 | newSize += newAmount * dataLayout.SizePerEntity; 114 | 115 | if (newAmount != dataLayout.Amount) 116 | difference = true; 117 | } 118 | 119 | return difference; 120 | } 121 | 122 | internal static unsafe void ResizeDataContainer(NativeList data, NativeArray dataLayouts, NativeArray newAmounts) 123 | { 124 | if (!CheckResize(dataLayouts, newAmounts, out int oldSize, out int newSize)) 125 | return; 126 | 127 | if (newSize > oldSize) 128 | data.Resize(newSize, NativeArrayOptions.ClearMemory); 129 | 130 | var startOfDataPtr = (byte*) data.GetUnsafePtr(); // important to do this after resize! 131 | 132 | { 133 | int newByteOffset = 0; 134 | for (int i = 0; i < dataLayouts.Length; i++) 135 | { 136 | var dataLayout = dataLayouts[i]; 137 | int newAmount = newAmounts[i]; 138 | 139 | int oldByteOffset = dataLayout.Offset; 140 | 141 | // ONLY SHRINK IN FIRST LOOP 142 | if (newAmount < dataLayout.Amount) 143 | { 144 | int oldByteAmount = dataLayout.Amount * dataLayout.SizePerEntity; 145 | var subArray = data.AsArray().GetSubArray(oldByteOffset, oldByteAmount); 146 | ShrinkFixUp(subArray, newAmount, dataLayout); 147 | } 148 | else 149 | { 150 | newAmount = dataLayout.Amount; 151 | } 152 | 153 | AssertMovingToLeft(oldByteOffset, newByteOffset); 154 | 155 | // MOVE SUB ARRAY TO LEFT 156 | int subArraySize = newAmount * dataLayout.SizePerEntity; 157 | UnsafeUtility.MemMove(startOfDataPtr + newByteOffset, startOfDataPtr + oldByteOffset, subArraySize); 158 | 159 | // Update data layout 160 | dataLayout.Amount = newAmount; 161 | dataLayout.Offset = newByteOffset; 162 | dataLayouts[i] = dataLayout; 163 | 164 | // newByteOffset for next layout 165 | newByteOffset += subArraySize; 166 | } 167 | } 168 | 169 | { 170 | int newByteOffset = newSize; 171 | for (int i = dataLayouts.Length - 1; i >= 0; i--) 172 | { 173 | var dataLayout = dataLayouts[i]; 174 | int newAmount = newAmounts[i]; 175 | 176 | int oldByteOffset = dataLayout.Offset; 177 | 178 | // ONLY GROW IN SECOND LOOP 179 | if (newAmount > dataLayout.Amount) 180 | { 181 | int newByteAmount = newAmount * dataLayout.SizePerEntity; 182 | var subArray = data.AsArray().GetSubArray(oldByteOffset, newByteAmount); 183 | GrowFixUp(subArray, newAmount, dataLayout); 184 | } 185 | else 186 | { 187 | // Assert, if it's not growing, the amount is already correct (since we shrunk already) 188 | AssertNewAmountAlreadyCorrect(newAmount, dataLayout); 189 | } 190 | 191 | int subArraySize = newAmount * dataLayout.SizePerEntity; 192 | 193 | // newByteOffset for this layout 194 | newByteOffset -= subArraySize; 195 | AssertMovingToRight(oldByteOffset, newByteOffset); 196 | 197 | // MOVE SUB ARRAY TO RIGHT 198 | UnsafeUtility.MemMove(startOfDataPtr + newByteOffset, startOfDataPtr + oldByteOffset, subArraySize); 199 | 200 | // Update data layout 201 | dataLayout.Amount = newAmount; 202 | dataLayout.Offset = newByteOffset; 203 | dataLayouts[i] = dataLayout; 204 | } 205 | } 206 | 207 | if (newSize < oldSize) 208 | data.Resize(newSize, NativeArrayOptions.ClearMemory); 209 | } 210 | 211 | private static unsafe void ShrinkFixUp(NativeArray data, int newAmount, QuickSaveArchetypeDataLayout dataLayout) 212 | { 213 | // Shrinking -> Move memory from high to low -> do lowest memory block first 214 | AssertNotResized(data, newAmount, dataLayout); // if shrinking ensure the array is not yet resized (dataLayout.Amount represents oldAmount) 215 | 216 | ref BlobArray infoArray = ref dataLayout.TypeInfoArrayRef.Value; 217 | 218 | int newOffset = 0; 219 | var startOfDataPtr = (byte*) data.GetUnsafePtr(); 220 | for (int i = 0; i < infoArray.Length; i++) 221 | { 222 | QuickSaveArchetypeDataLayout.TypeInfo info = infoArray[i]; 223 | int oldOffset = info.Offset; 224 | 225 | // Update data layout 226 | info.Offset = newOffset; 227 | infoArray[i] = info; 228 | 229 | // Move Data 230 | UnsafeUtility.MemMove(startOfDataPtr + newOffset, startOfDataPtr + oldOffset, info.SizePerEntityForThisType * newAmount); 231 | 232 | // newOffset for next type 233 | newOffset += info.SizePerEntityForThisType * newAmount; 234 | } 235 | } 236 | 237 | private static unsafe void GrowFixUp(NativeArray data, int newAmount, QuickSaveArchetypeDataLayout dataLayout) 238 | { 239 | // Growing -> Move memory from low to high -> do highest memory block first 240 | AssertResized(data, newAmount, dataLayout); // if growing ensure the array is already resized 241 | 242 | ref BlobArray infoArray = ref dataLayout.TypeInfoArrayRef.Value; 243 | int oldAmount = dataLayout.Amount; 244 | 245 | int newOffset = data.Length; 246 | var startOfDataPtr = (byte*) data.GetUnsafePtr(); // Important that this call is after the resize 247 | for (int i = infoArray.Length - 1; i >= 0; i--) 248 | { 249 | QuickSaveArchetypeDataLayout.TypeInfo info = infoArray[i]; 250 | int oldOffset = info.Offset; 251 | 252 | // newOffset for this type 253 | newOffset -= info.SizePerEntityForThisType * newAmount; 254 | 255 | // Update data layout 256 | info.Offset = newOffset; 257 | infoArray[i] = info; 258 | 259 | // Move Data 260 | UnsafeUtility.MemMove(startOfDataPtr + newOffset, startOfDataPtr + oldOffset, info.SizePerEntityForThisType * oldAmount); 261 | } 262 | } 263 | 264 | [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] 265 | private static void AssertResized(NativeArray data, int newAmount, QuickSaveArchetypeDataLayout dataLayout) 266 | { 267 | if (data.Length != newAmount * dataLayout.SizePerEntity) 268 | { 269 | throw new ArgumentException("Expected the (sub)array to already have the new grown size!"); 270 | } 271 | } 272 | 273 | [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] 274 | private static void AssertNotResized(NativeArray data, int newAmount, QuickSaveArchetypeDataLayout dataLayout) 275 | { 276 | if (data.Length != dataLayout.Amount * dataLayout.SizePerEntity) 277 | { 278 | throw new ArgumentException("Expected the (sub)array to still have the old non-shrunk size!"); 279 | } 280 | } 281 | 282 | [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] 283 | private static void AssertMovingToRight(int oldByteOffset, int newByteOffset) 284 | { 285 | if (oldByteOffset > newByteOffset) 286 | { 287 | throw new Exception("Expected to only move data to the right (low to high) here!"); 288 | } 289 | } 290 | 291 | [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] 292 | private static void AssertMovingToLeft(int oldByteOffset, int newByteOffset) 293 | { 294 | if (newByteOffset > oldByteOffset) 295 | { 296 | throw new Exception("Expected to only move data to the left (high to low) here!"); 297 | } 298 | } 299 | 300 | [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] 301 | private static void CheckNewAmountsArray(NativeArray dataLayouts, NativeArray newAmounts) 302 | { 303 | if (dataLayouts.Length != newAmounts.Length) 304 | { 305 | throw new Exception("Expected the 'NewAmounts' array to have the same length as the amount of QuickSaveArchetypes in this container!"); 306 | } 307 | } 308 | 309 | [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] 310 | private static void AssertNewAmountAlreadyCorrect(int newAmount, QuickSaveArchetypeDataLayout layout) 311 | { 312 | if (newAmount != layout.Amount) 313 | { 314 | throw new Exception("Expected that if this QuickSaveArchetype layout did not grow, it already had the correct size since shrinking already happened."); 315 | } 316 | } 317 | 318 | [BurstDiscard] // Todo perhaps we need a validate type that is burst compatible 319 | [Conditional("DEBUG")] 320 | private static void ValidateType(TypeManager.TypeInfo typeInfo) 321 | { 322 | if (!QuickSaveSettings.IsSupported(typeInfo, out string notSupportedReason)) 323 | { 324 | throw new NotSupportedException(notSupportedReason); 325 | } 326 | } 327 | } 328 | } -------------------------------------------------------------------------------- /QuickSave/DataLayoutHelpers.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f65d9c3c8035f584bb281f3b69a94b58 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /QuickSave/DefaultQuickSaveSerialization.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8cc9981d62ae0ab468aca7246fe7b5cf 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /QuickSave/IndexInContainer.cs: -------------------------------------------------------------------------------- 1 | // Author: Jonas De Maeseneer 2 | 3 | using System; 4 | using System.Diagnostics; 5 | using Unity.Entities; 6 | 7 | namespace QuickSave 8 | { 9 | // Info for grabbing the correct sub-array & dataLayout for a chunk 10 | [Serializable] 11 | public struct QuickSaveArchetypeIndexInContainer : ISharedComponentData 12 | { 13 | public ushort IndexInContainer; 14 | } 15 | 16 | // This component holds the index into the sub array 17 | public struct LocalIndexInContainer : IComponentData, IEquatable 18 | { 19 | public int LocalIndex; 20 | 21 | public bool Equals(LocalIndexInContainer other) 22 | { 23 | return LocalIndex == other.LocalIndex; 24 | } 25 | } 26 | 27 | // This struct sits in front of every data block in a persisted data array 28 | public readonly struct QuickSaveMetaData 29 | { 30 | private readonly ushort _data; 31 | public const int SizeOfStruct = sizeof(ushort); 32 | 33 | public QuickSaveMetaData(int diff, ushort amount, bool enabled) 34 | { 35 | CheckMaxAmount(amount); 36 | _data = amount; 37 | if (diff != 0) 38 | { 39 | _data |= ChangedFlag; 40 | } 41 | if (enabled) 42 | { 43 | _data |= EnabledFlag; 44 | } 45 | } 46 | 47 | public QuickSaveMetaData(int diff, ushort amount) 48 | { 49 | CheckMaxAmount(amount); 50 | _data = amount; 51 | if (diff != 0) 52 | { 53 | _data |= ChangedFlag; 54 | } 55 | _data |= EnabledFlag; 56 | } 57 | 58 | public int AmountFound => _data & MaxValueForAmount; 59 | public bool FoundOne => AmountFound != 0; 60 | 61 | public bool HasChanged => (_data & ChangedFlag) != 0; 62 | private const ushort ChangedFlag = 0b1000000000000000; // 1000 0000 0000 0000 63 | public bool Enabled => (_data & EnabledFlag) != 0; 64 | private const ushort EnabledFlag = 0b0100000000000000; // 0100 0000 0000 0000 65 | 66 | public const ushort MaxValueForAmount = 0b0011111111111111; // 0011 1111 1111 1111 67 | 68 | [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] 69 | private static void CheckMaxAmount(int amount) 70 | { 71 | if (amount > MaxValueForAmount) 72 | { 73 | throw new ArgumentException($"QuickSaveMetaData expects the 'amount' parameter to be smaller or equal to {MaxValueForAmount}."); 74 | } 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /QuickSave/IndexInContainer.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 865af9070cce3cc4b8cb486dd4a0bef3 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /QuickSave/QuickSaveAPI.cs: -------------------------------------------------------------------------------- 1 | // Author: Jonas De Maeseneer 2 | 3 | using System; 4 | using System.Diagnostics; 5 | using System.Text; 6 | using Unity.Collections; 7 | using Unity.Entities; 8 | using UnityEngine; 9 | using Hash128 = Unity.Entities.Hash128; 10 | 11 | using System.Runtime.CompilerServices; 12 | [assembly:InternalsVisibleTo("com.studioaurelius.quicksave.baking")] 13 | 14 | namespace QuickSave 15 | { 16 | public static class QuickSaveAPI 17 | { 18 | public static readonly ComponentType[] InitializedContainerArchetype = 19 | { 20 | typeof(QuickSaveDataContainer), typeof(QuickSaveDataContainer.Data), typeof(QuickSaveArchetypeDataLayout), 21 | typeof(DataTransferRequest) 22 | }; 23 | public static readonly ComponentType[] InitializedSerializableContainerArchetype = 24 | { 25 | typeof(QuickSaveDataContainer), typeof(QuickSaveDataContainer.Data), typeof(QuickSaveArchetypeDataLayout), 26 | typeof(DataTransferRequest), typeof(RequestSerialization), typeof(RequestDeserialization) 27 | }; 28 | public static readonly ComponentType[] UninitializedContainerArchetype = 29 | { 30 | typeof(QuickSaveDataContainer), typeof(QuickSaveDataContainer.Data), typeof(QuickSaveArchetypeDataLayout), 31 | typeof(RequestDeserialization) 32 | }; 33 | 34 | // The reason for not just storing a bool on the container is that this way you can duplicate/instantiate the initial container 35 | // and the new container won't be the initial one! 36 | public static bool IsInitialContainer(Entity containerEntity, QuickSaveDataContainer container) 37 | { 38 | return containerEntity == container.InitialContainer; 39 | } 40 | 41 | // This creates a duplicate of an existing container & its data 42 | // This is the main way for users to create containers 43 | // QuickSaveSceneSystem makes a valid container for each subscene that loads for the very first time, 44 | // these containers can be safely duplicated after QuickSaveBeginFrameSystem has initialized them with valid 'initial scene state' 45 | public static Entity InstantiateContainer(EntityManager entityManager, Entity validContainerEntity) 46 | { 47 | CheckContainerInitialized(entityManager, validContainerEntity); 48 | return entityManager.Instantiate(validContainerEntity); 49 | } 50 | 51 | // Ecb variant of InstantiateContainer (doesn't have any checks) 52 | public static Entity InstantiateContainer(EntityCommandBuffer ecb, Entity validContainerEntity) 53 | { 54 | return ecb.Instantiate(validContainerEntity); 55 | } 56 | 57 | // This is the second way to create a container, but should only be used in cases when no container exists yet for the subscene. 58 | // Returns an uninitialized container that will only become valid once these 2 things happened in order: 59 | // 1. Deserialization puts valid data into it. 60 | // 2. The Scene is loaded and you had this container as the AutoApplyOnLoad. 61 | public static Entity RequestDeserializeIntoNewUninitializedContainer(EntityManager entityManager, Hash128 sceneGUID, RequestDeserialization request) 62 | { 63 | Entity containerEntity = entityManager.CreateEntity(UninitializedContainerArchetype); 64 | entityManager.SetComponentData(containerEntity, new QuickSaveDataContainer 65 | { 66 | GUID = sceneGUID, 67 | }); 68 | entityManager.SetComponentData(containerEntity, request); 69 | entityManager.SetName(containerEntity, QuickSaveSettings.GetQuickSaveContainerName()); 70 | return containerEntity; 71 | } 72 | 73 | // Ecb variant of RequestDeserializeIntoNewUninitializedContainer 74 | public static Entity RequestDeserializeIntoNewUninitializedContainer(EntityCommandBuffer ecb, Hash128 sceneGUID, RequestDeserialization request) 75 | { 76 | Entity containerEntity = ecb.CreateEntity(); 77 | ecb.AddComponent(containerEntity, new QuickSaveDataContainer 78 | { 79 | GUID = sceneGUID, 80 | }); 81 | ecb.AddBuffer(containerEntity); 82 | ecb.AddBuffer(containerEntity); 83 | ecb.AddComponent(containerEntity, request); 84 | ecb.SetName(containerEntity, QuickSaveSettings.GetQuickSaveContainerName()); 85 | return containerEntity; 86 | } 87 | 88 | // CalculatePathString creates a managed string that can be used to find the container its serialized counterpart on disk. 89 | // It creates the folders if they do not exist yet. 90 | // It uses a cached StringBuilder for improved performance. 91 | public static string CalculatePathString(StringBuilder sb, string folderName, Hash128 guid, string postFix = "") 92 | { 93 | sb.Clear(); 94 | sb.Append(Application.streamingAssetsPath); 95 | sb.Append("/QuickSave/"); 96 | sb.Append(string.IsNullOrEmpty(folderName) ? "DefaultSceneStatesFolder" : folderName); 97 | sb.Append("/"); 98 | sb.Append(guid.ToString()); 99 | if (!string.IsNullOrEmpty(postFix)) 100 | { 101 | sb.Append("_"); 102 | sb.Append(postFix); 103 | } 104 | sb.Append(".qs"); // qs stands for QuickSave :) 105 | var path = sb.ToString(); 106 | sb.Clear(); 107 | 108 | return path; 109 | } 110 | 111 | // Use this to assert an entity is an initialized container 112 | [Conditional("DEBUG")] 113 | public static void CheckContainerInitialized(EntityManager entityManager, Entity containerEntity) 114 | { 115 | foreach (var componentType in InitializedContainerArchetype) 116 | { 117 | CheckEntityHasComponent(entityManager, containerEntity, componentType); 118 | } 119 | } 120 | 121 | 122 | // Internal 123 | // ******** 124 | 125 | // This method creates blob assets, ownership of them go to the caller of the method! 126 | internal static Entity CreateInitialSceneContainer(EntityManager entityManager, Hash128 guid, ref QuickSaveSceneInfo sceneInfo, 127 | out DynamicBuffer outBuffer, 128 | NativeList>> blobAssetsOwnedByCaller) 129 | { 130 | var archetype = entityManager.CreateArchetype(InitializedContainerArchetype); 131 | Entity containerEntity = entityManager.CreateEntity(archetype); 132 | 133 | var layouts = entityManager.GetBuffer(containerEntity); 134 | DataLayoutHelpers.BuildDataLayouts(ref sceneInfo, layouts); 135 | 136 | int amountEntities = 0; 137 | int containerSize = 0; 138 | foreach (var layout in layouts) 139 | { 140 | blobAssetsOwnedByCaller.Add(layout.TypeInfoArrayRef); 141 | amountEntities += layout.Amount; 142 | containerSize += layout.Amount * layout.SizePerEntity; 143 | } 144 | 145 | entityManager.SetComponentData(containerEntity, new QuickSaveDataContainer() 146 | { 147 | GUID = guid, 148 | DataLayoutHash = sceneInfo.DataLayoutHash, 149 | EntityCapacity = amountEntities, 150 | FrameIdentifier = -1, 151 | InitialContainer = containerEntity 152 | }); 153 | 154 | outBuffer = entityManager.GetBuffer(containerEntity); 155 | outBuffer.Resize(containerSize, NativeArrayOptions.ClearMemory); 156 | 157 | return containerEntity; 158 | } 159 | 160 | // This method creates blob assets, ownership of them go to the caller of the method! 161 | internal static Entity CreateInitialSceneContainer(EntityCommandBuffer ecb, Hash128 guid, ref QuickSaveSceneInfo sceneInfo, DataTransferRequest request, 162 | out QuickSaveDataContainer container, out DynamicBuffer initialContainerData, out DynamicBuffer layouts, 163 | NativeList>> blobAssetsOwnedByCaller) 164 | { 165 | Entity containerEntity = ecb.CreateEntity(); 166 | layouts = ecb.AddBuffer(containerEntity); 167 | DataLayoutHelpers.BuildDataLayouts(ref sceneInfo, layouts); 168 | int amountEntities = 0; 169 | int containerSize = 0; 170 | foreach (var layout in layouts) 171 | { 172 | blobAssetsOwnedByCaller.Add(layout.TypeInfoArrayRef); 173 | amountEntities += layout.Amount; 174 | containerSize += layout.Amount * layout.SizePerEntity; 175 | } 176 | 177 | container = new QuickSaveDataContainer() 178 | { 179 | GUID = guid, 180 | DataLayoutHash = sceneInfo.DataLayoutHash, 181 | EntityCapacity = amountEntities, 182 | FrameIdentifier = -1, 183 | InitialContainer = containerEntity 184 | }; 185 | ecb.AddComponent(containerEntity, container); 186 | 187 | var requestBuffer = ecb.AddBuffer(containerEntity); 188 | requestBuffer.Add(request); 189 | 190 | initialContainerData = ecb.AddBuffer(containerEntity); 191 | initialContainerData.Resize(containerSize, NativeArrayOptions.ClearMemory); 192 | 193 | ecb.SetName(containerEntity, QuickSaveSettings.GetQuickSaveContainerName()); 194 | 195 | return containerEntity; 196 | } 197 | 198 | [Conditional("DEBUG")] 199 | private static void CheckEntityHasComponent(EntityManager entityManager, Entity containerEntity, ComponentType componentType) 200 | { 201 | if (!entityManager.HasComponent(containerEntity, componentType)) 202 | { 203 | throw new ArgumentException($"Expected a valid container as argument, but the entity missed the {componentType.ToString()} component."); 204 | } 205 | } 206 | } 207 | } -------------------------------------------------------------------------------- /QuickSave/QuickSaveAPI.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: dc35f35f51ad2f449bfd93b5701efa46 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /QuickSave/QuickSaveArchetypeCollection.cs: -------------------------------------------------------------------------------- 1 | // Author: Jonas De Maeseneer 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using UnityEngine; 6 | 7 | namespace QuickSave 8 | { 9 | public class QuickSaveArchetypeCollection : ScriptableObject 10 | { 11 | private const string AssetPath = QuickSaveSettingsAsset.AssetFolder + "/QuickSaveArchetypeCollection.asset"; 12 | 13 | [Serializable] 14 | public class QuickSaveArchetypeDefinition 15 | { 16 | public string Name = "New_QuickSaveArchetype_RenameMe"; 17 | public List FullTypeNames = new List(); 18 | } 19 | 20 | public List Definitions = new List(); 21 | 22 | #if UNITY_EDITOR 23 | public static QuickSaveArchetypeCollection CreateInEditor() 24 | { 25 | var asset = UnityEditor.AssetDatabase.LoadAssetAtPath(AssetPath); 26 | if (asset != null) 27 | return asset; 28 | 29 | asset = CreateInstance(); 30 | UnityEditor.AssetDatabase.CreateAsset(asset, AssetPath); 31 | UnityEditor.AssetDatabase.Refresh(); 32 | return asset; 33 | } 34 | #endif 35 | } 36 | } -------------------------------------------------------------------------------- /QuickSave/QuickSaveArchetypeCollection.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4493c42805dda044592aa7bac6d3f3ca 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /QuickSave/QuickSaveAuthoring.cs: -------------------------------------------------------------------------------- 1 | // Author: Jonas De Maeseneer 2 | 3 | using System.Collections.Generic; 4 | using UnityEngine; 5 | 6 | namespace QuickSave 7 | { 8 | [DisallowMultipleComponent] 9 | public class QuickSaveAuthoring : MonoBehaviour 10 | { 11 | public string QuickSaveArchetypeName = ""; 12 | public List FullTypeNamesToPersist = new List(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /QuickSave/QuickSaveAuthoring.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7f6c0563cdd57ab49a0741be6f3a2d0b 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /QuickSave/QuickSaveBeginFrameSystem.cs: -------------------------------------------------------------------------------- 1 | // Author: Jonas De Maeseneer 2 | 3 | using Unity.Entities; 4 | using Unity.Scenes; 5 | 6 | namespace QuickSave 7 | { 8 | [UpdateInGroup(typeof(InitializationSystemGroup))] 9 | [UpdateAfter(typeof(SceneSystemGroup))] 10 | public partial class QuickSaveBeginFrameSystem : QuickSaveSystemBase 11 | { 12 | public override bool HandlesPersistRequests => true; 13 | public override bool HandlesApplyRequests => true; 14 | public override EntityCommandBufferSystem EcbSystem => World.GetOrCreateSystemManaged(); 15 | } 16 | } -------------------------------------------------------------------------------- /QuickSave/QuickSaveBeginFrameSystem.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: de3d3d1ee7d22444cb9880e11a6d811c 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /QuickSave/QuickSaveDataContainer.cs: -------------------------------------------------------------------------------- 1 | // Author: Jonas De Maeseneer 2 | 3 | using Unity.Entities; 4 | using Hash128 = Unity.Entities.Hash128; 5 | 6 | namespace QuickSave 7 | { 8 | // Container components 9 | public struct QuickSaveDataContainer : IComponentData 10 | { 11 | public Hash128 GUID; 12 | public ulong DataLayoutHash; 13 | public int FrameIdentifier; 14 | public int EntityCapacity; 15 | public Entity InitialContainer; 16 | 17 | public bool ValidData; 18 | 19 | [InternalBufferCapacity(16)] 20 | public struct Data : IBufferElementData 21 | { 22 | public byte Byte; 23 | } 24 | } 25 | 26 | // This structure has all the details needed to write/read from a sub-array of a large contiguous array for 1 specific QuickSaveArchetype & an amount 27 | public struct QuickSaveArchetypeDataLayout : IBufferElementData 28 | { 29 | public BlobAssetReference> TypeInfoArrayRef; 30 | public int Amount; 31 | public int Offset; 32 | public int SizePerEntity; 33 | 34 | public struct TypeInfo 35 | { 36 | public int ElementSize; 37 | public int MaxElements; 38 | public int Offset; 39 | public QuickSaveTypeHandle QuickSaveTypeHandle; 40 | public bool IsBuffer; 41 | public int SizePerEntityForThisType => (ElementSize * MaxElements) + QuickSaveMetaData.SizeOfStruct; 42 | } 43 | } 44 | 45 | public struct DataTransferRequest : IBufferElementData 46 | { 47 | public enum Type : byte 48 | { 49 | FromEntitiesToDataContainer, // = Persist 50 | FromDataContainerToEntities // = Apply 51 | } 52 | 53 | public Type RequestType; 54 | public SystemHandle ExecutingSystem; // Specify which system needs to execute this request 55 | } 56 | } -------------------------------------------------------------------------------- /QuickSave/QuickSaveDataContainer.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7589c926658189541a3b5d1da92bcc72 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /QuickSave/QuickSaveEndFrameSystem.cs: -------------------------------------------------------------------------------- 1 | // Author: Jonas De Maeseneer 2 | 3 | using Unity.Entities; 4 | 5 | namespace QuickSave 6 | { 7 | [UpdateInGroup(typeof(PresentationSystemGroup))] 8 | public partial class QuickSaveEndFrameSystem : QuickSaveSystemBase 9 | { 10 | public override bool HandlesPersistRequests => true; 11 | public override bool HandlesApplyRequests => false; 12 | public override EntityCommandBufferSystem EcbSystem => World.GetOrCreateSystemManaged(); 13 | } 14 | } -------------------------------------------------------------------------------- /QuickSave/QuickSaveEndFrameSystem.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8b01ae4406f60a84b885169885e6be47 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /QuickSave/QuickSaveGroupedJobs.cs: -------------------------------------------------------------------------------- 1 | // Author: Jonas De Maeseneer 2 | 3 | using System; 4 | using System.Diagnostics; 5 | using QuickSave.Containers; 6 | using Unity.Burst; 7 | using Unity.Burst.Intrinsics; 8 | using Unity.Collections; 9 | using Unity.Collections.LowLevel.Unsafe; 10 | using Unity.Entities; 11 | 12 | namespace QuickSave 13 | { 14 | // This job uses nested native containers, the unity job safety system doesn't support that, so we only run these jobs in a build. 15 | [BurstCompile] 16 | internal struct GroupedApplyJob : IJobChunk 17 | { 18 | [ReadOnly] 19 | public BufferLookup ByteArrayLookup; 20 | [ReadOnly] 21 | public BufferLookup DataLayoutLookup; 22 | public Entity ContainerEntity; 23 | 24 | [ReadOnly] public EntityTypeHandle EntityTypeHandle; 25 | [ReadOnly] public ComponentTypeHandle LocalIndexInContainerTypeHandle; 26 | [ReadOnly] public SharedComponentTypeHandle QuickSaveArchetypeIndexInContainerHandle; 27 | 28 | [DeallocateOnJobCompletion] 29 | [ReadOnly] public ComponentTypeHandleArray DynamicComponentTypeHandles; 30 | 31 | public EntityCommandBuffer.ParallelWriter Ecb; 32 | 33 | public void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask) 34 | { 35 | NativeArray persistenceStateArray = chunk.GetNativeArray(ref LocalIndexInContainerTypeHandle); 36 | NativeArray entityArray = chunk.GetNativeArray(EntityTypeHandle); 37 | 38 | ushort archetypeIndexInContainer = chunk.GetSharedComponent(QuickSaveArchetypeIndexInContainerHandle).IndexInContainer; 39 | 40 | SafetyChecks.CheckEntityHasDataLayouts(ContainerEntity, DataLayoutLookup); 41 | QuickSaveArchetypeDataLayout dataLayout = DataLayoutLookup[ContainerEntity][archetypeIndexInContainer]; 42 | ref BlobArray typeInfoArray = ref dataLayout.TypeInfoArrayRef.Value; 43 | SafetyChecks.CheckEntityHasDataBuffer(ContainerEntity, ByteArrayLookup); 44 | var dataForArchetype = ByteArrayLookup[ContainerEntity].Reinterpret().AsNativeArray().GetSubArray(dataLayout.Offset, dataLayout.Amount * dataLayout.SizePerEntity); 45 | 46 | for (int typeInfoIndex = 0; typeInfoIndex < typeInfoArray.Length; typeInfoIndex++) 47 | { 48 | // type info 49 | QuickSaveArchetypeDataLayout.TypeInfo typeInfo = typeInfoArray[typeInfoIndex]; 50 | ComponentType runtimeType = ComponentType.ReadWrite(QuickSaveSettings.GetTypeIndex(typeInfo.QuickSaveTypeHandle)); 51 | int stride = typeInfo.ElementSize * typeInfo.MaxElements + QuickSaveMetaData.SizeOfStruct; 52 | int byteSize = dataLayout.Amount * stride; 53 | 54 | // Grab read-only containers 55 | var inputData = dataForArchetype.GetSubArray(typeInfo.Offset, byteSize); 56 | 57 | bool found = DynamicComponentTypeHandles.GetByTypeIndex(runtimeType.TypeIndex, out DynamicComponentTypeHandle typeHandle); 58 | SafetyChecks.CheckFoundDynamicTypeHandle(found); 59 | UntypedAccessExtensionMethods.DisableSafetyChecks(ref typeHandle); 60 | 61 | if (typeInfo.IsBuffer) 62 | { 63 | SafetyChecks.CheckHasBufferComponent(chunk, ref typeHandle); // Removing/Adding buffer data is not supported 64 | var untypedBufferAccessor = chunk.GetUntypedBufferAccessor(ref typeHandle); 65 | EnabledMask enabledMask = runtimeType.IsEnableable ? chunk.GetEnabledMask(ref typeHandle) : default; 66 | CopyByteArrayToBufferElements.Execute(inputData, typeInfo.MaxElements, untypedBufferAccessor, persistenceStateArray, enabledMask, runtimeType.IsEnableable); 67 | } 68 | else // if ComponentData 69 | { 70 | if (chunk.Has(ref typeHandle)) 71 | { 72 | if (typeInfo.ElementSize > 0) 73 | { 74 | var byteArray = chunk.GetComponentDataAsByteArray(ref typeHandle); 75 | CopyByteArrayToComponentData.Execute(inputData, typeInfo.ElementSize, byteArray, persistenceStateArray); 76 | } 77 | 78 | EnabledMask enabledMask = runtimeType.IsEnableable ? chunk.GetEnabledMask(ref typeHandle) : default; 79 | RemoveEnableOrDisableExistingComponent.Execute(inputData, runtimeType, typeInfo.ElementSize, entityArray, persistenceStateArray, enabledMask, Ecb, unfilteredChunkIndex); 80 | } 81 | else 82 | { 83 | AddMissingComponent.Execute(inputData, runtimeType, typeInfo.ElementSize, entityArray, persistenceStateArray, Ecb, unfilteredChunkIndex); 84 | } 85 | } 86 | } 87 | } 88 | } 89 | 90 | // This job uses nested native containers, the unity job safety system doesn't support that, so we only run these jobs in a build. 91 | [BurstCompile] 92 | internal struct GroupedPersistJob : IJobChunk 93 | { 94 | [WriteOnly, NativeDisableContainerSafetyRestriction] 95 | public BufferLookup ByteArrayLookup; 96 | [ReadOnly] 97 | public BufferLookup DataLayoutLookup; 98 | public Entity ContainerEntity; 99 | 100 | [ReadOnly] public ComponentTypeHandle LocalIndexInContainerTypeHandle; 101 | [ReadOnly] public SharedComponentTypeHandle QuickSaveArchetypeIndexInContainerHandle; 102 | 103 | [DeallocateOnJobCompletion] 104 | [ReadOnly] public ComponentTypeHandleArray DynamicComponentTypeHandles; 105 | 106 | public void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask) 107 | { 108 | NativeArray persistenceStateArray = chunk.GetNativeArray(ref LocalIndexInContainerTypeHandle); 109 | 110 | ushort archetypeIndexInContainer = chunk.GetSharedComponent(QuickSaveArchetypeIndexInContainerHandle).IndexInContainer; 111 | 112 | SafetyChecks.CheckEntityHasDataLayouts(ContainerEntity, DataLayoutLookup); 113 | QuickSaveArchetypeDataLayout dataLayout = DataLayoutLookup[ContainerEntity][archetypeIndexInContainer]; 114 | ref BlobArray typeInfoArray = ref dataLayout.TypeInfoArrayRef.Value; 115 | SafetyChecks.CheckEntityHasDataBuffer(ContainerEntity, ByteArrayLookup); 116 | var dataForArchetype = ByteArrayLookup[ContainerEntity].Reinterpret().AsNativeArray().GetSubArray(dataLayout.Offset, dataLayout.Amount * dataLayout.SizePerEntity); 117 | 118 | for (int typeInfoIndex = 0; typeInfoIndex < typeInfoArray.Length; typeInfoIndex++) 119 | { 120 | // type info 121 | QuickSaveArchetypeDataLayout.TypeInfo typeInfo = typeInfoArray[typeInfoIndex]; 122 | ComponentType runtimeType = ComponentType.ReadOnly(QuickSaveSettings.GetTypeIndex(typeInfo.QuickSaveTypeHandle)); 123 | int stride = typeInfo.ElementSize * typeInfo.MaxElements + QuickSaveMetaData.SizeOfStruct; 124 | int byteSize = dataLayout.Amount * stride; 125 | bool checkEnabled = runtimeType.IsEnableable; 126 | 127 | // Grab containers 128 | var outputData = dataForArchetype.GetSubArray(typeInfo.Offset, byteSize); 129 | 130 | bool found = DynamicComponentTypeHandles.GetByTypeIndex(runtimeType.TypeIndex, out DynamicComponentTypeHandle typeHandle); 131 | SafetyChecks.CheckFoundDynamicTypeHandle(found); 132 | UntypedAccessExtensionMethods.DisableSafetyChecks(ref typeHandle); 133 | 134 | if (typeInfo.IsBuffer) 135 | { 136 | SafetyChecks.CheckHasBufferComponent(chunk, ref typeHandle); // Removing/Adding buffer data is not supported 137 | EnabledMask enabledMask = checkEnabled ? chunk.GetEnabledMask(ref typeHandle) : default; 138 | var untypedBufferAccessor = chunk.GetUntypedBufferAccessor(ref typeHandle); 139 | CopyBufferElementsToByteArray.Execute(outputData, typeInfo.MaxElements, untypedBufferAccessor, persistenceStateArray, checkEnabled, enabledMask); 140 | } 141 | else 142 | { 143 | if (chunk.Has(ref typeHandle)) 144 | { 145 | EnabledMask enabledMask = checkEnabled ? chunk.GetEnabledMask(ref typeHandle) : default; 146 | if (typeInfo.ElementSize > 0) 147 | { 148 | var byteArray = chunk.GetComponentDataAsByteArray(ref typeHandle); 149 | CopyComponentDataToByteArray.Execute(outputData, typeInfo.ElementSize, byteArray, persistenceStateArray, checkEnabled, enabledMask); 150 | } 151 | else 152 | { 153 | UpdateMetaDataForComponent.Execute(outputData, stride, persistenceStateArray, 1, checkEnabled, enabledMask); 154 | } 155 | } 156 | else 157 | { 158 | UpdateMetaDataForComponent.Execute(outputData, stride, persistenceStateArray, 0, false, default); 159 | } 160 | } 161 | } 162 | } 163 | } 164 | 165 | internal static partial class SafetyChecks 166 | { 167 | [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] 168 | internal static void CheckEntityHasDataLayouts(Entity e, BufferLookup lookup) 169 | { 170 | if (!lookup.HasBuffer(e)) 171 | { 172 | throw new IndexOutOfRangeException("QuickSave(JobSafety): LocalIndexInContainer.LocalIndex seems to be out of range. Or the stride is wrong."); 173 | } 174 | } 175 | 176 | [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] 177 | internal static void CheckFoundDynamicTypeHandle(bool found) 178 | { 179 | if (!found) 180 | { 181 | throw new Exception("QuickSave(JobSafety): Did not find the DynamicComponentTypeHandle we were looking for!"); 182 | } 183 | } 184 | } 185 | } -------------------------------------------------------------------------------- /QuickSave/QuickSaveGroupedJobs.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 95c5a8941d4790b449c776dea088e901 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /QuickSave/QuickSaveJobs.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2cfaf8e7355b1164b8b4dcbe5cab21ce 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /QuickSave/QuickSaveSceneComponents.cs: -------------------------------------------------------------------------------- 1 | // Author: Jonas De Maeseneer 2 | 3 | using Unity.Entities; 4 | 5 | namespace QuickSave 6 | { 7 | // IMPORTANT: QuickSave only supports saving & applying state for scene section 0 of a subscene! 8 | 9 | // Optional component to put on SceneSection entities 10 | // The state in the specified container will be applied to all relevant entities in the scene section right after it is loaded 11 | public struct AutoApplyOnLoad : IComponentData 12 | { 13 | public Entity ContainerEntityToApply; 14 | } 15 | 16 | // Needs to be used together with RequestSceneUnloaded! 17 | // Optional component to put on SceneSection entities 18 | // The state of all relevant entities in the scene section will be saved to the specified container right before the section is unloaded 19 | public struct AutoPersistOnUnload : IComponentData 20 | { 21 | public Entity ContainerEntityToPersist; 22 | } 23 | 24 | // Should be used together with AutoPersistOnUnload 25 | // Adding this component to a scene section will result in it being unloaded, the standard way of doing this is to remove the RequestSceneLoaded component. 26 | // But using this component enables the QuickSaveEndFrameSystem to save the state of the scene section right before it is unloaded. 27 | public struct RequestSceneUnloaded : IComponentData 28 | { 29 | 30 | } 31 | 32 | // This indicates a scene section that was loaded at some point & is tracked by QuickSave 33 | // If the scene section entity is destroyed, the QuickSaveSceneSystem will automatically destroy the initial container it created. 34 | // All other containers are owned by the user, but since the QuickSaveSceneSystem auto-creates the initial containers it will also handle their destruction. 35 | public struct QuickSaveSceneSection : ICleanupComponentData 36 | { 37 | public Entity InitialStateContainerEntity; 38 | } 39 | 40 | // Each scene section with persisting entities will have a singleton entity with this component. 41 | // It contains info to quickly construct QuickSaveArchetypeDataLayout at runtime 42 | public struct QuickSaveSceneInfoRef : IComponentData 43 | { 44 | public BlobAssetReference InfoRef; 45 | } 46 | 47 | public struct QuickSaveSceneInfo 48 | { 49 | public BlobArray AllUniqueTypeHandles; 50 | internal BlobArray QuickSaveArchetypesInScene; 51 | public Hash128 SceneGUID; 52 | public ulong DataLayoutHash; 53 | } 54 | 55 | internal struct QuickSaveArchetypesInScene 56 | { 57 | public BlobArray QuickSaveTypeHandles; 58 | 59 | // This is the amount of entities with this QuickSaveArchetype in the scene 60 | public int AmountEntities; 61 | } 62 | } -------------------------------------------------------------------------------- /QuickSave/QuickSaveSceneComponents.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0b0feebab054f0144a2ec5f841901796 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /QuickSave/QuickSaveSceneSystem.cs: -------------------------------------------------------------------------------- 1 | // Author: Jonas De Maeseneer 2 | 3 | using Unity.Burst; 4 | using Unity.Collections; 5 | using Unity.Entities; 6 | using Unity.Scenes; 7 | using Debug = UnityEngine.Debug; 8 | 9 | namespace QuickSave 10 | { 11 | [BurstCompile] 12 | [UpdateInGroup(typeof(InitializationSystemGroup))] 13 | [UpdateAfter(typeof(SceneSystemGroup))] 14 | [UpdateBefore(typeof(QuickSaveBeginFrameSystem))] 15 | public partial struct QuickSaveSceneSystem : ISystem 16 | { 17 | private NativeHashMap _tempNewSceneSections; 18 | private SystemHandle _executingSystemHandle; 19 | private EntityQuery _quickSaveSceneInfoQuery; 20 | 21 | private EntityQuery _sectionsToCleanup; 22 | 23 | private ComponentLookup _quickSaveSceneSectionLookup; 24 | private ComponentLookup _autoApplyOnLoadLookup; 25 | private ComponentLookup _containerLookup; 26 | private BufferLookup _dataLookup; 27 | private BufferLookup _dataLayoutLookup; 28 | private BufferLookup _dataTransferRequestLookup; 29 | 30 | private NativeList>> _ownedBlobAssets; 31 | 32 | public void OnCreate(ref SystemState state) 33 | { 34 | QuickSaveSettings.Initialize(); 35 | 36 | _tempNewSceneSections = new NativeHashMap(16, Allocator.Persistent); 37 | _executingSystemHandle = state.World.GetOrCreateSystem(); 38 | 39 | _quickSaveSceneInfoQuery = new EntityQueryBuilder(Allocator.Temp).WithAll().Build(state.EntityManager); 40 | state.RequireForUpdate(_quickSaveSceneInfoQuery); 41 | 42 | _sectionsToCleanup = new EntityQueryBuilder(Allocator.Temp) 43 | .WithNone() 44 | .WithAll().Build(state.EntityManager); 45 | 46 | _quickSaveSceneSectionLookup = state.GetComponentLookup(true); 47 | _autoApplyOnLoadLookup = state.GetComponentLookup(true); 48 | _containerLookup = state.GetComponentLookup(true); 49 | _dataLookup = state.GetBufferLookup(); 50 | _dataLayoutLookup = state.GetBufferLookup(); 51 | _dataTransferRequestLookup = state.GetBufferLookup(); 52 | 53 | _ownedBlobAssets = new NativeList>>(256, Allocator.Persistent); 54 | 55 | // Create some archetypes at the start to avoid the cost at runtime 56 | CacheArchetypes(ref state); 57 | } 58 | 59 | [BurstCompile] 60 | public void OnUpdate(ref SystemState state) 61 | { 62 | EntityCommandBuffer ecb = new EntityCommandBuffer(Allocator.TempJob, PlaybackPolicy.SinglePlayback); 63 | 64 | // Cleanup any initial containers that belonged to scene section entities that were destroyed 65 | // This systems doesn't always run, so the cleanup can be delayed, but that's fine, it will run before new initial containers are created. 66 | new CleanupDestroyedSceneSections{ Ecb = ecb }.Run(_sectionsToCleanup); 67 | 68 | // QuickSaveSceneInfoRef that are not disabled notify us of a newly loaded sceneSection 69 | _tempNewSceneSections.Clear(); 70 | new ProcessQuickSaveSceneInfo() 71 | { 72 | MapToFill = _tempNewSceneSections, 73 | Ecb = ecb 74 | }.Run(_quickSaveSceneInfoQuery); 75 | 76 | _quickSaveSceneSectionLookup.Update(ref state); 77 | _autoApplyOnLoadLookup.Update(ref state); 78 | _containerLookup.Update(ref state); 79 | _dataLookup.Update(ref state); 80 | _dataLayoutLookup.Update(ref state); 81 | _dataTransferRequestLookup.Update(ref state); 82 | 83 | new ProcessNewSceneSections() 84 | { 85 | InfoMap = _tempNewSceneSections, 86 | Ecb = ecb, 87 | OwnedBlobAssetsToAppendTo = _ownedBlobAssets, 88 | ExecutingRequestsSystem = _executingSystemHandle, 89 | QuickSaveSceneSectionLookup = _quickSaveSceneSectionLookup, 90 | AutoApplyOnLoadLookup = _autoApplyOnLoadLookup, 91 | ContainerLookup = _containerLookup, 92 | DataLookup = _dataLookup, 93 | DataLayoutLookup = _dataLayoutLookup, 94 | DataTransferRequestLookup = _dataTransferRequestLookup 95 | }.Run(); 96 | 97 | ecb.Playback(state.EntityManager); 98 | ecb.Dispose(); 99 | } 100 | 101 | public void OnDestroy(ref SystemState state) 102 | { 103 | _tempNewSceneSections.Dispose(); 104 | for (int i = 0; i < _ownedBlobAssets.Length; i++) 105 | { 106 | _ownedBlobAssets[i].Dispose(); 107 | } 108 | _ownedBlobAssets.Dispose(); 109 | QuickSaveSettings.CleanUp(); 110 | } 111 | 112 | [BurstCompile] 113 | private partial struct CleanupDestroyedSceneSections : IJobEntity 114 | { 115 | public EntityCommandBuffer Ecb; 116 | 117 | public void Execute(Entity entity, in QuickSaveSceneSection cleanupComp) 118 | { 119 | if (cleanupComp.InitialStateContainerEntity.Index > 0) 120 | Ecb.DestroyEntity(cleanupComp.InitialStateContainerEntity); 121 | 122 | Ecb.RemoveComponent(entity); 123 | } 124 | } 125 | 126 | [BurstCompile] 127 | private partial struct ProcessQuickSaveSceneInfo : IJobEntity 128 | { 129 | [WriteOnly] 130 | public NativeHashMap MapToFill; 131 | public EntityCommandBuffer Ecb; 132 | 133 | public void Execute(Entity entity, in QuickSaveSceneInfoRef sceneSectionInfo) 134 | { 135 | var sceneSection = new SceneSection() 136 | { 137 | SceneGUID = sceneSectionInfo.InfoRef.Value.SceneGUID, 138 | Section = 0 139 | }; 140 | MapToFill.Add(sceneSection, sceneSectionInfo); 141 | Ecb.AddComponent(entity); 142 | } 143 | } 144 | 145 | [BurstCompile] 146 | private partial struct ProcessNewSceneSections : IJobEntity 147 | { 148 | [ReadOnly] 149 | public NativeHashMap InfoMap; 150 | public EntityCommandBuffer Ecb; 151 | 152 | public NativeList>> OwnedBlobAssetsToAppendTo; 153 | 154 | public SystemHandle ExecutingRequestsSystem; 155 | 156 | [ReadOnly] 157 | public ComponentLookup QuickSaveSceneSectionLookup; 158 | [ReadOnly] 159 | public ComponentLookup AutoApplyOnLoadLookup; 160 | [ReadOnly] 161 | public ComponentLookup ContainerLookup; 162 | public BufferLookup DataLookup; 163 | public BufferLookup DataLayoutLookup; 164 | public BufferLookup DataTransferRequestLookup; 165 | 166 | public void Execute(Entity sceneSectionEntity, in SceneSectionData sceneSectionData) 167 | { 168 | var sceneSection = new SceneSection {SceneGUID = sceneSectionData.SceneGUID, Section = sceneSectionData.SubSectionIndex}; 169 | if (!InfoMap.TryGetValue(sceneSection, out var info)) // Todo this filtering could be done in the query? 170 | return; 171 | 172 | Hash128 containerIdentifier = sceneSection.SceneGUID; 173 | ref QuickSaveSceneInfo sceneInfo = ref info.InfoRef.Value; 174 | 175 | Entity initialContainerEntity; 176 | QuickSaveDataContainer initialContainer; 177 | DynamicBuffer initialContainerData; 178 | DynamicBuffer initialDataLayouts; 179 | if (QuickSaveSceneSectionLookup.TryGetComponent(sceneSectionEntity, out var quickSaveSceneSection)) 180 | { 181 | initialContainerEntity = quickSaveSceneSection.InitialStateContainerEntity; 182 | initialContainer = ContainerLookup[initialContainerEntity]; 183 | initialContainerData = DataLookup[initialContainerEntity]; 184 | initialDataLayouts = DataLayoutLookup[initialContainerEntity]; 185 | } 186 | else 187 | { 188 | var initialRequest = new DataTransferRequest() 189 | { 190 | ExecutingSystem = ExecutingRequestsSystem, 191 | RequestType = DataTransferRequest.Type.FromEntitiesToDataContainer 192 | }; 193 | initialContainerEntity = QuickSaveAPI.CreateInitialSceneContainer(Ecb, containerIdentifier, ref sceneInfo, initialRequest, 194 | out initialContainer, out initialContainerData, out initialDataLayouts, OwnedBlobAssetsToAppendTo); 195 | 196 | Ecb.AddComponent(sceneSectionEntity, new QuickSaveSceneSection {InitialStateContainerEntity = initialContainerEntity}); 197 | } 198 | 199 | // AUTO APPLY 200 | // ********** 201 | 202 | if (AutoApplyOnLoadLookup.TryGetComponent(sceneSectionEntity, out AutoApplyOnLoad autoApply)) 203 | { 204 | Entity containerToAutoApplyEntity = autoApply.ContainerEntityToApply; 205 | if (containerToAutoApplyEntity != Entity.Null) 206 | { 207 | if (!DataTransferRequestLookup.HasBuffer(containerToAutoApplyEntity)) 208 | { 209 | // TODO Add functionality that upgrades every container that needs upgrading, not only the auto apply one (then this code gets replaced by this) 210 | // TODO then also update the comments on RequestDeserializeIntoInvalidContainer 211 | var containerToApply = ContainerLookup[containerToAutoApplyEntity]; 212 | var dataToApply = DataLookup[containerToAutoApplyEntity]; 213 | var layoutsToApply = DataLayoutLookup[containerToAutoApplyEntity]; 214 | 215 | if (ValidateAndUpgradeContainer(ref containerToApply, ref dataToApply, ref layoutsToApply, in initialContainer, in initialContainerData, in initialDataLayouts)) 216 | { 217 | containerToApply.InitialContainer = initialContainerEntity; 218 | containerToApply.ValidData = true; 219 | 220 | // Needs to be done via ECB because there's the possibility that initialContainerEntity is an unrealized entity (created in future ecb playback) 221 | Ecb.SetComponent(containerToAutoApplyEntity, containerToApply); 222 | 223 | var requests = Ecb.AddBuffer(containerToAutoApplyEntity); 224 | requests.Add(new DataTransferRequest() 225 | { 226 | ExecutingSystem = ExecutingRequestsSystem, 227 | RequestType = DataTransferRequest.Type.FromDataContainerToEntities 228 | }); 229 | } 230 | else 231 | { 232 | Debug.LogWarning("Validation failed on AutoApplyOnLoad container."); 233 | containerToApply.ValidData = false; 234 | Ecb.SetComponent(containerToAutoApplyEntity, containerToApply); 235 | } 236 | } 237 | else 238 | { 239 | DataTransferRequestLookup[containerToAutoApplyEntity].Add(new DataTransferRequest() 240 | { 241 | ExecutingSystem = ExecutingRequestsSystem, 242 | RequestType = DataTransferRequest.Type.FromDataContainerToEntities 243 | }); 244 | } 245 | } 246 | else 247 | { 248 | Debug.LogWarning("Encountered an AutoApplyOnLoad component that has Entity.Null as its container!"); 249 | } 250 | } 251 | } 252 | } 253 | 254 | private static bool ValidateAndUpgradeContainer(ref QuickSaveDataContainer containerToUpgrade, ref DynamicBuffer dataToUpgrade, ref DynamicBuffer dataLayoutsToUpgrade, 255 | in QuickSaveDataContainer initialContainer, in DynamicBuffer initialData, in DynamicBuffer initialLayouts) 256 | { 257 | if (containerToUpgrade.DataLayoutHash != initialContainer.DataLayoutHash) 258 | { 259 | Debug.LogError("ValidateAndUpgradeContainer: Container had a different data layout than its initial container!"); 260 | return false; 261 | } 262 | 263 | if (containerToUpgrade.EntityCapacity != initialContainer.EntityCapacity) 264 | { 265 | Debug.LogError("ValidateAndUpgradeContainer: Container had a different amount of entities than its initial container!"); 266 | return false; 267 | } 268 | 269 | if (dataToUpgrade.Length != initialData.Length) 270 | { 271 | Debug.LogError("ValidateAndUpgradeContainer: Container had a different amount of bytes than its initial container!"); 272 | return false; 273 | } 274 | 275 | dataLayoutsToUpgrade.CopyFrom(initialLayouts); 276 | return true; 277 | } 278 | 279 | private static void CacheArchetypes(ref SystemState state) 280 | { 281 | state.EntityManager.CreateArchetype(QuickSaveAPI.InitializedContainerArchetype); 282 | state.EntityManager.CreateArchetype(QuickSaveAPI.InitializedSerializableContainerArchetype); 283 | state.EntityManager.CreateArchetype(QuickSaveAPI.UninitializedContainerArchetype); 284 | } 285 | } 286 | } -------------------------------------------------------------------------------- /QuickSave/QuickSaveSceneSystem.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: fce0a561970cbdd4f970f5daeec27512 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /QuickSave/QuickSaveSettings.cs: -------------------------------------------------------------------------------- 1 | // Author: Jonas De Maeseneer 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Diagnostics; 6 | using Unity.Burst; 7 | using Unity.Collections; 8 | using Unity.Collections.LowLevel.Unsafe; 9 | using Unity.Entities; 10 | 11 | namespace QuickSave 12 | { 13 | public static class QuickSaveSettings 14 | { 15 | private static bool _initialized; 16 | 17 | // Settings 18 | private static bool _verboseBakingLog = false; 19 | public static bool VerboseBakingLog => _verboseBakingLog; 20 | 21 | // Type Info (Accessible in burst via SharedStatic) 22 | private static NativeArray _typeIndexArray; 23 | private static NativeArray _unManagedTypeInfos; 24 | // Type Info (Not accessible in burst) 25 | private static NativeHashMap _typeIndexToHandle; 26 | public static ulong StableHashOfAllQuickSaveTypes; 27 | private static readonly ulong HashOfZero = TypeHash.FNV1A64(0); 28 | private static ManagedTypeInfo[] _managedTypeInfos; 29 | 30 | public static bool NothingToSave => StableHashOfAllQuickSaveTypes == HashOfZero; 31 | 32 | internal struct ManagedTypeInfo 33 | { 34 | public string FullTypeName; 35 | } 36 | internal struct UnManagedTypeInfo 37 | { 38 | public int MaxElements; 39 | } 40 | 41 | public static void Initialize() 42 | { 43 | if (_initialized) 44 | return; 45 | 46 | var asset = QuickSaveSettingsAsset.Get(); 47 | if (asset != null) 48 | Initialize(asset); 49 | else 50 | InitializeNoAsset(); 51 | } 52 | 53 | internal static unsafe void Initialize(QuickSaveSettingsAsset asset) 54 | { 55 | if (_initialized) 56 | return; 57 | 58 | if (asset.AllQuickSaveTypeInfos.Count > QuickSaveTypeHandle.MaxTypes) 59 | { 60 | throw new ArgumentException($"The maximum number of QuickSave Types is {QuickSaveTypeHandle.MaxTypes} & this project has {asset.AllQuickSaveTypeInfos.Count}!"); 61 | } 62 | 63 | _verboseBakingLog = asset.VerboseBakingLog; 64 | 65 | _typeIndexArray = new NativeArray(asset.AllQuickSaveTypeInfos.Count, Allocator.Persistent); 66 | _typeIndexToHandle = new NativeHashMap(asset.AllQuickSaveTypeInfos.Count, Allocator.Persistent); 67 | StableHashOfAllQuickSaveTypes = HashOfZero; 68 | 69 | for (int i = 0; i < asset.AllQuickSaveTypeInfos.Count; i++) 70 | { 71 | var typeInfo = asset.AllQuickSaveTypeInfos[i]; 72 | typeInfo.ValidityCheck(); 73 | 74 | StableHashOfAllQuickSaveTypes = TypeHash.CombineFNV1A64(StableHashOfAllQuickSaveTypes, typeInfo.StableTypeHash); 75 | StableHashOfAllQuickSaveTypes = TypeHash.CombineFNV1A64(StableHashOfAllQuickSaveTypes, typeInfo.IsBuffer ? 17UL : 31UL); 76 | StableHashOfAllQuickSaveTypes = TypeHash.CombineFNV1A64(StableHashOfAllQuickSaveTypes, (ulong)typeInfo.MaxElements); 77 | 78 | int typeIndex = TypeManager.GetTypeIndexFromStableTypeHash(typeInfo.StableTypeHash); 79 | _typeIndexArray[i] = typeIndex; 80 | _typeIndexToHandle[typeIndex] = new QuickSaveTypeHandle(i); 81 | } 82 | 83 | _unManagedTypeInfos = new NativeArray(asset.AllQuickSaveTypeInfos.Count, Allocator.Persistent); 84 | _managedTypeInfos = new ManagedTypeInfo[asset.AllQuickSaveTypeInfos.Count]; 85 | for (int i = 0; i < asset.AllQuickSaveTypeInfos.Count; i++) 86 | { 87 | _unManagedTypeInfos[i] = new UnManagedTypeInfo {MaxElements = asset.AllQuickSaveTypeInfos[i].MaxElements}; 88 | _managedTypeInfos[i] = new ManagedTypeInfo {FullTypeName = asset.AllQuickSaveTypeInfos[i].FullTypeName}; 89 | } 90 | 91 | TypeIndexArray.Ref.Data = new IntPtr(_typeIndexArray.GetUnsafePtr()); 92 | UnManagedTypeInfoArray.Ref.Data = new IntPtr(_unManagedTypeInfos.GetUnsafePtr()); 93 | TypeArraySize.Ref.Data = asset.AllQuickSaveTypeInfos.Count; 94 | ForceUseNonGroupedJobsInBuild.Ref.Data = asset.ForceUseNonGroupedJobsInBuild; 95 | ForceUseGroupedJobsInEditor.Ref.Data = asset.ForceUseGroupedJobsInEditor; 96 | QuickSaveContainerName.Ref.Data = new FixedString64Bytes("QuickSaveContainer"); 97 | 98 | _initialized = true; 99 | } 100 | 101 | private static unsafe void InitializeNoAsset() 102 | { 103 | _typeIndexArray = new NativeArray(1, Allocator.Persistent); 104 | _unManagedTypeInfos = new NativeArray(1, Allocator.Persistent); 105 | _typeIndexToHandle = new NativeHashMap(0, Allocator.Persistent); 106 | _managedTypeInfos = new ManagedTypeInfo[0]; 107 | 108 | TypeIndexArray.Ref.Data = new IntPtr(_typeIndexArray.GetUnsafePtr()); 109 | UnManagedTypeInfoArray.Ref.Data = new IntPtr(_unManagedTypeInfos.GetUnsafePtr()); 110 | TypeArraySize.Ref.Data = 0; 111 | ForceUseNonGroupedJobsInBuild.Ref.Data = false; 112 | ForceUseGroupedJobsInEditor.Ref.Data = false; 113 | QuickSaveContainerName.Ref.Data = new FixedString64Bytes("QuickSaveContainer"); 114 | 115 | _initialized = true; 116 | } 117 | 118 | public static void CleanUp() 119 | { 120 | if (!_initialized) 121 | return; 122 | 123 | _typeIndexArray.Dispose(); 124 | TypeIndexArray.Ref.Data = default; 125 | 126 | _unManagedTypeInfos.Dispose(); 127 | UnManagedTypeInfoArray.Ref.Data = default; 128 | 129 | TypeArraySize.Ref.Data = 0; 130 | ForceUseNonGroupedJobsInBuild.Ref.Data = false; 131 | ForceUseGroupedJobsInEditor.Ref.Data = false; 132 | QuickSaveContainerName.Ref.Data = default; 133 | 134 | _typeIndexToHandle.Dispose(); 135 | _managedTypeInfos = default; 136 | 137 | _initialized = false; 138 | } 139 | 140 | public static ref FixedString64Bytes GetQuickSaveContainerName() 141 | { 142 | return ref QuickSaveContainerName.Ref.Data; 143 | } 144 | 145 | public static QuickSaveTypeHandle GetTypeHandleFromTypeIndex(int typeIndex) 146 | { 147 | return _typeIndexToHandle[typeIndex]; 148 | } 149 | 150 | // fastest way to get the type index 151 | public static unsafe TypeIndex GetTypeIndex(QuickSaveTypeHandle typeHandle) 152 | { 153 | CheckValidTypeHandle(typeHandle); 154 | return GetTypeIndexPointer()[typeHandle.IndexForQuickSaveSettings]; 155 | } 156 | 157 | public static NativeArray.ReadOnly GetAllTypeIndices() 158 | { 159 | return _typeIndexArray.AsReadOnly(); 160 | } 161 | 162 | public static unsafe int GetMaxElements(QuickSaveTypeHandle typeHandle) 163 | { 164 | CheckValidTypeHandle(typeHandle); 165 | return GetUnManagedTypeInfoPointer()[typeHandle.IndexForQuickSaveSettings].MaxElements; 166 | } 167 | 168 | [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")] 169 | private static void CheckValidTypeHandle(QuickSaveTypeHandle typeHandle) 170 | { 171 | if (!typeHandle.IsValid && typeHandle.IndexForQuickSaveSettings < TypeArraySize.Ref.Data) 172 | { 173 | throw new ArgumentException("Expected a valid type handle"); 174 | } 175 | } 176 | 177 | public static QuickSaveTypeHandle GetQuickSaveTypeHandleFromFullTypeName(string fullTypeName) 178 | { 179 | for (int i = 0; i < _managedTypeInfos.Length; i++) 180 | { 181 | if (_managedTypeInfos[i].FullTypeName == fullTypeName) 182 | { 183 | return new QuickSaveTypeHandle(i); 184 | } 185 | } 186 | 187 | throw new ArgumentException($"{fullTypeName} was not registered as a QuickSave type."); 188 | } 189 | 190 | internal static NativeArray GetTypeHandles(List fullTypeNames, Allocator allocator) 191 | { 192 | var typeHandleList = new NativeList(fullTypeNames.Count, allocator); 193 | 194 | foreach (var fullTypeName in fullTypeNames) 195 | { 196 | if (ContainsType(fullTypeName)) 197 | { 198 | typeHandleList.Add(GetQuickSaveTypeHandleFromFullTypeName(fullTypeName)); 199 | } 200 | } 201 | 202 | return typeHandleList.AsArray(); 203 | } 204 | 205 | public static bool ContainsType(string fullTypeName) 206 | { 207 | for (int i = 0; i < _managedTypeInfos.Length; i++) 208 | { 209 | if (_managedTypeInfos[i].FullTypeName == fullTypeName) 210 | { 211 | return true; 212 | } 213 | } 214 | return false; 215 | } 216 | 217 | public static bool UseGroupedJobs() 218 | { 219 | #if UNITY_EDITOR 220 | return ForceUseGroupedJobsInEditor.Ref.Data; 221 | #elif ENABLE_UNITY_COLLECTIONS_CHECKS 222 | return false; 223 | #else 224 | return !ForceUseNonGroupedJobsInBuild.Ref.Data; 225 | #endif 226 | } 227 | 228 | // This method can be used without the QuickSaveSettings being initialized 229 | public static bool IsSupported(TypeManager.TypeInfo info, out string notSupportedReason) 230 | { 231 | if (info.Category != TypeManager.TypeCategory.BufferData && info.Category != TypeManager.TypeCategory.ComponentData) 232 | { 233 | notSupportedReason = $"Type: {ComponentType.FromTypeIndex(info.TypeIndex).ToString()} is not supported." + 234 | $" Reason: Needs to be {nameof(IComponentData)} or {nameof(IBufferElementData)}. (But it was {info.Category})."; 235 | return false; 236 | } 237 | 238 | if (info.EntityOffsetCount > 0) 239 | { 240 | notSupportedReason = $"Type: {ComponentType.FromTypeIndex(info.TypeIndex).ToString()} is not supported." + 241 | $" Reason: Persisting components with Entity References is not supported."; 242 | return false; 243 | } 244 | 245 | if (info.HasBlobAssetRefs) 246 | { 247 | notSupportedReason = $"Type: {ComponentType.FromTypeIndex(info.TypeIndex).ToString()} is not supported." + 248 | $" Reason: Persisting components with BlobAssetReferences is not supported."; 249 | return false; 250 | } 251 | 252 | if (info.HasWeakAssetRefs) 253 | { 254 | notSupportedReason = $"Type: {ComponentType.FromTypeIndex(info.TypeIndex).ToString()} is not supported." + 255 | $" Reason: Persisting components with WeakAssetReferences is not supported."; 256 | return false; 257 | } 258 | 259 | if (info.TypeIndex.IsManagedComponent) 260 | { 261 | notSupportedReason = $"Type: {ComponentType.FromTypeIndex(info.TypeIndex).ToString()} is not supported." + 262 | $" Reason: Persisting managed components is not supported."; 263 | return false; 264 | } 265 | 266 | if (info.BakingOnlyType || info.TemporaryBakingType) 267 | { 268 | notSupportedReason = $"Type: {ComponentType.FromTypeIndex(info.TypeIndex).ToString()} is not supported. " + 269 | $"Reason: Persisting baking-only components is not supported. " + 270 | $"(Components with {nameof(BakingTypeAttribute)} attribute or {nameof(TemporaryBakingTypeAttribute)} attribute)"; 271 | return false; 272 | } 273 | 274 | notSupportedReason = ""; 275 | return true; 276 | } 277 | 278 | private struct QuickSaveSettingsKeyContext { } 279 | private struct TypeIndexArray 280 | { 281 | public static readonly SharedStatic Ref = SharedStatic.GetOrCreate(); 282 | } 283 | private struct UnManagedTypeInfoArray 284 | { 285 | public static readonly SharedStatic Ref = SharedStatic.GetOrCreate(); 286 | } 287 | private struct TypeArraySize 288 | { 289 | public static readonly SharedStatic Ref = SharedStatic.GetOrCreate(); 290 | } 291 | private struct ForceUseNonGroupedJobsInBuild 292 | { 293 | public static readonly SharedStatic Ref = SharedStatic.GetOrCreate(); 294 | } 295 | private struct ForceUseGroupedJobsInEditor 296 | { 297 | public static readonly SharedStatic Ref = SharedStatic.GetOrCreate(); 298 | } 299 | private struct QuickSaveContainerName 300 | { 301 | public static readonly SharedStatic Ref = SharedStatic.GetOrCreate(); 302 | } 303 | 304 | private static unsafe TypeIndex* GetTypeIndexPointer() 305 | { 306 | return (TypeIndex*)TypeIndexArray.Ref.Data; 307 | } 308 | 309 | private static unsafe UnManagedTypeInfo* GetUnManagedTypeInfoPointer() 310 | { 311 | return (UnManagedTypeInfo*)UnManagedTypeInfoArray.Ref.Data; 312 | } 313 | } 314 | } 315 | -------------------------------------------------------------------------------- /QuickSave/QuickSaveSettings.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8d74c4edb4e623f47bd586b49069c370 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /QuickSave/QuickSaveSettingsAsset.cs: -------------------------------------------------------------------------------- 1 | // Author: Jonas De Maeseneer 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Data; 6 | using System.Diagnostics; 7 | using System.Linq; 8 | using System.Runtime.CompilerServices; 9 | using Unity.Entities; 10 | using Unity.Mathematics; 11 | using Unity.Transforms; 12 | using UnityEngine; 13 | using Debug = UnityEngine.Debug; 14 | 15 | [assembly:InternalsVisibleTo("com.studioaurelius.quicksave.editor")] 16 | [assembly:InternalsVisibleTo("com.studioaurelius.quicksave.tests")] 17 | 18 | namespace QuickSave 19 | { 20 | public class QuickSaveSettingsAsset : ScriptableObject 21 | { 22 | private const string ResourceFolder = "Assets/QuickSave/Resources"; 23 | public const string AssetFolder = ResourceFolder + "/QuickSave"; 24 | private const string RelativeFilePathNoExt = "QuickSave/QuickSaveSettings"; 25 | private const string RelativeFilePath = RelativeFilePathNoExt + ".asset"; 26 | private const string AssetPath = ResourceFolder + "/" + RelativeFilePath; 27 | private const string NotFoundMessage = "QuickSaveSettings.asset was not found, attach the PersistencyAuthoring script to a gameobject & press the 'create settings file' button."; 28 | 29 | [SerializeField] 30 | private List _allQuickSaveTypeInfos = new List(); 31 | internal List AllQuickSaveTypeInfos => _allQuickSaveTypeInfos; 32 | 33 | [SerializeField] 34 | public QuickSaveArchetypeCollection QuickSaveArchetypeCollection = null; 35 | 36 | [SerializeField] 37 | internal bool ForceUseNonGroupedJobsInBuild = false; 38 | [SerializeField] 39 | internal bool ForceUseGroupedJobsInEditor = false; 40 | [SerializeField] 41 | internal bool VerboseBakingLog = false; 42 | 43 | [Serializable] 44 | internal struct QuickSaveSettingsTypeInfo 45 | { 46 | // If any of these values change on the type definition itself the validity check will pick that up & the user needs to force update 47 | // example: user makes his previous IComponentData into an IBufferElementData 48 | // example: user renames his type or changes the namespace 49 | // example: something in unity changes that makes the stable type hash different than before 50 | public string FullTypeName; 51 | public ulong StableTypeHash; 52 | public bool IsBuffer; 53 | public int MaxElements; 54 | 55 | [Conditional("DEBUG")] 56 | public void ValidityCheck() 57 | { 58 | if (MaxElements < 1 || string.IsNullOrEmpty(FullTypeName)) 59 | { 60 | throw new DataException("Invalid PersistableTypeInfo in the RuntimePersistableTypesInfo asset! Try force updating RuntimePersistableTypesInfo (search the asset & press the force update button & look at console)"); 61 | } 62 | 63 | TypeIndex typeIndex = TypeManager.GetTypeIndexFromStableTypeHash(StableTypeHash); 64 | 65 | if (typeIndex == -1 || typeIndex == TypeIndex.Null) 66 | { 67 | throw new DataException($"{FullTypeName} has an invalid StableTypeHash in PersistableTypeInfo in the RuntimePersistableTypesInfo asset! Try force updating RuntimePersistableTypesInfo (search the asset & press the force update button & look at console)"); 68 | } 69 | 70 | if (TypeManager.IsBuffer(typeIndex) != IsBuffer) 71 | { 72 | throw new DataException($"{FullTypeName} is set as a buffer in the RuntimePersistableTypesInfo asset, but is not actually a buffer type! Try force updating RuntimePersistableTypesInfo (search the asset & press the force update button & look at console)"); 73 | } 74 | 75 | if (!IsBuffer && MaxElements > 1) 76 | { 77 | throw new DataException($"{FullTypeName} has {MaxElements.ToString()} as MaxElements the RuntimePersistableTypesInfo asset, but it is not a buffer type! Try force updating RuntimePersistableTypesInfo (search the asset & press the force update button & look at console)"); 78 | } 79 | } 80 | } 81 | 82 | public static QuickSaveSettingsAsset Get() 83 | { 84 | #if UNITY_EDITOR 85 | QuickSaveSettingsAsset settingsAsset = UnityEditor.AssetDatabase.LoadAssetAtPath(AssetPath); 86 | if (Application.isPlaying && UnityEditor.EditorUtility.IsDirty(settingsAsset)) 87 | { 88 | Debug.LogError("The PersistencySettings ScriptableObject was modified but not yet saved! This will result in the persistency logic completely breaking. (Exit PlayMode & Press Ctrl+S to fix)"); 89 | Debug.Break(); 90 | } 91 | #else 92 | // This only actually loads it the first time, so multiple calls are totally fine 93 | QuickSaveSettingsAsset settingsAsset = Resources.Load(RelativeFilePathNoExt); 94 | #endif 95 | if (settingsAsset == null) 96 | { 97 | Debug.LogWarning(NotFoundMessage); 98 | } 99 | return settingsAsset; 100 | } 101 | 102 | public static bool IsSupported(TypeManager.TypeInfo info, out string notSupportedReason) 103 | { 104 | // This method can be used without the QuickSaveSettings being initialized 105 | return QuickSaveSettings.IsSupported(info, out notSupportedReason); 106 | } 107 | 108 | #if UNITY_EDITOR 109 | public static void CreateInEditor() 110 | { 111 | var settings = UnityEditor.AssetDatabase.LoadAssetAtPath(AssetPath); 112 | if (settings == null) 113 | { 114 | System.IO.Directory.CreateDirectory(AssetFolder); 115 | 116 | settings = CreateInstance(); 117 | settings.AddQuickSaveTypeInEditor(typeof(LocalTransform).FullName); 118 | settings.AddQuickSaveTypeInEditor(typeof(Disabled).FullName); 119 | UnityEditor.AssetDatabase.CreateAsset(settings, AssetPath); 120 | } 121 | } 122 | 123 | public bool AddQuickSaveTypeInEditor(string fullTypeName, int maxElements = -1) 124 | { 125 | if (_allQuickSaveTypeInfos.Any(info => info.FullTypeName == fullTypeName)) 126 | { 127 | Debug.Log($"Failed to add {fullTypeName} is already in the list!"); 128 | return false; 129 | } 130 | 131 | bool creationSuccess = CreatePersistableTypeInfoFromFullTypeName(fullTypeName, out QuickSaveSettingsTypeInfo persistableTypeInfo); 132 | if (!creationSuccess) 133 | { 134 | Debug.Log($"Removed {fullTypeName}, type doesn't exist in the ECS TypeManager"); 135 | return false; 136 | } 137 | 138 | if (_allQuickSaveTypeInfos.Count + 1 > QuickSaveTypeHandle.MaxTypes) 139 | { 140 | Debug.Log($"Failed to add {fullTypeName}, reached the maximum number of types in the list! {_allQuickSaveTypeInfos.Count}/{QuickSaveTypeHandle.MaxTypes}"); 141 | return false; 142 | } 143 | 144 | if (maxElements > 0 && persistableTypeInfo.IsBuffer) 145 | { 146 | persistableTypeInfo.MaxElements = maxElements; 147 | } 148 | 149 | _allQuickSaveTypeInfos.Add(persistableTypeInfo); 150 | _allQuickSaveTypeInfos.Sort((info1, info2) => string.CompareOrdinal(info1.FullTypeName, info2.FullTypeName)); 151 | UnityEditor.EditorUtility.SetDirty(this); 152 | return true; 153 | } 154 | 155 | private static bool CreatePersistableTypeInfoFromFullTypeName(string fullTypeName, out QuickSaveSettingsTypeInfo persistableTypeInfo) 156 | { 157 | foreach (var typeInfoEntry in TypeManager.AllTypes) 158 | { 159 | if (typeInfoEntry.Type != null && typeInfoEntry.Type.FullName == fullTypeName && QuickSaveSettings.IsSupported(typeInfoEntry, out _)) 160 | { 161 | bool isBuffer = typeInfoEntry.Category == TypeManager.TypeCategory.BufferData; 162 | persistableTypeInfo = new QuickSaveSettingsTypeInfo() 163 | { 164 | FullTypeName = fullTypeName, 165 | StableTypeHash = typeInfoEntry.StableTypeHash, 166 | IsBuffer = isBuffer, 167 | MaxElements = math.max(1, isBuffer ? typeInfoEntry.BufferCapacity : 1) // Initial BufferCapacity seems like a decent default max 168 | }; 169 | return true; 170 | } 171 | } 172 | 173 | persistableTypeInfo = default; 174 | return false; 175 | } 176 | 177 | public void ClearPersistableTypesInEditor() 178 | { 179 | _allQuickSaveTypeInfos.Clear(); 180 | UnityEditor.EditorUtility.SetDirty(this); 181 | } 182 | 183 | internal string GetPrettyNameInEditor(string fullTypeName) 184 | { 185 | for (int i = 0; i < _allQuickSaveTypeInfos.Count; i++) 186 | { 187 | var typeInfo = _allQuickSaveTypeInfos[i]; 188 | if (_allQuickSaveTypeInfos[i].FullTypeName == fullTypeName) 189 | { 190 | string prettyName = fullTypeName.Substring(math.clamp(fullTypeName.LastIndexOf('.') + 1, 0, fullTypeName.Length)) 191 | + (typeInfo.IsBuffer ? " [B]" : ""); 192 | return prettyName; 193 | } 194 | } 195 | 196 | return "Invalid"; 197 | } 198 | #endif // UNITY_EDITOR 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /QuickSave/QuickSaveSettingsAsset.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 75b09d9b21e9c9f48abc5305570c127c 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /QuickSave/QuickSaveSubScene.cs: -------------------------------------------------------------------------------- 1 | // Author: Jonas De Maeseneer 2 | 3 | using Unity.Scenes; 4 | using UnityEngine; 5 | 6 | namespace QuickSave 7 | { 8 | [DisallowMultipleComponent] 9 | [RequireComponent(typeof(SubScene))] 10 | public class QuickSaveSubScene : MonoBehaviour 11 | { 12 | 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /QuickSave/QuickSaveSubScene.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3d195c4ffb26eef4aa37717960831cca 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /QuickSave/QuickSaveSystemBase.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c93f5b7d1510de64a99f350974a70eee 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /QuickSave/QuickSaveTypeHandle.cs: -------------------------------------------------------------------------------- 1 | // Author: Jonas De Maeseneer 2 | 3 | using System; 4 | using UnityEngine; 5 | 6 | namespace QuickSave 7 | { 8 | [Serializable] 9 | public struct QuickSaveTypeHandle : IEquatable 10 | { 11 | [SerializeField] 12 | private ushort _handle; 13 | 14 | public bool IsValid => _handle > 0; 15 | internal int IndexForQuickSaveSettings => _handle - 1; 16 | 17 | public static QuickSaveTypeHandle Invalid => default; 18 | public static int MaxTypes => ushort.MaxValue - 1; 19 | 20 | public QuickSaveTypeHandle(int indexInQuickSaveSettings) 21 | { 22 | _handle = (ushort) (indexInQuickSaveSettings + 1); 23 | } 24 | 25 | public bool Equals(QuickSaveTypeHandle other) 26 | { 27 | return _handle == other._handle; 28 | } 29 | 30 | public int CompareTo(QuickSaveTypeHandle other) 31 | { 32 | return _handle.CompareTo(other._handle); 33 | } 34 | 35 | public override bool Equals(object obj) 36 | { 37 | return obj is QuickSaveTypeHandle other && Equals(other); 38 | } 39 | 40 | public override int GetHashCode() 41 | { 42 | return _handle.GetHashCode(); 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /QuickSave/QuickSaveTypeHandle.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b19dff1d933d79c4abce2b315018ecb9 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /QuickSave/QuickSaveUnloadSceneSystem.cs: -------------------------------------------------------------------------------- 1 | // Author: Jonas De Maeseneer 2 | 3 | using Unity.Burst; 4 | using Unity.Collections; 5 | using Unity.Entities; 6 | 7 | namespace QuickSave 8 | { 9 | [BurstCompile] 10 | [UpdateInGroup(typeof(LateSimulationSystemGroup))] 11 | public partial struct QuickSaveUnloadSceneSystem : ISystem 12 | { 13 | private BufferLookup _requestLookup; 14 | 15 | private SystemHandle _quickSaveSystemHandle; 16 | private EntityQuery _autoPersistRequests; 17 | 18 | private ComponentTypeSet _typesToRemoveForUnload; 19 | private EntityQuery _unloadRequests; 20 | 21 | public void OnCreate(ref SystemState state) 22 | { 23 | _requestLookup = state.GetBufferLookup(); 24 | 25 | _quickSaveSystemHandle = state.World.GetOrCreateSystem(); 26 | _autoPersistRequests = new EntityQueryBuilder(Allocator.Temp) 27 | .WithAll() 28 | .Build(ref state); 29 | 30 | _typesToRemoveForUnload = new ComponentTypeSet(typeof(RequestSceneLoaded), typeof(RequestSceneUnloaded)); 31 | _unloadRequests = new EntityQueryBuilder(Allocator.Temp) 32 | .WithAll() 33 | .Build(ref state); 34 | } 35 | 36 | public void OnDestroy(ref SystemState state) { } 37 | 38 | [BurstCompile] 39 | public void OnUpdate(ref SystemState state) 40 | { 41 | if (!_autoPersistRequests.IsEmpty) 42 | { 43 | _requestLookup.Update(ref state); 44 | new AutoPersistRequestJob 45 | { 46 | DataTransferRequestLookup = _requestLookup, 47 | ExecutingSystem = _quickSaveSystemHandle 48 | }.Schedule(_autoPersistRequests); 49 | } 50 | 51 | if (!_unloadRequests.IsEmpty) 52 | { 53 | // this will trigger the actual unload 54 | var ecbSystem = SystemAPI.GetSingleton(); 55 | EntityCommandBuffer ecb = ecbSystem.CreateCommandBuffer(state.WorldUnmanaged); 56 | ecb.RemoveComponent(_unloadRequests, _typesToRemoveForUnload, EntityQueryCaptureMode.AtPlayback); 57 | } 58 | } 59 | 60 | [BurstCompile] 61 | public partial struct AutoPersistRequestJob : IJobEntity 62 | { 63 | public BufferLookup DataTransferRequestLookup; 64 | public SystemHandle ExecutingSystem; 65 | 66 | public void Execute(in AutoPersistOnUnload autoPersistOnUnload) 67 | { 68 | if (DataTransferRequestLookup.HasBuffer(autoPersistOnUnload.ContainerEntityToPersist)) 69 | { 70 | var buffer = DataTransferRequestLookup[autoPersistOnUnload.ContainerEntityToPersist]; 71 | buffer.Add(new DataTransferRequest 72 | { 73 | ExecutingSystem = ExecutingSystem, 74 | RequestType = DataTransferRequest.Type.FromEntitiesToDataContainer 75 | }); 76 | } 77 | } 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /QuickSave/QuickSaveUnloadSceneSystem.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6d59e34fc64b52e468b57a1e6eef6c41 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /QuickSave/com.studioaurelius.quicksave.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.studioaurelius.quicksave", 3 | "rootNamespace": "", 4 | "references": [ 5 | "GUID:e63a9f9b38516b1469a5055028a5560d", 6 | "GUID:2665a8d13d1b3f18800f46e256720795", 7 | "GUID:e0cd26848372d4e5c891c569017e11f1", 8 | "GUID:734d92eba21c94caba915361bd5ac177", 9 | "GUID:8a2eafa29b15f444eb6d74f94a930e1d", 10 | "GUID:5f3cf485eb0554709a8abbeace890c86", 11 | "GUID:d8b63aba1907145bea998dd612889d6b", 12 | "GUID:e04e6c86a9f3947eb95fded39f9e60cc", 13 | "GUID:a5baed0c9693541a5bd947d336ec7659" 14 | ], 15 | "includePlatforms": [], 16 | "excludePlatforms": [], 17 | "allowUnsafeCode": true, 18 | "overrideReferences": false, 19 | "precompiledReferences": [], 20 | "autoReferenced": true, 21 | "defineConstraints": [], 22 | "versionDefines": [], 23 | "noEngineReferences": false 24 | } -------------------------------------------------------------------------------- /QuickSave/com.studioaurelius.quicksave.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 42697df707fc47a4aa81e8cf52f99bfe 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![quicksavebanner](https://user-images.githubusercontent.com/23634827/218306750-7084d9a7-b36d-42c8-872b-dc86b690cfb5.png) 2 | *For any commercial work you can buy the asset on the Unity Asset Store [here](https://assetstore.unity.com/packages/tools/utilities/quicksave-state-saving-for-dots-246695). 3 | This repository comes with a restrictive [non-commercial license](LICENSE.MD).* 4 | 5 | ## In Short 6 | This Unity DOTS-related package enables you to asynchronously load and save state on entities without the need for any feature-specific code. 7 | The highly optimized implementation makes it a solid foundation for ambitious features since it can easily run on every frame. 8 | 9 | 10 | This version of the package supports Entities 1.0.11, it will get updated as unity releases new versions of their Entities package. 11 | 12 | ## Getting Started 13 | * [Package Manual](PackageManual.md) 14 | * [Demo Project](https://github.com/JonasDeM/QuickSaveDemo) 15 | 16 | ## Potential Use Cases 17 | Depending on what state you choose to save, anything is possible. 18 | You can choose to save the whole state of your application (given it is in ECS), maybe you choose to only save a handful of gameplay entities for a certain game mechanic, or you can do anything inbetween! 19 | Here are some ideas: 20 | * Replay System 21 | * Level Reset without reloading scenes 22 | * World Streaming & World State Saving 23 | * Time-rewind Gameplay 24 | * User Generated Content 25 | * QA Testing & Bug Reproduction 26 | * Networking 27 | 28 | ## Project Pillars 29 | 30 | #### Easy to Use 31 | * Stores the state from any set amount of entities into one simple array 32 | * No need for any state-specific saving or loading code 33 | * ECS API with all the benefits of Unity's safety systems 34 | 35 | #### Performant 36 | * All code is Burst compiled & most of the work is done in async jobs 37 | * Single schedule call per container 38 | 39 | #### Scalable 40 | * Partitions savedata by subscenes (or by any other GUID) 41 | * Stores user-defined state in binary format 42 | * (Planned for 2.0) LZ4 + Delta Compression 43 | 44 | #### Extendable 45 | * Make & Manage your own QuickSaveSystems 46 | * Make & Manage your own Containers 47 | 48 | ## Current Limitations 49 | * No support for quicksaving SharedComponentData, ChunkComponentData or any components with references/pointers. 50 | The Package Manual helps with working with this limitation. 51 | Support for this is technically possible, but would come with additional complexity & loss of performance. 52 | * No support for quicksaving destruction/creation of entities. Use the 'Disabled' component & quicksave that. 53 | The QuickSave Demo Project gives an example on how to implement a constant-size pool for dynamically spawnable prefabs. 54 | Supporting variable-sized containers is being looked at, but not guaranteed to see the light of day. 55 | 56 | ## Goals for 2.0 release: 57 | * Compression in the default serialization implementation. 58 | * Versioning & Upgradability of serialized state. 59 | * Improved support for dynamically spawned entities. 60 | * Inspector for containers. 61 | -------------------------------------------------------------------------------- /README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5443de95a5284e445a1b628622cf0a53 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.studioaurelius.quicksave", 3 | "version": "1.0.3", 4 | "displayName": "QuickSave", 5 | "description": "This Unity DOTS-related package enables you to asynchronously load and save state on entities without the need for any feature-specific code.\r\nThe highly optimized implementation makes it a solid foundation for ambitious features since it can easily run on every frame.", 6 | "unity": "2022.2", 7 | "author": { 8 | "name": "Studio Aurelius", 9 | "email": "jonas.de.maeseneer@outlook.com", 10 | "url": "https://www.studioaurelius.com" 11 | }, 12 | "dependencies": { 13 | "com.unity.entities": "1.0.11" 14 | }, 15 | "type": "library" 16 | } -------------------------------------------------------------------------------- /package.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 14b9bf9d4baaedf4d98714dd4fff6309 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | --------------------------------------------------------------------------------