yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
project_graphics_import_command.cc
Go to the documentation of this file.
2
3#include <utility>
4
5#include "absl/strings/str_cat.h"
8#include "util/macro.h"
9
10namespace yaze::editor {
11namespace {
12
14 public:
16 const ProjectGraphicsImportPlan& plan,
17 std::unique_ptr<UndoAction> definitions,
18 const zelda3::GameData* owner,
20 std::string description)
21 : resource_(std::move(resource)),
22 before_(plan.resource_before),
23 after_(plan.resource_after),
24 palettes_(plan.palette_changes),
25 definitions_(std::move(definitions)),
26 owner_(owner),
27 refresh_(std::move(refresh)),
28 description_(std::move(description)) {}
29
30 absl::Status Undo() override { return Apply(false); }
31 absl::Status Redo() override { return Apply(true); }
32 std::string Description() const override { return description_; }
33 size_t MemoryUsage() const override {
34 return before_.size() + after_.size() +
35 palettes_.size() * sizeof(gfx::PaletteColorChange) +
36 (definitions_ ? definitions_->MemoryUsage() : 0);
37 }
38
39 private:
40 absl::Status Apply(bool forward) {
41 if (poisoned_)
42 return absl::FailedPreconditionError(
43 "Import rollback failed; reload the document");
44 if (forward == applied_)
45 return absl::FailedPreconditionError(
46 "Import action is already in that state");
47 const auto& expected = forward ? before_ : after_;
48 const auto& desired = forward ? after_ : before_;
49 ASSIGN_OR_RETURN(const auto current, resource_.read());
50 if (current != expected) {
51 return absl::FailedPreconditionError(
52 "Graphics resource changed since import planning");
53 }
54 auto changes = palettes_;
55 if (!forward) {
56 for (auto& change : changes)
57 std::swap(change.original_color, change.new_color);
58 }
59 bool participated = false;
60 bool resource_applied = false;
61 bool definitions_applied = false;
62 std::string rollback_errors;
63 auto record_rollback = [&](const absl::Status& status, const char* phase) {
64 if (!status.ok()) {
65 absl::StrAppend(&rollback_errors, "; ", phase, ": ", status.message());
66 poisoned_ = true;
67 }
68 };
69 auto rollback = [&](const absl::Status& failure) -> absl::Status {
70 if (definitions_applied) {
71 record_rollback(forward ? definitions_->Undo() : definitions_->Redo(),
72 "definition rollback");
73 }
74 if (resource_applied)
75 record_rollback(resource_.replace(expected), "resource rollback");
76 return rollback_errors.empty() ? failure
77 : absl::InternalError(absl::StrCat(
78 failure.message(), rollback_errors));
79 };
80 const auto status = gfx::PaletteManager::Get().ApplyExternalChanges(
81 owner_, changes, [&]() -> absl::Status {
82 participated = true;
83 if (expected != desired) {
84 const auto replaced = resource_.replace(desired);
85 if (!replaced.ok())
86 return rollback(replaced);
87 resource_applied = true;
88 }
89 const auto source_refreshed = refresh_();
90 if (!source_refreshed.ok())
91 return rollback(source_refreshed);
92 if (definitions_) {
93 const auto defined =
94 forward ? definitions_->Redo() : definitions_->Undo();
95 if (!defined.ok())
96 return rollback(defined);
97 definitions_applied = true;
98 }
99 const auto view_refreshed = refresh_();
100 if (!view_refreshed.ok())
101 return rollback(view_refreshed);
102 return absl::OkStatus();
103 });
104 if (!status.ok()) {
105 // ApplyExternalChanges has now restored palette values and tracking.
106 // Refreshing inside its participant would retain the rejected colors.
107 if (participated) {
108 const auto restored = refresh_();
109 if (!restored.ok()) {
110 poisoned_ = true;
111 return absl::InternalError(
112 absl::StrCat(status.message(),
113 "; restored-view refresh: ", restored.message()));
114 }
115 }
116 return status;
117 }
118 applied_ = forward;
119 return absl::OkStatus();
120 }
121
123 std::vector<uint8_t> before_, after_;
124 std::vector<gfx::PaletteColorChange> palettes_;
125 std::unique_ptr<UndoAction> definitions_;
128 std::string description_;
129 bool applied_ = false;
130 bool poisoned_ = false;
131};
132
133} // namespace
134
135absl::StatusOr<std::unique_ptr<UndoAction>> CreateProjectGraphicsImportAction(
137 const ProjectGraphicsImportPlan& plan,
138 std::unique_ptr<UndoAction> prepared_definitions,
140 std::string description) {
141 if (!resource.read || !resource.replace || !refresh || !owner ||
143 return absl::FailedPreconditionError(
144 "Import document session is unavailable");
145 }
146 if (plan.resource_before.empty() ||
147 plan.resource_before.size() != plan.resource_after.size() ||
148 plan.resource_before.size() % 32 != 0) {
149 return absl::InvalidArgumentError(
150 "Import must preserve aligned resource size");
151 }
152 ASSIGN_OR_RETURN(const auto current, resource.read());
153 if (current != plan.resource_before) {
154 return absl::FailedPreconditionError(
155 "Graphics resource changed since import planning");
156 }
157 return std::unique_ptr<UndoAction>(
158 std::make_unique<ProjectGraphicsImportAction>(
159 std::move(resource), plan, std::move(prepared_definitions), owner,
160 std::move(refresh), std::move(description)));
161}
162
163absl::StatusOr<std::unique_ptr<UndoAction>> CreateProjectGraphicsImportAction(
164 std::shared_ptr<ProjectGraphicsSource> source,
166 const ProjectGraphicsImportPlan& plan,
167 std::unique_ptr<UndoAction> prepared_definitions,
169 std::string description) {
170 if (!source || !source->loaded() || !resolve_resource)
171 return absl::FailedPreconditionError(
172 "Project graphics source is unavailable");
173 const auto source_root = source->source_root();
174 const auto catalog = source->portable_catalog();
175 auto resolve = [source, resolve_resource, source_root,
176 catalog]() -> absl::StatusOr<std::string> {
177 if (!source->loaded() || source->source_root() != source_root ||
178 source->portable_catalog() != catalog) {
179 return absl::FailedPreconditionError(
180 "Project graphics source session changed");
181 }
182 return resolve_resource(*source);
183 };
185 resource.read = [source, resolve]() -> absl::StatusOr<std::vector<uint8_t>> {
186 ASSIGN_OR_RETURN(const auto path, resolve());
187 for (const auto& candidate : source->resources()) {
188 if (candidate.source_path == path)
189 return candidate.pending_bytes;
190 }
191 return absl::NotFoundError("Import resource binding is unavailable");
192 };
193 resource.replace =
194 [source, resolve](const std::vector<uint8_t>& bytes) -> absl::Status {
195 ASSIGN_OR_RETURN(const auto path, resolve());
196 return source->ReplaceResourceBytes(path, bytes);
197 };
199 std::move(resource), plan, std::move(prepared_definitions), owner,
200 std::move(refresh), std::move(description));
201}
202
203} // namespace yaze::editor
Abstract base for all undoable actions (Command pattern)
Definition undo_action.h:20
std::string Description() const override
Human-readable description (e.g., "Paint 12 tiles on map 5")
ProjectGraphicsImportAction(ProjectGraphicsImportResourceCallbacks resource, const ProjectGraphicsImportPlan &plan, std::unique_ptr< UndoAction > definitions, const zelda3::GameData *owner, ProjectGraphicsImportRefresh refresh, std::string description)
bool IsManaging(const zelda3::GameData *game_data) const
Check whether the manager is bound to this exact GameData and ROM.
static PaletteManager & Get()
Get the singleton instance.
#define ASSIGN_OR_RETURN(type_variable_name, expression)
Definition macro.h:62
Editors are the view controllers for the application.
std::function< absl::Status()> ProjectGraphicsImportRefresh
std::function< absl::StatusOr< std::string >(const ProjectGraphicsSource &)> ProjectGraphicsImportResourceResolver
absl::StatusOr< std::unique_ptr< UndoAction > > CreateProjectGraphicsImportAction(ProjectGraphicsImportResourceCallbacks resource, const ProjectGraphicsImportPlan &plan, std::unique_ptr< UndoAction > prepared_definitions, const zelda3::GameData *owner, ProjectGraphicsImportRefresh refresh, std::string description)
std::function< absl::StatusOr< std::vector< uint8_t > >()> read
std::function< absl::Status(const std::vector< uint8_t > &) replace)
Represents a single color change operation.