Selaa lähdekoodia

resource: bring resources online() in the correct order

Daniele Bartolini 1 vuosi sitten
vanhempi
sitoutus
85247974fa

+ 1 - 0
docs/changelog.rst

@@ -13,6 +13,7 @@ Changelog
 * Fixed intra-frame button press/release detection.
 * Added ``--hidden`` CLI option.
 * Fixed HashMap and HashSet.
+* Packages will now bring resources online in the correct order. This enables runtime optimizations and features previously impossible to have.
 * Windows: fixed resolution property from boot.config not being honored.
 
 **Tools**

+ 1 - 1
src/device/device.cpp

@@ -490,7 +490,7 @@ void Device::run()
 
 		const StringId64 config_name(boot_dir.c_str());
 
-		while (!_resource_manager->try_load(PACKAGE_RESOURCE_NONE, RESOURCE_TYPE_CONFIG, config_name)) {
+		while (!_resource_manager->try_load(PACKAGE_RESOURCE_NONE, RESOURCE_TYPE_CONFIG, config_name, 0)) {
 			_resource_manager->complete_requests();
 #if CROWN_PLATFORM_EMSCRIPTEN
 			os::sleep(16);

+ 32 - 4
src/resource/package_resource.cpp

@@ -48,6 +48,7 @@ bool operator==(const ResourceOffset &a, const ResourceOffset &b)
 namespace package_resource_internal
 {
 	s32 bring_in_requirements(HashSet<ResourceOffset> &output
+		, u32 *graph_level
 		, CompileOptions &opts
 		, ResourceId res_id
 		)
@@ -55,6 +56,9 @@ namespace package_resource_internal
 		const HashMap<DynamicString, u32> reqs_deffault(default_allocator());
 		const HashMap<DynamicString, u32> &reqs = hash_map::get(opts._data_compiler._data_requirements, res_id, reqs_deffault);
 
+		u32 max_graph_level = 0;
+		u32 cur_graph_level;
+
 		auto cur = hash_map::begin(reqs);
 		auto end = hash_map::end(reqs);
 		for (; cur != end; ++cur) {
@@ -67,14 +71,19 @@ namespace package_resource_internal
 			const StringId64 req_type_hash(req_type);
 			const StringId64 req_name_hash(req_filename, req_name_len);
 
+			cur_graph_level = 0;
+			bring_in_requirements(output, &cur_graph_level, opts, resource_id(req_type_hash, req_name_hash));
+			max_graph_level = max(max_graph_level, cur_graph_level + 1);
+
 			ResourceOffset ro;
 			ro.type = req_type_hash;
 			ro.name = req_name_hash;
+			ro.online_order = cur_graph_level;
+			ro._pad = 0;
 			hash_set::insert(output, ro);
-
-			bring_in_requirements(output, opts, resource_id(req_type_hash, req_name_hash));
 		}
 
+		*graph_level = max_graph_level;
 		return 0;
 	}
 
@@ -98,10 +107,14 @@ namespace package_resource_internal
 			ResourceOffset ro;
 			ro.type = type_hash;
 			ro.name = sjson::parse_resource_name(names[i]);
-			hash_set::insert(output, ro);
 
 			// Bring in requirements
-			bring_in_requirements(output, opts, resource_id(ro.type, ro.name));
+			u32 graph_level = 0;
+			bring_in_requirements(output, &graph_level, opts, resource_id(ro.type, ro.name));
+
+			ro.online_order = graph_level;
+			ro._pad = 0;
+			hash_set::insert(output, ro);
 		}
 
 		return 0;
@@ -190,6 +203,17 @@ namespace package_resource_internal
 			array::push_back(resources, *cur);
 		}
 
+		// Generate sequential online order numbers.
+		Array<ResourceOffset> original_resources = resources;
+		u32 online_order = 0;
+		for (u32 i = 0; i < array::size(resources); ++i) {
+			for (u32 j = 0; j < array::size(resources); ++j) {
+				if (original_resources[j].online_order == i)
+					resources[j].online_order = online_order++;
+			}
+		}
+		DATA_COMPILER_ENSURE(online_order == array::size(resources), opts);
+
 		// Write
 		opts.write(RESOURCE_HEADER(RESOURCE_VERSION_PACKAGE));
 		opts.write(array::size(resources));
@@ -222,6 +246,8 @@ namespace package_resource_internal
 				opts.write(resources[ii].name);
 				opts.write(data_offset);
 				opts.write(data_size);
+				opts.write(resources[ii].online_order);
+				opts.write(resources[ii]._pad);
 			}
 
 			// Write bundled data.
@@ -233,6 +259,8 @@ namespace package_resource_internal
 				opts.write(resources[ii].name);
 				opts.write(UINT32_MAX);
 				opts.write(UINT32_MAX);
+				opts.write(resources[ii].online_order);
+				opts.write(resources[ii]._pad);
 			}
 		}
 

+ 2 - 0
src/resource/package_resource.h

@@ -20,6 +20,8 @@ struct ResourceOffset
 	StringId64 name;
 	u32 offset;      ///< Relative offset from package_resource::data().
 	u32 size;
+	u32 online_order;
+	u32 _pad;
 };
 
 struct PackageResource

+ 3 - 1
src/resource/resource_loader.h

@@ -11,6 +11,7 @@
 #include "core/thread/condition_variable.h"
 #include "core/thread/mutex.h"
 #include "core/thread/spsc_queue.inl"
+#include "core/thread/mpsc_queue.inl"
 #include "core/thread/thread.h"
 #include "core/types.h"
 #include "resource/types.h"
@@ -26,6 +27,7 @@ struct ResourceRequest
 	StringId64 type;
 	StringId64 name;
 	u32 version;
+	u32 online_order;
 	LoadFunction load_function;
 	Allocator *allocator;
 	void *data;
@@ -40,7 +42,7 @@ struct ResourceLoader
 	bool _is_bundle;
 
 	SPSCQueue<ResourceRequest, 128> _requests;
-	SPSCQueue<ResourceRequest, 128> _loaded;
+	MPSCQueue<ResourceRequest, 128> _loaded;
 	HashMap<StringId64, StringId64> _fallback;
 
 	Thread _thread;

+ 95 - 22
src/resource/resource_manager.cpp

@@ -31,11 +31,33 @@ bool operator==(const ResourceManager::ResourcePair &a, const ResourceManager::R
 bool operator==(const ResourceManager::ResourceData &a, const ResourceManager::ResourceData &b)
 {
 	return a.references == b.references
+		&& a.online_sequence_num == b.online_sequence_num
+		&& a.allocator == b.allocator
 		&& a.data == b.data
 		;
 }
 
-const ResourceManager::ResourceData ResourceManager::ResourceData::NOT_FOUND = { UINT32_MAX, NULL, NULL };
+bool operator!=(const ResourceManager::ResourceData &a, const ResourceManager::ResourceData &b)
+{
+	return !(a == b);
+}
+
+const ResourceManager::ResourceData ResourceManager::ResourceData::NOT_FOUND = { UINT32_MAX, 0u, NULL, NULL };
+
+bool operator==(const ResourceManager::ResourceTypeData &a, const ResourceManager::ResourceTypeData &b)
+{
+	return a.version == b.version
+		&& a.load == b.load
+		&& a.online == b.online
+		&& a.offline == b.offline
+		&& a.unload == b.unload
+		;
+}
+
+bool operator!=(const ResourceManager::ResourceTypeData &a, const ResourceManager::ResourceTypeData &b)
+{
+	return !(a == b);
+}
 
 const ResourceManager::ResourceTypeData ResourceManager::ResourceTypeData::NOT_FOUND = { UINT32_MAX, NULL, NULL, NULL, NULL };
 
@@ -48,6 +70,24 @@ struct hash<ResourceManager::ResourcePair>
 	}
 };
 
+namespace resource_manager_internal
+{
+	void add_resource(ResourceManager &rm, StringId64 type, StringId64 name, Allocator *allocator, void *data)
+	{
+		ResourceManager::ResourceData rd;
+		rd.references = 1;
+		rd.online_sequence_num = 0;
+		rd.allocator = allocator;
+		rd.data = data;
+
+		ResourceManager::ResourcePair id = { type, name };
+		hash_map::set(rm._resources, id, rd);
+
+		rm.on_online(type, name);
+	}
+
+} // namespace resource_manager_internal
+
 ResourceManager::ResourceManager(ResourceLoader &rl)
 	: _resource_heap(default_allocator(), "resource")
 	, _loader(&rl)
@@ -71,28 +111,37 @@ ResourceManager::~ResourceManager()
 	}
 }
 
-bool ResourceManager::try_load(StringId64 package_name, StringId64 type, StringId64 name)
+bool ResourceManager::try_load(StringId64 package_name, StringId64 type, StringId64 name, u32 online_order)
 {
 	ResourcePair id = { type, name };
 	ResourceData &rd = hash_map::get(_resources, id, ResourceData::NOT_FOUND);
 
+	ResourceRequest rr;
+	rr.resource_manager = this;
+	rr.package_name = package_name;
+	rr.type = type;
+	rr.name = name;
+	rr.online_order = online_order;
+	rr.data = NULL;
+
 	if (rd == ResourceData::NOT_FOUND) {
 		ResourceTypeData rtd = hash_map::get(_types, type, ResourceTypeData::NOT_FOUND);
+		CE_ENSURE(rtd != ResourceTypeData::NOT_FOUND);
 
-		ResourceRequest rr;
-		rr.resource_manager = this;
-		rr.package_name = package_name;
-		rr.type = type;
-		rr.name = name;
+		rr.allocator = &_resource_heap;
 		rr.version = rtd.version;
 		rr.load_function = rtd.load;
-		rr.allocator = &_resource_heap;
-		rr.data = NULL;
-
 		return _loader->add_request(rr);
 	}
 
 	rd.references++;
+
+	// Push a spurious loaded resource event. This avoids blocking forever
+	// in complete_requests() by keeping the online_sequence_num updated.
+	rr.allocator = NULL;
+	rr.version = 0;
+	rr.load_function = NULL;
+	_loader->_loaded.push(rr);
 	return true;
 }
 
@@ -120,7 +169,7 @@ void ResourceManager::reload(StringId64 type, StringId64 name)
 
 	unload(type, name);
 
-	while (!try_load(PACKAGE_RESOURCE_NONE, type, name)) {
+	while (!try_load(PACKAGE_RESOURCE_NONE, type, name, 0)) {
 		complete_requests();
 	}
 
@@ -145,7 +194,7 @@ const void *ResourceManager::get(StringId64 type, StringId64 name)
 	const ResourcePair id = { type, name };
 
 	if (_autoload && !hash_map::has(_resources, id)) {
-		while (!try_load(PACKAGE_RESOURCE_NONE, type, name)) {
+		while (!try_load(PACKAGE_RESOURCE_NONE, type, name, 0)) {
 			complete_requests();
 		}
 
@@ -166,16 +215,40 @@ void ResourceManager::complete_requests()
 {
 	ResourceRequest rr;
 	while (_loader->_loaded.pop(rr)) {
-		ResourceData rd;
-		rd.references = 1;
-		rd.data = rr.data;
-		rd.allocator = rr.allocator;
-
-		ResourcePair id = { rr.type, rr.name };
-
-		hash_map::set(_resources, id, rd);
-
-		on_online(rr.type, rr.name);
+		if (rr.type == RESOURCE_TYPE_PACKAGE || rr.type == RESOURCE_TYPE_CONFIG || _autoload) {
+			// Always add packages and configs to the resource map because they never have
+			// requirements and are never required by any resource, hence no online() order
+			// constraints apply.
+			resource_manager_internal::add_resource(*this
+				, rr.type
+				, rr.name
+				, rr.allocator
+				, rr.data
+				);
+		} else {
+			ResourcePair rp { RESOURCE_TYPE_PACKAGE, rr.package_name };
+			ResourceData &pkg_data = hash_map::get(_resources, rp, ResourceData::NOT_FOUND);
+			CE_ENSURE(pkg_data != ResourceData::NOT_FOUND);
+
+			if (rr.online_order != pkg_data.online_sequence_num) {
+				// Cannot process this resource yet; we need to wait for all its requirements to be
+				// put online() first. Put the request back into the loaded queue to try again
+				// later.
+				_loader->_loaded.push(rr);
+			} else {
+				++pkg_data.online_sequence_num;
+
+				if (rr.data != NULL && rr.allocator != NULL) {
+					// If this is a non-spurious request, add it to the resource map.
+					resource_manager_internal::add_resource(*this
+						, rr.type
+						, rr.name
+						, rr.allocator
+						, rr.data
+						);
+				}
+			}
+		}
 	}
 }
 

+ 2 - 1
src/resource/resource_manager.h

@@ -36,6 +36,7 @@ struct ResourceManager
 	struct ResourceData
 	{
 		u32 references;
+		u32 online_sequence_num;
 		Allocator *allocator;
 		void *data;
 
@@ -73,7 +74,7 @@ struct ResourceManager
 	/// When the load queue is full, it may fail returning false. In such case,
 	/// you must call complete_requests() and try again later until true is returned.
 	/// Use can_get() to check whether the resource can be used.
-	bool try_load(StringId64 package_name, StringId64 type, StringId64 name);
+	bool try_load(StringId64 package_name, StringId64 type, StringId64 name, u32 online_order);
 
 	/// Unloads the resource @a type @a name.
 	void unload(StringId64 type, StringId64 name);

+ 6 - 2
src/resource/resource_package.cpp

@@ -35,7 +35,11 @@ void ResourcePackage::load()
 {
 	// Load the package resource itself.
 	if (!_package_resource_queued) {
-		_package_resource_queued = _resource_manager->try_load(PACKAGE_RESOURCE_NONE, RESOURCE_TYPE_PACKAGE, _package_resource_name);
+		_package_resource_queued = _resource_manager->try_load(PACKAGE_RESOURCE_NONE
+			, RESOURCE_TYPE_PACKAGE
+			, _package_resource_name
+			, 0
+			);
 	} else {
 		if (_package_resource == NULL) {
 			if (!_resource_manager->can_get(RESOURCE_TYPE_PACKAGE, _package_resource_name)) {
@@ -50,7 +54,7 @@ void ResourcePackage::load()
 		// resources it contains.
 		for (u32 ii = _num_resources_queued; ii < _package_resource->num_resources; ++ii) {
 			const ResourceOffset *ro = package_resource::resource_offset(_package_resource, ii);
-			if (!_resource_manager->try_load(_package_resource_name, ro->type, ro->name))
+			if (!_resource_manager->try_load(_package_resource_name, ro->type, ro->name, ro->online_order))
 				break;
 
 			++_num_resources_queued;

+ 1 - 1
src/resource/types.h

@@ -81,7 +81,7 @@ struct Platform
 #define RESOURCE_VERSION_LEVEL            (RESOURCE_VERSION_UNIT + 4) //!< Level embeds UnitResource
 #define RESOURCE_VERSION_MATERIAL         RESOURCE_VERSION(4)
 #define RESOURCE_VERSION_MESH             RESOURCE_VERSION(5)
-#define RESOURCE_VERSION_PACKAGE          RESOURCE_VERSION(6)
+#define RESOURCE_VERSION_PACKAGE          RESOURCE_VERSION(7)
 #define RESOURCE_VERSION_PHYSICS_CONFIG   RESOURCE_VERSION(2)
 #define RESOURCE_VERSION_SCRIPT           RESOURCE_VERSION(4)
 #define RESOURCE_VERSION_SHADER           RESOURCE_VERSION(12)