<commit_before>#include <algorithm>
#include <map>
#include <string>

#include "Profiling.h"
#include "IRMutator.h"
#include "IROperator.h"

namespace Halide {
namespace Internal {

namespace {
    const char kBufName[] = "ProfilerBuffer";
    const char kToplevel[] = "$total$";
    const char kOverhead[] = "$overhead$";
    const char kIgnore[] = "$ignore$";
    const char kIgnoreBuf[] = "$ignore_buf$";
}

int profiling_level() {
    char *trace = getenv("HL_PROFILE");
    return trace ? atoi(trace) : 0;
}

using std::map;
using std::string;
using std::vector;

class InjectProfiling : public IRMutator {
public:
    InjectProfiling(string func_name)
        : level(profiling_level()),
          func_name(sanitize(func_name)) {
    }

    Stmt inject(Stmt s) {
        if (level >= 1) {
            // Add calls to the nsec and timer at the start and end,
            // so that we can get an estimate of how long a single
            // tick of the profiling_timer is (since it may be dependent
            // on e.g. processor clock rate)
            {
                PushCallStack st(this, kToplevel, kToplevel);
                s = mutate(s);
            }
            s = add_count_and_ticks(kToplevel, kToplevel, s);
            s = add_nsec(kToplevel, kToplevel, s);

            // Note that this is tacked on to the front of the block, since it must come
            // before the calls to halide_current_time_ns.
            Expr begin_clock_call = Call::make(Int(32), "halide_start_clock", vector<Expr>(), Call::Extern);
            Stmt begin_clock = AssertStmt::make(begin_clock_call == 0, "Failed to start clock");
            s = Block::make(begin_clock, s);

            // Do a little calibration: make a loop that does a large number of calls to add_ticks
            // and measures the total time, so we can calculate the average overhead
            // and subtract it from the final results. (The "body" of this loop is just
            // a store of 0 to scratch that we expect to be optimized away.) This isn't a perfect
            // solution, but is much better than nothing.
            //
            // Note that we deliberately unroll a bit to minimize loop overhead, otherwise our
            // estimate will be too high.
            //
            // NOTE: we deliberately do this *after* measuring
            // the total, so this should *not* be included in "kToplevel".
            const int kIters = 1000000;
            const int kUnroll = 4;
            Stmt ticker_block = Stmt();
            for (int i = 0; i < kUnroll; i++) {
                ticker_block = Block::make(
                    add_ticks(kIgnore, kIgnore, Store::make(kIgnoreBuf, Cast::make(UInt(32), 0), 0)),
                        ticker_block);
            }

            Expr j = Variable::make(Int(32), "j");
            Stmt do_timings = For::make("j", 0, kIters, For::Serial, ticker_block);
            do_timings = add_ticks(kOverhead, kOverhead, do_timings);
            do_timings = add_delta("count", kOverhead, kOverhead, Cast::make(UInt(64), 0),
                Cast::make(UInt(64), kIters * kUnroll), do_timings);
            s = Block::make(s, do_timings);
            s = Allocate::make(kIgnoreBuf, UInt(32), vec(Expr(1)), const_true(), s);

            // Tack on code to print the counters.
            for (map<string, int>::const_iterator it = indices.begin(); it != indices.end(); ++it) {
                int idx = it->second;
                Expr val = Load::make(UInt(64), kBufName, idx, Buffer(), Parameter());
                Expr print_val = print(it->first, val);
                Stmt print_stmt = Evaluate::make(print_val);
                s = Block::make(s, print_stmt);
            }

            // Now that we know the final size, allocate the buffer and init to zero.
            Expr i = Variable::make(Int(32), "i");
            Stmt init = For::make("i", 0, (int)indices.size(), For::Serial,
                Store::make(kBufName, Cast::make(UInt(64), 0), i));
            s = Block::make(init, s);

            s = Allocate::make(kBufName, UInt(64), vec(Expr((int)indices.size())), const_true(), s);
        } else {
            s = mutate(s);
        }
        return s;
    }

private:
    using IRMutator::visit;

    const int level;
    const string func_name;
    map<string, int> indices;   // map name -> index in buffer.
    vector<string> call_stack;  // names of the nodes upstream

    class PushCallStack {
    public:
        InjectProfiling* ip;

        PushCallStack(InjectProfiling* ip, const string& op_type, const string& op_name) : ip(ip) {
            ip->call_stack.push_back(op_type + " " + op_name);
        }

        ~PushCallStack() {
            ip->call_stack.pop_back();
        }
    };

    // replace all spaces with '_'
    static string sanitize(const string& s) {
      string san = s;
      std::replace(san.begin(), san.end(), ' ', '_');
      return san;
    }

    Expr get_index(const string& s) {
        if (indices.find(s) == indices.end()) {
            int idx = indices.size();
            indices[s] = idx;
        }
        return indices[s];
    }

    Stmt add_count_and_ticks(const string& op_type, const string& op_name, Stmt s) {
        s = add_count(op_type, op_name, s);
        s = add_ticks(op_type, op_name, s);
        return s;
    }

    Stmt add_count(const string& op_type, const string& op_name, Stmt s) {
        return add_delta("count", op_type, op_name, Cast::make(UInt(64), 0), Cast::make(UInt(64), 1), s);
    }

    Stmt add_ticks(const string& op_type, const string& op_name, Stmt s) {
        Expr ticks = Call::make(UInt(64), Internal::Call::profiling_timer, vector<Expr>(), Call::Intrinsic);
        return add_delta("ticks", op_type, op_name, ticks, ticks, s);
    }

    Stmt add_nsec(const string& op_type, const string& op_name, Stmt s) {
        Expr nsec = Call::make(UInt(64), "halide_current_time_ns", std::vector<Expr>(), Call::Extern);
        return add_delta("nsec", op_type, op_name, nsec, nsec, s);
    }

    Stmt add_delta(const string& metric_name, const string& op_type, const string& op_name,
                   Expr begin_val, Expr end_val, Stmt s) {
        string parent_name_pair = call_stack.empty() ?
            "null null" :
            call_stack.back();
        string full_name = "halide_profiler " +
            metric_name + " " +
            func_name + " " +
            op_type + " " +
            sanitize(op_name) + " " +
            parent_name_pair;
        internal_assert(begin_val.type() == UInt(64));
        internal_assert(end_val.type() == UInt(64));
        Expr idx = get_index(full_name);

        string begin_var_name = "begin_" + full_name;
        // variable name doesn't matter at all, but de-spacing
        // makes the Stmt output easier to read
        std::replace(begin_var_name.begin(), begin_var_name.end(), ' ', '_');
        Expr begin_var = Variable::make(UInt(64), begin_var_name);

        Expr old_val = Load::make(UInt(64), kBufName, idx, Buffer(), Parameter());
        Expr delta_val = Sub::make(end_val, begin_var);
        Expr new_val = Add::make(old_val, delta_val);
        s = Block::make(s, Store::make(kBufName, new_val, idx));
        s = LetStmt::make(begin_var_name, begin_val, s);

        return s;
    }

    void visit(const Pipeline *op) {
        if (level >= 1) {
            Stmt produce, update, consume;
            {
                PushCallStack st(this, "produce", op->name);
                produce = mutate(op->produce);
            }
            {
                PushCallStack st(this, "update", op->name);
                update = op->update.defined() ? mutate(op->update) : Stmt();
            }
            {
                PushCallStack st(this, "consume", op->name);
                consume = mutate(op->consume);
            }

            produce = add_count_and_ticks("produce", op->name, produce);
            update = update.defined() ? add_count_and_ticks("update", op->name, update) : Stmt();
            consume = add_count_and_ticks("consume", op->name, consume);

            stmt = Pipeline::make(op->name, produce, update, consume);
        } else {
            IRMutator::visit(op);
        }
    }

    void visit(const For *op) {
        if (op->for_type == For::Parallel && level >= 1) {
            std::cerr << "Warning: The Halide profiler does not yet support "
                      << "parallel schedules. Not profiling inside the loop over "
                      << op->name << "\n";
            stmt = op;
        } else {
            PushCallStack st(this, "forloop", op->name);
            IRMutator::visit(op);
        }
        // We only instrument loops at profiling level 2 or higher
        if (level >= 2) {
            stmt = add_count_and_ticks("forloop", op->name, stmt);
        }
    }
};

Stmt inject_profiling(Stmt s, string name) {
    InjectProfiling profiling(name);
    s = profiling.inject(s);
    return s;
}

}
}
<commit_msg>Added possibility to set maximum loop level in the profiler<commit_after>#include <algorithm>
#include <map>
#include <string>
#include <limits>

#include "Profiling.h"
#include "IRMutator.h"
#include "IROperator.h"

namespace Halide {
namespace Internal {

namespace {
    const char kBufName[] = "ProfilerBuffer";
    const char kToplevel[] = "$total$";
    const char kOverhead[] = "$overhead$";
    const char kIgnore[] = "$ignore$";
    const char kIgnoreBuf[] = "$ignore_buf$";
}

int profiling_level() {
    char *trace = getenv("HL_PROFILE");
    return trace ? atoi(trace) : 0;
}

int profiling_loop_level() {
    char *loop_level = getenv("HL_PROFILE_LOOP_LEVEL");
    return loop_level ? atoi(loop_level) : std::numeric_limits<int>::max();
}

using std::map;
using std::string;
using std::vector;

class InjectProfiling : public IRMutator {
public:
    InjectProfiling(string func_name)
        : level(profiling_level()),
          maximum_loop_level(profiling_loop_level()),
          current_loop_level(0),
          func_name(sanitize(func_name)) {
    }

    Stmt inject(Stmt s) {
        if (level >= 1) {
            // Add calls to the nsec and timer at the start and end,
            // so that we can get an estimate of how long a single
            // tick of the profiling_timer is (since it may be dependent
            // on e.g. processor clock rate)
            {
                PushCallStack st(this, kToplevel, kToplevel);
                s = mutate(s);
            }
            s = add_count_and_ticks(kToplevel, kToplevel, s);
            s = add_nsec(kToplevel, kToplevel, s);

            // Note that this is tacked on to the front of the block, since it must come
            // before the calls to halide_current_time_ns.
            Expr begin_clock_call = Call::make(Int(32), "halide_start_clock", vector<Expr>(), Call::Extern);
            Stmt begin_clock = AssertStmt::make(begin_clock_call == 0, "Failed to start clock");
            s = Block::make(begin_clock, s);

            // Do a little calibration: make a loop that does a large number of calls to add_ticks
            // and measures the total time, so we can calculate the average overhead
            // and subtract it from the final results. (The "body" of this loop is just
            // a store of 0 to scratch that we expect to be optimized away.) This isn't a perfect
            // solution, but is much better than nothing.
            //
            // Note that we deliberately unroll a bit to minimize loop overhead, otherwise our
            // estimate will be too high.
            //
            // NOTE: we deliberately do this *after* measuring
            // the total, so this should *not* be included in "kToplevel".
            const int kIters = 1000000;
            const int kUnroll = 4;
            Stmt ticker_block = Stmt();
            for (int i = 0; i < kUnroll; i++) {
                ticker_block = Block::make(
                    add_ticks(kIgnore, kIgnore, Store::make(kIgnoreBuf, Cast::make(UInt(32), 0), 0)),
                        ticker_block);
            }

            Expr j = Variable::make(Int(32), "j");
            Stmt do_timings = For::make("j", 0, kIters, For::Serial, ticker_block);
            do_timings = add_ticks(kOverhead, kOverhead, do_timings);
            do_timings = add_delta("count", kOverhead, kOverhead, Cast::make(UInt(64), 0),
                Cast::make(UInt(64), kIters * kUnroll), do_timings);
            s = Block::make(s, do_timings);
            s = Allocate::make(kIgnoreBuf, UInt(32), vec(Expr(1)), const_true(), s);

            // Tack on code to print the counters.
            for (map<string, int>::const_iterator it = indices.begin(); it != indices.end(); ++it) {
                int idx = it->second;
                Expr val = Load::make(UInt(64), kBufName, idx, Buffer(), Parameter());
                Expr print_val = print(it->first, val);
                Stmt print_stmt = Evaluate::make(print_val);
                s = Block::make(s, print_stmt);
            }

            // Now that we know the final size, allocate the buffer and init to zero.
            Expr i = Variable::make(Int(32), "i");
            Stmt init = For::make("i", 0, (int)indices.size(), For::Serial,
                Store::make(kBufName, Cast::make(UInt(64), 0), i));
            s = Block::make(init, s);

            s = Allocate::make(kBufName, UInt(64), vec(Expr((int)indices.size())), const_true(), s);
        } else {
            s = mutate(s);
        }
        return s;
    }

private:
    using IRMutator::visit;

    const int level;
    const int maximum_loop_level;
    int current_loop_level;
    const string func_name;
    map<string, int> indices;   // map name -> index in buffer.
    vector<string> call_stack;  // names of the nodes upstream

    class PushCallStack {
    public:
        InjectProfiling* ip;

        PushCallStack(InjectProfiling* ip, const string& op_type, const string& op_name) : ip(ip) {
            ip->call_stack.push_back(op_type + " " + op_name);
        }

        ~PushCallStack() {
            ip->call_stack.pop_back();
        }
    };

    // replace all spaces with '_'
    static string sanitize(const string& s) {
      string san = s;
      std::replace(san.begin(), san.end(), ' ', '_');
      return san;
    }

    Expr get_index(const string& s) {
        if (indices.find(s) == indices.end()) {
            int idx = indices.size();
            indices[s] = idx;
        }
        return indices[s];
    }

    Stmt add_count_and_ticks(const string& op_type, const string& op_name, Stmt s) {
        s = add_count(op_type, op_name, s);
        s = add_ticks(op_type, op_name, s);
        return s;
    }

    Stmt add_count(const string& op_type, const string& op_name, Stmt s) {
        return add_delta("count", op_type, op_name, Cast::make(UInt(64), 0), Cast::make(UInt(64), 1), s);
    }

    Stmt add_ticks(const string& op_type, const string& op_name, Stmt s) {
        Expr ticks = Call::make(UInt(64), Internal::Call::profiling_timer, vector<Expr>(), Call::Intrinsic);
        return add_delta("ticks", op_type, op_name, ticks, ticks, s);
    }

    Stmt add_nsec(const string& op_type, const string& op_name, Stmt s) {
        Expr nsec = Call::make(UInt(64), "halide_current_time_ns", std::vector<Expr>(), Call::Extern);
        return add_delta("nsec", op_type, op_name, nsec, nsec, s);
    }

    Stmt add_delta(const string& metric_name, const string& op_type, const string& op_name,
                   Expr begin_val, Expr end_val, Stmt s) {
        string parent_name_pair = call_stack.empty() ?
            "null null" :
            call_stack.back();
        string full_name = "halide_profiler " +
            metric_name + " " +
            func_name + " " +
            op_type + " " +
            sanitize(op_name) + " " +
            parent_name_pair;
        internal_assert(begin_val.type() == UInt(64));
        internal_assert(end_val.type() == UInt(64));
        Expr idx = get_index(full_name);

        string begin_var_name = "begin_" + full_name;
        // variable name doesn't matter at all, but de-spacing
        // makes the Stmt output easier to read
        std::replace(begin_var_name.begin(), begin_var_name.end(), ' ', '_');
        Expr begin_var = Variable::make(UInt(64), begin_var_name);

        Expr old_val = Load::make(UInt(64), kBufName, idx, Buffer(), Parameter());
        Expr delta_val = Sub::make(end_val, begin_var);
        Expr new_val = Add::make(old_val, delta_val);
        s = Block::make(s, Store::make(kBufName, new_val, idx));
        s = LetStmt::make(begin_var_name, begin_val, s);

        return s;
    }

    void visit(const Pipeline *op) {
        if (level >= 1) {
            Stmt produce, update, consume;
            {
                PushCallStack st(this, "produce", op->name);
                produce = mutate(op->produce);
            }
            {
                PushCallStack st(this, "update", op->name);
                update = op->update.defined() ? mutate(op->update) : Stmt();
            }
            {
                PushCallStack st(this, "consume", op->name);
                consume = mutate(op->consume);
            }

            produce = add_count_and_ticks("produce", op->name, produce);
            update = update.defined() ? add_count_and_ticks("update", op->name, update) : Stmt();
            consume = add_count_and_ticks("consume", op->name, consume);

            stmt = Pipeline::make(op->name, produce, update, consume);
        } else {
            IRMutator::visit(op);
        }
    }

    void visit(const For *op) {
        current_loop_level++;
        if (op->for_type == For::Parallel && level >= 1) {
            std::cerr << "Warning: The Halide profiler does not yet support "
                      << "parallel schedules. Not profiling inside the loop over "
                      << op->name << "\n";
            stmt = op;
        } else {
            PushCallStack st(this, "forloop", op->name);
            IRMutator::visit(op);
        }
        // We only instrument loops at profiling level 2 or higher
        if (level >= 2 && current_loop_level <= maximum_loop_level) {
            stmt = add_count_and_ticks("forloop", op->name, stmt);
        }
        current_loop_level--;
    }
};

Stmt inject_profiling(Stmt s, string name) {
    InjectProfiling profiling(name);
    s = profiling.inject(s);
    return s;
}

}
}
<|endoftext|>


<commit_before>// Copyright 2010-2014 RethinkDB, all rights reserved.
#include "btree/depth_first_traversal.hpp"

#include "btree/internal_node.hpp"
#include "btree/operations.hpp"
#include "concurrency/interruptor.hpp"
#include "rdb_protocol/profile.hpp"

scoped_key_value_t::scoped_key_value_t(const btree_key_t *key,
                                       const void *value,
                                       movable_t<counted_buf_lock_and_read_t> &&buf)
    : key_(key), value_(value), buf_(std::move(buf)) {
    guarantee(buf_.has());
}

scoped_key_value_t::scoped_key_value_t(scoped_key_value_t &&movee)
    : key_(movee.key_),
      value_(movee.value_),
      buf_(std::move(movee.buf_)) {
    movee.key_ = NULL;
    movee.value_ = NULL;
}

scoped_key_value_t::~scoped_key_value_t() { }

buf_parent_t scoped_key_value_t::expose_buf() {
    guarantee(buf_.has());
    return buf_parent_t(&buf_->lock);
}

// Releases the hold on the buf_lock_t, after which key(), value(), and expose_buf()
// may not be used.
void scoped_key_value_t::reset() {
    buf_.reset();
}

/* Returns `true` if we reached the end of the subtree or range, and `false` if
`cb->handle_value()` returned `false`. */
continue_bool_t btree_depth_first_traversal(
        counted_t<counted_buf_lock_and_read_t> block,
        const key_range_t &range,
        depth_first_traversal_callback_t *cb,
        access_t access,
        direction_t direction,
        const btree_key_t *left_excl_or_null,
        const btree_key_t *right_incl,
        signal_t *interruptor);

continue_bool_t btree_depth_first_traversal(
        superblock_t *superblock,
        const key_range_t &range,
        depth_first_traversal_callback_t *cb,
        access_t access,
        direction_t direction,
        release_superblock_t release_superblock,
        signal_t *interruptor) {
    if (range.is_empty()) {
        return continue_bool_t::CONTINUE;
    }

    const btree_key_t *left_excl_or_null;
    store_key_t left_excl_buf(range.left);
    if (left_excl_buf.decrement()) {
        left_excl_or_null = left_excl_buf.btree_key();
    } else {
        left_excl_or_null = nullptr;
    }
    store_key_t right_incl_buf;
    if (range.right.unbounded) {
        right_incl_buf = store_key_t::max();
    } else {
        right_incl_buf = range.right.key();
        bool ok = right_incl_buf.decrement();
        guarantee(ok, "this is impossible because we checked range is not empty");
    }

    block_id_t root_block_id = superblock->get_root_block_id();
    if (root_block_id == NULL_BLOCK_ID || range.is_empty()) {
        if (release_superblock == release_superblock_t::RELEASE) {
            superblock->release();
        }
        return cb->handle_empty(left_excl_or_null, right_incl_buf.btree_key(),
            interruptor);
    } else {
        counted_t<counted_buf_lock_and_read_t> root_block;
        {
            // We know that `superblock` is already read-acquired because we call
            // get_block_id() above -- so `starter` won't measure time waiting for
            // the parent to become acquired.
            profile::starter_t starter("Acquire block for read.", cb->get_trace());
            root_block = make_counted<counted_buf_lock_and_read_t>(
                superblock->expose_buf(), root_block_id, access);
            if (release_superblock == release_superblock_t::RELEASE) {
                // Release the superblock ASAP because that's good.
                superblock->release();
            }
            // Wait for read acquisition of the root block, so that `starter`'s
            // profiling information is correct.
            wait_interruptible(root_block->lock.read_acq_signal(), interruptor);
        }

        return btree_depth_first_traversal(
            std::move(root_block), range, cb, access, direction,
            left_excl_or_null, right_incl_buf.btree_key(), interruptor);
    }
}

void get_child_key_range(
        const internal_node_t *inode,
        int child_index,
        /* `parent_*` describe the intersection of `inode`'s range and the range of keys
        that the traversal is interested in */
        const btree_key_t *parent_left_excl_or_null,
        const btree_key_t *parent_right_incl,
        const btree_key_t **left_excl_or_null_out,
        const btree_key_t **right_incl_out) {
    const btree_internal_pair *pair =
        internal_node::get_pair_by_index(inode, child_index);
    if (child_index != inode->npairs - 1) {
        rassert(child_index < inode->npairs - 1);
        if (btree_key_cmp(&pair->key, parent_right_incl) < 0) {
            *right_incl_out = &pair->key;
        } else {
            *right_incl_out = parent_right_incl;
        }
    } else {
        *right_incl_out = parent_right_incl;
    }

    if (child_index > 0) {
        const btree_internal_pair *left_neighbor =
            internal_node::get_pair_by_index(inode, child_index - 1);
        if (parent_left_excl_or_null == nullptr ||
                btree_key_cmp(&left_neighbor->key, parent_left_excl_or_null) > 0) {
            *left_excl_or_null_out = &left_neighbor->key;
        } else {
            *left_excl_or_null_out = parent_left_excl_or_null;
        }
    } else {
        *left_excl_or_null_out = parent_left_excl_or_null;
    }
}

continue_bool_t btree_depth_first_traversal(
        counted_t<counted_buf_lock_and_read_t> block,
        const key_range_t &range,
        depth_first_traversal_callback_t *cb,
        access_t access,
        direction_t direction,
        const btree_key_t *left_excl_or_null,
        const btree_key_t *right_incl,
        signal_t *interruptor) {
    bool skip;
    if (continue_bool_t::ABORT == cb->filter_range_ts(
            left_excl_or_null, right_incl, block->lock.get_recency(), interruptor,
            &skip)) {
        return continue_bool_t::ABORT;
    }
    if (skip) {
        return continue_bool_t::CONTINUE;
    }
    block->read.init(new buf_read_t(&block->lock));
    const node_t *node = static_cast<const node_t *>(block->read->get_data_read());
    if (node::is_internal(node)) {
        const internal_node_t *inode = reinterpret_cast<const internal_node_t *>(node);
        int start_index = internal_node::get_offset_index(inode, range.left.btree_key());
        int end_index;
        if (range.right.unbounded) {
            end_index = inode->npairs;
        } else {
            store_key_t r = range.right.key();
            r.decrement();
            end_index = internal_node::get_offset_index(inode, r.btree_key()) + 1;
        }
        for (int i = 0; i < end_index - start_index; ++i) {
            int true_index = (direction == FORWARD ? start_index + i : (end_index - 1) - i);
            const btree_internal_pair *pair = internal_node::get_pair_by_index(inode, true_index);

            // Get the child key range
            const btree_key_t *child_left_excl_or_null;
            const btree_key_t *child_right_incl;
            get_child_key_range(inode, true_index,
                                left_excl_or_null, right_incl,
                                &child_left_excl_or_null, &child_right_incl);

            if (continue_bool_t::ABORT == cb->filter_range(
                    child_left_excl_or_null, child_right_incl, interruptor, &skip)) {
                return continue_bool_t::ABORT;
            }
            if (!skip) {
                counted_t<counted_buf_lock_and_read_t> lock;
                {
                    profile::starter_t starter("Acquire block for read.", cb->get_trace());
                    lock = make_counted<counted_buf_lock_and_read_t>(
                        &block->lock, pair->lnode, access);
                    wait_interruptible(lock->lock.read_acq_signal(), interruptor);
                }
                if (continue_bool_t::ABORT == btree_depth_first_traversal(
                        std::move(lock), range, cb, access, direction,
                        child_left_excl_or_null, child_right_incl, interruptor)) {
                    return continue_bool_t::ABORT;
                }
            }
        }
        return continue_bool_t::CONTINUE;
    } else {
        if (continue_bool_t::ABORT == cb->handle_pre_leaf(
                block, left_excl_or_null, right_incl, interruptor, &skip)) {
            return continue_bool_t::ABORT;
        }
        if (skip) {
            return continue_bool_t::CONTINUE;
        }

        const leaf_node_t *lnode = reinterpret_cast<const leaf_node_t *>(node);
        const btree_key_t *key;

        if (direction == FORWARD) {
            for (auto it = leaf::inclusive_lower_bound(range.left.btree_key(), *lnode);
                 it != leaf::end(*lnode); ++it) {
                key = (*it).first;
                // range.right is exclusive
                if (!range.right.unbounded &&
                    btree_key_cmp(key, range.right.key().btree_key()) >= 0) {
                    break;
                }
                if (continue_bool_t::ABORT == cb->handle_pair(
                        scoped_key_value_t(
                            key, (*it).second,
                            movable_t<counted_buf_lock_and_read_t>(block)),
                        interruptor)) {
                    return continue_bool_t::ABORT;
                }
            }
        } else {
            leaf_node_t::reverse_iterator it;
            if (range.right.unbounded) {
                it = leaf::rbegin(*lnode);
            } else {
                it = leaf::exclusive_upper_bound(range.right.key().btree_key(), *lnode);
            }
            for (/* assignment above */; it != leaf::rend(*lnode); ++it) {
                key = (*it).first;

                // range.left is inclusive
                if (btree_key_cmp(key, range.left.btree_key()) < 0) {
                    break;
                }

                if (continue_bool_t::ABORT == cb->handle_pair(
                        scoped_key_value_t(
                            key, (*it).second,
                            movable_t<counted_buf_lock_and_read_t>(block)),
                        interruptor)) {
                    return continue_bool_t::ABORT;
                }
            }
        }
        return continue_bool_t::CONTINUE;
    }
}

<commit_msg>Remove spurious test.<commit_after>// Copyright 2010-2014 RethinkDB, all rights reserved.
#include "btree/depth_first_traversal.hpp"

#include "btree/internal_node.hpp"
#include "btree/operations.hpp"
#include "concurrency/interruptor.hpp"
#include "rdb_protocol/profile.hpp"

scoped_key_value_t::scoped_key_value_t(const btree_key_t *key,
                                       const void *value,
                                       movable_t<counted_buf_lock_and_read_t> &&buf)
    : key_(key), value_(value), buf_(std::move(buf)) {
    guarantee(buf_.has());
}

scoped_key_value_t::scoped_key_value_t(scoped_key_value_t &&movee)
    : key_(movee.key_),
      value_(movee.value_),
      buf_(std::move(movee.buf_)) {
    movee.key_ = NULL;
    movee.value_ = NULL;
}

scoped_key_value_t::~scoped_key_value_t() { }

buf_parent_t scoped_key_value_t::expose_buf() {
    guarantee(buf_.has());
    return buf_parent_t(&buf_->lock);
}

// Releases the hold on the buf_lock_t, after which key(), value(), and expose_buf()
// may not be used.
void scoped_key_value_t::reset() {
    buf_.reset();
}

/* Returns `true` if we reached the end of the subtree or range, and `false` if
`cb->handle_value()` returned `false`. */
continue_bool_t btree_depth_first_traversal(
        counted_t<counted_buf_lock_and_read_t> block,
        const key_range_t &range,
        depth_first_traversal_callback_t *cb,
        access_t access,
        direction_t direction,
        const btree_key_t *left_excl_or_null,
        const btree_key_t *right_incl,
        signal_t *interruptor);

continue_bool_t btree_depth_first_traversal(
        superblock_t *superblock,
        const key_range_t &range,
        depth_first_traversal_callback_t *cb,
        access_t access,
        direction_t direction,
        release_superblock_t release_superblock,
        signal_t *interruptor) {
    if (range.is_empty()) {
        return continue_bool_t::CONTINUE;
    }

    const btree_key_t *left_excl_or_null;
    store_key_t left_excl_buf(range.left);
    if (left_excl_buf.decrement()) {
        left_excl_or_null = left_excl_buf.btree_key();
    } else {
        left_excl_or_null = nullptr;
    }
    store_key_t right_incl_buf;
    if (range.right.unbounded) {
        right_incl_buf = store_key_t::max();
    } else {
        right_incl_buf = range.right.key();
        bool ok = right_incl_buf.decrement();
        guarantee(ok, "this is impossible because we checked range is not empty");
    }

    block_id_t root_block_id = superblock->get_root_block_id();
    if (root_block_id == NULL_BLOCK_ID) {
        if (release_superblock == release_superblock_t::RELEASE) {
            superblock->release();
        }
        return cb->handle_empty(left_excl_or_null, right_incl_buf.btree_key(),
            interruptor);
    } else {
        counted_t<counted_buf_lock_and_read_t> root_block;
        {
            // We know that `superblock` is already read-acquired because we call
            // get_block_id() above -- so `starter` won't measure time waiting for
            // the parent to become acquired.
            profile::starter_t starter("Acquire block for read.", cb->get_trace());
            root_block = make_counted<counted_buf_lock_and_read_t>(
                superblock->expose_buf(), root_block_id, access);
            if (release_superblock == release_superblock_t::RELEASE) {
                // Release the superblock ASAP because that's good.
                superblock->release();
            }
            // Wait for read acquisition of the root block, so that `starter`'s
            // profiling information is correct.
            wait_interruptible(root_block->lock.read_acq_signal(), interruptor);
        }

        return btree_depth_first_traversal(
            std::move(root_block), range, cb, access, direction,
            left_excl_or_null, right_incl_buf.btree_key(), interruptor);
    }
}

void get_child_key_range(
        const internal_node_t *inode,
        int child_index,
        /* `parent_*` describe the intersection of `inode`'s range and the range of keys
        that the traversal is interested in */
        const btree_key_t *parent_left_excl_or_null,
        const btree_key_t *parent_right_incl,
        const btree_key_t **left_excl_or_null_out,
        const btree_key_t **right_incl_out) {
    const btree_internal_pair *pair =
        internal_node::get_pair_by_index(inode, child_index);
    if (child_index != inode->npairs - 1) {
        rassert(child_index < inode->npairs - 1);
        if (btree_key_cmp(&pair->key, parent_right_incl) < 0) {
            *right_incl_out = &pair->key;
        } else {
            *right_incl_out = parent_right_incl;
        }
    } else {
        *right_incl_out = parent_right_incl;
    }

    if (child_index > 0) {
        const btree_internal_pair *left_neighbor =
            internal_node::get_pair_by_index(inode, child_index - 1);
        if (parent_left_excl_or_null == nullptr ||
                btree_key_cmp(&left_neighbor->key, parent_left_excl_or_null) > 0) {
            *left_excl_or_null_out = &left_neighbor->key;
        } else {
            *left_excl_or_null_out = parent_left_excl_or_null;
        }
    } else {
        *left_excl_or_null_out = parent_left_excl_or_null;
    }
}

continue_bool_t btree_depth_first_traversal(
        counted_t<counted_buf_lock_and_read_t> block,
        const key_range_t &range,
        depth_first_traversal_callback_t *cb,
        access_t access,
        direction_t direction,
        const btree_key_t *left_excl_or_null,
        const btree_key_t *right_incl,
        signal_t *interruptor) {
    bool skip;
    if (continue_bool_t::ABORT == cb->filter_range_ts(
            left_excl_or_null, right_incl, block->lock.get_recency(), interruptor,
            &skip)) {
        return continue_bool_t::ABORT;
    }
    if (skip) {
        return continue_bool_t::CONTINUE;
    }
    block->read.init(new buf_read_t(&block->lock));
    const node_t *node = static_cast<const node_t *>(block->read->get_data_read());
    if (node::is_internal(node)) {
        const internal_node_t *inode = reinterpret_cast<const internal_node_t *>(node);
        int start_index = internal_node::get_offset_index(inode, range.left.btree_key());
        int end_index;
        if (range.right.unbounded) {
            end_index = inode->npairs;
        } else {
            store_key_t r = range.right.key();
            r.decrement();
            end_index = internal_node::get_offset_index(inode, r.btree_key()) + 1;
        }
        for (int i = 0; i < end_index - start_index; ++i) {
            int true_index = (direction == FORWARD ? start_index + i : (end_index - 1) - i);
            const btree_internal_pair *pair = internal_node::get_pair_by_index(inode, true_index);

            // Get the child key range
            const btree_key_t *child_left_excl_or_null;
            const btree_key_t *child_right_incl;
            get_child_key_range(inode, true_index,
                                left_excl_or_null, right_incl,
                                &child_left_excl_or_null, &child_right_incl);

            if (continue_bool_t::ABORT == cb->filter_range(
                    child_left_excl_or_null, child_right_incl, interruptor, &skip)) {
                return continue_bool_t::ABORT;
            }
            if (!skip) {
                counted_t<counted_buf_lock_and_read_t> lock;
                {
                    profile::starter_t starter("Acquire block for read.", cb->get_trace());
                    lock = make_counted<counted_buf_lock_and_read_t>(
                        &block->lock, pair->lnode, access);
                    wait_interruptible(lock->lock.read_acq_signal(), interruptor);
                }
                if (continue_bool_t::ABORT == btree_depth_first_traversal(
                        std::move(lock), range, cb, access, direction,
                        child_left_excl_or_null, child_right_incl, interruptor)) {
                    return continue_bool_t::ABORT;
                }
            }
        }
        return continue_bool_t::CONTINUE;
    } else {
        if (continue_bool_t::ABORT == cb->handle_pre_leaf(
                block, left_excl_or_null, right_incl, interruptor, &skip)) {
            return continue_bool_t::ABORT;
        }
        if (skip) {
            return continue_bool_t::CONTINUE;
        }

        const leaf_node_t *lnode = reinterpret_cast<const leaf_node_t *>(node);
        const btree_key_t *key;

        if (direction == FORWARD) {
            for (auto it = leaf::inclusive_lower_bound(range.left.btree_key(), *lnode);
                 it != leaf::end(*lnode); ++it) {
                key = (*it).first;
                // range.right is exclusive
                if (!range.right.unbounded &&
                    btree_key_cmp(key, range.right.key().btree_key()) >= 0) {
                    break;
                }
                if (continue_bool_t::ABORT == cb->handle_pair(
                        scoped_key_value_t(
                            key, (*it).second,
                            movable_t<counted_buf_lock_and_read_t>(block)),
                        interruptor)) {
                    return continue_bool_t::ABORT;
                }
            }
        } else {
            leaf_node_t::reverse_iterator it;
            if (range.right.unbounded) {
                it = leaf::rbegin(*lnode);
            } else {
                it = leaf::exclusive_upper_bound(range.right.key().btree_key(), *lnode);
            }
            for (/* assignment above */; it != leaf::rend(*lnode); ++it) {
                key = (*it).first;

                // range.left is inclusive
                if (btree_key_cmp(key, range.left.btree_key()) < 0) {
                    break;
                }

                if (continue_bool_t::ABORT == cb->handle_pair(
                        scoped_key_value_t(
                            key, (*it).second,
                            movable_t<counted_buf_lock_and_read_t>(block)),
                        interruptor)) {
                    return continue_bool_t::ABORT;
                }
            }
        }
        return continue_bool_t::CONTINUE;
    }
}

<|endoftext|>


<commit_before>//
// Copyright (c) 2015 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//

// Image.cpp: Implements the egl::Image class representing the EGLimage object.

#include "libANGLE/Image.h"

#include "common/debug.h"
#include "common/utilities.h"
#include "libANGLE/Context.h"
#include "libANGLE/Renderbuffer.h"
#include "libANGLE/Texture.h"
#include "libANGLE/angletypes.h"
#include "libANGLE/formatutils.h"
#include "libANGLE/renderer/EGLImplFactory.h"
#include "libANGLE/renderer/ImageImpl.h"

namespace egl
{

namespace
{
gl::ImageIndex GetImageIndex(EGLenum eglTarget, const egl::AttributeMap &attribs)
{
    if (!IsTextureTarget(eglTarget))
    {
        return gl::ImageIndex();
    }

    gl::TextureTarget target = egl_gl::EGLImageTargetToTextureTarget(eglTarget);
    GLint mip                = static_cast<GLint>(attribs.get(EGL_GL_TEXTURE_LEVEL_KHR, 0));
    GLint layer              = static_cast<GLint>(attribs.get(EGL_GL_TEXTURE_ZOFFSET_KHR, 0));

    if (target == gl::TextureTarget::_3D)
    {
        return gl::ImageIndex::Make3D(mip, layer);
    }
    else
    {
        ASSERT(layer == 0);
        return gl::ImageIndex::MakeFromTarget(target, mip);
    }
}

const Display *DisplayFromContext(const gl::Context *context)
{
    return (context ? context->getDisplay() : nullptr);
}
}  // anonymous namespace

ImageSibling::ImageSibling() : FramebufferAttachmentObject(), mSourcesOf(), mTargetOf() {}

ImageSibling::~ImageSibling()
{
    // EGL images should hold a ref to their targets and siblings, a Texture should not be deletable
    // while it is attached to an EGL image.
    // Child class should orphan images before destruction.
    ASSERT(mSourcesOf.empty());
    ASSERT(mTargetOf.get() == nullptr);
}

void ImageSibling::setTargetImage(const gl::Context *context, egl::Image *imageTarget)
{
    ASSERT(imageTarget != nullptr);
    mTargetOf.set(DisplayFromContext(context), imageTarget);
    imageTarget->addTargetSibling(this);
}

angle::Result ImageSibling::orphanImages(const gl::Context *context)
{
    if (mTargetOf.get() != nullptr)
    {
        // Can't be a target and have sources.
        ASSERT(mSourcesOf.empty());

        ANGLE_TRY(mTargetOf->orphanSibling(context, this));
        mTargetOf.set(DisplayFromContext(context), nullptr);
    }
    else
    {
        for (Image *sourceImage : mSourcesOf)
        {
            ANGLE_TRY(sourceImage->orphanSibling(context, this));
        }
        mSourcesOf.clear();
    }

    return angle::Result::Continue;
}

void ImageSibling::addImageSource(egl::Image *imageSource)
{
    ASSERT(imageSource != nullptr);
    mSourcesOf.insert(imageSource);
}

void ImageSibling::removeImageSource(egl::Image *imageSource)
{
    ASSERT(mSourcesOf.find(imageSource) != mSourcesOf.end());
    mSourcesOf.erase(imageSource);
}

bool ImageSibling::isEGLImageTarget() const
{
    return (mTargetOf.get() != nullptr);
}

gl::InitState ImageSibling::sourceEGLImageInitState() const
{
    ASSERT(isEGLImageTarget());
    return mTargetOf->sourceInitState();
}

void ImageSibling::setSourceEGLImageInitState(gl::InitState initState) const
{
    ASSERT(isEGLImageTarget());
    mTargetOf->setInitState(initState);
}

bool ImageSibling::isRenderable(const gl::Context *context,
                                GLenum binding,
                                const gl::ImageIndex &imageIndex) const
{
    ASSERT(isEGLImageTarget());
    return mTargetOf->isRenderable(context);
}

void ImageSibling::notifySiblings(angle::SubjectMessage message)
{
    if (mTargetOf.get())
    {
        mTargetOf->notifySiblings(this, message);
    }
    for (Image *source : mSourcesOf)
    {
        source->notifySiblings(this, message);
    }
}

ExternalImageSibling::ExternalImageSibling(rx::EGLImplFactory *factory,
                                           const gl::Context *context,
                                           EGLenum target,
                                           EGLClientBuffer buffer,
                                           const AttributeMap &attribs)
    : mImplementation(factory->createExternalImageSibling(context, target, buffer, attribs))
{}

ExternalImageSibling::~ExternalImageSibling() = default;

void ExternalImageSibling::onDestroy(const egl::Display *display)
{
    mImplementation->onDestroy(display);
}

Error ExternalImageSibling::initialize(const egl::Display *display)
{
    return mImplementation->initialize(display);
}

gl::Extents ExternalImageSibling::getAttachmentSize(const gl::ImageIndex &imageIndex) const
{
    return mImplementation->getSize();
}

gl::Format ExternalImageSibling::getAttachmentFormat(GLenum binding,
                                                     const gl::ImageIndex &imageIndex) const
{
    return mImplementation->getFormat();
}

GLsizei ExternalImageSibling::getAttachmentSamples(const gl::ImageIndex &imageIndex) const
{
    return mImplementation->getSamples();
}

bool ExternalImageSibling::isRenderable(const gl::Context *context,
                                        GLenum binding,
                                        const gl::ImageIndex &imageIndex) const
{
    return mImplementation->isRenderable(context);
}

bool ExternalImageSibling::isTextureable(const gl::Context *context) const
{
    return mImplementation->isTexturable(context);
}

void ExternalImageSibling::onAttach(const gl::Context *context) {}

void ExternalImageSibling::onDetach(const gl::Context *context) {}

GLuint ExternalImageSibling::getId() const
{
    UNREACHABLE();
    return 0;
}

gl::InitState ExternalImageSibling::initState(const gl::ImageIndex &imageIndex) const
{
    return gl::InitState::Initialized;
}

void ExternalImageSibling::setInitState(const gl::ImageIndex &imageIndex, gl::InitState initState)
{}

rx::ExternalImageSiblingImpl *ExternalImageSibling::getImplementation() const
{
    return mImplementation.get();
}

rx::FramebufferAttachmentObjectImpl *ExternalImageSibling::getAttachmentImpl() const
{
    return mImplementation.get();
}

ImageState::ImageState(EGLenum target, ImageSibling *buffer, const AttributeMap &attribs)
    : label(nullptr),
      target(target),
      imageIndex(GetImageIndex(target, attribs)),
      source(buffer),
      targets(),
      format(GL_NONE),
      size(),
      samples(),
      sourceType(target)
{}

ImageState::~ImageState() {}

Image::Image(rx::EGLImplFactory *factory,
             const gl::Context *context,
             EGLenum target,
             ImageSibling *buffer,
             const AttributeMap &attribs)
    : mState(target, buffer, attribs),
      mImplementation(factory->createImage(mState, context, target, attribs)),
      mOrphanedAndNeedsInit(false)
{
    ASSERT(mImplementation != nullptr);
    ASSERT(buffer != nullptr);

    mState.source->addImageSource(this);
}

void Image::onDestroy(const Display *display)
{
    // All targets should hold a ref to the egl image and it should not be deleted until there are
    // no siblings left.
    ASSERT(mState.targets.empty());

    // Tell the source that it is no longer used by this image
    if (mState.source != nullptr)
    {
        mState.source->removeImageSource(this);

        // If the source is an external object, delete it
        if (IsExternalImageTarget(mState.sourceType))
        {
            ExternalImageSibling *externalSibling = rx::GetAs<ExternalImageSibling>(mState.source);
            externalSibling->onDestroy(display);
            delete externalSibling;
        }

        mState.source = nullptr;
    }

    mImplementation->onDestroy(display);
}

Image::~Image()
{
    SafeDelete(mImplementation);
}

void Image::setLabel(EGLLabelKHR label)
{
    mState.label = label;
}

EGLLabelKHR Image::getLabel() const
{
    return mState.label;
}

void Image::addTargetSibling(ImageSibling *sibling)
{
    mState.targets.insert(sibling);
}

angle::Result Image::orphanSibling(const gl::Context *context, ImageSibling *sibling)
{
    ASSERT(sibling != nullptr);

    // notify impl
    ANGLE_TRY(mImplementation->orphan(context, sibling));

    if (mState.source == sibling)
    {
        // The external source of an image cannot be redefined so it cannot be orpahend.
        ASSERT(!IsExternalImageTarget(mState.sourceType));

        // If the sibling is the source, it cannot be a target.
        ASSERT(mState.targets.find(sibling) == mState.targets.end());
        mState.source = nullptr;
        mOrphanedAndNeedsInit =
            (sibling->initState(mState.imageIndex) == gl::InitState::MayNeedInit);
    }
    else
    {
        mState.targets.erase(sibling);
    }

    return angle::Result::Continue;
}

const gl::Format &Image::getFormat() const
{
    return mState.format;
}

bool Image::isRenderable(const gl::Context *context) const
{
    if (IsTextureTarget(mState.sourceType))
    {
        return mState.format.info->textureAttachmentSupport(context->getClientVersion(),
                                                            context->getExtensions());
    }
    else if (IsRenderbufferTarget(mState.sourceType))
    {
        return mState.format.info->renderbufferSupport(context->getClientVersion(),
                                                       context->getExtensions());
    }
    else if (IsExternalImageTarget(mState.sourceType))
    {
        ASSERT(mState.source != nullptr);
        return mState.source->isRenderable(context, GL_NONE, gl::ImageIndex());
    }

    UNREACHABLE();
    return false;
}

bool Image::isTexturable(const gl::Context *context) const
{
    if (IsTextureTarget(mState.sourceType))
    {
        return mState.format.info->textureSupport(context->getClientVersion(),
                                                  context->getExtensions());
    }
    else if (IsRenderbufferTarget(mState.sourceType))
    {
        return true;
    }
    else if (IsExternalImageTarget(mState.sourceType))
    {
        ASSERT(mState.source != nullptr);
        return rx::GetAs<ExternalImageSibling>(mState.source)->isTextureable(context);
    }

    UNREACHABLE();
    return false;
}

size_t Image::getWidth() const
{
    return mState.size.width;
}

size_t Image::getHeight() const
{
    return mState.size.height;
}

size_t Image::getSamples() const
{
    return mState.samples;
}

rx::ImageImpl *Image::getImplementation() const
{
    return mImplementation;
}

Error Image::initialize(const Display *display)
{
    if (IsExternalImageTarget(mState.sourceType))
    {
        ANGLE_TRY(rx::GetAs<ExternalImageSibling>(mState.source)->initialize(display));
    }

    mState.format  = mState.source->getAttachmentFormat(GL_NONE, mState.imageIndex);
    mState.size    = mState.source->getAttachmentSize(mState.imageIndex);
    mState.samples = mState.source->getAttachmentSamples(mState.imageIndex);

    return mImplementation->initialize(display);
}

bool Image::orphaned() const
{
    return (mState.source == nullptr);
}

gl::InitState Image::sourceInitState() const
{
    if (orphaned())
    {
        return mOrphanedAndNeedsInit ? gl::InitState::MayNeedInit : gl::InitState::Initialized;
    }

    return mState.source->initState(mState.imageIndex);
}

void Image::setInitState(gl::InitState initState)
{
    if (orphaned())
    {
        mOrphanedAndNeedsInit = false;
    }

    return mState.source->setInitState(mState.imageIndex, initState);
}

void Image::notifySiblings(const ImageSibling *notifier, angle::SubjectMessage message)
{
    if (mState.source && mState.source != notifier)
    {
        mState.source->onSubjectStateChange(rx::kTextureImageSiblingMessageIndex, message);
    }

    for (ImageSibling *target : mState.targets)
    {
        if (target != notifier)
        {
            target->onSubjectStateChange(rx::kTextureImageSiblingMessageIndex, message);
        }
    }
}

}  // namespace egl
<commit_msg>Call ImageImpl::destroy before destroying the image source.<commit_after>//
// Copyright (c) 2015 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//

// Image.cpp: Implements the egl::Image class representing the EGLimage object.

#include "libANGLE/Image.h"

#include "common/debug.h"
#include "common/utilities.h"
#include "libANGLE/Context.h"
#include "libANGLE/Renderbuffer.h"
#include "libANGLE/Texture.h"
#include "libANGLE/angletypes.h"
#include "libANGLE/formatutils.h"
#include "libANGLE/renderer/EGLImplFactory.h"
#include "libANGLE/renderer/ImageImpl.h"

namespace egl
{

namespace
{
gl::ImageIndex GetImageIndex(EGLenum eglTarget, const egl::AttributeMap &attribs)
{
    if (!IsTextureTarget(eglTarget))
    {
        return gl::ImageIndex();
    }

    gl::TextureTarget target = egl_gl::EGLImageTargetToTextureTarget(eglTarget);
    GLint mip                = static_cast<GLint>(attribs.get(EGL_GL_TEXTURE_LEVEL_KHR, 0));
    GLint layer              = static_cast<GLint>(attribs.get(EGL_GL_TEXTURE_ZOFFSET_KHR, 0));

    if (target == gl::TextureTarget::_3D)
    {
        return gl::ImageIndex::Make3D(mip, layer);
    }
    else
    {
        ASSERT(layer == 0);
        return gl::ImageIndex::MakeFromTarget(target, mip);
    }
}

const Display *DisplayFromContext(const gl::Context *context)
{
    return (context ? context->getDisplay() : nullptr);
}
}  // anonymous namespace

ImageSibling::ImageSibling() : FramebufferAttachmentObject(), mSourcesOf(), mTargetOf() {}

ImageSibling::~ImageSibling()
{
    // EGL images should hold a ref to their targets and siblings, a Texture should not be deletable
    // while it is attached to an EGL image.
    // Child class should orphan images before destruction.
    ASSERT(mSourcesOf.empty());
    ASSERT(mTargetOf.get() == nullptr);
}

void ImageSibling::setTargetImage(const gl::Context *context, egl::Image *imageTarget)
{
    ASSERT(imageTarget != nullptr);
    mTargetOf.set(DisplayFromContext(context), imageTarget);
    imageTarget->addTargetSibling(this);
}

angle::Result ImageSibling::orphanImages(const gl::Context *context)
{
    if (mTargetOf.get() != nullptr)
    {
        // Can't be a target and have sources.
        ASSERT(mSourcesOf.empty());

        ANGLE_TRY(mTargetOf->orphanSibling(context, this));
        mTargetOf.set(DisplayFromContext(context), nullptr);
    }
    else
    {
        for (Image *sourceImage : mSourcesOf)
        {
            ANGLE_TRY(sourceImage->orphanSibling(context, this));
        }
        mSourcesOf.clear();
    }

    return angle::Result::Continue;
}

void ImageSibling::addImageSource(egl::Image *imageSource)
{
    ASSERT(imageSource != nullptr);
    mSourcesOf.insert(imageSource);
}

void ImageSibling::removeImageSource(egl::Image *imageSource)
{
    ASSERT(mSourcesOf.find(imageSource) != mSourcesOf.end());
    mSourcesOf.erase(imageSource);
}

bool ImageSibling::isEGLImageTarget() const
{
    return (mTargetOf.get() != nullptr);
}

gl::InitState ImageSibling::sourceEGLImageInitState() const
{
    ASSERT(isEGLImageTarget());
    return mTargetOf->sourceInitState();
}

void ImageSibling::setSourceEGLImageInitState(gl::InitState initState) const
{
    ASSERT(isEGLImageTarget());
    mTargetOf->setInitState(initState);
}

bool ImageSibling::isRenderable(const gl::Context *context,
                                GLenum binding,
                                const gl::ImageIndex &imageIndex) const
{
    ASSERT(isEGLImageTarget());
    return mTargetOf->isRenderable(context);
}

void ImageSibling::notifySiblings(angle::SubjectMessage message)
{
    if (mTargetOf.get())
    {
        mTargetOf->notifySiblings(this, message);
    }
    for (Image *source : mSourcesOf)
    {
        source->notifySiblings(this, message);
    }
}

ExternalImageSibling::ExternalImageSibling(rx::EGLImplFactory *factory,
                                           const gl::Context *context,
                                           EGLenum target,
                                           EGLClientBuffer buffer,
                                           const AttributeMap &attribs)
    : mImplementation(factory->createExternalImageSibling(context, target, buffer, attribs))
{}

ExternalImageSibling::~ExternalImageSibling() = default;

void ExternalImageSibling::onDestroy(const egl::Display *display)
{
    mImplementation->onDestroy(display);
}

Error ExternalImageSibling::initialize(const egl::Display *display)
{
    return mImplementation->initialize(display);
}

gl::Extents ExternalImageSibling::getAttachmentSize(const gl::ImageIndex &imageIndex) const
{
    return mImplementation->getSize();
}

gl::Format ExternalImageSibling::getAttachmentFormat(GLenum binding,
                                                     const gl::ImageIndex &imageIndex) const
{
    return mImplementation->getFormat();
}

GLsizei ExternalImageSibling::getAttachmentSamples(const gl::ImageIndex &imageIndex) const
{
    return mImplementation->getSamples();
}

bool ExternalImageSibling::isRenderable(const gl::Context *context,
                                        GLenum binding,
                                        const gl::ImageIndex &imageIndex) const
{
    return mImplementation->isRenderable(context);
}

bool ExternalImageSibling::isTextureable(const gl::Context *context) const
{
    return mImplementation->isTexturable(context);
}

void ExternalImageSibling::onAttach(const gl::Context *context) {}

void ExternalImageSibling::onDetach(const gl::Context *context) {}

GLuint ExternalImageSibling::getId() const
{
    UNREACHABLE();
    return 0;
}

gl::InitState ExternalImageSibling::initState(const gl::ImageIndex &imageIndex) const
{
    return gl::InitState::Initialized;
}

void ExternalImageSibling::setInitState(const gl::ImageIndex &imageIndex, gl::InitState initState)
{}

rx::ExternalImageSiblingImpl *ExternalImageSibling::getImplementation() const
{
    return mImplementation.get();
}

rx::FramebufferAttachmentObjectImpl *ExternalImageSibling::getAttachmentImpl() const
{
    return mImplementation.get();
}

ImageState::ImageState(EGLenum target, ImageSibling *buffer, const AttributeMap &attribs)
    : label(nullptr),
      target(target),
      imageIndex(GetImageIndex(target, attribs)),
      source(buffer),
      targets(),
      format(GL_NONE),
      size(),
      samples(),
      sourceType(target)
{}

ImageState::~ImageState() {}

Image::Image(rx::EGLImplFactory *factory,
             const gl::Context *context,
             EGLenum target,
             ImageSibling *buffer,
             const AttributeMap &attribs)
    : mState(target, buffer, attribs),
      mImplementation(factory->createImage(mState, context, target, attribs)),
      mOrphanedAndNeedsInit(false)
{
    ASSERT(mImplementation != nullptr);
    ASSERT(buffer != nullptr);

    mState.source->addImageSource(this);
}

void Image::onDestroy(const Display *display)
{
    // All targets should hold a ref to the egl image and it should not be deleted until there are
    // no siblings left.
    ASSERT(mState.targets.empty());

    // Make sure the implementation gets a chance to clean up before we delete the source.
    mImplementation->onDestroy(display);

    // Tell the source that it is no longer used by this image
    if (mState.source != nullptr)
    {
        mState.source->removeImageSource(this);

        // If the source is an external object, delete it
        if (IsExternalImageTarget(mState.sourceType))
        {
            ExternalImageSibling *externalSibling = rx::GetAs<ExternalImageSibling>(mState.source);
            externalSibling->onDestroy(display);
            delete externalSibling;
        }

        mState.source = nullptr;
    }
}

Image::~Image()
{
    SafeDelete(mImplementation);
}

void Image::setLabel(EGLLabelKHR label)
{
    mState.label = label;
}

EGLLabelKHR Image::getLabel() const
{
    return mState.label;
}

void Image::addTargetSibling(ImageSibling *sibling)
{
    mState.targets.insert(sibling);
}

angle::Result Image::orphanSibling(const gl::Context *context, ImageSibling *sibling)
{
    ASSERT(sibling != nullptr);

    // notify impl
    ANGLE_TRY(mImplementation->orphan(context, sibling));

    if (mState.source == sibling)
    {
        // The external source of an image cannot be redefined so it cannot be orpahend.
        ASSERT(!IsExternalImageTarget(mState.sourceType));

        // If the sibling is the source, it cannot be a target.
        ASSERT(mState.targets.find(sibling) == mState.targets.end());
        mState.source = nullptr;
        mOrphanedAndNeedsInit =
            (sibling->initState(mState.imageIndex) == gl::InitState::MayNeedInit);
    }
    else
    {
        mState.targets.erase(sibling);
    }

    return angle::Result::Continue;
}

const gl::Format &Image::getFormat() const
{
    return mState.format;
}

bool Image::isRenderable(const gl::Context *context) const
{
    if (IsTextureTarget(mState.sourceType))
    {
        return mState.format.info->textureAttachmentSupport(context->getClientVersion(),
                                                            context->getExtensions());
    }
    else if (IsRenderbufferTarget(mState.sourceType))
    {
        return mState.format.info->renderbufferSupport(context->getClientVersion(),
                                                       context->getExtensions());
    }
    else if (IsExternalImageTarget(mState.sourceType))
    {
        ASSERT(mState.source != nullptr);
        return mState.source->isRenderable(context, GL_NONE, gl::ImageIndex());
    }

    UNREACHABLE();
    return false;
}

bool Image::isTexturable(const gl::Context *context) const
{
    if (IsTextureTarget(mState.sourceType))
    {
        return mState.format.info->textureSupport(context->getClientVersion(),
                                                  context->getExtensions());
    }
    else if (IsRenderbufferTarget(mState.sourceType))
    {
        return true;
    }
    else if (IsExternalImageTarget(mState.sourceType))
    {
        ASSERT(mState.source != nullptr);
        return rx::GetAs<ExternalImageSibling>(mState.source)->isTextureable(context);
    }

    UNREACHABLE();
    return false;
}

size_t Image::getWidth() const
{
    return mState.size.width;
}

size_t Image::getHeight() const
{
    return mState.size.height;
}

size_t Image::getSamples() const
{
    return mState.samples;
}

rx::ImageImpl *Image::getImplementation() const
{
    return mImplementation;
}

Error Image::initialize(const Display *display)
{
    if (IsExternalImageTarget(mState.sourceType))
    {
        ANGLE_TRY(rx::GetAs<ExternalImageSibling>(mState.source)->initialize(display));
    }

    mState.format  = mState.source->getAttachmentFormat(GL_NONE, mState.imageIndex);
    mState.size    = mState.source->getAttachmentSize(mState.imageIndex);
    mState.samples = mState.source->getAttachmentSamples(mState.imageIndex);

    return mImplementation->initialize(display);
}

bool Image::orphaned() const
{
    return (mState.source == nullptr);
}

gl::InitState Image::sourceInitState() const
{
    if (orphaned())
    {
        return mOrphanedAndNeedsInit ? gl::InitState::MayNeedInit : gl::InitState::Initialized;
    }

    return mState.source->initState(mState.imageIndex);
}

void Image::setInitState(gl::InitState initState)
{
    if (orphaned())
    {
        mOrphanedAndNeedsInit = false;
    }

    return mState.source->setInitState(mState.imageIndex, initState);
}

void Image::notifySiblings(const ImageSibling *notifier, angle::SubjectMessage message)
{
    if (mState.source && mState.source != notifier)
    {
        mState.source->onSubjectStateChange(rx::kTextureImageSiblingMessageIndex, message);
    }

    for (ImageSibling *target : mState.targets)
    {
        if (target != notifier)
        {
            target->onSubjectStateChange(rx::kTextureImageSiblingMessageIndex, message);
        }
    }
}

}  // namespace egl
<|endoftext|>


<commit_before>/* bzflag
* Copyright (c) 1993 - 2006 Tim Riker
*
* This package is free software;  you can redistribute it and/or
* modify it under the terms of the license found in the file
* named COPYING that should have accompanied this file.
*
* THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*/

/* interface header */
#include "bzfsPlayerStateVerify.h"

/* system implementation headers */
#include <iostream>
#include <assert.h>

/* common implementation headers */
#include "StateDatabase.h"
#include "BZDBCache.h"

bool doSpeedChecks ( GameKeeper::Player *playerData, PlayerState &state )
{
	// Speed problems occur around flag drops, so don't check for
	// a short period of time after player drops a flag. Currently
	// 2 second, adjust as needed.
	if (playerData->player.isFlagTransitSafe())
	{
		// we'll be checking against the player's flag type
		int pFlag = playerData->player.getFlag();

		// check for highspeed cheat; if inertia is enabled, skip test for now
		if (BZDB.eval(StateDatabase::BZDB_INERTIALINEAR) == 0.0f)
		{
			// Doesn't account for going fast backwards, or jumping/falling
			float curPlanarSpeedSqr = state.velocity[0]*state.velocity[0] + state.velocity[1]*state.velocity[1];

			float maxPlanarSpeed = BZDBCache::tankSpeed;

			bool logOnly = false;

			// if tank is not driving cannot be sure it didn't toss
			// (V) in flight

			// if tank is not alive cannot be sure it didn't just toss
			// (V)
			if (pFlag >= 0)
			{
				FlagInfo &flag = *FlagInfo::get(pFlag);
				if (flag.flag.type == Flags::Velocity)
					maxPlanarSpeed *= BZDB.eval(StateDatabase::BZDB_VELOCITYAD);
				else if (flag.flag.type == Flags::Thief)
					maxPlanarSpeed *= BZDB.eval(StateDatabase::BZDB_THIEFVELAD);
				else if (flag.flag.type == Flags::Agility)
					maxPlanarSpeed *= BZDB.eval(StateDatabase::BZDB_AGILITYADVEL);
				else if ((flag.flag.type == Flags::Burrow) && (playerData->lastState.pos[2] == state.pos[2]) && (playerData->lastState.velocity[2] == state.velocity[2]) && (state.pos[2] <= BZDB.eval(StateDatabase::BZDB_BURROWDEPTH)))
				{	// if we have burrow and are not actively burrowing
					// You may have burrow and still be above ground. Must
					// check z in ground!!
					maxPlanarSpeed *= BZDB.eval(StateDatabase::BZDB_BURROWSPEEDAD);
				}
			}

			float maxPlanarSpeedSqr = maxPlanarSpeed * maxPlanarSpeed;

			// If player is moving vertically, or not alive the speed checks
			// seem to be problematic. If this happens, just log it for now,
			// but don't actually kick
			if ((playerData->lastState.pos[2] != state.pos[2]) ||  (playerData->lastState.velocity[2] != state.velocity[2]) || ((state.status & PlayerState::Alive) == 0))
				logOnly = true;

			// allow a 10% tolerance level for speed if -speedtol is not sane
			if (cheatProtectionOptions.doSpeedChecks)
			{
				float realtol = 1.1f;
				if (speedTolerance > 1.0f)
					realtol = speedTolerance;
				maxPlanarSpeedSqr *= realtol;

				if (curPlanarSpeedSqr > maxPlanarSpeedSqr)
				{
					if (logOnly)
						DEBUG1("Logging Player %s [%d] tank too fast (tank: %f, allowed: %f){Dead or v[z] != 0}\n", playerData->player.getCallSign(), playerData->getIndex(), sqrt(curPlanarSpeedSqr), sqrt(maxPlanarSpeedSqr));
					else
					{
						DEBUG1("Kicking Player %s [%d] tank too fast (tank: %f, allowed: %f)\n", playerData->player.getCallSign(), playerData->getIndex(), sqrt(curPlanarSpeedSqr), sqrt(maxPlanarSpeedSqr));
						sendMessage(ServerPlayer, playerData->getIndex(), "Autokick: Player tank is moving too fast.");
						removePlayer(playerData->getIndex(), "too fast");
					}
					return false;
				}
			}
		}
	}
	return true;
}

bool doBoundsChecks ( GameKeeper::Player *playerData, PlayerState &state )
{
	// make sure the player is still in the map
	// test all the map bounds + some fudge factor, just in case
	static const float positionFudge = 10.0f; /* linear distance */
	bool InBounds = true;
	float worldSize = BZDBCache::worldSize;

	if ( (state.pos[1] >= worldSize*0.5f + positionFudge) || (state.pos[1] <= -worldSize*0.5f - positionFudge))
	{
		std::cout << "y position (" << state.pos[1] << ") is out of bounds (" << worldSize * 0.5f << " + " << positionFudge << ")" << std::endl;
		InBounds = false;
	} else if ( (state.pos[0] >= worldSize*0.5f + positionFudge) || (state.pos[0] <= -worldSize*0.5f - positionFudge))
	{
		std::cout << "x position (" << state.pos[0] << ") is out of bounds (" << worldSize * 0.5f << " + " << positionFudge << ")" << std::endl;
		InBounds = false;
	}

	static const float burrowFudge = 1.0f; /* linear distance */
	if (state.pos[2]<BZDB.eval(StateDatabase::BZDB_BURROWDEPTH) - burrowFudge)
	{
		std::cout << "z depth (" << state.pos[2] << ") is less than burrow depth (" << BZDB.eval(StateDatabase::BZDB_BURROWDEPTH) << " - " << burrowFudge << ")" << std::endl;
		InBounds = false;
	}

	// kick em cus they are most likely cheating or using a buggy client
	if (!InBounds)
	{
		DEBUG1("Kicking Player %s [%d] Out of map bounds at position (%.2f,%.2f,%.2f)\n", playerData->player.getCallSign(), playerData->getIndex(), state.pos[0], state.pos[1], state.pos[2]);
		sendMessage(ServerPlayer, playerData->getIndex(), "Autokick: Player location was outside the playing area.");
		removePlayer(playerData->getIndex(), "Out of map bounds", true);
		return false;
	}
	return true;
}

bool doPauseChecks ( GameKeeper::Player *playerData, PlayerState &state )
{
	// make sure the player only pauses after the waiting time for pause is over
	// we need some inaccuracy here that is computed using pauseRequestTime and pauseRequestLag
	// lag will cause all players to pause sooner than 5 seconds
	if (state.status & PlayerState::Paused) {
		TimeKeeper pauseDelay = playerData->player.pauseRequestTime;
		pauseDelay += (- playerData->player.pauseRequestLag / 1000.0);

		if ((TimeKeeper::getCurrent() - pauseDelay) < 5.0f
			&& (playerData->player.pauseRequestTime - TimeKeeper::getNullTime() != 0)) {
			// we have one of those players all love
			DEBUG1("Kicking Player %s [%d] Paused too fast!\n", playerData->player.getCallSign(),
			playerData->getIndex());
			sendMessage(ServerPlayer, playerData->getIndex(), "Autokick: Player paused too fast.");
			removePlayer(playerData->getIndex(), "Paused too fast");
			return false;
		} else {
			pausePlayer(playerData->player.getPlayerIndex(), true);
		}

		// kick the players when they do not pause within allowed situations
		if ((state.status & PlayerState::InBuilding) || (state.status & PlayerState::PhantomZoned)
			|| (state.status & PlayerState::Falling) || (state.status & PlayerState::Alive) == false) {
			// the player did pause while being a wall or in air
			DEBUG1("Kicking Player %s [%d] Paused in unallowed state!\n", playerData->player.getCallSign(),
			playerData->getIndex());
			sendMessage(ServerPlayer, playerData->getIndex(), "Autokick: Player paused in unallowed state.");
			removePlayer(playerData->getIndex(), "Paused in unallowed state");
			return false;
		}
	}

	return true;
}

bool doHeightChecks ( GameKeeper::Player *playerData, PlayerState &state )
{
	float wingsGravity = BZDB.eval(StateDatabase::BZDB_WINGSGRAVITY);
	float normalGravity = BZDBCache::gravity;

	// All tanks with wings are flying away or they do without a flag
	if (((wingsGravity >= 0.0f) && (normalGravity >= 0.0f)) || (normalGravity >= 0.0f)) {
		return true;
	}

	float normalMaxHeight = 0.0f;
	float wingsMaxHeight = 0.0f;

	if (!(state.status & PlayerState::Falling) || (playerData->player.allowedHeightAtJumpStart < 0)) {
		playerData->player.jumpStartPos = state.pos[2];
	}

	static const float heightFudge = 1.10f; /* 10% */

	int pFlag = playerData->player.getFlag();
	bool hasWings = false;

	if (pFlag >= 0)
	{
		FlagInfo &flag = *FlagInfo::get(pFlag);
		if (flag.flag.type == Flags::Wings) {
			hasWings = true;
		}
	}

	if (hasWings) {
		wingsMaxHeight = BZDB.eval(StateDatabase::BZDB_WINGSJUMPVELOCITY);
		wingsMaxHeight *= wingsMaxHeight;
		wingsMaxHeight *= (1 + BZDB.eval(StateDatabase::BZDB_WINGSJUMPCOUNT));
		wingsMaxHeight /= (-wingsGravity * 2.0f);
	} else {
		normalMaxHeight = BZDB.eval(StateDatabase::BZDB_JUMPVELOCITY);
		normalMaxHeight *= normalMaxHeight;
		normalMaxHeight /= (-normalGravity * 2.0f);
	}

	// _wingsGravity is set to a positive value
	if (wingsMaxHeight < 0) {
		wingsMaxHeight = MAXFLOAT;
	}

	// use either the wings height or the usual height
	float maxHeight;
	if (wingsMaxHeight > normalMaxHeight) {
		maxHeight = wingsMaxHeight;
	} else {
		maxHeight = normalMaxHeight;
	}

	if (playerData->player.allowedHeightAtJumpStart > 0.0f && !(state.status & PlayerState::Falling)) {
		playerData->player.allowedHeightAtJumpStart = 0.0f;
	}

	if ((playerData->player.allowedHeightAtJumpStart <= 0.0f) && !(state.status & PlayerState::Falling)) {
		playerData->player.allowedHeightAtJumpStart = maxHeight;
	}

	// Don't kick players if the world param is still changing
	if (worldStateChanging()) {
		playerData->player.allowedHeightAtJumpStart = MAXFLOAT;
	}

	// currently we don't know how height the teleporter is so skip the check
	if ((state.status & PlayerState::Falling) && (state.status & PlayerState::Teleporting)) {
		playerData->player.allowedHeightAtJumpStart = MAXFLOAT;
	}

	// if one of the values changed while the player was in air
	// use the higher allowed one in case we did not get a
	// new update from him yet
	if (playerData->player.allowedHeightAtJumpStart > maxHeight) {
		maxHeight = playerData->player.allowedHeightAtJumpStart;
	}

	if ((normalGravity < -25.0f) && !(playerData->player.allowedHeightAtJumpStart == MAXFLOAT)) {
		maxHeight += 1.0f;
	}

	// final adjustments
	if (playerData->player.allowedHeightAtJumpStart == MAXFLOAT) {
		maxHeight = MAXFLOAT;
	} else {
		maxHeight *= heightFudge;
		maxHeight += playerData->player.jumpStartPos;
	}

	if (state.pos[2] > maxHeight) {
		DEBUG1("Kicking Player %s [%d] jumped too high [max: %f height: %f]\n",
		playerData->player.getCallSign(), playerData->getIndex(), maxHeight, state.pos[2]);
		sendMessage(ServerPlayer, playerData->getIndex(), "Autokick: Player location was too high.");
		removePlayer(playerData->getIndex(), "too high", true);
		return false;
	}
	return true;
}

bool validatePlayerState(GameKeeper::Player *playerData, PlayerState &state)
{
	// Don't kick players if the world param is still changing
	if (!worldStateChanging())
	{
		if (!doBoundsChecks(playerData,state))
			return false;

		if (!doSpeedChecks(playerData,state))
			return false;

		if (!doPauseChecks(playerData,state))
			return false;
	}

	// see if the player is too high
	if (!doHeightChecks(playerData,state))
		return false;
	return true;
}

bool checkFlagCheats ( GameKeeper::Player *playerData, int teamIndex )
{
	bool foundACheat = false;
	TeamColor base = whoseBase(playerData->lastState.pos[0], playerData->lastState.pos[1], playerData->lastState.pos[2]);
	if ((teamIndex == playerData->player.getTeam() && base == playerData->player.getTeam()))
	{
		DEBUG1("Player %s [%d] might have sent MsgCaptureFlag for taking their own "
		"flag onto their own base\n",
		playerData->player.getCallSign(), playerData->getIndex());
		foundACheat = true;
	}

	if ((teamIndex != playerData->player.getTeam() && base != playerData->player.getTeam()))
	{
		DEBUG1("Player %s [%d] (%s) might have tried to capture %s flag without "
				"reaching their own base. (Player position: %f %f %f)\n",
				playerData->player.getCallSign(), playerData->getIndex(),
				Team::getName(playerData->player.getTeam()),
				Team::getName((TeamColor)teamIndex),
				playerData->lastState.pos[0], playerData->lastState.pos[1],
				playerData->lastState.pos[2]);
		foundACheat = true;
	}

	return foundACheat;
}

// Local Variables: ***
// mode:C++ ***
// tab-width: 8 ***
// c-basic-offset: 2 ***
// indent-tabs-mode: t ***
// End: ***
// ex: shiftwidth=2 tabstop=8
<commit_msg>Don't kick players that are spawning in the air<commit_after>/* bzflag
* Copyright (c) 1993 - 2006 Tim Riker
*
* This package is free software;  you can redistribute it and/or
* modify it under the terms of the license found in the file
* named COPYING that should have accompanied this file.
*
* THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*/

/* interface header */
#include "bzfsPlayerStateVerify.h"

/* system implementation headers */
#include <iostream>
#include <assert.h>

/* common implementation headers */
#include "StateDatabase.h"
#include "BZDBCache.h"

bool doSpeedChecks ( GameKeeper::Player *playerData, PlayerState &state )
{
	// Speed problems occur around flag drops, so don't check for
	// a short period of time after player drops a flag. Currently
	// 2 second, adjust as needed.
	if (playerData->player.isFlagTransitSafe())
	{
		// we'll be checking against the player's flag type
		int pFlag = playerData->player.getFlag();

		// check for highspeed cheat; if inertia is enabled, skip test for now
		if (BZDB.eval(StateDatabase::BZDB_INERTIALINEAR) == 0.0f)
		{
			// Doesn't account for going fast backwards, or jumping/falling
			float curPlanarSpeedSqr = state.velocity[0]*state.velocity[0] + state.velocity[1]*state.velocity[1];

			float maxPlanarSpeed = BZDBCache::tankSpeed;

			bool logOnly = false;

			// if tank is not driving cannot be sure it didn't toss
			// (V) in flight

			// if tank is not alive cannot be sure it didn't just toss
			// (V)
			if (pFlag >= 0)
			{
				FlagInfo &flag = *FlagInfo::get(pFlag);
				if (flag.flag.type == Flags::Velocity)
					maxPlanarSpeed *= BZDB.eval(StateDatabase::BZDB_VELOCITYAD);
				else if (flag.flag.type == Flags::Thief)
					maxPlanarSpeed *= BZDB.eval(StateDatabase::BZDB_THIEFVELAD);
				else if (flag.flag.type == Flags::Agility)
					maxPlanarSpeed *= BZDB.eval(StateDatabase::BZDB_AGILITYADVEL);
				else if ((flag.flag.type == Flags::Burrow) && (playerData->lastState.pos[2] == state.pos[2]) && (playerData->lastState.velocity[2] == state.velocity[2]) && (state.pos[2] <= BZDB.eval(StateDatabase::BZDB_BURROWDEPTH)))
				{	// if we have burrow and are not actively burrowing
					// You may have burrow and still be above ground. Must
					// check z in ground!!
					maxPlanarSpeed *= BZDB.eval(StateDatabase::BZDB_BURROWSPEEDAD);
				}
			}

			float maxPlanarSpeedSqr = maxPlanarSpeed * maxPlanarSpeed;

			// If player is moving vertically, or not alive the speed checks
			// seem to be problematic. If this happens, just log it for now,
			// but don't actually kick
			if ((playerData->lastState.pos[2] != state.pos[2]) ||  (playerData->lastState.velocity[2] != state.velocity[2]) || ((state.status & PlayerState::Alive) == 0))
				logOnly = true;

			// allow a 10% tolerance level for speed if -speedtol is not sane
			if (cheatProtectionOptions.doSpeedChecks)
			{
				float realtol = 1.1f;
				if (speedTolerance > 1.0f)
					realtol = speedTolerance;
				maxPlanarSpeedSqr *= realtol;

				if (curPlanarSpeedSqr > maxPlanarSpeedSqr)
				{
					if (logOnly)
						DEBUG1("Logging Player %s [%d] tank too fast (tank: %f, allowed: %f){Dead or v[z] != 0}\n", playerData->player.getCallSign(), playerData->getIndex(), sqrt(curPlanarSpeedSqr), sqrt(maxPlanarSpeedSqr));
					else
					{
						DEBUG1("Kicking Player %s [%d] tank too fast (tank: %f, allowed: %f)\n", playerData->player.getCallSign(), playerData->getIndex(), sqrt(curPlanarSpeedSqr), sqrt(maxPlanarSpeedSqr));
						sendMessage(ServerPlayer, playerData->getIndex(), "Autokick: Player tank is moving too fast.");
						removePlayer(playerData->getIndex(), "too fast");
					}
					return false;
				}
			}
		}
	}
	return true;
}

bool doBoundsChecks ( GameKeeper::Player *playerData, PlayerState &state )
{
	// make sure the player is still in the map
	// test all the map bounds + some fudge factor, just in case
	static const float positionFudge = 10.0f; /* linear distance */
	bool InBounds = true;
	float worldSize = BZDBCache::worldSize;

	if ( (state.pos[1] >= worldSize*0.5f + positionFudge) || (state.pos[1] <= -worldSize*0.5f - positionFudge))
	{
		std::cout << "y position (" << state.pos[1] << ") is out of bounds (" << worldSize * 0.5f << " + " << positionFudge << ")" << std::endl;
		InBounds = false;
	} else if ( (state.pos[0] >= worldSize*0.5f + positionFudge) || (state.pos[0] <= -worldSize*0.5f - positionFudge))
	{
		std::cout << "x position (" << state.pos[0] << ") is out of bounds (" << worldSize * 0.5f << " + " << positionFudge << ")" << std::endl;
		InBounds = false;
	}

	static const float burrowFudge = 1.0f; /* linear distance */
	if (state.pos[2]<BZDB.eval(StateDatabase::BZDB_BURROWDEPTH) - burrowFudge)
	{
		std::cout << "z depth (" << state.pos[2] << ") is less than burrow depth (" << BZDB.eval(StateDatabase::BZDB_BURROWDEPTH) << " - " << burrowFudge << ")" << std::endl;
		InBounds = false;
	}

	// kick em cus they are most likely cheating or using a buggy client
	if (!InBounds)
	{
		DEBUG1("Kicking Player %s [%d] Out of map bounds at position (%.2f,%.2f,%.2f)\n", playerData->player.getCallSign(), playerData->getIndex(), state.pos[0], state.pos[1], state.pos[2]);
		sendMessage(ServerPlayer, playerData->getIndex(), "Autokick: Player location was outside the playing area.");
		removePlayer(playerData->getIndex(), "Out of map bounds", true);
		return false;
	}
	return true;
}

bool doPauseChecks ( GameKeeper::Player *playerData, PlayerState &state )
{
	// make sure the player only pauses after the waiting time for pause is over
	// we need some inaccuracy here that is computed using pauseRequestTime and pauseRequestLag
	// lag will cause all players to pause sooner than 5 seconds
	if (state.status & PlayerState::Paused) {
		TimeKeeper pauseDelay = playerData->player.pauseRequestTime;
		pauseDelay += (- playerData->player.pauseRequestLag / 1000.0);

		if ((TimeKeeper::getCurrent() - pauseDelay) < 5.0f
			&& (playerData->player.pauseRequestTime - TimeKeeper::getNullTime() != 0)) {
			// we have one of those players all love
			DEBUG1("Kicking Player %s [%d] Paused too fast!\n", playerData->player.getCallSign(),
			playerData->getIndex());
			sendMessage(ServerPlayer, playerData->getIndex(), "Autokick: Player paused too fast.");
			removePlayer(playerData->getIndex(), "Paused too fast");
			return false;
		} else {
			pausePlayer(playerData->player.getPlayerIndex(), true);
		}

		// kick the players when they do not pause within allowed situations
		if ((state.status & PlayerState::InBuilding) || (state.status & PlayerState::PhantomZoned)
			|| (state.status & PlayerState::Falling) || (state.status & PlayerState::Alive) == false) {
			// the player did pause while being a wall or in air
			DEBUG1("Kicking Player %s [%d] Paused in unallowed state!\n", playerData->player.getCallSign(),
			playerData->getIndex());
			sendMessage(ServerPlayer, playerData->getIndex(), "Autokick: Player paused in unallowed state.");
			removePlayer(playerData->getIndex(), "Paused in unallowed state");
			return false;
		}
	}

	return true;
}

bool doHeightChecks ( GameKeeper::Player *playerData, PlayerState &state )
{
	float wingsGravity = BZDB.eval(StateDatabase::BZDB_WINGSGRAVITY);
	float normalGravity = BZDBCache::gravity;

	// All tanks with wings are flying away or they do without a flag
	if (((wingsGravity >= 0.0f) && (normalGravity >= 0.0f)) || (normalGravity >= 0.0f)) {
		return true;
	}

	float normalMaxHeight = 0.0f;
	float wingsMaxHeight = 0.0f;

	if (!(state.status & PlayerState::Falling) || (playerData->player.allowedHeightAtJumpStart < 0)) {
		playerData->player.jumpStartPos = state.pos[2];
	}

	static const float heightFudge = 1.10f; /* 10% */

	int pFlag = playerData->player.getFlag();
	bool hasWings = false;

	if (pFlag >= 0)
	{
		FlagInfo &flag = *FlagInfo::get(pFlag);
		if (flag.flag.type == Flags::Wings) {
			hasWings = true;
		}
	}

	if (hasWings) {
		wingsMaxHeight = BZDB.eval(StateDatabase::BZDB_WINGSJUMPVELOCITY);
		wingsMaxHeight *= wingsMaxHeight;
		wingsMaxHeight *= (1 + BZDB.eval(StateDatabase::BZDB_WINGSJUMPCOUNT));
		wingsMaxHeight /= (-wingsGravity * 2.0f);
	} else {
		normalMaxHeight = BZDB.eval(StateDatabase::BZDB_JUMPVELOCITY);
		normalMaxHeight *= normalMaxHeight;
		normalMaxHeight /= (-normalGravity * 2.0f);
	}

	// _wingsGravity is set to a positive value
	if (wingsMaxHeight < 0) {
		wingsMaxHeight = MAXFLOAT;
	}

	// use either the wings height or the usual height
	float maxHeight;
	if (wingsMaxHeight > normalMaxHeight) {
		maxHeight = wingsMaxHeight;
	} else {
		maxHeight = normalMaxHeight;
	}

	if (playerData->player.allowedHeightAtJumpStart > 0.0f && !(state.status & PlayerState::Falling)) {
		playerData->player.allowedHeightAtJumpStart = 0.0f;
	}

	if ((playerData->player.allowedHeightAtJumpStart <= 0.0f) && !(state.status & PlayerState::Falling)) {
		playerData->player.allowedHeightAtJumpStart = maxHeight;
	}

	// Don't kick players if the world param is still changing
	if (worldStateChanging()) {
		playerData->player.allowedHeightAtJumpStart = MAXFLOAT;
	}

	// Don't kick players that are spawning in the air
	if ((state.status & PlayerState::Falling) && (!(playerData->lastState.status & PlayerState::Alive))) {
		playerData->player.allowedHeightAtJumpStart = MAXFLOAT;
	}

	// currently we don't know how height the teleporter is so skip the check
	if ((state.status & PlayerState::Falling) && (state.status & PlayerState::Teleporting)) {
		playerData->player.allowedHeightAtJumpStart = MAXFLOAT;
	}

	// if one of the values changed while the player was in air
	// use the higher allowed one in case we did not get a
	// new update from him yet
	if (playerData->player.allowedHeightAtJumpStart > maxHeight) {
		maxHeight = playerData->player.allowedHeightAtJumpStart;
	}

	if ((normalGravity < -25.0f) && !(playerData->player.allowedHeightAtJumpStart == MAXFLOAT)) {
		maxHeight += 1.0f;
	}

	// final adjustments
	if (playerData->player.allowedHeightAtJumpStart == MAXFLOAT) {
		maxHeight = MAXFLOAT;
	} else {
		maxHeight *= heightFudge;
		maxHeight += playerData->player.jumpStartPos;
	}

	if (state.pos[2] > maxHeight) {
		DEBUG1("Kicking Player %s [%d] jumped too high [max: %f height: %f]\n",
		playerData->player.getCallSign(), playerData->getIndex(), maxHeight, state.pos[2]);
		sendMessage(ServerPlayer, playerData->getIndex(), "Autokick: Player location was too high.");
		removePlayer(playerData->getIndex(), "too high", true);
		return false;
	}
	return true;
}

bool validatePlayerState(GameKeeper::Player *playerData, PlayerState &state)
{
	// Don't kick players if the world param is still changing
	if (!worldStateChanging())
	{
		if (!doBoundsChecks(playerData,state))
			return false;

		if (!doSpeedChecks(playerData,state))
			return false;

		if (!doPauseChecks(playerData,state))
			return false;
	}

	// see if the player is too high
	if (!doHeightChecks(playerData,state))
		return false;
	return true;
}

bool checkFlagCheats ( GameKeeper::Player *playerData, int teamIndex )
{
	bool foundACheat = false;
	TeamColor base = whoseBase(playerData->lastState.pos[0], playerData->lastState.pos[1], playerData->lastState.pos[2]);
	if ((teamIndex == playerData->player.getTeam() && base == playerData->player.getTeam()))
	{
		DEBUG1("Player %s [%d] might have sent MsgCaptureFlag for taking their own "
		"flag onto their own base\n",
		playerData->player.getCallSign(), playerData->getIndex());
		foundACheat = true;
	}

	if ((teamIndex != playerData->player.getTeam() && base != playerData->player.getTeam()))
	{
		DEBUG1("Player %s [%d] (%s) might have tried to capture %s flag without "
				"reaching their own base. (Player position: %f %f %f)\n",
				playerData->player.getCallSign(), playerData->getIndex(),
				Team::getName(playerData->player.getTeam()),
				Team::getName((TeamColor)teamIndex),
				playerData->lastState.pos[0], playerData->lastState.pos[1],
				playerData->lastState.pos[2]);
		foundACheat = true;
	}

	return foundACheat;
}

// Local Variables: ***
// mode:C++ ***
// tab-width: 8 ***
// c-basic-offset: 2 ***
// indent-tabs-mode: t ***
// End: ***
// ex: shiftwidth=2 tabstop=8
<|endoftext|>