Add main mesh fixes
This commit is contained in:
15
Assets/Asset Cleaner/AssetCleaner.asmdef
Normal file
15
Assets/Asset Cleaner/AssetCleaner.asmdef
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "AssetCleaner",
|
||||
"references": [],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": false,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
7
Assets/Asset Cleaner/AssetCleaner.asmdef.meta
Normal file
7
Assets/Asset Cleaner/AssetCleaner.asmdef.meta
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: de141fd48bce7594e9474fe440f5ea10
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
1236
Assets/Asset Cleaner/CleanerStyle.asset
Normal file
1236
Assets/Asset Cleaner/CleanerStyle.asset
Normal file
File diff suppressed because it is too large
Load Diff
8
Assets/Asset Cleaner/CleanerStyle.asset.meta
Normal file
8
Assets/Asset Cleaner/CleanerStyle.asset.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7f4dfe9d348c0ff4cb525a0b2c48fc34
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Asset Cleaner/Content.meta
Normal file
8
Assets/Asset Cleaner/Content.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f45f9c9770a8371428fce5031e1c7c5c
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/Asset Cleaner/Content/ac_back1.png
LFS
Normal file
BIN
Assets/Asset Cleaner/Content/ac_back1.png
LFS
Normal file
Binary file not shown.
110
Assets/Asset Cleaner/Content/ac_back1.png.meta
Normal file
110
Assets/Asset Cleaner/Content/ac_back1.png.meta
Normal file
@@ -0,0 +1,110 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bfec65de7ee2e114199f83507d375a6f
|
||||
TextureImporter:
|
||||
fileIDToRecycleName: {}
|
||||
externalObjects: {}
|
||||
serializedVersion: 9
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: -1
|
||||
aniso: 1
|
||||
mipBias: -100
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: -1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 2
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 2
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 32
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 2
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
- serializedVersion: 2
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 32
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 2
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
- serializedVersion: 2
|
||||
buildTarget: Android
|
||||
maxTextureSize: 32
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 2
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 5e97eb03825dee720800000000000000
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
spritePackingTag:
|
||||
pSDRemoveMatte: 0
|
||||
pSDShowRemoveMatteOption: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/Asset Cleaner/Content/ac_delete_asset.png
LFS
Normal file
BIN
Assets/Asset Cleaner/Content/ac_delete_asset.png
LFS
Normal file
Binary file not shown.
110
Assets/Asset Cleaner/Content/ac_delete_asset.png.meta
Normal file
110
Assets/Asset Cleaner/Content/ac_delete_asset.png.meta
Normal file
@@ -0,0 +1,110 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f4b3e5a29ec20194a8ba1d6cf7b1b037
|
||||
TextureImporter:
|
||||
fileIDToRecycleName: {}
|
||||
externalObjects: {}
|
||||
serializedVersion: 9
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: -1
|
||||
aniso: 1
|
||||
mipBias: -100
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: -1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 2
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 2
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 32
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 2
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
- serializedVersion: 2
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 32
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 2
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
- serializedVersion: 2
|
||||
buildTarget: Android
|
||||
maxTextureSize: 32
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 2
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID:
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
spritePackingTag:
|
||||
pSDRemoveMatte: 0
|
||||
pSDShowRemoveMatteOption: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/Asset Cleaner/Content/ac_delete_scene_asset.png
LFS
Normal file
BIN
Assets/Asset Cleaner/Content/ac_delete_scene_asset.png
LFS
Normal file
Binary file not shown.
110
Assets/Asset Cleaner/Content/ac_delete_scene_asset.png.meta
Normal file
110
Assets/Asset Cleaner/Content/ac_delete_scene_asset.png.meta
Normal file
@@ -0,0 +1,110 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3fec3bf4c4f5e2d48835dbe2f21aa7d0
|
||||
TextureImporter:
|
||||
fileIDToRecycleName: {}
|
||||
externalObjects: {}
|
||||
serializedVersion: 9
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: -1
|
||||
aniso: 1
|
||||
mipBias: -100
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: -1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 2
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 2
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 32
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 2
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
- serializedVersion: 2
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 32
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 2
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
- serializedVersion: 2
|
||||
buildTarget: Android
|
||||
maxTextureSize: 32
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 2
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID:
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
spritePackingTag:
|
||||
pSDRemoveMatte: 0
|
||||
pSDShowRemoveMatteOption: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/Asset Cleaner/Content/ac_forward1.png
LFS
Normal file
BIN
Assets/Asset Cleaner/Content/ac_forward1.png
LFS
Normal file
Binary file not shown.
110
Assets/Asset Cleaner/Content/ac_forward1.png.meta
Normal file
110
Assets/Asset Cleaner/Content/ac_forward1.png.meta
Normal file
@@ -0,0 +1,110 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 07bb28c80f378c64194dd46c34b2d6c1
|
||||
TextureImporter:
|
||||
fileIDToRecycleName: {}
|
||||
externalObjects: {}
|
||||
serializedVersion: 9
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: -1
|
||||
aniso: 1
|
||||
mipBias: -100
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: -1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 2
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 2
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 32
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 2
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
- serializedVersion: 2
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 32
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 2
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
- serializedVersion: 2
|
||||
buildTarget: Android
|
||||
maxTextureSize: 32
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 2
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 5e97eb03825dee720800000000000000
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
spritePackingTag:
|
||||
pSDRemoveMatte: 0
|
||||
pSDShowRemoveMatteOption: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/Asset Cleaner/Content/ac_lock.png
LFS
Normal file
BIN
Assets/Asset Cleaner/Content/ac_lock.png
LFS
Normal file
Binary file not shown.
110
Assets/Asset Cleaner/Content/ac_lock.png.meta
Normal file
110
Assets/Asset Cleaner/Content/ac_lock.png.meta
Normal file
@@ -0,0 +1,110 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c5fd88366c67daf4585631953fa79779
|
||||
TextureImporter:
|
||||
fileIDToRecycleName: {}
|
||||
externalObjects: {}
|
||||
serializedVersion: 9
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: -1
|
||||
aniso: 1
|
||||
mipBias: -100
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: -1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 2
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 2
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 32
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 2
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
- serializedVersion: 2
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 32
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 2
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
- serializedVersion: 2
|
||||
buildTarget: Android
|
||||
maxTextureSize: 32
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 2
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID:
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
spritePackingTag:
|
||||
pSDRemoveMatte: 0
|
||||
pSDShowRemoveMatteOption: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/Asset Cleaner/Content/ac_multiselect.png
LFS
Normal file
BIN
Assets/Asset Cleaner/Content/ac_multiselect.png
LFS
Normal file
Binary file not shown.
110
Assets/Asset Cleaner/Content/ac_multiselect.png.meta
Normal file
110
Assets/Asset Cleaner/Content/ac_multiselect.png.meta
Normal file
@@ -0,0 +1,110 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e0208e3a37267be46ba2d10fad510cef
|
||||
TextureImporter:
|
||||
fileIDToRecycleName: {}
|
||||
externalObjects: {}
|
||||
serializedVersion: 9
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: -1
|
||||
aniso: 1
|
||||
mipBias: -100
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: -1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 2
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 2
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 32
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
- serializedVersion: 2
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 32
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
- serializedVersion: 2
|
||||
buildTarget: Android
|
||||
maxTextureSize: 32
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID:
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
spritePackingTag:
|
||||
pSDRemoveMatte: 0
|
||||
pSDShowRemoveMatteOption: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/Asset Cleaner/Content/ac_unlock.png
LFS
Normal file
BIN
Assets/Asset Cleaner/Content/ac_unlock.png
LFS
Normal file
Binary file not shown.
110
Assets/Asset Cleaner/Content/ac_unlock.png.meta
Normal file
110
Assets/Asset Cleaner/Content/ac_unlock.png.meta
Normal file
@@ -0,0 +1,110 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e755b5ef4cb427f469ba580ae9956237
|
||||
TextureImporter:
|
||||
fileIDToRecycleName: {}
|
||||
externalObjects: {}
|
||||
serializedVersion: 9
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: -1
|
||||
aniso: 1
|
||||
mipBias: -100
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: -1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 2
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 2
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 32
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 2
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
- serializedVersion: 2
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 32
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 2
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
- serializedVersion: 2
|
||||
buildTarget: Android
|
||||
maxTextureSize: 32
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 2
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID:
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
spritePackingTag:
|
||||
pSDRemoveMatte: 0
|
||||
pSDShowRemoveMatteOption: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Asset Cleaner/Data.meta
Normal file
8
Assets/Asset Cleaner/Data.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4848aa5846cd6264c984dcc6cd9d72af
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
74
Assets/Asset Cleaner/Data/AufSerializableData.cs
Normal file
74
Assets/Asset Cleaner/Data/AufSerializableData.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Asset_Cleaner {
|
||||
[Serializable]
|
||||
class AufSerializableData {
|
||||
public const int CurrentVersion = 1;
|
||||
public int Version;
|
||||
public bool MarkRed;
|
||||
public int RebuildCacheOnDemand;
|
||||
public bool ShowInfoBox;
|
||||
public string IgnorePathContainsCombined;
|
||||
public bool IgnoreMaterial;
|
||||
public bool IgnoreScriptable;
|
||||
|
||||
static int BoolToInt(bool val) {
|
||||
return val ? 2 : 1;
|
||||
}
|
||||
|
||||
static bool IntToBool(int val, bool defaultVal) {
|
||||
switch (val) {
|
||||
case 2:
|
||||
return true;
|
||||
case 1:
|
||||
return false;
|
||||
default:
|
||||
return defaultVal;
|
||||
}
|
||||
}
|
||||
|
||||
public static AufSerializableData Default() {
|
||||
return new AufSerializableData {
|
||||
Version = CurrentVersion,
|
||||
MarkRed = true,
|
||||
ShowInfoBox = true,
|
||||
IgnorePathContainsCombined = "Gizmos;Resources;Editor;Asset Cleaner;Asset Usage Finder;",
|
||||
IgnoreMaterial = false,
|
||||
IgnoreScriptable = true,
|
||||
RebuildCacheOnDemand = 2,
|
||||
};
|
||||
}
|
||||
|
||||
public static void OnSerialize(in Config src, out AufSerializableData result) {
|
||||
result = new AufSerializableData();
|
||||
result.Version = CurrentVersion;
|
||||
result.MarkRed = src.MarkRed;
|
||||
result.ShowInfoBox = src.ShowInfoBox;
|
||||
result.IgnorePathContainsCombined = src.IgnorePathContainsCombined;
|
||||
result.IgnoreMaterial = src.IgnoreMaterial;
|
||||
result.IgnoreScriptable = src.IgnoreScriptable;
|
||||
result.RebuildCacheOnDemand = BoolToInt(src.RebuildCacheOnDemand);
|
||||
}
|
||||
|
||||
public static void OnDeserialize(in AufSerializableData src, ref Config result) {
|
||||
var def = Default();
|
||||
|
||||
result.MarkRed = src.MarkRed;
|
||||
result.IgnorePathContainsCombined = src.IgnorePathContainsCombined;
|
||||
result.ShowInfoBox = src.ShowInfoBox;
|
||||
result.IgnorePathContains = result.IgnorePathContainsCombined
|
||||
.Split(';')
|
||||
.Select(s => s.Trim())
|
||||
.Where(s => !string.IsNullOrWhiteSpace(s))
|
||||
.ToArray();
|
||||
result.IgnoreMaterial = src.IgnoreMaterial;
|
||||
result.IgnoreScriptable = src.IgnoreScriptable;
|
||||
result.RebuildCacheOnDemand = IntToBool(src.RebuildCacheOnDemand, def.RebuildCacheOnDemand == 2);
|
||||
}
|
||||
|
||||
public bool Valid() {
|
||||
return Version == CurrentVersion || IgnorePathContainsCombined == null;
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Asset Cleaner/Data/AufSerializableData.cs.meta
Normal file
3
Assets/Asset Cleaner/Data/AufSerializableData.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 14980ecc573d44c59b5f9323603c7bdf
|
||||
timeCreated: 1588518687
|
||||
105
Assets/Asset Cleaner/Data/CleanerStyleAsset.cs
Normal file
105
Assets/Asset Cleaner/Data/CleanerStyleAsset.cs
Normal file
@@ -0,0 +1,105 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
#pragma warning disable 649
|
||||
|
||||
namespace Asset_Cleaner {
|
||||
class CleanerStyleAsset : ScriptableObject {
|
||||
[Serializable]
|
||||
public class Style {
|
||||
public Color RedHighlight = new Color(1, 0, 0, 1f);
|
||||
|
||||
public GUIContent Lock;
|
||||
public GUIStyle LockBtn = new GUIStyle();
|
||||
public GUIStyle SampleBtn = new GUIStyle();
|
||||
|
||||
public GUIContent Unlock;
|
||||
public GUIStyle UnlockBtn = new GUIStyle();
|
||||
|
||||
public GUIContent RemoveFile;
|
||||
public GUIContent RemoveScene;
|
||||
|
||||
public GUIStyle RowMainAssetBtn = new GUIStyle();
|
||||
public GUIStyle RemoveUnusedBtn = new GUIStyle();
|
||||
|
||||
public GUIStyle CurrentBtn = new GUIStyle();
|
||||
|
||||
public GUIContent ArrowL;
|
||||
public GUIContent ArrowR;
|
||||
public GUIStyle ArrowBtn = new GUIStyle();
|
||||
|
||||
public float SceneIndent1 = 20f;
|
||||
public float SceneIndent2 = 20f;
|
||||
public GUIStyle ProjectViewCounterLabel;
|
||||
|
||||
public GUIContent MultiSelect;
|
||||
|
||||
public static bool TryFindSelf(out Style value) {
|
||||
const string typeName = nameof(CleanerStyleAsset);
|
||||
|
||||
var guids = AssetDatabase.FindAssets($"t:{typeName}");
|
||||
if (!guids.Any()) {
|
||||
AssetDatabase.Refresh(ImportAssetOptions.ForceUpdate);
|
||||
}
|
||||
|
||||
Asr.IsTrue(guids.Length > 0, $"No '{typeName}' assets found");
|
||||
var res = guids.Select(AssetDatabase.GUIDToAssetPath).Select(t => (CleanerStyleAsset) AssetDatabase.LoadAssetAtPath(t, typeof(CleanerStyleAsset))).FirstOrDefault();
|
||||
if (res == null) {
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
value = EditorGUIUtility.isProSkin ? res.Pro : res.Personal;
|
||||
return value != null;
|
||||
}
|
||||
}
|
||||
#pragma warning disable 0649
|
||||
public Style Pro;
|
||||
public Style Personal;
|
||||
#pragma warning restore
|
||||
|
||||
[CustomEditor(typeof(CleanerStyleAsset))]
|
||||
class Editor : UnityEditor.Editor {
|
||||
public override void OnInspectorGUI() {
|
||||
#if false
|
||||
if (GUILayout.Button("Update Btn backgrounds")) {
|
||||
var targ = (CleanerStyleAsset) target;
|
||||
Set(targ.Pro);
|
||||
}
|
||||
#endif
|
||||
EditorGUI.BeginChangeCheck();
|
||||
base.OnInspectorGUI();
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
UnityEditorInternal.InternalEditorUtility.RepaintAllViews();
|
||||
}
|
||||
|
||||
#if false
|
||||
static void Set(Style style) {
|
||||
var st = style;
|
||||
var sample = st.SampleBtn;
|
||||
|
||||
foreach (var btn in new[] {
|
||||
st.LockBtn,
|
||||
st.UnlockBtn,
|
||||
st.RowMainAssetBtn,
|
||||
st.RemoveUnusedBtn,
|
||||
st.CurrentBtn,
|
||||
st.ArrowBtn,
|
||||
}) {
|
||||
btn.normal = sample.normal;
|
||||
btn.hover = sample.hover;
|
||||
btn.active = sample.active;
|
||||
btn.focused = sample.focused;
|
||||
btn.onNormal = sample.onNormal;
|
||||
btn.onHover = sample.onHover;
|
||||
btn.onActive = sample.onActive;
|
||||
btn.onFocused = sample.onFocused;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Asset Cleaner/Data/CleanerStyleAsset.cs.meta
Normal file
11
Assets/Asset Cleaner/Data/CleanerStyleAsset.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 16578cbec6a24ac4d951b5f328a970ca
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Asset Cleaner/Data/FindModeEnum.cs
Normal file
8
Assets/Asset Cleaner/Data/FindModeEnum.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace Asset_Cleaner {
|
||||
enum FindModeEnum {
|
||||
None = 0,
|
||||
File = 1,
|
||||
Scene = 2,
|
||||
Stage = 3,
|
||||
}
|
||||
}
|
||||
11
Assets/Asset Cleaner/Data/FindModeEnum.cs.meta
Normal file
11
Assets/Asset Cleaner/Data/FindModeEnum.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 34e14d6333c649f4fb74ccdb6c86d8b6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
3
Assets/Asset Cleaner/Data/Globals.meta
Normal file
3
Assets/Asset Cleaner/Data/Globals.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f94bcb0962724eeea17b007a6ddfd885
|
||||
timeCreated: 1596213773
|
||||
286
Assets/Asset Cleaner/Data/Globals/BacklinkStore.cs
Normal file
286
Assets/Asset Cleaner/Data/Globals/BacklinkStore.cs
Normal file
@@ -0,0 +1,286 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEditorInternal;
|
||||
using Debug = UnityEngine.Debug;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace Asset_Cleaner {
|
||||
class BacklinkStore {
|
||||
public bool Initialized { get; private set; }
|
||||
|
||||
public Dictionary<string, long> UnusedFiles { get; private set; }
|
||||
public Dictionary<string, long> UnusedScenes { get; private set; }
|
||||
public Dictionary<string, BwMeta> Backward { get; private set; }
|
||||
public Dictionary<string, UnusedQty> FoldersWithQty { get; private set; }
|
||||
|
||||
Dictionary<string, FwMeta> _forward;
|
||||
List<string> Folders { get; set; }
|
||||
|
||||
|
||||
public void Init() {
|
||||
FoldersWithQty = new Dictionary<string, UnusedQty>();
|
||||
_forward = new Dictionary<string, FwMeta>();
|
||||
Backward = new Dictionary<string, BwMeta>();
|
||||
|
||||
var defaultAss = typeof(DefaultAsset);
|
||||
var asmdefAss = typeof(AssemblyDefinitionAsset);
|
||||
|
||||
var paths = AssetDatabase.GetAllAssetPaths()
|
||||
.Distinct()
|
||||
.Where(s => s.StartsWith("Assets") || s.StartsWith("ProjectSettings"))
|
||||
.Where(p => {
|
||||
var t = AssetDatabase.GetMainAssetTypeAtPath(p);
|
||||
return !t.IsAssignableFromInverse(defaultAss) && !t.IsAssignableFromInverse(asmdefAss);
|
||||
})
|
||||
.ToArray();
|
||||
|
||||
|
||||
var i = 0f;
|
||||
var total = (float) paths.Length;
|
||||
foreach (var path in paths) {
|
||||
_FillFwAndBacklinks(path);
|
||||
var percent = i * 100f / total;
|
||||
if (Math.Abs(percent % 5f) < 0.01f) {
|
||||
if (EditorUtility.DisplayCancelableProgressBar(
|
||||
"Please wait...",
|
||||
"Building the cache...", percent))
|
||||
Debug.LogError("Cache build aborted");
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
EditorUtility.ClearProgressBar();
|
||||
|
||||
// FillFoldersWithQtyByPaths
|
||||
List<string> foldersAll = new List<string>();
|
||||
foreach (var path in paths) {
|
||||
var folders = GetAllFoldersFromPath(path);
|
||||
foldersAll.AddRange(folders);
|
||||
}
|
||||
|
||||
Folders = foldersAll.Distinct().OrderBy(p => p).ToList();
|
||||
UpdateUnusedAssets();
|
||||
Initialized = true;
|
||||
}
|
||||
|
||||
void _FillFwAndBacklinks(string path) {
|
||||
var dependencies = _Dependencies(path);
|
||||
var hs = new FwMeta {Dependencies = new HashSet<string>(dependencies)};
|
||||
_forward.Add(path, hs);
|
||||
foreach (var backPath in dependencies) {
|
||||
if (!Backward.TryGetValue(backPath, out var val)) {
|
||||
val = new BwMeta();
|
||||
val.Lookup = new HashSet<string>();
|
||||
Backward.Add(backPath, val);
|
||||
}
|
||||
|
||||
val.Lookup.Add(path);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void UpdateFoldersWithQtyByPath(string path) {
|
||||
var folders = GetAllFoldersFromPath(path);
|
||||
foreach (var folder in folders) {
|
||||
if (!Folders.Exists(p => p == folder))
|
||||
Folders.Add(folder);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static List<string> GetAllFoldersFromPath(string p) {
|
||||
var result = new List<string>();
|
||||
var i = p.IndexOf('/', 0);
|
||||
while (i > 0) {
|
||||
var item = p.Substring(0, i);
|
||||
result.Add(item);
|
||||
i = p.IndexOf('/', i + 1);
|
||||
}
|
||||
|
||||
return result.Distinct().ToList();
|
||||
}
|
||||
|
||||
public void UpdateUnusedAssets() {
|
||||
var all = new HashSet<string>(_forward.Keys);
|
||||
var withBacklinks =
|
||||
new HashSet<string>(Backward.Where(kv => kv.Value.Lookup.Count > 0).Select(kv => kv.Key));
|
||||
|
||||
all.ExceptWith(withBacklinks);
|
||||
all.RemoveWhere(SearchUtils.IsFileIgrnoredBySettings);
|
||||
|
||||
var unusedAssets = all;
|
||||
|
||||
var scenes = unusedAssets.Where(s =>
|
||||
AssetDatabase.GetMainAssetTypeAtPath(s).IsAssignableFromInverse(typeof(SceneAsset))).ToArray();
|
||||
|
||||
unusedAssets.ExceptWith(scenes);
|
||||
var files = unusedAssets;
|
||||
UnusedFiles = new Dictionary<string, long>();
|
||||
|
||||
foreach (var file in files) UnusedFiles[file] = CommonUtils.Size(file);
|
||||
|
||||
UnusedScenes = new Dictionary<string, long>();
|
||||
foreach (var scene in scenes) UnusedScenes[scene] = CommonUtils.Size(scene);
|
||||
|
||||
// UpdateFoldersWithQty();
|
||||
foreach (var folder in Folders) {
|
||||
var unusedFilesQty = UnusedFiles.Count(p => p.Key.StartsWith(folder));
|
||||
var unusedScenesQty = UnusedScenes.Count(p => p.Key.StartsWith(folder));
|
||||
long size = 0;
|
||||
size = UnusedFiles.Where((p => p.Key.StartsWith(folder))).Sum(p => p.Value);
|
||||
size += UnusedScenes.Where(p => p.Key.StartsWith(folder)).Sum(p => p.Value);
|
||||
|
||||
FoldersWithQty.TryGetValue(folder, out var folderWithQty);
|
||||
if (folderWithQty == null) {
|
||||
FoldersWithQty.Add(folder, new UnusedQty(unusedFilesQty, unusedScenesQty, size));
|
||||
}
|
||||
else {
|
||||
folderWithQty.UnusedFilesQty = unusedFilesQty;
|
||||
folderWithQty.UnusedScenesQty = unusedScenesQty;
|
||||
folderWithQty.UnusedSize = size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void Remove(string path) {
|
||||
if (!_forward.TryGetValue(path, out var fwMeta))
|
||||
return;
|
||||
|
||||
foreach (var dependency in fwMeta.Dependencies) {
|
||||
if (!Backward.TryGetValue(dependency, out var dep)) continue;
|
||||
|
||||
dep.Lookup.Remove(path);
|
||||
}
|
||||
|
||||
_forward.Remove(path);
|
||||
UpdateFoldersWithQtyByPath(path);
|
||||
}
|
||||
|
||||
public void Replace(string src, string dest) {
|
||||
_Upd(_forward);
|
||||
_Upd(Backward);
|
||||
UpdateFoldersWithQtyByPath(dest);
|
||||
|
||||
void _Upd<T>(Dictionary<string, T> dic) {
|
||||
if (!dic.TryGetValue(src, out var refs)) return;
|
||||
|
||||
dic.Remove(src);
|
||||
dic.Add(dest, refs);
|
||||
}
|
||||
}
|
||||
|
||||
public void RebuildFor(string path, bool remove) {
|
||||
if (!_forward.TryGetValue(path, out var fwMeta)) {
|
||||
fwMeta = new FwMeta();
|
||||
_forward.Add(path, fwMeta);
|
||||
}
|
||||
else if (remove) {
|
||||
foreach (var dependency in fwMeta.Dependencies) {
|
||||
if (!Backward.TryGetValue(dependency, out var backDep)) continue;
|
||||
|
||||
backDep.Lookup.Remove(path);
|
||||
}
|
||||
|
||||
fwMeta.Dependencies = null;
|
||||
}
|
||||
|
||||
var dependencies = _Dependencies(path);
|
||||
fwMeta.Dependencies = new HashSet<string>(dependencies);
|
||||
|
||||
foreach (var backPath in dependencies) {
|
||||
if (!Backward.TryGetValue(backPath, out var bwMeta)) {
|
||||
bwMeta = new BwMeta {Lookup = new HashSet<string>()};
|
||||
Backward.Add(backPath, bwMeta);
|
||||
}
|
||||
else if (remove)
|
||||
bwMeta.Lookup.Remove(path);
|
||||
|
||||
bwMeta.Lookup.Add(path);
|
||||
}
|
||||
|
||||
if (!remove) {
|
||||
UpdateFoldersWithQtyByPath(path);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static string[] _Dependencies(string s) {
|
||||
if (s[0] == 'A')
|
||||
return AssetDatabase.GetDependencies(s, false);
|
||||
var obj = LoadAllOrMain(s)[0];
|
||||
return GetDependenciesManualPaths().ToArray();
|
||||
|
||||
Object[] LoadAllOrMain(string assetPath) {
|
||||
// prevents error "Do not use readobjectthreaded on scene objects!"
|
||||
return typeof(SceneAsset) == AssetDatabase.GetMainAssetTypeAtPath(assetPath)
|
||||
? new[] {AssetDatabase.LoadMainAssetAtPath(assetPath)}
|
||||
: AssetDatabase.LoadAllAssetsAtPath(assetPath);
|
||||
}
|
||||
|
||||
IEnumerable<string> GetDependenciesManualPaths() {
|
||||
if (obj is EditorBuildSettings) {
|
||||
foreach (var scene in EditorBuildSettings.scenes)
|
||||
yield return scene.path;
|
||||
}
|
||||
|
||||
using (var so = new SerializedObject(obj)) {
|
||||
var props = so.GetIterator();
|
||||
while (props.Next(true)) {
|
||||
switch (props.propertyType) {
|
||||
case SerializedPropertyType.ObjectReference:
|
||||
var propsObjectReferenceValue = props.objectReferenceValue;
|
||||
if (!propsObjectReferenceValue) continue;
|
||||
|
||||
var assetPath = AssetDatabase.GetAssetPath(propsObjectReferenceValue);
|
||||
yield return assetPath;
|
||||
break;
|
||||
#if later
|
||||
case SerializedPropertyType.Generic:
|
||||
case SerializedPropertyType.ExposedReference:
|
||||
case SerializedPropertyType.ManagedReference:
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class FwMeta {
|
||||
public HashSet<string> Dependencies;
|
||||
}
|
||||
|
||||
public class BwMeta {
|
||||
public HashSet<string> Lookup;
|
||||
}
|
||||
|
||||
public class UnusedQty {
|
||||
public int UnusedFilesQty;
|
||||
public int UnusedScenesQty;
|
||||
|
||||
public long UnusedSize;
|
||||
|
||||
public UnusedQty() {
|
||||
Init(0, 0, 0);
|
||||
}
|
||||
|
||||
public UnusedQty(int unusedFilesQty, int unusedScenesQty, long unusedSize) {
|
||||
Init(unusedFilesQty, unusedScenesQty, unusedSize);
|
||||
}
|
||||
|
||||
private void Init(int unusedFilesQty, int unusedScenesQty, long unusedSize) {
|
||||
UnusedFilesQty = unusedFilesQty;
|
||||
UnusedScenesQty = unusedScenesQty;
|
||||
UnusedSize = unusedSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Asset Cleaner/Data/Globals/BacklinkStore.cs.meta
Normal file
3
Assets/Asset Cleaner/Data/Globals/BacklinkStore.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 004e5680782f4e679ae93027d2d40023
|
||||
timeCreated: 1577106223
|
||||
20
Assets/Asset Cleaner/Data/Globals/Config.cs
Normal file
20
Assets/Asset Cleaner/Data/Globals/Config.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
namespace Asset_Cleaner {
|
||||
class Config {
|
||||
// serialized
|
||||
public bool MarkRed;
|
||||
public string IgnorePathContainsCombined;
|
||||
public bool ShowInfoBox;
|
||||
public bool RebuildCacheOnDemand;
|
||||
|
||||
// todo make type array
|
||||
public bool IgnoreMaterial;
|
||||
public bool IgnoreScriptable;
|
||||
|
||||
// serialized only while window is opened
|
||||
public bool Locked;
|
||||
|
||||
// non-serialized
|
||||
public string[] IgnorePathContains;
|
||||
public string InitializationTime;
|
||||
}
|
||||
}
|
||||
3
Assets/Asset Cleaner/Data/Globals/Config.cs.meta
Normal file
3
Assets/Asset Cleaner/Data/Globals/Config.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a6ff796e4dab4324887a303cba29eed4
|
||||
timeCreated: 1577117228
|
||||
15
Assets/Asset Cleaner/Data/Globals/PersistentUndoRedoState.cs
Normal file
15
Assets/Asset Cleaner/Data/Globals/PersistentUndoRedoState.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Asset_Cleaner {
|
||||
[Serializable]
|
||||
class PersistentUndoRedoState {
|
||||
public List<SelectionEntry> History = new List<SelectionEntry>();
|
||||
public int Id;
|
||||
|
||||
public void Deconstruct(out List<SelectionEntry> list, out int id) {
|
||||
id = Id;
|
||||
list = History;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5414db21dfea4d7eaff8ce4c8f106c8d
|
||||
timeCreated: 1596213717
|
||||
6
Assets/Asset Cleaner/Data/Globals/UndoRedoState.cs
Normal file
6
Assets/Asset Cleaner/Data/Globals/UndoRedoState.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace Asset_Cleaner {
|
||||
class UndoRedoState {
|
||||
public bool UndoEnabled;
|
||||
public bool RedoEnabled;
|
||||
}
|
||||
}
|
||||
3
Assets/Asset Cleaner/Data/Globals/UndoRedoState.cs.meta
Normal file
3
Assets/Asset Cleaner/Data/Globals/UndoRedoState.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c3ac9efa32c8491aa8a11749f8020d96
|
||||
timeCreated: 1596213776
|
||||
14
Assets/Asset Cleaner/Data/Globals/WindowData.cs
Normal file
14
Assets/Asset Cleaner/Data/Globals/WindowData.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Asset_Cleaner {
|
||||
class WindowData {
|
||||
public bool ExpandFiles;
|
||||
public bool ExpandScenes;
|
||||
public Vector2 ScrollPos;
|
||||
public CleanerStyleAsset.Style Style;
|
||||
public GUIContent SceneFoldout;
|
||||
public PrevClick Click;
|
||||
public AufWindow Window;
|
||||
public FindModeEnum FindFrom;
|
||||
}
|
||||
}
|
||||
3
Assets/Asset Cleaner/Data/Globals/WindowData.cs.meta
Normal file
3
Assets/Asset Cleaner/Data/Globals/WindowData.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8481f043b2414c08a45957dcd6035fbe
|
||||
timeCreated: 1575305740
|
||||
11
Assets/Asset Cleaner/Data/InSceneResult.cs
Normal file
11
Assets/Asset Cleaner/Data/InSceneResult.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using Leopotam.Ecs;
|
||||
|
||||
namespace Asset_Cleaner {
|
||||
class InSceneResult : IEcsAutoReset {
|
||||
public string ScenePath;
|
||||
|
||||
public void Reset() {
|
||||
ScenePath = default;
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Asset Cleaner/Data/InSceneResult.cs.meta
Normal file
3
Assets/Asset Cleaner/Data/InSceneResult.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 013577ffef1140109e8367f2a5f75fa6
|
||||
timeCreated: 1577269723
|
||||
18
Assets/Asset Cleaner/Data/PrevClick.cs
Normal file
18
Assets/Asset Cleaner/Data/PrevClick.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Asset_Cleaner {
|
||||
public struct PrevClick {
|
||||
const float DoubleClickTime = 0.5f;
|
||||
Object _target;
|
||||
float _timeClicked;
|
||||
|
||||
public PrevClick(Object target) {
|
||||
_target = target;
|
||||
_timeClicked = Time.realtimeSinceStartup;
|
||||
}
|
||||
|
||||
public bool IsDoubleClick(Object o) {
|
||||
return _target == o && Time.realtimeSinceStartup - _timeClicked < DoubleClickTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Asset Cleaner/Data/PrevClick.cs.meta
Normal file
3
Assets/Asset Cleaner/Data/PrevClick.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 31c0f204d1b044caaa02a0726f11a270
|
||||
timeCreated: 1576087327
|
||||
19
Assets/Asset Cleaner/Data/Result.cs
Normal file
19
Assets/Asset Cleaner/Data/Result.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using Leopotam.Ecs;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace Asset_Cleaner {
|
||||
class Result : IEcsAutoReset {
|
||||
public string FilePath;
|
||||
public Object File;
|
||||
public Object MainFile;
|
||||
public GameObject RootGo;
|
||||
|
||||
public void Reset() {
|
||||
FilePath = default;
|
||||
File = default;
|
||||
MainFile = default;
|
||||
RootGo = default;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Asset Cleaner/Data/Result.cs.meta
Normal file
11
Assets/Asset Cleaner/Data/Result.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 46f4fd24243adc749b377209f744d628
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
20
Assets/Asset Cleaner/Data/SceneDetails.cs
Normal file
20
Assets/Asset Cleaner/Data/SceneDetails.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using Leopotam.Ecs;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace Asset_Cleaner {
|
||||
class SceneDetails : IEcsAutoReset {
|
||||
public string Path;
|
||||
public Scene Scene;
|
||||
public bool SearchRequested;
|
||||
public bool SearchDone;
|
||||
public bool WasOpened;
|
||||
|
||||
public void Reset() {
|
||||
Path = default;
|
||||
Scene = default;
|
||||
SearchRequested = default;
|
||||
SearchDone = default;
|
||||
WasOpened = default;
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Asset Cleaner/Data/SceneDetails.cs.meta
Normal file
3
Assets/Asset Cleaner/Data/SceneDetails.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 14749f86c2c14febb592442508f8c23a
|
||||
timeCreated: 1577266613
|
||||
11
Assets/Asset Cleaner/Data/SceneResult.cs
Normal file
11
Assets/Asset Cleaner/Data/SceneResult.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using Leopotam.Ecs;
|
||||
|
||||
namespace Asset_Cleaner {
|
||||
class SceneResult : IEcsAutoReset {
|
||||
public string PathNicified;
|
||||
|
||||
public void Reset() {
|
||||
PathNicified = default;
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Asset Cleaner/Data/SceneResult.cs.meta
Normal file
3
Assets/Asset Cleaner/Data/SceneResult.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 338e9e86c9274f91808429753a912d4b
|
||||
timeCreated: 1576083610
|
||||
26
Assets/Asset Cleaner/Data/SearchArg.cs
Normal file
26
Assets/Asset Cleaner/Data/SearchArg.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using System.Collections.Generic;
|
||||
using Leopotam.Ecs;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace Asset_Cleaner {
|
||||
class SearchArg : IEcsAutoReset {
|
||||
public Object Target;
|
||||
public Object Main;
|
||||
public string FilePath;
|
||||
public Option<Object[]> SubAssets;
|
||||
public Scene Scene;
|
||||
public List<string> UnusedAssetsFiltered;
|
||||
public List<string> UnusedScenesFiltered;
|
||||
|
||||
public void Reset() {
|
||||
UnusedAssetsFiltered = default;
|
||||
UnusedScenesFiltered = default;
|
||||
Target = default;
|
||||
Main = default;
|
||||
SubAssets = default;
|
||||
Scene = default;
|
||||
FilePath = default;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Asset Cleaner/Data/SearchArg.cs.meta
Normal file
11
Assets/Asset Cleaner/Data/SearchArg.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4a6b25ca9296c9e41affadc9ccecb1f1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
32
Assets/Asset Cleaner/Data/SearchResultGui.cs
Normal file
32
Assets/Asset Cleaner/Data/SearchResultGui.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System.Collections.Generic;
|
||||
using Leopotam.Ecs;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Asset_Cleaner {
|
||||
class SearchResultGui : IEcsAutoReset {
|
||||
public SerializedObject SerializedObject;
|
||||
public List<PropertyData> Properties;
|
||||
public GUIContent Label;
|
||||
public string TransformPath;
|
||||
|
||||
public void Reset() {
|
||||
SerializedObject?.Dispose();
|
||||
SerializedObject = default;
|
||||
|
||||
if (Properties != default)
|
||||
foreach (var propertyData in Properties) {
|
||||
propertyData.Property.Dispose();
|
||||
}
|
||||
|
||||
Properties = default;
|
||||
Label = default;
|
||||
TransformPath = default;
|
||||
}
|
||||
|
||||
public class PropertyData {
|
||||
public SerializedProperty Property;
|
||||
public GUIContent Content;
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Asset Cleaner/Data/SearchResultGui.cs.meta
Normal file
3
Assets/Asset Cleaner/Data/SearchResultGui.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 14f26ff078c04c5685fdbb3ec3f92382
|
||||
timeCreated: 1576168925
|
||||
17
Assets/Asset Cleaner/Data/SelectionChanged.cs
Normal file
17
Assets/Asset Cleaner/Data/SelectionChanged.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using Leopotam.Ecs;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace Asset_Cleaner {
|
||||
class SelectionChanged : IEcsAutoReset {
|
||||
public Object Target;
|
||||
public Scene Scene;
|
||||
public FindModeEnum From;
|
||||
|
||||
public void Reset() {
|
||||
Target = default;
|
||||
Scene = default;
|
||||
From = default;
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Asset Cleaner/Data/SelectionChanged.cs.meta
Normal file
3
Assets/Asset Cleaner/Data/SelectionChanged.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1d0dc4d007bb4ebbbeea6626197d17b9
|
||||
timeCreated: 1575365553
|
||||
31
Assets/Asset Cleaner/Data/SelectionEntry.cs
Normal file
31
Assets/Asset Cleaner/Data/SelectionEntry.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using UnityEditor;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace Asset_Cleaner {
|
||||
[Serializable]
|
||||
class SelectionEntry {
|
||||
public bool IsGuids;
|
||||
public string[] Guids;
|
||||
|
||||
public Object[] SceneObjects;
|
||||
|
||||
public bool Valid() {
|
||||
if (IsGuids) {
|
||||
foreach (var guid in Guids) {
|
||||
var path = AssetDatabase.GUIDToAssetPath(guid);
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var sceneObject in SceneObjects)
|
||||
if (sceneObject)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Asset Cleaner/Data/SelectionEntry.cs.meta
Normal file
3
Assets/Asset Cleaner/Data/SelectionEntry.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2b4476be23474ea4b20262c0ee59983c
|
||||
timeCreated: 1596139978
|
||||
34
Assets/Asset Cleaner/IgnoreTypes.cs
Normal file
34
Assets/Asset Cleaner/IgnoreTypes.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using UnityEditor;
|
||||
using UnityEditorInternal;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Asset_Cleaner {
|
||||
static class IgnoreTypes {
|
||||
public static bool Check(string path, out Type type) {
|
||||
type = AssetDatabase.GetMainAssetTypeAtPath(path);
|
||||
if (type == null) return false;
|
||||
var conf = Globals<Config>.Value;
|
||||
|
||||
if (type.IsAssignableFromInverse(typeof(MonoScript))) return true;
|
||||
if (type.IsAssignableFromInverse(typeof(DefaultAsset))) return true;
|
||||
if (conf.IgnoreScriptable && type.IsAssignableFromInverse(typeof(ScriptableObject))) return true;
|
||||
|
||||
if (type.IsAssignableFromInverse(typeof(Shader))) return true;
|
||||
if (type.IsAssignableFromInverse(typeof(ComputeShader))) return true;
|
||||
if (type.IsAssignableFromInverse(typeof(ShaderVariantCollection))) return true;
|
||||
#if UNITY_2019_3_OR_NEWER
|
||||
if (type.IsAssignableFromInverse(typeof(UnityEngine.Experimental.Rendering.RayTracingShader))) return true; // todo: track of Experimental namespace
|
||||
#endif
|
||||
|
||||
if (type.IsAssignableFromInverse(typeof(TextAsset))) return true;
|
||||
if (type.IsAssignableFromInverse(typeof(AssemblyDefinitionAsset))) return true;
|
||||
|
||||
if (type.IsAssignableFromInverse(typeof(UnityEngine.U2D.SpriteAtlas))) return true;
|
||||
|
||||
if (conf.IgnoreMaterial && type.IsAssignableFromInverse(typeof(Material))) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Asset Cleaner/IgnoreTypes.cs.meta
Normal file
11
Assets/Asset Cleaner/IgnoreTypes.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 34f1d98d6e464e5ebee0df9de721af39
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
3
Assets/Asset Cleaner/Systems.meta
Normal file
3
Assets/Asset Cleaner/Systems.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4b15d89b453047218955641734d4ec9e
|
||||
timeCreated: 1596213484
|
||||
3
Assets/Asset Cleaner/Systems/External.meta
Normal file
3
Assets/Asset Cleaner/Systems/External.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cbedea52d931409a8b62256873637225
|
||||
timeCreated: 1596214557
|
||||
94
Assets/Asset Cleaner/Systems/External/AufWindow.cs
vendored
Normal file
94
Assets/Asset Cleaner/Systems/External/AufWindow.cs
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
using System.Diagnostics;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using Debug = UnityEngine.Debug;
|
||||
|
||||
namespace Asset_Cleaner {
|
||||
class AufWindow : EditorWindow {
|
||||
[SerializeField] PersistentUndoRedoState _persistentUndo;
|
||||
|
||||
[MenuItem("Window/- Asset Cleaner %L")]
|
||||
static void OpenActiveWindow() {
|
||||
GetWindow<AufWindow>();
|
||||
}
|
||||
|
||||
// restore window state after recompilation
|
||||
void OnEnable() {
|
||||
var wd = Globals<WindowData>.Value = new WindowData();
|
||||
wd.Window = this;
|
||||
|
||||
var firstTime = _persistentUndo == null;
|
||||
Globals<PersistentUndoRedoState>.Value = _persistentUndo ?? new PersistentUndoRedoState();
|
||||
Globals<BacklinkStore>.Value = new BacklinkStore();
|
||||
var config = Globals<Config>.Value = new Config();
|
||||
PersistenceUtils.Load(ref config);
|
||||
|
||||
if (firstTime || !config.RebuildCacheOnDemand)
|
||||
Globals<BacklinkStore>.Value.Init();
|
||||
|
||||
EditorApplication.update += Upd;
|
||||
EditorApplication.projectWindowItemOnGUI += ProjectViewGui.OnProjectWindowItemOnGui;
|
||||
|
||||
AufCtx.TryInitWorld();
|
||||
|
||||
// need to close window in case of Asset Cleaner uninstalled
|
||||
if (!CleanerStyleAsset.Style.TryFindSelf(out wd.Style))
|
||||
ForceClose();
|
||||
}
|
||||
|
||||
void OnGUI() {
|
||||
var store = Globals<BacklinkStore>.Value;
|
||||
if (!store.Initialized) {
|
||||
// prevent further window GUI rendering
|
||||
if (!GUILayout.Button("Initialize Cache")) return;
|
||||
var stopwatch = new Stopwatch();
|
||||
stopwatch.Start();
|
||||
store.Init();
|
||||
stopwatch.Stop();
|
||||
Globals<Config>.Value.InitializationTime = $"Initialized in {stopwatch.Elapsed.TotalSeconds:N} s";
|
||||
AufCtx.World.NewEntityWith(out RequestRepaintEvt _);
|
||||
}
|
||||
|
||||
AufCtx.OnGuiGroup.Run();
|
||||
}
|
||||
|
||||
static void Upd() {
|
||||
if (AufCtx.World == null) {
|
||||
AufCtx.DestroyWorld();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Globals<BacklinkStore>.Value.Initialized) return;
|
||||
AufCtx.UpdateGroup.Run();
|
||||
}
|
||||
|
||||
bool _closing;
|
||||
|
||||
void ForceClose() {
|
||||
if (_closing) return;
|
||||
_closing = true;
|
||||
Close();
|
||||
EditorWindow.DestroyImmediate(this);
|
||||
}
|
||||
|
||||
void OnDisable() {
|
||||
if (AufCtx.Destroyed) return;
|
||||
_persistentUndo = Globals<PersistentUndoRedoState>.Value;
|
||||
|
||||
AufCtx.UpdateGroup.Destroy();
|
||||
AufCtx.OnGuiGroup.Destroy();
|
||||
AufCtx.DestroyWorld();
|
||||
|
||||
Globals<Config>.Value = default;
|
||||
Globals<PersistentUndoRedoState>.Value = default;
|
||||
Globals<WindowData>.Value = default;
|
||||
|
||||
EditorApplication.update -= Upd;
|
||||
EditorApplication.projectWindowItemOnGUI -= ProjectViewGui.OnProjectWindowItemOnGui;
|
||||
|
||||
// need to close window in case of Asset Cleaner uninstalled
|
||||
if (!CleanerStyleAsset.Style.TryFindSelf(out _))
|
||||
ForceClose();
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Asset Cleaner/Systems/External/AufWindow.cs.meta
vendored
Normal file
11
Assets/Asset Cleaner/Systems/External/AufWindow.cs.meta
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 44ebd7bbb2bb41b4ba0187901e8d583f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
23
Assets/Asset Cleaner/Systems/External/ProcessAllAssets.cs
vendored
Normal file
23
Assets/Asset Cleaner/Systems/External/ProcessAllAssets.cs
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
using UnityEditor;
|
||||
|
||||
namespace Asset_Cleaner {
|
||||
class ProcessAllAssets : AssetPostprocessor {
|
||||
static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths) {
|
||||
if (!AufCtx.InitStarted) return;
|
||||
if (!Globals<BacklinkStore>.Value.Initialized) return;
|
||||
|
||||
var store = Globals<BacklinkStore>.Value;
|
||||
var length = movedAssets.Length;
|
||||
for (var i = 0; i < length; i++)
|
||||
store.Replace(movedFromAssetPaths[i], movedAssets[i]);
|
||||
|
||||
foreach (var path in deletedAssets)
|
||||
store.Remove(path);
|
||||
|
||||
foreach (var path in importedAssets)
|
||||
store.RebuildFor(path, true);
|
||||
|
||||
store.UpdateUnusedAssets();
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Asset Cleaner/Systems/External/ProcessAllAssets.cs.meta
vendored
Normal file
11
Assets/Asset Cleaner/Systems/External/ProcessAllAssets.cs.meta
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 17c1f845fa88d38448c4cf65e9745f30
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
47
Assets/Asset Cleaner/Systems/External/ProjectViewGui.cs
vendored
Normal file
47
Assets/Asset Cleaner/Systems/External/ProjectViewGui.cs
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Asset_Cleaner {
|
||||
static class ProjectViewGui {
|
||||
static CleanerStyleAsset.Style _style = Globals<WindowData>.Value.Style;
|
||||
|
||||
public static void OnProjectWindowItemOnGui(string guid, Rect rect) {
|
||||
if (!Globals<Config>.Value.MarkRed) return;
|
||||
|
||||
var store = Globals<BacklinkStore>.Value;
|
||||
if (!store.Initialized) return;
|
||||
|
||||
var path = AssetDatabase.GUIDToAssetPath(guid);
|
||||
ShowRowQuantity(rect, path, store);
|
||||
|
||||
long size = 0;
|
||||
var _ = store.UnusedFiles.TryGetValue(path, out size) || store.UnusedScenes.TryGetValue(path, out size);
|
||||
|
||||
if (SearchUtils.IsUnused(path)) {
|
||||
var buf = GUI.color;
|
||||
{
|
||||
GUI.color = _style.RedHighlight;
|
||||
GUI.Box(rect, string.Empty);
|
||||
}
|
||||
GUI.color = buf;
|
||||
GUI.Label(rect, CommonUtils.BytesToString(size), _style.ProjectViewCounterLabel);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void ShowRowQuantity(Rect rect, string path, BacklinkStore backlinkStore) {
|
||||
if (!AssetDatabase.IsValidFolder(path))
|
||||
return;
|
||||
|
||||
backlinkStore.FoldersWithQty.TryGetValue(path, out var folderWithQty);
|
||||
|
||||
var cntFiles = folderWithQty?.UnusedFilesQty ?? 0;
|
||||
var cntScenes = folderWithQty?.UnusedScenesQty ?? 0;
|
||||
long size = folderWithQty?.UnusedSize ?? 0;
|
||||
|
||||
if (cntFiles == 0 && cntScenes == 0) return;
|
||||
var countStr = cntFiles + cntScenes > 0 ? $"{cntFiles} | {cntScenes} ({CommonUtils.BytesToString(size)})" : "";
|
||||
GUI.Label(rect, countStr, _style.ProjectViewCounterLabel);
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Asset Cleaner/Systems/External/ProjectViewGui.cs.meta
vendored
Normal file
3
Assets/Asset Cleaner/Systems/External/ProjectViewGui.cs.meta
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a17718cc645348b0bc4ed6d7e514ab30
|
||||
timeCreated: 1589032770
|
||||
56
Assets/Asset Cleaner/Systems/SysProcessSearch.cs
Normal file
56
Assets/Asset Cleaner/Systems/SysProcessSearch.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using Leopotam.Ecs;
|
||||
using UnityEngine;
|
||||
using static Asset_Cleaner.AufCtx;
|
||||
|
||||
namespace Asset_Cleaner {
|
||||
class SysProcessSearch : IEcsRunSystem {
|
||||
EcsFilter<SelectionChanged> _from = null;
|
||||
|
||||
EcsFilter<Result, SearchResultGui, InSceneResult> SceneResultRows = null;
|
||||
EcsFilter<SceneResult, SceneDetails> ScenePaths = null;
|
||||
EcsFilter<SearchArg>.Exclude<InSceneResult> SearchArgMain = null;
|
||||
EcsFilter<Result, SearchResultGui, FileResultTag> FileResultRows = null;
|
||||
|
||||
public void Run() {
|
||||
if (_from.IsEmpty())
|
||||
return;
|
||||
|
||||
SearchArgMain.AllDestroy();
|
||||
ScenePaths.AllDestroy();
|
||||
FileResultRows.AllDestroy();
|
||||
SceneResultRows.AllDestroy();
|
||||
|
||||
var wd = Globals<WindowData>.Value;
|
||||
if (wd.Window)
|
||||
wd.Window.Repaint();
|
||||
|
||||
foreach (var i in _from.Out(out var get1, out _)) {
|
||||
var t1 = get1[i];
|
||||
if (!t1.Target) continue;
|
||||
wd.FindFrom = t1.From;
|
||||
|
||||
try {
|
||||
switch (t1.From) {
|
||||
case FindModeEnum.Scene:
|
||||
World.NewEntityWith(out SearchArg st);
|
||||
SearchUtils.Init(st, t1.Target, t1.Scene);
|
||||
SearchUtils.InScene(st, t1.Scene);
|
||||
break;
|
||||
case FindModeEnum.File:
|
||||
World.NewEntityWith(out SearchArg arg);
|
||||
SearchUtils.Init(arg, t1.Target);
|
||||
SearchUtils.FilesThatReference(arg);
|
||||
SearchUtils.ScenesThatContain(t1.Target);
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
Debug.LogException(e);
|
||||
}
|
||||
}
|
||||
|
||||
_from.AllUnset<SelectionChanged>();
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Asset Cleaner/Systems/SysProcessSearch.cs.meta
Normal file
3
Assets/Asset Cleaner/Systems/SysProcessSearch.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: afd561b0c1384bcc82976f2624afa895
|
||||
timeCreated: 1577287503
|
||||
30
Assets/Asset Cleaner/Systems/SysRepaintWindow.cs
Normal file
30
Assets/Asset Cleaner/Systems/SysRepaintWindow.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using Leopotam.Ecs;
|
||||
using UnityEditor;
|
||||
using UnityEditorInternal;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Asset_Cleaner {
|
||||
class RequestRepaintEvt { }
|
||||
|
||||
class SysRepaintWindow : IEcsRunSystem, IEcsInitSystem {
|
||||
EcsFilter<RequestRepaintEvt> Repaint = null;
|
||||
|
||||
public void Init() {
|
||||
var wd = Globals<WindowData>.Value;
|
||||
wd.SceneFoldout = new GUIContent(AssetPreview.GetMiniTypeThumbnail(typeof(SceneAsset)));
|
||||
wd.ExpandScenes = true;
|
||||
wd.ExpandFiles = true;
|
||||
wd.ScrollPos = Vector2.zero;
|
||||
wd.Window.titleContent = new GUIContent("Asset Cleaner v1.0");
|
||||
}
|
||||
|
||||
public void Run() {
|
||||
var wd = Globals<WindowData>.Value;
|
||||
|
||||
if (Repaint.IsEmpty()) return;
|
||||
wd.Window.Repaint();
|
||||
InternalEditorUtility.RepaintAllViews();
|
||||
Repaint.AllDestroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Asset Cleaner/Systems/SysRepaintWindow.cs.meta
Normal file
3
Assets/Asset Cleaner/Systems/SysRepaintWindow.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 673a2486762b4e3da784f1657b90bb93
|
||||
timeCreated: 1577288020
|
||||
44
Assets/Asset Cleaner/Systems/SysSceneCleanup.cs
Normal file
44
Assets/Asset Cleaner/Systems/SysSceneCleanup.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using Leopotam.Ecs;
|
||||
using UnityEditor;
|
||||
using UnityEditor.SceneManagement;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace Asset_Cleaner {
|
||||
class SceneToClose : IEcsAutoReset {
|
||||
public Scene Scene;
|
||||
public int SelectionId;
|
||||
public bool ForceClose;
|
||||
|
||||
public void Reset() {
|
||||
ForceClose = default;
|
||||
Scene = default;
|
||||
SelectionId = default;
|
||||
}
|
||||
}
|
||||
|
||||
class SysSceneCleanup : IEcsRunSystem, IEcsDestroySystem {
|
||||
EcsFilter<SceneToClose> ScenesToClose = default;
|
||||
|
||||
public void Run() {
|
||||
if (ScenesToClose.IsEmpty()) return;
|
||||
|
||||
var selectionId = Globals<PersistentUndoRedoState>.Value.Id;
|
||||
|
||||
foreach (var i in ScenesToClose.Out(out var g1, out var entities)) {
|
||||
var s = g1[i].Scene;
|
||||
if (g1[i].SelectionId == selectionId && !g1[i].ForceClose) continue;
|
||||
if (Selection.activeGameObject && Selection.activeGameObject.scene == s) continue;
|
||||
if (s.isLoaded) EditorSceneManager.CloseScene(s, removeScene: true);
|
||||
entities[i].Destroy();
|
||||
}
|
||||
}
|
||||
|
||||
// close scenes on window close
|
||||
public void Destroy() {
|
||||
foreach (var i in ScenesToClose.Out(out var g1, out _)) {
|
||||
var s = g1[i].Scene;
|
||||
if (s.isLoaded) EditorSceneManager.CloseScene(s, removeScene: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Asset Cleaner/Systems/SysSceneCleanup.cs.meta
Normal file
3
Assets/Asset Cleaner/Systems/SysSceneCleanup.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d42cae4aa3ff4500948ddd623a270120
|
||||
timeCreated: 1596142629
|
||||
190
Assets/Asset Cleaner/Systems/SysUndoRedoSelection.cs
Normal file
190
Assets/Asset Cleaner/Systems/SysUndoRedoSelection.cs
Normal file
@@ -0,0 +1,190 @@
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using Leopotam.Ecs;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using static Asset_Cleaner.AufCtx;
|
||||
|
||||
namespace Asset_Cleaner {
|
||||
class CleanupPrevArg { }
|
||||
|
||||
class UndoEvt { }
|
||||
|
||||
class RedoEvt { }
|
||||
|
||||
class SysUndoRedoSelection : IEcsRunSystem, IEcsInitSystem, IEcsDestroySystem {
|
||||
EcsFilter<UndoEvt> UndoEvt = default;
|
||||
EcsFilter<RedoEvt> RedoEvt = default;
|
||||
|
||||
bool _preventHistoryInsert;
|
||||
bool _preventSelectionSet;
|
||||
|
||||
public void Init() {
|
||||
Undo.undoRedoPerformed += OnUndoRedoPerformed;
|
||||
Selection.selectionChanged += OnSelectionChanged;
|
||||
Globals<UndoRedoState>.Value = new UndoRedoState();
|
||||
if (Globals<PersistentUndoRedoState>.Value.History.Count > 0)
|
||||
_preventHistoryInsert = true;
|
||||
OnSelectionChanged(); //init selection
|
||||
}
|
||||
|
||||
public void Destroy() {
|
||||
Undo.undoRedoPerformed -= OnUndoRedoPerformed;
|
||||
Selection.selectionChanged -= OnSelectionChanged;
|
||||
Globals<UndoRedoState>.Value = default;
|
||||
}
|
||||
|
||||
public void Run() {
|
||||
MouseInput();
|
||||
|
||||
if (UndoEvt.IsEmpty() && RedoEvt.IsEmpty()) return;
|
||||
Counters(undo: !UndoEvt.IsEmpty(), redo: !RedoEvt.IsEmpty(), false);
|
||||
_preventHistoryInsert = true;
|
||||
if (!_preventSelectionSet) {
|
||||
(var history, var id) = Globals<PersistentUndoRedoState>.Value;
|
||||
SelectionEntry entry = history[id];
|
||||
if (entry.Valid())
|
||||
Selection.objects = entry.IsGuids
|
||||
? entry.Guids.Select(AssetDatabase.GUIDToAssetPath).Select(AssetDatabase.LoadAssetAtPath<Object>).Where(obj => obj).ToArray()
|
||||
: entry.SceneObjects;
|
||||
}
|
||||
|
||||
_preventSelectionSet = false;
|
||||
|
||||
UndoEvt.AllDestroy();
|
||||
RedoEvt.AllDestroy();
|
||||
}
|
||||
|
||||
|
||||
static void Counters(bool undo, bool redo, bool insertToHistory) {
|
||||
var state = Globals<PersistentUndoRedoState>.Value;
|
||||
World.NewEntityWith(out RequestRepaintEvt _);
|
||||
|
||||
const int MinId = 0;
|
||||
if (insertToHistory) {
|
||||
var entry = new SelectionEntry();
|
||||
|
||||
var count = state.History.Count - 1 - state.Id;
|
||||
if (count > 0)
|
||||
state.History.RemoveRange(state.Id + 1, count);
|
||||
|
||||
state.History.Add(entry);
|
||||
state.Id = MaxId();
|
||||
|
||||
if (Selection.assetGUIDs.Length > 0) {
|
||||
entry.IsGuids = true;
|
||||
entry.Guids = Selection.assetGUIDs;
|
||||
}
|
||||
else {
|
||||
entry.SceneObjects = Selection.objects;
|
||||
}
|
||||
}
|
||||
|
||||
if (undo) {
|
||||
// loop to skip invalid
|
||||
while (true) {
|
||||
state.Id -= 1;
|
||||
if (state.Id < MinId) break;
|
||||
if (state.History[state.Id].Valid()) break;
|
||||
}
|
||||
}
|
||||
|
||||
if (redo) {
|
||||
// loop to skip invalid
|
||||
while (true) {
|
||||
state.Id += 1;
|
||||
if (state.Id > MaxId()) break;
|
||||
if (state.History[state.Id].Valid()) break;
|
||||
}
|
||||
}
|
||||
|
||||
state.Id = Mathf.Clamp(state.Id, MinId, MaxId());
|
||||
|
||||
var undoRedoState = Globals<UndoRedoState>.Value;
|
||||
undoRedoState.UndoEnabled = state.Id != MinId;
|
||||
undoRedoState.RedoEnabled = state.Id != MaxId();
|
||||
|
||||
int MaxId() => Mathf.Max(0, state.History.Count - 1);
|
||||
}
|
||||
|
||||
void OnSelectionChanged() {
|
||||
World.NewEntityWith(out RequestRepaintEvt _);
|
||||
if (Globals<Config>.Value.Locked) return;
|
||||
Counters(undo: false, redo: false, insertToHistory: !_preventHistoryInsert);
|
||||
_preventHistoryInsert = false;
|
||||
|
||||
World.NewEntityWith(out SelectionChanged comp);
|
||||
World.NewEntityWith(out CleanupPrevArg _);
|
||||
var go = Selection.activeGameObject;
|
||||
if (go && go.scene.IsValid()) {
|
||||
comp.From = FindModeEnum.Scene;
|
||||
comp.Target = go;
|
||||
comp.Scene = go.scene;
|
||||
}
|
||||
else {
|
||||
var guids = Selection.assetGUIDs;
|
||||
// comp.Guids = Selection.assetGUIDs;
|
||||
bool any = guids != null && guids.Length > 0;
|
||||
if (any) {
|
||||
comp.From = FindModeEnum.File;
|
||||
var path = AssetDatabase.GUIDToAssetPath(guids[0]);
|
||||
comp.Target = AssetDatabase.LoadAssetAtPath<Object>(path);
|
||||
}
|
||||
else {
|
||||
comp.From = FindModeEnum.None;
|
||||
comp.Target = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// prevents selection history flooding
|
||||
void OnUndoRedoPerformed() {
|
||||
// below is a hackish way to catch Undo/Redo from editor
|
||||
if (!Undo.GetCurrentGroupName().Equals("Selection Change")) return;
|
||||
var evt = Event.current;
|
||||
if (evt == null) return;
|
||||
if (evt.rawType != EventType.KeyDown) return;
|
||||
|
||||
switch (evt.keyCode) {
|
||||
case KeyCode.Z:
|
||||
World.NewEntityWith(out UndoEvt _);
|
||||
_preventSelectionSet = true; // prevent manual Selection set
|
||||
break;
|
||||
case KeyCode.Y:
|
||||
World.NewEntityWith(out RedoEvt _);
|
||||
_preventSelectionSet = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void MouseInput() {
|
||||
if (_nextClick > EditorApplication.timeSinceStartup) return;
|
||||
|
||||
var any = false;
|
||||
if (Pressed(0x5)) {
|
||||
World.NewEntityWith(out UndoEvt _);
|
||||
any = true;
|
||||
}
|
||||
|
||||
if (Pressed(0x6)) {
|
||||
World.NewEntityWith(out RedoEvt _);
|
||||
any = true;
|
||||
}
|
||||
|
||||
if (any)
|
||||
_nextClick = EditorApplication.timeSinceStartup + 0.25;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR_WIN
|
||||
[DllImport("USER32.dll")]
|
||||
static extern short GetKeyState(int keycode);
|
||||
#else
|
||||
static short GetKeyState(int keycode) => 0;
|
||||
#endif
|
||||
|
||||
double _nextClick;
|
||||
|
||||
// 5 back, 6 fw
|
||||
static bool Pressed(int keyCode) => (GetKeyState(keyCode) & 0x100) != 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6e6a2f49ce174cabbc560ea8e3e9975c
|
||||
timeCreated: 1596008857
|
||||
970
Assets/Asset Cleaner/Systems/SysWindowGui.cs
Normal file
970
Assets/Asset Cleaner/Systems/SysWindowGui.cs
Normal file
@@ -0,0 +1,970 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Leopotam.Ecs;
|
||||
using UnityEditor;
|
||||
using UnityEditor.SceneManagement;
|
||||
using UnityEditorInternal;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace Asset_Cleaner {
|
||||
class FileResultTag { }
|
||||
|
||||
enum TargetTypeEnum {
|
||||
File = 0,
|
||||
Directory = 1,
|
||||
Scene = 2,
|
||||
ObjectInScene = 3,
|
||||
ObjectInStage = 4
|
||||
}
|
||||
|
||||
class SysWindowGui : IEcsRunSystem, IEcsInitSystem {
|
||||
EcsFilter<Result, SearchResultGui, InSceneResult> SceneResultRows = null;
|
||||
EcsFilter<SceneResult, SceneDetails> ScenePaths = null;
|
||||
EcsFilter<SearchArg>.Exclude<InSceneResult> SearchArgMain = null;
|
||||
EcsFilter<Result, SearchResultGui, FileResultTag> FileResultRows = null;
|
||||
|
||||
public void Init() {
|
||||
BacklinkStoreDirty(true);
|
||||
VisualSettingDirty(true);
|
||||
}
|
||||
|
||||
public void Run() {
|
||||
var windowData = Globals<WindowData>.Value;
|
||||
|
||||
_toolbarSelection = GUILayout.Toolbar(_toolbarSelection, _toolbarStrings, GUILayout.ExpandWidth(false));
|
||||
var conf = Globals<Config>.Value;
|
||||
switch (_toolbarSelection) {
|
||||
case 0: {
|
||||
ShowTabMain(conf, windowData);
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
ShowTabSettings(conf);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string[] _toolbarStrings = {"Main", "Settings"};
|
||||
|
||||
const int _progressBarShowFromLevel = 10;
|
||||
int _toolbarSelection = 0;
|
||||
|
||||
int _settingIgnoredPathsHash1;
|
||||
|
||||
bool BacklinkStoreDirty(bool set) {
|
||||
var res = Hash() != _settingIgnoredPathsHash1;
|
||||
if (set) _settingIgnoredPathsHash1 = Hash();
|
||||
return res;
|
||||
|
||||
int Hash() {
|
||||
var conf = Globals<Config>.Value;
|
||||
return DirtyUtils.HashCode(conf.IgnorePathContainsCombined,
|
||||
conf.IgnoreMaterial,
|
||||
conf.IgnoreScriptable);
|
||||
}
|
||||
}
|
||||
|
||||
int _settingCodeHash1;
|
||||
|
||||
bool VisualSettingDirty(bool set) {
|
||||
var res = Hash() != _settingCodeHash1;
|
||||
if (set) _settingCodeHash1 = Hash();
|
||||
return res;
|
||||
|
||||
int Hash() {
|
||||
var conf = Globals<Config>.Value;
|
||||
return DirtyUtils.HashCode(
|
||||
conf.MarkRed,
|
||||
conf.ShowInfoBox,
|
||||
conf.RebuildCacheOnDemand);
|
||||
}
|
||||
}
|
||||
|
||||
void ShowTabSettings(Config conf) {
|
||||
using (new EditorGUILayout.VerticalScope()) {
|
||||
EditorGUILayout.Space();
|
||||
|
||||
var enabled = GUI.enabled;
|
||||
GUI.enabled = true;
|
||||
|
||||
|
||||
using (new EditorGUILayout.VerticalScope()) {
|
||||
conf.MarkRed = GUILayout.Toggle(conf.MarkRed, "Display counters and red overlay in Project View");
|
||||
conf.ShowInfoBox = GUILayout.Toggle(conf.ShowInfoBox, "Help suggestions");
|
||||
conf.RebuildCacheOnDemand = GUILayout.Toggle(conf.RebuildCacheOnDemand, "Rebuild cache on demand (when scripts are updated often)");
|
||||
EditorGUILayout.Space();
|
||||
conf.IgnoreMaterial = GUILayout.Toggle(conf.IgnoreMaterial, "Ignore Materials");
|
||||
conf.IgnoreScriptable = GUILayout.Toggle(conf.IgnoreScriptable, "Ignore ScriptableObjects");
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
GUI.enabled = enabled;
|
||||
|
||||
EditorGUILayout.LabelField("Ignore Path(s) contains:");
|
||||
|
||||
conf.IgnorePathContainsCombined = GUILayout.TextArea(conf.IgnorePathContainsCombined);
|
||||
|
||||
using (new EditorGUILayout.HorizontalScope(GUILayout.ExpandWidth(false))) {
|
||||
EditorGUILayout.Space();
|
||||
|
||||
var previous = GUI.enabled;
|
||||
|
||||
GUI.enabled = BacklinkStoreDirty(false) || VisualSettingDirty(false);
|
||||
if (GUILayout.Button("Apply")) {
|
||||
conf.IgnorePathContains = conf.IgnorePathContainsCombined.Split(';')
|
||||
.Where(s => !string.IsNullOrWhiteSpace(s)).ToArray();
|
||||
Apply();
|
||||
}
|
||||
|
||||
GUI.enabled = previous;
|
||||
|
||||
var selectedGuids = Selection.assetGUIDs;
|
||||
var assetPaths = new List<string>();
|
||||
if (selectedGuids.Length > 0) {
|
||||
foreach (var guid in selectedGuids) {
|
||||
var realAssetPath = AssetDatabase.GUIDToAssetPath(guid);
|
||||
var obj = AssetDatabase.LoadAssetAtPath<Object>(realAssetPath);
|
||||
var assetPath = realAssetPath.Replace("Assets/", string.Empty);
|
||||
if (obj is DefaultAsset &&
|
||||
!conf.IgnorePathContains.Any(p => (StringComparer.Ordinal.Equals(p, assetPath)))) {
|
||||
assetPaths.Add(assetPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GUI.enabled = (assetPaths.Count > 0);
|
||||
var foldersList = string.Join(", ", assetPaths);
|
||||
if (GUILayout.Button("Add Selected Path")) {
|
||||
var choice = EditorUtility.DisplayDialog(
|
||||
title: "Asset Cleaner",
|
||||
message:
|
||||
$"Do you really want to add these folder(s) to ignored list: \"{foldersList}\"?",
|
||||
ok: "Ignore",
|
||||
cancel: "Cancel");
|
||||
if (choice) {
|
||||
conf.IgnorePathContainsCombined += $"{foldersList};";
|
||||
conf.IgnorePathContains = conf.IgnorePathContainsCombined.Split(';')
|
||||
.Where(s => !string.IsNullOrWhiteSpace(s)).ToArray();
|
||||
Apply();
|
||||
}
|
||||
}
|
||||
|
||||
GUI.enabled = true;
|
||||
if (GUILayout.Button("Reset")) {
|
||||
var choice = EditorUtility.DisplayDialog(
|
||||
title: "Asset Cleaner",
|
||||
message:
|
||||
$"Do you really want to reset to the factory settings?",
|
||||
ok: "Reset",
|
||||
cancel: "Cancel");
|
||||
if (choice) {
|
||||
var serializable = AufSerializableData.Default();
|
||||
AufSerializableData.OnDeserialize(in serializable, ref conf);
|
||||
Apply();
|
||||
}
|
||||
}
|
||||
|
||||
GUI.enabled = previous;
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField(conf.InitializationTime);
|
||||
|
||||
var buf = GUI.enabled;
|
||||
GUI.enabled = Selection.objects.Length > 0;
|
||||
if (GUILayout.Button("Reserialize selected assets", GUILayout.ExpandWidth(false))) {
|
||||
var paths = Selection.objects.Select(AssetDatabase.GetAssetPath);
|
||||
AssetDatabase.ForceReserializeAssets(paths);
|
||||
EditorApplication.ExecuteMenuItem("File/Save Project");
|
||||
AssetDatabase.Refresh();
|
||||
}
|
||||
|
||||
GUI.enabled = buf;
|
||||
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
|
||||
void Apply() {
|
||||
var rebuild = BacklinkStoreDirty(true);
|
||||
VisualSettingDirty(true);
|
||||
PersistenceUtils.Save(in conf);
|
||||
AufCtx.World.NewEntityWith(out RequestRepaintEvt _);
|
||||
if (rebuild)
|
||||
Globals<BacklinkStore>.Value.UpdateUnusedAssets();
|
||||
InternalEditorUtility.RepaintAllViews();
|
||||
}
|
||||
}
|
||||
|
||||
void ShowTabMain(Config conf, WindowData windowData) {
|
||||
var store = Globals<BacklinkStore>.Value;
|
||||
EditorGUIUtility.labelWidth = windowData.Window.position.width * .7f;
|
||||
|
||||
int Hash() => DirtyUtils.HashCode(conf.Locked);
|
||||
var active = SearchArgMain.Get1[0];
|
||||
if (conf.Locked && (windowData.FindFrom == FindModeEnum.File &&
|
||||
(active == null || active.Main == null || !AssetDatabase.Contains(active.Main)))) {
|
||||
conf.Locked = false;
|
||||
AufCtx.World.NewEntityWith(out RequestRepaintEvt _);
|
||||
}
|
||||
|
||||
var style = windowData.Style;
|
||||
var hash = Hash();
|
||||
if (hash != Hash()) {
|
||||
PersistenceUtils.Save(in conf);
|
||||
AufCtx.World.NewEntityWith(out RequestRepaintEvt _);
|
||||
}
|
||||
|
||||
// if (Globals<WindowData>.Get() == null) return;
|
||||
EditorGUILayout.Space();
|
||||
|
||||
SearchArg arg = default;
|
||||
foreach (var i in SearchArgMain) {
|
||||
arg = SearchArgMain.Get1[i];
|
||||
if (arg != null && arg.Main != null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (arg == default)
|
||||
return;
|
||||
|
||||
var targetTypeEnum = GetTargetType(windowData, arg?.Main);
|
||||
BacklinkStore.UnusedQty unusedQty = new BacklinkStore.UnusedQty(0, 0, 0);
|
||||
|
||||
using (new EditorGUILayout.HorizontalScope()) {
|
||||
var enabledBuf = GUI.enabled;
|
||||
var selectedGuids = Selection.assetGUIDs;
|
||||
|
||||
var undoRedoState = Globals<UndoRedoState>.Value;
|
||||
|
||||
GUI.enabled = selectedGuids != null && !conf.Locked && undoRedoState.UndoEnabled;
|
||||
if (GUILayout.Button(style.ArrowL, style.ArrowBtn)) {
|
||||
AufCtx.World.NewEntityWith(out UndoEvt _);
|
||||
}
|
||||
|
||||
GUI.enabled = selectedGuids != null && !conf.Locked && undoRedoState.RedoEnabled;
|
||||
if (GUILayout.Button(style.ArrowR, style.ArrowBtn)) {
|
||||
AufCtx.World.NewEntityWith(out RedoEvt _);
|
||||
}
|
||||
|
||||
GUI.enabled = enabledBuf;
|
||||
|
||||
if (conf.Locked) {
|
||||
if (GUILayout.Button(style.Lock, style.LockBtn)) {
|
||||
AufCtx.World.NewEntityWith(out SelectionChanged selectionChanged);
|
||||
conf.Locked = false;
|
||||
if (Selection.activeObject != arg.Target) {
|
||||
selectionChanged.From = FindModeEnum.Scene;
|
||||
selectionChanged.Scene = SceneManager.GetActiveScene();
|
||||
selectionChanged.Target = Selection.activeObject;
|
||||
}
|
||||
else if (Selection.assetGUIDs is string[] guids) {
|
||||
// todo show info box multiple selection is unsupported
|
||||
if (guids.Length > 0) {
|
||||
var path = AssetDatabase.GUIDToAssetPath(guids[0]);
|
||||
selectionChanged.Target = AssetDatabase.LoadAssetAtPath<Object>(path);
|
||||
switch (Selection.selectionChanged.Target) {
|
||||
case DefaultAsset _:
|
||||
selectionChanged.From = FindModeEnum.File;
|
||||
break;
|
||||
case GameObject go when go.scene.isLoaded:
|
||||
selectionChanged.From = FindModeEnum.Scene;
|
||||
selectionChanged.Scene = SceneManager.GetActiveScene();
|
||||
break;
|
||||
default:
|
||||
selectionChanged.From = FindModeEnum.File;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (Selection.activeObject is GameObject go && go.scene.isLoaded) {
|
||||
selectionChanged.From = FindModeEnum.Scene;
|
||||
selectionChanged.Target = Selection.activeObject;
|
||||
selectionChanged.Scene = SceneManager.GetActiveScene();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
var enabled = GUI.enabled;
|
||||
GUI.enabled = selectedGuids != null && selectedGuids.Length == 1;
|
||||
if (GUILayout.Button(style.Unlock, style.UnlockBtn)) {
|
||||
conf.Locked = true;
|
||||
}
|
||||
|
||||
GUI.enabled = enabled;
|
||||
}
|
||||
|
||||
unusedQty = ShowObjectName(store, windowData, targetTypeEnum, arg, selectedGuids);
|
||||
}
|
||||
|
||||
bool isMultiSelect = Selection.assetGUIDs != null && Selection.assetGUIDs.Length > 1;
|
||||
|
||||
if (conf.ShowInfoBox) {
|
||||
if (isMultiSelect && (unusedQty.UnusedFilesQty + unusedQty.UnusedScenesQty > 0)) {
|
||||
var msgUnusedFiles = (unusedQty.UnusedFilesQty > 0)
|
||||
? $"unused files ({unusedQty.UnusedFilesQty}),"
|
||||
: "";
|
||||
var msgUnusedScenes = (unusedQty.UnusedScenesQty > 0)
|
||||
? $"unused scenes ({unusedQty.UnusedScenesQty}),"
|
||||
: "";
|
||||
var msgMultiSelect = $"This multi-selection contains: " +
|
||||
msgUnusedFiles + msgUnusedScenes +
|
||||
$"\nYou could delete them pressing corresponding button to the right.";
|
||||
EditorGUILayout.HelpBox(msgMultiSelect, MessageType.Info);
|
||||
}
|
||||
else if (TryGetHelpInfo(arg, out var msg, out var msgType)) {
|
||||
EditorGUILayout.HelpBox(msg, msgType);
|
||||
}
|
||||
}
|
||||
|
||||
if (targetTypeEnum != TargetTypeEnum.Directory && !isMultiSelect) {
|
||||
var windowData2 = Globals<WindowData>.Value;
|
||||
|
||||
EditorGUILayout.BeginVertical();
|
||||
{
|
||||
windowData2.ScrollPos = EditorGUILayout.BeginScrollView(windowData2.ScrollPos);
|
||||
{
|
||||
RenderRows(windowData2); //TODO?
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
EditorGUILayout.EndScrollView();
|
||||
}
|
||||
EditorGUILayout.EndVertical();
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static bool TryGetHelpInfo(SearchArg arg, out string msg, out MessageType msgType) {
|
||||
msgType = MessageType.Info;
|
||||
if (arg == null) {
|
||||
msg = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
var path = arg.FilePath;
|
||||
if (string.IsNullOrEmpty(path)) {
|
||||
msg = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (SearchUtils.IgnoredPaths(path, out var subPath)) {
|
||||
msg = $"Paths containing '{subPath}' are ignored. You could add or remove it in Settings tab";
|
||||
return true;
|
||||
}
|
||||
|
||||
if (SearchUtils.IgnoredNonAssets(path) && !path.Eq("Assets")) {
|
||||
msg = $"Asset is outside of Assets folder";
|
||||
return true;
|
||||
}
|
||||
|
||||
if (IgnoreTypes.Check(path, out var type)) {
|
||||
if (AssetDatabase.IsValidFolder(path)) {
|
||||
var scenes = arg.UnusedScenesFiltered?.Count;
|
||||
var files = arg.UnusedAssetsFiltered?.Count;
|
||||
if (scenes == 0 && files == 0) {
|
||||
msg = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
var b = new StringBuilder();
|
||||
b.Append("This directory contains: ");
|
||||
var any = false;
|
||||
|
||||
if (files > 0) {
|
||||
any = true;
|
||||
|
||||
b.Append($"unused files ({files})");
|
||||
}
|
||||
|
||||
if (scenes > 0) {
|
||||
if (any)
|
||||
b.Append(", ");
|
||||
b.Append($"unused scenes ({scenes})");
|
||||
}
|
||||
|
||||
b.Append(
|
||||
".\nYou could delete them pressing corresponding button to the right.\nIf you don't want it to be inspected, please add it to Ignore list in the Settings tab");
|
||||
msg = b.ToString();
|
||||
return true;
|
||||
}
|
||||
|
||||
msg = $"Assets of type '{type.Name}' are ignored. Please contact support if you need to change it";
|
||||
return true;
|
||||
}
|
||||
|
||||
// if (Filters.ScenePaths.GetEntitiesCount() == 0 && Filters.FileResultRows.GetEntitiesCount() == 0) {
|
||||
if (SearchUtils.IsUnused(path)) {
|
||||
type = AssetDatabase.GetMainAssetTypeAtPath(path);
|
||||
var name = type.IsAssignableFromInverse(typeof(SceneAsset)) ? "scene" : "file";
|
||||
msg =
|
||||
$"This {name} has no explicit serialized usages and potentially could be removed. If you don't want it to be inspected, please add the containing folder to ignore list";
|
||||
return true;
|
||||
}
|
||||
|
||||
msgType = default;
|
||||
msg = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
static TargetTypeEnum GetTargetType(WindowData windowData1, Object obj) {
|
||||
if (obj == null) return TargetTypeEnum.File;
|
||||
var targetTypeEnum = TargetTypeEnum.Directory;
|
||||
var path = AssetDatabase.GetAssetPath(obj);
|
||||
switch (windowData1.FindFrom) {
|
||||
case FindModeEnum.File when obj is DefaultAsset:
|
||||
targetTypeEnum = TargetTypeEnum.Directory;
|
||||
break;
|
||||
case FindModeEnum.File when path.LastIndexOf(".unity", StringComparison.Ordinal) != -1:
|
||||
targetTypeEnum = TargetTypeEnum.Scene;
|
||||
break;
|
||||
case FindModeEnum.File:
|
||||
targetTypeEnum = TargetTypeEnum.File;
|
||||
break;
|
||||
case FindModeEnum.Scene:
|
||||
targetTypeEnum = TargetTypeEnum.ObjectInScene;
|
||||
break;
|
||||
case FindModeEnum.Stage:
|
||||
targetTypeEnum = TargetTypeEnum.ObjectInStage;
|
||||
break;
|
||||
}
|
||||
|
||||
return targetTypeEnum;
|
||||
}
|
||||
|
||||
GUIContent _contentBuf = new GUIContent();
|
||||
GUIContent _buf2 = new GUIContent();
|
||||
|
||||
BacklinkStore.UnusedQty ShowObjectName(BacklinkStore store, WindowData windowData, TargetTypeEnum targetTypeEnum, SearchArg arg, string[] selectedGuids) {
|
||||
float TextWidth() {
|
||||
_buf2.text = _contentBuf.text;
|
||||
return 20f + GUI.skin.button.CalcSize(_buf2).x;
|
||||
}
|
||||
|
||||
if (arg == null || arg.Main == null) return new BacklinkStore.UnusedQty();
|
||||
|
||||
bool isMultiSelect = selectedGuids != null && selectedGuids.Length > 1;
|
||||
|
||||
if (_contentBuf == null) {
|
||||
_contentBuf = new GUIContent {tooltip = $"Click to ping"};
|
||||
}
|
||||
|
||||
_contentBuf.image = isMultiSelect
|
||||
? windowData.Style.MultiSelect.image
|
||||
: AssetPreview.GetMiniThumbnail(arg.Target);
|
||||
_contentBuf.text = string.Empty;
|
||||
_contentBuf.tooltip = string.Empty;
|
||||
|
||||
if (!isMultiSelect) {
|
||||
switch (targetTypeEnum) {
|
||||
case TargetTypeEnum.Directory:
|
||||
if (!SearchArgMain.IsEmpty()) {
|
||||
_contentBuf.text = $"{arg.Main.name} (Folder)";
|
||||
if (GUILayout.Button(_contentBuf, windowData.Style.CurrentBtn,
|
||||
GUILayout.MinWidth(TextWidth()))) {
|
||||
EditorGUIUtility.PingObject(arg.Main);
|
||||
}
|
||||
|
||||
if (AskDeleteUnusedFiles(arg, arg.UnusedAssetsFiltered, windowData))
|
||||
return new BacklinkStore.UnusedQty();
|
||||
|
||||
if (AskDeleteUnusedScenes(arg, arg.UnusedScenesFiltered, windowData))
|
||||
return new BacklinkStore.UnusedQty();
|
||||
}
|
||||
|
||||
break;
|
||||
case TargetTypeEnum.File:
|
||||
_contentBuf.text = $"{arg.Main.name} (File Asset)";
|
||||
if (GUILayout.Button(_contentBuf, windowData.Style.CurrentBtn,
|
||||
GUILayout.MinWidth(TextWidth()))) {
|
||||
EditorGUIUtility.PingObject(arg.Main);
|
||||
}
|
||||
|
||||
bool hasUnusedFile = SearchUtils.IsUnused(arg.FilePath);
|
||||
var previous = GUI.enabled;
|
||||
GUI.enabled = hasUnusedFile;
|
||||
|
||||
if (GUILayout.Button(windowData.Style.RemoveFile,
|
||||
windowData.Style.RemoveUnusedBtn)) {
|
||||
var choice = EditorUtility.DisplayDialog(
|
||||
title: "Asset Cleaner",
|
||||
message:
|
||||
$"Do you really want to remove file: \"{arg.Main.name}\"?",
|
||||
ok: "Remove",
|
||||
cancel: "Cancel");
|
||||
if (choice) {
|
||||
EditorApplication.ExecuteMenuItem("File/Save Project");
|
||||
DeleteWithMeta(arg.FilePath);
|
||||
AssetDatabase.Refresh();
|
||||
SearchUtils.Upd(arg);
|
||||
}
|
||||
}
|
||||
|
||||
GUI.enabled = previous;
|
||||
|
||||
break;
|
||||
case TargetTypeEnum.Scene:
|
||||
_contentBuf.text = $"{arg.Main.name} (Scene)";
|
||||
if (GUILayout.Button(_contentBuf, windowData.Style.CurrentBtn,
|
||||
GUILayout.MinWidth(TextWidth()))) {
|
||||
EditorGUIUtility.PingObject(arg.Main);
|
||||
}
|
||||
|
||||
bool hasUnusedScene = SearchUtils.IsUnused(arg.FilePath);
|
||||
previous = GUI.enabled;
|
||||
GUI.enabled = hasUnusedScene;
|
||||
|
||||
if (GUILayout.Button(windowData.Style.RemoveScene,
|
||||
windowData.Style.RemoveUnusedBtn)) {
|
||||
var choice = EditorUtility.DisplayDialog(
|
||||
title: "Asset Cleaner",
|
||||
message:
|
||||
$"Do you really want to remove scene: {arg.Main.name}?",
|
||||
ok: "Remove",
|
||||
cancel: "Cancel");
|
||||
if (choice) {
|
||||
EditorApplication.ExecuteMenuItem("File/Save Project");
|
||||
DeleteWithMeta(arg.FilePath);
|
||||
AssetDatabase.Refresh();
|
||||
SearchUtils.Upd(arg);
|
||||
}
|
||||
}
|
||||
|
||||
GUI.enabled = previous;
|
||||
break;
|
||||
case TargetTypeEnum.ObjectInScene:
|
||||
_contentBuf.text = $"{arg.Main.name} (Object in Scene)";
|
||||
|
||||
if (GUILayout.Button(_contentBuf, windowData.Style.CurrentBtn,
|
||||
GUILayout.MinWidth(TextWidth()))) {
|
||||
EditorGUIUtility.PingObject(arg.Main);
|
||||
}
|
||||
|
||||
break;
|
||||
case TargetTypeEnum.ObjectInStage:
|
||||
_contentBuf.image = AssetPreview.GetMiniThumbnail(arg.Target);
|
||||
_contentBuf.text = $"{arg.Main.name} (Object in Staging)";
|
||||
|
||||
if (GUILayout.Button(_contentBuf,
|
||||
windowData.Style.RemoveUnusedBtn)) {
|
||||
EditorGUIUtility.PingObject(arg.Main);
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
if (GUILayout.Button($"{arg.Main.name} (Unknown Object Type)",
|
||||
windowData.Style.RemoveUnusedBtn)) {
|
||||
EditorGUIUtility.PingObject(arg.Main);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
var unusedAssets = new List<string>();
|
||||
var unusedScenes = new List<string>();
|
||||
|
||||
foreach (var guid in selectedGuids) {
|
||||
var path = AssetDatabase.GUIDToAssetPath(guid);
|
||||
|
||||
if (store.UnusedFiles.TryGetValue(path, out _))
|
||||
unusedAssets.Add(path);
|
||||
|
||||
else if (store.UnusedScenes.TryGetValue(path, out _))
|
||||
unusedScenes.Add(path);
|
||||
|
||||
if (store.FoldersWithQty.TryGetValue(path, out _)) {
|
||||
SearchArg searchArg = new SearchArg() {
|
||||
FilePath = path,
|
||||
Target = AssetDatabase.LoadAssetAtPath<DefaultAsset>(path),
|
||||
Main = AssetDatabase.LoadAssetAtPath<DefaultAsset>(path)
|
||||
};
|
||||
SearchUtils.Upd(searchArg);
|
||||
|
||||
foreach (var unusedAssetPath in searchArg.UnusedAssetsFiltered)
|
||||
if (store.UnusedFiles.TryGetValue(unusedAssetPath, out _))
|
||||
unusedAssets.Add(unusedAssetPath);
|
||||
|
||||
foreach (var unusedScenePath in searchArg.UnusedScenesFiltered)
|
||||
if (store.UnusedScenes.TryGetValue(unusedScenePath, out _))
|
||||
unusedScenes.Add(unusedScenePath);
|
||||
}
|
||||
}
|
||||
|
||||
unusedAssets = unusedAssets.Distinct().ToList();
|
||||
unusedScenes = unusedScenes.Distinct().ToList();
|
||||
|
||||
var assetSize = unusedAssets.Sum(CommonUtils.Size);
|
||||
var sceneSize = unusedScenes.Sum(CommonUtils.Size);
|
||||
|
||||
_contentBuf.text =
|
||||
$"Assets: {unusedAssets.Count} ({CommonUtils.BytesToString(assetSize)}), Scenes: {unusedScenes.Count} ({CommonUtils.BytesToString(sceneSize)})";
|
||||
;
|
||||
|
||||
GUILayout.Button(_contentBuf, windowData.Style.CurrentBtn, GUILayout.MinWidth(TextWidth()));
|
||||
|
||||
if (AskDeleteUnusedFiles(arg, unusedAssets, windowData))
|
||||
return new BacklinkStore.UnusedQty();
|
||||
|
||||
if (AskDeleteUnusedScenes(arg, unusedScenes, windowData))
|
||||
return new BacklinkStore.UnusedQty();
|
||||
|
||||
return new BacklinkStore.UnusedQty(unusedAssets.Count, unusedScenes.Count, assetSize + sceneSize);
|
||||
}
|
||||
|
||||
return new BacklinkStore.UnusedQty();
|
||||
}
|
||||
|
||||
static bool AskDeleteUnusedFiles(SearchArg arg, List<string> unusedAssets, WindowData windowData) {
|
||||
if (arg == null || unusedAssets == null) return false;
|
||||
var hasUnusedAssets = unusedAssets.Count > 0;
|
||||
var previous = GUI.enabled;
|
||||
GUI.enabled = hasUnusedAssets;
|
||||
|
||||
var guiContentRemoveAssets = windowData.Style.RemoveFile;
|
||||
|
||||
if (GUILayout.Button(guiContentRemoveAssets,
|
||||
windowData.Style.RemoveUnusedBtn)) {
|
||||
var choice = EditorUtility.DisplayDialog(
|
||||
title: "Asset Cleaner",
|
||||
message:
|
||||
$"Do you really want to remove {unusedAssets.Count} asset(s)?",
|
||||
ok: "Remove",
|
||||
cancel: "Cancel");
|
||||
if (choice) {
|
||||
EditorApplication.ExecuteMenuItem("File/Save Project");
|
||||
|
||||
var i = 0f;
|
||||
var total = (float) unusedAssets.Count;
|
||||
|
||||
foreach (var f in unusedAssets) {
|
||||
var path = Application.dataPath.Replace("Assets", f);
|
||||
DeleteWithMeta(path);
|
||||
|
||||
var percent = i * 100 / total;
|
||||
if (total >= _progressBarShowFromLevel) {
|
||||
if (Math.Abs(percent % 5f) < 0.01f) {
|
||||
if (EditorUtility.DisplayCancelableProgressBar(
|
||||
"Please wait...",
|
||||
"Deleting assets...", percent))
|
||||
throw new Exception("Deleting aborted");
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
if (total >= _progressBarShowFromLevel) {
|
||||
EditorUtility.ClearProgressBar();
|
||||
}
|
||||
|
||||
AssetDatabase.Refresh();
|
||||
SearchUtils.Upd(arg);
|
||||
}
|
||||
|
||||
GUI.enabled = previous;
|
||||
return true;
|
||||
}
|
||||
|
||||
GUI.enabled = previous;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void DeleteWithMeta(string path) {
|
||||
FileUtil.DeleteFileOrDirectory(path);
|
||||
var metaPath = AssetDatabase.GetTextMetaFilePathFromAssetPath(path);
|
||||
if (!string.IsNullOrEmpty(metaPath))
|
||||
FileUtil.DeleteFileOrDirectory(metaPath);
|
||||
}
|
||||
|
||||
static bool AskDeleteUnusedScenes(SearchArg arg, List<string> unusedScenes, WindowData windowData) {
|
||||
if (arg == null || unusedScenes == null) return false;
|
||||
var hasUnusedScenes = unusedScenes.Count > 0;
|
||||
var previous = GUI.enabled;
|
||||
GUI.enabled = hasUnusedScenes;
|
||||
|
||||
var guiContentRemoveScenes = windowData.Style.RemoveScene;
|
||||
|
||||
if (GUILayout.Button(guiContentRemoveScenes,
|
||||
windowData.Style.RemoveUnusedBtn)) {
|
||||
var choice = EditorUtility.DisplayDialog(
|
||||
title: "Asset Cleaner",
|
||||
message:
|
||||
$"Do you really want to remove {unusedScenes.Count} scene(s)?",
|
||||
ok: "Remove",
|
||||
cancel: "Cancel");
|
||||
if (choice) {
|
||||
EditorApplication.ExecuteMenuItem("File/Save Project");
|
||||
|
||||
var i = 0f;
|
||||
var total = (float) unusedScenes.Count;
|
||||
|
||||
foreach (var scene in unusedScenes) {
|
||||
var path = Application.dataPath.Replace("Assets", scene);
|
||||
DeleteWithMeta(path);
|
||||
|
||||
if (total >= _progressBarShowFromLevel) {
|
||||
var percent = i * 100 / total;
|
||||
if (Math.Abs(percent % 5f) < 0.01f) {
|
||||
if (EditorUtility.DisplayCancelableProgressBar(
|
||||
"Please wait...",
|
||||
"Deleting scenes...", percent))
|
||||
throw new Exception("Deleting aborted");
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
if (total >= _progressBarShowFromLevel) {
|
||||
EditorUtility.ClearProgressBar();
|
||||
}
|
||||
|
||||
AssetDatabase.Refresh();
|
||||
SearchUtils.Upd(arg);
|
||||
}
|
||||
|
||||
GUI.enabled = previous;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
GUI.enabled = previous;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void RenderRows(WindowData windowData) {
|
||||
// todo show spinner until scene is loaded,
|
||||
|
||||
if (FileResultRows.GetEntitiesCount() > 0) {
|
||||
windowData.ExpandFiles =
|
||||
EditorGUILayout.Foldout(windowData.ExpandFiles,
|
||||
$"Usages in Project: {FileResultRows.GetEntitiesCount()}");
|
||||
}
|
||||
|
||||
if (SearchArgMain.IsEmpty())
|
||||
return;
|
||||
|
||||
if (windowData.ExpandFiles && windowData.FindFrom == FindModeEnum.File)
|
||||
foreach (var i in FileResultRows.Out(out var get1, out var get2, out _, out _))
|
||||
DrawRowFile(get1[i], get2[i], windowData);
|
||||
|
||||
|
||||
var sceneMessage = $"Usages in Scenes: {ScenePaths.GetEntitiesCount()}";
|
||||
if (ScenePaths.GetEntitiesCount() > 0) {
|
||||
windowData.ExpandScenes =
|
||||
EditorGUILayout.Foldout(windowData.ExpandScenes, sceneMessage);
|
||||
}
|
||||
|
||||
|
||||
if (!windowData.ExpandScenes) return;
|
||||
|
||||
if (windowData.ExpandScenes && windowData.FindFrom == FindModeEnum.Scene) {
|
||||
foreach (var (grp, indices) in SceneResultRows.Out(out _, out var get2, out _, out _)
|
||||
.GroupBy1(ResultComp.Instance)) {
|
||||
using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox)) {
|
||||
var count = 0;
|
||||
foreach (var i in indices) {
|
||||
if (count++ == 0)
|
||||
if (GUILayout.Button(get2[i].Label, windowData.Style.RowMainAssetBtn)) {
|
||||
if (windowData.Click.IsDoubleClick(grp.RootGo)) {
|
||||
// _selectionChangedByArrows = false;
|
||||
Selection.activeObject = grp.RootGo;
|
||||
}
|
||||
else
|
||||
EditorGUIUtility.PingObject(grp.RootGo);
|
||||
|
||||
windowData.Click = new PrevClick(grp.RootGo);
|
||||
}
|
||||
|
||||
DrawRowScene(get2[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
using (new GUILayout.HorizontalScope()) {
|
||||
GUILayout.Space(windowData.Style.SceneIndent1);
|
||||
using (new EditorGUILayout.VerticalScope()) {
|
||||
foreach (var i1 in ScenePaths.Out(out var get1, out var get2, out _)) {
|
||||
windowData.SceneFoldout.text = get1[i1].PathNicified;
|
||||
var details = get2[i1];
|
||||
|
||||
details.SearchRequested = details.Scene.isLoaded;
|
||||
details.SearchRequested = EditorGUILayout.Foldout(details.SearchRequested,
|
||||
windowData.SceneFoldout, EditorStyles.foldout);
|
||||
if (details.SearchRequested && details.Scene.isLoaded && !details.SearchDone) {
|
||||
var mainArg = SearchArgMain.GetSingle();
|
||||
|
||||
mainArg.Scene = SceneManager.GetSceneByPath(details.Scene.path);
|
||||
|
||||
SearchUtils.InScene(mainArg, details.Scene);
|
||||
details.SearchDone = true;
|
||||
}
|
||||
|
||||
if (!details.SearchRequested) {
|
||||
if (!details.Scene.isLoaded) continue;
|
||||
if (!details.WasOpened) {
|
||||
// to clean up on selection change
|
||||
AufCtx.World.NewEntityWith(out SceneToClose comp);
|
||||
comp.Scene = details.Scene;
|
||||
comp.ForceClose = true;
|
||||
}
|
||||
|
||||
foreach (var row in SceneResultRows.Out(out _, out _, out var get3, out var entities)) {
|
||||
if (!get3[row].ScenePath.Eq(details.Path))
|
||||
continue;
|
||||
|
||||
entities[row].Destroy();
|
||||
}
|
||||
|
||||
details.SearchDone = false;
|
||||
}
|
||||
else {
|
||||
if (!details.Scene.isLoaded) {
|
||||
details.Scene = EditorSceneManager.OpenScene(details.Path, OpenSceneMode.Additive);
|
||||
|
||||
// to clean up on selection change
|
||||
AufCtx.World.NewEntityWith(out SceneToClose comp);
|
||||
comp.Scene = details.Scene;
|
||||
comp.SelectionId = Globals<PersistentUndoRedoState>.Value.Id;
|
||||
|
||||
|
||||
#if UNITY_2019_1_OR_NEWER
|
||||
EditorSceneManager.SetSceneCullingMask(details.Scene, 0);
|
||||
#endif
|
||||
details.SearchRequested = true;
|
||||
// todo Scope component
|
||||
#if later
|
||||
if (details.Scene.isLoaded)
|
||||
EditorSceneManager.CloseScene(details.Scene, false);
|
||||
#endif
|
||||
}
|
||||
else if (SceneResultRows.IsEmpty())
|
||||
EditorGUILayout.LabelField("No in-scene dependencies found.");
|
||||
else
|
||||
using (new GUILayout.HorizontalScope()) {
|
||||
GUILayout.Space(windowData.Style.SceneIndent2);
|
||||
using (new EditorGUILayout.VerticalScope())
|
||||
foreach (var (grp, indices) in SceneResultRows
|
||||
.Out(out var g1, out var g2, out var g3, out _)
|
||||
.GroupBy1(ResultComp.Instance)) {
|
||||
var any = false;
|
||||
foreach (var i3 in indices) {
|
||||
if (!g3[i3].ScenePath.Eq(details.Path))
|
||||
continue;
|
||||
any = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!any)
|
||||
continue;
|
||||
|
||||
using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox)) {
|
||||
var count = 0;
|
||||
foreach (var i2 in indices) {
|
||||
if (!g3[i2].ScenePath.Eq(details.Path))
|
||||
continue;
|
||||
|
||||
if (count++ == 0) {
|
||||
Result comp = g1[i2];
|
||||
if (GUILayout.Button(g2[i2].Label,
|
||||
windowData.Style.RowMainAssetBtn)) {
|
||||
if (windowData.Click.IsDoubleClick(grp.RootGo)) {
|
||||
// _selectionChangedByArrows = false;
|
||||
Selection.activeObject = comp.RootGo;
|
||||
}
|
||||
else
|
||||
EditorGUIUtility.PingObject(comp.RootGo);
|
||||
|
||||
windowData.Click = new PrevClick(comp.RootGo);
|
||||
}
|
||||
}
|
||||
|
||||
DrawRowScene(g2[i2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ResultComp : IEqualityComparer<Result> {
|
||||
public static ResultComp Instance { get; } = new ResultComp();
|
||||
public bool Equals(Result x, Result y) => GetHashCode(x) == GetHashCode(y);
|
||||
public int GetHashCode(Result obj) => obj.RootGo.GetInstanceID();
|
||||
}
|
||||
|
||||
static void DrawRowScene(SearchResultGui gui) {
|
||||
EditorGUI.BeginChangeCheck();
|
||||
// if (data.TargetGo || data.TargetComponent)
|
||||
foreach (var prop in gui.Properties) {
|
||||
{
|
||||
var locked = prop.Property.objectReferenceValue is MonoScript;
|
||||
var f = GUI.enabled;
|
||||
|
||||
if (locked) GUI.enabled = false;
|
||||
|
||||
EditorGUILayout.PropertyField(prop.Property, prop.Content, false);
|
||||
|
||||
if (locked) GUI.enabled = f;
|
||||
}
|
||||
}
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
gui.SerializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
|
||||
static void DrawRowFile(Result data, SearchResultGui gui, WindowData windowData) {
|
||||
using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox)) {
|
||||
var buf = GUI.color;
|
||||
var pingGo = data.MainFile == null ? data.RootGo : data.MainFile;
|
||||
if (GUILayout.Button(gui.Label, windowData.Style.RowMainAssetBtn)) {
|
||||
if (windowData.Click.IsDoubleClick(pingGo)) {
|
||||
// _selectionChangedByArrows = false;
|
||||
Selection.activeObject = pingGo;
|
||||
}
|
||||
else {
|
||||
EditorGUIUtility.PingObject(pingGo);
|
||||
}
|
||||
|
||||
windowData.Click = new PrevClick(pingGo);
|
||||
}
|
||||
|
||||
GUI.color = buf;
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
if (data.File) {
|
||||
foreach (var prop in gui.Properties) {
|
||||
using (new EditorGUILayout.HorizontalScope()) {
|
||||
var locked = prop.Property.objectReferenceValue is MonoScript;
|
||||
var f = GUI.enabled;
|
||||
if (locked) GUI.enabled = false;
|
||||
EditorGUILayout.PropertyField(prop.Property, prop.Content, false);
|
||||
if (locked) GUI.enabled = f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (EditorGUI.EndChangeCheck()) {
|
||||
gui.SerializedObject.ApplyModifiedProperties();
|
||||
// dependency.SerializedObject.Update();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Asset Cleaner/Systems/SysWindowGui.cs.meta
Normal file
3
Assets/Asset Cleaner/Systems/SysWindowGui.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 87ac41283aa347c4aece096c90076c7f
|
||||
timeCreated: 1596213420
|
||||
8
Assets/Asset Cleaner/Utils.meta
Normal file
8
Assets/Asset Cleaner/Utils.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8f9623ba76cbf214994b7b865c65f362
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
34
Assets/Asset Cleaner/Utils/Asr.cs
Normal file
34
Assets/Asset Cleaner/Utils/Asr.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using System.Diagnostics;
|
||||
using UnityEngine.Assertions;
|
||||
|
||||
namespace Asset_Cleaner {
|
||||
static class FLAGS {
|
||||
// cleanup in release
|
||||
public const string DEBUG = "DEBUG1";
|
||||
public const string M_DISABLE_POOLING = "M_DISABLE_POOLING";
|
||||
}
|
||||
|
||||
static class Asr {
|
||||
#line hidden
|
||||
[Conditional(FLAGS.DEBUG)]
|
||||
public static void AreEqual(int a, int b) {
|
||||
Assert.AreEqual(a, b);
|
||||
}
|
||||
|
||||
[Conditional(FLAGS.DEBUG)]
|
||||
public static void IsTrue(bool b, string format = null) {
|
||||
Assert.IsTrue(b, format);
|
||||
}
|
||||
|
||||
[Conditional(FLAGS.DEBUG)]
|
||||
public static void IsFalse(bool b, string format = null) {
|
||||
Assert.IsFalse(b, format);
|
||||
}
|
||||
|
||||
[Conditional(FLAGS.DEBUG)]
|
||||
public static void IsNotNull(object target, string format = null) {
|
||||
Assert.IsNotNull(target, format);
|
||||
}
|
||||
#line default
|
||||
}
|
||||
}
|
||||
3
Assets/Asset Cleaner/Utils/Asr.cs.meta
Normal file
3
Assets/Asset Cleaner/Utils/Asr.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 53c5eeeb89d541a5b26f906f93ffe650
|
||||
timeCreated: 1581424097
|
||||
37
Assets/Asset Cleaner/Utils/AufCtx.cs
Normal file
37
Assets/Asset Cleaner/Utils/AufCtx.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using Leopotam.Ecs;
|
||||
|
||||
namespace Asset_Cleaner {
|
||||
static class AufCtx {
|
||||
public static EcsWorld World;
|
||||
|
||||
public static EcsSystems UpdateGroup;
|
||||
public static EcsSystems OnGuiGroup;
|
||||
|
||||
internal static bool InitStarted { get; private set; }
|
||||
internal static bool Destroyed { get; private set; }
|
||||
|
||||
internal static void TryInitWorld() {
|
||||
if (InitStarted) return;
|
||||
InitStarted = true;
|
||||
|
||||
World = new EcsWorld();
|
||||
|
||||
(OnGuiGroup = new EcsSystems(World)
|
||||
.Add(new SysWindowGui())).Init();
|
||||
|
||||
(UpdateGroup = new EcsSystems(World)
|
||||
.Add(new SysRepaintWindow())
|
||||
.Add(new SysUndoRedoSelection())
|
||||
.Add(new SysProcessSearch())
|
||||
.Add(new SysSceneCleanup())
|
||||
).Init();
|
||||
}
|
||||
|
||||
internal static void DestroyWorld() {
|
||||
if (!InitStarted) return;
|
||||
InitStarted = false;
|
||||
Destroyed = true;
|
||||
Asr.IsFalse(__GlobalsCounter.HasAnyValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Asset Cleaner/Utils/AufCtx.cs.meta
Normal file
11
Assets/Asset Cleaner/Utils/AufCtx.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 999cc1329c8dd5743b39fe17eb02a7aa
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
40
Assets/Asset Cleaner/Utils/CommonUtils.cs
Normal file
40
Assets/Asset Cleaner/Utils/CommonUtils.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Asset_Cleaner {
|
||||
static class CommonUtils {
|
||||
static string[] _suffix = {"B", "KB", "MB", "GB", "TB"};
|
||||
|
||||
public static string BytesToString(long byteCount) {
|
||||
if (byteCount == 0)
|
||||
return $"0 {_suffix[0]}";
|
||||
|
||||
var bytes = Math.Abs(byteCount);
|
||||
var place = Convert.ToInt32(Math.Floor(Math.Log(bytes, 1024)));
|
||||
double num;
|
||||
if (place == 0 || place == 1) { // display B, KB in MB
|
||||
num = Math.Round(bytes / Math.Pow(1024, 2), 4);
|
||||
return $"{Math.Sign(byteCount) * num:N} {_suffix[2]}";
|
||||
}
|
||||
|
||||
num = Math.Round(bytes / Math.Pow(1024, place), 1);
|
||||
return $"{Math.Sign(byteCount) * num:F0} {_suffix[place]}";
|
||||
}
|
||||
|
||||
// todo
|
||||
public static long Size(string path) {
|
||||
return TryGetSize(path, out var res) ? res : 0L;
|
||||
}
|
||||
|
||||
public static bool TryGetSize(string path, out long result) {
|
||||
if (!File.Exists(path)) {
|
||||
result = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
var fi = new FileInfo(path);
|
||||
result = fi.Length;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Asset Cleaner/Utils/CommonUtils.cs.meta
Normal file
3
Assets/Asset Cleaner/Utils/CommonUtils.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4a0fa79cb05e495a8dc80a251a33970c
|
||||
timeCreated: 1595072385
|
||||
46
Assets/Asset Cleaner/Utils/DirtyUtils.cs
Normal file
46
Assets/Asset Cleaner/Utils/DirtyUtils.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Asset_Cleaner {
|
||||
static class DirtyUtils {
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int HashCode<T1>(in T1 v1) {
|
||||
var hash = v1.GetHashCode();
|
||||
hash = (hash * 397);
|
||||
return hash;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int HashCode<T1, T2>(in T1 v1, in T2 v2) {
|
||||
var hash = v1.GetHashCode();
|
||||
hash = (hash * 397) ^ v2.GetHashCode();
|
||||
return hash;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int HashCode<T1, T2, T3>(in T1 v1, in T2 v2, in T3 v3) {
|
||||
var hash = v1.GetHashCode();
|
||||
hash = (hash * 397) ^ v2.GetHashCode();
|
||||
hash = (hash * 397) ^ v3.GetHashCode();
|
||||
return hash;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int HashCode<T1, T2, T3, T4>(in T1 v1, in T2 v2, in T3 v3, in T4 v4) {
|
||||
var hash = v1.GetHashCode();
|
||||
hash = (hash * 397) ^ v2.GetHashCode();
|
||||
hash = (hash * 397) ^ v3.GetHashCode();
|
||||
hash = (hash * 397) ^ v4.GetHashCode();
|
||||
return hash;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int HashCode<T1, T2, T3, T4, T5>(in T1 v1, in T2 v2, in T3 v3, in T4 v4, in T5 v5) {
|
||||
var hash = v1.GetHashCode();
|
||||
hash = (hash * 397) ^ v2.GetHashCode();
|
||||
hash = (hash * 397) ^ v3.GetHashCode();
|
||||
hash = (hash * 397) ^ v4.GetHashCode();
|
||||
hash = (hash * 397) ^ v5.GetHashCode();
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Asset Cleaner/Utils/DirtyUtils.cs.meta
Normal file
3
Assets/Asset Cleaner/Utils/DirtyUtils.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3fd8e20db38143e3a3c51ea733bd0bab
|
||||
timeCreated: 1577270369
|
||||
55
Assets/Asset Cleaner/Utils/EcsUtils.cs
Normal file
55
Assets/Asset Cleaner/Utils/EcsUtils.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Leopotam.Ecs;
|
||||
|
||||
namespace Asset_Cleaner {
|
||||
static class EcsUtils {
|
||||
public static IEnumerable<(T Group, IEnumerable<int> Indices)> GroupBy1<T, T1, T2>(this EcsFilter<T, T1, T2> f, IEqualityComparer<T> comp)
|
||||
where T : class
|
||||
where T1 : class
|
||||
where T2 : class {
|
||||
foreach (var group in Inner().GroupBy(tuple => tuple.Group, comp))
|
||||
yield return (group.Key, group.Select(g => g.EcsIndex));
|
||||
|
||||
IEnumerable<(T Group, int EcsIndex)> Inner() {
|
||||
var get1 = f.Get1;
|
||||
foreach (var i in f) yield return (get1[i], i);
|
||||
}
|
||||
}
|
||||
|
||||
public static EcsFilter<T> Out<T>(this EcsFilter<T> filter, out T[] get1, out EcsEntity[] entities) where T : class {
|
||||
get1 = filter.Get1;
|
||||
entities = filter.Entities;
|
||||
return filter;
|
||||
}
|
||||
|
||||
public static EcsFilter<T1, T2> Out<T1, T2>(this EcsFilter<T1, T2> filter, out T1[] get1, out T2[] get2, out EcsEntity[] entities)
|
||||
where T1 : class where T2 : class {
|
||||
get1 = filter.Get1;
|
||||
get2 = filter.Get2;
|
||||
entities = filter.Entities;
|
||||
return filter;
|
||||
}
|
||||
|
||||
public static EcsFilter<T1, T2, T3> Out<T1, T2, T3>(this EcsFilter<T1, T2, T3> filter, out T1[] get1, out T2[] get2, out T3[] get3, out EcsEntity[] entities)
|
||||
where T1 : class where T2 : class where T3 : class {
|
||||
get1 = filter.Get1;
|
||||
get2 = filter.Get2;
|
||||
get3 = filter.Get3;
|
||||
entities = filter.Entities;
|
||||
return filter;
|
||||
}
|
||||
|
||||
public static void AllDestroy(this EcsFilter f) {
|
||||
var ecsEntities = f.Entities;
|
||||
foreach (var i in f)
|
||||
ecsEntities[i].Destroy();
|
||||
}
|
||||
|
||||
public static void AllUnset<T>(this EcsFilter f) where T : class {
|
||||
var e = f.Entities;
|
||||
foreach (var i in f)
|
||||
e[i].Unset<T>();
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Asset Cleaner/Utils/EcsUtils.cs.meta
Normal file
11
Assets/Asset Cleaner/Utils/EcsUtils.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3d81e9bb0744bc4448ddb9bbd93bffd3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
17
Assets/Asset Cleaner/Utils/Ext.cs
Normal file
17
Assets/Asset Cleaner/Utils/Ext.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using Leopotam.Ecs;
|
||||
|
||||
namespace Asset_Cleaner {
|
||||
static class Ext {
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Eq(this string s1, string s2) => (s1 == s2);
|
||||
// public static bool Eq(this string s1, string s2) => StringComparer.Ordinal.Equals(s1, s2);
|
||||
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static T GetSingle<T>(this EcsFilter<T> f) where T : class {
|
||||
Asr.AreEqual(f.GetEntitiesCount(), 1);
|
||||
return f.Get1[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Asset Cleaner/Utils/Ext.cs.meta
Normal file
11
Assets/Asset Cleaner/Utils/Ext.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 66f6b6922016ea04797deac8d082bc80
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
29
Assets/Asset Cleaner/Utils/Globals.cs
Normal file
29
Assets/Asset Cleaner/Utils/Globals.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
namespace Asset_Cleaner {
|
||||
static class Globals<T> where T : class {
|
||||
static T _instance;
|
||||
|
||||
public static T Value {
|
||||
get {
|
||||
Asr.IsFalse(_instance == null);
|
||||
return _instance;
|
||||
}
|
||||
set {
|
||||
var was = HasValue();
|
||||
_instance = value;
|
||||
|
||||
// keep counter to check during deinitialization if all Globals are cleared
|
||||
if (was && !HasValue())
|
||||
__GlobalsCounter.Counter -= 1;
|
||||
if (!was && HasValue())
|
||||
__GlobalsCounter.Counter += 1;
|
||||
|
||||
bool HasValue() => _instance != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class __GlobalsCounter {
|
||||
internal static int Counter;
|
||||
public static bool HasAnyValue() => Counter > 0;
|
||||
}
|
||||
}
|
||||
3
Assets/Asset Cleaner/Utils/Globals.cs.meta
Normal file
3
Assets/Asset Cleaner/Utils/Globals.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dd0464bfdb1b4253ba131c7140510c81
|
||||
timeCreated: 1596137163
|
||||
124
Assets/Asset Cleaner/Utils/Option.cs
Normal file
124
Assets/Asset Cleaner/Utils/Option.cs
Normal file
@@ -0,0 +1,124 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Asset_Cleaner {
|
||||
readonly struct Option<T> : IEquatable<Option<T>>, IComparable<Option<T>> {
|
||||
// ReSharper disable once StaticMemberInGenericType
|
||||
static readonly bool IsValueType;
|
||||
|
||||
public bool HasValue { get; }
|
||||
|
||||
T Value { get; }
|
||||
|
||||
public static implicit operator Option<T>(T arg) {
|
||||
if (!IsValueType) return ReferenceEquals(arg, null) ? new Option<T>() : new Option<T>(arg, true);
|
||||
#if M_WARN
|
||||
if (arg.Equals(default(T)))
|
||||
Warn.Warning($"{arg} has default value");
|
||||
#endif
|
||||
return new Option<T>(arg, true);
|
||||
}
|
||||
|
||||
static Option() {
|
||||
IsValueType = typeof(T).IsValueType;
|
||||
}
|
||||
|
||||
public void GetOrFail(out T value) {
|
||||
if (!TryGet(out value))
|
||||
Fail($"Option<{typeof(T).Name}> has no value");
|
||||
}
|
||||
|
||||
public T GetOrFail() {
|
||||
if (!TryGet(out var value))
|
||||
Fail($"Option<{typeof(T).Name}> has no value");
|
||||
return value;
|
||||
}
|
||||
|
||||
[Conditional("DEBUG1")]
|
||||
static void Fail(string format = null) {
|
||||
throw new Exception(format);
|
||||
}
|
||||
|
||||
public bool TryGet(out T value) {
|
||||
if (!HasValue) {
|
||||
value = default(T);
|
||||
return false;
|
||||
}
|
||||
|
||||
value = Value;
|
||||
return true;
|
||||
}
|
||||
|
||||
internal Option(T value, bool hasValue) {
|
||||
Value = value;
|
||||
HasValue = hasValue;
|
||||
}
|
||||
|
||||
public T ValueOr(T alternative) {
|
||||
return HasValue ? Value : alternative;
|
||||
}
|
||||
|
||||
// for debug purposes
|
||||
public override string ToString() {
|
||||
if (!HasValue) return "None";
|
||||
|
||||
return Value == null ? "Some(null)" : $"Some({Value})";
|
||||
}
|
||||
|
||||
#region eq comparers boilerplate
|
||||
|
||||
public bool Equals(Option<T> other) {
|
||||
if (!HasValue && !other.HasValue)
|
||||
return true;
|
||||
|
||||
if (HasValue && other.HasValue)
|
||||
return EqualityComparer<T>.Default.Equals(Value, other.Value);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj) {
|
||||
return obj is Option<T> && Equals((Option<T>) obj);
|
||||
}
|
||||
|
||||
public static bool operator ==(Option<T> left, Option<T> right) {
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(Option<T> left, Option<T> right) {
|
||||
return !left.Equals(right);
|
||||
}
|
||||
|
||||
public override int GetHashCode() {
|
||||
if (!HasValue) return 0;
|
||||
|
||||
return IsValueType || Value != null ? Value.GetHashCode() : 1;
|
||||
}
|
||||
|
||||
public int CompareTo(Option<T> other) {
|
||||
if (HasValue && !other.HasValue) return 1;
|
||||
if (!HasValue && other.HasValue) return -1;
|
||||
|
||||
return Comparer<T>.Default.Compare(Value, other.Value);
|
||||
}
|
||||
|
||||
public static bool operator <(Option<T> left, Option<T> right) {
|
||||
return left.CompareTo(right) < 0;
|
||||
}
|
||||
|
||||
public static bool operator <=(Option<T> left, Option<T> right) {
|
||||
return left.CompareTo(right) <= 0;
|
||||
}
|
||||
|
||||
public static bool operator >(Option<T> left, Option<T> right) {
|
||||
return left.CompareTo(right) > 0;
|
||||
}
|
||||
|
||||
public static bool operator >=(Option<T> left, Option<T> right) {
|
||||
return left.CompareTo(right) >= 0;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
3
Assets/Asset Cleaner/Utils/Option.cs.meta
Normal file
3
Assets/Asset Cleaner/Utils/Option.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 098c45ccebe14f5caa28635386021c94
|
||||
timeCreated: 1591801098
|
||||
57
Assets/Asset Cleaner/Utils/PersistenceUtils.cs
Normal file
57
Assets/Asset Cleaner/Utils/PersistenceUtils.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Asset_Cleaner {
|
||||
static class PersistenceUtils {
|
||||
public static void Load(ref Config result) {
|
||||
var serializable = Deserialize();
|
||||
AufSerializableData.OnDeserialize(in serializable, ref result);
|
||||
}
|
||||
|
||||
public static void Save(in Config src) {
|
||||
AufSerializableData.OnSerialize(in src, out var serializable);
|
||||
var json = JsonUtility.ToJson(serializable);
|
||||
File.WriteAllText(Path, json);
|
||||
}
|
||||
|
||||
static AufSerializableData Deserialize() {
|
||||
AufSerializableData serializableData;
|
||||
string json;
|
||||
|
||||
if (!File.Exists(Path)) {
|
||||
// not exists - write new
|
||||
serializableData = AufSerializableData.Default();
|
||||
json = JsonUtility.ToJson(serializableData);
|
||||
File.WriteAllText(Path, json);
|
||||
}
|
||||
else {
|
||||
// exists
|
||||
json = File.ReadAllText(Path);
|
||||
|
||||
if (string.IsNullOrEmpty(json)) {
|
||||
// but corrupted - overwrite with new
|
||||
serializableData = AufSerializableData.Default();
|
||||
json = JsonUtility.ToJson(serializableData);
|
||||
File.WriteAllText(Path, json);
|
||||
}
|
||||
|
||||
serializableData = JsonUtility.FromJson<AufSerializableData>(json);
|
||||
if (serializableData.Valid())
|
||||
return serializableData;
|
||||
|
||||
serializableData = AufSerializableData.Default();
|
||||
json = JsonUtility.ToJson(serializableData);
|
||||
File.WriteAllText(Path, json);
|
||||
}
|
||||
|
||||
return serializableData;
|
||||
}
|
||||
|
||||
static string Path => $"{Application.temporaryCachePath}/AssetCleaner_{AufSerializableData.CurrentVersion}.json";
|
||||
|
||||
// [MenuItem("Tools/LogPath")]
|
||||
static void Log() {
|
||||
Debug.Log(Application.temporaryCachePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Asset Cleaner/Utils/PersistenceUtils.cs.meta
Normal file
3
Assets/Asset Cleaner/Utils/PersistenceUtils.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 764a6ef52ca2456eb7c276bbcd0929f0
|
||||
timeCreated: 1589466213
|
||||
81
Assets/Asset Cleaner/Utils/Pool.cs
Normal file
81
Assets/Asset Cleaner/Utils/Pool.cs
Normal file
@@ -0,0 +1,81 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Asset_Cleaner {
|
||||
class Pool<T> : IDisposable where T : class {
|
||||
Func<T> _ctor;
|
||||
readonly Stack<T> _stack;
|
||||
|
||||
// todo place asserts on app quit
|
||||
Action<T> _reset;
|
||||
Action<T> _destroy;
|
||||
|
||||
static Action<T> Empty = _ => { };
|
||||
|
||||
public Pool(Func<T> ctor, Action<T> reset, Action<T> destroy = null) {
|
||||
_ctor = ctor;
|
||||
#if !M_DISABLE_POOLING
|
||||
_destroy = destroy ?? Empty;
|
||||
_reset = reset;
|
||||
_stack = new Stack<T>();
|
||||
#endif
|
||||
}
|
||||
|
||||
public T Get() {
|
||||
#if M_DISABLE_POOLING
|
||||
return _ctor.Invoke();
|
||||
#else
|
||||
T element;
|
||||
if (_stack.Count == 0) {
|
||||
element = _ctor();
|
||||
}
|
||||
else {
|
||||
element = _stack.Pop();
|
||||
}
|
||||
|
||||
return element;
|
||||
#endif
|
||||
}
|
||||
|
||||
public void Release(ref T element) {
|
||||
#if !M_DISABLE_POOLING
|
||||
Asr.IsFalse(_stack.Count > 0 && ReferenceEquals(_stack.Peek(), element),
|
||||
"Internal error. Trying to release object that is already released to pool. ");
|
||||
|
||||
_reset.Invoke(element);
|
||||
_stack.Push(element);
|
||||
#endif
|
||||
|
||||
element = null;
|
||||
}
|
||||
|
||||
|
||||
public void Dispose() {
|
||||
#if !M_DISABLE_POOLING
|
||||
while (_stack.Count > 0) {
|
||||
var t = _stack.Pop();
|
||||
_destroy.Invoke(t);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public _Scope GetScoped(out T tmp) {
|
||||
tmp = Get();
|
||||
return new _Scope(this, ref tmp);
|
||||
}
|
||||
|
||||
public struct _Scope : IDisposable {
|
||||
Pool<T> _pool;
|
||||
T _val;
|
||||
|
||||
internal _Scope(Pool<T> pool, ref T val) {
|
||||
_pool = pool;
|
||||
_val = val;
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
_pool.Release(ref _val);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Assets/Asset Cleaner/Utils/Pool.cs.meta
Normal file
3
Assets/Asset Cleaner/Utils/Pool.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b5a618b193c34a5cba77e7cc0df122f1
|
||||
timeCreated: 1591254176
|
||||
458
Assets/Asset Cleaner/Utils/SearchUtils.cs
Normal file
458
Assets/Asset Cleaner/Utils/SearchUtils.cs
Normal file
@@ -0,0 +1,458 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Leopotam.Ecs;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Animations;
|
||||
using UnityEditor.SceneManagement;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
using static Asset_Cleaner.AufCtx;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace Asset_Cleaner {
|
||||
static class SearchUtils {
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsAssignableFromInverse(this Type lhs, Type rhs) {
|
||||
if (lhs == null || rhs == null)
|
||||
return false;
|
||||
|
||||
return rhs.IsAssignableFrom(lhs);
|
||||
}
|
||||
|
||||
static Queue<SerializedProperty> _tmp = new Queue<SerializedProperty>();
|
||||
|
||||
public static void Upd(SearchArg arg) {
|
||||
if (arg.Target is DefaultAsset folder) {
|
||||
var path = AssetDatabase.GetAssetPath(folder);
|
||||
var store = Globals<BacklinkStore>.Value;
|
||||
arg.UnusedAssetsFiltered = store.UnusedFiles.Where(p => p.Key.StartsWith(path)).Select(p => p.Key).ToList();
|
||||
arg.UnusedScenesFiltered = store.UnusedScenes.Where(p => p.Key.StartsWith(path)).Select(p => p.Key).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
public static void Init(SearchArg arg, Object target, Scene scene = default) {
|
||||
Asr.IsNotNull(target, "Asset you're trying to search is corrupted");
|
||||
|
||||
arg.Target = target;
|
||||
|
||||
arg.FilePath = AssetDatabase.GetAssetPath(arg.Target);
|
||||
if (!scene.IsValid()) {
|
||||
Upd(arg);
|
||||
|
||||
arg.Main = AssetDatabase.LoadMainAssetAtPath(arg.FilePath);
|
||||
if (AssetDatabase.IsSubAsset(arg.Target)) { }
|
||||
else {
|
||||
switch (target) {
|
||||
case SceneAsset _:
|
||||
// todo support cross-scene references?
|
||||
// nested = all assets
|
||||
break;
|
||||
default:
|
||||
// AssetDatabase.IsMainAssetAtPathLoaded()
|
||||
var subAssets = AssetDatabase.LoadAllAssetsAtPath(arg.FilePath).Where(Predicate).ToArray();
|
||||
arg.SubAssets = subAssets.Length == 0 ? default(Option<Object[]>) : subAssets;
|
||||
|
||||
bool Predicate(Object s) {
|
||||
if (!s)
|
||||
return false;
|
||||
return s.GetInstanceID() != arg.Target.GetInstanceID();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
switch (arg.Target) {
|
||||
case GameObject gg:
|
||||
arg.Main = gg;
|
||||
arg.Scene = scene;
|
||||
arg.SubAssets = gg.GetComponents<Component>().OfType<Object>().ToArray();
|
||||
break;
|
||||
case Component component: {
|
||||
// treat like subAsset
|
||||
arg.Main = component.gameObject;
|
||||
arg.Scene = scene;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
// project asset such as Material
|
||||
arg.Main = arg.Target;
|
||||
arg.Scene = scene;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool SearchInChildProperties(SearchArg arg, Object suspect, bool scene, out EcsEntity entity) {
|
||||
if (IsTargetOrNested(arg, suspect)) {
|
||||
entity = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!suspect) {
|
||||
entity = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
var so = new SerializedObject(suspect);
|
||||
_tmp.Clear();
|
||||
var queue = _tmp;
|
||||
var propIterator = so.GetIterator();
|
||||
|
||||
var prefabInstance = false;
|
||||
|
||||
if (scene && !string.IsNullOrEmpty(arg.FilePath) && PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(suspect) == arg.FilePath) {
|
||||
prefabInstance = true;
|
||||
while (propIterator.NextVisible(true)) {
|
||||
if (propIterator.propertyType != SerializedPropertyType.ObjectReference)
|
||||
continue;
|
||||
if (!IsTargetOrNested(arg, propIterator.objectReferenceValue))
|
||||
continue;
|
||||
|
||||
queue.Enqueue(propIterator.Copy());
|
||||
}
|
||||
}
|
||||
else {
|
||||
while (propIterator.Next(true)) {
|
||||
if (propIterator.propertyType != SerializedPropertyType.ObjectReference)
|
||||
continue;
|
||||
if (!IsTargetOrNested(arg, propIterator.objectReferenceValue))
|
||||
continue;
|
||||
|
||||
queue.Enqueue(propIterator.Copy());
|
||||
}
|
||||
}
|
||||
|
||||
if (queue.Count == 0 && !prefabInstance) {
|
||||
entity = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
entity = World.NewEntityWith(out Result data);
|
||||
|
||||
var gui = entity.Set<SearchResultGui>();
|
||||
gui.Properties = new List<SearchResultGui.PropertyData>();
|
||||
gui.SerializedObject = so;
|
||||
gui.Label = new GUIContent();
|
||||
|
||||
// init header
|
||||
Texture2D miniTypeThumbnail = null;
|
||||
if (scene) {
|
||||
switch (suspect) {
|
||||
case Component component:
|
||||
data.RootGo = component.gameObject;
|
||||
gui.TransformPath = AnimationUtility.CalculateTransformPath(component.transform, null);
|
||||
gui.Label.image = AssetPreview.GetMiniThumbnail(data.RootGo);
|
||||
gui.Label.text = gui.TransformPath;
|
||||
break;
|
||||
case GameObject go:
|
||||
data.RootGo = go;
|
||||
gui.Label.image = AssetPreview.GetMiniThumbnail(data.RootGo);
|
||||
gui.TransformPath = AnimationUtility.CalculateTransformPath(go.transform, null);
|
||||
gui.Label.text = gui.TransformPath;
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
miniTypeThumbnail = data.RootGo.GetInstanceID() == suspect.GetInstanceID()
|
||||
? null
|
||||
: AssetPreview.GetMiniThumbnail(suspect);
|
||||
}
|
||||
else {
|
||||
data.File = suspect;
|
||||
data.FilePath = AssetDatabase.GetAssetPath(data.File);
|
||||
data.MainFile = AssetDatabase.LoadMainAssetAtPath(data.FilePath);
|
||||
|
||||
// todo
|
||||
var prefabInstanceStatus = PrefabUtility.GetPrefabInstanceStatus(data.MainFile);
|
||||
switch (prefabInstanceStatus) {
|
||||
case PrefabInstanceStatus.Connected:
|
||||
case PrefabInstanceStatus.Disconnected:
|
||||
switch (data.File) {
|
||||
case Component comp:
|
||||
// transformPath = $"{AnimationUtility.CalculateTransformPath(comp.transform, null)}/".Replace("/", "/\n");
|
||||
gui.TransformPath =
|
||||
$"{AnimationUtility.CalculateTransformPath(comp.transform, null)}";
|
||||
break;
|
||||
case GameObject go:
|
||||
// transformPath = $"{AnimationUtility.CalculateTransformPath(go.transform, null)}/".Replace("/", "/\n");
|
||||
gui.TransformPath =
|
||||
$"{AnimationUtility.CalculateTransformPath(go.transform, null)}";
|
||||
break;
|
||||
default:
|
||||
// Assert.Fail("Not a component"); //todo
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
case PrefabInstanceStatus.NotAPrefab:
|
||||
case PrefabInstanceStatus.MissingAsset:
|
||||
if (!AssetDatabase.IsMainAsset(data.File)) {
|
||||
// {row.Main.name}
|
||||
gui.TransformPath = $"/{data.File.name}";
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
gui.Label.text = data.FilePath.Replace(AssetsRootPath, string.Empty);
|
||||
gui.Label.image = AssetDatabase.GetCachedIcon(data.FilePath);
|
||||
}
|
||||
|
||||
gui.Label.tooltip = gui.TransformPath;
|
||||
|
||||
|
||||
// init properties (footer)
|
||||
while (queue.Count > 0) {
|
||||
var prop = queue.Dequeue();
|
||||
var targetObject = prop.serializedObject.targetObject;
|
||||
var item = new SearchResultGui.PropertyData {
|
||||
Property = prop,
|
||||
Content = new GUIContent()
|
||||
};
|
||||
item.Content.image = miniTypeThumbnail;
|
||||
item.Content.text = Nicify(prop, targetObject, gui.TransformPath);
|
||||
item.Content.tooltip = gui.TransformPath;
|
||||
var typeName = targetObject.GetType().Name;
|
||||
if (StringComparer.Ordinal.Equals(typeName, targetObject.name))
|
||||
item.Content.tooltip = $"{gui.TransformPath}.{prop.propertyPath}";
|
||||
else
|
||||
item.Content.tooltip = $"{gui.TransformPath}({typeName}).{prop.propertyPath}";
|
||||
gui.Properties.Add(item: item);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool IsFileIgrnoredBySettings(string path) {
|
||||
if (IgnoreTypes.Check(path, out _)) return true;
|
||||
if (IgnoredNonAssets(path)) return true;
|
||||
if (IgnoredPaths(path, out _)) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool IgnoredPaths(string path, out string str) {
|
||||
var conf = Globals<Config>.Value;
|
||||
foreach (var substr in conf.IgnorePathContains) {
|
||||
Asr.IsNotNull(path);
|
||||
Asr.IsNotNull(substr);
|
||||
if (!path.Contains(substr)) continue;
|
||||
str = substr;
|
||||
return true;
|
||||
}
|
||||
|
||||
str = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool IgnoredNonAssets(string path) {
|
||||
return !path.Contains("Assets/");
|
||||
}
|
||||
|
||||
#region Project
|
||||
|
||||
public static bool IsUnused(string path) {
|
||||
if (IsFileIgrnoredBySettings(path))
|
||||
return false;
|
||||
return !AnyDependencies(path);
|
||||
}
|
||||
|
||||
static bool AnyDependencies(string path) {
|
||||
var store = Globals<BacklinkStore>.Value;
|
||||
if (store.UnusedFiles.Select(p => p.Key).Contains(path))
|
||||
return false;
|
||||
if (store.UnusedScenes.Select(p => p.Key).Contains(path))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void FilesThatReference(SearchArg arg) {
|
||||
var store = Globals<BacklinkStore>.Value;
|
||||
var path1 = AssetDatabase.GetAssetPath(arg.Target);
|
||||
|
||||
if (!store.Backward.TryGetValue(path1, out var dep))
|
||||
return;
|
||||
|
||||
foreach (var path in dep.Lookup) {
|
||||
var mainAsset = AssetDatabase.GetMainAssetTypeAtPath(path);
|
||||
if (mainAsset.IsAssignableFromInverse(typeof(SceneAsset)))
|
||||
continue;
|
||||
|
||||
var any = false;
|
||||
if (mainAsset.IsAssignableFromInverse(typeof(GameObject))) { }
|
||||
else {
|
||||
var allAssetsAtPath = AssetDatabase.LoadAllAssetsAtPath(path);
|
||||
foreach (var suspect in allAssetsAtPath) {
|
||||
if (suspect is DefaultAsset || suspect is Transform || !suspect) continue;
|
||||
|
||||
if (!SearchInChildProperties(arg, suspect, false, out var entity))
|
||||
continue;
|
||||
|
||||
entity.Set<FileResultTag>();
|
||||
any = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (any) continue;
|
||||
|
||||
// failed to find any property - just show main asset
|
||||
var e = World.NewEntity();
|
||||
var gui = e.Set<SearchResultGui>();
|
||||
gui.Properties = new List<SearchResultGui.PropertyData>();
|
||||
var main = AssetDatabase.LoadMainAssetAtPath(path);
|
||||
gui.Label = new GUIContent() {
|
||||
image = AssetPreview.GetMiniThumbnail(main),
|
||||
text = path.Replace(AssetsRootPath, string.Empty)
|
||||
};
|
||||
var res = e.Set<Result>();
|
||||
res.MainFile = main;
|
||||
e.Set<FileResultTag>();
|
||||
}
|
||||
}
|
||||
|
||||
public static void ScenesThatContain(Object activeObject) {
|
||||
var store = Globals<BacklinkStore>.Value;
|
||||
var path1 = AssetDatabase.GetAssetPath(activeObject);
|
||||
if (!store.Backward.TryGetValue(path1, out var dep))
|
||||
return;
|
||||
|
||||
foreach (var path in dep.Lookup) {
|
||||
if (!AssetDatabase.GetMainAssetTypeAtPath(path).IsAssignableFromInverse(typeof(SceneAsset)))
|
||||
continue;
|
||||
|
||||
World.NewEntityWith(out SceneResult sp, out SceneDetails sceneDetails);
|
||||
sp.PathNicified = path.Replace("Assets/", string.Empty);
|
||||
|
||||
// heavy
|
||||
sceneDetails.Path = path;
|
||||
var alreadyOpened = false;
|
||||
for (var i = 0; i < EditorSceneManager.loadedSceneCount; i++) {
|
||||
var cur = SceneManager.GetSceneAt(i);
|
||||
if (!cur.path.Eq(sceneDetails.Path)) continue;
|
||||
alreadyOpened = true;
|
||||
sceneDetails.Scene = cur;
|
||||
}
|
||||
|
||||
sceneDetails.WasOpened = alreadyOpened;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Scene
|
||||
|
||||
static Pool<List<Component>> ComponentListPool = new Pool<List<Component>>(() => new List<Component>(), list => list.Clear());
|
||||
|
||||
// todo provide explicit scene arg
|
||||
public static void InScene(SearchArg arg, Scene currentScene) {
|
||||
var rootGameObjects = currentScene.GetRootGameObjects();
|
||||
|
||||
foreach (var suspect in Traverse(rootGameObjects)) {
|
||||
if (!SearchInChildProperties(arg, suspect, scene: true, out var entity))
|
||||
continue;
|
||||
var s = entity.Set<InSceneResult>();
|
||||
s.ScenePath = arg.Scene.path;
|
||||
}
|
||||
|
||||
IEnumerable<Object> Traverse(GameObject[] roots) {
|
||||
foreach (var rootGo in roots)
|
||||
foreach (var comp in GoAndChildComps(rootGo)) {
|
||||
yield return comp;
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerable<Object> GoAndChildComps(GameObject rr) {
|
||||
using (ComponentListPool.GetScoped(out var pooled)) {
|
||||
rr.GetComponents(pooled);
|
||||
foreach (var component in pooled) {
|
||||
if (component is Transform)
|
||||
continue;
|
||||
yield return component;
|
||||
}
|
||||
}
|
||||
|
||||
var transform = rr.transform;
|
||||
var childCount = transform.childCount;
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
var g = transform.GetChild(i).gameObject;
|
||||
foreach (var res in GoAndChildComps(g))
|
||||
yield return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
#if backup
|
||||
static void InScene(SearchArg arg, Scene currentScene) {
|
||||
var allObjects = currentScene
|
||||
.GetRootGameObjects()
|
||||
.SelectMany(g => g.GetComponentsInChildren<Component>(true)
|
||||
.Where(c => c && !(c is Transform))
|
||||
.Union(Enumerable.Repeat(g as Object, 1))
|
||||
)
|
||||
.ToArray();
|
||||
|
||||
var total = allObjects.Length;
|
||||
for (var i = 0; i < total; i++) {
|
||||
var suspect = allObjects[i];
|
||||
|
||||
if (SearchInChildProperties(arg, suspect, true, out var entity)) {
|
||||
var s = entity.Set<InSceneResult>();
|
||||
s.ScenePath = arg.Scene.path;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static bool IsTargetOrNested(SearchArg target, Object suspect) {
|
||||
if (!suspect)
|
||||
return false;
|
||||
|
||||
if (target.Target.GetInstanceID() == suspect.GetInstanceID() || target.Main.GetInstanceID() == (suspect).GetInstanceID())
|
||||
return true;
|
||||
|
||||
if (target.SubAssets.TryGet(out var subassets))
|
||||
foreach (var asset in subassets) {
|
||||
if (asset.GetInstanceID() == (suspect).GetInstanceID())
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static string Nicify(SerializedProperty sp, Object o, string transformPath) {
|
||||
// return sp.propertyPath;
|
||||
|
||||
string nice;
|
||||
switch (o) {
|
||||
case AnimatorState animatorState:
|
||||
return animatorState.name;
|
||||
case Material material:
|
||||
nice = material.name;
|
||||
break;
|
||||
default: {
|
||||
nice = sp.propertyPath.Replace(".Array.data", string.Empty);
|
||||
if (nice.IndexOf(".m_PersistentCalls.m_Calls", StringComparison.Ordinal) > 0) {
|
||||
nice = nice.Replace(".m_PersistentCalls.m_Calls", string.Empty)
|
||||
.Replace(".m_Target", string.Empty);
|
||||
}
|
||||
|
||||
nice = nice.Split('.').Select(t => ObjectNames.NicifyVariableName(t).Replace(" ", string.Empty))
|
||||
.Aggregate((a, b) => a + "." + b);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// nice = $"{transformPath}({o.GetType().Name}).{nice}";
|
||||
nice = $"({o.GetType().Name}).{nice}";
|
||||
return nice;
|
||||
}
|
||||
|
||||
const string AssetsRootPath = "Assets/";
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
3
Assets/Asset Cleaner/Utils/SearchUtils.cs.meta
Normal file
3
Assets/Asset Cleaner/Utils/SearchUtils.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 66f3f357bedc44e2907d7e5f48de45d3
|
||||
timeCreated: 1576134438
|
||||
8
Assets/Asset Cleaner/com.leopotam.ecs.meta
Normal file
8
Assets/Asset Cleaner/com.leopotam.ecs.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cf3dfe88eca681d449c3f24f79d64b4c
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
408
Assets/Asset Cleaner/com.leopotam.ecs/README.md
Normal file
408
Assets/Asset Cleaner/com.leopotam.ecs/README.md
Normal file
@@ -0,0 +1,408 @@
|
||||
[](https://discord.gg/5GZVde6)
|
||||
[](https://github.com/Leopotam/ecs/blob/develop/LICENSE)
|
||||
# LeoECS - Simple lightweight C# Entity Component System framework
|
||||
Performance, zero/small memory allocations/footprint, no dependencies on any game engine - main goals of this project.
|
||||
|
||||
> C#7.3 or above required for this framework.
|
||||
|
||||
> Tested on unity 2019.1 (not dependent on it) and contains assembly definition for compiling to separate assembly file for performance reason.
|
||||
|
||||
> **Important!** Dont forget to use `DEBUG` builds for development and `RELEASE` builds in production: all internal error checks / exception throwing works only in `DEBUG` builds and eleminated for performance reasons in `RELEASE`.
|
||||
|
||||
|
||||
# Installation
|
||||
|
||||
## As unity module
|
||||
This repository can be installed as unity module directly from git url. In this way new line should be added to `Packages/manifest.json`:
|
||||
```
|
||||
"com.leopotam.ecs": "https://github.com/Leopotam/ecs.git",
|
||||
```
|
||||
By default last released version will be used. If you need trunk / developing version then `develop` name of branch should be added after hash:
|
||||
```
|
||||
"com.leopotam.ecs": "https://github.com/Leopotam/ecs.git#develop",
|
||||
```
|
||||
|
||||
## As unity module from npm registry (Experimental)
|
||||
This repository can be installed as unity module from external npm registry with support of different versions. In this way new block should be added to `Packages/manifest.json` right after opening `{` bracket:
|
||||
```
|
||||
"scopedRegistries": [
|
||||
{
|
||||
"name": "Leopotam",
|
||||
"url": "https://npm.leopotam.com",
|
||||
"scopes": [
|
||||
"com.leopotam"
|
||||
]
|
||||
}
|
||||
],
|
||||
```
|
||||
After this operation registry can be installed from list of packages from standard unity module manager.
|
||||
> **Important!** Url can be changed later, check actual url at `README`.
|
||||
|
||||
## As source
|
||||
If you can't / don't want to use unity modules, code can be downloaded as sources archive of required release from [Releases page](`https://github.com/Leopotam/ecs/releases`).
|
||||
|
||||
|
||||
# Main parts of ecs
|
||||
|
||||
## Component
|
||||
Container for user data without / with small logic inside. Can be used any user class without any additional inheritance:
|
||||
```csharp
|
||||
class WeaponComponent {
|
||||
public int Ammo;
|
||||
public string GunName;
|
||||
}
|
||||
```
|
||||
|
||||
> **Important!** Dont forget to manually init all fields of new added component. Default value initializers will not work due all components can be reused automatically multiple times through builtin pooling mechanism (no destroying / creating new instance for each request for performance reason).
|
||||
|
||||
> **Important!** Dont forget to cleanup reference links to instances of any classes before removing components from entity, otherwise it can lead to memory leaks.
|
||||
>
|
||||
> By default all `marshal-by-reference` typed fields of component (classes in common case) will be checked for null on removing attempt in `DEBUG`-mode. If you know that you have object instance that should be not null (preinited collections for example) - `[EcsIgnoreNullCheck]` attribute can be used for disabling these checks.
|
||||
|
||||
## Entity
|
||||
Сontainer for components. Implemented as `EcsEntity` for wrapping internal identifiers:
|
||||
```csharp
|
||||
EcsEntity entity = _world.NewEntity ();
|
||||
Component1 c1 = entity.Set<Component1> ();
|
||||
Component2 c2 = entity.Set<Component2> ();
|
||||
```
|
||||
There are some helpers to simplify creation of entity with multiple components:
|
||||
```csharp
|
||||
EcsEntity entity = _world.NewEntityWith<Component1, Component2> (out Component1 c1, out Component2 c2);
|
||||
```
|
||||
|
||||
> **Important!** Entities without components on them will be automatically removed on last `EcsEntity.Unset()` call.
|
||||
|
||||
## System
|
||||
Сontainer for logic for processing filtered entities. User class should implements `IEcsInitSystem`, `IEcsDestroySystem`, `IEcsRunSystem` (or other supported) interfaces:
|
||||
```csharp
|
||||
class WeaponSystem : IEcsPreInitSystem, IEcsInitSystem, IEcsDestroySystem, IEcsPreDestroySystem {
|
||||
public void PreInit () {
|
||||
// Will be called once during EcsSystems.Init() call and before IEcsInitSystem.Init.
|
||||
}
|
||||
|
||||
public void Init () {
|
||||
// Will be called once during EcsSystems.Init() call.
|
||||
}
|
||||
|
||||
public void Destroy () {
|
||||
// Will be called once during EcsSystems.Destroy() call.
|
||||
}
|
||||
|
||||
public void AfterDestroy () {
|
||||
// Will be called once during EcsSystems.Destroy() call and after IEcsDestroySystem.Destroy.
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```csharp
|
||||
class HealthSystem : IEcsRunSystem {
|
||||
public void Run () {
|
||||
// Will be called on each EcsSystems.Run() call.
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
# Data injection
|
||||
All compatible `EcsWorld` and `EcsFilter<T>` fields of ecs-system will be auto-initialized (auto-injected):
|
||||
```csharp
|
||||
class HealthSystem : IEcsSystem {
|
||||
// auto-injected fields.
|
||||
EcsWorld _world = null;
|
||||
EcsFilter<WeaponComponent> _weaponFilter = null;
|
||||
}
|
||||
```
|
||||
Instance of any custom type can be injected to all systems through `EcsSystems.Inject()` method:
|
||||
```csharp
|
||||
var systems = new EcsSystems (world)
|
||||
.Add (new TestSystem1 ())
|
||||
.Add (new TestSystem2 ())
|
||||
.Add (new TestSystem3 ())
|
||||
.Inject (a)
|
||||
.Inject (b)
|
||||
.Inject (c)
|
||||
.Inject (d);
|
||||
systems.Init ();
|
||||
```
|
||||
Each system will be scanned for compatible fields (can contains all of them or no one) with proper initialization.
|
||||
|
||||
> **Important!** Data injection for any user type can be used for sharing external data between systems.
|
||||
|
||||
## Data Injection with multiple EcsSystems
|
||||
|
||||
If you want to use multiple `EcsSystems` you can find strange behaviour with DI:
|
||||
|
||||
```csharp
|
||||
class Component1 { }
|
||||
|
||||
class System1 : IEcsInitSystem {
|
||||
EcsWorld _world = null;
|
||||
|
||||
public void Init () {
|
||||
_world.NewEntity ().Set<Component1> ();
|
||||
}
|
||||
}
|
||||
|
||||
class System2 : IEcsInitSystem {
|
||||
EcsFilter<Component1> _filter = null;
|
||||
|
||||
public void Init () {
|
||||
Debug.Log (_filter.GetEntitiesCount ());
|
||||
}
|
||||
}
|
||||
|
||||
var systems1 = new EcsSystems (world);
|
||||
var systems2 = new EcsSystems (world);
|
||||
systems1.Add (new System1 ());
|
||||
systems2.Add (new System2 ());
|
||||
systems1.Init ();
|
||||
systems2.Init ();
|
||||
```
|
||||
You will get "0" at console. Problem is that DI starts at `Init` method inside each `EcsSystems`. It means that any new `EcsFilter` instance (with lazy initialization) will be correctly injected only at current `EcsSystems`.
|
||||
|
||||
To fix this behaviour startup code should be modified in this way:
|
||||
|
||||
```csharp
|
||||
var systems1 = new EcsSystems (world);
|
||||
var systems2 = new EcsSystems (world);
|
||||
systems1.Add (new System1 ());
|
||||
systems2.Add (new System2 ());
|
||||
systems1.ProcessInjects ();
|
||||
systems2.ProcessInjects ();
|
||||
systems1.Init ();
|
||||
systems2.Init ();
|
||||
```
|
||||
You should get "1" at console after fix.
|
||||
|
||||
# Special classes
|
||||
|
||||
## EcsFilter<T>
|
||||
Container for keeping filtered entities with specified component list:
|
||||
```csharp
|
||||
class WeaponSystem : IEcsInitSystem, IEcsRunSystem {
|
||||
// auto-injected fields: EcsWorld instance and EcsFilter.
|
||||
EcsWorld _world = null;
|
||||
// We wants to get entities with "WeaponComponent" and without "HealthComponent".
|
||||
EcsFilter<WeaponComponent>.Exclude<HealthComponent> _filter = null;
|
||||
|
||||
public void Init () {
|
||||
// new C# syntax can be used if component instance not required right now.
|
||||
_world.NewEntityWith<WeaponComponent> (out _);
|
||||
}
|
||||
|
||||
public void Run () {
|
||||
foreach (var i in _filter) {
|
||||
// entity that contains WeaponComponent.
|
||||
// Performance hint: use 'ref' prefixes for disable copying entity structure.
|
||||
// Important: dont use `ref` on filter data outside of foreach-loop over this filter.
|
||||
ref var entity = ref _filter.Entities[i];
|
||||
|
||||
// Get1 will return link to attached "WeaponComponent".
|
||||
var weapon = _filter.Get1[i];
|
||||
weapon.Ammo = System.Math.Max (0, weapon.Ammo - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
> **Important!** You should not use `ref` modifier for any filter data outside of foreach-loop over this filter if you want to destroy part of this data (entity or component) - it will break memory integrity.
|
||||
|
||||
All components from filter `Include` constraint can be fast accessed through `filter.Get1()`, `filter.Get2()`, etc - in same order as they were used in filter type declaration.
|
||||
|
||||
If fast access not required (for example, for flag-based components without data), component can implements `IEcsIgnoreInFilter` interface for decrease memory usage and increase performance:
|
||||
```csharp
|
||||
class Component1 { }
|
||||
|
||||
class Component2 : IEcsIgnoreInFilter { }
|
||||
|
||||
class TestSystem : IEcsRunSystem {
|
||||
EcsFilter<Component1, Component2> _filter = null;
|
||||
|
||||
public void Run () {
|
||||
foreach (var i in _filter) {
|
||||
// its valid code.
|
||||
var component1 = _filter.Get1[i];
|
||||
|
||||
// its invalid code due to cache for _filter.Get2[] is null for memory / performance reasons.
|
||||
var component2 = _filter.Get2[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> Important: Any filter supports up to 4 component types as "include" constraints and up to 2 component types as "exclude" constraints. Shorter constraints - better performance.
|
||||
|
||||
> Important: If you will try to use 2 filters with same components but in different order - you will get exception with detailed info about conflicted types, but only in `DEBUG` mode. In `RELEASE` mode all checks will be skipped.
|
||||
|
||||
## EcsWorld
|
||||
Root level container for all entities / components, works like isolated environment.
|
||||
|
||||
> Important: Do not forget to call `EcsWorld.Destroy()` method when instance will not be used anymore.
|
||||
|
||||
## EcsSystems
|
||||
Group of systems to process `EcsWorld` instance:
|
||||
```csharp
|
||||
class Startup : MonoBehaviour {
|
||||
EcsWorld _world;
|
||||
EcsSystems _systems;
|
||||
|
||||
void Start () {
|
||||
// create ecs environment.
|
||||
_world = new EcsWorld ();
|
||||
_systems = new EcsSystems (_world)
|
||||
.Add (new WeaponSystem ());
|
||||
_systems.Init ();
|
||||
}
|
||||
|
||||
void Update () {
|
||||
// process all dependent systems.
|
||||
_systems.Run ();
|
||||
_world.EndFrame ();
|
||||
}
|
||||
|
||||
void OnDestroy () {
|
||||
// destroy systems logical group.
|
||||
_systems.Destroy ();
|
||||
// destroy world.
|
||||
_world.Destroy ();
|
||||
}
|
||||
}
|
||||
```
|
||||
> Important: Do not forget to call `EcsWorld.EndFrame()` method when all `EcsSystems` completed.
|
||||
|
||||
`EcsSystems` instance can be used as nested system (any types of `IEcsInitSystem`, `IEcsRunSystem`, ecs behaviours are supported):
|
||||
```csharp
|
||||
// initialization.
|
||||
var nestedSystems = new EcsSystems (_world).Add (new NestedSystem ());
|
||||
// dont call nestedSystems.Init() here, rootSystems will do it automatically.
|
||||
|
||||
var rootSystems = new EcsSystems (_world).Add (nestedSystems);
|
||||
rootSystems.Init ();
|
||||
|
||||
// update loop.
|
||||
// dont call nestedSystems.Run() here, rootSystems will do it automatically.
|
||||
rootSystems.Run ();
|
||||
|
||||
// destroying.
|
||||
// dont call nestedSystems.Destroy() here, rootSystems will do it automatically.
|
||||
rootSystems.Destroy ();
|
||||
```
|
||||
|
||||
Any `IEcsRunSystem` or `EcsSystems` instance can be enabled or disabled from processing in runtime:
|
||||
```csharp
|
||||
class TestSystem : IEcsRunSystem {
|
||||
public void Run () { }
|
||||
}
|
||||
var systems = new EcsSystems (_world);
|
||||
systems.Add (new TestSystem (), "my special system");
|
||||
systems.Init ();
|
||||
var idx = systems.GetNamedRunSystem ("my special system");
|
||||
|
||||
// state will be true here, all systems are active by default.
|
||||
var state = systems.GetRunSystemState (idx);
|
||||
|
||||
// disable system from execution.
|
||||
systems.SetRunSystemState (idx, false);
|
||||
```
|
||||
|
||||
# Examples
|
||||
##With sources:
|
||||
* [Snake game](https://github.com/Leopotam/ecs-snake)
|
||||
* [Pacman game](https://github.com/SH42913/pacmanecs)
|
||||
* [GTA5 custom wounds mod](https://github.com/SH42913/gunshotwound3)
|
||||
* [Ecs Hybrid Unity integration](https://github.com/SH42913/leoecshybrid)
|
||||
|
||||
##Without sources:
|
||||
* [Hattori2 game](https://www.instagram.com/hattorigame/)
|
||||
* [Natives game](https://alex-kpojb.itch.io/natives-ecs)
|
||||
* [PrincessRun android game](https://play.google.com/store/apps/details?id=ru.zlodey.princessrun)
|
||||
* [TowerRunner Revenge android game](https://play.google.com/store/apps/details?id=ru.zlodey.towerrunner20)
|
||||
* [HypnoTap android game](https://play.google.com/store/apps/details?id=com.ZlodeyStudios.HypnoTap)
|
||||
* [Elves-vs-Dwarfs game](https://globalgamejam.org/2019/games/elves-vs-dwarfs)
|
||||
|
||||
# Extensions
|
||||
* [Unity editor integration](https://github.com/Leopotam/ecs-unityintegration)
|
||||
* [Unity uGui events support](https://github.com/Leopotam/ecs-ui)
|
||||
* [Multi-threading support](https://github.com/Leopotam/ecs-threads)
|
||||
* [Reactive systems](https://github.com/Leopotam/ecs-reactive)
|
||||
* [Engine independent types](https://github.com/Leopotam/ecs-types)
|
||||
|
||||
# License
|
||||
The software released under the terms of the [MIT license](./LICENSE.md). Enjoy.
|
||||
|
||||
# Donate
|
||||
Its free opensource software, but you can buy me a coffee:
|
||||
|
||||
<a href="https://www.buymeacoffee.com/leopotam" target="_blank"><img src="https://www.buymeacoffee.com/assets/img/custom_images/yellow_img.png" alt="Buy Me A Coffee" style="height: auto !important;width: auto !important;" ></a>
|
||||
|
||||
# FAQ
|
||||
|
||||
### I want to process one system at MonoBehaviour.Update() and another - at MonoBehaviour.FixedUpdate(). How I can do it?
|
||||
|
||||
For splitting systems by `MonoBehaviour`-method multiple `EcsSystems` logical groups should be used:
|
||||
```csharp
|
||||
EcsSystems _update;
|
||||
EcsSystems _fixedUpdate;
|
||||
|
||||
void Start () {
|
||||
var world = new EcsWorld ();
|
||||
_update = new EcsSystems (world).Add (new UpdateSystem ());
|
||||
_update.Init ();
|
||||
_fixedUpdate = new EcsSystems (world).Add (new FixedUpdateSystem ());
|
||||
_fixedUpdate.Init ();
|
||||
}
|
||||
|
||||
void Update () {
|
||||
_update.Run ();
|
||||
}
|
||||
|
||||
void FixedUpdate () {
|
||||
_fixedUpdate.Run ();
|
||||
}
|
||||
```
|
||||
|
||||
### I like how dependency injection works, but i want to skip some fields from initialization. How I can do it?
|
||||
|
||||
You can use `[EcsIgnoreInject]` attribute on any field of system:
|
||||
```csharp
|
||||
...
|
||||
// will be injected.
|
||||
EcsFilter<C1> _filter1 = null;
|
||||
|
||||
// will be skipped.
|
||||
[EcsIgnoreInject]
|
||||
EcsFilter<C2> _filter2 = null;
|
||||
```
|
||||
|
||||
### I do not like foreach-loops, I know that for-loops are faster. How I can use it?
|
||||
|
||||
Current implementation of foreach-loop fast enough (custom enumerator, no memory allocation), small performance differences can be found on 10k items and more. Current version doesnt support for-loop iterations anymore.
|
||||
|
||||
### I copy&paste my reset components code again and again. How I can do it in other manner?
|
||||
|
||||
If you want to simplify your code and keep reset-code in one place, you can use `IEcsAutoReset` interface for components:
|
||||
```csharp
|
||||
class MyComponent : IEcsAutoReset {
|
||||
public object LinkToAnotherComponent;
|
||||
|
||||
public void Reset() {
|
||||
// Cleanup all marshal-by-reference fields here.
|
||||
LinkToAnotherComponent = null;
|
||||
}
|
||||
}
|
||||
```
|
||||
This method will be automatically called after component removing from entity and before recycling to component pool.
|
||||
|
||||
|
||||
### I use components as events that works only one frame, then remove it at last system in execution sequence. It's boring, how I can automate it?
|
||||
|
||||
If you want to remove one-frame components without additional custom code, you can implement `IEcsOneFrame` interface:
|
||||
```csharp
|
||||
class MyComponent : IEcsOneFrame { }
|
||||
```
|
||||
> Important: Do not forget to call `EcsWorld.EndFrame()` method once after all `EcsSystems.Run` calls.
|
||||
|
||||
> Important: Do not forget that if one-frame component contains `marshal-by-reference` typed fields - this component should implements `IEcsAutoReset` interface.
|
||||
|
||||
### I need more than 4 components in filter, how i can do it?
|
||||
|
||||
Check `EcsFilter<Inc1, Inc2, Inc3, Inc4>` type source, copy&paste it to your project and add additional components support in same manner.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user