Space Ad Mobile 9.5.1
Space Ad Mobile makes ads easy to add into your mobile game: one unified workflow for LevelPlay, AdMob, Yandex.Ads across banners, interstitials and rewarded.
Loading...
Searching...
No Matches
AdAggregatorResource.cs
1using System;
2using System.Collections.Generic;
3using System.IO;
4using System.Linq;
5using UnityEditor;
6using UnityEngine;
7using Object = UnityEngine.Object;
8
9namespace SCG.SpaceAdMobile
10{
14 // For Core Developers Only
15 // [CreateAssetMenu(fileName = nameof(AdAggregatorResource), menuName = nameof(SCG) + "/" + "Create " + nameof(AdAggregatorResource))]
16 public class AdAggregatorResource : ScriptableObject
17 {
18 #region Validation sync
19
20#if UNITY_6000_4_OR_NEWER
21 private static readonly HashSet<EntityId> pendingValidationInstanceIds = new();
22#else
23 private static readonly HashSet<int> pendingValidationInstanceIds = new();
24#endif
25 private static bool isSynchronizingAggregators;
26
27 #endregion
28
33 {
34 get
35 {
36 if (instance != null) return instance;
37
38 instance = Resources.Load<AdAggregatorResource>(nameof(AdAggregatorResource));
39 return instance;
40 }
41 }
42 private static AdAggregatorResource instance;
43
45 public const string KeyDeleteOtherFolders = nameof(SpaceAdMobile) + "." + nameof(DeleteOtherFolders);
47 public const string KeyDeleteDemo = nameof(SpaceAdMobile) + "." + nameof(DeleteDemo);
49 public const string KeyDeleteDocumentation = nameof(SpaceAdMobile) + "." + nameof(DeleteDocumentation);
52
54 public bool IsFreeVersion;
55
57 public bool DeleteOtherFolders;
59 public bool DeleteDemo;
62
64 [Tooltip("Controls whether enhanced Yandex editor dummies are generated and applied.")]
66
68 [Tooltip("Folder names to exclude when scanning for scripts (matched by path segments, case-insensitive).")]
69 public string[] ExcludedFolderNames =
70 {
71 "Prefabs",
72 "Democumentation", "Demo", "Documentation",
73 "Data"
74 };
75
77
80
82 [TextArea(20, 50)]
83 public string WelcomeText;
84
88 protected void OnValidate()
89 {
90#if !UPM_PACKAGE && UNITY_ASTOOLS_EXPERIMENTAL
91 if (!this || Aggregators == null)
92 return;
93
94 // AssetDatabase queries are unsafe while the editor is refreshing/importing assets.
95 if (EditorApplication.isUpdating || EditorApplication.isCompiling)
96 {
97#if UNITY_6000_4_OR_NEWER
98 ScheduleSynchronization(GetEntityId());
99#else
100 ScheduleSynchronization(GetInstanceID());
101#endif
102 return;
103 }
104
105 SynchronizeAggregators();
106#endif
107
108#if LEGACY_YANDEX_MOBILE_ADS
110#endif
111 }
112
116 protected virtual void OnDestroy() => instance = null;
117
118 #region Validation sync
119
120 private void SynchronizeAggregators()
121 {
122 if (isSynchronizingAggregators || Aggregators == null)
123 return;
124
125 isSynchronizingAggregators = true;
126
127 try
128 {
129 var anyChanged = false;
130
131 foreach (var aggregator in Aggregators)
132 {
133 var newList = CollectHelper.CollectScriptsForAggregator(aggregator, ExcludedFolderNames);
134 var newPrefabs = CollectHelper.CollectPrefabsForAggregator(aggregator);
135
136 if (!CollectHelper.ArraysEqual(aggregator.Migrations, newList))
137 {
138 aggregator.Migrations = newList;
139 anyChanged = true;
140 }
141
142 if (!CollectHelper.ArraysEqual(aggregator.Prefabs, newPrefabs))
143 {
144 aggregator.Prefabs = newPrefabs;
145 anyChanged = true;
146 }
147 }
148
149 if (!anyChanged)
150 return;
151
152 EditorUtility.SetDirty(this);
153 AssetDatabase.SaveAssets();
154 }
155 finally
156 {
157 isSynchronizingAggregators = false;
158 }
159 }
160
161#if !UPM_PACKAGE && UNITY_ASTOOLS_EXPERIMENTAL
162#if UNITY_6000_4_OR_NEWER
163 private static void ScheduleSynchronization(EntityId entityId)
164 {
165 if (!pendingValidationInstanceIds.Add(entityId))
166 return;
167
168 EditorApplication.delayCall += () =>
169 {
170 if (EditorApplication.isUpdating || EditorApplication.isCompiling)
171 {
172 pendingValidationInstanceIds.Remove(entityId);
173 ScheduleSynchronization(entityId);
174 return;
175 }
176
177 pendingValidationInstanceIds.Remove(entityId);
178
179 var resource = EditorUtility.EntityIdToObject(entityId) as AdAggregatorResource;
180 if (!resource)
181 return;
182
183 resource.SynchronizeAggregators();
184 };
185 }
186#else
187 private static void ScheduleSynchronization(int instanceId)
188 {
189 if (!pendingValidationInstanceIds.Add(instanceId))
190 return;
191
192 EditorApplication.delayCall += () =>
193 {
194 if (EditorApplication.isUpdating || EditorApplication.isCompiling)
195 {
196 pendingValidationInstanceIds.Remove(instanceId);
197 ScheduleSynchronization(instanceId);
198 return;
199 }
200
201 pendingValidationInstanceIds.Remove(instanceId);
202
203 var resource = EditorUtility.InstanceIDToObject(instanceId) as AdAggregatorResource;
204 if (!resource)
205 return;
206
207 resource.SynchronizeAggregators();
208 };
209 }
210#endif
211#endif
212
213 #endregion
214 }
215
219 [Serializable]
220 public class Aggregator
221 {
223 public string Name;
225 public string ShortName;
227 public Texture2D Texture;
229 public string Define;
231 public Object Folder;
233 public string DemoFolderName;
234
236 public MonoScript[] Migrations;
238 public GameObject[] Prefabs;
239
241 [Tooltip("Install configuration used by the selector to detect SDK presence and open install flows.")]
243 }
244
250 public enum InstallOpenMode : byte
251 {
254
257 }
258
262 [Serializable]
264 {
266 [Tooltip("Determines how the Install action is opened for this aggregator.")]
268
270 [Tooltip("External installation URL opened when InstallMode is ExternalUrl.")]
271 public string InstallUrl;
272
274 [Tooltip("Package IDs used for install checks and Package Manager deep links.")]
275 public string[] InstallPackageIds = Array.Empty<string>();
276
278 [Tooltip("Folder names used to detect installed SDK files in Assets or Packages.")]
279 public string[] InstallFolderNames = Array.Empty<string>();
280
282 [Tooltip("Assembly names used to detect installed SDK assemblies in the editor domain.")]
283 public string[] InstallAssemblyNames = Array.Empty<string>();
284
286 [Tooltip("Optional Package Manager filter or section label for the Install action.")]
287 public string PackageManagerFilter;
288 }
289
293 [Serializable]
294 public class YandexData
295 {
297 public Object Asmdef;
299 public string AssetPath = "YandexMobileAds";
301 public string EditorPath = "YandexMobileAds/Editor";
302
304 public string MainAsmdefName = "YandexMobileAds";
306 public string EditorAsmdefName = "YandexMobileAds.Editor";
307
309 public string RemoveDefineConstraint = "YANDEX_ASMDEF_NONEXISTENT";
310
312 [TextArea(15, 30)]
313 public string AsmdefTemplate =
314@"{
315 ""name"": ""YandexMobileAds"",
316 ""rootNamespace"": ""YandexMobileAds"",
317 ""references"": [
318 ""AppMetricaAdRevenueAdapter""
319 ],
320 ""includePlatforms"": [],
321 ""excludePlatforms"": [],
322 ""allowUnsafeCode"": true,
323 ""overrideReferences"": false,
324 ""precompiledReferences"": [],
325 ""autoReferenced"": false,
326 ""defineConstraints"": [],
327 ""versionDefines"": [],
328 ""noEngineReferences"": false
329}";
330
332 [TextArea(20, 30)]
333 public string AsmdefEditorTemplate =
334@"{
335 ""name"": ""YandexMobileAds.Editor"",
336 ""rootNamespace"": """",
337 ""references"": [
338 ""YandexMobileAds""
339 ],
340 ""includePlatforms"": [
341 ""Editor""
342 ],
343 ""excludePlatforms"": [],
344 ""allowUnsafeCode"": false,
345 ""overrideReferences"": false,
346 ""precompiledReferences"": [],
347 ""autoReferenced"": true,
348 ""defineConstraints"": [
349 ""YANDEX_MOBILE_ADS""
350 ],
351 ""versionDefines"": [],
352 ""noEngineReferences"": false
353}";
354
356 [Tooltip("C# source template for the enhanced rewarded ad editor dummy.")]
357 [TextArea(20, 50)]
359
361 [Tooltip("C# source template for the enhanced interstitial ad editor dummy.")]
362 [TextArea(20, 50)]
364
366 [Tooltip("C# source template for the enhanced app-open ad editor dummy.")]
367 [TextArea(20, 50)]
368 public string AppOpenDummyTemplate;
369
371 [Tooltip("C# source template for the enhanced banner client editor dummy.")]
372 [TextArea(20, 50)]
373 public string BannerDummyTemplate;
374 }
375
376 internal static class CollectHelper
377 {
378 private static readonly StringComparer pathComparer = StringComparer.OrdinalIgnoreCase;
379
384 public static MonoScript[] CollectScriptsForAggregator(Aggregator aggregator, IList<string> excludedNames)
385 {
386 var scripts = new List<MonoScript>();
387
388 if (aggregator?.Folder == null)
389 return scripts.ToArray();
390
391 var folderPath = AssetDatabase.GetAssetPath(aggregator.Folder);
392 if (string.IsNullOrEmpty(folderPath))
393 return scripts.ToArray();
394
395 // If a file was assigned instead of a folder, use its directory.
396 if (!AssetDatabase.IsValidFolder(folderPath))
397 folderPath = Path.GetDirectoryName(folderPath)?.Replace('\\', '/') ?? folderPath;
398
399 if (string.IsNullOrEmpty(folderPath) || !AssetDatabase.IsValidFolder(folderPath))
400 return scripts.ToArray();
401
402 var guids = AssetDatabase.FindAssets("t:MonoScript", new[] { folderPath });
403 scripts.AddRange(
404 from guid
405 in guids
406 select AssetDatabase.GUIDToAssetPath(guid)
407 into path
408 where path.EndsWith(".cs", StringComparison.OrdinalIgnoreCase)
409 where !IsPathExcluded(path, excludedNames)
410 select AssetDatabase.LoadAssetAtPath<MonoScript>(path)
411 into ms
412 where ms != null
413 select ms);
414
415 // Stable order: by file name, then by full path.
416 return scripts
417 .OrderBy(ms => Path.GetFileNameWithoutExtension(AssetDatabase.GetAssetPath(ms)), pathComparer)
418 .ThenBy(AssetDatabase.GetAssetPath, pathComparer)
419 .ToArray();
420 }
421
426 public static GameObject[] CollectPrefabsForAggregator(Aggregator aggregator, string prefabsFolderName = "Prefabs")
427 {
428 // Return empty if folder ref is missing
429 if (aggregator?.Folder == null)
430 return Array.Empty<GameObject>();
431
432 // Resolve base folder path
433 var basePath = AssetDatabase.GetAssetPath(aggregator.Folder);
434 if (string.IsNullOrEmpty(basePath))
435 return Array.Empty<GameObject>();
436
437 // If a file was assigned, use its directory
438 if (!AssetDatabase.IsValidFolder(basePath))
439 basePath = Path.GetDirectoryName(basePath)?.Replace('\\', '/') ?? basePath;
440
441 if (string.IsNullOrEmpty(basePath) || !AssetDatabase.IsValidFolder(basePath))
442 return Array.Empty<GameObject>();
443
444 // Build "<base>/Prefabs" path
445 var prefabsPath = $"{basePath.TrimEnd('/')}/{prefabsFolderName}";
446
447 // If direct subfolder is missing, try to locate any subfolder named exactly "prefabsFolderName"
448 if (!AssetDatabase.IsValidFolder(prefabsPath))
449 {
450 // Search for folders under basePath and pick the first that matches by last segment
451 var folderGuids = AssetDatabase.FindAssets("t:Folder", new[] { basePath });
452 var match = folderGuids
453 .Select(AssetDatabase.GUIDToAssetPath)
454 .FirstOrDefault(p => pathComparer.Equals(Path.GetFileName(p.TrimEnd('/')), prefabsFolderName));
455
456 if (match == null || !AssetDatabase.IsValidFolder(match))
457 return Array.Empty<GameObject>();
458
459 prefabsPath = match;
460 }
461
462 // Find prefabs only inside the resolved Prefabs folder
463 var guids = AssetDatabase.FindAssets("t:Prefab", new[] { prefabsPath });
464
465 var list = new List<GameObject>(guids.Length);
466 list.AddRange(
467 from guid
468 in guids
469 select AssetDatabase.GUIDToAssetPath(guid)
470 into path
471 where path.EndsWith(".prefab", StringComparison.OrdinalIgnoreCase)
472 select AssetDatabase.LoadAssetAtPath<GameObject>(path)
473 into go
474 where go != null
475 select go);
476
477 // Stable order: by name, then by full path
478 return list
479 .OrderBy(go => go.name, pathComparer)
480 .ThenBy(AssetDatabase.GetAssetPath, pathComparer)
481 .ToArray();
482 }
483
487 private static bool IsPathExcluded(string assetPath, IList<string> excludedNames)
488 {
489 if (excludedNames == null || excludedNames.Count == 0) return false;
490
491 var normalized = assetPath.Replace('\\', '/');
492 var segments = normalized.Split('/');
493
494 // Check only directory segments, not the file name (last segment).
495 for (var i = 0; i < segments.Length - 1; i++)
496 {
497 var seg = segments[i];
498 if (excludedNames.Any(t => pathComparer.Equals(seg, t)))
499 {
500 return true;
501 }
502 }
503 return false;
504 }
505
506 public static bool ArraysEqual<T>(T[] a, T[] b) where T : Object
507 {
508 if (ReferenceEquals(a, b)) return true;
509 if (a == null || b == null) return false;
510 if (a.Length != b.Length) return false;
511 return !a.Where((t, i) => t != b[i]).Any();
512 }
513 }
514}
const string KeyDeleteOtherFolders
Specifies the key delete other folders.
bool IsFreeVersion
Specifies the is free version.
void OnValidate()
Validates serialized configuration in the Unity Editor.
Aggregator[] Aggregators
Specifies the aggregators.
const string KeyDeleteDemo
Specifies the key delete demo.
string WelcomeText
Specifies the welcome text.
YandexData Yandex
Configuration used to prepare Yandex Mobile Ads assemblies.
bool UseEnhancedYandexEditorDummies
Controls whether enhanced Yandex editor dummies are generated and applied.
const string KeyUseEnhancedYandexEditorDummies
Specifies the key use enhanced yandex editor dummies.
bool DeleteOtherFolders
Controls whether folders for unselected ad aggregators are deleted.
virtual void OnDestroy()
Releases resources during Unity's destroy callback.
const string KeyDeleteDocumentation
Specifies the key delete documentation.
bool DeleteDocumentation
Controls whether documentation content is deleted during package preparation.
static AdAggregatorResource Instance
Singleton Resource instance.
string[] ExcludedFolderNames
Folder names to exclude when scanning for scripts (matched by path segments, case-insensitive).
bool DeleteDemo
Controls whether demo content is deleted during package preparation.
Provides ad aggregator resource functionality.
string Name
Specifies the name.
GameObject[] Prefabs
Specifies the prefabs.
string Define
Specifies the define.
AggregatorInstallResource Install
Install configuration used by the selector to detect SDK presence and open install flows.
string ShortName
Specifies the short name.
string DemoFolderName
Specifies the demo folder name.
Object Folder
Specifies the folder.
Texture2D Texture
Specifies the texture.
MonoScript[] Migrations
Specifies the migrations.
Provides aggregator functionality.
string[] InstallPackageIds
Package IDs used for install checks and Package Manager deep links.
string[] InstallFolderNames
Folder names used to detect installed SDK files in Assets or Packages.
InstallOpenMode InstallMode
Determines how the Install action is opened for this aggregator.
string[] InstallAssemblyNames
Assembly names used to detect installed SDK assemblies in the editor domain.
string PackageManagerFilter
Optional Package Manager filter or section label for the Install action.
string InstallUrl
External installation URL opened when InstallMode is ExternalUrl.
Provides aggregator install resource functionality.
Represents a mobile ad manager component specifically designed for integration with Google AdMob serv...
string AsmdefEditorTemplate
Specifies the asmdef editor template.
string MainAsmdefName
Specifies the main asmdef name.
string BannerDummyTemplate
C# source template for the enhanced banner client editor dummy.
string RewardedDummyTemplate
C# source template for the enhanced rewarded ad editor dummy.
string AssetPath
Specifies the asset path.
string AsmdefTemplate
Specifies the asmdef template.
string EditorPath
Specifies the editor path.
string EditorAsmdefName
Specifies the editor asmdef name.
string RemoveDefineConstraint
Specifies the remove define constraint.
Object Asmdef
Specifies the asmdef.
string AppOpenDummyTemplate
C# source template for the enhanced app-open ad editor dummy.
string InterstitialDummyTemplate
C# source template for the enhanced interstitial ad editor dummy.
Provides yandex data functionality.
InstallOpenMode
Defines the target destination for the Install action in the aggregator selector. External URL opens ...
@ PackageManager
Open Unity Package Manager and fallback to URL when unavailable.
@ ExternalUrl
Open the configured external URL in a browser.