Game Events: refactored event handler storage

This throws out the custom smart_list class in favor of a plain std::list. It also greatly simplifies
a few things. First, event handlers no longer remove themselves from the main list in event_handlers.
Now they just flag themselves as disabled (which means they will never execute once marked) and cleaned
up later in a newly added cleanup stage. This means a handler no longer needs to keep its index in the
active handler vector.

This removal of reliance on indices also means I could add the aforementioned cleanup stage. With the
smart_list code, event handlers were never actually removed from the active vector, nor any weak_ptrs
pointing to them removed either. This wasn't exactly a problem, since the handlers were stored via
shared_ptrs which would then simply be null after one deleted itself. Still, it's cleaner to drop any
invalid ones (and unlockable weak_ptrs) from any relevant containers. I've opted to do this in
manager::execute_on_events. Seems a good enough place as any.

The net result of this is the code is much cleaner. We're able to get rid of a bunch of unnecessary
feelers into various classes. This also makes the manager::iteration dereference code a lot easier
to understand. There certainly could be further refactoring, but I think this is a good start.
This commit is contained in:
Charles Dang 2017-05-21 12:17:45 +11:00
parent f62b62b700
commit 056d7ac8f8
8 changed files with 104 additions and 1056 deletions

View file

@ -45,90 +45,40 @@ static lg::log_domain log_event_handler("event_handler");
// This file is in the game_events namespace.
namespace game_events
{
/* ** handler_list::iterator ** */
/**
* Dereference.
* If the current element has become invalid, we will increment first.
*/
handler_ptr handler_list::iterator::operator*()
{
// Check for an available handler.
while(iter_.derefable()) {
// Handler still accessible?
if(handler_ptr lock = iter_->lock()) {
return lock;
} else {
// Remove the now-defunct entry.
iter_ = list_t::erase(iter_);
}
}
// End of the list.
return handler_ptr();
}
/* ** event_handler ** */
event_handler::event_handler(const config& cfg, bool imi, handler_vec::size_type index, manager& man)
event_handler::event_handler(const config& cfg, bool imi)
: first_time_only_(cfg["first_time_only"].to_bool(true))
, is_menu_item_(imi)
, index_(index)
, man_(&man)
, disabled_(false)
, cfg_(cfg)
{
}
/**
* Disables *this, removing it from the game.
* (Technically, the handler is only removed once no one is hanging on to a
* handler_ptr to *this. So be careful how long they persist.)
*
* WARNING: *this may be destroyed at the end of this call, unless
* the caller maintains a handler_ptr to this.
*/
void event_handler::disable()
{
assert(man_);
assert(man_->event_handlers_);
// Handlers must have an index after they're created.
assert(index_ < man_->event_handlers_->size());
// Disable this handler.
(*man_->event_handlers_)[index_].reset();
assert(!disabled_);;
disabled_ = true;
}
/**
* Handles the queued event, according to our WML instructions.
* WARNING: *this may be destroyed at the end of this call, unless
* the caller maintains a handler_ptr to this.
*
* @param[in] event_info Information about the event that needs handling.
* @param[in,out] handler_p The caller's smart pointer to *this. It may be
* reset() during processing.
*/
void event_handler::handle_event(const queued_event& event_info, handler_ptr& handler_p, game_lua_kernel& lk)
void event_handler::handle_event(const queued_event& event_info, game_lua_kernel& lk)
{
// We will need our config after possibly self-destructing. Make a copy now.
// TODO: instead of copying possibly huge config objects we should use shared things and only increase a refcount
// here.
vconfig vcfg(cfg_, true);
// If this even is disabled, do nothing.
if(disabled_) {
return;
}
if(is_menu_item_) {
DBG_NG << cfg_["name"] << " will now invoke the following command(s):\n" << cfg_;
}
// Disable this handler if it's a one-time event.
if(first_time_only_) {
// Disable this handler.
disable();
// Also remove our caller's hold on us.
handler_p.reset();
}
// *WARNING*: At this point, dereferencing this could be a memory violation!
lk.run_wml_action("command", vcfg, event_info);
lk.run_wml_action("command", vconfig(cfg_, false), event_info);
sound::commit_music_changes();
}

View file

@ -24,8 +24,8 @@
#pragma once
#include "config.hpp"
#include "utils/smart_list.hpp"
#include <list>
#include <memory>
#include <set>
#include <string>
@ -39,21 +39,18 @@ struct queued_event;
class event_handler; // Defined a few lines down.
class manager;
/// Shared pointer to handler objects.
typedef std::shared_ptr<event_handler> handler_ptr;
/// Storage of event handlers.
typedef std::vector<handler_ptr> handler_vec;
using handler_ptr = std::shared_ptr<event_handler>;
using weak_handler_ptr = std::weak_ptr<event_handler>;
using handler_list = std::list<weak_handler_ptr>;
class event_handler
{
public:
event_handler(const config& cfg, bool is_menu_item, handler_vec::size_type index, manager&);
event_handler(const config& cfg, bool is_menu_item);
/** The index of *this should only be of interest when controlling iterations. */
handler_vec::size_type index() const
bool disabled() const
{
return index_;
return disabled_;
}
bool matches_name(const std::string& name, const game_data* data) const;
@ -63,9 +60,15 @@ public:
return is_menu_item_;
}
/** Disables *this, removing it from the game. */
/** Flag this handler as disabled. */
void disable();
void handle_event(const queued_event& event_info, handler_ptr& handler_p, game_lua_kernel&);
/**
* Handles the queued event, according to our WML instructions.
*
* @param[in] event_info Information about the event that needs handling.
*/
void handle_event(const queued_event& event_info, game_lua_kernel&);
const config& get_config() const
{
@ -75,109 +78,8 @@ public:
private:
bool first_time_only_;
bool is_menu_item_;
handler_vec::size_type index_;
manager* man_;
bool disabled_;
config cfg_;
};
/**
* This is a wrapper for a list of weak pointers to handlers. It allows forward
* iterations of the list, with each element returned as a shared pointer.
* (Weak pointers that fail to lock are silently removed from the list.) These
* iterations can be used recursively, even when the innermost iteration might
* erase arbitrary elements from the list.
*
* The interface is not the standard list interface because that would be
* inconvenient. The functionality implemented is that required by Wesnoth.
*/
class handler_list
{
/// The weak pointers that are used internally.
typedef std::weak_ptr<event_handler> internal_ptr;
/// The underlying list.
typedef utils::smart_list<internal_ptr> list_t;
public:
/**
* Handler list iterators are rather limited. They can be constructed
* from a reference iterator (not default constructed), incremented,
* and dereferenced. Consecutive dereferences are not guaranteed to
* return the same element (if the list mutates between them, the next
* element might be returned). An increment guarantees that the next
* dereference will differ from the previous (unless at the end of the
* list). The end of the list is indicated by dereferencing to a null
* pointer.
*/
class iterator
{
/// The current element.
list_t::iterator iter_;
public:
/// Initialized constructor (to be called by handler_list).
explicit iterator(const list_t::iterator& base_iter)
: iter_(base_iter)
{
}
/// Increment.
iterator& operator++()
{
++iter_;
return *this;
}
/// Dereference.
handler_ptr operator*();
};
friend class iterator;
typedef iterator const_iterator;
public:
/**
* Default constructor.
* Note: This explicit definition is required (by the more pedantic
* compilers) in order to declare a default-constructed, static,
* and const variable in event_handlers::get(), in handlers.cpp.
*/
handler_list()
: data_()
{
}
const_iterator begin() const
{
return iterator(const_cast<list_t&>(data_).begin());
}
// The above const_cast is so the iterator can remove obsolete entries.
const_iterator end() const
{
return iterator(const_cast<list_t&>(data_).end());
}
// push_front() is probably unneeded, but I'll leave the code here, just in case.
// (These lists must be maintained in index order, which means pushing to the back.)
void push_front(const handler_ptr& p)
{
data_.push_front(internal_ptr(p));
}
void push_back(const handler_ptr& p)
{
data_.push_back(internal_ptr(p));
}
void clear()
{
data_.clear();
}
private:
/// No implementation of operator=() since smart_list does not support it.
handler_list& operator=(const handler_list&);
/// The actual list.
list_t data_;
};
}

View file

@ -44,7 +44,7 @@ namespace game_events
/** Create an event handler. */
void manager::add_event_handler(const config& handler, bool is_menu_item)
{
event_handlers_->add_event_handler(handler, *this, is_menu_item);
event_handlers_->add_event_handler(handler, is_menu_item);
}
/** Removes an event handler. */
@ -101,7 +101,6 @@ manager::iteration::iteration(const std::string& event_name, manager& man)
: main_list_(man.event_handlers_->get(event_name))
, var_list_(man.event_handlers_->get_dynamic())
, event_name_(event_name)
, end_(man.event_handlers_->size())
, current_is_known_(false)
, main_is_current_(false)
, main_it_(main_list_.begin())
@ -113,7 +112,7 @@ manager::iteration::iteration(const std::string& event_name, manager& man)
/**
* Increment
* Incrementing guarantees that the next dereference will differ from the
* previous derference (unless the iteration is exhausted). However, multiple
* previous dereference (unless the iteration is exhausted). However, multiple
* increments between dereferences are allowed to have the same effect as a
* single increment.
*/
@ -139,6 +138,20 @@ manager::iteration& manager::iteration::operator++()
return *this;
}
// Small helper function to ensure we don't try to dereference an invalid iterator.
static handler_ptr lock_ptr(const handler_list& list, handler_list::iterator iter)
{
if(iter != list.end()) {
if(handler_ptr ptr = iter->lock()) {
return ptr;
} else {
assert(false && "Found null handler in handler list!");
}
}
return nullptr;
}
/**
* Dereference
* Will return a null pointer when the end of the iteration is reached.
@ -146,22 +159,23 @@ manager::iteration& manager::iteration::operator++()
handler_ptr manager::iteration::operator*()
{
// Get the candidate for the current element from the main list.
handler_ptr main_ptr = *main_it_;
handler_vec::size_type main_index = ptr_index(main_ptr);
handler_ptr main_ptr = lock_ptr(main_list_, main_it_);
// Get the candidate for the current element from the var list.
handler_ptr var_ptr = *var_it_;
handler_ptr var_ptr = lock_ptr(var_list_, var_it_);
// (Loop while var_ptr would be chosen over main_ptr, but the name does not match.)
while(var_ptr && var_ptr->index() < main_index && !var_ptr->matches_name(event_name_, gamedata_)) {
var_ptr = *++var_it_;
// If we have a variable-name event but its name doesn't match event_name_,
// keep iterating until we find a match. If we reach var_list_ end var_ptr
// will be nullptr.
while(var_ptr && !var_ptr->matches_name(event_name_, gamedata_)) {
var_ptr = lock_ptr(var_list_, ++var_it_);
}
handler_vec::size_type var_index = ptr_index(var_ptr);
// Are either of the handler ptrs valid?
current_is_known_ = main_ptr != nullptr || var_ptr != nullptr;
// Which list? (Index ties go to the main list.)
current_is_known_ = main_index < end_ || var_index < end_;
main_is_current_ = main_index <= var_index;
// If var_ptr is invalid, we use the ptr from the main list.
main_is_current_ = var_ptr == nullptr;
if(!current_is_known_) {
return nullptr; // End of list; return a null pointer.
@ -212,6 +226,9 @@ void manager::execute_on_events(const std::string& event_id, manager::event_func
func(*this, hand);
++iter;
}
// Clean up expired ptrs. This saves us effort later since it ensures every ptr is valid.
event_handlers_->clean_up_expired_handlers(event_id);
}
game_events::wml_event_pump& manager::pump()

View file

@ -69,23 +69,13 @@ private:
// Dereference:
handler_ptr operator*();
private:
/// Gets the index from a pointer, capped at end_.
handler_vec::size_type ptr_index(const handler_ptr& ptr) const
{
return !bool(ptr) ? end_ : std::min(ptr->index(), end_);
}
private:
/// The fixed-name event handlers for this iteration.
const handler_list& main_list_;
handler_list& main_list_;
/// The varying-name event handlers for this iteration.
const handler_list& var_list_;
handler_list& var_list_;
/// The event name for this iteration.
const std::string event_name_;
/// The end of this iteration. We intentionally exclude handlers
/// added after *this is constructed.
const handler_vec::size_type end_;
/// Set to true upon dereferencing.
bool current_is_known_;

View file

@ -80,10 +80,10 @@ std::string event_handlers::standardize_name(const std::string& name)
/**
* Read-only access to the handlers with fixed event names, by event name.
*/
const handler_list& event_handlers::get(const std::string& name) const
handler_list& event_handlers::get(const std::string& name)
{
// Empty list for the "not found" case.
static const handler_list empty_list;
static handler_list empty_list;
// Look for the name in the name map.
auto find_it = by_name_.find(standardize_name(name));
@ -95,7 +95,7 @@ const handler_list& event_handlers::get(const std::string& name) const
* An event with a nonempty ID will not be added if an event with that
* ID already exists.
*/
void event_handlers::add_event_handler(const config& cfg, manager& man, bool is_menu_item)
void event_handlers::add_event_handler(const config& cfg, bool is_menu_item)
{
const std::string name = cfg["name"];
std::string id = cfg["id"];
@ -111,7 +111,7 @@ void event_handlers::add_event_handler(const config& cfg, manager& man, bool is_
// Create a new handler.
DBG_EH << "inserting event handler for name=" << name << " with id=" << id << "\n";
handler_ptr new_handler(new event_handler(cfg, is_menu_item, active_.size(), man));
handler_ptr new_handler(new event_handler(cfg, is_menu_item));
active_.push_back(new_handler);
@ -162,6 +162,26 @@ void event_handlers::remove_event_handler(const std::string& id)
log_handlers();
}
void event_handlers::clean_up_expired_handlers(const std::string& event_name)
{
// First, remove all disabled handlers from the main list.
auto to_remove = std::remove_if(active_.begin(), active_.end(),
[](handler_ptr p) { return p->disabled(); }
);
active_.erase(to_remove, active_.end());
// Then remove any now-unlockable weak_ptrs from tbe by-name list.
get(event_name).remove_if(
[](weak_handler_ptr ptr) { return ptr.expired(); }
);
// And finally remove any now-unlockable weak_ptrs from the with-variables name list.
dynamic_.remove_if(
[](weak_handler_ptr ptr) { return ptr.expired(); }
);
}
const handler_ptr event_handlers::get_event_handler_by_id(const std::string& id)
{
auto find_it = id_map_.find(id);

View file

@ -25,15 +25,16 @@ namespace game_events
class event_handlers
{
private:
typedef std::unordered_map<std::string, handler_list> map_t;
typedef std::unordered_map<std::string, std::weak_ptr<event_handler>> id_map_t;
using handler_vec_t = std::vector<handler_ptr>;
using map_t = std::unordered_map<std::string, handler_list>;
using id_map_t = std::unordered_map<std::string, weak_handler_ptr>;
/**
* Active event handlers. Will not have elements removed unless the event_handlers is clear()ed.
* This is the only container that actually 'owns' any events in the form of shared_ptrs. The other
* three storage methods own weak_ptrs.
*/
handler_vec active_;
handler_vec_t active_;
/** Active event handlers with fixed event names, organized by event name. */
map_t by_name_;
@ -50,9 +51,6 @@ private:
static std::string standardize_name(const std::string& name);
public:
// TODO: remove
typedef handler_vec::size_type size_type;
event_handlers()
: active_()
, by_name_()
@ -61,41 +59,43 @@ public:
{
}
/** Read-only access to the handlers with varying event names. */
const handler_list& get_dynamic() const
/** Access to the handlers with varying event names. */
handler_list& get_dynamic()
{
return dynamic_;
}
/** Read-only access to the active event handlers. Essentially gives all events. */
const handler_vec& get_active() const
const handler_vec_t& get_active() const
{
return active_;
}
/** Read-only access to the handlers with fixed event names, by event name. */
const handler_list& get(const std::string& name) const;
/** Access to the handlers with fixed event names, by event name. */
handler_list& get(const std::string& name);
/** Adds an event handler. */
void add_event_handler(const config& cfg, manager& man, bool is_menu_item = false);
void add_event_handler(const config& cfg, bool is_menu_item = false);
/** Removes an event handler, identified by its ID. */
void remove_event_handler(const std::string& id);
/**
* Removes all expired event handlers and any weak_ptrs to them.
*
* @param event_name The event name from whose by-name queue to clean
* up handlers.
*/
void clean_up_expired_handlers(const std::string& event_name);
/** Gets an event handler, identified by its ID. */
const handler_ptr get_event_handler_by_id(const std::string& id);
/** The number of active event handlers. */
size_type size() const
size_t size() const
{
return active_.size();
}
/** Access to active event handlers by index. */
handler_ptr& operator[](size_type index)
{
return active_[index];
}
};
} // end namespace game_events

View file

@ -303,7 +303,7 @@ void wml_event_pump::process_event(handler_ptr& handler_p, const queued_event& e
++impl_->internal_wml_tracking;
context::scoped evc(impl_->contexts_);
assert(resources::lua_kernel != nullptr);
handler_p->handle_event(ev, handler_p, *resources::lua_kernel);
handler_p->handle_event(ev, *resources::lua_kernel);
// NOTE: handler_p may be null at this point!
if(ev.name == "select") {
@ -596,8 +596,6 @@ pump_result_t wml_event_pump::operator()()
// Let this handler process our event.
process_event(ptr, ev);
// NOTE: ptr may be null at this point!
});
} else {
// Get the handler directly via ID

View file

@ -1,829 +0,0 @@
/*
Copyright (C) 2014 - 2017 by David White <dave@whitevine.net>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
/**
* @file A list whose iterators never invalidate.
*/
#pragma once
#include <cassert>
#include <iterator>
#include <vector>
namespace utils
{
/* ** smart_list ** */
/// This is a variant on a list that never invalidates iterators (unless, of
/// course, the list ceases to exist). In particular, an erase() will only flag
/// an element for removal from the list. Flagged elements are ignored by list
/// functions, but they still exist from the perspective of iterators that had
/// pointed (and still point) to the element.
///
/// Assignment is incompatible with the goal of preserving iterators, so it is
/// not implemented (declared private though, to emphasize that this is
/// intentional).
///
/// The insert() and erase() members are static, as they do not require a
/// reference to the list.
///
/// Instead of being undefined, ++end() is equal to begin(), and --begin() is
/// equal to end().
template <class Data>
class smart_list
{
/// Nodes in the smart list.
struct node_t
{
/// Default constructor. This is for creating the root of a list.
node_t() : dat_ptr(nullptr), ref_count(1), next(this), prev(this)
{}
/// Initialized constructor. This is for creating a node in a list.
explicit node_t(const Data & d) : dat_ptr(new Data(d)), ref_count(1), next(nullptr), prev(nullptr)
{}
/// Destructor.
~node_t();
/// The data of the node. This is nullptr for a list's root.
Data * const dat_ptr;
/// ref_count counts 1 for the list and 2 for each iterator pointing to
/// this node. (So nodes are flagged for deletion when ref_count is even.)
mutable size_t ref_count;
// List linking.
node_t * next;
node_t * prev;
private: // Disallowed (and unneeded) functions (declared but not implemented).
/// No assignments.
node_t & operator=(const node_t &);
/// No copying.
node_t(const node_t & that);
};
/// The base for the list's iterator classes.
/// Value is the value type of this iterator.
/// Reversed is true for the reverse iterators.
/// The actual iterators must have neither data nor destructors.
template <class Value, bool Reversed>
class iterator_base
{
friend class smart_list<Data>;
typedef typename smart_list<Data>::node_t node_t;
public:
// Types required of an iterator:
typedef Value value_type;
typedef value_type * pointer;
typedef value_type & reference;
typedef std::bidirectional_iterator_tag iterator_category;
typedef ptrdiff_t difference_type;
protected: // Construct this via derived classes.
/// Default constructor
iterator_base() : ptr_(nullptr) {}
/// Initialized constructor
explicit iterator_base(node_t * ptr) : ptr_(ptr)
{ skip_flagged(); refer(); }
/// Conversion constructors.
template <class V, bool R>
iterator_base(const iterator_base<V,R> & that) : ptr_(that.ptr_)
{ refer(); }
/// Copy constructor (the default overrides the conversion template).
iterator_base(const iterator_base & that) : ptr_(that.ptr_)
{ refer(); }
public:
/// Destructor
/// (Not virtual, since the derived classes are mere shells.)
~iterator_base() { unref(ptr_); }
// Assignment
iterator_base & operator=(const iterator_base & that)
// NOTE: This is defined here because MSVC was unable to match the
// definition to the declaration when they were separate.
{
// Update our pointer.
node_t * old_ptr = ptr_;
ptr_ = that.ptr_;
// Update reference counts.
refer();
unref(old_ptr);
return *this;
}
// Comparison:
bool operator==(const iterator_base & that) const { return ptr_ == that.ptr_; }
bool operator!=(const iterator_base & that) const { return ptr_ != that.ptr_; }
// Dereference:
reference operator*() const { return *ptr_->dat_ptr; }
pointer operator->() const { return ptr_->dat_ptr; }
// Increment/decrement:
iterator_base & operator++() { advance(Reversed); return *this; }
iterator_base & operator--() { advance(!Reversed); return *this; }
iterator_base operator++(int)
// NOTE: This is defined here because MSVC was unable to match the
// definition to the declaration when they were separate.
{
iterator_base retval(*this);
advance(Reversed);
return retval;
}
iterator_base operator--(int)
// NOTE: This is defined here because MSVC was unable to match the
// definition to the declaration when they were separate.
{
iterator_base retval(*this);
advance(!Reversed);
return retval;
}
/// Test for being in a list, rather than past-the-end (or unassigned).
bool derefable() const { return derefable(ptr_); }
private: // functions
/// Test for being in a list, rather than past-the-end (or unassigned).
static bool derefable(node_t * ptr){ return ptr && ptr->dat_ptr; }
/// Advances our pointer to an unflagged node, possibly in the reverse direction.
void advance(bool reverse);
/// Advances our pointer one step, possibly in the reverse direction.
void inc(bool reverse) { ptr_ = reverse ? ptr_->prev : ptr_->next; }
/// Make sure we are not pointing to a flagged node.
void skip_flagged(bool reverse=Reversed);
/// Add a reference.
void refer() const;
/// Remove a reference.
static void unref(node_t * old_ptr);
private: // data
node_t * ptr_;
};
template <class Value, bool Reversed> friend class iterator_base;
public: // The standard list interface, minus assignment.
typedef Data value_type;
typedef value_type * pointer;
typedef value_type & reference;
typedef const value_type & const_reference;
typedef size_t size_type;
// Making these structs instead of typedefs cuts down on the length of
// compiler messages and helps define which conversions are allowed.
struct iterator : public iterator_base<Data, false> {
/// Default constructor
iterator() : iterator_base<Data, false>() {}
/// Initialized constructor
explicit iterator(node_t * ptr) : iterator_base<Data, false>(ptr) {}
/// Copy constructor.
iterator(const iterator & that) : iterator_base<Data, false>(that) {}
/// Conversion from reverse_iterator.
explicit iterator(const iterator_base<Data, true> & that) : iterator_base<Data, false>(that) {}
};
struct const_iterator : public iterator_base<const Data, false> {
/// Default constructor
const_iterator() : iterator_base<const Data, false>() {}
/// Initialized constructor
explicit const_iterator(node_t * ptr) : iterator_base<const Data, false>(ptr) {}
/// Copy constructor.
const_iterator(const const_iterator & that) : iterator_base<const Data, false>(that) {}
/// Conversion from iterator.
const_iterator(const iterator_base<Data, false> & that) : iterator_base<const Data, false>(that) {}
/// Conversion from const_reverse_iterator.
explicit const_iterator(const iterator_base<const Data, true> & that) : iterator_base<const Data, false>(that) {}
};
struct reverse_iterator : public iterator_base<Data, true> {
/// Default constructor
reverse_iterator() : iterator_base<Data, true>() {}
/// Initialized constructor
explicit reverse_iterator(node_t * ptr) : iterator_base<Data, true>(ptr) {}
/// Copy constructor.
reverse_iterator(const reverse_iterator & that) : iterator_base<Data, true>(that) {}
/// Conversion from iterator.
explicit reverse_iterator(const iterator_base<Data, false> & that) : iterator_base<Data, true>(that) {}
};
struct const_reverse_iterator : public iterator_base<const Data, true> {
/// Default constructor
const_reverse_iterator() : iterator_base<const Data, true>() {}
/// Initialized constructor
explicit const_reverse_iterator(node_t * ptr) : iterator_base<const Data, true>(ptr) {}
/// Copy constructor.
const_reverse_iterator(const const_reverse_iterator & that) : iterator_base<const Data, true>(that) {}
/// Conversion from reverse_iterator.
const_reverse_iterator(const iterator_base<Data, true> & that) : iterator_base<const Data, true>(that) {}
/// Conversion from const_iterator.
explicit const_reverse_iterator(const iterator_base<const Data, false> & that) : iterator_base<const Data, true>(that) {}
};
smart_list() : root_() {}
smart_list(size_type n);
smart_list(size_type n, const value_type & d);
smart_list(const smart_list & that);
template <class InputIterator>
smart_list(const InputIterator & f, const InputIterator & l);
~smart_list() { clear(); }
iterator begin() { return iterator(root_.next); }
iterator end() { return iterator(&root_); }
const_iterator begin() const { return const_iterator(root_.next); }
const_iterator end() const { return const_iterator(const_cast<node_t *>(&root_)); }
reverse_iterator rbegin() { return reverse_iterator(root_.prev); }
reverse_iterator rend() { return reverse_iterator(&root_); }
const_reverse_iterator rbegin() const { return const_reverse_iterator(root_.prev); }
const_reverse_iterator rend() const { return const_reverse_iterator(const_cast<node_t *>(&root_)); }
size_type size() const;
size_type max_size() const { return size_type(-1); }
/// Note: if empty() returns true, the list might still contain nodes
/// flagged for deletion.
bool empty() const { return begin() == end(); }
// Note: these functions use iterators so flagged nodes are skipped.
reference front() { return *begin(); }
const_reference front() const { return *begin(); }
reference back() { return *rbegin(); }
const_reference back() const { return *rbegin(); }
void push_front(const value_type & d) { insert(root_.next, d); }
void push_back(const value_type & d) { insert(&root_, d); }
void pop_front() { erase(begin()); }
void pop_back() { erase(iterator(rbegin())); }
void swap(smart_list & that);
static iterator insert(const iterator & pos, const value_type & d);
template <class InputIterator>
static void insert(const iterator & pos, const InputIterator & f, const InputIterator & l);
static void insert(const iterator & pos, size_type n, const value_type & d);
static iterator erase(const iterator & pos);
static iterator erase(const iterator & start, const iterator & stop);
void clear() { erase(begin(), end()); }
void resize(size_type n) { resize(n, value_type()); }
void resize(size_type n, const value_type & d);
void splice(const iterator & pos, smart_list & L);
void splice(const iterator & pos, smart_list & L, const iterator & i);
void splice(const iterator & pos, smart_list & L, const iterator & f, const iterator & l);
void remove(const value_type & value);
template <class Predicate>
void remove_if(const Predicate & p);
void unique() { unique(std::equal_to<value_type>()); }
template <class BinaryPredicate>
void unique(const BinaryPredicate p);
void merge(smart_list & L) { merge(L, std::less<value_type>()); }
template <class BinaryPredicate>
void merge(smart_list & L, const BinaryPredicate & p);
void sort() { sort(std::less<value_type>()); }
template <class BinaryPredicate>
void sort(const BinaryPredicate & p);
private: // functions
/// No implementation of operator=() since that would not preserve iterators.
smart_list & operator=(const smart_list &);
/// Returns true if @a node has been flagged for deletion.
static bool flagged(const node_t & node) { return node.ref_count % 2 == 0; }
/// Flags @a node for deletion.
static void flag(const node_t & node) { node.ref_count &= ~size_t(1); }
// Low-level implementations. The list is designed so that a reference
// to the list itself is not needed.
static node_t * insert(node_t * const pos, const value_type & d);
static node_t * check_erase(node_t * const pos);
static void link(node_t * const pos, node_t & begin_link, node_t & end_link);
static void unlink(node_t & begin_unlink, node_t & end_unlink);
static void splice(node_t * const pos, node_t & f, node_t & l);
private: // data
/// Root of the list.
node_t root_;
// I started doing this using STL and Boost, but it was taking longer than
// it would take for me to write stuff from scratch.
};
/** Sized constructor */
template <class Data>
inline smart_list<Data>::smart_list(size_type n) : root_()
{
for ( size_type i = 0; i < n; ++i )
push_back(value_type());
}
/** Sized constructor with a value */
template <class Data>
inline smart_list<Data>::smart_list(size_type n, const value_type & d) : root_()
{
for ( size_type i = 0; i < n; ++i )
push_back(d);
}
/** Copy constructor */
template <class Data>
inline smart_list<Data>::smart_list(const smart_list & that) : root_()
{
// Copy non-flagged nodes.
for ( const_iterator it = that.begin(); it.derefable(); ++it )
push_back(*it);
}
/** Constructor from a range */
template <class Data> template <class InputIterator>
inline smart_list<Data>::smart_list(const InputIterator & f, const InputIterator & l) :
root_()
{
for ( InputIterator it = f; f != l; ++f )
push_back(*it);
}
/** The size of the list, not counting flagged nodes. */
template <class Data>
inline typename smart_list<Data>::size_type smart_list<Data>::size() const
{
size_type count = 0;
// Look for nodes not flagged for deletion.
for ( const_iterator it = begin(); it.derefable(); ++it )
++count;
return count;
}
/** Swaps two lists. Done in constant time, with no iterator invalidation. */
template <class Data>
inline void smart_list<Data>::swap(smart_list & that)
{
smart_list temp_list;
// Swap, using splices instead of assignments.
temp_list.splice(temp_list.end(), that);
that.splice(that.end(), *this);
splice(end(), temp_list);
}
/** Insert a node before @a pos. */
template <class Data>
inline typename smart_list<Data>::iterator smart_list<Data>::insert
(const iterator & pos, const value_type & d)
{
return iterator(insert(pos.ptr_, d));
}
/** Insert a range before @a pos. */
template <class Data>
template <class InputIterator>
inline void smart_list<Data>::insert(const iterator & pos, const InputIterator & f,
const InputIterator & l)
{
for ( InputIterator it = f; it != l; ++it )
insert(pos.ptr_, *it);
}
/** Insert @a n copies of @a d at @a pos. */
template <class Data>
inline void smart_list<Data>::insert(const iterator & pos, size_type n,
const value_type & d)
{
for ( size_type i = 0; i < n; ++i )
insert(pos.ptr_, d);
}
/** Erase the node at @a pos. */
template <class Data>
inline typename smart_list<Data>::iterator smart_list<Data>::erase
(const iterator & pos)
{
flag(*pos.ptr_); // We know *pos cannot get deleted yet because pos points to it.
return iterator(pos.ptr_->next);
}
/** Erase a range of nodes. */
template <class Data>
inline typename smart_list<Data>::iterator smart_list<Data>::erase
(const iterator & start, const iterator & stop)
{
// Loop through all nodes from start to stop.
// We cannot rely on iterators because *stop might be flagged.
node_t * node_ptr = start.ptr_;
while ( node_ptr != stop.ptr_ && iterator::derefable(node_ptr) ) {
flag(*node_ptr);
node_ptr = check_erase(node_ptr);
}
// node_ptr is more reliable than stop because of the derefable() condition.
return iterator(node_ptr);
}
/**
* Resize the list.
* This does not count flagged nodes.
*/
template <class Data>
inline void smart_list<Data>::resize(size_type n, const value_type & d)
{
size_type count = 0;
iterator it = begin();
iterator _end = end();
// See how our current size compares to n.
// (Not calling size(), since that would do at least as much work.)
while ( it != _end && count < n ) {
++it;
++count;
}
if ( count < n )
// We need more nodes.
insert(_end, n - count, d);
else if ( it != _end )
// We need to remove nodes.
erase(it, _end);
}
/** Splice all of @a L into *this before @a pos. */
template <class Data>
inline void smart_list<Data>::splice(const iterator & pos, smart_list & L)
{
if ( L.root_.next != &L.root ) // if L has nodes to splice.
splice(pos.ptr_, *L.root_.next, *L.root_.prev);
}
/** Splice the node @a i points to (assumed from @a L) into *this before @a pos. */
template <class Data>
inline void smart_list<Data>::splice(const iterator & pos, smart_list & /*L*/,
const iterator & i)
{
if ( i.ptr_.derefable() )
splice(pos.ptr_, *i.ptr_, *i.ptr_);
}
/** Splice a range (assumed from @a L) into *this before @a pos. */
template <class Data>
inline void smart_list<Data>::splice(const iterator & pos, smart_list & /*L*/,
const iterator & f, const iterator & l)
{
// Abort on degenerate cases.
if ( !f.derefable() || f == l )
return;
// Splice from f to the node before l.
splice(pos.ptr_, *(f.ptr_), *(l.ptr_->prev));
}
/** Remove all nodes whose data equals @a value. */
template <class Data>
inline void smart_list<Data>::remove(const value_type & value)
{
// Look for elements to erase.
iterator it = begin(), _end = end();
while ( it != _end )
if ( *it == value )
it = erase(it);
else
++it;
}
/** Remove all nodes whose data satisfies @a p. */
template <class Data>
template <class Predicate>
inline void smart_list<Data>::remove_if(const Predicate & p)
{
// Look for elements to erase.
iterator it = begin(), _end = end();
while ( it != _end() )
if ( p(*it) )
it = erase(it);
else
++it;
}
/** Remove nodes equal (under @a p) to their immediate predecessor. */
template <class Data>
template <class BinaryPredicate>
inline void smart_list<Data>::unique(const BinaryPredicate p)
{
// Initial state.
iterator _end = end();
iterator cur = begin();
iterator prev = cur;
// Look at the second node, making the first node "previous".
if ( cur != _end )
++cur;
// Look for elements equal to their predecessors.
while ( cur != _end )
if ( p(*prev, *cur) )
// Duplicate. Remove it.
cur = erase(cur);
else
// Update the tracking of our previous element.
prev = cur++;
}
/**
* Merge @a L into *this, using @a p to determine order.
* If both lists are sorted under @a p, then so is the merged list.
* This is stable; equivalent nodes retain their order, with nodes of *this
* considered to come before elements of @a L.
*/
template <class Data>
template <class BinaryPredicate>
inline void smart_list<Data>::merge(smart_list & L, const BinaryPredicate & p)
{
// Merging a list into itself is a no-op, not a duplication.
if ( this == &L )
return;
node_t * dest = root_.next;
node_t * source = L.root_.next;
// Basic merge loop. Continues until one of the lists is depleted.
while ( iterator::derefable(dest) && iterator::derefable(source) ) {
if ( p(*(source->dat_ptr), *(dest->dat_ptr)) ) {
// We found something to merge in. See if we can merge multiple
// nodes at once.
node_t * end_merge = source->next;
while ( iterator::derefable(end_merge) && p(*(end_merge->dat_ptr), *(dest->dat_ptr)) )
end_merge = end_merge->next;
// Now end_merge is one past the nodes to merge.
// Move the nodes over.
splice(dest, *source, *(end_merge->prev));
// Advance the source.
source = end_merge;
}
else
// Advance the destination.
dest = dest->next;
}
// Append any remaining nodes from L.
if ( iterator::derefable(source) ) // hence, we made it to our end.
splice(&root_, *source, *(L.root_.prev));
}
/**
* Sort *this, using the order given by @a p.
* This is stable; equivalent nodes retain their order.
* This is an efficient sort, O(n lg n).
*/
template <class Data>
template <class BinaryPredicate>
inline void smart_list<Data>::sort(const BinaryPredicate & p)
{
// We'll leverage merge() to do a merge sort.
// First, get the real size of the list, so we can allocate memory before
// altering *this (exception safety).
size_type count = 0;
for ( node_t * node_ptr = root_.next; iterator::derefable(node_ptr); node_ptr = node_ptr->next )
++count;
// Abort on trivial cases.
if ( count < 2 )
return;
// Split *this into 1-length pieces.
std::vector<smart_list> sorter(count); // No memory allocations after this point.
for ( size_type i = 0; i < count; ++i )
sorter[i].splice(&(sorter[i].root_), *(root_.next), *(root_.next));
// At the start of each iteration, step will be the distance between lists
// containing data. (There will be O(lg n) iterations.)
for ( size_type step = 1; step < count; step *= 2 )
// Merge consecutive data-bearing lists. Summing over all iterations of
// this (inner) loop, there will be O(n) comparisons made.
for ( size_type i = 0; i + step < count; i += 2*step )
sorter[i].merge(sorter[i+step], p);
// The entire (sorted) list is now in the first element of the vector.
swap(sorter[0]);
}
/**
* Low-level insertion.
* The insertion will occur before @a pos. Pass &root_ to append to the list.
*/
template <class Data>
inline typename smart_list<Data>::node_t * smart_list<Data>::insert
(node_t * const pos, const value_type & d)
{
node_t * new_node = new node_t(d);
// The new node is essentially a 1-node list.
link(pos, *new_node, *new_node);
// Return a pointer to the new node.
return new_node;
}
/**
* Low-level erasure.
* The node will only be erased if its ref_count is 0. (So the caller must
* remove their reference before calling this.)
* @returns the next node in the list (could be a flagged node).
*/
template <class Data>
inline typename smart_list<Data>::node_t * smart_list<Data>::check_erase
(node_t * const pos)
{
// Sanity check.
if ( !iterator::derefable(pos) )
return nullptr;
// Remember our successor.
node_t * ret_val = pos->next;
// Can we actually erase?
if ( pos->ref_count == 0 ) {
// Disconnect *pos from the list.
unlink(*pos, *pos);
// Now we can delete.
delete pos;
}
return ret_val;
}
/**
* Assuming the nodes from @a begin_link to @a end_link are linked, they will
* be inserted into *this before @a pos. (Pass &root_ to append to the list.)
* Note that *end_link is included in the insert; this is not exactly
* analogous to a range of iterators.
* This is a constant-time operation; reference counts are not updated.
*/
template <class Data>
inline void smart_list<Data>::link(node_t * const pos, node_t & begin_link, node_t & end_link)
{
// Link the new nodes into the list.
begin_link.prev = pos->prev;
end_link.next = pos;
// Link the list to the new nodes.
begin_link.prev->next = &begin_link;
end_link.next->prev = &end_link;
}
/**
* Assuming @a begin_unlink and @a end_unlink are nodes from *this, with
* @a end_unlink a successor of @a begin_unlink, that chain of nodes will
* be removed from *this.
* Ownership of the removed chain is transferred to the caller, who becomes
* repsonsible for not leaving the nodes in limbo.
* This is a constant-time operation; reference counts are not updated.
*/
template <class Data>
inline void smart_list<Data>::unlink(node_t & begin_unlink, node_t & end_unlink)
{
// Disconnect the list from the nodes.
begin_unlink.prev->next = end_unlink.next;
end_unlink.next->prev = begin_unlink.prev;
// Disconnect the nodes from the list. This leaves the nodes in limbo.
end_unlink.next = nullptr;
begin_unlink.prev = nullptr;
}
/**
* Low-level splice of the chain from @a b to @a e (including b and e)
* to the spot before @a pos.
*/
template <class Data>
inline void smart_list<Data>::splice(node_t * const pos, node_t & b, node_t & e)
{
// Remove from the old list.
unlink(b, e);
// Splice into the new list.
link(pos, b, e);
}
/**
* Equality, if lists contain equal elements in the same order.
*/
template <class Data>
inline bool operator==(const smart_list<Data> & a, const smart_list<Data> & b)
{
typename smart_list<Data>::const_iterator end_a = a.end();
typename smart_list<Data>::const_iterator end_b = b.end();
typename smart_list<Data>::const_iterator it_a = a.begin();
typename smart_list<Data>::const_iterator it_b = b.begin();
for ( ; it_a != end_a && it_b != end_b; ++it_a, ++it_b )
if ( *it_a != *it_b )
// mismatch
return false;
// All comparisons were equal, so they are the same if we compared everything.
return it_a == end_a && it_b == end_b;
}
/**
* Lexicographical order.
*/
template <class Data>
inline bool operator<(const smart_list<Data> & a, const smart_list<Data> & b)
{
typename smart_list<Data>::const_iterator end_a = a.end();
typename smart_list<Data>::const_iterator end_b = b.end();
typename smart_list<Data>::const_iterator it_a = a.begin();
typename smart_list<Data>::const_iterator it_b = b.begin();
for ( ; it_a != end_a && it_b != end_b; ++it_a, ++it_b )
if ( *it_a < *it_b )
// a is less than b.
return true;
else if ( *it_b < *it_a )
// b is less than a.
return false;
// All comparisons were equal, so a is less than b if a is shorter.
return it_b != end_b;
}
/* ** smart_list::node_t ** */
template <class Data>
inline smart_list<Data>::node_t::~node_t()
{
// Some safety checks.
if ( dat_ptr == nullptr )
// Root node: make sure there are no lingering iterators to the list.
assert(next == this);
else {
// Normal node: make sure we are not still in a list.
assert(next == nullptr && prev == nullptr);
// Make sure no iterators point to us.
assert(ref_count == 0);
}
delete dat_ptr;
}
/* ** smart_list::iterator_base ** */
/**
* Advances our pointer to an unflagged node, possibly in the reverse direction.
* This will advance at least one step, and will not stop on a flagged node.
* In addition, this takes care of updating reference counts.
* (This is the code shared by increments and decrements.)
*/
template <class Data>
template <class Value, bool Reversed>
inline void smart_list<Data>::iterator_base<Value, Reversed>::advance(bool reverse)
{
node_t * old_ptr = ptr_;
// Guarantee a change.
inc(reverse);
// Skip as necessary.
skip_flagged(reverse);
// Update reference counts.
refer();
unref(old_ptr);
}
/** Make sure we are not pointing to a flagged node. */
template <class Data>
template <class Value, bool Reversed>
inline void smart_list<Data>::iterator_base<Value, Reversed>::skip_flagged
(bool reverse)
{
while ( derefable() && smart_list<Data>::flagged(*ptr_) )
inc(reverse);
}
/** Add a reference to that to which we point. */
template <class Data>
template <class Value, bool Reversed>
inline void smart_list<Data>::iterator_base<Value, Reversed>::refer() const
{
if ( derefable() )
ptr_->ref_count += 2;
}
/**
* Remove a reference.
* May delete old_ptr. So call this after updating ptr_ to a new value.
*/
template <class Data>
template <class Value, bool Reversed>
inline void smart_list<Data>::iterator_base<Value, Reversed>::unref(node_t * old_ptr)
{
if ( derefable(old_ptr) ) {
// Remove a reference.
old_ptr->ref_count -= 2;
smart_list<Data>::check_erase(old_ptr);
}
}
}// namespace utils