ares-openbsd/nall/set.hpp

268 行
6.8 KiB
C++
Raw 通常表示 履歴

#pragma once
//set
//implementation: red-black tree
//
//search: O(log n) average; O(log n) worst
//insert: O(log n) average; O(log n) worst
//remove: O(log n) average; O(log n) worst
//
//requirements:
// bool T::operator==(const T&) const;
// bool T::operator< (const T&) const;
#include <nall/utility.hpp>
#include <nall/vector.hpp>
namespace nall {
template<typename T> struct set {
struct node_t {
T value;
bool red = 1;
node_t* link[2] = {nullptr, nullptr};
node_t() = default;
node_t(const T& value) : value(value) {}
};
node_t* root = nullptr;
u32 nodes = 0;
set() = default;
set(const set& source) { operator=(source); }
2022-09-13 16:47:47 +09:00
set(set&& source) { operator=(std::move(source)); }
set(std::initializer_list<T> list) { for(auto& value : list) insert(value); }
~set() { reset(); }
auto operator=(const set& source) -> set& {
Update to v106r95 release. byuu says: Another eleven hours in ... I think we're finally starting to see some hope. Unserialization of the tree works now. An absolutely heroic effort. The design challenges were thus: - given a text document, reproduce a tree of shared pointer objects - the text document may have extra or bad node names (perhaps it's from an older version) -- ignore these so they don't appear in the actual tree - the text document may be missing some node names (again, maybe an older version) -- populate the actual tree nodes with default values in this case - any peripherals connected in text document nodes won't be in the actual tree, so we have to connect them - upon the creation of a new actual tree peripheral, we must populate it with the metadata from the text document recursively, with the above rules in effect - we also need to allow the GUI to attach new peripherals, where it would not know about the child nodes each peripheral contains (eg the buttons on a gamepad) - lastly, even though the tree is small, try not to take an absolute eternity to import a document into the tree There's a hell of a lot of restructuring that needs doing, in the SNES core far more than anywhere else. We have to split bus mapping to occur at power-on time, and manifest generation + slot attachments to happen during load / system configuration. GBA also should move to detecting save types at run-time. And honestly, that scan is pretty painfully slow ... it may make sense to suffix array it ... 13*4MiB isn't that much memory. That or just write a smarter substring scanner. Sigh. So now that things can be serialized, the next step is going to be writing an input manager. It's going to be a radical departure from the old way of doing things. Now, when the core wants input state, it'll call inputPoll with a Node::Input object that we can directly write the value to (well, after we `dynamic_cast` it to its specialized type. It'd sure be nice if there were a `dynamic_multicast` switch construct, so we didn't have to keep doing repeated `dynamic_cast` calls for each input type. I digress.) I guess we can cache things ... Thus, input polling will be more like this: - produce a map of Node::Input to `shared_pointer<InputManager::Binding>` objects - maybe even do a multitap as a lazier way of handling multiple input bindings - have the InputManager::Binding hold a `shared_pointer<nall::HID>` object, the group ID, and the input ID - polling will thus be a map (or multitap) lookup; copying the nall::HID value over to the Node::Input value - it should be fine to just store `shared_pointers` here, to save the overhead of constantly converting `weak_pointers` And of course, we have to write a GUI for this. Oh, and before I can even do the above ... I have to implement template importing, and implement serialization support when attaching / detaching peripherals. Hmm, what else ... I got rid of the Port specializations, as Port ends up needing a lot of glue code and any special handling can be done inside the emulation cores anyway. So now, a Port is a Port. It's just a Node with the special property that it may contain either zero or one Peripheral nodes. In addition to nodes of other types, of course. I'm thinking we should probably also enforce a tree rule that no parent node shall have two child nodes that have both the same type and the same name. If we stick to that rule, we can simplify path traversal, and allow the append function to be used as a "create if not exists" function. Also, we're really going to need a system to pick sensible default peripheral and template paths for ports, including for dynamic ports. Having to manually assign these, and having the values on dynamically generated ports lost easily, is very annoying.
2019-02-03 16:53:32 +09:00
if(this == &source) return *this;
reset();
copy(root, source.root);
nodes = source.nodes;
return *this;
}
auto operator=(set&& source) -> set& {
Update to v106r95 release. byuu says: Another eleven hours in ... I think we're finally starting to see some hope. Unserialization of the tree works now. An absolutely heroic effort. The design challenges were thus: - given a text document, reproduce a tree of shared pointer objects - the text document may have extra or bad node names (perhaps it's from an older version) -- ignore these so they don't appear in the actual tree - the text document may be missing some node names (again, maybe an older version) -- populate the actual tree nodes with default values in this case - any peripherals connected in text document nodes won't be in the actual tree, so we have to connect them - upon the creation of a new actual tree peripheral, we must populate it with the metadata from the text document recursively, with the above rules in effect - we also need to allow the GUI to attach new peripherals, where it would not know about the child nodes each peripheral contains (eg the buttons on a gamepad) - lastly, even though the tree is small, try not to take an absolute eternity to import a document into the tree There's a hell of a lot of restructuring that needs doing, in the SNES core far more than anywhere else. We have to split bus mapping to occur at power-on time, and manifest generation + slot attachments to happen during load / system configuration. GBA also should move to detecting save types at run-time. And honestly, that scan is pretty painfully slow ... it may make sense to suffix array it ... 13*4MiB isn't that much memory. That or just write a smarter substring scanner. Sigh. So now that things can be serialized, the next step is going to be writing an input manager. It's going to be a radical departure from the old way of doing things. Now, when the core wants input state, it'll call inputPoll with a Node::Input object that we can directly write the value to (well, after we `dynamic_cast` it to its specialized type. It'd sure be nice if there were a `dynamic_multicast` switch construct, so we didn't have to keep doing repeated `dynamic_cast` calls for each input type. I digress.) I guess we can cache things ... Thus, input polling will be more like this: - produce a map of Node::Input to `shared_pointer<InputManager::Binding>` objects - maybe even do a multitap as a lazier way of handling multiple input bindings - have the InputManager::Binding hold a `shared_pointer<nall::HID>` object, the group ID, and the input ID - polling will thus be a map (or multitap) lookup; copying the nall::HID value over to the Node::Input value - it should be fine to just store `shared_pointers` here, to save the overhead of constantly converting `weak_pointers` And of course, we have to write a GUI for this. Oh, and before I can even do the above ... I have to implement template importing, and implement serialization support when attaching / detaching peripherals. Hmm, what else ... I got rid of the Port specializations, as Port ends up needing a lot of glue code and any special handling can be done inside the emulation cores anyway. So now, a Port is a Port. It's just a Node with the special property that it may contain either zero or one Peripheral nodes. In addition to nodes of other types, of course. I'm thinking we should probably also enforce a tree rule that no parent node shall have two child nodes that have both the same type and the same name. If we stick to that rule, we can simplify path traversal, and allow the append function to be used as a "create if not exists" function. Also, we're really going to need a system to pick sensible default peripheral and template paths for ports, including for dynamic ports. Having to manually assign these, and having the values on dynamically generated ports lost easily, is very annoying.
2019-02-03 16:53:32 +09:00
if(this == &source) return *this;
reset();
root = source.root;
nodes = source.nodes;
source.root = nullptr;
source.nodes = 0;
return *this;
}
explicit operator bool() const { return nodes; }
auto size() const -> u32 { return nodes; }
auto reset() -> void {
reset(root);
nodes = 0;
}
auto find(const T& value) -> maybe<T&> {
if(node_t* node = find(root, value)) return node->value;
return nothing;
}
auto find(const T& value) const -> maybe<const T&> {
if(node_t* node = find(root, value)) return node->value;
return nothing;
}
auto insert(const T& value) -> maybe<T&> {
u32 count = size();
node_t* v = insert(root, value);
root->red = 0;
if(size() == count) return nothing;
return v->value;
}
template<typename... P> auto insert(const T& value, P&&... p) -> bool {
bool result = insert(value);
2022-09-13 16:47:47 +09:00
insert(std::forward<P>(p)...) | result;
return result;
}
auto remove(const T& value) -> bool {
u32 count = size();
bool done = 0;
remove(root, &value, done);
if(root) root->red = 0;
return size() < count;
}
template<typename... P> auto remove(const T& value, P&&... p) -> bool {
bool result = remove(value);
2022-09-13 16:47:47 +09:00
return remove(std::forward<P>(p)...) | result;
}
struct base_iterator {
auto operator!=(const base_iterator& source) const -> bool { return position != source.position; }
auto operator++() -> base_iterator& {
if(++position >= source.size()) { position = source.size(); return *this; }
if(stack.right()->link[1]) {
stack.append(stack.right()->link[1]);
while(stack.right()->link[0]) stack.append(stack.right()->link[0]);
} else {
node_t* child;
do child = stack.takeRight();
while(child == stack.right()->link[1]);
}
return *this;
}
base_iterator(const set& source, u32 position) : source(source), position(position) {
node_t* node = source.root;
while(node) {
stack.append(node);
node = node->link[0];
}
}
protected:
const set& source;
u32 position;
vector<node_t*> stack;
};
struct iterator : base_iterator {
iterator(const set& source, u32 position) : base_iterator(source, position) {}
auto operator*() const -> T& { return base_iterator::stack.right()->value; }
};
auto begin() -> iterator { return iterator(*this, 0); }
auto end() -> iterator { return iterator(*this, size()); }
struct const_iterator : base_iterator {
const_iterator(const set& source, u32 position) : base_iterator(source, position) {}
auto operator*() const -> const T& { return base_iterator::stack.right()->value; }
};
auto begin() const -> const const_iterator { return const_iterator(*this, 0); }
auto end() const -> const const_iterator { return const_iterator(*this, size()); }
private:
auto reset(node_t*& node) -> void {
if(!node) return;
if(node->link[0]) reset(node->link[0]);
if(node->link[1]) reset(node->link[1]);
delete node;
node = nullptr;
}
auto copy(node_t*& target, const node_t* source) -> void {
if(!source) return;
target = new node_t(source->value);
target->red = source->red;
copy(target->link[0], source->link[0]);
copy(target->link[1], source->link[1]);
}
auto find(node_t* node, const T& value) const -> node_t* {
if(node == nullptr) return nullptr;
if(node->value == value) return node;
return find(node->link[node->value < value], value);
}
auto red(node_t* node) const -> bool { return node && node->red; }
auto black(node_t* node) const -> bool { return !red(node); }
auto rotate(node_t*& a, bool dir) -> void {
node_t*& b = a->link[!dir];
node_t*& c = b->link[dir];
a->red = 1, b->red = 0;
std::swap(a, b);
std::swap(b, c);
}
auto rotateTwice(node_t*& node, bool dir) -> void {
rotate(node->link[!dir], !dir);
rotate(node, dir);
}
auto insert(node_t*& node, const T& value) -> node_t* {
if(!node) { nodes++; node = new node_t(value); return node; }
if(node->value == value) { node->value = value; return node; } //prevent duplicate entries
bool dir = node->value < value;
node_t* v = insert(node->link[dir], value);
if(black(node->link[dir])) return v;
if(red(node->link[!dir])) {
node->red = 1;
node->link[0]->red = 0;
node->link[1]->red = 0;
} else if(red(node->link[dir]->link[dir])) {
rotate(node, !dir);
} else if(red(node->link[dir]->link[!dir])) {
rotateTwice(node, !dir);
}
return v;
}
auto balance(node_t*& node, bool dir, bool& done) -> void {
node_t* p = node;
node_t* s = node->link[!dir];
if(!s) return;
if(red(s)) {
rotate(node, dir);
s = p->link[!dir];
}
if(black(s->link[0]) && black(s->link[1])) {
if(red(p)) done = 1;
p->red = 0, s->red = 1;
} else {
bool save = p->red;
bool head = node == p;
if(red(s->link[!dir])) rotate(p, dir);
else rotateTwice(p, dir);
p->red = save;
p->link[0]->red = 0;
p->link[1]->red = 0;
if(head) node = p;
else node->link[dir] = p;
done = 1;
}
}
auto remove(node_t*& node, const T* value, bool& done) -> void {
if(!node) { done = 1; return; }
if(node->value == *value) {
if(!node->link[0] || !node->link[1]) {
node_t* save = node->link[!node->link[0]];
if(red(node)) done = 1;
else if(red(save)) save->red = 0, done = 1;
nodes--;
delete node;
node = save;
return;
} else {
node_t* heir = node->link[0];
while(heir->link[1]) heir = heir->link[1];
node->value = heir->value;
value = &heir->value;
}
}
bool dir = node->value < *value;
remove(node->link[dir], value, done);
if(!done) balance(node, dir, done);
}
};
}