yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
overworld_undo_actions.h
Go to the documentation of this file.
1#ifndef YAZE_APP_EDITOR_OVERWORLD_UNDO_ACTIONS_H_
2#define YAZE_APP_EDITOR_OVERWORLD_UNDO_ACTIONS_H_
3
4#include <chrono>
5#include <cstddef>
6#include <functional>
7#include <optional>
8#include <string>
9#include <unordered_map>
10#include <utility>
11#include <vector>
12
13#include "absl/status/status.h"
14#include "absl/strings/str_format.h"
17#include "util/macro.h"
20
21namespace yaze {
22namespace editor {
23
28 int x = 0;
29 int y = 0;
30 int old_tile_id = 0;
31 int new_tile_id = 0;
32};
33
46 public:
48 static constexpr int kMergeWindowMs = 500;
49
58 std::vector<OverworldTileChange> tile_changes,
59 zelda3::Overworld* overworld,
60 std::function<void()> refresh_fn)
61 : map_id_(map_id),
63 tile_changes_(std::move(tile_changes)),
64 overworld_(overworld),
65 refresh_fn_(std::move(refresh_fn)),
66 timestamp_(std::chrono::steady_clock::now()) {}
67
68 absl::Status Undo() override {
69 if (!overworld_) {
70 return absl::InternalError("Overworld pointer is null");
71 }
72 auto& world_tiles = overworld_->GetMapTiles(world_);
73 for (const auto& change : tile_changes_) {
74 world_tiles[change.x][change.y] = change.old_tile_id;
75 }
76 if (refresh_fn_) {
78 }
79 return absl::OkStatus();
80 }
81
82 absl::Status Redo() override {
83 if (!overworld_) {
84 return absl::InternalError("Overworld pointer is null");
85 }
86 auto& world_tiles = overworld_->GetMapTiles(world_);
87 for (const auto& change : tile_changes_) {
88 world_tiles[change.x][change.y] = change.new_tile_id;
89 }
90 if (refresh_fn_) {
92 }
93 return absl::OkStatus();
94 }
95
96 std::string Description() const override {
97 return absl::StrFormat("Paint %d tile%s on map %d", tile_changes_.size(),
98 tile_changes_.size() == 1 ? "" : "s", map_id_);
99 }
100
101 size_t MemoryUsage() const override {
102 return sizeof(*this) + tile_changes_.size() * sizeof(OverworldTileChange);
103 }
104
105 bool CanMergeWith(const UndoAction& prev) const override {
106 const auto* prev_paint =
107 dynamic_cast<const OverworldTilePaintAction*>(&prev);
108 if (!prev_paint)
109 return false;
110 if (prev_paint->map_id_ != map_id_)
111 return false;
112 if (prev_paint->world_ != world_)
113 return false;
114
115 auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
116 timestamp_ - prev_paint->timestamp_);
117 return elapsed.count() <= kMergeWindowMs;
118 }
119
120 void MergeWith(UndoAction& prev) override {
121 auto& prev_paint = static_cast<OverworldTilePaintAction&>(prev);
122
123 // Build a map of (x,y) -> index in our tile_changes_ for fast lookup
124 // so we can keep the earliest old_tile_id for coordinates that appear
125 // in both actions.
126 std::unordered_map<int64_t, size_t> coord_index;
127 for (size_t i = 0; i < tile_changes_.size(); ++i) {
128 int64_t key = (static_cast<int64_t>(tile_changes_[i].x) << 32) |
129 static_cast<int64_t>(tile_changes_[i].y);
130 coord_index[key] = i;
131 }
132
133 for (const auto& prev_change : prev_paint.tile_changes_) {
134 int64_t key = (static_cast<int64_t>(prev_change.x) << 32) |
135 static_cast<int64_t>(prev_change.y);
136 auto it = coord_index.find(key);
137 if (it != coord_index.end()) {
138 // Same coordinate exists in both: keep the older old_tile_id
139 tile_changes_[it->second].old_tile_id = prev_change.old_tile_id;
140 } else {
141 // Coordinate only in prev: adopt it as-is
142 tile_changes_.push_back(prev_change);
143 }
144 }
145
146 // Keep the earlier timestamp so subsequent merges measure from
147 // the start of the combined stroke.
148 timestamp_ = prev_paint.timestamp_;
149 }
150
151 int map_id() const { return map_id_; }
152 int world() const { return world_; }
153 const std::vector<OverworldTileChange>& tile_changes() const {
154 return tile_changes_;
155 }
156
157 private:
160 std::vector<OverworldTileChange> tile_changes_;
162 std::function<void()> refresh_fn_; // callback to refresh map visuals
163 std::chrono::steady_clock::time_point timestamp_;
164};
165
172 std::vector<zelda3::OverworldItem> items;
173 std::optional<zelda3::OverworldItem> selected_item_identity;
174};
175
183 public:
184 using RestoreFn = std::function<void(const OverworldItemsSnapshot&)>;
185
187 OverworldItemsSnapshot after, RestoreFn restore,
188 std::string description)
189 : before_(std::move(before)),
190 after_(std::move(after)),
191 restore_(std::move(restore)),
192 description_(std::move(description)) {}
193
194 absl::Status Undo() override {
195 if (!restore_) {
196 return absl::InternalError(
197 "OverworldItemsEditAction: no restore callback");
198 }
200 return absl::OkStatus();
201 }
202
203 absl::Status Redo() override {
204 if (!restore_) {
205 return absl::InternalError(
206 "OverworldItemsEditAction: no restore callback");
207 }
209 return absl::OkStatus();
210 }
211
212 std::string Description() const override { return description_; }
213
214 size_t MemoryUsage() const override {
215 const size_t before_size =
216 before_.items.size() * sizeof(zelda3::OverworldItem);
217 const size_t after_size =
218 after_.items.size() * sizeof(zelda3::OverworldItem);
219 return sizeof(*this) + before_size + after_size;
220 }
221
222 bool CanMergeWith(const UndoAction& /*prev*/) const override { return false; }
223
224 private:
228 std::string description_;
229};
230
232 public:
233 using ApplyFn = std::function<absl::Status(const OverworldPropertyEdit&)>;
234
236 OverworldPropertyEdit after, ApplyFn apply,
237 std::string description)
238 : before_(std::move(before)),
239 after_(std::move(after)),
240 apply_(std::move(apply)),
241 description_(std::move(description)) {}
242
243 absl::Status Undo() override {
244 if (!apply_) {
245 return absl::InternalError(
246 "OverworldMapPropertyEditAction: no apply callback");
247 }
248 return apply_(before_);
249 }
250
251 absl::Status Redo() override {
252 if (!apply_) {
253 return absl::InternalError(
254 "OverworldMapPropertyEditAction: no apply callback");
255 }
256 return apply_(after_);
257 }
258
259 std::string Description() const override { return description_; }
260
261 size_t MemoryUsage() const override {
262 return sizeof(*this) + before_.description.size() +
263 after_.description.size() + description_.size();
264 }
265
266 bool CanMergeWith(const UndoAction& /*prev*/) const override { return false; }
267
268 private:
272 std::string description_;
273};
274
276 public:
277 using ApplyFn = std::function<absl::Status(const OverworldPropertyEdit&)>;
278
279 OverworldMapPropertyBatchEditAction(std::vector<OverworldPropertyEdit> before,
280 std::vector<OverworldPropertyEdit> after,
281 ApplyFn apply, std::string description)
282 : before_(std::move(before)),
283 after_(std::move(after)),
284 apply_(std::move(apply)),
285 description_(std::move(description)) {}
286
287 absl::Status Undo() override {
288 if (!apply_) {
289 return absl::InternalError(
290 "OverworldMapPropertyBatchEditAction: no apply callback");
291 }
292 for (auto it = before_.rbegin(); it != before_.rend(); ++it) {
294 }
295 return absl::OkStatus();
296 }
297
298 absl::Status Redo() override {
299 if (!apply_) {
300 return absl::InternalError(
301 "OverworldMapPropertyBatchEditAction: no apply callback");
302 }
303 for (const auto& edit : after_) {
304 RETURN_IF_ERROR(apply_(edit));
305 }
306 return absl::OkStatus();
307 }
308
309 std::string Description() const override { return description_; }
310
311 size_t MemoryUsage() const override {
312 size_t total = sizeof(*this) + description_.size();
313 for (const auto& edit : before_) {
314 total += sizeof(edit) + edit.description.size();
315 }
316 for (const auto& edit : after_) {
317 total += sizeof(edit) + edit.description.size();
318 }
319 return total;
320 }
321
322 bool CanMergeWith(const UndoAction& /*prev*/) const override { return false; }
323
324 private:
325 std::vector<OverworldPropertyEdit> before_;
326 std::vector<OverworldPropertyEdit> after_;
328 std::string description_;
329};
330
332 public:
333 using ApplyFn = std::function<absl::Status(const std::string&)>;
334
335 OverworldProjectLabelEditAction(std::string before, std::string after,
336 ApplyFn apply, std::string description)
337 : before_(std::move(before)),
338 after_(std::move(after)),
339 apply_(std::move(apply)),
340 description_(std::move(description)) {}
341
342 absl::Status Undo() override {
343 if (!apply_) {
344 return absl::InternalError(
345 "OverworldProjectLabelEditAction: no apply callback");
346 }
347 return apply_(before_);
348 }
349
350 absl::Status Redo() override {
351 if (!apply_) {
352 return absl::InternalError(
353 "OverworldProjectLabelEditAction: no apply callback");
354 }
355 return apply_(after_);
356 }
357
358 std::string Description() const override { return description_; }
359
360 size_t MemoryUsage() const override {
361 return sizeof(*this) + before_.size() + after_.size() + description_.size();
362 }
363
364 bool CanMergeWith(const UndoAction& /*prev*/) const override { return false; }
365
366 private:
367 std::string before_;
368 std::string after_;
370 std::string description_;
371};
372
373} // namespace editor
374} // namespace yaze
375
376#endif // YAZE_APP_EDITOR_OVERWORLD_UNDO_ACTIONS_H_
Undoable action for overworld item mutations.
bool CanMergeWith(const UndoAction &) const override
std::string Description() const override
Human-readable description (e.g., "Paint 12 tiles on map 5")
OverworldItemsEditAction(OverworldItemsSnapshot before, OverworldItemsSnapshot after, RestoreFn restore, std::string description)
std::function< void(const OverworldItemsSnapshot &)> RestoreFn
size_t MemoryUsage() const override
Approximate memory footprint for budget enforcement.
std::string Description() const override
Human-readable description (e.g., "Paint 12 tiles on map 5")
std::function< absl::Status(const OverworldPropertyEdit &)> ApplyFn
size_t MemoryUsage() const override
Approximate memory footprint for budget enforcement.
OverworldMapPropertyBatchEditAction(std::vector< OverworldPropertyEdit > before, std::vector< OverworldPropertyEdit > after, ApplyFn apply, std::string description)
bool CanMergeWith(const UndoAction &) const override
size_t MemoryUsage() const override
Approximate memory footprint for budget enforcement.
bool CanMergeWith(const UndoAction &) const override
std::string Description() const override
Human-readable description (e.g., "Paint 12 tiles on map 5")
OverworldMapPropertyEditAction(OverworldPropertyEdit before, OverworldPropertyEdit after, ApplyFn apply, std::string description)
std::function< absl::Status(const OverworldPropertyEdit &)> ApplyFn
OverworldProjectLabelEditAction(std::string before, std::string after, ApplyFn apply, std::string description)
size_t MemoryUsage() const override
Approximate memory footprint for budget enforcement.
std::function< absl::Status(const std::string &)> ApplyFn
bool CanMergeWith(const UndoAction &) const override
std::string Description() const override
Human-readable description (e.g., "Paint 12 tiles on map 5")
Undoable action for painting tiles on the overworld map.
size_t MemoryUsage() const override
Approximate memory footprint for budget enforcement.
void MergeWith(UndoAction &prev) override
bool CanMergeWith(const UndoAction &prev) const override
static constexpr int kMergeWindowMs
Merge window: consecutive paints within this duration become one step.
OverworldTilePaintAction(int map_id, int world, std::vector< OverworldTileChange > tile_changes, zelda3::Overworld *overworld, std::function< void()> refresh_fn)
std::string Description() const override
Human-readable description (e.g., "Paint 12 tiles on map 5")
std::vector< OverworldTileChange > tile_changes_
const std::vector< OverworldTileChange > & tile_changes() const
std::chrono::steady_clock::time_point timestamp_
Abstract base for all undoable actions (Command pattern)
Definition undo_action.h:20
Represents the full Overworld data, light and dark world.
Definition overworld.h:389
OverworldBlockset & GetMapTiles(int world_type)
Definition overworld.h:646
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22
Snapshot of overworld item list + current item selection.
std::vector< zelda3::OverworldItem > items
std::optional< zelda3::OverworldItem > selected_item_identity
A single tile coordinate + old/new value pair for undo/redo.