yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
tile16_edit_history.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <cstdint>
5#include <set>
6
9#include "util/macro.h"
12
13namespace yaze::editor {
14namespace {
15bool SameDefinition(const gfx::Tile16& a, const gfx::Tile16& b) {
16 for (int q = 0; q < 4; ++q) {
19 return false;
20 }
21 }
22 return true;
23}
24
25bool ContainsTile(const std::vector<Tile16Commit>& edits, int tile_id) {
26 return std::any_of(edits.begin(), edits.end(), [tile_id](const auto& edit) {
27 return edit.tile_id == tile_id;
28 });
29}
30
32 public:
33 using Apply = std::function<absl::Status(const std::vector<Tile16Commit>&)>;
34 Tile16DefinitionEditAction(std::vector<Tile16Commit> before,
35 std::vector<Tile16Commit> after, Apply apply,
36 std::string description = {},
37 const void* owner = nullptr,
38 uint64_t edit_group = 0)
39 : before_(std::move(before)),
40 after_(std::move(after)),
41 apply_(std::move(apply)),
42 description_(std::move(description)),
43 owner_(owner),
44 edit_group_(edit_group) {}
45 absl::Status Undo() override { return apply_(before_); }
46 absl::Status Redo() override { return apply_(after_); }
47 std::string Description() const override {
48 if (!description_.empty())
49 return description_;
50 return before_.size() == 1 ? "Edit Tile16 definition" : "Edit Tile16 stamp";
51 }
52 size_t MemoryUsage() const override {
53 return (before_.size() + after_.size()) * sizeof(Tile16Commit);
54 }
55 bool CanMergeWith(const UndoAction& prev) const override {
56 const auto* previous =
57 dynamic_cast<const Tile16DefinitionEditAction*>(&prev);
58 return edit_group_ != 0 && previous != nullptr &&
59 previous->owner_ == owner_ && previous->edit_group_ == edit_group_;
60 }
61 // Keeps the earliest before state and the latest after state of each tile.
62 void MergeWith(UndoAction& prev) override {
63 auto& previous = static_cast<Tile16DefinitionEditAction&>(prev);
64 std::vector<Tile16Commit> before = std::move(previous.before_);
65 for (auto& edit : before_) {
66 if (!ContainsTile(before, edit.tile_id))
67 before.push_back(std::move(edit));
68 }
69 for (auto& edit : previous.after_) {
70 if (!ContainsTile(after_, edit.tile_id))
71 after_.push_back(std::move(edit));
72 }
73 before_ = std::move(before);
74 }
75
76 private:
77 std::vector<Tile16Commit> before_, after_;
79 std::string description_;
80 const void* owner_ = nullptr;
81 uint64_t edit_group_ = 0;
82};
83} // namespace
84
85absl::StatusOr<gfx::Tile16> Tile16EditSession::ReadDocumentTile(int id) const {
87 if (id < 0 || id >= static_cast<int>(document_definitions_->size())) {
88 return absl::OutOfRangeError("Tile16 is outside the document");
89 }
90 return (*document_definitions_)[id];
91 }
92 const auto it = document_edits_.find(id);
93 if (it != document_edits_.end())
94 return it->second;
95 if (!rom_)
96 return absl::FailedPreconditionError("No Tile16 document");
98}
99
101 const std::vector<Tile16Commit>& definitions) {
102 // Validate and render the complete batch before publishing any definition.
103 // History stores metadata, not pixels tied to the map's graphics/palette.
104 std::vector<gfx::Bitmap> bitmaps(definitions.size());
105 for (size_t i = 0; i < definitions.size(); ++i) {
106 const auto& edit = definitions[i];
107 if (edit.tile_id < 0 || edit.tile_id >= zelda3::kNumTile16Individual ||
109 edit.tile_id >= static_cast<int>(document_definitions_->size()))) {
110 return absl::OutOfRangeError("Tile16 edit is outside the document");
111 }
112 RETURN_IF_ERROR(BuildTile16BitmapFromData(edit.tile_data, &bitmaps[i]));
113 bitmaps[i].SetPalette(current_tile16_bmp_.palette());
114 }
115 for (size_t i = 0; i < definitions.size(); ++i) {
116 const auto& edit = definitions[i];
117 document_edits_[edit.tile_id] = edit.tile_data;
118 edited_tile_bitmaps_[edit.tile_id] = bitmaps[i];
120 (*document_definitions_)[edit.tile_id] = edit.tile_data;
121 }
122 CopyTileBitmapToBlockset(edit.tile_id, bitmaps[i]);
123 }
125 preview_dirty_ = true;
128 on_document_changed_(definitions);
129 if (rom_)
130 rom_->set_dirty(true);
134 }
138 }
139 return absl::OkStatus();
140}
141
143 const std::vector<Tile16Commit>& definitions) const {
144 if (definitions.empty())
145 return absl::OkStatus();
146 if (!rom_ || !tile16_blockset_) {
147 return absl::FailedPreconditionError(
148 "Tile16 blockset or ROM not initialized");
149 }
150 std::set<int> destinations;
151 for (const auto& edit : definitions) {
152 if (edit.tile_id < 0 || edit.tile_id >= zelda3::kNumTile16Individual ||
154 edit.tile_id >= static_cast<int>(document_definitions_->size()))) {
155 return absl::OutOfRangeError("Tile16 import is outside the document");
156 }
157 if (!destinations.insert(edit.tile_id).second) {
158 return absl::InvalidArgumentError(
159 "Tile16 import contains a duplicate destination");
160 }
161 ASSIGN_OR_RETURN(const auto original, ReadDocumentTile(edit.tile_id));
162 for (int q = 0; q < 4; ++q) {
163 const auto& info = zelda3::Tile16QuadrantInfo(edit.tile_data, q);
164 if (info.id_ >= 1024 ||
165 info.id_ >= static_cast<int>(current_gfx_individual_.size()) ||
166 info.palette_ > 7) {
167 return absl::OutOfRangeError(
168 "Tile16 import has invalid Tile8 metadata");
169 }
170 }
171 gfx::Bitmap before, after;
172 RETURN_IF_ERROR(BuildTile16BitmapFromData(original, &before));
173 RETURN_IF_ERROR(BuildTile16BitmapFromData(edit.tile_data, &after));
174 }
175 // ApplyDefinitions refreshes the current selection after publishing. Check
176 // that render now too, so its last fallible operation cannot partially apply
177 // a batch. Redo/Undo repeat this preflight against the current source.
179 gfx::Bitmap preview;
180 return BuildTile16BitmapFromData(current, &preview);
181}
182
183absl::StatusOr<std::unique_ptr<UndoAction>>
185 const std::vector<Tile16Commit>& definitions, std::string description) {
187 std::vector<Tile16Commit> before, after;
188 for (const auto& edit : definitions) {
189 ASSIGN_OR_RETURN(auto original, ReadDocumentTile(edit.tile_id));
190 auto normalized = edit.tile_data;
191 zelda3::SyncTile16TilesInfo(&normalized);
192 if (!SameDefinition(original, normalized)) {
193 before.push_back({edit.tile_id, std::move(original)});
194 after.push_back({edit.tile_id, std::move(normalized)});
195 }
196 }
197 if (after.empty())
198 return std::unique_ptr<UndoAction>{};
199 return std::unique_ptr<UndoAction>(
200 std::make_unique<Tile16DefinitionEditAction>(
201 std::move(before), std::move(after),
202 [this](const std::vector<Tile16Commit>& edits) {
204 return ApplyDefinitions(edits);
205 },
206 std::move(description)));
207}
208
210 const std::vector<Tile16Commit>& definitions, std::string description) {
212 definitions, std::move(description)));
213 if (!action)
214 return absl::OkStatus();
215 // Match RunEdit's chronological ordering without finalizing pending painting
216 // for a refused/no-op preparation or for an externally owned action.
217 if (before_edit_)
218 before_edit_();
219 RETURN_IF_ERROR(action->Redo());
220 history_->Push(std::move(action));
221 return absl::OkStatus();
222}
223
225 const std::function<absl::Status()>& operation, uint64_t edit_group) {
226 // A map stroke must enter history before the following definition edit.
227 if (before_edit_)
228 before_edit_();
229 const auto previous = document_edits_;
230 const auto old_current = current_tile16_data_;
231 const auto result = operation();
232 const auto proposed = document_edits_;
233 document_edits_ = previous;
234 current_tile16_data_ = old_current;
235
236 std::vector<Tile16Commit> before, after;
237 for (const auto& [id, data] : proposed) {
238 auto original = ReadDocumentTile(id);
239 if (!original.ok()) {
241 return original.status();
242 }
243 if (!SameDefinition(*original, data)) {
244 before.push_back({id, *original});
245 after.push_back({id, data});
246 }
247 }
248 if (!result.ok()) {
249 // Restore derived previews changed by a failed render/stamp operation.
250 for (const auto& edit : before) {
251 gfx::Bitmap bitmap;
252 if (BuildTile16BitmapFromData(edit.tile_data, &bitmap).ok()) {
253 CopyTileBitmapToBlockset(edit.tile_id, bitmap);
254 }
255 }
257 return result;
258 }
259 if (after.empty())
261 const auto applied = ApplyDefinitions(after);
262 if (!applied.ok()) {
263 (void)ApplyDefinitions(before);
264 document_edits_ = previous;
265 return applied;
266 }
267 history_->Push(std::make_unique<Tile16DefinitionEditAction>(
268 std::move(before), std::move(after),
269 [this](const std::vector<Tile16Commit>& edits) {
270 return ApplyDefinitions(edits);
271 },
272 std::string{}, this, edit_group));
273 return absl::OkStatus();
274}
275
277 uint64_t edit_group) {
278 return RunEdit(
279 [&]() {
284 return absl::OkStatus();
285 },
286 edit_group);
287}
288} // namespace yaze::editor
absl::StatusOr< gfx::Tile16 > ReadTile16(uint32_t tile16_id, uint32_t tile16_ptr)
Definition rom.cc:1072
void set_dirty(bool dirty)
Definition rom.h:201
absl::Status ApplyDefinitions(const std::vector< Tile16Commit > &definitions)
absl::Status RunEdit(const std::function< absl::Status()> &operation, uint64_t edit_group=0)
absl::StatusOr< gfx::Tile16 > ReadDocumentTile(int id) const
void CopyTileBitmapToBlockset(int tile_id, const gfx::Bitmap &tile_bitmap)
std::map< int, gfx::Bitmap > edited_tile_bitmaps_
absl::Status ValidateDefinitionImport(const std::vector< Tile16Commit > &definitions) const
absl::Status ReplaceCurrentTile(const gfx::Tile16 &data, uint64_t edit_group=0)
std::map< int, gfx::Tile16 > document_edits_
absl::Status ImportDefinitions(const std::vector< Tile16Commit > &definitions, std::string description="Import Tile16 definitions")
absl::Status BuildTile16BitmapFromData(const gfx::Tile16 &tile_data, gfx::Bitmap *output_bitmap) const
std::function< void(const std::vector< Tile16Commit > &) on_document_changed_)
absl::StatusOr< std::unique_ptr< UndoAction > > PrepareDefinitionImport(const std::vector< Tile16Commit > &definitions, std::string description="Import Tile16 definitions")
std::vector< gfx::Tile16 > * document_definitions_
std::vector< zelda3::Tile8PixelData > current_gfx_individual_
Abstract base for all undoable actions (Command pattern)
Definition undo_action.h:20
void Push(std::unique_ptr< UndoAction > action)
Tile16DefinitionEditAction(std::vector< Tile16Commit > before, std::vector< Tile16Commit > after, Apply apply, std::string description={}, const void *owner=nullptr, uint64_t edit_group=0)
size_t MemoryUsage() const override
Approximate memory footprint for budget enforcement.
std::function< absl::Status(const std::vector< Tile16Commit > &)> Apply
std::string Description() const override
Human-readable description (e.g., "Paint 12 tiles on map 5")
void QueueTextureCommand(TextureCommandType type, Bitmap *bitmap)
Definition arena.cc:39
static Arena & Get()
Definition arena.cc:24
Represents a bitmap image optimized for SNES ROM hacking.
Definition bitmap.h:70
const SnesPalette & palette() const
Definition bitmap.h:393
bool is_active() const
Definition bitmap.h:414
Tile composition of four 8x8 tiles.
Definition snes_tile.h:142
#define ASSIGN_OR_RETURN(type_variable_name, expression)
Definition macro.h:62
bool ContainsTile(const std::vector< Tile16Commit > &edits, int tile_id)
Editors are the view controllers for the application.
uint16_t TileInfoToShort(TileInfo tile_info)
Definition snes_tile.cc:394
void SyncTile16TilesInfo(gfx::Tile16 *tile)
constexpr int kNumTile16Individual
Definition overworld.h:244
constexpr uint32_t kTile16Ptr
Definition game_data.h:61
const gfx::TileInfo & Tile16QuadrantInfo(const gfx::Tile16 &tile, int quadrant)
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22
Bitmap atlas
Master bitmap containing all tiles.
Definition tilemap.h:119