asset_manager.hxx 7.07 KB
Newer Older
1
2
#pragma once

3
#ifndef MIRRAGE_ASSETMANAGER_INCLUDED
4
5
6
#include "asset_manager.hpp"
#endif

Florian Oetke's avatar
Florian Oetke committed
7
#ifndef __clang_analyzer__
Florian Oetke's avatar
Florian Oetke committed
8
#include <async++.h>
Florian Oetke's avatar
Florian Oetke committed
9
#endif
10

11
namespace mirrage::asset {
12

13
	template <class R>
14
15
	auto Ptr<R>::get_blocking() const -> const R&
	{
16
17
		if(_cached_result)
			return *_cached_result;
18

19
		return *(_cached_result = &_task.get());
20
21
22
	}

	template <class R>
23
	auto Ptr<R>::get_if_ready() const -> util::maybe<const R&>
24
	{
25
26
		if(_cached_result)
			return util::justPtr(_cached_result);
27

28
		return ready() ? util::nothing : util::maybe<const R&>(get_blocking());
29
	}
30

31
	template <class R>
32
33
	auto Ptr<R>::ready() const -> bool
	{
34
		return _task.ready();
35
36
	}

37
	template <class R>
38
39
	void Ptr<R>::reset()
	{
40
41
42
		_aid           = {};
		_task          = {};
		_cached_result = nullptr;
43
44
	}

45
46
47
48
49
50
51
52
53
54

	namespace detail {
		template <class T>
		struct has_reload {
		  private:
			typedef char one;
			typedef long two;

			template <typename C>
			static one test(decltype(std::declval<Loader<C>>().reload(std::declval<istream>(),
55
			                                                          std::declval<C&>()))*);
56
57
58
59
60
61
62
63
64
65
66
			template <typename C>
			static two test(...);


		  public:
			enum { value = sizeof(test<T>(nullptr)) == sizeof(char) };
		};

		template <class T>
		constexpr auto has_reload_v = has_reload<T>::value;

Florian Oetke's avatar
Florian Oetke committed
67
		template <class TaskType, class T>
68
		constexpr auto is_task_v =
Florian Oetke's avatar
Florian Oetke committed
69
70
		        std::is_same_v<T,
		                       ::async::task<TaskType>> || std::is_same_v<T, ::async::shared_task<TaskType>>;
71
72

		template <typename T>
73
74
		auto Asset_container<T>::load(AID aid, const std::string& path, bool cache) -> Ptr<T>
		{
75
76
77
78
			auto lock = std::scoped_lock{_container_mutex};

			auto found = _assets.find(path);
			if(found != _assets.end())
79
				return {aid, found->second.task};
80
81

			// not found => load
82
			// clang-format off
83
			auto loading = async::spawn([path = std::string(path), aid, this] {
84
				return Loader<T>::load(_manager._open(aid, path));
85
86
			}).share();
			// clang-format on
87
88

			if(cache)
89
				_assets.try_emplace(path, Asset{aid, loading, _manager._last_modified(path)});
90

91
			return {aid, loading};
92
93
		}

94
		template <typename T>
95
96
		void Asset_container<T>::save(const AID& aid, const std::string& name, const T& obj)
		{
97
98
			auto lock = std::scoped_lock{_container_mutex};

99
			Loader<T>::save(_manager._open_rw(aid, name), obj);
100
101

			auto found = _assets.find(name);
102
			if(found != _assets.end() && &found.value().task.get() != &obj) {
103
104
105
106
107
				_reload_asset(found.value(), found.key()); // replace existing value
			}
		}

		template <typename T>
108
109
		void Asset_container<T>::shrink_to_fit() noexcept
		{
110
111
			auto lock = std::scoped_lock{_container_mutex};

112
			util::erase_if(_assets, [](const auto& v) { return v.second.task.refcount() <= 1; });
113
114
115
		}

		template <typename T>
116
117
		void Asset_container<T>::reload()
		{
118
119
			auto lock = std::scoped_lock{_container_mutex};

120
121
			for(auto&& entry : _assets) {
				auto last_mod = _manager._last_modified(entry.first);
122

123
124
				if(last_mod > entry.second.last_modified) {
					_reload_asset(const_cast<Asset&>(entry.second), entry.first);
125
126
127
128
				}
			}
		}

129
		// TODO: test if this actually works
130
		template <typename T>
131
132
		void Asset_container<T>::_reload_asset(Asset& asset, const std::string& path)
		{
133
			auto& old_value = const_cast<T&>(asset.task.get());
134
135
136
137

			asset.last_modified = _manager._last_modified(path);

			if constexpr(has_reload_v<T>) {
138
				Loader<T>::reload(_manager._open(asset.aid, path), old_value);
139
			} else {
140
141
142
143
144
145
146
147
148
				auto new_value = Loader<T>::load(_manager._open(asset.aid, path));

				if constexpr(std::is_same_v<decltype(new_value), async::task<T>>) {
					old_value = std::move(new_value.get());
				} else if constexpr(std::is_same_v<decltype(new_value), async::shared_task<T>>) {
					old_value = std::move(const_cast<T&>(new_value.get()));
				} else {
					old_value = std::move(new_value);
				}
149
			}
150
151

			// TODO: notify other systems about change
152
		}
153
154
	} // namespace detail

155

156
	template <typename T>
157
158
	auto Asset_manager::load(const AID& id, bool cache) -> Ptr<T>
	{
159
160
		auto path = resolve(id);
		if(path.is_nothing())
161
			throw std::system_error(Asset_error::resolve_failed, id.str());
162

163
164
165
166
167
		auto&& container = _find_container<T>();
		if(container.is_nothing())
			throw std::system_error(Asset_error::stateful_loader_not_initialized, util::type_name<T>());

		return container.get_or_throw().load(id, path.get_or_throw(), cache);
168
169
	}

170
	template <typename T>
171
172
	auto Asset_manager::load_maybe(const AID& id, bool cache) -> util::maybe<Ptr<T>>
	{
173
174
175
176
177
178
179
180
181
182
		auto path = resolve(id);
		if(path.is_nothing())
			return util::nothing;

		return _find_container<T>().process([&](detail::Asset_container<T>& container) {
			return container.load(id, path.get_or_throw(), cache);
		});
	}

	template <typename T>
183
184
	void Asset_manager::save(const AID& id, const T& asset)
	{
185
		auto path = resolve(id, false);
186
		if(path.is_nothing())
187
			throw std::system_error(Asset_error::resolve_failed, id.str());
188
189
190

		auto container = _find_container<T>();
		if(container.is_nothing())
191
			throw std::system_error(Asset_error::stateful_loader_not_initialized, id.str());
192
193
194
195
196

		container.get_or_throw().save(id, path.get_or_throw(), asset);
	}

	template <typename T>
197
198
	void Asset_manager::save(const Ptr<T>& asset)
	{
199
200
201
202
		save(asset.aid(), *asset);
	}

	template <typename T, typename... Args>
203
204
	void Asset_manager::create_stateful_loader(Args&&... args)
	{
205
206
207
208
209
210
211
212
213
214
215
216
		auto key = util::type_uid_of<T>();

		auto lock = std::scoped_lock{_containers_mutex};

		auto container = _containers.find(key);
		if(container == _containers.end()) {
			_containers.emplace(
			        key, std::make_unique<detail::Asset_container<T>>(*this, std::forward<Args>(args)...));
		}
	}

	template <typename T>
217
218
	void Asset_manager::remove_stateful_loader()
	{
219
220
221
222
223
		auto lock = std::scoped_lock{_containers_mutex};

		_containers.erase(util::type_uid_of<T>());
	}

Florian Oetke's avatar
Florian Oetke committed
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
	template <typename T>
	auto Asset_manager::load_stream(asset::istream stream) -> Ptr<T>
	{
		auto container = _find_container<T>();
		if(container.is_nothing())
			throw std::system_error(Asset_error::stateful_loader_not_initialized, util::type_name<T>());


		auto new_value = container.get_or_throw().load(std::move(stream));

		if constexpr(std::is_same_v<decltype(new_value), async::task<T>>)
			return Ptr<T>(stream.aid(), new_value.share());
		else if constexpr(std::is_same_v<decltype(new_value), async::shared_task<T>>)
			return Ptr<T>(stream.aid(), std::move(new_value));
		else
			return Ptr<T>(stream.aid(), async::make_task(std::move(new_value)).share());
	}

242
	template <typename T>
243
244
	auto Asset_manager::_find_container() -> util::maybe<detail::Asset_container<T>&>
	{
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
		auto key = util::type_uid_of<T>();

		auto lock = std::scoped_lock{_containers_mutex};

		auto container = _containers.find(key);
		if(container != _containers.end()) {
			return util::justPtr(static_cast<detail::Asset_container<T>*>(&*container->second));
		}

		// no container for T, yet
		if constexpr(std::is_default_constructible_v<Loader<T>>) {
			container = _containers.emplace(key, std::make_unique<detail::Asset_container<T>>(*this)).first;

			return util::justPtr(static_cast<detail::Asset_container<T>*>(&*container->second));
		} else {
			return util::nothing;
		}
262
	}
263

264
} // namespace mirrage::asset