diff --git a/src/modules/stream/ui/graph_module.cpp b/src/modules/stream/ui/graph_module.cpp index ef003c9..7aa7622 100644 --- a/src/modules/stream/ui/graph_module.cpp +++ b/src/modules/stream/ui/graph_module.cpp @@ -3,6 +3,9 @@ #include "world_editor.h" #include "editor_event.h" #include "building_layout_graph.h" +#include "grid_misc.h" +#include "grow_job.h" +#include "growth_regions.h" #include "graph_module.h" #define MIN_ROOM_SIZE 16 /* 4 * 4 tiles */ @@ -207,6 +210,14 @@ void BuildingLayoutGraph::graph_module::grow_cell(flecs::entity seed_e, int id) c_e.add( second); }); + int mcount = 0; + seed_e.each( + [&c_e, &mcount](flecs::entity second) { + assert(mcount == 0); + c_e.add( + second); + mcount++; + }); floor_e.get_mut() ->size_left--; @@ -256,15 +267,17 @@ void BuildingLayoutGraph::graph_module::create_floor_components( assert(!floor_e.has()); floor_e.set( { Set(), size.grid_size, size.growth_size }); - floor_e.set( - { Vector(), false }); + floor_e.set({ List(), false }); floor_e.add(base_floor_e); } +#if 0 + void growth_regions::create_region(flecs::entity floor_e, flecs::entity seed_e, flecs::entity parent_e, flecs::entity region_e, - const Vector2i &position, float area) + const Vector2i &position, float area, + int parent_index) { int i; struct growth_regions::region r; @@ -277,6 +290,13 @@ void growth_regions::create_region(flecs::entity floor_e, flecs::entity seed_e, r.can_grow_square = true; r.can_grow = true; r.complete = false; + r.parent_region = parent_index; + flecs::log::dbg("create region %s in %s", + (r.rect.operator String()).ascii().ptr(), + (parent_regions[parent_index].rect.operator String()) + .ascii() + .ptr()); + assert(parent_regions[parent_index].rect.encloses(r.rect)); bool ok = true; assert(check_region(-1, r.rect)); const struct growth_regions *reg = floor_e.get(); @@ -300,9 +320,11 @@ void growth_regions::create_region(flecs::entity floor_e, flecs::entity seed_e, region_e.path().c_str()); } } +#endif flecs::entity growth_regions::create_cell(flecs::entity floor_e, flecs::entity region_e, int id) { +#if 0 flecs::entity ret; flecs::log::dbg("create_cell: %s %d", region_e.path().c_str(), id); if (floor_e.get() @@ -310,8 +332,11 @@ flecs::entity growth_regions::create_cell(flecs::entity floor_e, flecs::log::err("cell %d already exists", id); return ret; } +#endif String pname("cell_" + itos(id)); flecs::entity cell_e = floor_e.lookup(pname.ascii().ptr()); + if (cell_e.is_valid()) + flecs::log::err("cell %d already exists", id); assert(!cell_e.is_valid()); cell_e = floor_e.world().entity(pname.ascii().ptr()).child_of(floor_e); floor_e.get_mut() @@ -323,16 +348,178 @@ flecs::entity growth_regions::create_cell(flecs::entity floor_e, cell_e.set( { String(region_e.name()), id }); cell_e.add(region_e); + if (region_e.has< + WorldEditor::components::buildings_layout_room>()) { + int mcount = 0; + cell_e.each( + [&mcount, region_e](flecs::entity e) { + if (e.id() != region_e.id()) + assert(false); + mcount++; + }); + if (mcount == 0) + cell_e.add( + region_e); + } } return cell_e; } -bool BuildingLayoutGraph::graph_module::check_region(flecs::entity floor_e, - int index, - const Rect2i &rect) const +flecs::entity growth_regions::update_cell(flecs::entity floor_e, + flecs::entity parent_e, + flecs::entity region_e, int id) { - const growth_regions *g = floor_e.get(); - return g->check_region(index, rect); + flecs::log::dbg("create_cell: %s %d", region_e.path().c_str(), id); + String pname("cell_" + itos(id)); + flecs::entity cell_e = floor_e.lookup(pname.ascii().ptr()); + if (!cell_e.is_valid()) { + flecs::log::warn("creating new cell %s", cell_e.path()); + cell_e = floor_e.world() + .entity(pname.ascii().ptr()) + .child_of(floor_e); + assert(cell_e.is_valid()); + } + // assert(cell_e.has(parent_e)); + /* already there */ + floor_e.get_mut() + ->cells.insert(id); + floor_e.modified(); + cell_e.set( + { String(region_e.name()), id }); + cell_e.remove(parent_e); + cell_e.add(region_e); + if (region_e.has()) { + int mcount = 0; + cell_e.each( + [&mcount, region_e, cell_e](flecs::entity e) { + flecs::log::err("adding region %s to cell %s", + region_e.path().c_str(), + cell_e.path().c_str()); + flecs::log::err("already belongs to %s", + e.path().c_str()); + assert(mcount == 0); + assert(region_e.id() == e.id()); + mcount++; + }); + if (mcount == 0) + cell_e.add( + region_e); + } + return cell_e; } +static inline int int_distance(const Vector2i &v1, const Vector2i &v2) +{ + Vector2i l = v2 - v1; + return l.x * l.x + l.y * l.y; +} +#if 0 +void growth_regions::split_region( + flecs::entity grid_floor_e, int region_index, + const List > ®ion_list) +{ + int i, j; + struct make_random r(173); + int grid_size = grid_floor_e + .get() + ->grid_size; + growth_regions::region parent_region = regions[region_index]; + // can't split rooms + assert(!grid_floor_e.world() + .entity(parent_region.region_et) + .has()); + regions.remove(region_index); + RegionRect2i clip_rect = parent_region.rect; + int parent_region_index = parent_regions.size(); + parent_regions.push_back(parent_region); + flecs::log::warn("moved parent region %d", region_index); + assert(clip_rect.get_area() > region_list.size()); + const List >::Element *e = + region_list.front(); + Set positions; + LocalVector > new_start; + int base_index = 0; + while (e) { + Vector2i pos; + int iterations = 1000; + while (1) { + int i; + pos = clip_rect.position + + Vector2i(r.get() % clip_rect.size.x, + r.get() % clip_rect.size.y); + assert(clip_rect.has_point(pos)); + if (!positions.has(pos)) { + bool ok = true; + for (i = 0; i < (int)new_start.size(); i++) { + if (iterations < 100 && + int_distance(new_start[i].second, + pos) < 2) { + ok = false; + break; + } + if (iterations < 200 && + int_distance(new_start[i].second, + pos) < 1) { + ok = false; + break; + } + } + if (ok) { + positions.insert(pos); + break; + } + } + iterations--; + if (iterations < 0) { + assert(false); + break; + } + } + for (i = 0; i < regions.size(); i++) { + if (parent_region_index == regions[i].parent_region) + flecs::log::dbg( + "sibling regions: %d: %s", i, + (regions[i].rect.operator String()) + .ascii() + .ptr()); + } + flecs::log::dbg("cell: position: %d, %d", pos.x, pos.y); + for (i = 0; i < regions.size(); i++) + assert(parent_regions[regions[i].parent_region] + .rect.encloses(regions[i].rect)); + for (i = 0; i < regions.size(); i++) + for (j = 0; j < regions.size(); j++) { + if (i == j) + continue; + if (regions[i].rect.encloses(regions[j].rect) || + regions[j].rect.intersects( + regions[i].rect) || + regions[i].rect.intersects(regions[j].rect)) + assert(false); + } + assert(parent_region.rect.has_point(pos)); + struct region reg; + new_start.push_back({ e->get().first, pos }); + reg.can_grow = true; + reg.can_grow_square = true; + reg.complete = false; + reg.parent = parent_region.region_et; + reg.parent_region = parent_region_index; + reg.rect = RegionRect2i(pos, Vector2i(1, 1)); + reg.region_et = e->get().first.id(); + reg.remains_area = (int)Math::ceil(e->get().second); + int cell_id = pos.x + grid_size * pos.y; + flecs::entity parent_e = + grid_floor_e.world().entity(reg.parent); + flecs::entity cell_e = update_cell(grid_floor_e, parent_e, + e->get().first, cell_id); + reg.seed_et = cell_e.id(); + regions.push_back(reg); + e = e->next(); + base_index++; + complete = false; + } +} +#endif void BuildingLayoutGraph::graph_module::zones_graph_module( flecs::world &ecs, const String &module_name) { @@ -451,7 +638,8 @@ void BuildingLayoutGraph::graph_module::zones_graph_module( buildings_layout_floor_index &rindex) { if (index.index == rindex.index) { - sum += MAX(rarea.area, MIN_ROOM_SIZE); + sum += MAX(rarea.area, MIN_ROOM_SIZE) * + 10 / 8; assert(sum >= 0.0f); count_rooms++; } @@ -567,6 +755,7 @@ BuildingLayoutGraph::graph_module::graph_module(flecs::world &ecs) ecs.component() .member("index"); ecs.component(); + ecs.component(); #if 0 ecs.component< WorldEditor::components::buildings_layout_commands::command>() @@ -1072,10 +1261,11 @@ BuildingLayoutGraph::graph_module::graph_module(flecs::world &ecs) ->get_layout_grid_base(); flecs::entity grid_e = grid_base_e.lookup(graph_e.name()); - flecs::log::warn("deleting entity %s", - grid_e.path().c_str()); - if (grid_e.is_valid()) + if (grid_e.is_valid()) { + flecs::log::warn("deleting entity %s", + grid_e.path().c_str()); grid_e.destruct(); + } it.world().defer_resume(); }); ecs.system( diff --git a/src/modules/stream/ui/graph_module.h b/src/modules/stream/ui/graph_module.h index 5a8aa3d..ee6fa25 100644 --- a/src/modules/stream/ui/graph_module.h +++ b/src/modules/stream/ui/graph_module.h @@ -1,87 +1,235 @@ #ifndef GRAPH_MODULE_H_ #define GRAPH_MODULE_H_ -struct growth_regions { - struct region { - flecs::entity_t parent; - flecs::entity_t seed_et; - flecs::entity_t region_et; - Rect2i rect; - int remains_area; - bool can_grow_square; - bool can_grow; - bool complete; - bool can_grow_region() const - { - bool ret = can_grow; - if (remains_area <= 0) - ret = false; - return ret; +#include +#include "grid_misc.h" +#include "grow_job.h" +struct RegionRect2i { + Point2i position; + Size2i size; + + const Point2i &get_position() const + { + return position; + } + void set_position(const Point2i &p_position) + { + position = p_position; + } + const Size2i &get_size() const + { + return size; + } + void set_size(const Size2i &p_size) + { + size = p_size; + } + + int get_area() const + { + return size.width * size.height; + } + + _FORCE_INLINE_ Vector2i get_center() const + { + return position + (size / 2); + } + + inline bool intersects(const RegionRect2i &p_rect) const + { + if (position.x > (p_rect.position.x + p_rect.size.width - 1)) { + return false; } - bool update_region_size(Rect2i &mrect) - { - bool ret = false; - int old_area = rect.get_area(); - int new_area = mrect.get_area(); - int area_diff = new_area - old_area; - if (area_diff > 0) { - rect = mrect; - remains_area -= area_diff; - ret = true; - } - if (remains_area <= 0) { - can_grow_square = false; - can_grow = false; - } - flecs::log::dbg("update_region_size %d -> %d", - area_diff, ret); - return ret; + if ((position.x + size.width - 1) < p_rect.position.x) { + return false; } + if (position.y > (p_rect.position.y + p_rect.size.height - 1)) { + return false; + } + if ((position.y + size.height - 1) < p_rect.position.y) { + return false; + } + + return true; + } + + inline bool encloses(const RegionRect2i &p_rect) const + { + return (p_rect.position.x >= position.x) && + (p_rect.position.y >= position.y) && + ((p_rect.position.x + p_rect.size.x) <= + (position.x + size.x)) && + ((p_rect.position.y + p_rect.size.y) <= + (position.y + size.y)); + } + + _FORCE_INLINE_ bool has_no_area() const + { + return (size.x <= 0 || size.y <= 0); + } + inline RegionRect2i clip(const RegionRect2i &p_rect) const + { /// return a clipped rect + + RegionRect2i new_rect = p_rect; + + if (!intersects(new_rect)) { + return RegionRect2i(); + } + + new_rect.position.x = MAX(p_rect.position.x, position.x); + new_rect.position.y = MAX(p_rect.position.y, position.y); + + Point2 p_rect_end = p_rect.position + p_rect.size; + Point2 end = position + size; + + new_rect.size.x = + (int)(MIN(p_rect_end.x, end.x) - new_rect.position.x); + new_rect.size.y = + (int)(MIN(p_rect_end.y, end.y) - new_rect.position.y); + + return new_rect; + } + + inline RegionRect2i merge(const RegionRect2i &p_rect) const + { ///< return a merged rect + + RegionRect2i new_rect; + + new_rect.position.x = MIN(p_rect.position.x, position.x); + new_rect.position.y = MIN(p_rect.position.y, position.y); + + new_rect.size.x = MAX(p_rect.position.x + p_rect.size.x, + position.x + size.x); + new_rect.size.y = MAX(p_rect.position.y + p_rect.size.y, + position.y + size.y); + + new_rect.size = + new_rect.size - new_rect.position; //make relative again + + return new_rect; }; - Vector regions; - bool complete; - bool check_region(int index, const Rect2i &rect) const + bool has_point(const Point2 &p_point) const { - int i; - bool ret = true; - for (i = 0; i < regions.size(); i++) { - if (i == index) - continue; - if (rect.intersects(regions[i].rect)) { - ret = false; - break; - } + if (p_point.x < position.x) { + return false; } - flecs::log::dbg("check_region: %d -> %d", index, ret); - return ret; + if (p_point.y < position.y) { + return false; + } + + if (p_point.x >= (position.x + size.x)) { + return false; + } + if (p_point.y >= (position.y + size.y)) { + return false; + } + + return true; } - bool update_region_size(int index, Rect2i &mrect) + + bool operator==(const RegionRect2i &p_rect) const + { + return position == p_rect.position && size == p_rect.size; + } + bool operator!=(const RegionRect2i &p_rect) const + { + return position != p_rect.position || size != p_rect.size; + } + + RegionRect2i grow(int p_by) const + { + RegionRect2i g = *this; + g.position.x -= p_by; + g.position.y -= p_by; + g.size.width += p_by * 2; + g.size.height += p_by * 2; + return g; + } + + inline RegionRect2i grow_margin(Margin p_margin, int p_amount) const + { + RegionRect2i g = *this; + g = g.grow_individual((MARGIN_LEFT == p_margin) ? p_amount : 0, + (MARGIN_TOP == p_margin) ? p_amount : 0, + (MARGIN_RIGHT == p_margin) ? p_amount : 0, + (MARGIN_BOTTOM == p_margin) ? p_amount : + 0); + return g; + } + + inline RegionRect2i grow_individual(int p_left, int p_top, int p_right, + int p_bottom) const + { + RegionRect2i g = *this; + g.position.x -= p_left; + g.position.y -= p_top; + g.size.width += p_left + p_right; + g.size.height += p_top + p_bottom; + + return g; + } + + _FORCE_INLINE_ RegionRect2i expand(const Vector2i &p_vector) const + { + RegionRect2i r = *this; + r.expand_to(p_vector); + return r; + } + + inline void expand_to(const Point2i &p_vector) + { + Point2i begin = position; + Point2i end = position + size; + + if (p_vector.x < begin.x) { + begin.x = p_vector.x; + } + if (p_vector.y < begin.y) { + begin.y = p_vector.y; + } + + if (p_vector.x > end.x) { + end.x = p_vector.x; + } + if (p_vector.y > end.y) { + end.y = p_vector.y; + } + + position = begin; + size = end - begin; + } + + operator String() const + { + return String(position) + ", " + String(size); + } + + operator Rect2() const + { + return Rect2(position, size); + } + RegionRect2i(const Rect2 &p_r2) + : position(p_r2.position) + , size(p_r2.size) + { + } + RegionRect2i(const Rect2i &p_r2) + : position(p_r2.position) + , size(p_r2.size) + { + } + RegionRect2i() + { + } + RegionRect2i(int p_x, int p_y, int p_width, int p_height) + : position(Point2(p_x, p_y)) + , size(Size2(p_width, p_height)) + { + } + RegionRect2i(const Point2 &p_pos, const Size2 &p_size) + : position(p_pos) + , size(p_size) { - bool ret = false; - bool ok = check_region(index, mrect); - if (ok) - ret = regions.write[index].update_region_size(mrect); - flecs::log::dbg("update_region_size %d -> %d", index, ret); - return ret; } - void create_region(flecs::entity floor_e, flecs::entity seed_e, - flecs::entity parent_e, flecs::entity region_e, - const Vector2i &position, float area); - flecs::entity create_cell(flecs::entity floor_e, flecs::entity region_e, - int id); }; -struct make_random { - int seed; - int next; - make_random(int seed) - : seed(seed) - , next(seed) - { - } - int get() - { - next = next * 1103515245 + 12345; - return (int)((unsigned)next >> 16) % 32768; - } -}; #endif \ No newline at end of file diff --git a/src/modules/stream/ui/region_tree.cpp b/src/modules/stream/ui/region_tree.cpp new file mode 100644 index 0000000..4768c24 --- /dev/null +++ b/src/modules/stream/ui/region_tree.cpp @@ -0,0 +1,477 @@ +#undef NDEBUG +#include +#include +#include +#include "region_tree.h" + +void region_tree::split(const List ®ions) +{ + assert(children.size() == 0); + const List::Element *e = regions.front(); + while (e) { + struct region_tree *child = memnew(struct region_tree); + child->parent = this; + child->region = e->get(); + children.push_back(child); + e = e->next(); + } +} + +const region_tree *region_tree::find(flecs::entity_t which) const +{ + int i; + List queue; + queue.push_back(this); + while (!queue.empty()) { + const struct region_tree *item = queue.front()->get(); + if (item->region.region_et == which) + return item; + queue.pop_front(); + for (i = 0; i < item->children.size(); i++) + queue.push_back(item->children[i]); + } + return nullptr; +} + +region_tree *region_tree::find(flecs::entity_t which) +{ + int i; + List queue; + queue.push_back(this); + while (!queue.empty()) { + struct region_tree *item = queue.front()->get(); + if (item->region.region_et == which) + return item; + queue.pop_front(); + for (i = 0; i < item->children.size(); i++) + queue.push_back(item->children[i]); + } + return nullptr; +} + +void region_tree::dump(flecs::entity grid_floor_e) const +{ + List > queue; + queue.push_back({ 0, this }); + flecs::log::warn("tree dump:"); + while (!queue.empty()) { + int tabs = queue.front()->get().first; + const struct region_tree *item = queue.front()->get().second; + queue.pop_front(); + String stabs = "\t"; + int i; + for (i = 0; i < tabs; i++) + stabs += "\t"; + flecs::log::warn("%sregion: %s", stabs.ascii().ptr(), + grid_floor_e.world() + .entity(item->region.region_et) + .path() + .c_str()); + flecs::log::warn( + "%srect: %s", stabs.ascii().ptr(), + (item->region.rect.operator String()).ascii().ptr()); + flecs::log::warn(""); + for (i = 0; i < item->children.size(); i++) + queue.push_back({ tabs + 1, item->children[i] }); + } +} + +void region_tree::grow() +{ +#ifndef TESTS + List grow_list; + List queue; + List::Element *e, *e1; + if (region.complete) + queue.push_back(this); + while (!queue.empty()) { + int i; + struct region_tree *item = queue.front()->get(); + queue.pop_front(); + if (item->region.can_grow || item->region.can_grow_square) + continue; + for (i = 0; i < (int)item->children.size(); i++) { + if (item->children[i]->region.can_grow) + grow_list.push_back(item->children[i]); + queue.push_back(item->children[i]); + } + } + flecs::log::warn("grow list: %d", grow_list.size()); + e = grow_list.front(); + int amount = 0; + while (e) { + struct region_tree *item = e->get(); + flecs::log::warn( + "item: %s", + (item->region.rect.operator String()).ascii().ptr()); + if (!item->region.can_grow) { + e = e->next(); + flecs::log::err("can't grow"); + continue; + } + RegionRect2i candidate = item->region.rect.grow(1); + e1 = grow_list.front(); + bool ok = true; + while (e1) { + struct region_tree *check_item = e1->get(); + if (item != check_item) { + flecs::log::warn("check item: %s candidate: %s", + (check_item->region.rect + .operator String()) + .ascii() + .ptr(), + (candidate.operator String()) + .ascii() + .ptr()); + if (!check_item->check_candidate(candidate)) { + ok = false; + flecs::log::err("check failed"); + break; + } + } + e1 = e1->next(); + } + if (ok) { + item->update_candidate(candidate); + amount++; + } else { + flecs::log::err("can't update candidate"); + } + e = e->next(); + } +#if 0 + assert(false); + if (region.complete) + queue.push_back(this); + while (!queue.empty()) { + struct region_tree *item = queue.front()->get(); + queue.pop_front(); + if (item->region.can_grow || item->region.can_grow_square) + continue; + int i, j; + int amount = 0; + amount = 0; + for (i = 0; i < (int)item->children.size(); i++) { + if (item->children[i]->region.can_grow_square) { + RegionRect2i candidate = + item->children[i]->region.rect.grow(1); + if (!item->region.rect.encloses(candidate)) { + item->children.write[i] + ->region.can_grow_square = + false; + continue; + } + bool ok = item->check_candidate(i, candidate); + if (ok) { + item->children.write[i] + ->update_candidate(candidate); + amount++; + } else + item->children.write[i] + ->region.can_grow_square = + false; + } + } + if (amount > 0) /* try again later */ + queue.push_back(item); +#if 0 + for (i = 0; i < (int)item->children.size(); i++) { + if (!can_grow_square) + item->children.write[i] + ->region.can_grow_square = + false; + } + while (can_grow) { + amount = 0; + for (i = 0; i < (int)item->children.size(); + i++) { + if (!item->children[i]->region.can_grow) + continue; + int orig_area = + item->children[i] + ->region.rect.get_area(); + RegionRect2i candidate = + item->children[i]->region.rect; + candidate.size.x += 1; + if (!item->region.rect.encloses( + candidate)) + continue; + bool ok = item->check_candidate( + i, candidate); + if (ok) { + item->children.write[i] + ->update_candidate( + candidate); + amount++; + } + } + for (i = 0; i < (int)item->children.size(); + i++) { + if (!item->children[i]->region.can_grow) + continue; + int orig_area = + item->children[i] + ->region.rect.get_area(); + RegionRect2i candidate = + item->children[i]->region.rect; + candidate.size.x += 1; + candidate.position.x -= 1; + if (!item->region.rect.encloses( + candidate)) + continue; + bool ok = item->check_candidate( + i, candidate); + if (ok) { + item->children.write[i] + ->update_candidate( + candidate); + amount++; + } + } + for (i = 0; i < (int)item->children.size(); + i++) { + if (!item->children[i]->region.can_grow) + continue; + int orig_area = + item->children[i] + ->region.rect.get_area(); + RegionRect2i candidate = + item->children[i]->region.rect; + candidate.size.y += 1; + if (!item->region.rect.encloses( + candidate)) + continue; + bool ok = item->check_candidate( + i, candidate); + if (ok) { + item->children.write[i] + ->update_candidate( + candidate); + amount++; + } + } + for (i = 0; i < (int)item->children.size(); + i++) { + if (!item->children[i]->region.can_grow) + continue; + int orig_area = + item->children[i] + ->region.rect.get_area(); + RegionRect2i candidate = + item->children[i]->region.rect; + candidate.size.y += 1; + candidate.position.y -= 1; + if (!item->region.rect.encloses( + candidate)) + continue; + bool ok = item->check_candidate( + i, candidate); + if (ok) { + item->children.write[i] + ->update_candidate( + candidate); + amount++; + } + } + for (i = 0; i < (int)item->children.size(); + i++) { + if (item->children[i] + ->region.remains_area < + -100) { + item->children[i] + ->region.can_grow = + false; + item->children[i] + ->region.complete = + true; + } + } + if (amount == 0) + can_grow = false; + } +#endif + for (i = 0; i < (int)item->children.size(); i++) { + queue.push_back(item->children[i]); + } + break; + } +#endif +#endif +} + +void region_tree::place(flecs::entity grid_floor_e) const +{ +#ifndef TESTS + int i, j; + List queue; + int grid_size = grid_floor_e + .get() + ->grid_size; + for (i = region.rect.position.x; + i < region.rect.position.x + region.rect.size.x; i++) + for (j = region.rect.position.y; + j < region.rect.position.y + region.rect.size.y; j++) { + int cell_id = i + grid_size * j; + String cname = "cell_" + itos(cell_id); + flecs::entity cell_e = + grid_floor_e.lookup(cname.ascii().ptr()); + if (cell_e.is_valid()) + cell_e.destruct(); + } + queue.push_back(this); + while (!queue.empty()) { + const struct region_tree *item = queue.front()->get(); + queue.pop_front(); + if (item->is_leaf()) { + const RegionRect2i &rect = item->region.rect; + for (i = rect.position.x; + i < rect.position.x + rect.size.x; i++) + for (j = rect.position.x; + j < rect.position.x + rect.size.x; j++) { + int cell_id = i + grid_size * j; + update_cell(grid_floor_e, + item->region.parent, + item->region.region_et, + cell_id); + } + } + for (i = 0; i < (int)item->children.size(); i++) { + queue.push_back(item->children[i]); + } + } +#endif +} + +void region_tree::get_rects(List *rect_list) const +{ +#ifndef TESTS + List queue; + queue.push_back(this); + while (!queue.empty()) { + int i; + const struct region_tree *item = queue.front()->get(); + queue.pop_front(); + if (item->children.size() == 0) { + flecs::log::warn("rect: %s", + (item->region.rect.operator String()) + .ascii() + .ptr()); + rect_list->push_back(item->region.rect); + } + for (i = 0; i < (int)item->children.size(); i++) + queue.push_back(item->children[i]); + } +#endif +} + +bool region_tree::check_candidate(int i, const RegionRect2i &candidate) const +{ + int j; + bool ok = true; +#ifndef TESTS + if (!region.rect.encloses(candidate)) + return false; + for (j = 0; j < children.size(); j++) { + if (i == j) + continue; + if (candidate.intersects(children[j]->region.rect)) { + ok = false; + break; + } + if (candidate.encloses(children[j]->region.rect)) { + ok = false; + break; + } + if (children[j]->region.rect.encloses(candidate)) { + ok = false; + break; + } + if (children[j]->region.rect.intersects(candidate)) { + ok = false; + break; + } + } +#endif + return ok; +} + +bool region_tree::check_candidate(const RegionRect2i &candidate) const +{ + bool ok = true; +#ifndef TESTS + if (parent && !parent->region.rect.encloses(candidate)) + return false; + if (candidate.intersects(region.rect)) + ok = false; + if (candidate.encloses(region.rect)) + ok = false; + if (region.rect.encloses(candidate)) + ok = false; + if (region.rect.intersects(candidate)) + ok = false; +#endif + return ok; +} + +flecs::entity region_tree::update_cell(flecs::entity grid_floor_e, + flecs::entity_t parent_et, + flecs::entity_t region_et, int id) const +{ + flecs::entity region_e = grid_floor_e.world().entity(region_et); + flecs::entity parent_e = grid_floor_e.world().entity(parent_et); + flecs::log::dbg("create_cell: %s %d", region_e.path().c_str(), id); +#ifndef TESTS + String pname("cell_" + itos(id)); + flecs::entity cell_e = grid_floor_e.lookup(pname.ascii().ptr()); + if (!cell_e.is_valid()) { + flecs::log::warn("creating new cell %s", pname.ascii().ptr()); + cell_e = grid_floor_e.world() + .entity(pname.ascii().ptr()) + .child_of(grid_floor_e); + assert(cell_e.is_valid()); + } + // assert(cell_e.has(parent_e)); + /* already there */ + grid_floor_e + .get_mut() + ->cells.insert(id); + grid_floor_e.modified< + WorldEditor::components::buildings_layout_grid_floor>(); + cell_e.set( + { String(region_e.name()), id }); + /* cell_e.remove(parent_e); */ + cell_e.add(region_e); + if (region_e.has()) { + int mcount = 0; + cell_e.each( + [&mcount, region_e, cell_e](flecs::entity e) { + flecs::log::err("adding region %s to cell %s", + region_e.path().c_str(), + cell_e.path().c_str()); + flecs::log::err("already belongs to %s", + e.path().c_str()); + assert(mcount == 0); + assert(region_e.id() == e.id()); + mcount++; + }); + if (mcount == 0) + cell_e.add( + region_e); + } +#else + flecs::entity cell_e; +#endif + return cell_e; +} + +void region_tree::update_candidate(const RegionRect2i &candidate) +{ +#if 0 + int orig_area = region.rect.get_area(); + region.rect = candidate; + int new_area = candidate.get_area(); + int avalue = MAX(0, new_area - orig_area); + region.remains_area -= avalue; +#endif +} diff --git a/src/modules/stream/ui/region_tree.h b/src/modules/stream/ui/region_tree.h new file mode 100644 index 0000000..8a692bc --- /dev/null +++ b/src/modules/stream/ui/region_tree.h @@ -0,0 +1,29 @@ +/* ~/godot-projects/streaming_world/src/modules/stream/ui/region_tree.h */ +#ifndef REGION_TREE_H_ +#define REGION_TREE_H_ +#include "growth_regions.h" +struct region_tree { + struct region region; + Vector children; + struct region_tree *parent; + void split(const List ®ions); + const struct region_tree *find(flecs::entity_t which) const; + struct region_tree *find(flecs::entity_t which); + bool is_leaf() const + { + return children.size() == 0; + } + void dump(flecs::entity grid_floor_e) const; + void grow(); + void place(flecs::entity grid_floor_e) const; + void get_rects(List *rect_list) const; + +private: + bool check_candidate(int i, const RegionRect2i &candidate) const; + bool check_candidate(const RegionRect2i &candidate) const; + flecs::entity update_cell(flecs::entity grid_floor_e, + flecs::entity_t parent_et, + flecs::entity_t region_et, int id) const; + void update_candidate(const RegionRect2i &candidate); +}; +#endif // REGION_TREE_H_ \ No newline at end of file diff --git a/tests/Makefile b/tests/Makefile index a5e997a..d45a9e7 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -1,17 +1,64 @@ -all: graph_test entity_test rect2i +all: graph_test entity_test rect2i regions +LIBS= +# LIBS= ./godot_mockery/libgodot-mockery.a -CFLAGS = -I../src/flecs/distr -CXXFLAGS = -I../src/flecs/distr -I../src/modules/stream/ui -I../src/godot -I../src/godot/platform/x11 +CFLAGS = -I../src/flecs/distr -g +CXXFLAGS = -I../src/flecs/distr -I../src/modules/stream/ui -I./godot_mockery -I../src/godot -I../src/godot/platform/x11 -I. -I../src/modules/stream -g +#CXXFLAGS = -I../src/flecs/distr -I../src/modules/stream/ui -I../src/godot -I../src/godot/platform/x11 -I. -I../src/modules/stream -g -graph_test: graph_module.o flecs.o - $(CXX) -o $@ graph_module.o flecs.o +graph_test: graph_module.o flecs.o $(LIBS) + $(CXX) -o $@ graph_module.o flecs.o $(LIBS) -entity_test: entity_test.o flecs.o - $(CXX) -o $@ entity_test.o flecs.o +entity_test: entity_test.o flecs.o $(LIBS) + $(CXX) -o $@ entity_test.o flecs.o $(LIBS) flecs.o: ../src/flecs/distr/flecs.c $(CC) -o $@ -c $< -rect2i: rect2i.o - $(CXX) -o $@ rect2i.o +rect2i: rect2i.o $(LIBS) + $(CXX) -o $@ rect2i.o $(LIBS) -.PHONY: all +region_tree.o: ../src/modules/stream/ui/region_tree.cpp + $(CC) $(CFLAGS) $(CXXFLAGS) -DTESTS -o $@ -c $< + +regions: regions.o flecs.o region_tree.o godot_mockery/core/os/memory.o godot_mockery/core/ustring.o $(LIBS) + $(CXX) -o $@ regions.o flecs.o region_tree.o godot_mockery/core/os/memory.o godot_mockery/core/ustring.o $(LIBS) + +MOCK_OBJS=godot_mockery/core/os/memory.o godot_mockery/core/error_macros.o \ + godot_mockery/core/ustring.o godot_mockery/core/array.o \ + godot_mockery/core/variant.o godot_mockery/core/variant_op.o \ + godot_mockery/core/pool_allocator.o godot_mockery/core/node_path.o \ + godot_mockery/core/pool_vector.o godot_mockery/core/reference.o \ + godot_mockery/core/object.o godot_mockery/core/ref_ptr.o \ + godot_mockery/core/string_name.o godot_mockery/core/method_bind.o \ + godot_mockery/core/class_db.o godot_mockery/core/dictionary.o \ + godot_mockery/core/color.o godot_mockery/core/math/plane.o \ + godot_mockery/core/math/transform.o godot_mockery/core/math/basis.o \ + godot_mockery/core/math/vector2.o godot_mockery/core/math/vector3.o \ + godot_mockery/core/math/math_funcs.o godot_mockery/core/resource.o \ + godot_mockery/core/math/random_pcg.o godot_mockery/core/math/transform_2d.o \ + godot_mockery/core/core_string_names.o godot_mockery/core/os/os.o \ + + + +./godot_mockery/libgodot-mockery.a: $(MOCK_OBJS) + ar rcs $@ $(MOCK_OBJS) + +godot_mockery/core/os/memory.o: godot_mockery/core/os/memory.cpp + $(CC) $(CFLAGS) $(CXXFLAGS) -DTESTS -o $@ -c $< +godot_mockery/core/error_macros.o: godot_mockery/core/error_macros.cpp + $(CC) $(CFLAGS) $(CXXFLAGS) -DTESTS -o $@ -c $< +godot_mockery/core/ustring.o: godot_mockery/core/ustring.cpp + $(CC) $(CFLAGS) $(CXXFLAGS) -DTESTS -o $@ -c $< +godot_mockery/core/array.o: ../src/godot/core/array.cpp + $(CC) $(CFLAGS) $(CXXFLAGS) -DTESTS -o $@ -c $< +godot_mockery/core/%.o: ../src/godot/core/%.cpp + $(CC) $(CFLAGS) $(CXXFLAGS) -DTESTS -o $@ -c $< +godot_mockery/core/math/%.o: ../src/godot/core/math/%.cpp + mkdir -p godot_mockery/core/math + $(CC) $(CFLAGS) $(CXXFLAGS) -DTESTS -o $@ -c $< + +clean: + rm -f godot_mockery/core/*.o godot_mockery/core/os/*.o *.o graph_test entity_test rect2i regions + + +.PHONY: all clean diff --git a/tests/entity_test b/tests/entity_test deleted file mode 100755 index 25f05a0..0000000 Binary files a/tests/entity_test and /dev/null differ diff --git a/tests/godot_mockery/core/cowdata.h b/tests/godot_mockery/core/cowdata.h new file mode 100644 index 0000000..671f46d --- /dev/null +++ b/tests/godot_mockery/core/cowdata.h @@ -0,0 +1,379 @@ +/**************************************************************************/ +/* cowdata.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef COWDATA_H +#define COWDATA_H + +#include +#include + +#include "core/error_macros.h" +#include "core/os/memory.h" +#include "core/safe_refcount.h" + +template +class Vector; +class String; +class CharString; +template +class VMap; + +#if !defined(NO_THREADS) +SAFE_NUMERIC_TYPE_PUN_GUARANTEES(uint32_t) +#endif + +template +class CowData { + template + friend class Vector; + friend class String; + friend class CharString; + template + friend class VMap; + +private: + mutable T *_ptr; + + // internal helpers + + _FORCE_INLINE_ SafeNumeric *_get_refcount() const { + if (!_ptr) { + return nullptr; + } + + return reinterpret_cast *>(_ptr) - 2; + } + + _FORCE_INLINE_ uint32_t *_get_size() const { + if (!_ptr) { + return nullptr; + } + + return reinterpret_cast(_ptr) - 1; + } + + _FORCE_INLINE_ size_t _get_alloc_size(size_t p_elements) const { + //return nearest_power_of_2_templated(p_elements*sizeof(T)+sizeof(SafeRefCount)+sizeof(int)); + return next_power_of_2(p_elements * sizeof(T)); + } + + _FORCE_INLINE_ bool _get_alloc_size_checked(size_t p_elements, size_t *out) const { +#if defined(_add_overflow) && defined(_mul_overflow) + size_t o; + size_t p; + if (_mul_overflow(p_elements, sizeof(T), &o)) { + *out = 0; + return false; + } + *out = next_power_of_2(o); + if (_add_overflow(o, static_cast(32), &p)) { + return false; //no longer allocated here + } + return true; +#else + // Speed is more important than correctness here, do the operations unchecked + // and hope the best + *out = _get_alloc_size(p_elements); + return true; +#endif + } + + void _unref(void *p_data); + void _ref(const CowData *p_from); + void _ref(const CowData &p_from); + uint32_t _copy_on_write(); + +public: + void operator=(const CowData &p_from) { _ref(p_from); } + + _FORCE_INLINE_ T *ptrw() { + _copy_on_write(); + return _ptr; + } + + _FORCE_INLINE_ const T *ptr() const { + return _ptr; + } + + _FORCE_INLINE_ int size() const { + uint32_t *size = (uint32_t *)_get_size(); + if (size) { + return *size; + } else { + return 0; + } + } + + _FORCE_INLINE_ void clear() { resize(0); } + _FORCE_INLINE_ bool empty() const { return _ptr == nullptr; } + + _FORCE_INLINE_ void set(int p_index, const T &p_elem) { + assert(p_index < size()); + _copy_on_write(); + _ptr[p_index] = p_elem; + } + + _FORCE_INLINE_ T &get_m(int p_index) { + CRASH_BAD_INDEX(p_index, size()); + _copy_on_write(); + return _ptr[p_index]; + } + + _FORCE_INLINE_ const T &get(int p_index) const { + CRASH_BAD_INDEX(p_index, size()); + + return _ptr[p_index]; + } + + Error resize(int p_size); + + _FORCE_INLINE_ void remove(int p_index) { + ERR_FAIL_INDEX(p_index, size()); + T *p = ptrw(); + int len = size(); + for (int i = p_index; i < len - 1; i++) { + p[i] = p[i + 1]; + }; + + resize(len - 1); + }; + + Error insert(int p_pos, const T &p_val) { + ERR_FAIL_INDEX_V(p_pos, size() + 1, ERR_INVALID_PARAMETER); + resize(size() + 1); + for (int i = (size() - 1); i > p_pos; i--) { + set(i, get(i - 1)); + } + set(p_pos, p_val); + + return OK; + }; + + int find(const T &p_val, int p_from = 0) const; + + _FORCE_INLINE_ CowData(); + _FORCE_INLINE_ ~CowData(); + _FORCE_INLINE_ CowData(CowData &p_from) { _ref(p_from); }; +}; + +template +void CowData::_unref(void *p_data) { + if (!p_data) { + return; + } + + SafeNumeric *refc = _get_refcount(); + + if (refc->decrement() > 0) { + return; // still in use + } + // clean up + + if (!std::is_trivially_destructible::value) { + uint32_t *count = _get_size(); + T *data = (T *)(count + 1); + + for (uint32_t i = 0; i < *count; ++i) { + // call destructors + data[i].~T(); + } + } + + // free mem + Memory::free_static((uint8_t *)p_data, true); +} + +template +uint32_t CowData::_copy_on_write() { + if (!_ptr) { + return 0; + } + + SafeNumeric *refc = _get_refcount(); + + uint32_t rc = refc->get(); + if (likely(rc > 1)) { + /* in use by more than me */ + uint32_t current_size = *_get_size(); + + uint32_t *mem_new = (uint32_t *)Memory::alloc_static(_get_alloc_size(current_size), true); + + new (mem_new - 2, sizeof(uint32_t), "") SafeNumeric(1); //refcount + *(mem_new - 1) = current_size; //size + + T *_data = (T *)(mem_new); + + // initialize new elements + if (std::is_trivially_copyable::value) { + memcpy(mem_new, _ptr, current_size * sizeof(T)); + + } else { + for (uint32_t i = 0; i < current_size; i++) { + memnew_placement(&_data[i], T(_ptr[i])); + } + } + + _unref(_ptr); + _ptr = _data; + + rc = 1; + } + return rc; +} + +template +Error CowData::resize(int p_size) { + assert(p_size >= 0); + + int current_size = size(); + + if (p_size == current_size) { + return OK; + } + + if (p_size == 0) { + // wants to clean up + _unref(_ptr); + _ptr = nullptr; + return OK; + } + + // possibly changing size, copy on write + uint32_t rc = _copy_on_write(); + + size_t current_alloc_size = _get_alloc_size(current_size); + size_t alloc_size; + assert(_get_alloc_size_checked(p_size, &alloc_size)); + + if (p_size > current_size) { + if (alloc_size != current_alloc_size) { + if (current_size == 0) { + // alloc from scratch + uint32_t *ptr = (uint32_t *)Memory::alloc_static(alloc_size, true); + ERR_FAIL_COND_V(!ptr, ERR_OUT_OF_MEMORY); + *(ptr - 1) = 0; //size, currently none + new (ptr - 2, sizeof(uint32_t), "") SafeNumeric(1); //refcount + + _ptr = (T *)ptr; + + } else { + uint32_t *_ptrnew = (uint32_t *)Memory::realloc_static(_ptr, alloc_size, true); + ERR_FAIL_COND_V(!_ptrnew, ERR_OUT_OF_MEMORY); + new (_ptrnew - 2, sizeof(uint32_t), "") SafeNumeric(rc); //refcount + + _ptr = (T *)(_ptrnew); + } + } + + // construct the newly created elements + + if (!std::is_trivially_constructible::value) { + for (int i = *_get_size(); i < p_size; i++) { + memnew_placement(&_ptr[i], T); + } + } + + *_get_size() = p_size; + + } else if (p_size < current_size) { + if (!std::is_trivially_destructible::value) { + // deinitialize no longer needed elements + for (uint32_t i = p_size; i < *_get_size(); i++) { + T *t = &_ptr[i]; + t->~T(); + } + } + + if (alloc_size != current_alloc_size) { + uint32_t *_ptrnew = (uint32_t *)Memory::realloc_static(_ptr, alloc_size, true); + ERR_FAIL_COND_V(!_ptrnew, ERR_OUT_OF_MEMORY); + new (_ptrnew - 2, sizeof(uint32_t), "") SafeNumeric(rc); //refcount + + _ptr = (T *)(_ptrnew); + } + + *_get_size() = p_size; + } + + return OK; +} + +template +int CowData::find(const T &p_val, int p_from) const { + int ret = -1; + + if (p_from < 0 || size() == 0) { + return ret; + } + + for (int i = p_from; i < size(); i++) { + if (get(i) == p_val) { + ret = i; + break; + } + } + + return ret; +} + +template +void CowData::_ref(const CowData *p_from) { + _ref(*p_from); +} + +template +void CowData::_ref(const CowData &p_from) { + if (_ptr == p_from._ptr) { + return; // self assign, do nothing. + } + + _unref(_ptr); + _ptr = nullptr; + + if (!p_from._ptr) { + return; //nothing to do + } + + if (p_from._get_refcount()->conditional_increment() > 0) { // could reference + _ptr = p_from._ptr; + } +} + +template +CowData::CowData() { + _ptr = nullptr; +} + +template +CowData::~CowData() { + _unref(_ptr); +} + +#endif // COWDATA_H diff --git a/tests/godot_mockery/core/error_macros.cpp b/tests/godot_mockery/core/error_macros.cpp new file mode 100644 index 0000000..37440ad --- /dev/null +++ b/tests/godot_mockery/core/error_macros.cpp @@ -0,0 +1,176 @@ +/**************************************************************************/ +/* error_macros.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include +#include +#include +#include "error_macros.h" + +#if 0 + +#include "core/io/logger.h" +#include "core/ustring.h" +#include "os/os.h" + + +#if defined(DEBUG_ENABLED) && defined(TOOLS_ENABLED) +#include "scene/main/node.h" +#endif + +#endif + +static ErrorHandlerList *error_handler_list = nullptr; + +void add_error_handler(ErrorHandlerList *p_handler) { + _global_lock(); + p_handler->next = error_handler_list; + error_handler_list = p_handler; + _global_unlock(); +} + +void remove_error_handler(ErrorHandlerList *p_handler) { + _global_lock(); + + ErrorHandlerList *prev = nullptr; + ErrorHandlerList *l = error_handler_list; + + while (l) { + if (l == p_handler) { + if (prev) { + prev->next = l->next; + } else { + error_handler_list = l->next; + } + break; + } + prev = l; + l = l->next; + } + + _global_unlock(); +} + +void _err_print_error(const char *p_function, const char *p_file, int p_line, const char *p_error, ErrorHandlerType p_type) { + _err_print_error(p_function, p_file, p_line, p_error, "", p_type); +} + +void _err_print_error(const char *p_function, const char *p_file, int p_line, const String &p_error, ErrorHandlerType p_type) { + _err_print_error(p_function, p_file, p_line, p_error.utf8().get_data(), "", p_type); +} + +void _err_print_error(const char *p_function, const char *p_file, int p_line, const char *p_error, const char *p_message, ErrorHandlerType p_type) { + // Fallback if errors happen before OS init or after it's destroyed. + const char *err_details = (p_message && *p_message) ? p_message : p_error; + fprintf(stderr, "ERROR: %s\n at: %s (%s:%i)\n", err_details, p_function, p_file, p_line); + + _global_lock(); + ErrorHandlerList *l = error_handler_list; + while (l) { + l->errfunc(l->userdata, p_function, p_file, p_line, p_error, p_message, p_type); + l = l->next; + } + + _global_unlock(); +} + +void _err_print_error(const char *p_function, const char *p_file, int p_line, const String &p_error, const char *p_message, ErrorHandlerType p_type) { + _err_print_error(p_function, p_file, p_line, p_error.utf8().get_data(), p_message, p_type); +} + +void _err_print_error(const char *p_function, const char *p_file, int p_line, const char *p_error, const String &p_message, ErrorHandlerType p_type) { + _err_print_error(p_function, p_file, p_line, p_error, p_message.utf8().get_data(), p_type); +} + +void _err_print_error(const char *p_function, const char *p_file, int p_line, const String &p_error, const String &p_message, ErrorHandlerType p_type) { + _err_print_error(p_function, p_file, p_line, p_error.utf8().get_data(), p_message.utf8().get_data(), p_type); +} + +void _err_print_index_error(const char *p_function, const char *p_file, int p_line, int64_t p_index, int64_t p_size, const char *p_index_str, const char *p_size_str, const char *p_message, bool fatal) { + String fstr(fatal ? "FATAL: " : ""); + String err(fstr + "Index " + p_index_str + " = " + itos(p_index) + " is out of bounds (" + p_size_str + " = " + itos(p_size) + ")."); + _err_print_error(p_function, p_file, p_line, err.utf8().get_data(), p_message); +} + +void _err_print_index_error(const char *p_function, const char *p_file, int p_line, int64_t p_index, int64_t p_size, const char *p_index_str, const char *p_size_str, const String &p_message, bool fatal) { + _err_print_index_error(p_function, p_file, p_line, p_index, p_size, p_index_str, p_size_str, p_message.utf8().get_data(), fatal); +} + +void _err_flush_stdout() { + fflush(stdout); +} + +#if 0 +// Prevent error spam by limiting the warnings to a certain frequency. +void _physics_interpolation_warning(const char *p_function, const char *p_file, int p_line, ObjectID p_id, const char *p_warn_string) { +#if defined(DEBUG_ENABLED) && defined(TOOLS_ENABLED) + const uint32_t warn_max = 2048; + const uint32_t warn_timeout_seconds = 15; + + static uint32_t warn_count = warn_max; + static uint32_t warn_timeout = warn_timeout_seconds; + + uint32_t time_now = UINT32_MAX; + + if (warn_count) { + warn_count--; + } + + if (!warn_count) { + time_now = OS::get_singleton()->get_ticks_msec() / 1000; + } + + if ((warn_count == 0) && (time_now >= warn_timeout)) { + warn_count = warn_max; + warn_timeout = time_now + warn_timeout_seconds; + + if (GLOBAL_GET("debug/settings/physics_interpolation/enable_warnings")) { + // UINT64_MAX means unused. + if (p_id == UINT64_MAX) { + _err_print_error(p_function, p_file, p_line, "[Physics interpolation] " + String(p_warn_string) + " (possibly benign).", ERR_HANDLER_WARNING); + } else { + String node_name; + if (p_id != 0) { + if (ObjectDB::get_instance(p_id)) { + Node *node = Object::cast_to(ObjectDB::get_instance(p_id)); + if (node && node->is_inside_tree()) { + node_name = "\"" + String(node->get_path()) + "\""; + } else { + node_name = "\"unknown\""; + } + } + } + + _err_print_error(p_function, p_file, p_line, "[Physics interpolation] " + String(p_warn_string) + ": " + node_name + " (possibly benign).", ERR_HANDLER_WARNING); + } + } + } +#endif +} +#endif diff --git a/tests/godot_mockery/core/error_macros.h b/tests/godot_mockery/core/error_macros.h new file mode 100644 index 0000000..cc1c7fa --- /dev/null +++ b/tests/godot_mockery/core/error_macros.h @@ -0,0 +1,605 @@ +/**************************************************************************/ +/* error_macros.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef ERROR_MACROS_H +#define ERROR_MACROS_H +#include +#include +#include + +#define ERR_FAIL_COND_V_MSG(cond, val, msg) {assert(!(cond));} +#define ERR_FAIL_COND_MSG(cond, msg) {assert(!(cond));} +#define ERR_FAIL_COND_V(cond, val) {assert(!(cond));} +#define ERR_FAIL_COND(cond) {assert(!(cond));} +#define ERR_FAIL_V_MSG(val, msg) {assert(false);} +#define ERR_FAIL_V(val) {assert(false);} +#define ERR_FAIL_MSG(msg) {assert(false);} +#define CRASH_BAD_INDEX(idx, size) {assert(idx < size);} +#define ERR_FAIL_INDEX(idx, size) {assert(idx < size);} +#define ERR_FAIL_INDEX_V(idx, size, val) {assert(idx < size);} +#define ERR_PRINT(x) {assert(false);} +#define WARN_PRINT(x) {assert(false);} +#define CRASH_COND(x) {assert(!(x));} +#define DEV_ASSERT(x) {assert(x);} +#define CRASH_NOW() {assert(false);} + +enum ErrorHandlerType { + ERR_HANDLER_ERROR, + ERR_HANDLER_WARNING, + ERR_HANDLER_SCRIPT, + ERR_HANDLER_SHADER, +}; + + +#if 0 + +#include "core/object_id.h" +#include "core/safe_refcount.h" +#include "core/typedefs.h" + + +/** + * Error macros. Unlike exceptions and asserts, these macros try to maintain consistency and stability + * inside the code. It is recommended to always return processable data, so in case of an error, + * the engine can keep working well. + * In most cases, bugs and/or invalid data are not fatal and should never allow a perfectly running application + * to fail or crash. + */ + +/** + * Pointer to the error macro printing function. Reassign to any function to have errors printed + */ + +/** Function used by the error macros */ + +// function, file, line, error, explanation + +enum ErrorHandlerType { + ERR_HANDLER_ERROR, + ERR_HANDLER_WARNING, + ERR_HANDLER_SCRIPT, + ERR_HANDLER_SHADER, +}; + +class String; +typedef void (*ErrorHandlerFunc)(void *, const char *, const char *, int p_line, const char *, const char *, ErrorHandlerType p_type); + +struct ErrorHandlerList { + ErrorHandlerFunc errfunc; + void *userdata; + + ErrorHandlerList *next; + + ErrorHandlerList() { + errfunc = nullptr; + next = nullptr; + userdata = nullptr; + } +}; + +void add_error_handler(ErrorHandlerList *p_handler); +void remove_error_handler(ErrorHandlerList *p_handler); + +static inline void _err_print_error(const char *p_function, const char *p_file, int p_line, const char *p_error, ErrorHandlerType p_type = ERR_HANDLER_ERROR) {} +static inline void _err_print_error(const char *p_function, const char *p_file, int p_line, const String &p_error, ErrorHandlerType p_type = ERR_HANDLER_ERROR) {} +static inline void _err_print_error(const char *p_function, const char *p_file, int p_line, const char *p_error, const char *p_message, ErrorHandlerType p_type = ERR_HANDLER_ERROR) {} +static inline void _err_print_error(const char *p_function, const char *p_file, int p_line, const String &p_error, const char *p_message, ErrorHandlerType p_type = ERR_HANDLER_ERROR) {} +static inline void _err_print_error(const char *p_function, const char *p_file, int p_line, const char *p_error, const String &p_message, ErrorHandlerType p_type = ERR_HANDLER_ERROR) {} +static inline void _err_print_error(const char *p_function, const char *p_file, int p_line, const String &p_error, const String &p_message, ErrorHandlerType p_type = ERR_HANDLER_ERROR) {} +static inline void _err_print_index_error(const char *p_function, const char *p_file, int p_line, int64_t p_index, int64_t p_size, const char *p_index_str, const char *p_size_str, const char *p_message = "", bool fatal = false) {} +static inline void _err_print_index_error(const char *p_function, const char *p_file, int p_line, int64_t p_index, int64_t p_size, const char *p_index_str, const char *p_size_str, const String &p_message, bool fatal = false) {} +static inline void _err_flush_stdout() {} + +// void _physics_interpolation_warning(const char *p_function, const char *p_file, int p_line, ObjectID p_id, const char *p_warn_string); + +#ifndef _STR +#define _STR(m_x) #m_x +#define _MKSTR(m_x) _STR(m_x) +#endif + +#define _FNL __FILE__ ":" + +/** An index has failed if m_index<0 or m_index >=m_size, the function exits */ + +#ifdef __GNUC__ +//#define FUNCTION_STR __PRETTY_FUNCTION__ - too annoying +#define FUNCTION_STR __FUNCTION__ +#else +#define FUNCTION_STR __FUNCTION__ +#endif + +// Don't use this directly; instead, use any of the CRASH_* macros +#ifdef _MSC_VER +#define GENERATE_TRAP \ + __debugbreak(); \ + /* Avoid warning about control paths */ \ + for (;;) { \ + } +#else +#define GENERATE_TRAP __builtin_trap(); +#endif + +// (*): See https://stackoverflow.com/questions/257418/do-while-0-what-is-it-good-for + +/** + * If `m_index` is less than 0 or greater than or equal to `m_size`, prints a generic + * error message and returns from the function. This macro should be preferred to + * `ERR_FAIL_COND` for bounds checking. + */ +#define ERR_FAIL_INDEX(m_index, m_size) \ + if (unlikely((m_index) < 0 || (m_index) >= (m_size))) { \ + _err_print_index_error(FUNCTION_STR, __FILE__, __LINE__, m_index, m_size, _STR(m_index), _STR(m_size)); \ + return; \ + } else \ + ((void)0) + +/** + * If `m_index` is less than 0 or greater than or equal to `m_size`, prints a custom + * error message and returns from the function. This macro should be preferred to + * `ERR_FAIL_COND_MSG` for bounds checking. + */ +#define ERR_FAIL_INDEX_MSG(m_index, m_size, m_msg) \ + if (unlikely((m_index) < 0 || (m_index) >= (m_size))) { \ + _err_print_index_error(FUNCTION_STR, __FILE__, __LINE__, m_index, m_size, _STR(m_index), _STR(m_size), m_msg); \ + return; \ + } else \ + ((void)0) + +/** + * If `m_index` is less than 0 or greater than or equal to `m_size`, + * prints a generic error message and returns the value specified in `m_retval`. + * This macro should be preferred to `ERR_FAIL_COND_V` for bounds checking. + */ +#define ERR_FAIL_INDEX_V(m_index, m_size, m_retval) \ + if (unlikely((m_index) < 0 || (m_index) >= (m_size))) { \ + _err_print_index_error(FUNCTION_STR, __FILE__, __LINE__, m_index, m_size, _STR(m_index), _STR(m_size)); \ + return m_retval; \ + } else \ + ((void)0) + +/** + * If `m_index` is less than 0 or greater than or equal to `m_size`, + * prints a custom error message and returns the value specified in `m_retval`. + * This macro should be preferred to `ERR_FAIL_COND_V_MSG` for bounds checking. + */ +#define ERR_FAIL_INDEX_V_MSG(m_index, m_size, m_retval, m_msg) \ + if (unlikely((m_index) < 0 || (m_index) >= (m_size))) { \ + _err_print_index_error(FUNCTION_STR, __FILE__, __LINE__, m_index, m_size, _STR(m_index), _STR(m_size), m_msg); \ + return m_retval; \ + } else \ + ((void)0) + +/** + * If `m_index` is greater than or equal to `m_size`, + * prints a generic error message and returns the value specified in `m_retval`. + * This macro should be preferred to `ERR_FAIL_COND_V` for unsigned bounds checking. + */ +#define ERR_FAIL_UNSIGNED_INDEX(m_index, m_size) \ + if (unlikely((m_index) >= (m_size))) { \ + _err_print_index_error(FUNCTION_STR, __FILE__, __LINE__, m_index, m_size, _STR(m_index), _STR(m_size)); \ + return; \ + } else \ + ((void)0) + +/** + * If `m_index` is greater than or equal to `m_size`, + * prints a generic error message and returns the value specified in `m_retval`. + * This macro should be preferred to `ERR_FAIL_COND_V` for unsigned bounds checking. + */ +#define ERR_FAIL_UNSIGNED_INDEX_V(m_index, m_size, m_retval) \ + if (unlikely((m_index) >= (m_size))) { \ + _err_print_index_error(FUNCTION_STR, __FILE__, __LINE__, m_index, m_size, _STR(m_index), _STR(m_size)); \ + return m_retval; \ + } else \ + ((void)0) + +/** + * If `m_index` is greater than or equal to `m_size`, + * prints a custom error message and returns the value specified in `m_retval`. + * This macro should be preferred to `ERR_FAIL_COND_V_MSG` for unsigned bounds checking. + */ +#define ERR_FAIL_UNSIGNED_INDEX_V_MSG(m_index, m_size, m_retval, m_msg) \ + if (unlikely((m_index) >= (m_size))) { \ + _err_print_index_error(FUNCTION_STR, __FILE__, __LINE__, m_index, m_size, _STR(m_index), _STR(m_size), m_msg); \ + return m_retval; \ + } else \ + ((void)0) + +/** + * If `m_index` is less than 0 or greater than or equal to `m_size`, + * crashes the engine immediately with a generic error message. + * Only use this if there's no sensible fallback (i.e. the error is unrecoverable). + * This macro should be preferred to `CRASH_COND` for bounds checking. + */ +#define CRASH_BAD_INDEX(m_index, m_size) \ + if (unlikely((m_index) < 0 || (m_index) >= (m_size))) { \ + _err_print_index_error(FUNCTION_STR, __FILE__, __LINE__, m_index, m_size, _STR(m_index), _STR(m_size), "", true); \ + _err_flush_stdout(); \ + GENERATE_TRAP \ + } else \ + ((void)0) + +/** + * If `m_index` is less than 0 or greater than or equal to `m_size`, + * crashes the engine immediately with a custom error message. + * Only use this if there's no sensible fallback (i.e. the error is unrecoverable). + * This macro should be preferred to `CRASH_COND` for bounds checking. + */ +#define CRASH_BAD_INDEX_MSG(m_index, m_size, m_msg) \ + if (unlikely((m_index) < 0 || (m_index) >= (m_size))) { \ + _err_print_index_error(FUNCTION_STR, __FILE__, __LINE__, m_index, m_size, _STR(m_index), _STR(m_size), m_msg, true); \ + _err_flush_stdout(); \ + GENERATE_TRAP \ + } else \ + ((void)0) + +/** + * If `m_index` is greater than or equal to `m_size`, + * crashes the engine immediately with a generic error message. + * Only use this if there's no sensible fallback (i.e. the error is unrecoverable). + * This macro should be preferred to `CRASH_COND` for bounds checking. + */ +#define CRASH_BAD_UNSIGNED_INDEX(m_index, m_size) \ + if (unlikely((m_index) >= (m_size))) { \ + _err_print_index_error(FUNCTION_STR, __FILE__, __LINE__, m_index, m_size, _STR(m_index), _STR(m_size), "", true); \ + _err_flush_stdout(); \ + GENERATE_TRAP \ + } else \ + ((void)0) + +/** + * If `m_param` is `null`, prints a generic error message and returns from the function. + */ +#define ERR_FAIL_NULL(m_param) \ + if (unlikely(!m_param)) { \ + _err_print_error(FUNCTION_STR, __FILE__, __LINE__, "Parameter \"" _STR(m_param) "\" is null."); \ + return; \ + } else \ + ((void)0) + +/** + * If `m_param` is `null`, prints a custom error message and returns from the function. + */ +#define ERR_FAIL_NULL_MSG(m_param, m_msg) \ + if (unlikely(!m_param)) { \ + _err_print_error(FUNCTION_STR, __FILE__, __LINE__, "Parameter \"" _STR(m_param) "\" is null.", m_msg); \ + return; \ + } else \ + ((void)0) + +/** + * If `m_param` is `null`, prints a generic error message and returns the value specified in `m_retval`. + */ +#define ERR_FAIL_NULL_V(m_param, m_retval) \ + if (unlikely(!m_param)) { \ + _err_print_error(FUNCTION_STR, __FILE__, __LINE__, "Parameter \"" _STR(m_param) "\" is null."); \ + return m_retval; \ + } else \ + ((void)0) + +/** + * If `m_param` is `null`, prints a custom error message and returns the value specified in `m_retval`. + */ +#define ERR_FAIL_NULL_V_MSG(m_param, m_retval, m_msg) \ + if (unlikely(!m_param)) { \ + _err_print_error(FUNCTION_STR, __FILE__, __LINE__, "Parameter \"" _STR(m_param) "\" is null.", m_msg); \ + return m_retval; \ + } else \ + ((void)0) + +/** + * If `m_cond` evaluates to `true`, prints a generic error message and returns from the function. + */ +#define ERR_FAIL_COND(m_cond) \ + if (unlikely(m_cond)) { \ + _err_print_error(FUNCTION_STR, __FILE__, __LINE__, "Condition \"" _STR(m_cond) "\" is true."); \ + return; \ + } else \ + ((void)0) + +/** + * If `m_cond` evaluates to `true`, prints a custom error message and returns from the function. + */ +#define ERR_FAIL_COND_MSG(m_cond, m_msg) \ + if (unlikely(m_cond)) { \ + _err_print_error(FUNCTION_STR, __FILE__, __LINE__, "Condition \"" _STR(m_cond) "\" is true.", m_msg); \ + return; \ + } else \ + ((void)0) + +/** + * If `m_cond` evaluates to `true`, crashes the engine immediately with a generic error message. + * Only use this if there's no sensible fallback (i.e. the error is unrecoverable). + */ +#define CRASH_COND(m_cond) \ + if (unlikely(m_cond)) { \ + _err_print_error(FUNCTION_STR, __FILE__, __LINE__, "FATAL: Condition \"" _STR(m_cond) "\" is true."); \ + _err_flush_stdout(); \ + GENERATE_TRAP \ + } else \ + ((void)0) + +/** + * If `m_cond` evaluates to `true`, crashes the engine immediately with a custom error message. + * Only use this if there's no sensible fallback (i.e. the error is unrecoverable). + */ +#define CRASH_COND_MSG(m_cond, m_msg) \ + if (unlikely(m_cond)) { \ + _err_print_error(FUNCTION_STR, __FILE__, __LINE__, "FATAL: Condition \"" _STR(m_cond) "\" is true.", m_msg); \ + _err_flush_stdout(); \ + GENERATE_TRAP \ + } else \ + ((void)0) + +/** + * If `m_cond` evaluates to `true`, prints a generic error message and returns the value specified in `m_retval`. + */ +#define ERR_FAIL_COND_V(m_cond, m_retval) \ + if (unlikely(m_cond)) { \ + _err_print_error(FUNCTION_STR, __FILE__, __LINE__, "Condition \"" _STR(m_cond) "\" is true. Returned: " _STR(m_retval)); \ + return m_retval; \ + } else \ + ((void)0) + +/** + * If `m_cond` evaluates to `true`, prints a custom error message and returns the value specified in `m_retval`. + */ +#define ERR_FAIL_COND_V_MSG(m_cond, m_retval, m_msg) \ + if (unlikely(m_cond)) { \ + _err_print_error(FUNCTION_STR, __FILE__, __LINE__, "Condition \"" _STR(m_cond) "\" is true. Returned: " _STR(m_retval), m_msg); \ + return m_retval; \ + } else \ + ((void)0) + +/** + * If `m_cond` evaluates to `true`, prints a custom error message and continues the loop the macro is located in. + */ +#define ERR_CONTINUE(m_cond) \ + if (unlikely(m_cond)) { \ + _err_print_error(FUNCTION_STR, __FILE__, __LINE__, "Condition \"" _STR(m_cond) "\" is true. Continuing."); \ + continue; \ + } else \ + ((void)0) + +/** + * If `m_cond` evaluates to `true`, prints a custom error message and continues the loop the macro is located in. + */ +#define ERR_CONTINUE_MSG(m_cond, m_msg) \ + if (unlikely(m_cond)) { \ + _err_print_error(FUNCTION_STR, __FILE__, __LINE__, "Condition \"" _STR(m_cond) "\" is true. Continuing.", m_msg); \ + continue; \ + } else \ + ((void)0) + +/** + * If `m_cond` evaluates to `true`, prints a generic error message and breaks from the loop the macro is located in. + */ +#define ERR_BREAK(m_cond) \ + if (unlikely(m_cond)) { \ + _err_print_error(FUNCTION_STR, __FILE__, __LINE__, "Condition \"" _STR(m_cond) "\" is true. Breaking."); \ + break; \ + } else \ + ((void)0) + +/** + * If `m_cond` evaluates to `true`, prints a custom error message and breaks from the loop the macro is located in. + */ +#define ERR_BREAK_MSG(m_cond, m_msg) \ + if (unlikely(m_cond)) { \ + _err_print_error(FUNCTION_STR, __FILE__, __LINE__, "Condition \"" _STR(m_cond) "\" is true. Breaking.", m_msg); \ + break; \ + } else \ + ((void)0) + +/** + * Prints a generic error message and returns from the function. + */ +#define ERR_FAIL() \ + if (true) { \ + _err_print_error(FUNCTION_STR, __FILE__, __LINE__, "Method failed."); \ + return; \ + } else \ + ((void)0) + +/** + * Prints a custom error message and returns from the function. + */ +#define ERR_FAIL_MSG(m_msg) \ + if (true) { \ + _err_print_error(FUNCTION_STR, __FILE__, __LINE__, "Method failed.", m_msg); \ + return; \ + } else \ + ((void)0) + +/** + * Prints a generic error message and returns the value specified in `m_retval`. + */ +#define ERR_FAIL_V(m_retval) \ + if (true) { \ + _err_print_error(FUNCTION_STR, __FILE__, __LINE__, "Method failed. Returning: " __STR(m_retval)); \ + return m_retval; \ + } else \ + ((void)0) + +/** + * Prints a custom error message and returns the value specified in `m_retval`. + */ +#define ERR_FAIL_V_MSG(m_retval, m_msg) \ + if (true) { \ + _err_print_error(FUNCTION_STR, __FILE__, __LINE__, "Method failed. Returning: " __STR(m_retval), m_msg); \ + return m_retval; \ + } else \ + ((void)0) + +/** + * Crashes the engine immediately with a generic error message. + * Only use this if there's no sensible fallback (i.e. the error is unrecoverable). + */ +#define CRASH_NOW() \ + if (true) { \ + _err_print_error(FUNCTION_STR, __FILE__, __LINE__, "FATAL: Method failed."); \ + _err_flush_stdout(); \ + GENERATE_TRAP \ + } else \ + ((void)0) + +/** + * Crashes the engine immediately with a custom error message. + * Only use this if there's no sensible fallback (i.e. the error is unrecoverable). + */ +#define CRASH_NOW_MSG(m_msg) \ + if (true) { \ + _err_print_error(FUNCTION_STR, __FILE__, __LINE__, "FATAL: Method failed.", m_msg); \ + _err_flush_stdout(); \ + GENERATE_TRAP \ + } else \ + ((void)0) + +/** + * Prints an error message without returning. + */ +#define ERR_PRINT(m_string) \ + _err_print_error(FUNCTION_STR, __FILE__, __LINE__, m_string) + +/** + * Prints an error message without returning, but only do so once in the application lifecycle. + * This can be used to avoid spamming the console with error messages. + */ +#define ERR_PRINT_ONCE(m_string) \ + if (true) { \ + static bool first_print = true; \ + if (first_print) { \ + _err_print_error(FUNCTION_STR, __FILE__, __LINE__, m_string); \ + first_print = false; \ + } \ + } else \ + ((void)0) + +/** + * Prints a warning message without returning. To warn about deprecated usage, + * use `WARN_DEPRECATED` or `WARN_DEPRECATED_MSG` instead. + */ +#define WARN_PRINT(m_string) \ + _err_print_error(FUNCTION_STR, __FILE__, __LINE__, m_string, ERR_HANDLER_WARNING) + +/** + * Prints a warning message without returning, but only do so once in the application lifecycle. + * This can be used to avoid spamming the console with warning messages. + */ +#define WARN_PRINT_ONCE(m_string) \ + if (true) { \ + static bool first_print = true; \ + if (first_print) { \ + _err_print_error(FUNCTION_STR, __FILE__, __LINE__, m_string, ERR_HANDLER_WARNING); \ + first_print = false; \ + } \ + } else \ + ((void)0) + +/** + * Prints a generic deprecation warning message without returning. + * This should be preferred to `WARN_PRINT` for deprecation warnings. + */ +#define WARN_DEPRECATED \ + if (true) { \ + static SafeFlag warning_shown; \ + if (!warning_shown.is_set()) { \ + _err_print_error(FUNCTION_STR, __FILE__, __LINE__, "This method has been deprecated and will be removed in the future.", ERR_HANDLER_WARNING); \ + warning_shown.set(); \ + } \ + } else \ + ((void)0) + +/** + * Prints a custom deprecation warning message without returning. + * This should be preferred to `WARN_PRINT` for deprecation warnings. + */ +#define WARN_DEPRECATED_MSG(m_msg) \ + if (true) { \ + static SafeFlag warning_shown; \ + if (!warning_shown.is_set()) { \ + _err_print_error(FUNCTION_STR, __FILE__, __LINE__, "This method has been deprecated and will be removed in the future.", m_msg, ERR_HANDLER_WARNING); \ + warning_shown.set(); \ + } \ + } else \ + ((void)0) + +/** + * This should be a 'free' assert for program flow and should not be needed in any releases, + * only used in dev builds. + */ +#ifdef DEV_ENABLED +#define DEV_ASSERT(m_cond) \ + if (unlikely(!(m_cond))) { \ + _err_print_error(FUNCTION_STR, __FILE__, __LINE__, "FATAL: DEV_ASSERT failed \"" _STR(m_cond) "\" is false."); \ + _err_flush_stdout(); \ + GENERATE_TRAP \ + } else \ + ((void)0) +#else +#define DEV_ASSERT(m_cond) +#endif + +/** + * These should be 'free' checks for program flow and should not be needed in any releases, + * only used in dev builds. + */ +#ifdef DEV_ENABLED +#define DEV_CHECK(m_cond) \ + if (unlikely(!(m_cond))) { \ + ERR_PRINT("DEV_CHECK failed \"" _STR(m_cond) "\" is false."); \ + } else \ + ((void)0) +#else +#define DEV_CHECK(m_cond) +#endif + +#ifdef DEV_ENABLED +#define DEV_CHECK_ONCE(m_cond) \ + if (unlikely(!(m_cond))) { \ + ERR_PRINT_ONCE("DEV_CHECK_ONCE failed \"" _STR(m_cond) "\" is false."); \ + } else \ + ((void)0) +#else +#define DEV_CHECK_ONCE(m_cond) +#endif + +/** + * Physics Interpolation warnings. + * These are spam protection warnings. + */ +#define PHYSICS_INTERPOLATION_NODE_WARNING(m_object_id, m_string) \ + _physics_interpolation_warning(FUNCTION_STR, __FILE__, __LINE__, m_object_id, m_string) + +#define PHYSICS_INTERPOLATION_WARNING(m_string) \ + _physics_interpolation_warning(FUNCTION_STR, __FILE__, __LINE__, UINT64_MAX, m_string) +#endif + +#endif // ERROR_MACROS_H diff --git a/tests/godot_mockery/core/list.h b/tests/godot_mockery/core/list.h new file mode 100644 index 0000000..56160e2 --- /dev/null +++ b/tests/godot_mockery/core/list.h @@ -0,0 +1,699 @@ +/**************************************************************************/ +/* list.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef LIST_H +#define LIST_H + +#include "core/os/memory.h" +#include "core/sort_array.h" + +/** + * Generic Templatized Linked List Implementation. + * The implementation differs from the STL one because + * a compatible preallocated linked list can be written + * using the same API, or features such as erasing an element + * from the iterator. + */ + +template +class List { + struct _Data; + +public: + class Element { + private: + friend class List; + + T value; + Element *next_ptr; + Element *prev_ptr; + _Data *data; + + public: + /** + * Get NEXT Element iterator, for constant lists. + */ + _FORCE_INLINE_ const Element *next() const { + return next_ptr; + }; + /** + * Get NEXT Element iterator, + */ + _FORCE_INLINE_ Element *next() { + return next_ptr; + }; + + /** + * Get PREV Element iterator, for constant lists. + */ + _FORCE_INLINE_ const Element *prev() const { + return prev_ptr; + }; + /** + * Get PREV Element iterator, + */ + _FORCE_INLINE_ Element *prev() { + return prev_ptr; + }; + + /** + * * operator, for using as *iterator, when iterators are defined on stack. + */ + _FORCE_INLINE_ const T &operator*() const { + return value; + }; + /** + * operator->, for using as iterator->, when iterators are defined on stack, for constant lists. + */ + _FORCE_INLINE_ const T *operator->() const { + return &value; + }; + /** + * * operator, for using as *iterator, when iterators are defined on stack, + */ + _FORCE_INLINE_ T &operator*() { + return value; + }; + /** + * operator->, for using as iterator->, when iterators are defined on stack, for constant lists. + */ + _FORCE_INLINE_ T *operator->() { + return &value; + }; + + /** + * get the value stored in this element. + */ + _FORCE_INLINE_ T &get() { + return value; + }; + /** + * get the value stored in this element, for constant lists + */ + _FORCE_INLINE_ const T &get() const { + return value; + }; + /** + * set the value stored in this element. + */ + _FORCE_INLINE_ void set(const T &p_value) { + value = (T &)p_value; + }; + + void erase() { + data->erase(this); + } + + _FORCE_INLINE_ Element() { + next_ptr = nullptr; + prev_ptr = nullptr; + data = nullptr; + }; + }; + +private: + struct _Data { + Element *first; + Element *last; + int size_cache; + + bool erase(const Element *p_I) { + ERR_FAIL_COND_V(!p_I, false); + ERR_FAIL_COND_V(p_I->data != this, false); + + if (first == p_I) { + first = p_I->next_ptr; + }; + + if (last == p_I) { + last = p_I->prev_ptr; + } + + if (p_I->prev_ptr) { + p_I->prev_ptr->next_ptr = p_I->next_ptr; + } + + if (p_I->next_ptr) { + p_I->next_ptr->prev_ptr = p_I->prev_ptr; + } + + memdelete_allocator(const_cast(p_I)); + size_cache--; + + return true; + } + }; + + _Data *_data; + +public: + /** + * return a const iterator to the beginning of the list. + */ + _FORCE_INLINE_ const Element *front() const { + return _data ? _data->first : nullptr; + }; + + /** + * return an iterator to the beginning of the list. + */ + _FORCE_INLINE_ Element *front() { + return _data ? _data->first : nullptr; + }; + + /** + * return a const iterator to the last member of the list. + */ + _FORCE_INLINE_ const Element *back() const { + return _data ? _data->last : nullptr; + }; + + /** + * return an iterator to the last member of the list. + */ + _FORCE_INLINE_ Element *back() { + return _data ? _data->last : nullptr; + }; + + /** + * store a new element at the end of the list + */ + Element *push_back(const T &value) { + if (!_data) { + _data = memnew_allocator(_Data, A); + _data->first = nullptr; + _data->last = nullptr; + _data->size_cache = 0; + } + + Element *n = memnew_allocator(Element, A); + n->value = (T &)value; + + n->prev_ptr = _data->last; + n->next_ptr = nullptr; + n->data = _data; + + if (_data->last) { + _data->last->next_ptr = n; + } + + _data->last = n; + + if (!_data->first) { + _data->first = n; + } + + _data->size_cache++; + + return n; + }; + + void pop_back() { + if (_data && _data->last) { + erase(_data->last); + } + } + + /** + * store a new element at the beginning of the list + */ + Element *push_front(const T &value) { + if (!_data) { + _data = memnew_allocator(_Data, A); + _data->first = nullptr; + _data->last = nullptr; + _data->size_cache = 0; + } + + Element *n = memnew_allocator(Element, A); + n->value = (T &)value; + n->prev_ptr = nullptr; + n->next_ptr = _data->first; + n->data = _data; + + if (_data->first) { + _data->first->prev_ptr = n; + } + + _data->first = n; + + if (!_data->last) { + _data->last = n; + } + + _data->size_cache++; + + return n; + }; + + void pop_front() { + if (_data && _data->first) { + erase(_data->first); + } + } + + Element *insert_after(Element *p_element, const T &p_value) { + CRASH_COND(p_element && (!_data || p_element->data != _data)); + + if (!p_element) { + return push_back(p_value); + } + + Element *n = memnew_allocator(Element, A); + n->value = (T &)p_value; + n->prev_ptr = p_element; + n->next_ptr = p_element->next_ptr; + n->data = _data; + + if (!p_element->next_ptr) { + _data->last = n; + } else { + p_element->next_ptr->prev_ptr = n; + } + + p_element->next_ptr = n; + + _data->size_cache++; + + return n; + } + + Element *insert_before(Element *p_element, const T &p_value) { + CRASH_COND(p_element && (!_data || p_element->data != _data)); + + if (!p_element) { + return push_back(p_value); + } + + Element *n = memnew_allocator(Element, A); + n->value = (T &)p_value; + n->prev_ptr = p_element->prev_ptr; + n->next_ptr = p_element; + n->data = _data; + + if (!p_element->prev_ptr) { + _data->first = n; + } else { + p_element->prev_ptr->next_ptr = n; + } + + p_element->prev_ptr = n; + + _data->size_cache++; + + return n; + } + + /** + * find an element in the list, + */ + template + Element *find(const T_v &p_val) { + Element *it = front(); + while (it) { + if (it->value == p_val) { + return it; + } + it = it->next(); + }; + + return nullptr; + }; + + /** + * erase an element in the list, by iterator pointing to it. Return true if it was found/erased. + */ + bool erase(const Element *p_I) { + if (_data) { + bool ret = _data->erase(p_I); + + if (_data->size_cache == 0) { + memdelete_allocator<_Data, A>(_data); + _data = nullptr; + } + + return ret; + } + + return false; + }; + + /** + * erase the first element in the list, that contains value + */ + bool erase(const T &value) { + Element *I = find(value); + return erase(I); + }; + + /** + * return whether the list is empty + */ + _FORCE_INLINE_ bool empty() const { + return (!_data || !_data->size_cache); + } + + /** + * clear the list + */ + void clear() { + while (front()) { + erase(front()); + }; + }; + + _FORCE_INLINE_ int size() const { + return _data ? _data->size_cache : 0; + } + + void swap(Element *p_A, Element *p_B) { + ERR_FAIL_COND(!p_A || !p_B); + ERR_FAIL_COND(p_A->data != _data); + ERR_FAIL_COND(p_B->data != _data); + + if (p_A == p_B) { + return; + } + Element *A_prev = p_A->prev_ptr; + Element *A_next = p_A->next_ptr; + Element *B_prev = p_B->prev_ptr; + Element *B_next = p_B->next_ptr; + + if (A_prev) { + A_prev->next_ptr = p_B; + } else { + _data->first = p_B; + } + if (B_prev) { + B_prev->next_ptr = p_A; + } else { + _data->first = p_A; + } + if (A_next) { + A_next->prev_ptr = p_B; + } else { + _data->last = p_B; + } + if (B_next) { + B_next->prev_ptr = p_A; + } else { + _data->last = p_A; + } + p_A->prev_ptr = A_next == p_B ? p_B : B_prev; + p_A->next_ptr = B_next == p_A ? p_B : B_next; + p_B->prev_ptr = B_next == p_A ? p_A : A_prev; + p_B->next_ptr = A_next == p_B ? p_A : A_next; + } + /** + * copy the list + */ + void operator=(const List &p_list) { + clear(); + const Element *it = p_list.front(); + while (it) { + push_back(it->get()); + it = it->next(); + } + } + + T &operator[](int p_index) { + CRASH_BAD_INDEX(p_index, size()); + + Element *I = front(); + int c = 0; + while (I) { + if (c == p_index) { + return I->get(); + } + I = I->next(); + c++; + } + + CRASH_NOW(); // bug!! + } + + const T &operator[](int p_index) const { + CRASH_BAD_INDEX(p_index, size()); + + const Element *I = front(); + int c = 0; + while (I) { + if (c == p_index) { + return I->get(); + } + I = I->next(); + c++; + } + + CRASH_NOW(); // bug!! + } + + void move_to_back(Element *p_I) { + ERR_FAIL_COND(p_I->data != _data); + if (!p_I->next_ptr) { + return; + } + + if (_data->first == p_I) { + _data->first = p_I->next_ptr; + }; + + if (_data->last == p_I) { + _data->last = p_I->prev_ptr; + } + + if (p_I->prev_ptr) { + p_I->prev_ptr->next_ptr = p_I->next_ptr; + } + + p_I->next_ptr->prev_ptr = p_I->prev_ptr; + + _data->last->next_ptr = p_I; + p_I->prev_ptr = _data->last; + p_I->next_ptr = nullptr; + _data->last = p_I; + } + + void invert() { + int s = size() / 2; + Element *F = front(); + Element *B = back(); + for (int i = 0; i < s; i++) { + SWAP(F->value, B->value); + F = F->next(); + B = B->prev(); + } + } + + void move_to_front(Element *p_I) { + ERR_FAIL_COND(p_I->data != _data); + if (!p_I->prev_ptr) { + return; + } + + if (_data->first == p_I) { + _data->first = p_I->next_ptr; + }; + + if (_data->last == p_I) { + _data->last = p_I->prev_ptr; + } + + p_I->prev_ptr->next_ptr = p_I->next_ptr; + + if (p_I->next_ptr) { + p_I->next_ptr->prev_ptr = p_I->prev_ptr; + } + + _data->first->prev_ptr = p_I; + p_I->next_ptr = _data->first; + p_I->prev_ptr = nullptr; + _data->first = p_I; + } + + void move_before(Element *value, Element *where) { + if (value->prev_ptr) { + value->prev_ptr->next_ptr = value->next_ptr; + } else { + _data->first = value->next_ptr; + } + if (value->next_ptr) { + value->next_ptr->prev_ptr = value->prev_ptr; + } else { + _data->last = value->prev_ptr; + } + + value->next_ptr = where; + if (!where) { + value->prev_ptr = _data->last; + _data->last = value; + return; + }; + + value->prev_ptr = where->prev_ptr; + + if (where->prev_ptr) { + where->prev_ptr->next_ptr = value; + } else { + _data->first = value; + }; + + where->prev_ptr = value; + }; + + /** + * simple insertion sort + */ + + void sort() { + sort_custom>(); + } + + template + void sort_custom_inplace() { + if (size() < 2) { + return; + } + + Element *from = front(); + Element *current = from; + Element *to = from; + + while (current) { + Element *next = current->next_ptr; + + if (from != current) { + current->prev_ptr = NULL; + current->next_ptr = from; + + Element *find = from; + C less; + while (find && less(find->value, current->value)) { + current->prev_ptr = find; + current->next_ptr = find->next_ptr; + find = find->next_ptr; + } + + if (current->prev_ptr) { + current->prev_ptr->next_ptr = current; + } else { + from = current; + } + + if (current->next_ptr) { + current->next_ptr->prev_ptr = current; + } else { + to = current; + } + } else { + current->prev_ptr = NULL; + current->next_ptr = NULL; + } + + current = next; + } + _data->first = from; + _data->last = to; + } + + template + struct AuxiliaryComparator { + C compare; + _FORCE_INLINE_ bool operator()(const Element *a, const Element *b) const { + return compare(a->value, b->value); + } + }; + + template + void sort_custom() { + //this version uses auxiliary memory for speed. + //if you don't want to use auxiliary memory, use the in_place version + + int s = size(); + if (s < 2) { + return; + } + + Element **aux_buffer = memnew_arr(Element *, s); + + int idx = 0; + for (Element *E = front(); E; E = E->next_ptr) { + aux_buffer[idx] = E; + idx++; + } + + SortArray> sort; + sort.sort(aux_buffer, s); + + _data->first = aux_buffer[0]; + aux_buffer[0]->prev_ptr = nullptr; + aux_buffer[0]->next_ptr = aux_buffer[1]; + + _data->last = aux_buffer[s - 1]; + aux_buffer[s - 1]->prev_ptr = aux_buffer[s - 2]; + aux_buffer[s - 1]->next_ptr = nullptr; + + for (int i = 1; i < s - 1; i++) { + aux_buffer[i]->prev_ptr = aux_buffer[i - 1]; + aux_buffer[i]->next_ptr = aux_buffer[i + 1]; + } + + memdelete_arr(aux_buffer); + } + + const void *id() const { + return (void *)_data; + } + + /** + * copy constructor for the list + */ + List(const List &p_list) { + _data = nullptr; + const Element *it = p_list.front(); + while (it) { + push_back(it->get()); + it = it->next(); + } + } + + List() { + _data = nullptr; + }; + ~List() { + clear(); + if (_data) { + ERR_FAIL_COND(_data->size_cache); + memdelete_allocator<_Data, A>(_data); + } + }; +}; + +#endif // LIST_H diff --git a/tests/godot_mockery/core/os/memory.cpp b/tests/godot_mockery/core/os/memory.cpp new file mode 100644 index 0000000..df900f3 --- /dev/null +++ b/tests/godot_mockery/core/os/memory.cpp @@ -0,0 +1,201 @@ +/**************************************************************************/ +/* memory.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "memory.h" + +#include "core/error_macros.h" +#include "core/safe_refcount.h" + +#include +#include + +void *operator new(size_t p_size, const char *p_description) { + return Memory::alloc_static(p_size, false); +} + +void *operator new(size_t p_size, void *(*p_allocfunc)(size_t p_size)) { + return p_allocfunc(p_size); +} + +#ifdef _MSC_VER +void operator delete(void *p_mem, const char *p_description) { + CRASH_NOW_MSG("Call to placement delete should not happen."); +} + +void operator delete(void *p_mem, void *(*p_allocfunc)(size_t p_size)) { + CRASH_NOW_MSG("Call to placement delete should not happen."); +} + +void operator delete(void *p_mem, void *p_pointer, size_t check, const char *p_description) { + CRASH_NOW_MSG("Call to placement delete should not happen."); +} +#endif + +#ifdef DEBUG_ENABLED +SafeNumeric Memory::mem_usage; +SafeNumeric Memory::max_usage; +#endif + +SafeNumeric Memory::alloc_count; + +void *Memory::alloc_static(size_t p_bytes, bool p_pad_align) { +#ifdef DEBUG_ENABLED + bool prepad = true; +#else + bool prepad = p_pad_align; +#endif + + void *mem = malloc(p_bytes + (prepad ? PAD_ALIGN : 0)); + + ERR_FAIL_COND_V(!mem, nullptr); + + alloc_count.increment(); + + if (prepad) { + uint64_t *s = (uint64_t *)mem; + *s = p_bytes; + + uint8_t *s8 = (uint8_t *)mem; + +#ifdef DEBUG_ENABLED + uint64_t new_mem_usage = mem_usage.add(p_bytes); + max_usage.exchange_if_greater(new_mem_usage); +#endif + return s8 + PAD_ALIGN; + } else { + return mem; + } +} + +void *Memory::realloc_static(void *p_memory, size_t p_bytes, bool p_pad_align) { + if (p_memory == nullptr) { + return alloc_static(p_bytes, p_pad_align); + } + + uint8_t *mem = (uint8_t *)p_memory; + +#ifdef DEBUG_ENABLED + bool prepad = true; +#else + bool prepad = p_pad_align; +#endif + + if (prepad) { + mem -= PAD_ALIGN; + uint64_t *s = (uint64_t *)mem; + +#ifdef DEBUG_ENABLED + if (p_bytes > *s) { + uint64_t new_mem_usage = mem_usage.add(p_bytes - *s); + max_usage.exchange_if_greater(new_mem_usage); + } else { + mem_usage.sub(*s - p_bytes); + } +#endif + + if (p_bytes == 0) { + free(mem); + return nullptr; + } else { + *s = p_bytes; + + mem = (uint8_t *)realloc(mem, p_bytes + PAD_ALIGN); + ERR_FAIL_COND_V(!mem, nullptr); + + s = (uint64_t *)mem; + + *s = p_bytes; + + return mem + PAD_ALIGN; + } + } else { + mem = (uint8_t *)realloc(mem, p_bytes); + + ERR_FAIL_COND_V(mem == nullptr && p_bytes > 0, nullptr); + + return mem; + } +} + +void Memory::free_static(void *p_ptr, bool p_pad_align) { + ERR_FAIL_COND(p_ptr == nullptr); + + uint8_t *mem = (uint8_t *)p_ptr; + +#ifdef DEBUG_ENABLED + bool prepad = true; +#else + bool prepad = p_pad_align; +#endif + + alloc_count.decrement(); + + if (prepad) { + mem -= PAD_ALIGN; + +#ifdef DEBUG_ENABLED + uint64_t *s = (uint64_t *)mem; + mem_usage.sub(*s); +#endif + + free(mem); + } else { + free(mem); + } +} + +uint64_t Memory::get_mem_available() { + return -1; // 0xFFFF... +} + +uint64_t Memory::get_mem_usage() { +#ifdef DEBUG_ENABLED + return mem_usage.get(); +#else + return 0; +#endif +} + +uint64_t Memory::get_mem_max_usage() { +#ifdef DEBUG_ENABLED + return max_usage.get(); +#else + return 0; +#endif +} + +_GlobalNil::_GlobalNil() { + color = 1; + left = this; + right = this; + parent = this; +} + +_GlobalNil _GlobalNilClass::_nil; diff --git a/tests/godot_mockery/core/os/memory.h b/tests/godot_mockery/core/os/memory.h new file mode 100644 index 0000000..65a444c --- /dev/null +++ b/tests/godot_mockery/core/os/memory.h @@ -0,0 +1,206 @@ +/**************************************************************************/ +/* memory.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef MEMORY_H +#define MEMORY_H + +// #include "core/error_macros.h" +#include "core/safe_refcount.h" + +#include +#include + +#ifndef PAD_ALIGN +#define PAD_ALIGN 16 //must always be greater than this at much +#endif + +class Memory { +#ifdef DEBUG_ENABLED + static SafeNumeric mem_usage; + static SafeNumeric max_usage; +#endif + + static SafeNumeric alloc_count; + +public: + static void *alloc_static(size_t p_bytes, bool p_pad_align = false); + static void *realloc_static(void *p_memory, size_t p_bytes, bool p_pad_align = false); + static void free_static(void *p_ptr, bool p_pad_align = false); + + static uint64_t get_mem_available(); + static uint64_t get_mem_usage(); + static uint64_t get_mem_max_usage(); +}; + +class DefaultAllocator { +public: + _FORCE_INLINE_ static void *alloc(size_t p_memory) { return Memory::alloc_static(p_memory, false); } + _FORCE_INLINE_ static void free(void *p_ptr) { Memory::free_static(p_ptr, false); } +}; + +void *operator new(size_t p_size, const char *p_description); ///< operator new that takes a description and uses MemoryStaticPool +void *operator new(size_t p_size, void *(*p_allocfunc)(size_t p_size)); ///< operator new that takes a description and uses MemoryStaticPool + +void *operator new(size_t p_size, void *p_pointer, size_t check, const char *p_description); ///< operator new that takes a description and uses a pointer to the preallocated memory + +#ifdef _MSC_VER +// When compiling with VC++ 2017, the above declarations of placement new generate many irrelevant warnings (C4291). +// The purpose of the following definitions is to muffle these warnings, not to provide a usable implementation of placement delete. +void operator delete(void *p_mem, const char *p_description); +void operator delete(void *p_mem, void *(*p_allocfunc)(size_t p_size)); +void operator delete(void *p_mem, void *p_pointer, size_t check, const char *p_description); +#endif + +#define memalloc(m_size) Memory::alloc_static(m_size) +#define memrealloc(m_mem, m_size) Memory::realloc_static(m_mem, m_size) +#define memfree(m_mem) Memory::free_static(m_mem) + +_ALWAYS_INLINE_ void postinitialize_handler(void *) {} + +template +_ALWAYS_INLINE_ T *_post_initialize(T *p_obj) { + postinitialize_handler(p_obj); + return p_obj; +} + +#define memnew(m_class) _post_initialize(new ("") m_class) + +_ALWAYS_INLINE_ void *operator new(size_t p_size, void *p_pointer, size_t check, const char *p_description) { + //void *failptr=0; + //ERR_FAIL_COND_V( check < p_size , failptr); /** bug, or strange compiler, most likely */ + + return p_pointer; +} + +#define memnew_allocator(m_class, m_allocator) _post_initialize(new (m_allocator::alloc) m_class) +#define memnew_placement(m_placement, m_class) _post_initialize(new (m_placement, sizeof(m_class), "") m_class) + +_ALWAYS_INLINE_ bool predelete_handler(void *) { + return true; +} + +template +void memdelete(T *p_class) { + if (!predelete_handler(p_class)) { + return; // doesn't want to be deleted + } + if (!std::is_trivially_destructible::value) { + p_class->~T(); + } + + Memory::free_static(p_class, false); +} + +template +void memdelete_allocator(T *p_class) { + if (!predelete_handler(p_class)) { + return; // doesn't want to be deleted + } + if (!std::is_trivially_destructible::value) { + p_class->~T(); + } + + A::free(p_class); +} + +#define memdelete_notnull(m_v) \ + { \ + if (m_v) \ + memdelete(m_v); \ + } + +#define memnew_arr(m_class, m_count) memnew_arr_template(m_count) + +template +T *memnew_arr_template(size_t p_elements, const char *p_descr = "") { + if (p_elements == 0) { + return nullptr; + } + /** overloading operator new[] cannot be done , because it may not return the real allocated address (it may pad the 'element count' before the actual array). Because of that, it must be done by hand. This is the + same strategy used by std::vector, and the PoolVector class, so it should be safe.*/ + + size_t len = sizeof(T) * p_elements; + uint64_t *mem = (uint64_t *)Memory::alloc_static(len, true); + T *failptr = nullptr; //get rid of a warning + ERR_FAIL_COND_V(!mem, failptr); + *(mem - 1) = p_elements; + + if (!std::is_trivially_constructible::value) { + T *elems = (T *)mem; + + /* call operator new */ + for (size_t i = 0; i < p_elements; i++) { + new (&elems[i], sizeof(T), p_descr) T; + } + } + + return (T *)mem; +} + +/** + * Wonders of having own array functions, you can actually check the length of + * an allocated-with memnew_arr() array + */ + +template +size_t memarr_len(const T *p_class) { + uint64_t *ptr = (uint64_t *)p_class; + return *(ptr - 1); +} + +template +void memdelete_arr(T *p_class) { + uint64_t *ptr = (uint64_t *)p_class; + + if (!std::is_trivially_destructible::value) { + uint64_t elem_count = *(ptr - 1); + + for (uint64_t i = 0; i < elem_count; i++) { + p_class[i].~T(); + } + } + + Memory::free_static(ptr, true); +} + +struct _GlobalNil { + int color; + _GlobalNil *right; + _GlobalNil *left; + _GlobalNil *parent; + + _GlobalNil(); +}; + +struct _GlobalNilClass { + static _GlobalNil _nil; +}; + +#endif // MEMORY_H diff --git a/tests/godot_mockery/core/ustring.cpp b/tests/godot_mockery/core/ustring.cpp new file mode 100644 index 0000000..488d9d7 --- /dev/null +++ b/tests/godot_mockery/core/ustring.cpp @@ -0,0 +1,318 @@ +#ifndef NO_USE_STDLIB +#include +#include +#endif +#include +#include +#include "core/math/math_funcs.h" + +#include "ustring.h" + +const char CharString::_null = 0; +const CharType String::_null = 0; + +String &String::operator+=(const String &p_str) { + const int lhs_len = length(); + if (lhs_len == 0) { + *this = p_str; + return *this; + } + + const int rhs_len = p_str.length(); + if (rhs_len == 0) { + return *this; + } + + resize(lhs_len + rhs_len + 1); + + const CharType *src = p_str.c_str(); + CharType *dst = ptrw() + lhs_len; + + memcpy(dst, src, (rhs_len + 1) * sizeof(CharType)); + + return *this; +} + +String &String::operator+=(const CharType *p_str) { + *this += String(p_str); + return *this; +} + +String &String::operator+=(CharType p_char) { + const int lhs_len = length(); + resize(lhs_len + 2); + + CharType *dst = ptrw(); + dst[lhs_len] = p_char; + dst[lhs_len + 1] = 0; + + return *this; +} + +String &String::operator+=(const char *p_str) { + if (!p_str || p_str[0] == 0) { + return *this; + } + + const size_t rhs_len = strlen(p_str); + const int lhs_len = length(); + + resize(lhs_len + rhs_len + 1); + + CharType *dst = ptrw() + lhs_len; + + for (size_t i = 0; i <= rhs_len; i++) { + dst[i] = p_str[i]; + } + + return *this; +} + +CharString String::ascii(bool p_allow_extended) const { + if (!length()) { + return CharString(); + } + + CharString cs; + cs.resize(size()); + + for (int i = 0; i < size(); i++) { + cs[i] = operator[](i); + } + + return cs; +} + +String String::operator+(const String &p_str) const { + String res = *this; + res += p_str; + return res; +} + +String String::num(double p_num, int p_decimals) { + if (Math::is_nan(p_num)) { + return "nan"; + } + +#ifndef NO_USE_STDLIB + + if (p_decimals > 16) { + p_decimals = 16; + } + + char fmt[7]; + fmt[0] = '%'; + fmt[1] = '.'; + + if (p_decimals < 0) { + fmt[1] = 'l'; + fmt[2] = 'f'; + fmt[3] = 0; + + } else if (p_decimals < 10) { + fmt[2] = '0' + p_decimals; + fmt[3] = 'l'; + fmt[4] = 'f'; + fmt[5] = 0; + } else { + fmt[2] = '0' + (p_decimals / 10); + fmt[3] = '0' + (p_decimals % 10); + fmt[4] = 'l'; + fmt[5] = 'f'; + fmt[6] = 0; + } + char buf[256]; + +#if defined(__GNUC__) || defined(_MSC_VER) + snprintf(buf, 256, fmt, p_num); +#else + sprintf(buf, fmt, p_num); +#endif + + buf[255] = 0; + //destroy trailing zeroes + { + bool period = false; + int z = 0; + while (buf[z]) { + if (buf[z] == '.') { + period = true; + } + z++; + } + + if (period) { + z--; + while (z > 0) { + if (buf[z] == '0') { + buf[z] = 0; + } else if (buf[z] == '.') { + buf[z] = 0; + break; + } else { + break; + } + + z--; + } + } + } + + return buf; +#else + + String s; + String sd; + /* integer part */ + + bool neg = p_num < 0; + p_num = ABS(p_num); + int intn = (int)p_num; + + /* decimal part */ + + if (p_decimals > 0 || (p_decimals == -1 && (int)p_num != p_num)) { + double dec = p_num - (float)((int)p_num); + + int digit = 0; + if (p_decimals > MAX_DIGITS) + p_decimals = MAX_DIGITS; + + int dec_int = 0; + int dec_max = 0; + + while (true) { + dec *= 10.0; + dec_int = dec_int * 10 + (int)dec % 10; + dec_max = dec_max * 10 + 9; + digit++; + + if (p_decimals == -1) { + if (digit == MAX_DIGITS) //no point in going to infinite + break; + + if ((dec - (float)((int)dec)) < 1e-6) + break; + } + + if (digit == p_decimals) + break; + } + dec *= 10; + int last = (int)dec % 10; + + if (last > 5) { + if (dec_int == dec_max) { + dec_int = 0; + intn++; + } else { + dec_int++; + } + } + + String decimal; + for (int i = 0; i < digit; i++) { + char num[2] = { 0, 0 }; + num[0] = '0' + dec_int % 10; + decimal = num + decimal; + dec_int /= 10; + } + sd = '.' + decimal; + } + + if (intn == 0) + + s = "0"; + else { + while (intn) { + CharType num = '0' + (intn % 10); + intn /= 10; + s = num + s; + } + } + + s = s + sd; + if (neg) + s = "-" + s; + return s; +#endif +} +String::String(const char *p_str) { + copy_from(p_str); +} + +String::String(const CharType *p_str, int p_clip_to_len) { + copy_from(p_str, p_clip_to_len); +} + +void String::copy_from(const char *p_cstr) { + if (!p_cstr) { + resize(0); + return; + } + + const size_t len = strlen(p_cstr); + + if (len == 0) { + resize(0); + return; + } + + resize(len + 1); // include 0 + + CharType *dst = this->ptrw(); + + for (size_t i = 0; i <= len; i++) { + dst[i] = p_cstr[i]; + } +} + + +void String::copy_from(const CharType *p_cstr, const int p_clip_to) { + if (!p_cstr) { + resize(0); + return; + } + + int len = 0; + const CharType *ptr = p_cstr; + while ((p_clip_to < 0 || len < p_clip_to) && *(ptr++) != 0) { + len++; + } + + if (len == 0) { + resize(0); + return; + } + + copy_from_unchecked(p_cstr, len); +} + +// assumes the following have already been validated: +// p_char != NULL +// p_length > 0 +// p_length <= p_char strlen +void String::copy_from_unchecked(const CharType *p_char, const int p_length) { + resize(p_length + 1); + + CharType *dst = ptrw(); + memcpy(dst, p_char, p_length * sizeof(CharType)); + dst[p_length] = 0; +} + +void String::copy_from(const CharType &p_char) { + resize(2); + CharType *dst = ptrw(); + dst[0] = p_char; + dst[1] = 0; +} + +const CharType *String::c_str() const { + static const CharType zero = 0; + + return size() ? &operator[](0) : &zero; +} + + + + diff --git a/tests/godot_mockery/core/ustring.h b/tests/godot_mockery/core/ustring.h new file mode 100644 index 0000000..bf6ef7c --- /dev/null +++ b/tests/godot_mockery/core/ustring.h @@ -0,0 +1,496 @@ +/**************************************************************************/ +/* ustring.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef USTRING_GODOT_H +#define USTRING_GODOT_H + +#if 0 +class String { + flecs::string string; +public: + String() noexcept {} + String(const char *p_str) noexcept string(p_str) {} + String &operator=(const String &str) noexcept + { + string = str.string; + return *this; + } + String &operator+(const String &) + { + String res = *this; + res += p_str; + return res; + } + String &operator+=(const String &p_str) + { + string += p_str.string; + return *this; + } + String &operator+=(const char *s) + { + string += s; + return *this; + } + bool operator==(const String &other) + { + return string == other.string; + } + bool operator==(const char *other) + { + return string == other; + } +}; +#endif + +#include "core/array.h" +#include "core/cowdata.h" +#include "core/typedefs.h" +#include "core/vector.h" + +template +class CharProxy { + friend class CharString; + friend class String; + + const int _index; + CowData &_cowdata; + static const T _null = 0; + + _FORCE_INLINE_ CharProxy(const int &p_index, CowData &p_cowdata) : + _index(p_index), + _cowdata(p_cowdata) {} + +public: + _FORCE_INLINE_ CharProxy(const CharProxy &p_other) : + _index(p_other._index), + _cowdata(p_other._cowdata) {} + + _FORCE_INLINE_ operator T() const { + if (unlikely(_index == _cowdata.size())) { + return _null; + } + + return _cowdata.get(_index); + } + + _FORCE_INLINE_ const T *operator&() const { + return _cowdata.ptr() + _index; + } + + _FORCE_INLINE_ void operator=(const T &p_other) const { + _cowdata.set(_index, p_other); + } + + _FORCE_INLINE_ void operator=(const CharProxy &p_other) const { + _cowdata.set(_index, p_other.operator T()); + } +}; + +class CharString { + CowData _cowdata; + static const char _null; + +public: + _FORCE_INLINE_ char *ptrw() { return _cowdata.ptrw(); } + _FORCE_INLINE_ const char *ptr() const { return _cowdata.ptr(); } + _FORCE_INLINE_ int size() const { return _cowdata.size(); } + Error resize(int p_size) { return _cowdata.resize(p_size); } + + _FORCE_INLINE_ char get(int p_index) const { return _cowdata.get(p_index); } + _FORCE_INLINE_ void set(int p_index, const char &p_elem) { _cowdata.set(p_index, p_elem); } + _FORCE_INLINE_ const char &operator[](int p_index) const { + if (unlikely(p_index == _cowdata.size())) { + return _null; + } + + return _cowdata.get(p_index); + } + _FORCE_INLINE_ CharProxy operator[](int p_index) { return CharProxy(p_index, _cowdata); } + + _FORCE_INLINE_ CharString() {} + _FORCE_INLINE_ CharString(const CharString &p_str) { _cowdata._ref(p_str._cowdata); } + _FORCE_INLINE_ CharString operator=(const CharString &p_str) { + _cowdata._ref(p_str._cowdata); + return *this; + } + _FORCE_INLINE_ CharString(const char *p_cstr) { copy_from(p_cstr); } + + CharString &operator=(const char *p_cstr); + bool operator<(const CharString &p_right) const; + CharString &operator+=(char p_char); + int length() const { return size() ? size() - 1 : 0; } + const char *get_data() const; + operator const char *() const { return get_data(); }; + +protected: + void copy_from(const char *p_cstr); +}; + +typedef wchar_t CharType; + +struct StrRange { + const CharType *c_str; + int len; + + StrRange(const CharType *p_c_str = nullptr, int p_len = 0) { + c_str = p_c_str; + len = p_len; + } +}; + +class String { + CowData _cowdata; + static const CharType _null; + + void copy_from(const char *p_cstr); + void copy_from(const CharType *p_cstr, const int p_clip_to = -1); + void copy_from(const CharType &p_char); + void copy_from_unchecked(const CharType *p_char, const int p_length); + bool _base_is_subsequence_of(const String &p_string, bool case_insensitive) const; + int _count(const String &p_string, int p_from, int p_to, bool p_case_insensitive) const; + +public: + enum { + + npos = -1 /// operator[](int p_index) { return CharProxy(p_index, _cowdata); } + + bool operator==(const String &p_str) const; + bool operator!=(const String &p_str) const; + String operator+(const String &p_str) const; + //String operator+(CharType p_char) const; + + String &operator+=(const String &); + String &operator+=(CharType p_char); + String &operator+=(const char *p_str); + String &operator+=(const CharType *p_str); + + /* Compatibility Operators */ + + void operator=(const char *p_str); + void operator=(const CharType *p_str); + bool operator==(const char *p_str) const; + bool operator==(const CharType *p_str) const; + bool operator==(const StrRange &p_str_range) const; + bool operator!=(const char *p_str) const; + bool operator!=(const CharType *p_str) const; + bool operator<(const CharType *p_str) const; + bool operator<(const char *p_str) const; + bool operator<(const String &p_str) const; + bool operator<=(const String &p_str) const; + + signed char casecmp_to(const String &p_str) const; + signed char nocasecmp_to(const String &p_str) const; + signed char naturalnocasecmp_to(const String &p_str) const; + + const CharType *c_str() const; + /* standard size stuff */ + + _FORCE_INLINE_ int length() const { + int s = size(); + return s ? (s - 1) : 0; // length does not include zero + } + + /* complex helpers */ + String substr(int p_from, int p_chars = -1) const; + int find(const String &p_str, int p_from = 0) const; ///< return <0 if failed + int find(const char *p_str, int p_from = 0) const; ///< return <0 if failed + int find_char(const CharType &p_char, int p_from = 0) const; ///< return <0 if failed + int find_last(const String &p_str) const; ///< return <0 if failed + int findn(const String &p_str, int p_from = 0) const; ///< return <0 if failed, case insensitive + int rfind(const String &p_str, int p_from = -1) const; ///< return <0 if failed + int rfindn(const String &p_str, int p_from = -1) const; ///< return <0 if failed, case insensitive + int findmk(const Vector &p_keys, int p_from = 0, int *r_key = nullptr) const; ///< return <0 if failed + bool match(const String &p_wildcard) const; + bool matchn(const String &p_wildcard) const; + bool begins_with(const String &p_string) const; + bool begins_with(const char *p_string) const; + bool ends_with(const String &p_string) const; + bool is_enclosed_in(const String &p_string) const; + bool is_subsequence_of(const String &p_string) const; + bool is_subsequence_ofi(const String &p_string) const; + bool is_quoted() const; + Vector bigrams() const; + float similarity(const String &p_string) const; + String format(const Variant &values, String placeholder = "{_}") const; + String replace_first(const String &p_key, const String &p_with) const; + String replace(const String &p_key, const String &p_with) const; + String replace(const char *p_key, const char *p_with) const; + String replacen(const String &p_key, const String &p_with) const; + String repeat(int p_count) const; + String insert(int p_at_pos, const String &p_string) const; + String pad_decimals(int p_digits) const; + String pad_zeros(int p_digits) const; + String trim_prefix(const String &p_prefix) const; + String trim_suffix(const String &p_suffix) const; + String lpad(int min_length, const String &character = " ") const; + String rpad(int min_length, const String &character = " ") const; + String sprintf(const Array &values, bool *error) const; + String quote(String quotechar = "\"") const; + String unquote() const; + static String num(double p_num, int p_decimals = -1); + static String num_scientific(double p_num); + static String num_real(double p_num); + static String num_int64(int64_t p_num, int base = 10, bool capitalize_hex = false); + static String num_uint64(uint64_t p_num, int base = 10, bool capitalize_hex = false); + static String chr(CharType p_char); + static String md5(const uint8_t *p_md5); + static String hex_encode_buffer(const uint8_t *p_buffer, int p_len); + bool is_numeric() const; + double to_double() const; + float to_float() const; + int hex_to_int(bool p_with_prefix = true) const; + int to_int() const; + + int64_t hex_to_int64(bool p_with_prefix = true) const; + int64_t bin_to_int64(bool p_with_prefix = true) const; + int64_t to_int64() const; + static int to_int(const char *p_str, int p_len = -1); + static double to_double(const char *p_str); + static double to_double(const CharType *p_str, const CharType **r_end = nullptr); + static int64_t to_int(const CharType *p_str, int p_len = -1); + String capitalize() const; + String camelcase_to_underscore(bool lowercase = true) const; + + int get_slice_count(String p_splitter) const; + String get_slice(String p_splitter, int p_slice) const; + String get_slicec(CharType p_splitter, int p_slice) const; + + Vector split(const String &p_splitter, bool p_allow_empty = true, int p_maxsplit = 0) const; + Vector rsplit(const String &p_splitter, bool p_allow_empty = true, int p_maxsplit = 0) const; + Vector split_spaces() const; + Vector split_floats(const String &p_splitter, bool p_allow_empty = true) const; + Vector split_floats_mk(const Vector &p_splitters, bool p_allow_empty = true) const; + Vector split_ints(const String &p_splitter, bool p_allow_empty = true) const; + Vector split_ints_mk(const Vector &p_splitters, bool p_allow_empty = true) const; + + String join(const Vector &parts) const; + + static CharType char_uppercase(CharType p_char); + static CharType char_lowercase(CharType p_char); + String to_upper() const; + String to_lower() const; + + int count(const String &p_string, int p_from = 0, int p_to = 0) const; + int countn(const String &p_string, int p_from = 0, int p_to = 0) const; + + String left(int p_pos) const; + String right(int p_pos) const; + String indent(const String &p_prefix) const; + String dedent() const; + String strip_edges(bool left = true, bool right = true) const; + String strip_escapes() const; + String lstrip(const String &p_chars) const; + String rstrip(const String &p_chars) const; + String get_extension() const; + String get_basename() const; + String plus_file(const String &p_file) const; + CharType ord_at(int p_idx) const; + + void erase(int p_pos, int p_chars); + + CharString ascii(bool p_allow_extended = false) const; + CharString utf8() const; + bool parse_utf8(const char *p_utf8, int p_len = -1, bool p_skip_cr = false); //return true on error + static String utf8(const char *p_utf8, int p_len = -1); + + static uint32_t hash(const CharType *p_cstr, int p_len); /* hash the string */ + static uint32_t hash(const CharType *p_cstr); /* hash the string */ + static uint32_t hash(const char *p_cstr, int p_len); /* hash the string */ + static uint32_t hash(const char *p_cstr); /* hash the string */ + uint32_t hash() const; /* hash the string */ + uint64_t hash64() const; /* hash the string */ + String md5_text() const; + String sha1_text() const; + String sha256_text() const; + Vector md5_buffer() const; + Vector sha1_buffer() const; + Vector sha256_buffer() const; + + _FORCE_INLINE_ bool empty() const { return length() == 0; } + + // path functions + bool is_abs_path() const; + bool is_rel_path() const; + bool is_resource_file() const; + String path_to(const String &p_path) const; + String path_to_file(const String &p_path) const; + String get_base_dir() const; + String get_file() const; + static String humanize_size(uint64_t p_size); + String simplify_path() const; + bool is_network_share_path() const; + + String xml_escape(bool p_escape_quotes = false) const; + String xml_unescape() const; + String http_escape() const; + String http_unescape() const; + String c_escape() const; + String c_escape_multiline() const; + String c_unescape() const; + String json_escape() const; + String word_wrap(int p_chars_per_line) const; + Error parse_url(String &r_scheme, String &r_host, int &r_port, String &r_path) const; + + String percent_encode() const; + String percent_decode() const; + + String property_name_encode() const; + + // node functions + static const String invalid_node_name_characters; + String validate_node_name() const; + String validate_identifier() const; + + bool is_valid_identifier() const; + bool is_valid_integer() const; + bool is_valid_float() const; + bool is_valid_hex_number(bool p_with_prefix) const; + bool is_valid_html_color() const; + bool is_valid_ip_address() const; + bool is_valid_filename() const; + + /** + * The constructors must not depend on other overloads + */ + /* String(CharType p_char);*/ + + _FORCE_INLINE_ String() {} + _FORCE_INLINE_ String(const String &p_str) { _cowdata._ref(p_str._cowdata); } + String operator=(const String &p_str) { + _cowdata._ref(p_str._cowdata); + return *this; + } + + String(const char *p_str); + String(const CharType *p_str, int p_clip_to_len = -1); + String(const StrRange &p_range); +}; + +bool operator==(const char *p_chr, const String &p_str); + +String operator+(const char *p_chr, const String &p_str); +String operator+(CharType p_chr, const String &p_str); + +String itos(int64_t p_val); +String uitos(uint64_t p_val); +String rtos(double p_val); +String rtoss(double p_val); //scientific version + +struct NoCaseComparator { + bool operator()(const String &p_a, const String &p_b) const { + return p_a.nocasecmp_to(p_b) < 0; + } +}; + +struct NaturalNoCaseComparator { + bool operator()(const String &p_a, const String &p_b) const { + return p_a.naturalnocasecmp_to(p_b) < 0; + } +}; + +template +_FORCE_INLINE_ bool is_str_less(const L *l_ptr, const R *r_ptr) { + while (true) { + if (*l_ptr == 0 && *r_ptr == 0) { + return false; + } else if (*l_ptr == 0) { + return true; + } else if (*r_ptr == 0) { + return false; + } else if (*l_ptr < *r_ptr) { + return true; + } else if (*l_ptr > *r_ptr) { + return false; + } + + l_ptr++; + r_ptr++; + } +} + +/* end of namespace */ + +// Tool translate (TTR and variants) for the editor UI, +// and doc translate for the class reference (DTR). +#if 0 +// Gets parsed. +String TTR(const String &p_text, const String &p_context = ""); +String DTR(const String &); +// Use for C strings. +#define TTRC(m_value) (m_value) +// Use to avoid parsing (for use later with C strings). +#define TTRGET(m_value) TTR(m_value) + +#else +#define TTR(m_value) (String()) +#define DTR(m_value) (String()) +#define TTRC(m_value) (m_value) +#define TTRGET(m_value) (m_value) +#endif + +// Use this to mark property names for editor translation. +// Often for dynamic properties defined in _get_property_list(). +// Property names defined directly inside EDITOR_DEF, GLOBAL_DEF, and ADD_PROPERTY macros don't need this. +#define PNAME(m_value) (m_value) + +// Similar to PNAME, but to mark groups, i.e. properties with PROPERTY_USAGE_GROUP. +// Groups defined directly inside ADD_GROUP macros don't need this. +// The arguments are the same as ADD_GROUP. m_prefix is only used for extraction. +#define GNAME(m_value, m_prefix) (m_value) + +// Runtime translate for the public node API. +String RTR(const String &); + +bool is_symbol(CharType c); +bool select_word(const String &p_s, int p_col, int &r_beg, int &r_end); + +#endif // USTRING_GODOT_H diff --git a/tests/godot_mockery/core/ustring2.cpp b/tests/godot_mockery/core/ustring2.cpp new file mode 100644 index 0000000..7fca808 --- /dev/null +++ b/tests/godot_mockery/core/ustring2.cpp @@ -0,0 +1,4620 @@ +/**************************************************************************/ +/* ustring.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifdef _MSC_VER +#define _CRT_SECURE_NO_WARNINGS // to disable build-time warning which suggested to use strcpy_s instead strcpy +#endif + +#include "ustring.h" + +#include "core/color.h" +#include "core/crypto/crypto_core.h" +#include "core/math/math_funcs.h" +#include "core/os/memory.h" +#include "core/print_string.h" +#include "core/string_name.h" +#include "core/translation.h" +#include "core/ucaps.h" +#include "core/variant.h" +#include "core/version_generated.gen.h" + +#include +#include + +#ifndef NO_USE_STDLIB +#include +#include +#endif + +#if defined(MINGW_ENABLED) || defined(_MSC_VER) +#define snprintf _snprintf_s +#endif + +#define MAX_DIGITS 6 +#define UPPERCASE(m_c) (((m_c) >= 'a' && (m_c) <= 'z') ? ((m_c) - ('a' - 'A')) : (m_c)) +#define LOWERCASE(m_c) (((m_c) >= 'A' && (m_c) <= 'Z') ? ((m_c) + ('a' - 'A')) : (m_c)) +#define IS_DIGIT(m_d) ((m_d) >= '0' && (m_d) <= '9') +#define IS_HEX_DIGIT(m_d) (((m_d) >= '0' && (m_d) <= '9') || ((m_d) >= 'a' && (m_d) <= 'f') || ((m_d) >= 'A' && (m_d) <= 'F')) + +const char CharString::_null = 0; +const CharType String::_null = 0; + +bool is_symbol(CharType c) { + return c != '_' && ((c >= '!' && c <= '/') || (c >= ':' && c <= '@') || (c >= '[' && c <= '`') || (c >= '{' && c <= '~') || c == '\t' || c == ' '); +} + +bool select_word(const String &p_s, int p_col, int &r_beg, int &r_end) { + const String &s = p_s; + int beg = CLAMP(p_col, 0, s.length()); + int end = beg; + + if (s[beg] > 32 || beg == s.length()) { + bool symbol = beg < s.length() && is_symbol(s[beg]); + + while (beg > 0 && s[beg - 1] > 32 && (symbol == is_symbol(s[beg - 1]))) { + beg--; + } + while (end < s.length() && s[end + 1] > 32 && (symbol == is_symbol(s[end + 1]))) { + end++; + } + + if (end < s.length()) { + end += 1; + } + + r_beg = beg; + r_end = end; + + return true; + } else { + return false; + } +} + +/** STRING **/ + +bool CharString::operator<(const CharString &p_right) const { + if (length() == 0) { + return p_right.length() != 0; + } + + return is_str_less(get_data(), p_right.get_data()); +} + +CharString &CharString::operator+=(char p_char) { + const int lhs_len = length(); + resize(lhs_len + 2); + + char *dst = ptrw(); + dst[lhs_len] = p_char; + dst[lhs_len + 1] = 0; + + return *this; +} + +const char *CharString::get_data() const { + if (size()) { + return &operator[](0); + } else { + return ""; + } +} + +CharString &CharString::operator=(const char *p_cstr) { + copy_from(p_cstr); + return *this; +} + +void CharString::copy_from(const char *p_cstr) { + if (!p_cstr) { + resize(0); + return; + } + + size_t len = strlen(p_cstr); + + if (len == 0) { + resize(0); + return; + } + + resize(len + 1); // include terminating null char + + strcpy(ptrw(), p_cstr); +} + +Error String::parse_url(String &r_scheme, String &r_host, int &r_port, String &r_path) const { + // Splits the URL into scheme, host, port, path. Strip credentials when present. + String base = *this; + r_scheme = ""; + r_host = ""; + r_port = 0; + r_path = ""; + int pos = base.find("://"); + // Scheme + if (pos != -1) { + r_scheme = base.substr(0, pos + 3).to_lower(); + base = base.substr(pos + 3, base.length() - pos - 3); + } + pos = base.find("/"); + // Path + if (pos != -1) { + r_path = base.substr(pos, base.length() - pos); + base = base.substr(0, pos); + } + // Host + pos = base.find("@"); + if (pos != -1) { + // Strip credentials + base = base.substr(pos + 1, base.length() - pos - 1); + } + if (base.begins_with("[")) { + // Literal IPv6 + pos = base.rfind("]"); + if (pos == -1) { + return ERR_INVALID_PARAMETER; + } + r_host = base.substr(1, pos - 1); + base = base.substr(pos + 1, base.length() - pos - 1); + } else { + // Anything else + if (base.get_slice_count(":") > 2) { + return ERR_INVALID_PARAMETER; + } + pos = base.rfind(":"); + if (pos == -1) { + r_host = base; + base = ""; + } else { + r_host = base.substr(0, pos); + base = base.substr(pos, base.length() - pos); + } + } + if (r_host.empty()) { + return ERR_INVALID_PARAMETER; + } + r_host = r_host.to_lower(); + // Port + if (base.begins_with(":")) { + base = base.substr(1, base.length() - 1); + if (!base.is_valid_integer()) { + return ERR_INVALID_PARAMETER; + } + r_port = base.to_int(); + if (r_port < 1 || r_port > 65535) { + return ERR_INVALID_PARAMETER; + } + } + return OK; +} + +void String::copy_from(const char *p_cstr) { + if (!p_cstr) { + resize(0); + return; + } + + const size_t len = strlen(p_cstr); + + if (len == 0) { + resize(0); + return; + } + + resize(len + 1); // include 0 + + CharType *dst = this->ptrw(); + + for (size_t i = 0; i <= len; i++) { + dst[i] = p_cstr[i]; + } +} + +void String::copy_from(const CharType *p_cstr, const int p_clip_to) { + if (!p_cstr) { + resize(0); + return; + } + + int len = 0; + const CharType *ptr = p_cstr; + while ((p_clip_to < 0 || len < p_clip_to) && *(ptr++) != 0) { + len++; + } + + if (len == 0) { + resize(0); + return; + } + + copy_from_unchecked(p_cstr, len); +} + +// assumes the following have already been validated: +// p_char != NULL +// p_length > 0 +// p_length <= p_char strlen +void String::copy_from_unchecked(const CharType *p_char, const int p_length) { + resize(p_length + 1); + + CharType *dst = ptrw(); + memcpy(dst, p_char, p_length * sizeof(CharType)); + dst[p_length] = 0; +} + +void String::copy_from(const CharType &p_char) { + resize(2); + CharType *dst = ptrw(); + dst[0] = p_char; + dst[1] = 0; +} + +bool String::operator==(const String &p_str) const { + if (length() != p_str.length()) { + return false; + } + if (empty()) { + return true; + } + + int l = length(); + + const CharType *src = c_str(); + const CharType *dst = p_str.c_str(); + + /* Compare char by char */ + for (int i = 0; i < l; i++) { + if (src[i] != dst[i]) { + return false; + } + } + + return true; +} + +bool String::operator!=(const String &p_str) const { + return !(*this == p_str); +} + +String String::operator+(const String &p_str) const { + String res = *this; + res += p_str; + return res; +} + +String &String::operator+=(const String &p_str) { + const int lhs_len = length(); + if (lhs_len == 0) { + *this = p_str; + return *this; + } + + const int rhs_len = p_str.length(); + if (rhs_len == 0) { + return *this; + } + + resize(lhs_len + rhs_len + 1); + + const CharType *src = p_str.c_str(); + CharType *dst = ptrw() + lhs_len; + + memcpy(dst, src, (rhs_len + 1) * sizeof(CharType)); + + return *this; +} + +String &String::operator+=(const CharType *p_str) { + *this += String(p_str); + return *this; +} + +String &String::operator+=(CharType p_char) { + const int lhs_len = length(); + resize(lhs_len + 2); + + CharType *dst = ptrw(); + dst[lhs_len] = p_char; + dst[lhs_len + 1] = 0; + + return *this; +} + +String &String::operator+=(const char *p_str) { + if (!p_str || p_str[0] == 0) { + return *this; + } + + const size_t rhs_len = strlen(p_str); + const int lhs_len = length(); + + resize(lhs_len + rhs_len + 1); + + CharType *dst = ptrw() + lhs_len; + + for (size_t i = 0; i <= rhs_len; i++) { + dst[i] = p_str[i]; + } + + return *this; +} + +void String::operator=(const char *p_str) { + copy_from(p_str); +} + +void String::operator=(const CharType *p_str) { + copy_from(p_str); +} + +bool String::operator==(const StrRange &p_str_range) const { + int len = p_str_range.len; + + if (length() != len) { + return false; + } + if (empty()) { + return true; + } + + const CharType *c_str = p_str_range.c_str; + const CharType *dst = &operator[](0); + + /* Compare char by char */ + for (int i = 0; i < len; i++) { + if (c_str[i] != dst[i]) { + return false; + } + } + + return true; +} + +bool String::operator==(const char *p_str) const { + int len = 0; + const char *aux = p_str; + + while (*(aux++) != 0) { + len++; + } + + if (length() != len) { + return false; + } + if (empty()) { + return true; + } + + int l = length(); + + const CharType *dst = c_str(); + + /* Compare char by char */ + for (int i = 0; i < l; i++) { + if (p_str[i] != dst[i]) { + return false; + } + } + + return true; +} + +bool String::operator==(const CharType *p_str) const { + int len = 0; + const CharType *aux = p_str; + + while (*(aux++) != 0) { + len++; + } + + if (length() != len) { + return false; + } + if (empty()) { + return true; + } + + int l = length(); + + const CharType *dst = c_str(); + + /* Compare char by char */ + for (int i = 0; i < l; i++) { + if (p_str[i] != dst[i]) { + return false; + } + } + + return true; +} + +bool String::operator!=(const char *p_str) const { + return (!(*this == p_str)); +} + +bool String::operator!=(const CharType *p_str) const { + return (!(*this == p_str)); +} + +bool String::operator<(const CharType *p_str) const { + if (empty() && p_str[0] == 0) { + return false; + } + if (empty()) { + return true; + } + + return is_str_less(c_str(), p_str); +} + +bool String::operator<=(const String &p_str) const { + return (*this < p_str) || (*this == p_str); +} + +bool String::operator<(const char *p_str) const { + if (empty() && p_str[0] == 0) { + return false; + } + if (empty()) { + return true; + } + + return is_str_less(c_str(), p_str); +} + +bool String::operator<(const String &p_str) const { + return operator<(p_str.c_str()); +} + +signed char String::nocasecmp_to(const String &p_str) const { + if (empty() && p_str.empty()) { + return 0; + } + if (empty()) { + return -1; + } + if (p_str.empty()) { + return 1; + } + + const CharType *that_str = p_str.c_str(); + const CharType *this_str = c_str(); + + while (true) { + if (*that_str == 0 && *this_str == 0) { + return 0; //we're equal + } else if (*this_str == 0) { + return -1; //if this is empty, and the other one is not, then we're less.. I think? + } else if (*that_str == 0) { + return 1; //otherwise the other one is smaller.. + } else if (_find_upper(*this_str) < _find_upper(*that_str)) { //more than + return -1; + } else if (_find_upper(*this_str) > _find_upper(*that_str)) { //less than + return 1; + } + + this_str++; + that_str++; + } +} + +signed char String::casecmp_to(const String &p_str) const { + if (empty() && p_str.empty()) { + return 0; + } + if (empty()) { + return -1; + } + if (p_str.empty()) { + return 1; + } + + const CharType *that_str = p_str.c_str(); + const CharType *this_str = c_str(); + + while (true) { + if (*that_str == 0 && *this_str == 0) { + return 0; //we're equal + } else if (*this_str == 0) { + return -1; //if this is empty, and the other one is not, then we're less.. I think? + } else if (*that_str == 0) { + return 1; //otherwise the other one is smaller.. + } else if (*this_str < *that_str) { //more than + return -1; + } else if (*this_str > *that_str) { //less than + return 1; + } + + this_str++; + that_str++; + } +} + +signed char String::naturalnocasecmp_to(const String &p_str) const { + const CharType *this_str = c_str(); + const CharType *that_str = p_str.c_str(); + + if (this_str && that_str) { + while (*this_str == '.' || *that_str == '.') { + if (*this_str++ != '.') { + return 1; + } + if (*that_str++ != '.') { + return -1; + } + if (!*that_str) { + return 1; + } + if (!*this_str) { + return -1; + } + } + + while (*this_str) { + if (!*that_str) { + return 1; + } else if (IS_DIGIT(*this_str)) { + if (!IS_DIGIT(*that_str)) { + return -1; + } + + // Keep ptrs to start of numerical sequences + const CharType *this_substr = this_str; + const CharType *that_substr = that_str; + + // Compare lengths of both numerical sequences, ignoring leading zeros + while (IS_DIGIT(*this_str)) { + this_str++; + } + while (IS_DIGIT(*that_str)) { + that_str++; + } + while (*this_substr == '0') { + this_substr++; + } + while (*that_substr == '0') { + that_substr++; + } + int this_len = this_str - this_substr; + int that_len = that_str - that_substr; + + if (this_len < that_len) { + return -1; + } else if (this_len > that_len) { + return 1; + } + + // If lengths equal, compare lexicographically + while (this_substr != this_str && that_substr != that_str) { + if (*this_substr < *that_substr) { + return -1; + } else if (*this_substr > *that_substr) { + return 1; + } + this_substr++; + that_substr++; + } + } else if (IS_DIGIT(*that_str)) { + return 1; + } else { + if (_find_upper(*this_str) < _find_upper(*that_str)) { //more than + return -1; + } else if (_find_upper(*this_str) > _find_upper(*that_str)) { //less than + return 1; + } + + this_str++; + that_str++; + } + } + if (*that_str) { + return -1; + } + } + + return 0; +} + +void String::erase(int p_pos, int p_chars) { + *this = left(p_pos) + substr(p_pos + p_chars, length() - ((p_pos + p_chars))); +} + +String String::capitalize() const { + String aux = this->camelcase_to_underscore(true).replace("_", " ").strip_edges(); + String cap; + for (int i = 0; i < aux.get_slice_count(" "); i++) { + String slice = aux.get_slicec(' ', i); + if (slice.length() > 0) { + slice[0] = _find_upper(slice[0]); + if (i > 0) { + cap += " "; + } + cap += slice; + } + } + + return cap; +} + +String String::camelcase_to_underscore(bool lowercase) const { + const CharType *cstr = c_str(); + String new_string; + const char A = 'A', Z = 'Z'; + const char a = 'a', z = 'z'; + int start_index = 0; + + for (int i = 1; i < this->size(); i++) { + bool is_upper = cstr[i] >= A && cstr[i] <= Z; + bool is_number = cstr[i] >= '0' && cstr[i] <= '9'; + bool are_next_2_lower = false; + bool is_next_lower = false; + bool is_next_number = false; + bool was_precedent_upper = cstr[i - 1] >= A && cstr[i - 1] <= Z; + bool was_precedent_number = cstr[i - 1] >= '0' && cstr[i - 1] <= '9'; + + if (i + 2 < this->size()) { + are_next_2_lower = cstr[i + 1] >= a && cstr[i + 1] <= z && cstr[i + 2] >= a && cstr[i + 2] <= z; + } + + if (i + 1 < this->size()) { + is_next_lower = cstr[i + 1] >= a && cstr[i + 1] <= z; + is_next_number = cstr[i + 1] >= '0' && cstr[i + 1] <= '9'; + } + + const bool cond_a = is_upper && !was_precedent_upper && !was_precedent_number; + const bool cond_b = was_precedent_upper && is_upper && are_next_2_lower; + const bool cond_c = is_number && !was_precedent_number; + const bool can_break_number_letter = is_number && !was_precedent_number && is_next_lower; + const bool can_break_letter_number = !is_number && was_precedent_number && (is_next_lower || is_next_number); + + bool should_split = cond_a || cond_b || cond_c || can_break_number_letter || can_break_letter_number; + if (should_split) { + new_string += this->substr(start_index, i - start_index) + "_"; + start_index = i; + } + } + + new_string += this->substr(start_index, this->size() - start_index); + return lowercase ? new_string.to_lower() : new_string; +} + +int String::get_slice_count(String p_splitter) const { + if (empty()) { + return 0; + } + if (p_splitter.empty()) { + return 0; + } + + int pos = 0; + int slices = 1; + + while ((pos = find(p_splitter, pos)) >= 0) { + slices++; + pos += p_splitter.length(); + } + + return slices; +} + +String String::get_slice(String p_splitter, int p_slice) const { + if (empty() || p_splitter.empty()) { + return ""; + } + + int pos = 0; + int prev_pos = 0; + //int slices=1; + if (p_slice < 0) { + return ""; + } + if (find(p_splitter) == -1) { + return *this; + } + + int i = 0; + while (true) { + pos = find(p_splitter, pos); + if (pos == -1) { + pos = length(); //reached end + } + + int from = prev_pos; + //int to=pos; + + if (p_slice == i) { + return substr(from, pos - from); + } + + if (pos == length()) { //reached end and no find + break; + } + pos += p_splitter.length(); + prev_pos = pos; + i++; + } + + return ""; //no find! +} + +String String::get_slicec(CharType p_splitter, int p_slice) const { + if (empty()) { + return String(); + } + + if (p_slice < 0) { + return String(); + } + + const CharType *c = this->ptr(); + int i = 0; + int prev = 0; + int count = 0; + while (true) { + if (c[i] == 0 || c[i] == p_splitter) { + if (p_slice == count) { + return substr(prev, i - prev); + } else if (c[i] == 0) { + return String(); + } else { + count++; + prev = i + 1; + } + } + + i++; + } +} + +Vector String::split_spaces() const { + Vector ret; + int from = 0; + int i = 0; + int len = length(); + if (len == 0) { + return ret; + } + + bool inside = false; + + while (true) { + bool empty = operator[](i) < 33; + + if (i == 0) { + inside = !empty; + } + + if (!empty && !inside) { + inside = true; + from = i; + } + + if (empty && inside) { + ret.push_back(substr(from, i - from)); + inside = false; + } + + if (i == len) { + break; + } + i++; + } + + return ret; +} + +Vector String::split(const String &p_splitter, bool p_allow_empty, int p_maxsplit) const { + Vector ret; + int from = 0; + int len = length(); + + while (true) { + int end = find(p_splitter, from); + if (end < 0) { + end = len; + } + if (p_allow_empty || (end > from)) { + if (p_maxsplit <= 0) { + ret.push_back(substr(from, end - from)); + } else { + // Put rest of the string and leave cycle. + if (p_maxsplit == ret.size()) { + ret.push_back(substr(from, len)); + break; + } + + // Otherwise, push items until positive limit is reached. + ret.push_back(substr(from, end - from)); + } + } + + if (end == len) { + break; + } + + from = end + p_splitter.length(); + } + + return ret; +} + +Vector String::rsplit(const String &p_splitter, bool p_allow_empty, int p_maxsplit) const { + Vector ret; + const int len = length(); + int remaining_len = len; + + while (true) { + if (remaining_len < p_splitter.length() || (p_maxsplit > 0 && p_maxsplit == ret.size())) { + // no room for another splitter or hit max splits, push what's left and we're done + if (p_allow_empty || remaining_len > 0) { + ret.push_back(substr(0, remaining_len)); + } + break; + } + + int left_edge = rfind(p_splitter, remaining_len - p_splitter.length()); + + if (left_edge < 0) { + // no more splitters, we're done + ret.push_back(substr(0, remaining_len)); + break; + } + + int substr_start = left_edge + p_splitter.length(); + if (p_allow_empty || substr_start < remaining_len) { + ret.push_back(substr(substr_start, remaining_len - substr_start)); + } + + remaining_len = left_edge; + } + + ret.invert(); + return ret; +} + +Vector String::split_floats(const String &p_splitter, bool p_allow_empty) const { + Vector ret; + int from = 0; + int len = length(); + + String buffer = *this; + while (true) { + int end = find(p_splitter, from); + if (end < 0) { + end = len; + } + if (p_allow_empty || (end > from)) { + buffer[end] = 0; + ret.push_back(String::to_double(&buffer.c_str()[from])); + buffer[end] = _cowdata.get(end); + } + + if (end == len) { + break; + } + + from = end + p_splitter.length(); + } + + return ret; +} + +Vector String::split_floats_mk(const Vector &p_splitters, bool p_allow_empty) const { + Vector ret; + int from = 0; + int len = length(); + + String buffer = *this; + while (true) { + int idx; + int end = findmk(p_splitters, from, &idx); + int spl_len = 1; + if (end < 0) { + end = len; + } else { + spl_len = p_splitters[idx].length(); + } + + if (p_allow_empty || (end > from)) { + buffer[end] = 0; + ret.push_back(String::to_double(&buffer.c_str()[from])); + buffer[end] = _cowdata.get(end); + } + + if (end == len) { + break; + } + + from = end + spl_len; + } + + return ret; +} + +Vector String::split_ints(const String &p_splitter, bool p_allow_empty) const { + Vector ret; + int from = 0; + int len = length(); + + while (true) { + int end = find(p_splitter, from); + if (end < 0) { + end = len; + } + if (p_allow_empty || (end > from)) { + ret.push_back(String::to_int(&c_str()[from], end - from)); + } + + if (end == len) { + break; + } + + from = end + p_splitter.length(); + } + + return ret; +} + +Vector String::split_ints_mk(const Vector &p_splitters, bool p_allow_empty) const { + Vector ret; + int from = 0; + int len = length(); + + while (true) { + int idx; + int end = findmk(p_splitters, from, &idx); + int spl_len = 1; + if (end < 0) { + end = len; + } else { + spl_len = p_splitters[idx].length(); + } + + if (p_allow_empty || (end > from)) { + ret.push_back(String::to_int(&c_str()[from], end - from)); + } + + if (end == len) { + break; + } + + from = end + spl_len; + } + + return ret; +} + +String String::join(const Vector &parts) const { + String ret; + for (int i = 0; i < parts.size(); ++i) { + if (i > 0) { + ret += *this; + } + ret += parts[i]; + } + return ret; +} + +CharType String::char_uppercase(CharType p_char) { + return _find_upper(p_char); +} + +CharType String::char_lowercase(CharType p_char) { + return _find_lower(p_char); +} + +String String::to_upper() const { + String upper = *this; + + for (int i = 0; i < upper.size(); i++) { + const CharType s = upper[i]; + const CharType t = _find_upper(s); + if (s != t) { // avoid copy on write + upper[i] = t; + } + } + + return upper; +} + +String String::to_lower() const { + String lower = *this; + + for (int i = 0; i < lower.size(); i++) { + const CharType s = lower[i]; + const CharType t = _find_lower(s); + if (s != t) { // avoid copy on write + lower[i] = t; + } + } + + return lower; +} + +const CharType *String::c_str() const { + static const CharType zero = 0; + + return size() ? &operator[](0) : &zero; +} + + +String String::md5(const uint8_t *p_md5) { + return String::hex_encode_buffer(p_md5, 16); +} + +String String::hex_encode_buffer(const uint8_t *p_buffer, int p_len) { + static const char hex[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + + String ret; + char v[2] = { 0, 0 }; + + for (int i = 0; i < p_len; i++) { + v[0] = hex[p_buffer[i] >> 4]; + ret += v; + v[0] = hex[p_buffer[i] & 0xF]; + ret += v; + } + + return ret; +} + +String String::chr(CharType p_char) { + CharType c[2] = { p_char, 0 }; + return String(c); +} +String String::num(double p_num, int p_decimals) { + if (Math::is_nan(p_num)) { + return "nan"; + } + +#ifndef NO_USE_STDLIB + + if (p_decimals > 16) { + p_decimals = 16; + } + + char fmt[7]; + fmt[0] = '%'; + fmt[1] = '.'; + + if (p_decimals < 0) { + fmt[1] = 'l'; + fmt[2] = 'f'; + fmt[3] = 0; + + } else if (p_decimals < 10) { + fmt[2] = '0' + p_decimals; + fmt[3] = 'l'; + fmt[4] = 'f'; + fmt[5] = 0; + } else { + fmt[2] = '0' + (p_decimals / 10); + fmt[3] = '0' + (p_decimals % 10); + fmt[4] = 'l'; + fmt[5] = 'f'; + fmt[6] = 0; + } + char buf[256]; + +#if defined(__GNUC__) || defined(_MSC_VER) + snprintf(buf, 256, fmt, p_num); +#else + sprintf(buf, fmt, p_num); +#endif + + buf[255] = 0; + //destroy trailing zeroes + { + bool period = false; + int z = 0; + while (buf[z]) { + if (buf[z] == '.') { + period = true; + } + z++; + } + + if (period) { + z--; + while (z > 0) { + if (buf[z] == '0') { + buf[z] = 0; + } else if (buf[z] == '.') { + buf[z] = 0; + break; + } else { + break; + } + + z--; + } + } + } + + return buf; +#else + + String s; + String sd; + /* integer part */ + + bool neg = p_num < 0; + p_num = ABS(p_num); + int intn = (int)p_num; + + /* decimal part */ + + if (p_decimals > 0 || (p_decimals == -1 && (int)p_num != p_num)) { + double dec = p_num - (float)((int)p_num); + + int digit = 0; + if (p_decimals > MAX_DIGITS) + p_decimals = MAX_DIGITS; + + int dec_int = 0; + int dec_max = 0; + + while (true) { + dec *= 10.0; + dec_int = dec_int * 10 + (int)dec % 10; + dec_max = dec_max * 10 + 9; + digit++; + + if (p_decimals == -1) { + if (digit == MAX_DIGITS) //no point in going to infinite + break; + + if ((dec - (float)((int)dec)) < 1e-6) + break; + } + + if (digit == p_decimals) + break; + } + dec *= 10; + int last = (int)dec % 10; + + if (last > 5) { + if (dec_int == dec_max) { + dec_int = 0; + intn++; + } else { + dec_int++; + } + } + + String decimal; + for (int i = 0; i < digit; i++) { + char num[2] = { 0, 0 }; + num[0] = '0' + dec_int % 10; + decimal = num + decimal; + dec_int /= 10; + } + sd = '.' + decimal; + } + + if (intn == 0) + + s = "0"; + else { + while (intn) { + CharType num = '0' + (intn % 10); + intn /= 10; + s = num + s; + } + } + + s = s + sd; + if (neg) + s = "-" + s; + return s; +#endif +} + +String String::num_int64(int64_t p_num, int base, bool capitalize_hex) { + bool sign = p_num < 0; + + int64_t n = p_num; + + int chars = 0; + do { + n /= base; + chars++; + } while (n); + + if (sign) { + chars++; + } + String s; + s.resize(chars + 1); + CharType *c = s.ptrw(); + c[chars] = 0; + n = p_num; + do { + int mod = ABS(n % base); + if (mod >= 10) { + char a = (capitalize_hex ? 'A' : 'a'); + c[--chars] = a + (mod - 10); + } else { + c[--chars] = '0' + mod; + } + + n /= base; + } while (n); + + if (sign) { + c[0] = '-'; + } + + return s; +} + +String String::num_uint64(uint64_t p_num, int base, bool capitalize_hex) { + uint64_t n = p_num; + + int chars = 0; + do { + n /= base; + chars++; + } while (n); + + String s; + s.resize(chars + 1); + CharType *c = s.ptrw(); + c[chars] = 0; + n = p_num; + do { + int mod = n % base; + if (mod >= 10) { + char a = (capitalize_hex ? 'A' : 'a'); + c[--chars] = a + (mod - 10); + } else { + c[--chars] = '0' + mod; + } + + n /= base; + } while (n); + + return s; +} + +String String::num_real(double p_num) { + String s; + String sd; + /* integer part */ + + bool neg = p_num < 0; + p_num = ABS(p_num); + int intn = (int)p_num; + + /* decimal part */ + + if ((int)p_num != p_num) { + double dec = p_num - (float)((int)p_num); + + int digit = 0; + int decimals = MAX_DIGITS; + + int dec_int = 0; + int dec_max = 0; + + while (true) { + dec *= 10.0; + dec_int = dec_int * 10 + (int)dec % 10; + dec_max = dec_max * 10 + 9; + digit++; + + if ((dec - (float)((int)dec)) < 1e-6) { + break; + } + + if (digit == decimals) { + break; + } + } + + dec *= 10; + int last = (int)dec % 10; + + if (last > 5) { + if (dec_int == dec_max) { + dec_int = 0; + intn++; + } else { + dec_int++; + } + } + + String decimal; + for (int i = 0; i < digit; i++) { + char num[2] = { 0, 0 }; + num[0] = '0' + dec_int % 10; + decimal = num + decimal; + dec_int /= 10; + } + sd = '.' + decimal; + } else { + sd = ".0"; + } + + if (intn == 0) { + s = "0"; + } else { + while (intn) { + CharType num = '0' + (intn % 10); + intn /= 10; + s = num + s; + } + } + + s = s + sd; + if (neg) { + s = "-" + s; + } + return s; +} + +String String::num_scientific(double p_num) { + if (Math::is_nan(p_num)) { + return "nan"; + } + +#ifndef NO_USE_STDLIB + + char buf[256]; + +#if defined(__GNUC__) || defined(_MSC_VER) + +#if defined(__MINGW32__) && defined(_TWO_DIGIT_EXPONENT) && !defined(_UCRT) + // MinGW requires _set_output_format() to conform to C99 output for printf + unsigned int old_exponent_format = _set_output_format(_TWO_DIGIT_EXPONENT); +#endif + snprintf(buf, 256, "%lg", p_num); + +#if defined(__MINGW32__) && defined(_TWO_DIGIT_EXPONENT) && !defined(_UCRT) + _set_output_format(old_exponent_format); +#endif + +#else + sprintf(buf, "%.16lg", p_num); +#endif + + buf[255] = 0; + + return buf; +#else + + return String::num(p_num); +#endif +} + +CharString String::ascii(bool p_allow_extended) const { + if (!length()) { + return CharString(); + } + + CharString cs; + cs.resize(size()); + + for (int i = 0; i < size(); i++) { + cs[i] = operator[](i); + } + + return cs; +} + +String String::utf8(const char *p_utf8, int p_len) { + String ret; + ret.parse_utf8(p_utf8, p_len); + + return ret; +}; + +bool String::parse_utf8(const char *p_utf8, int p_len, bool p_skip_cr) { +#define _UNICERROR(m_err) fprintf(stderr, "%s", m_err) + + if (!p_utf8) { + return true; + } + + String aux; + + int cstr_size = 0; + int str_size = 0; + + /* HANDLE BOM (Byte Order Mark) */ + if (p_len < 0 || p_len >= 3) { + bool has_bom = uint8_t(p_utf8[0]) == 0xEF && uint8_t(p_utf8[1]) == 0xBB && uint8_t(p_utf8[2]) == 0xBF; + if (has_bom) { + //just skip it + if (p_len >= 0) { + p_len -= 3; + } + p_utf8 += 3; + } + } + + { + const char *ptrtmp = p_utf8; + const char *ptrtmp_limit = &p_utf8[p_len]; + int skip = 0; + while (ptrtmp != ptrtmp_limit && *ptrtmp) { + if (skip == 0) { +#if CHAR_MIN == 0 + uint8_t c = *ptrtmp; +#else + uint8_t c = *ptrtmp >= 0 ? *ptrtmp : uint8_t(256 + *ptrtmp); +#endif + + if (p_skip_cr && c == '\r') { + ptrtmp++; + continue; + } + + /* Determine the number of characters in sequence */ + if ((c & 0x80) == 0) { + skip = 0; + } else if ((c & 0xE0) == 0xC0) { + skip = 1; + } else if ((c & 0xF0) == 0xE0) { + skip = 2; + } else if ((c & 0xF8) == 0xF0) { + skip = 3; + if (sizeof(wchar_t) == 2) { + str_size++; // encode as surrogate pair. + } + } else if ((c & 0xFC) == 0xF8) { + skip = 4; + // invalid character, too long to encode as surrogates. + } else if ((c & 0xFE) == 0xFC) { + skip = 5; + // invalid character, too long to encode as surrogates. + } else { + _UNICERROR("invalid skip"); + return true; //invalid utf8 + } + + if (skip == 1 && (c & 0x1E) == 0) { + //printf("overlong rejected\n"); + _UNICERROR("overlong rejected"); + return true; //reject overlong + } + + str_size++; + + } else { + --skip; + } + + cstr_size++; + ptrtmp++; + } + + if (skip) { + _UNICERROR("no space left"); + return true; //not enough spac + } + } + + if (str_size == 0) { + clear(); + return false; + } + + resize(str_size + 1); + CharType *dst = ptrw(); + dst[str_size] = 0; + + while (cstr_size) { + int len = 0; + + if (p_skip_cr && *p_utf8 == '\r') { + p_utf8++; + continue; + } + + /* Determine the number of characters in sequence */ + if ((*p_utf8 & 0x80) == 0) { + len = 1; + } else if ((*p_utf8 & 0xE0) == 0xC0) { + len = 2; + } else if ((*p_utf8 & 0xF0) == 0xE0) { + len = 3; + } else if ((*p_utf8 & 0xF8) == 0xF0) { + len = 4; + } else if ((*p_utf8 & 0xFC) == 0xF8) { + len = 5; + } else if ((*p_utf8 & 0xFE) == 0xFC) { + len = 6; + } else { + _UNICERROR("invalid len"); + + return true; //invalid UTF8 + } + + if (len > cstr_size) { + _UNICERROR("no space left"); + return true; //not enough space + } + + if (len == 2 && (*p_utf8 & 0x1E) == 0) { + //printf("overlong rejected\n"); + _UNICERROR("no space left"); + return true; //reject overlong + } + + /* Convert the first character */ + + uint32_t unichar = 0; + + if (len == 1) { + unichar = *p_utf8; + } else { + unichar = (0xFF >> (len + 1)) & *p_utf8; + + for (int i = 1; i < len; i++) { + if ((p_utf8[i] & 0xC0) != 0x80) { + _UNICERROR("invalid utf8"); + return true; //invalid utf8 + } + if (unichar == 0 && i == 2 && ((p_utf8[i] & 0x7F) >> (7 - len)) == 0) { + _UNICERROR("invalid utf8 overlong"); + return true; //no overlong + } + unichar = (unichar << 6) | (p_utf8[i] & 0x3F); + } + } + + if (sizeof(wchar_t) == 2 && unichar > 0x10FFFF) { + unichar = ' '; // invalid character, too long to encode as surrogates. + } else if (sizeof(wchar_t) == 2 && unichar > 0xFFFF) { + *(dst++) = uint32_t((unichar >> 10) + 0xD7C0); // lead surrogate. + *(dst++) = uint32_t((unichar & 0x3FF) | 0xDC00); // trail surrogate. + } else { + *(dst++) = unichar; + } + cstr_size -= len; + p_utf8 += len; + } + + return false; +} + +CharString String::utf8() const { + int l = length(); + if (!l) { + return CharString(); + } + + const CharType *d = &operator[](0); + int fl = 0; + for (int i = 0; i < l; i++) { + uint32_t c = d[i]; + if ((c & 0xfffffc00) == 0xd800) { // decode surrogate pair. + if ((i < l - 1) && (d[i + 1] & 0xfffffc00) == 0xdc00) { + c = (c << 10UL) + d[i + 1] - ((0xd800 << 10UL) + 0xdc00 - 0x10000); + i++; // skip trail surrogate. + } else { + fl += 1; + continue; + } + } else if ((c & 0xfffffc00) == 0xdc00) { + fl += 1; + continue; + } + if (c <= 0x7f) { // 7 bits. + fl += 1; + } else if (c <= 0x7ff) { // 11 bits + fl += 2; + } else if (c <= 0xffff) { // 16 bits + fl += 3; + } else if (c <= 0x001fffff) { // 21 bits + fl += 4; + } else if (c <= 0x03ffffff) { // 26 bits + fl += 5; + } else if (c <= 0x7fffffff) { // 31 bits + fl += 6; + } + } + + CharString utf8s; + if (fl == 0) { + return utf8s; + } + + utf8s.resize(fl + 1); + uint8_t *cdst = (uint8_t *)utf8s.get_data(); + +#define APPEND_CHAR(m_c) *(cdst++) = m_c + + for (int i = 0; i < l; i++) { + uint32_t c = d[i]; + if ((c & 0xfffffc00) == 0xd800) { // decode surrogate pair. + if ((i < l - 1) && (d[i + 1] & 0xfffffc00) == 0xdc00) { + c = (c << 10UL) + d[i + 1] - ((0xd800 << 10UL) + 0xdc00 - 0x10000); + i++; // skip trail surrogate. + } else { + APPEND_CHAR(' '); + continue; + } + } else if ((c & 0xfffffc00) == 0xdc00) { + APPEND_CHAR(' '); + continue; + } + + if (c <= 0x7f) { // 7 bits. + APPEND_CHAR(c); + } else if (c <= 0x7ff) { // 11 bits + + APPEND_CHAR(uint32_t(0xc0 | ((c >> 6) & 0x1f))); // Top 5 bits. + APPEND_CHAR(uint32_t(0x80 | (c & 0x3f))); // Bottom 6 bits. + } else if (c <= 0xffff) { // 16 bits + + APPEND_CHAR(uint32_t(0xe0 | ((c >> 12) & 0x0f))); // Top 4 bits. + APPEND_CHAR(uint32_t(0x80 | ((c >> 6) & 0x3f))); // Middle 6 bits. + APPEND_CHAR(uint32_t(0x80 | (c & 0x3f))); // Bottom 6 bits. + } else if (c <= 0x001fffff) { // 21 bits + + APPEND_CHAR(uint32_t(0xf0 | ((c >> 18) & 0x07))); // Top 3 bits. + APPEND_CHAR(uint32_t(0x80 | ((c >> 12) & 0x3f))); // Upper middle 6 bits. + APPEND_CHAR(uint32_t(0x80 | ((c >> 6) & 0x3f))); // Lower middle 6 bits. + APPEND_CHAR(uint32_t(0x80 | (c & 0x3f))); // Bottom 6 bits. + } else if (c <= 0x03ffffff) { // 26 bits + + APPEND_CHAR(uint32_t(0xf8 | ((c >> 24) & 0x03))); // Top 2 bits. + APPEND_CHAR(uint32_t(0x80 | ((c >> 18) & 0x3f))); // Upper middle 6 bits. + APPEND_CHAR(uint32_t(0x80 | ((c >> 12) & 0x3f))); // middle 6 bits. + APPEND_CHAR(uint32_t(0x80 | ((c >> 6) & 0x3f))); // Lower middle 6 bits. + APPEND_CHAR(uint32_t(0x80 | (c & 0x3f))); // Bottom 6 bits. + } else if (c <= 0x7fffffff) { // 31 bits + + APPEND_CHAR(uint32_t(0xfc | ((c >> 30) & 0x01))); // Top 1 bit. + APPEND_CHAR(uint32_t(0x80 | ((c >> 24) & 0x3f))); // Upper upper middle 6 bits. + APPEND_CHAR(uint32_t(0x80 | ((c >> 18) & 0x3f))); // Lower upper middle 6 bits. + APPEND_CHAR(uint32_t(0x80 | ((c >> 12) & 0x3f))); // Upper lower middle 6 bits. + APPEND_CHAR(uint32_t(0x80 | ((c >> 6) & 0x3f))); // Lower lower middle 6 bits. + APPEND_CHAR(uint32_t(0x80 | (c & 0x3f))); // Bottom 6 bits. + } + } +#undef APPEND_CHAR + *cdst = 0; //trailing zero + + return utf8s; +} + +String::String(const char *p_str) { + copy_from(p_str); +} + +String::String(const CharType *p_str, int p_clip_to_len) { + copy_from(p_str, p_clip_to_len); +} + +String::String(const StrRange &p_range) { + if (!p_range.c_str) { + return; + } + + copy_from(p_range.c_str, p_range.len); +} + +int String::hex_to_int(bool p_with_prefix) const { + int len = length(); + ERR_FAIL_COND_V_MSG(p_with_prefix ? len < 3 : len == 0, 0, String("Invalid hexadecimal notation length in string ") + (p_with_prefix ? "with" : "without") + " prefix \"" + *this + "\"."); + + const CharType *s = ptr(); + + int sign = s[0] == '-' ? -1 : 1; + + if (sign < 0) { + s++; + } + + if (p_with_prefix) { + ERR_FAIL_COND_V_MSG(s[0] != '0' || LOWERCASE(s[1]) != 'x', 0, "Invalid hexadecimal notation prefix in string \"" + *this + "\"."); + s += 2; + } + + int hex = 0; + + while (*s) { + CharType c = LOWERCASE(*s); + int n; + if (c >= '0' && c <= '9') { + n = c - '0'; + } else if (c >= 'a' && c <= 'f') { + n = (c - 'a') + 10; + } else { + ERR_FAIL_V_MSG(0, "Invalid hexadecimal notation character \"" + chr(*s) + "\" in string \"" + *this + "\"."); + } + // Check for overflow/underflow, with special case to ensure INT32_MIN does not result in error + bool overflow = ((hex > INT32_MAX / 16) && (sign == 1 || (sign == -1 && hex != (INT32_MAX >> 4) + 1))) || (sign == -1 && hex == (INT32_MAX >> 4) + 1 && c > '0'); + ERR_FAIL_COND_V_MSG(overflow, sign == 1 ? INT32_MAX : INT32_MIN, "Cannot represent " + *this + " as a 32-bit signed integer, since the value is " + (sign == 1 ? "too large." : "too small.")); + hex *= 16; + hex += n; + s++; + } + + return hex * sign; +} + +int64_t String::hex_to_int64(bool p_with_prefix) const { + int len = length(); + ERR_FAIL_COND_V_MSG(p_with_prefix ? len < 3 : len == 0, 0, String("Invalid hexadecimal notation length in string ") + (p_with_prefix ? "with" : "without") + " prefix \"" + *this + "\"."); + + const CharType *s = ptr(); + + int64_t sign = s[0] == '-' ? -1 : 1; + + if (sign < 0) { + s++; + } + + if (p_with_prefix) { + ERR_FAIL_COND_V_MSG(s[0] != '0' || LOWERCASE(s[1]) != 'x', 0, "Invalid hexadecimal notation prefix in string \"" + *this + "\"."); + s += 2; + } + + int64_t hex = 0; + + while (*s) { + CharType c = LOWERCASE(*s); + int64_t n; + if (c >= '0' && c <= '9') { + n = c - '0'; + } else if (c >= 'a' && c <= 'f') { + n = (c - 'a') + 10; + } else { + ERR_FAIL_V_MSG(0, "Invalid hexadecimal notation character \"" + chr(*s) + "\" in string \"" + *this + "\"."); + } + bool overflow = ((hex > INT64_MAX / 16) && (sign == 1 || (sign == -1 && hex != (INT64_MAX >> 4) + 1))) || (sign == -1 && hex == (INT64_MAX >> 4) + 1 && c > '0'); + ERR_FAIL_COND_V_MSG(overflow, sign == 1 ? INT64_MAX : INT64_MIN, "Cannot represent " + *this + " as a 64-bit signed integer, since the value is " + (sign == 1 ? "too large." : "too small.")); + hex *= 16; + hex += n; + s++; + } + + return hex * sign; +} + +int64_t String::bin_to_int64(bool p_with_prefix) const { + int len = length(); + ERR_FAIL_COND_V_MSG(p_with_prefix ? len < 3 : len == 0, 0, String("Invalid binary notation length in string ") + (p_with_prefix ? "with" : "without") + " prefix \"" + *this + "\"."); + + const CharType *s = ptr(); + + int64_t sign = s[0] == '-' ? -1 : 1; + + if (sign < 0) { + s++; + } + + if (p_with_prefix) { + ERR_FAIL_COND_V_MSG(s[0] != '0' || LOWERCASE(s[1]) != 'b', 0, "Invalid binary notation prefix in string \"" + *this + "\"."); + s += 2; + } + + int64_t binary = 0; + + while (*s) { + CharType c = LOWERCASE(*s); + int64_t n; + if (c == '0' || c == '1') { + n = c - '0'; + } else { + ERR_FAIL_V_MSG(0, "Invalid binary notation character \"" + chr(*s) + "\" in string \"" + *this + "\"."); + } + // Check for overflow/underflow, with special case to ensure INT64_MIN does not result in error + bool overflow = ((binary > INT64_MAX / 2) && (sign == 1 || (sign == -1 && binary != (INT64_MAX >> 1) + 1))) || (sign == -1 && binary == (INT64_MAX >> 1) + 1 && c > '0'); + ERR_FAIL_COND_V_MSG(overflow, sign == 1 ? INT64_MAX : INT64_MIN, "Cannot represent " + *this + " as a 64-bit signed integer, since the value is " + (sign == 1 ? "too large." : "too small.")); + binary *= 2; + binary += n; + s++; + } + + return binary * sign; +} + +int String::to_int() const { + if (length() == 0) { + return 0; + } + + int to = (find(".") >= 0) ? find(".") : length(); + + int integer = 0; + int sign = 1; + + for (int i = 0; i < to; i++) { + CharType c = operator[](i); + if (c >= '0' && c <= '9') { + bool overflow = (integer > INT32_MAX / 10) || (integer == INT32_MAX / 10 && ((sign == 1 && c > '7') || (sign == -1 && c > '8'))); + ERR_FAIL_COND_V_MSG(overflow, sign == 1 ? INT32_MAX : INT32_MIN, "Cannot represent " + *this + " as a 32-bit signed integer, since the value is " + (sign == 1 ? "too large." : "too small.")); + integer *= 10; + integer += c - '0'; + + } else if (integer == 0 && c == '-') { + sign = -sign; + } + } + + return integer * sign; +} + +int64_t String::to_int64() const { + if (length() == 0) { + return 0; + } + + int to = (find(".") >= 0) ? find(".") : length(); + + int64_t integer = 0; + int64_t sign = 1; + + for (int i = 0; i < to; i++) { + CharType c = operator[](i); + if (c >= '0' && c <= '9') { + bool overflow = (integer > INT64_MAX / 10) || (integer == INT64_MAX / 10 && ((sign == 1 && c > '7') || (sign == -1 && c > '8'))); + ERR_FAIL_COND_V_MSG(overflow, sign == 1 ? INT64_MAX : INT64_MIN, "Cannot represent " + *this + " as a 64-bit signed integer, since the value is " + (sign == 1 ? "too large." : "too small.")); + integer *= 10; + integer += c - '0'; + + } else if (integer == 0 && c == '-') { + sign = -sign; + } + } + + return integer * sign; +} + +int String::to_int(const char *p_str, int p_len) { + int to = 0; + if (p_len >= 0) { + to = p_len; + } else { + while (p_str[to] != 0 && p_str[to] != '.') { + to++; + } + } + + int integer = 0; + int sign = 1; + + for (int i = 0; i < to; i++) { + char c = p_str[i]; + if (c >= '0' && c <= '9') { + bool overflow = (integer > INT32_MAX / 10) || (integer == INT32_MAX / 10 && ((sign == 1 && c > '7') || (sign == -1 && c > '8'))); + ERR_FAIL_COND_V_MSG(overflow, sign == 1 ? INT32_MAX : INT32_MIN, "Cannot represent " + String(p_str).substr(0, to) + " as a 32-bit signed integer, since the value is " + (sign == 1 ? "too large." : "too small.")); + integer *= 10; + integer += c - '0'; + + } else if (c == '-' && integer == 0) { + sign = -sign; + } else if (c != ' ') { + break; + } + } + + return integer * sign; +} + +bool String::is_numeric() const { + if (length() == 0) { + return false; + }; + + int s = 0; + if (operator[](0) == '-') { + ++s; + } + bool dot = false; + for (int i = s; i < length(); i++) { + CharType c = operator[](i); + if (c == '.') { + if (dot) { + return false; + }; + dot = true; + } + if (c < '0' || c > '9') { + return false; + }; + }; + + return true; // TODO: Use the parser below for this instead +}; + +template +static double built_in_strtod( + /* A decimal ASCII floating-point number, + * optionally preceded by white space. Must + * have form "-I.FE-X", where I is the integer + * part of the mantissa, F is the fractional + * part of the mantissa, and X is the + * exponent. Either of the signs may be "+", + * "-", or omitted. Either I or F may be + * omitted, or both. The decimal point isn't + * necessary unless F is present. The "E" may + * actually be an "e". E and X may both be + * omitted (but not just one). */ + const C *string, + /* If non-nullptr, store terminating Cacter's + * address here. */ + C **endPtr = nullptr) { + /* Largest possible base 10 exponent. Any + * exponent larger than this will already + * produce underflow or overflow, so there's + * no need to worry about additional digits. */ + static const int maxExponent = 511; + /* Table giving binary powers of 10. Entry + * is 10^2^i. Used to convert decimal + * exponents into floating-point numbers. */ + static const double powersOf10[] = { + 10., + 100., + 1.0e4, + 1.0e8, + 1.0e16, + 1.0e32, + 1.0e64, + 1.0e128, + 1.0e256 + }; + + bool sign, expSign = false; + double fraction, dblExp; + const double *d; + const C *p; + int c; + /* Exponent read from "EX" field. */ + int exp = 0; + /* Exponent that derives from the fractional + * part. Under normal circumstances, it is + * the negative of the number of digits in F. + * However, if I is very long, the last digits + * of I get dropped (otherwise a long I with a + * large negative exponent could cause an + * unnecessary overflow on I alone). In this + * case, fracExp is incremented one for each + * dropped digit. */ + int fracExp = 0; + /* Number of digits in mantissa. */ + int mantSize; + /* Number of mantissa digits BEFORE decimal point. */ + int decPt; + /* Temporarily holds location of exponent in string. */ + const C *pExp; + + /* + * Strip off leading blanks and check for a sign. + */ + + p = string; + while (*p == ' ' || *p == '\t' || *p == '\n') { + p += 1; + } + if (*p == '-') { + sign = true; + p += 1; + } else { + if (*p == '+') { + p += 1; + } + sign = false; + } + + /* + * Count the number of digits in the mantissa (including the decimal + * point), and also locate the decimal point. + */ + + decPt = -1; + for (mantSize = 0;; mantSize += 1) { + c = *p; + if (!IS_DIGIT(c)) { + if ((c != '.') || (decPt >= 0)) { + break; + } + decPt = mantSize; + } + p += 1; + } + + /* + * Now suck up the digits in the mantissa. Use two integers to collect 9 + * digits each (this is faster than using floating-point). If the mantissa + * has more than 18 digits, ignore the extras, since they can't affect the + * value anyway. + */ + + pExp = p; + p -= mantSize; + if (decPt < 0) { + decPt = mantSize; + } else { + mantSize -= 1; /* One of the digits was the point. */ + } + if (mantSize > 18) { + fracExp = decPt - 18; + mantSize = 18; + } else { + fracExp = decPt - mantSize; + } + if (mantSize == 0) { + fraction = 0.0; + p = string; + goto done; + } else { + int frac1, frac2; + + frac1 = 0; + for (; mantSize > 9; mantSize -= 1) { + c = *p; + p += 1; + if (c == '.') { + c = *p; + p += 1; + } + frac1 = 10 * frac1 + (c - '0'); + } + frac2 = 0; + for (; mantSize > 0; mantSize -= 1) { + c = *p; + p += 1; + if (c == '.') { + c = *p; + p += 1; + } + frac2 = 10 * frac2 + (c - '0'); + } + fraction = (1.0e9 * frac1) + frac2; + } + + /* + * Skim off the exponent. + */ + + p = pExp; + if ((*p == 'E') || (*p == 'e')) { + p += 1; + if (*p == '-') { + expSign = true; + p += 1; + } else { + if (*p == '+') { + p += 1; + } + expSign = false; + } + if (!IS_DIGIT(CharType(*p))) { + p = pExp; + goto done; + } + while (IS_DIGIT(CharType(*p))) { + exp = exp * 10 + (*p - '0'); + p += 1; + } + } + if (expSign) { + exp = fracExp - exp; + } else { + exp = fracExp + exp; + } + + /* + * Generate a floating-point number that represents the exponent. Do this + * by processing the exponent one bit at a time to combine many powers of + * 2 of 10. Then combine the exponent with the fraction. + */ + + if (exp < 0) { + expSign = true; + exp = -exp; + } else { + expSign = false; + } + + if (exp > maxExponent) { + exp = maxExponent; + WARN_PRINT("Exponent too high"); + } + dblExp = 1.0; + for (d = powersOf10; exp != 0; exp >>= 1, ++d) { + if (exp & 01) { + dblExp *= *d; + } + } + if (expSign) { + fraction /= dblExp; + } else { + fraction *= dblExp; + } + +done: + if (endPtr != nullptr) { + *endPtr = (C *)p; + } + + if (sign) { + return -fraction; + } + return fraction; +} + +#define READING_SIGN 0 +#define READING_INT 1 +#define READING_DEC 2 +#define READING_EXP 3 +#define READING_DONE 4 + +double String::to_double(const char *p_str) { +#ifndef NO_USE_STDLIB + return built_in_strtod(p_str); +#else + return built_in_strtod(p_str); +#endif +} + +float String::to_float() const { + return to_double(); +} + +double String::to_double(const CharType *p_str, const CharType **r_end) { + return built_in_strtod(p_str, (CharType **)r_end); +} + +int64_t String::to_int(const CharType *p_str, int p_len) { + if (p_len == 0 || !p_str[0]) { + return 0; + } + ///@todo make more exact so saving and loading does not lose precision + + int64_t integer = 0; + int64_t sign = 1; + int reading = READING_SIGN; + + const CharType *str = p_str; + const CharType *limit = &p_str[p_len]; + + while (*str && reading != READING_DONE && str != limit) { + CharType c = *(str++); + switch (reading) { + case READING_SIGN: { + if (c >= '0' && c <= '9') { + reading = READING_INT; + // let it fallthrough + } else if (c == '-') { + sign = -1; + reading = READING_INT; + break; + } else if (c == '+') { + sign = 1; + reading = READING_INT; + break; + } else { + break; + } + } + case READING_INT: { + if (c >= '0' && c <= '9') { + if (integer > INT64_MAX / 10) { + String number(""); + str = p_str; + while (*str && str != limit) { + number += *(str++); + } + ERR_FAIL_V_MSG(sign == 1 ? INT64_MAX : INT64_MIN, "Cannot represent " + number + " as a 64-bit signed integer, since the value is " + (sign == 1 ? "too large." : "too small.")); + } + integer *= 10; + integer += c - '0'; + } else { + reading = READING_DONE; + } + + } break; + } + } + + return sign * integer; +} + +double String::to_double() const { + if (empty()) { + return 0; + } +#ifndef NO_USE_STDLIB + return built_in_strtod(c_str()); +//return wcstod(c_str(),NULL); DOES NOT WORK ON ANDROID :( +#else + return built_in_strtod(c_str()); +#endif +} + +bool operator==(const char *p_chr, const String &p_str) { + return p_str == p_chr; +} + +String operator+(const char *p_chr, const String &p_str) { + String tmp = p_chr; + tmp += p_str; + return tmp; +} +String operator+(CharType p_chr, const String &p_str) { + return (String::chr(p_chr) + p_str); +} + +uint32_t String::hash(const char *p_cstr) { + uint32_t hashv = 5381; + uint32_t c; + + while ((c = *p_cstr++)) { + hashv = ((hashv << 5) + hashv) + c; /* hash * 33 + c */ + } + + return hashv; +} + +uint32_t String::hash(const char *p_cstr, int p_len) { + uint32_t hashv = 5381; + for (int i = 0; i < p_len; i++) { + hashv = ((hashv << 5) + hashv) + p_cstr[i]; /* hash * 33 + c */ + } + + return hashv; +} + +uint32_t String::hash(const CharType *p_cstr, int p_len) { + uint32_t hashv = 5381; + for (int i = 0; i < p_len; i++) { + hashv = ((hashv << 5) + hashv) + p_cstr[i]; /* hash * 33 + c */ + } + + return hashv; +} + +uint32_t String::hash(const CharType *p_cstr) { + uint32_t hashv = 5381; + uint32_t c; + + while ((c = *p_cstr++)) { + hashv = ((hashv << 5) + hashv) + c; /* hash * 33 + c */ + } + + return hashv; +} + +uint32_t String::hash() const { + /* simple djb2 hashing */ + + const CharType *chr = c_str(); + uint32_t hashv = 5381; + uint32_t c; + + while ((c = *chr++)) { + hashv = ((hashv << 5) + hashv) + c; /* hash * 33 + c */ + } + + return hashv; +} + +uint64_t String::hash64() const { + /* simple djb2 hashing */ + + const CharType *chr = c_str(); + uint64_t hashv = 5381; + uint64_t c; + + while ((c = *chr++)) { + hashv = ((hashv << 5) + hashv) + c; /* hash * 33 + c */ + } + + return hashv; +} + +String String::md5_text() const { + CharString cs = utf8(); + unsigned char hash[16]; + CryptoCore::md5((unsigned char *)cs.ptr(), cs.length(), hash); + return String::hex_encode_buffer(hash, 16); +} + +String String::sha1_text() const { + CharString cs = utf8(); + unsigned char hash[20]; + CryptoCore::sha1((unsigned char *)cs.ptr(), cs.length(), hash); + return String::hex_encode_buffer(hash, 20); +} + +String String::sha256_text() const { + CharString cs = utf8(); + unsigned char hash[32]; + CryptoCore::sha256((unsigned char *)cs.ptr(), cs.length(), hash); + return String::hex_encode_buffer(hash, 32); +} + +Vector String::md5_buffer() const { + CharString cs = utf8(); + unsigned char hash[16]; + CryptoCore::md5((unsigned char *)cs.ptr(), cs.length(), hash); + + Vector ret; + ret.resize(16); + for (int i = 0; i < 16; i++) { + ret.write[i] = hash[i]; + } + return ret; +}; + +Vector String::sha1_buffer() const { + CharString cs = utf8(); + unsigned char hash[20]; + CryptoCore::sha1((unsigned char *)cs.ptr(), cs.length(), hash); + + Vector ret; + ret.resize(20); + for (int i = 0; i < 20; i++) { + ret.write[i] = hash[i]; + } + + return ret; +} + +Vector String::sha256_buffer() const { + CharString cs = utf8(); + unsigned char hash[32]; + CryptoCore::sha256((unsigned char *)cs.ptr(), cs.length(), hash); + + Vector ret; + ret.resize(32); + for (int i = 0; i < 32; i++) { + ret.write[i] = hash[i]; + } + return ret; +} + +String String::insert(int p_at_pos, const String &p_string) const { + if (p_at_pos < 0) { + return *this; + } + + if (p_at_pos > length()) { + p_at_pos = length(); + } + + String pre; + if (p_at_pos > 0) { + pre = substr(0, p_at_pos); + } + + String post; + if (p_at_pos < length()) { + post = substr(p_at_pos, length() - p_at_pos); + } + + return pre + p_string + post; +} +String String::substr(int p_from, int p_chars) const { + if (p_chars == -1) { + p_chars = length() - p_from; + } + + if (empty() || p_from < 0 || p_from >= length() || p_chars <= 0) { + return ""; + } + + if ((p_from + p_chars) > length()) { + p_chars = length() - p_from; + } + + if (p_from == 0 && p_chars >= length()) { + return String(*this); + } + + String s = String(); + s.copy_from_unchecked(&c_str()[p_from], p_chars); + return s; +} + +int String::find_last(const String &p_str) const { + return rfind(p_str); +} + +int String::find(const String &p_str, int p_from) const { + if (p_from < 0) { + return -1; + } + + const int src_len = p_str.length(); + + const int len = length(); + + if (src_len == 0 || len == 0) { + return -1; // won't find anything! + } + + const CharType *src = c_str(); + const CharType *str = p_str.c_str(); + + for (int i = p_from; i <= (len - src_len); i++) { + bool found = true; + for (int j = 0; j < src_len; j++) { + int read_pos = i + j; + + if (read_pos >= len) { + ERR_PRINT("read_pos>=len"); + return -1; + }; + + if (src[read_pos] != str[j]) { + found = false; + break; + } + } + + if (found) { + return i; + } + } + + return -1; +} + +int String::find(const char *p_str, int p_from) const { + if (p_from < 0) { + return -1; + } + + const int len = length(); + + if (len == 0) { + return -1; // won't find anything! + } + + const CharType *src = c_str(); + + int src_len = 0; + while (p_str[src_len] != '\0') { + src_len++; + } + + if (src_len == 1) { + const char needle = p_str[0]; + + for (int i = p_from; i < len; i++) { + if (src[i] == needle) { + return i; + } + } + + } else { + for (int i = p_from; i <= (len - src_len); i++) { + bool found = true; + for (int j = 0; j < src_len; j++) { + int read_pos = i + j; + + if (read_pos >= len) { + ERR_PRINT("read_pos>=len"); + return -1; + }; + + if (src[read_pos] != p_str[j]) { + found = false; + break; + } + } + + if (found) { + return i; + } + } + } + + return -1; +} + +int String::find_char(const CharType &p_char, int p_from) const { + return _cowdata.find(p_char, p_from); +} + +int String::findmk(const Vector &p_keys, int p_from, int *r_key) const { + if (p_from < 0) { + return -1; + } + if (p_keys.size() == 0) { + return -1; + } + + //int src_len=p_str.length(); + const String *keys = &p_keys[0]; + int key_count = p_keys.size(); + int len = length(); + + if (len == 0) { + return -1; // won't find anything! + } + + const CharType *src = c_str(); + + for (int i = p_from; i < len; i++) { + bool found = true; + for (int k = 0; k < key_count; k++) { + found = true; + if (r_key) { + *r_key = k; + } + const CharType *cmp = keys[k].c_str(); + int l = keys[k].length(); + + for (int j = 0; j < l; j++) { + int read_pos = i + j; + + if (read_pos >= len) { + found = false; + break; + }; + + if (src[read_pos] != cmp[j]) { + found = false; + break; + } + } + if (found) { + break; + } + } + + if (found) { + return i; + } + } + + return -1; +} + +int String::findn(const String &p_str, int p_from) const { + if (p_from < 0) { + return -1; + } + + int src_len = p_str.length(); + + if (src_len == 0 || length() == 0) { + return -1; // won't find anything! + } + + const CharType *srcd = c_str(); + + for (int i = p_from; i <= (length() - src_len); i++) { + bool found = true; + for (int j = 0; j < src_len; j++) { + int read_pos = i + j; + + if (read_pos >= length()) { + ERR_PRINT("read_pos>=length()"); + return -1; + }; + + CharType src = _find_lower(srcd[read_pos]); + CharType dst = _find_lower(p_str[j]); + + if (src != dst) { + found = false; + break; + } + } + + if (found) { + return i; + } + } + + return -1; +} + +int String::rfind(const String &p_str, int p_from) const { + // establish a limit + int limit = length() - p_str.length(); + if (limit < 0) { + return -1; + } + + // establish a starting point + if (p_from < 0) { + p_from = limit; + } else if (p_from > limit) { + p_from = limit; + } + + int src_len = p_str.length(); + int len = length(); + + if (src_len == 0 || len == 0) { + return -1; // won't find anything! + } + + const CharType *src = c_str(); + + for (int i = p_from; i >= 0; i--) { + bool found = true; + for (int j = 0; j < src_len; j++) { + int read_pos = i + j; + + if (read_pos >= len) { + ERR_PRINT("read_pos>=len"); + return -1; + }; + + if (src[read_pos] != p_str[j]) { + found = false; + break; + } + } + + if (found) { + return i; + } + } + + return -1; +} +int String::rfindn(const String &p_str, int p_from) const { + // establish a limit + int limit = length() - p_str.length(); + if (limit < 0) { + return -1; + } + + // establish a starting point + if (p_from < 0) { + p_from = limit; + } else if (p_from > limit) { + p_from = limit; + } + + int src_len = p_str.length(); + int len = length(); + + if (src_len == 0 || len == 0) { + return -1; // won't find anything! + } + + const CharType *src = c_str(); + + for (int i = p_from; i >= 0; i--) { + bool found = true; + for (int j = 0; j < src_len; j++) { + int read_pos = i + j; + + if (read_pos >= len) { + ERR_PRINT("read_pos>=len"); + return -1; + }; + + CharType srcc = _find_lower(src[read_pos]); + CharType dstc = _find_lower(p_str[j]); + + if (srcc != dstc) { + found = false; + break; + } + } + + if (found) { + return i; + } + } + + return -1; +} + +bool String::ends_with(const String &p_string) const { + int l = p_string.length(); + if (l > length()) { + return false; + } + + if (l == 0) { + return true; + } + + const CharType *p = &p_string[0]; + const CharType *s = &operator[](length() - l); + + for (int i = 0; i < l; i++) { + if (p[i] != s[i]) { + return false; + } + } + + return true; +} + +bool String::begins_with(const String &p_string) const { + int l = p_string.length(); + if (l > length()) { + return false; + } + + if (l == 0) { + return true; + } + + const CharType *p = &p_string[0]; + const CharType *s = &operator[](0); + + for (int i = 0; i < l; i++) { + if (p[i] != s[i]) { + return false; + } + } + + return true; +} + +bool String::begins_with(const char *p_string) const { + int l = length(); + if (l == 0 || !p_string) { + return false; + } + + const CharType *str = &operator[](0); + int i = 0; + + while (*p_string && i < l) { + if (*p_string != str[i]) { + return false; + } + i++; + p_string++; + } + + return *p_string == 0; +} + +bool String::is_enclosed_in(const String &p_string) const { + return begins_with(p_string) && ends_with(p_string); +} + +bool String::is_subsequence_of(const String &p_string) const { + return _base_is_subsequence_of(p_string, false); +} + +bool String::is_subsequence_ofi(const String &p_string) const { + return _base_is_subsequence_of(p_string, true); +} + +bool String::is_quoted() const { + return is_enclosed_in("\"") || is_enclosed_in("'"); +} + +int String::_count(const String &p_string, int p_from, int p_to, bool p_case_insensitive) const { + if (p_string.empty()) { + return 0; + } + int len = length(); + int slen = p_string.length(); + if (len < slen) { + return 0; + } + String str; + if (p_from >= 0 && p_to >= 0) { + if (p_to == 0) { + p_to = len; + } else if (p_from >= p_to) { + return 0; + } + if (p_from == 0 && p_to == len) { + str = String(); + str.copy_from_unchecked(&c_str()[0], len); + } else { + str = substr(p_from, p_to - p_from); + } + } else { + return 0; + } + int c = 0; + int idx = -1; + do { + idx = p_case_insensitive ? str.findn(p_string) : str.find(p_string); + if (idx != -1) { + str = str.substr(idx + slen, str.length() - slen); + ++c; + } + } while (idx != -1); + return c; +} + +int String::count(const String &p_string, int p_from, int p_to) const { + return _count(p_string, p_from, p_to, false); +} + +int String::countn(const String &p_string, int p_from, int p_to) const { + return _count(p_string, p_from, p_to, true); +} + +bool String::_base_is_subsequence_of(const String &p_string, bool case_insensitive) const { + int len = length(); + if (len == 0) { + // Technically an empty string is subsequence of any string + return true; + } + + if (len > p_string.length()) { + return false; + } + + const CharType *src = &operator[](0); + const CharType *tgt = &p_string[0]; + + for (; *src && *tgt; tgt++) { + bool match = false; + if (case_insensitive) { + CharType srcc = _find_lower(*src); + CharType tgtc = _find_lower(*tgt); + match = srcc == tgtc; + } else { + match = *src == *tgt; + } + if (match) { + src++; + if (!*src) { + return true; + } + } + } + + return false; +} + +Vector String::bigrams() const { + int n_pairs = length() - 1; + Vector b; + if (n_pairs <= 0) { + return b; + } + b.resize(n_pairs); + for (int i = 0; i < n_pairs; i++) { + b.write[i] = substr(i, 2); + } + return b; +} + +// Similarity according to Sorensen-Dice coefficient +float String::similarity(const String &p_string) const { + if (operator==(p_string)) { + // Equal strings are totally similar + return 1.0f; + } + if (length() < 2 || p_string.length() < 2) { + // No way to calculate similarity without a single bigram + return 0.0f; + } + + Vector src_bigrams = bigrams(); + Vector tgt_bigrams = p_string.bigrams(); + + int src_size = src_bigrams.size(); + int tgt_size = tgt_bigrams.size(); + + float sum = src_size + tgt_size; + float inter = 0; + for (int i = 0; i < src_size; i++) { + for (int j = 0; j < tgt_size; j++) { + if (src_bigrams[i] == tgt_bigrams[j]) { + inter++; + break; + } + } + } + + return (2.0f * inter) / sum; +} + +static bool _wildcard_match(const CharType *p_pattern, const CharType *p_string, bool p_case_sensitive) { + switch (*p_pattern) { + case '\0': + return !*p_string; + case '*': + return _wildcard_match(p_pattern + 1, p_string, p_case_sensitive) || (*p_string && _wildcard_match(p_pattern, p_string + 1, p_case_sensitive)); + case '?': + return *p_string && (*p_string != '.') && _wildcard_match(p_pattern + 1, p_string + 1, p_case_sensitive); + default: + + return (p_case_sensitive ? (*p_string == *p_pattern) : (_find_upper(*p_string) == _find_upper(*p_pattern))) && _wildcard_match(p_pattern + 1, p_string + 1, p_case_sensitive); + } +} + +bool String::match(const String &p_wildcard) const { + if (!p_wildcard.length() || !length()) { + return false; + } + + return _wildcard_match(p_wildcard.c_str(), c_str(), true); +} + +bool String::matchn(const String &p_wildcard) const { + if (!p_wildcard.length() || !length()) { + return false; + } + return _wildcard_match(p_wildcard.c_str(), c_str(), false); +} + +String String::format(const Variant &values, String placeholder) const { + String new_string = String(this->ptr()); + + if (values.get_type() == Variant::ARRAY) { + Array values_arr = values; + + for (int i = 0; i < values_arr.size(); i++) { + String i_as_str = String::num_int64(i); + + if (values_arr[i].get_type() == Variant::ARRAY) { //Array in Array structure [["name","RobotGuy"],[0,"godot"],["strength",9000.91]] + Array value_arr = values_arr[i]; + + if (value_arr.size() == 2) { + Variant v_key = value_arr[0]; + String key = v_key; + + Variant v_val = value_arr[1]; + String val = v_val; + + new_string = new_string.replace(placeholder.replace("_", key), val); + } else { + ERR_PRINT(String("STRING.format Inner Array size != 2 ").ascii().get_data()); + } + } else { //Array structure ["RobotGuy","Logis","rookie"] + Variant v_val = values_arr[i]; + String val = v_val; + + if (placeholder.find("_") > -1) { + new_string = new_string.replace(placeholder.replace("_", i_as_str), val); + } else { + new_string = new_string.replace_first(placeholder, val); + } + } + } + } else if (values.get_type() == Variant::DICTIONARY) { + Dictionary d = values; + List keys; + d.get_key_list(&keys); + + for (List::Element *E = keys.front(); E; E = E->next()) { + String key = E->get(); + String val = d[E->get()]; + + new_string = new_string.replace(placeholder.replace("_", key), val); + } + } else { + ERR_PRINT(String("Invalid type: use Array or Dictionary.").ascii().get_data()); + } + + return new_string; +} + +String String::replace(const String &p_key, const String &p_with) const { + String new_string; + int search_from = 0; + int result = 0; + + while ((result = find(p_key, search_from)) >= 0) { + new_string += substr(search_from, result - search_from); + new_string += p_with; + search_from = result + p_key.length(); + } + + if (search_from == 0) { + return *this; + } + + new_string += substr(search_from, length() - search_from); + + return new_string; +} + +String String::replace(const char *p_key, const char *p_with) const { + String new_string; + int search_from = 0; + int result = 0; + + while ((result = find(p_key, search_from)) >= 0) { + new_string += substr(search_from, result - search_from); + new_string += p_with; + int k = 0; + while (p_key[k] != '\0') { + k++; + } + search_from = result + k; + } + + if (search_from == 0) { + return *this; + } + + new_string += substr(search_from, length() - search_from); + + return new_string; +} + +String String::replace_first(const String &p_key, const String &p_with) const { + int pos = find(p_key); + if (pos >= 0) { + return substr(0, pos) + p_with + substr(pos + p_key.length(), length()); + } + + return *this; +} +String String::replacen(const String &p_key, const String &p_with) const { + String new_string; + int search_from = 0; + int result = 0; + + while ((result = findn(p_key, search_from)) >= 0) { + new_string += substr(search_from, result - search_from); + new_string += p_with; + search_from = result + p_key.length(); + } + + if (search_from == 0) { + return *this; + } + + new_string += substr(search_from, length() - search_from); + return new_string; +} + +String String::repeat(int p_count) const { + ERR_FAIL_COND_V_MSG(p_count < 0, "", "Parameter count should be a positive number."); + + int len = length(); + String new_string = *this; + new_string.resize(p_count * len + 1); + + CharType *dst = new_string.ptrw(); + int offset = 1; + int stride = 1; + while (offset < p_count) { + memcpy(dst + offset * len, dst, stride * len * sizeof(CharType)); + offset += stride; + stride = MIN(stride * 2, p_count - offset); + } + dst[p_count * len] = _null; + + return new_string; +} + +String String::left(int p_pos) const { + if (p_pos <= 0) { + return ""; + } + + if (p_pos >= length()) { + return *this; + } + + return substr(0, p_pos); +} + +String String::right(int p_pos) const { + if (p_pos >= length()) { + return ""; + } + + if (p_pos <= 0) { + return *this; + } + + return substr(p_pos, (length() - p_pos)); +} + +CharType String::ord_at(int p_idx) const { + ERR_FAIL_INDEX_V(p_idx, length(), 0); + return operator[](p_idx); +} + +String String::indent(const String &p_prefix) const { + String new_string; + int line_start = 0; + + for (int i = 0; i < length(); i++) { + const char32_t c = operator[](i); + if (c == '\n') { + if (i == line_start) { + new_string += c; // Leave empty lines empty. + } else { + new_string += p_prefix + substr(line_start, i - line_start + 1); + } + line_start = i + 1; + } + } + if (line_start != length()) { + new_string += p_prefix + substr(line_start); + } + return new_string; +} + +String String::dedent() const { + String new_string; + String indent; + bool has_indent = false; + bool has_text = false; + int line_start = 0; + int indent_stop = -1; + + for (int i = 0; i < length(); i++) { + CharType c = operator[](i); + if (c == '\n') { + if (has_text) { + new_string += substr(indent_stop, i - indent_stop); + } + new_string += "\n"; + has_text = false; + line_start = i + 1; + indent_stop = -1; + } else if (!has_text) { + if (c > 32) { + has_text = true; + if (!has_indent) { + has_indent = true; + indent = substr(line_start, i - line_start); + indent_stop = i; + } + } + if (has_indent && indent_stop < 0) { + int j = i - line_start; + if (j >= indent.length() || c != indent[j]) { + indent_stop = i; + } + } + } + } + + if (has_text) { + new_string += substr(indent_stop, length() - indent_stop); + } + + return new_string; +} + +String String::strip_edges(bool left, bool right) const { + int len = length(); + int beg = 0, end = len; + + if (left) { + for (int i = 0; i < len; i++) { + if (operator[](i) <= 32) { + beg++; + } else { + break; + } + } + } + + if (right) { + for (int i = (int)(len - 1); i >= 0; i--) { + if (operator[](i) <= 32) { + end--; + } else { + break; + } + } + } + + if (beg == 0 && end == len) { + return *this; + } + + return substr(beg, end - beg); +} + +String String::strip_escapes() const { + String new_string; + for (int i = 0; i < length(); i++) { + // Escape characters on first page of the ASCII table, before 32 (Space). + if (operator[](i) < 32) { + continue; + } + new_string += operator[](i); + } + + return new_string; +} + +String String::lstrip(const String &p_chars) const { + int len = length(); + int beg; + + for (beg = 0; beg < len; beg++) { + if (p_chars.find_char(get(beg)) == -1) { + break; + } + } + + if (beg == 0) { + return *this; + } + + return substr(beg, len - beg); +} + +String String::rstrip(const String &p_chars) const { + int len = length(); + int end; + + for (end = len - 1; end >= 0; end--) { + if (p_chars.find_char(get(end)) == -1) { + break; + } + } + + if (end == len - 1) { + return *this; + } + + return substr(0, end + 1); +} + +bool String::is_network_share_path() const { + return begins_with("//") || begins_with("\\\\"); +} + +String String::simplify_path() const { + String s = *this; + String drive; + if (s.begins_with("local://")) { + drive = "local://"; + s = s.substr(8, s.length()); + } else if (s.begins_with("res://")) { + drive = "res://"; + s = s.substr(6, s.length()); + } else if (s.begins_with("user://")) { + drive = "user://"; + s = s.substr(7, s.length()); + } else if (is_network_share_path()) { + drive = s.substr(0, 2); + s = s.substr(2, s.length() - 2); + } else if (s.begins_with("/") || s.begins_with("\\")) { + drive = s.substr(0, 1); + s = s.substr(1, s.length() - 1); + } else { + int p = s.find(":/"); + if (p == -1) { + p = s.find(":\\"); + } + if (p != -1 && p < s.find("/")) { + drive = s.substr(0, p + 2); + s = s.substr(p + 2, s.length()); + } + } + + s = s.replace("\\", "/"); + while (true) { // in case of using 2 or more slash + String compare = s.replace("//", "/"); + if (s == compare) { + break; + } else { + s = compare; + } + } + Vector dirs = s.split("/", false); + + for (int i = 0; i < dirs.size(); i++) { + String d = dirs[i]; + if (d == ".") { + dirs.remove(i); + i--; + } else if (d == "..") { + if (i == 0) { + dirs.remove(i); + i--; + } else { + dirs.remove(i); + dirs.remove(i - 1); + i -= 2; + } + } + } + + s = ""; + + for (int i = 0; i < dirs.size(); i++) { + if (i > 0) { + s += "/"; + } + s += dirs[i]; + } + + return drive + s; +} + +static int _humanize_digits(int p_num) { + if (p_num < 100) { + return 2; + } else if (p_num < 1024) { + return 1; + } else { + return 0; + } +} + +String String::humanize_size(uint64_t p_size) { + uint64_t _div = 1; + Vector prefixes; + prefixes.push_back(RTR("B")); + prefixes.push_back(RTR("KiB")); + prefixes.push_back(RTR("MiB")); + prefixes.push_back(RTR("GiB")); + prefixes.push_back(RTR("TiB")); + prefixes.push_back(RTR("PiB")); + prefixes.push_back(RTR("EiB")); + + int prefix_idx = 0; + + while (prefix_idx < prefixes.size() - 1 && p_size > (_div * 1024)) { + _div *= 1024; + prefix_idx++; + } + + const int digits = prefix_idx > 0 ? _humanize_digits(p_size / _div) : 0; + const double divisor = prefix_idx > 0 ? _div : 1; + + return String::num(p_size / divisor).pad_decimals(digits) + " " + prefixes[prefix_idx]; +} +bool String::is_abs_path() const { + if (length() > 1) { + return (operator[](0) == '/' || operator[](0) == '\\' || find(":/") != -1 || find(":\\") != -1); + } else if ((length()) == 1) { + return (operator[](0) == '/' || operator[](0) == '\\'); + } else { + return false; + } +} + +static _FORCE_INLINE_ bool _is_valid_identifier_bit(int p_index, CharType p_char) { + if (p_index == 0 && p_char >= '0' && p_char <= '9') { + return false; // No start with number plz. + } + return (p_char >= '0' && p_char <= '9') || (p_char >= 'a' && p_char <= 'z') || (p_char >= 'A' && p_char <= 'Z') || p_char == '_'; +} + +String String::validate_identifier() const { + if (empty()) { + return "_"; // Empty string is not a valid identifier. + } + + String result = *this; + int len = result.length(); + wchar_t *buffer = result.ptrw(); + + for (int i = 0; i < len; i++) { + if (!_is_valid_identifier_bit(i, buffer[i])) { + buffer[i] = '_'; + } + } + + return result; +} + +bool String::is_valid_identifier() const { + int len = length(); + + if (len == 0) { + return false; + } + + const wchar_t *str = &operator[](0); + + for (int i = 0; i < len; i++) { + if (!_is_valid_identifier_bit(i, str[i])) { + return false; + } + } + + return true; +} + +String String::word_wrap(int p_chars_per_line) const { + String ret; + + int line_start = 0; + int line_end = 0; // End of last word on current line. + int word_start = 0; // -1 if no word encountered. Leading spaces are part of a word. + int word_length = 0; + + for (int i = 0; i < length(); i++) { + const CharType c = operator[](i); + + switch (c) { + case '\n': { + // Force newline. + ret += substr(line_start, i - line_start + 1); + line_start = i + 1; + line_end = line_start; + word_start = line_start; + word_length = 0; + } break; + + case ' ': + case '\t': { + // A whitespace ends current word. + if (word_length > 0) { + line_end = i - 1; + word_start = -1; + word_length = 0; + } + } break; + + default: { + if (word_start == -1) { + word_start = i; + } + word_length += 1; + + if (word_length > p_chars_per_line) { + // Word too long: wrap before current character. + ret += substr(line_start, i - line_start) + "\n"; + line_start = i; + line_end = i; + word_start = i; + word_length = 1; + } else if (i - line_start + 1 > p_chars_per_line) { + // Line too long: wrap after the last word. + ret += substr(line_start, line_end - line_start + 1) + "\n"; + line_start = word_start; + line_end = line_start; + } + } break; + } + } + + const int remaining = length() - line_start; + if (remaining) { + ret += substr(line_start, remaining); + } + + return ret; +} + +String String::http_escape() const { + const CharString temp = utf8(); + String res; + for (int i = 0; i < temp.length(); ++i) { + uint8_t ord = temp[i]; + if (ord == '.' || ord == '-' || ord == '_' || ord == '~' || + (ord >= 'a' && ord <= 'z') || + (ord >= 'A' && ord <= 'Z') || + (ord >= '0' && ord <= '9')) { + res += ord; + } else { + char p[4] = { '%', 0, 0, 0 }; + static const char hex[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + + p[1] = hex[ord >> 4]; + p[2] = hex[ord & 0xF]; + res += p; + } + } + return res; +} + +String String::http_unescape() const { + String res; + for (int i = 0; i < length(); ++i) { + if (ord_at(i) == '%' && i + 2 < length()) { + CharType ord1 = ord_at(i + 1); + if ((ord1 >= '0' && ord1 <= '9') || (ord1 >= 'A' && ord1 <= 'Z')) { + CharType ord2 = ord_at(i + 2); + if ((ord2 >= '0' && ord2 <= '9') || (ord2 >= 'A' && ord2 <= 'Z')) { + char bytes[3] = { (char)ord1, (char)ord2, 0 }; + res += (char)strtol(bytes, nullptr, 16); + i += 2; + } + } else { + res += ord_at(i); + } + } else { + res += ord_at(i); + } + } + return String::utf8(res.ascii()); +} + +String String::c_unescape() const { + String escaped = *this; + escaped = escaped.replace("\\a", "\a"); + escaped = escaped.replace("\\b", "\b"); + escaped = escaped.replace("\\f", "\f"); + escaped = escaped.replace("\\n", "\n"); + escaped = escaped.replace("\\r", "\r"); + escaped = escaped.replace("\\t", "\t"); + escaped = escaped.replace("\\v", "\v"); + escaped = escaped.replace("\\'", "\'"); + escaped = escaped.replace("\\\"", "\""); + escaped = escaped.replace("\\?", "\?"); + escaped = escaped.replace("\\\\", "\\"); + + return escaped; +} + +String String::c_escape() const { + String escaped = *this; + escaped = escaped.replace("\\", "\\\\"); + escaped = escaped.replace("\a", "\\a"); + escaped = escaped.replace("\b", "\\b"); + escaped = escaped.replace("\f", "\\f"); + escaped = escaped.replace("\n", "\\n"); + escaped = escaped.replace("\r", "\\r"); + escaped = escaped.replace("\t", "\\t"); + escaped = escaped.replace("\v", "\\v"); + escaped = escaped.replace("\'", "\\'"); + escaped = escaped.replace("\?", "\\?"); + escaped = escaped.replace("\"", "\\\""); + + return escaped; +} + +String String::c_escape_multiline() const { + String escaped = *this; + escaped = escaped.replace("\\", "\\\\"); + escaped = escaped.replace("\"", "\\\""); + + return escaped; +} + +String String::json_escape() const { + String escaped = *this; + escaped = escaped.replace("\\", "\\\\"); + escaped = escaped.replace("\b", "\\b"); + escaped = escaped.replace("\f", "\\f"); + escaped = escaped.replace("\n", "\\n"); + escaped = escaped.replace("\r", "\\r"); + escaped = escaped.replace("\t", "\\t"); + escaped = escaped.replace("\v", "\\v"); + escaped = escaped.replace("\"", "\\\""); + + return escaped; +} + +String String::xml_escape(bool p_escape_quotes) const { + String str = *this; + str = str.replace("&", "&"); + str = str.replace("<", "<"); + str = str.replace(">", ">"); + if (p_escape_quotes) { + str = str.replace("'", "'"); + str = str.replace("\"", """); + } + /* + for (int i=1;i<32;i++) { + + char chr[2]={i,0}; + str=str.replace(chr,"&#"+String::num(i)+";"); + }*/ + return str; +} + +static _FORCE_INLINE_ int _xml_unescape(const CharType *p_src, int p_src_len, CharType *p_dst) { + int len = 0; + while (p_src_len) { + if (*p_src == '&') { + int eat = 0; + + if (p_src_len >= 4 && p_src[1] == '#') { + CharType c = 0; + bool overflow = false; + if (p_src[2] == 'x') { + // Hex entity &#x; + for (int i = 3; i < p_src_len; i++) { + eat = i + 1; + CharType ct = p_src[i]; + if (ct == ';') { + break; + } else if (ct >= '0' && ct <= '9') { + ct = ct - '0'; + } else if (ct >= 'a' && ct <= 'f') { + ct = (ct - 'a') + 10; + } else if (ct >= 'A' && ct <= 'F') { + ct = (ct - 'A') + 10; + } else { + break; + } + if (c > (WCHAR_MAX >> 4)) { + overflow = true; + break; + } + c <<= 4; + c |= ct; + } + } else { + // Decimal entity &#; + for (int i = 2; i < p_src_len; i++) { + eat = i + 1; + CharType ct = p_src[i]; + if (ct == ';' || ct < '0' || ct > '9') { + break; + } + } + if (p_src[eat - 1] == ';') { + int64_t val = String::to_int(p_src + 2, eat - 3); + if (val > 0 && val <= WCHAR_MAX) { + c = (CharType)val; + } else { + overflow = true; + } + } + } + + // Value must be non-zero, in the range of char32_t, + // actually end with ';'. If invalid, leave the entity as-is + if (c == '\0' || overflow || p_src[eat - 1] != ';') { + eat = 1; + c = *p_src; + } + if (p_dst) { + *p_dst = c; + } + } else if (p_src_len >= 4 && p_src[1] == 'g' && p_src[2] == 't' && p_src[3] == ';') { + if (p_dst) { + *p_dst = '>'; + } + eat = 4; + } else if (p_src_len >= 4 && p_src[1] == 'l' && p_src[2] == 't' && p_src[3] == ';') { + if (p_dst) { + *p_dst = '<'; + } + eat = 4; + } else if (p_src_len >= 5 && p_src[1] == 'a' && p_src[2] == 'm' && p_src[3] == 'p' && p_src[4] == ';') { + if (p_dst) { + *p_dst = '&'; + } + eat = 5; + } else if (p_src_len >= 6 && p_src[1] == 'q' && p_src[2] == 'u' && p_src[3] == 'o' && p_src[4] == 't' && p_src[5] == ';') { + if (p_dst) { + *p_dst = '"'; + } + eat = 6; + } else if (p_src_len >= 6 && p_src[1] == 'a' && p_src[2] == 'p' && p_src[3] == 'o' && p_src[4] == 's' && p_src[5] == ';') { + if (p_dst) { + *p_dst = '\''; + } + eat = 6; + } else { + if (p_dst) { + *p_dst = *p_src; + } + eat = 1; + } + + if (p_dst) { + p_dst++; + } + + len++; + p_src += eat; + p_src_len -= eat; + } else { + if (p_dst) { + *p_dst = *p_src; + p_dst++; + } + len++; + p_src++; + p_src_len--; + } + } + + return len; +} + +String String::xml_unescape() const { + String str; + int l = length(); + int len = _xml_unescape(c_str(), l, nullptr); + if (len == 0) { + return String(); + } + str.resize(len + 1); + _xml_unescape(c_str(), l, str.ptrw()); + str[len] = 0; + return str; +} + +String String::pad_decimals(int p_digits) const { + String s = *this; + int c = s.find("."); + + if (c == -1) { + if (p_digits <= 0) { + return s; + } + s += "."; + c = s.length() - 1; + } else { + if (p_digits <= 0) { + return s.substr(0, c); + } + } + + if (s.length() - (c + 1) > p_digits) { + s = s.substr(0, c + p_digits + 1); + } else { + while (s.length() - (c + 1) < p_digits) { + s += "0"; + } + } + return s; +} + +String String::pad_zeros(int p_digits) const { + String s = *this; + int end = s.find("."); + + if (end == -1) { + end = s.length(); + } + + if (end == 0) { + return s; + } + + int begin = 0; + + while (begin < end && (s[begin] < '0' || s[begin] > '9')) { + begin++; + } + + if (begin >= end) { + return s; + } + + while (end - begin < p_digits) { + s = s.insert(begin, "0"); + end++; + } + + return s; +} + +String String::trim_prefix(const String &p_prefix) const { + String s = *this; + if (s.begins_with(p_prefix)) { + return s.substr(p_prefix.length(), s.length() - p_prefix.length()); + } + return s; +} + +String String::trim_suffix(const String &p_suffix) const { + String s = *this; + if (s.ends_with(p_suffix)) { + return s.substr(0, s.length() - p_suffix.length()); + } + return s; +} + +bool String::is_valid_integer() const { + int len = length(); + + if (len == 0) { + return false; + } + + int from = 0; + if (len != 1 && (operator[](0) == '+' || operator[](0) == '-')) { + from++; + } + + for (int i = from; i < len; i++) { + if (operator[](i) < '0' || operator[](i) > '9') { + return false; // no start with number plz + } + } + + return true; +} + +bool String::is_valid_hex_number(bool p_with_prefix) const { + int len = length(); + + if (len == 0) { + return false; + } + + int from = 0; + if (len != 1 && (operator[](0) == '+' || operator[](0) == '-')) { + from++; + } + + if (p_with_prefix) { + if (len < 3) { + return false; + } + if (operator[](from) != '0' || operator[](from + 1) != 'x') { + return false; + } + from += 2; + } + + for (int i = from; i < len; i++) { + CharType c = operator[](i); + if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')) { + continue; + } + return false; + } + + return true; +}; + +bool String::is_valid_float() const { + int len = length(); + + if (len == 0) { + return false; + } + + int from = 0; + if (operator[](0) == '+' || operator[](0) == '-') { + from++; + } + + bool exponent_found = false; + bool period_found = false; + bool sign_found = false; + bool exponent_values_found = false; + bool numbers_found = false; + + for (int i = from; i < len; i++) { + if (operator[](i) >= '0' && operator[](i) <= '9') { + if (exponent_found) { + exponent_values_found = true; + } else { + numbers_found = true; + } + } else if (numbers_found && !exponent_found && operator[](i) == 'e') { + exponent_found = true; + } else if (!period_found && !exponent_found && operator[](i) == '.') { + period_found = true; + } else if ((operator[](i) == '-' || operator[](i) == '+') && exponent_found && !exponent_values_found && !sign_found) { + sign_found = true; + } else { + return false; // no start with number plz + } + } + + return numbers_found; +} + +String String::path_to_file(const String &p_path) const { + // Don't get base dir for src, this is expected to be a dir already. + String src = this->replace("\\", "/"); + String dst = p_path.replace("\\", "/").get_base_dir(); + String rel = src.path_to(dst); + if (rel == dst) { // failed + return p_path; + } else { + return rel + p_path.get_file(); + } +} + +String String::path_to(const String &p_path) const { + String src = this->replace("\\", "/"); + String dst = p_path.replace("\\", "/"); + if (!src.ends_with("/")) { + src += "/"; + } + if (!dst.ends_with("/")) { + dst += "/"; + } + + String base; + + if (src.begins_with("res://") && dst.begins_with("res://")) { + base = "res:/"; + src = src.replace("res://", "/"); + dst = dst.replace("res://", "/"); + + } else if (src.begins_with("user://") && dst.begins_with("user://")) { + base = "user:/"; + src = src.replace("user://", "/"); + dst = dst.replace("user://", "/"); + + } else if (src.begins_with("/") && dst.begins_with("/")) { + //nothing + } else { + //dos style + String src_begin = src.get_slicec('/', 0); + String dst_begin = dst.get_slicec('/', 0); + + if (src_begin != dst_begin) { + return p_path; //impossible to do this + } + + base = src_begin; + src = src.substr(src_begin.length(), src.length()); + dst = dst.substr(dst_begin.length(), dst.length()); + } + + //remove leading and trailing slash and split + Vector src_dirs = src.substr(1, src.length() - 2).split("/"); + Vector dst_dirs = dst.substr(1, dst.length() - 2).split("/"); + + //find common parent + int common_parent = 0; + + while (true) { + if (src_dirs.size() == common_parent) { + break; + } + if (dst_dirs.size() == common_parent) { + break; + } + if (src_dirs[common_parent] != dst_dirs[common_parent]) { + break; + } + common_parent++; + } + + common_parent--; + + String dir; + + for (int i = src_dirs.size() - 1; i > common_parent; i--) { + dir += "../"; + } + + for (int i = common_parent + 1; i < dst_dirs.size(); i++) { + dir += dst_dirs[i] + "/"; + } + + if (dir.length() == 0) { + dir = "./"; + } + return dir; +} + +bool String::is_valid_html_color() const { + return Color::html_is_valid(*this); +} + +bool String::is_valid_filename() const { + String stripped = strip_edges(); + if (*this != stripped) { + return false; + } + + if (stripped == String()) { + return false; + } + + return !(find(":") != -1 || find("/") != -1 || find("\\") != -1 || find("?") != -1 || find("*") != -1 || find("\"") != -1 || find("|") != -1 || find("%") != -1 || find("<") != -1 || find(">") != -1); +} + +bool String::is_valid_ip_address() const { + if (find(":") >= 0) { + Vector ip = split(":"); + for (int i = 0; i < ip.size(); i++) { + String n = ip[i]; + if (n.empty()) { + continue; + } + if (n.is_valid_hex_number(false)) { + int nint = n.hex_to_int(false); + if (nint < 0 || nint > 0xffff) { + return false; + } + continue; + }; + if (!n.is_valid_ip_address()) { + return false; + } + }; + + } else { + Vector ip = split("."); + if (ip.size() != 4) { + return false; + } + for (int i = 0; i < ip.size(); i++) { + String n = ip[i]; + if (!n.is_valid_integer()) { + return false; + } + int val = n.to_int(); + if (val < 0 || val > 255) { + return false; + } + } + }; + + return true; +} + +bool String::is_resource_file() const { + return begins_with("res://") && find("::") == -1; +} + +bool String::is_rel_path() const { + return !is_abs_path(); +} + +String String::get_base_dir() const { + int end = 0; + + // URL scheme style base. + int basepos = find("://"); + if (basepos != -1) { + end = basepos + 3; + } + + // Windows top level directory base. + if (end == 0) { + basepos = find(":/"); + if (basepos == -1) { + basepos = find(":\\"); + } + if (basepos != -1) { + end = basepos + 2; + } + } + + // Windows UNC network share path. + if (end == 0) { + if (is_network_share_path()) { + basepos = find("/", 2); + if (basepos == -1) { + basepos = find("\\", 2); + } + int servpos = find("/", basepos + 1); + if (servpos == -1) { + servpos = find("\\", basepos + 1); + } + if (servpos != -1) { + end = servpos + 1; + } + } + } + + // Unix root directory base. + if (end == 0) { + if (begins_with("/")) { + end = 1; + } + } + + String rs; + String base; + if (end != 0) { + rs = substr(end, length()); + base = substr(0, end); + } else { + rs = *this; + } + + int sep = MAX(rs.rfind("/"), rs.rfind("\\")); + if (sep == -1) { + return base; + } + + return base + rs.substr(0, sep); +} + +String String::get_file() const { + int sep = MAX(rfind("/"), rfind("\\")); + if (sep == -1) { + return *this; + } + + return substr(sep + 1, length()); +} + +String String::get_extension() const { + int pos = rfind("."); + if (pos < 0 || pos < MAX(rfind("/"), rfind("\\"))) { + return ""; + } + + return substr(pos + 1, length()); +} + +String String::plus_file(const String &p_file) const { + if (empty()) { + return p_file; + } + if (operator[](length() - 1) == '/' || (p_file.size() > 0 && p_file.operator[](0) == '/')) { + return *this + p_file; + } + return *this + "/" + p_file; +} + +String String::percent_encode() const { + CharString cs = utf8(); + String encoded; + for (int i = 0; i < cs.length(); i++) { + uint8_t c = cs[i]; + if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '~' || c == '.') { + char p[2] = { (char)c, 0 }; + encoded += p; + } else { + char p[4] = { '%', 0, 0, 0 }; + static const char hex[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + + p[1] = hex[c >> 4]; + p[2] = hex[c & 0xF]; + encoded += p; + } + } + + return encoded; +} +String String::percent_decode() const { + CharString pe; + + CharString cs = utf8(); + for (int i = 0; i < cs.length(); i++) { + uint8_t c = cs[i]; + if (c == '%' && i < length() - 2) { + uint8_t a = LOWERCASE(cs[i + 1]); + uint8_t b = LOWERCASE(cs[i + 2]); + + if (a >= '0' && a <= '9') { + c = (a - '0') << 4; + } else if (a >= 'a' && a <= 'f') { + c = (a - 'a' + 10) << 4; + } else { + continue; + } + + uint8_t d = 0; + + if (b >= '0' && b <= '9') { + d = (b - '0'); + } else if (b >= 'a' && b <= 'f') { + d = (b - 'a' + 10); + } else { + continue; + } + c += d; + i += 2; + } + pe += c; + } + + return String::utf8(pe.ptr()); +} + +String String::property_name_encode() const { + // Escape and quote strings with extended ASCII or further Unicode characters + // as well as '"', '=' or ' ' (32) + const CharType *cstr = c_str(); + for (int i = 0; cstr[i]; i++) { + if (cstr[i] == '=' || cstr[i] == '"' || cstr[i] == ';' || cstr[i] == '[' || cstr[i] == ']' || cstr[i] < 33 || cstr[i] > 126) { + return "\"" + c_escape_multiline() + "\""; + } + } + // Keep as is + return *this; +} + +// Changes made to the set of invalid characters must also be reflected in the String documentation. +const String String::invalid_node_name_characters = ". : @ / \" " UNIQUE_NODE_PREFIX; +; + +String String::validate_node_name() const { + Vector chars = String::invalid_node_name_characters.split(" "); + String name = this->replace(chars[0], ""); + for (int i = 1; i < chars.size(); i++) { + name = name.replace(chars[i], ""); + } + return name; +} + +String String::get_basename() const { + int pos = rfind("."); + if (pos < 0 || pos < MAX(rfind("/"), rfind("\\"))) { + return *this; + } + + return substr(0, pos); +} + +String itos(int64_t p_val) { + return String::num_int64(p_val); +} + +String uitos(uint64_t p_val) { + return String::num_uint64(p_val); +} + +String rtos(double p_val) { + return String::num(p_val); +} + +String rtoss(double p_val) { + return String::num_scientific(p_val); +} + +// Right-pad with a character. +String String::rpad(int min_length, const String &character) const { + String s = *this; + int padding = min_length - s.length(); + if (padding > 0) { + for (int i = 0; i < padding; i++) { + s = s + character; + } + } + + return s; +} +// Left-pad with a character. +String String::lpad(int min_length, const String &character) const { + String s = *this; + int padding = min_length - s.length(); + if (padding > 0) { + for (int i = 0; i < padding; i++) { + s = character + s; + } + } + + return s; +} + +// sprintf is implemented in GDScript via: +// "fish %s pie" % "frog" +// "fish %s %d pie" % ["frog", 12] +// In case of an error, the string returned is the error description and "error" is true. +String String::sprintf(const Array &values, bool *error) const { + String formatted; + CharType *self = (CharType *)c_str(); + bool in_format = false; + int value_index = 0; + int min_chars = 0; + int min_decimals = 0; + bool in_decimals = false; + bool pad_with_zeros = false; + bool left_justified = false; + bool show_sign = false; + + *error = true; + + for (; *self; self++) { + const CharType c = *self; + + if (in_format) { // We have % - lets see what else we get. + switch (c) { + case '%': { // Replace %% with % + formatted += chr(c); + in_format = false; + break; + } + case 'd': // Integer (signed) + case 'o': // Octal + case 'x': // Hexadecimal (lowercase) + case 'X': { // Hexadecimal (uppercase) + if (value_index >= values.size()) { + return "not enough arguments for format string"; + } + + if (!values[value_index].is_num()) { + return "a number is required"; + } + + int64_t value = values[value_index]; + int base = 16; + bool capitalize = false; + switch (c) { + case 'd': + base = 10; + break; + case 'o': + base = 8; + break; + case 'x': + break; + case 'X': + base = 16; + capitalize = true; + break; + } + // Get basic number. + String str = String::num_int64(ABS(value), base, capitalize); + int number_len = str.length(); + + // Padding. + int pad_chars_count = (value < 0 || show_sign) ? min_chars - 1 : min_chars; + String pad_char = pad_with_zeros ? String("0") : String(" "); + if (left_justified) { + str = str.rpad(pad_chars_count, pad_char); + } else { + str = str.lpad(pad_chars_count, pad_char); + } + + // Sign. + if (show_sign || value < 0) { + String sign_char = value < 0 ? "-" : "+"; + if (left_justified) { + str = str.insert(0, sign_char); + } else { + str = str.insert(pad_with_zeros ? 0 : str.length() - number_len, sign_char); + } + } + + formatted += str; + ++value_index; + in_format = false; + + break; + } + case 'f': { // Float + if (value_index >= values.size()) { + return "not enough arguments for format string"; + } + + if (!values[value_index].is_num()) { + return "a number is required"; + } + + double value = values[value_index]; + bool is_negative = (value < 0); + String str = String::num(ABS(value), min_decimals); + bool not_numeric = isinf(value) || isnan(value); + + // Pad decimals out. + if (!not_numeric) { + str = str.pad_decimals(min_decimals); + } + + int initial_len = str.length(); + + // Padding. Leave room for sign later if required. + int pad_chars_count = (is_negative || show_sign) ? min_chars - 1 : min_chars; + String pad_char = (pad_with_zeros && !not_numeric) ? String("0") : String(" "); // Never pad NaN or inf with zeros + if (left_justified) { + str = str.rpad(pad_chars_count, pad_char); + } else { + str = str.lpad(pad_chars_count, pad_char); + } + + // Add sign if needed. + if (show_sign || is_negative) { + String sign_char = is_negative ? "-" : "+"; + if (left_justified) { + str = str.insert(0, sign_char); + } else { + str = str.insert(pad_with_zeros ? 0 : str.length() - initial_len, sign_char); + } + } + + formatted += str; + ++value_index; + in_format = false; + break; + } + case 's': { // String + if (value_index >= values.size()) { + return "not enough arguments for format string"; + } + + String str = values[value_index]; + // Padding. + if (left_justified) { + str = str.rpad(min_chars); + } else { + str = str.lpad(min_chars); + } + + formatted += str; + ++value_index; + in_format = false; + break; + } + case 'c': { + if (value_index >= values.size()) { + return "not enough arguments for format string"; + } + + // Convert to character. + String str; + if (values[value_index].is_num()) { + int value = values[value_index]; + if (value < 0) { + return "unsigned byte integer is lower than maximum"; + } else if (value > 255) { + return "unsigned byte integer is greater than maximum"; + } + str = chr(values[value_index]); + } else if (values[value_index].get_type() == Variant::STRING) { + str = values[value_index]; + if (str.length() != 1) { + return "%c requires number or single-character string"; + } + } else { + return "%c requires number or single-character string"; + } + + // Padding. + if (left_justified) { + str = str.rpad(min_chars); + } else { + str = str.lpad(min_chars); + } + + formatted += str; + ++value_index; + in_format = false; + break; + } + case '-': { // Left justify + left_justified = true; + break; + } + case '+': { // Show + if positive. + show_sign = true; + break; + } + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': { + int n = c - '0'; + if (in_decimals) { + min_decimals *= 10; + min_decimals += n; + } else { + if (c == '0' && min_chars == 0) { + if (left_justified) { + WARN_PRINT("'0' flag ignored with '-' flag in string format"); + } else { + pad_with_zeros = true; + } + } else { + min_chars *= 10; + min_chars += n; + } + } + break; + } + case '.': { // Float separator. + if (in_decimals) { + return "too many decimal points in format"; + } + in_decimals = true; + min_decimals = 0; // We want to add the value manually. + break; + } + + case '*': { // Dynamic width, based on value. + if (value_index >= values.size()) { + return "not enough arguments for format string"; + } + + if (!values[value_index].is_num()) { + return "* wants number"; + } + + int size = values[value_index]; + + if (in_decimals) { + min_decimals = size; + } else { + min_chars = size; + } + + ++value_index; + break; + } + + default: { + return "unsupported format character"; + } + } + } else { // Not in format string. + switch (c) { + case '%': + in_format = true; + // Back to defaults: + min_chars = 0; + min_decimals = 6; + pad_with_zeros = false; + left_justified = false; + show_sign = false; + in_decimals = false; + break; + default: + formatted += chr(c); + } + } + } + + if (in_format) { + return "incomplete format"; + } + + if (value_index != values.size()) { + return "not all arguments converted during string formatting"; + } + + *error = false; + return formatted; +} + +String String::quote(String quotechar) const { + return quotechar + *this + quotechar; +} + +String String::unquote() const { + if (!is_quoted()) { + return *this; + } + + return substr(1, length() - 2); +} + +#ifdef TOOLS_ENABLED +String TTR(const String &p_text, const String &p_context) { + if (TranslationServer::get_singleton()) { + return TranslationServer::get_singleton()->tool_translate(p_text, p_context); + } + + return p_text; +} + +/* DTR is used for the documentation, handling descriptions extracted from the XML. + * It also replaces `$DOCS_URL` with the actual URL to the documentation's branch, + * to allow dehardcoding it in the XML and doing proper substitutions everywhere. + */ +String DTR(const String &p_text) { + // Comes straight from the XML, so remove indentation and any trailing whitespace. + const String text = p_text.dedent().strip_edges(); + + if (TranslationServer::get_singleton()) { + return String(TranslationServer::get_singleton()->doc_translate(text)).replace("$DOCS_URL", VERSION_DOCS_URL); + } + + return text.replace("$DOCS_URL", VERSION_DOCS_URL); +} +#endif + +String RTR(const String &p_text) { + if (TranslationServer::get_singleton()) { + String rtr = TranslationServer::get_singleton()->tool_translate(p_text, StringName()); + if (rtr == String() || rtr == p_text) { + return TranslationServer::get_singleton()->translate(p_text); + } else { + return rtr; + } + } + + return p_text; +} diff --git a/tests/godot_mockery/core/vector.h b/tests/godot_mockery/core/vector.h new file mode 100644 index 0000000..aa273ec --- /dev/null +++ b/tests/godot_mockery/core/vector.h @@ -0,0 +1,205 @@ +/**************************************************************************/ +/* vector.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef VECTOR_H +#define VECTOR_H + +/** + * @class Vector + * @author Juan Linietsky + * Vector container. Regular Vector Container. Use with care and for smaller arrays when possible. Use PoolVector for large arrays. + */ + +#include + +#include "core/cowdata.h" +#include "core/error_macros.h" +#include "core/os/memory.h" +#include "core/sort_array.h" + +template +class VectorWriteProxy { +public: + _FORCE_INLINE_ T &operator[](int p_index) { + CRASH_BAD_INDEX(p_index, ((Vector *)(this))->_cowdata.size()); + + return ((Vector *)(this))->_cowdata.ptrw()[p_index]; + } +}; + +template +class Vector { + friend class VectorWriteProxy; + +public: + VectorWriteProxy write; + +private: + CowData _cowdata; + +public: + bool push_back(T p_elem); + void fill(T p_elem); + + void remove(int p_index) { _cowdata.remove(p_index); } + void erase(const T &p_val) { + int idx = find(p_val); + if (idx >= 0) { + remove(idx); + } + }; + void invert(); + + _FORCE_INLINE_ T *ptrw() { return _cowdata.ptrw(); } + _FORCE_INLINE_ const T *ptr() const { return _cowdata.ptr(); } + _FORCE_INLINE_ void clear() { resize(0); } + _FORCE_INLINE_ bool empty() const { return _cowdata.empty(); } + + _FORCE_INLINE_ T get(int p_index) { return _cowdata.get(p_index); } + _FORCE_INLINE_ const T &get(int p_index) const { return _cowdata.get(p_index); } + _FORCE_INLINE_ void set(int p_index, const T &p_elem) { _cowdata.set(p_index, p_elem); } + _FORCE_INLINE_ int size() const { return _cowdata.size(); } + Error resize(int p_size) { return _cowdata.resize(p_size); } + _FORCE_INLINE_ const T &operator[](int p_index) const { return _cowdata.get(p_index); } + Error insert(int p_pos, T p_val) { return _cowdata.insert(p_pos, p_val); } + int find(const T &p_val, int p_from = 0) const { return _cowdata.find(p_val, p_from); } + + void append_array(Vector p_other); + + template + void sort_custom() { + int len = _cowdata.size(); + if (len == 0) { + return; + } + + T *data = ptrw(); + SortArray sorter; + sorter.sort(data, len); + } + + void sort() { + sort_custom<_DefaultComparator>(); + } + + void ordered_insert(const T &p_val) { + int i; + for (i = 0; i < _cowdata.size(); i++) { + if (p_val < operator[](i)) { + break; + }; + }; + insert(i, p_val); + } + + _FORCE_INLINE_ Vector() {} + _FORCE_INLINE_ Vector(const Vector &p_from) { _cowdata._ref(p_from._cowdata); } + inline Vector &operator=(const Vector &p_from) { + _cowdata._ref(p_from._cowdata); + return *this; + } + + Vector to_byte_array() const { + Vector ret; + ret.resize(size() * sizeof(T)); + memcpy(ret.ptrw(), ptr(), sizeof(T) * size()); + return ret; + } + + Vector slice(int p_begin, int p_end = INT32_MAX) const { + Vector result; + + const int s = size(); + + int begin = CLAMP(p_begin, -s, s); + if (begin < 0) { + begin += s; + } + int end = CLAMP(p_end, -s, s); + if (end < 0) { + end += s; + } + + assert(begin <= end); + + int result_size = end - begin; + result.resize(result_size); + + const T *const r = ptr(); + T *const w = result.ptrw(); + for (int i = 0; i < result_size; ++i) { + w[i] = r[begin + i]; + } + + return result; + } + + _FORCE_INLINE_ ~Vector() {} +}; + +template +void Vector::invert() { + for (int i = 0; i < size() / 2; i++) { + T *p = ptrw(); + SWAP(p[i], p[size() - i - 1]); + } +} + +template +void Vector::append_array(Vector p_other) { + const int ds = p_other.size(); + if (ds == 0) { + return; + } + const int bs = size(); + resize(bs + ds); + for (int i = 0; i < ds; ++i) { + ptrw()[bs + i] = p_other[i]; + } +} + +template +bool Vector::push_back(T p_elem) { + Error err = resize(size() + 1); + assert(err != OK); + set(size() - 1, p_elem); + + return false; +} + +template +void Vector::fill(T p_elem) { + T *p = ptrw(); + for (int i = 0; i < size(); i++) { + p[i] = p_elem; + } +} + +#endif // VECTOR_H diff --git a/tests/graph_module.cpp b/tests/graph_module.cpp index 3af1595..d2da8a6 100644 --- a/tests/graph_module.cpp +++ b/tests/graph_module.cpp @@ -4,26 +4,6 @@ #include #include "flecs.h" #include "graph_module.h" -namespace WorldEditor -{ - namespace components - { - struct buildings_layout_zone - { - int type; - bool align_wall; - }; - struct buildings_layout_area - { - float area; - }; - struct buildings_layout_system - { - flecs::entity e; - }; - - } -} struct graph_module { graph_module(flecs::world &ecs) diff --git a/tests/graph_test b/tests/graph_test deleted file mode 100755 index c3c5c97..0000000 Binary files a/tests/graph_test and /dev/null differ diff --git a/tests/rect2i b/tests/rect2i deleted file mode 100755 index 566f2bb..0000000 Binary files a/tests/rect2i and /dev/null differ diff --git a/tests/regions.cpp b/tests/regions.cpp new file mode 100644 index 0000000..0366a68 --- /dev/null +++ b/tests/regions.cpp @@ -0,0 +1,7 @@ +#include "../src/modules/stream/ui/region_tree.h" + +int main() +{ + return 0; +} +