-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathtest_cache_sweep.gd
More file actions
131 lines (106 loc) · 4.88 KB
/
Copy pathtest_cache_sweep.gd
File metadata and controls
131 lines (106 loc) · 4.88 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
## Cached character meshes are named by content hash, so every part change writes a new
## entry and abandons the old one. This covers the sweep that reclaims them, and in
## particular that it never removes an entry a scene - saved or still only in memory -
## is relying on. Run headless:
## godot --headless -s tests/test_cache_sweep.gd
extends SceneTree
const CACHE := "user://test_cache_sweep"
var _failures := 0
func _init() -> void:
ProjectSettings.set_setting("kickdot/build/combine_meshes", true)
ProjectSettings.set_setting(SKResourceCache.SETTING_CACHE_DIR, CACHE)
call_deferred("_run")
func _run() -> void:
_reset()
var library := SKPartLibrary.new()
library.scan()
var builder := SKCharacterBuilder.new(library, null)
var recipe := SKRecipe.load_from_file("res://synty_assets/characters/Starter_01.sk")
var character := builder.build(recipe)
root.add_child(character)
_persist(character)
var first := _cache_files()
_check(first.size() >= 2, "building a character caches a mesh and a skin (%d files)" % first.size())
# Change a part: a new entry appears and the old one is now garbage.
var edited := SKRecipe.load_from_file("res://synty_assets/characters/Starter_02.sk")
builder.rebuild_into(character, edited)
_persist(character)
var grown := _cache_files()
_check(grown.size() > first.size(),
"editing leaves the previous entry behind (%d -> %d files)" % [first.size(), grown.size()])
# Sweeping while the character is live must keep exactly what it uses.
var live_paths := _live_paths(character)
_check(not live_paths.is_empty(), "live character reports its cached resources")
var result := SKResourceCache.sweep_unused([character] as Array[Node])
var kept := _cache_files()
_check(int(result.removed) > 0, "sweep removed the abandoned entries (%d)" % int(result.removed))
var missing: Array[String] = []
for path: String in live_paths:
if not FileAccess.file_exists(path):
missing.append(path.get_file())
_check(missing.is_empty(), "sweep kept everything the live character uses (lost: %s)" % str(missing))
_check(kept.size() < grown.size(), "cache shrank (%d -> %d files)" % [grown.size(), kept.size()])
# With nothing live and nothing saved referencing them, the rest go too.
character.free()
SKResourceCache.sweep_unused()
_check(_cache_files().is_empty(), "an unreferenced cache sweeps empty (%d left)" % _cache_files().size())
_test_self_repair()
_reset()
print("CACHE SWEEP: %s" % ("ALL PASSED" if _failures == 0 else "%d FAILURES" % _failures))
quit(0 if _failures == 0 else 1)
## A cache entry deleted behind the project's back must come back: needs_build() treats
## an unloadable mesh as a reason to rebuild, and the rebuild re-writes the cache. Entry
## names are content hashes, so the restored files carry the very names a saved scene is
## still referencing.
func _test_self_repair() -> void:
_reset()
ProjectSettings.set_setting("kickdot/build/own_generated_meshes", true)
var recipe := SKRecipe.load_from_file("res://synty_assets/characters/Starter_01.sk")
var character := SidekickCharacter.new()
character.recipe_text = recipe.to_text()
root.add_child(character) # _enter_tree -> rebuild()
var built := _cache_files()
_check(built.size() >= 2, "a character entering the tree fills the cache (%d files)" % built.size())
var dir := DirAccess.open(CACHE)
for file in built:
dir.remove(file)
_check(_cache_files().is_empty(), "cache emptied behind the project's back")
character.rebuild()
var restored := _cache_files()
var identical := true
for file in built:
if not restored.has(file):
identical = false
_check(identical, "rebuild restores the same cache filenames (%s)" % str(restored))
character.free()
## Mirrors what the dock does after a rebuild: give every generated resource a file.
func _persist(character: Node) -> void:
for child in character.get_children():
if child is Skeleton3D:
for mesh_instance in child.get_children():
if mesh_instance is MeshInstance3D:
SKResourceCache.persist_mesh_instance(mesh_instance)
func _live_paths(character: Node) -> Array[String]:
var paths: Array[String] = []
for child in character.get_children():
if child is Skeleton3D:
for mesh_instance in child.get_children():
if mesh_instance is MeshInstance3D:
for resource: Resource in [(mesh_instance as MeshInstance3D).mesh,
(mesh_instance as MeshInstance3D).skin]:
if resource != null and resource.resource_path.begins_with(CACHE):
paths.append(resource.resource_path)
return paths
func _cache_files() -> PackedStringArray:
var dir := DirAccess.open(CACHE)
return dir.get_files() if dir != null else PackedStringArray()
func _reset() -> void:
var dir := DirAccess.open(CACHE)
if dir != null:
for file in dir.get_files():
dir.remove(file)
DirAccess.make_dir_recursive_absolute(CACHE)
func _check(ok: bool, what: String) -> void:
if not ok:
_failures += 1
print(" %s %s" % ["ok " if ok else "FAIL", what])