aboutsummaryrefslogtreecommitdiff
path: root/src/server/shared/Utilities/TaskScheduler.h
diff options
context:
space:
mode:
Diffstat (limited to 'src/server/shared/Utilities/TaskScheduler.h')
-rw-r--r--src/server/shared/Utilities/TaskScheduler.h650
1 files changed, 0 insertions, 650 deletions
diff --git a/src/server/shared/Utilities/TaskScheduler.h b/src/server/shared/Utilities/TaskScheduler.h
deleted file mode 100644
index f1fe7ea0a21..00000000000
--- a/src/server/shared/Utilities/TaskScheduler.h
+++ /dev/null
@@ -1,650 +0,0 @@
-/*
- * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.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; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
- * more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#ifndef _TASK_SCHEDULER_H_
-#define _TASK_SCHEDULER_H_
-
-#include <algorithm>
-#include <chrono>
-#include <vector>
-#include <queue>
-#include <memory>
-#include <utility>
-#include <set>
-
-#include <boost/optional.hpp>
-
-#include "Util.h"
-#include "Duration.h"
-
-class TaskContext;
-
-/// The TaskScheduler class provides the ability to schedule std::function's in the near future.
-/// Use TaskScheduler::Update to update the scheduler.
-/// Popular methods are:
-/// * Schedule (Schedules a std::function which will be executed in the near future).
-/// * Schedules an asynchronous function which will be executed at the next update tick.
-/// * Cancel, Delay & Reschedule (Methods to manipulate already scheduled tasks).
-/// Tasks are organized in groups (uint), multiple tasks can have the same group id,
-/// you can provide a group or not, but keep in mind that you can only manipulate specific tasks through its group id!
-/// Tasks callbacks use the function signature void(TaskContext) where TaskContext provides
-/// access to the function schedule plan which makes it possible to repeat the task
-/// with the same duration or a new one.
-/// It also provides access to the repeat counter which is useful for task that repeat itself often
-/// but behave different every time (spoken event dialogs for example).
-class TaskScheduler
-{
- friend class TaskContext;
-
- // Time definitions (use steady clock)
- typedef std::chrono::steady_clock clock_t;
- typedef clock_t::time_point timepoint_t;
- typedef clock_t::duration duration_t;
-
- // Task group type
- typedef uint32 group_t;
- // Task repeated type
- typedef uint32 repeated_t;
- // Task handle type
- typedef std::function<void(TaskContext)> task_handler_t;
- // Predicate type
- typedef std::function<bool()> predicate_t;
- // Success handle type
- typedef std::function<void()> success_t;
-
- class Task
- {
- friend class TaskContext;
- friend class TaskScheduler;
-
- timepoint_t _end;
- duration_t _duration;
- boost::optional<group_t> _group;
- repeated_t _repeated;
- task_handler_t _task;
-
- public:
- // All Argument construct
- Task(timepoint_t const& end, duration_t const& duration, boost::optional<group_t> const& group,
- repeated_t const repeated, task_handler_t const& task)
- : _end(end), _duration(duration), _group(group), _repeated(repeated), _task(task) { }
-
- // Minimal Argument construct
- Task(timepoint_t const& end, duration_t const& duration, task_handler_t const& task)
- : _end(end), _duration(duration), _group(boost::none), _repeated(0), _task(task) { }
-
- // Copy construct
- Task(Task const&) = delete;
- // Move construct
- Task(Task&&) = delete;
- // Copy Assign
- Task& operator= (Task const&) = default;
- // Move Assign
- Task& operator= (Task&& right) = delete;
-
- // Order tasks by its end
- inline bool operator< (Task const& other) const
- {
- return _end < other._end;
- }
-
- inline bool operator> (Task const& other) const
- {
- return _end > other._end;
- }
-
- // Compare tasks with its end
- inline bool operator== (Task const& other)
- {
- return _end == other._end;
- }
-
- // Returns true if the task is in the given group
- inline bool IsInGroup(group_t const group) const
- {
- return _group == group;
- }
- };
-
- typedef std::shared_ptr<Task> TaskContainer;
-
- /// Container which provides Task order, insert and reschedule operations.
- struct Compare
- {
- bool operator() (TaskContainer const& left, TaskContainer const& right)
- {
- return (*left.get()) < (*right.get());
- };
- };
-
- class TaskQueue
- {
- std::multiset<TaskContainer, Compare> container;
-
- public:
- // Pushes the task in the container
- void Push(TaskContainer&& task);
-
- /// Pops the task out of the container
- TaskContainer Pop();
-
- TaskContainer const& First() const;
-
- void Clear();
-
- void RemoveIf(std::function<bool(TaskContainer const&)> const& filter);
-
- void ModifyIf(std::function<bool(TaskContainer const&)> const& filter);
-
- bool IsEmpty() const;
- };
-
- /// Contains a self reference to track if this object was deleted or not.
- std::shared_ptr<TaskScheduler> self_reference;
-
- /// The current time point (now)
- timepoint_t _now;
-
- /// The Task Queue which contains all task objects.
- TaskQueue _task_holder;
-
- typedef std::queue<std::function<void()>> AsyncHolder;
-
- /// Contains all asynchronous tasks which will be invoked at
- /// the next update tick.
- AsyncHolder _asyncHolder;
-
- predicate_t _predicate;
-
- static bool EmptyValidator()
- {
- return true;
- }
-
- static void EmptyCallback()
- {
- }
-
-public:
- TaskScheduler()
- : self_reference(this, [](TaskScheduler const*) { }), _now(clock_t::now()), _predicate(EmptyValidator) { }
-
- template<typename P>
- TaskScheduler(P&& predicate)
- : self_reference(this, [](TaskScheduler const*) { }), _now(clock_t::now()), _predicate(std::forward<P>(predicate)) { }
-
- TaskScheduler(TaskScheduler const&) = delete;
- TaskScheduler(TaskScheduler&&) = delete;
- TaskScheduler& operator= (TaskScheduler const&) = delete;
- TaskScheduler& operator= (TaskScheduler&&) = delete;
-
- /// Sets a validator which is asked if tasks are allowed to be executed.
- template<typename P>
- TaskScheduler& SetValidator(P&& predicate)
- {
- _predicate = std::forward<P>(predicate);
- return *this;
- }
-
- /// Clears the validator which is asked if tasks are allowed to be executed.
- TaskScheduler& ClearValidator();
-
- /// Update the scheduler to the current time.
- /// Calls the optional callback on successfully finish.
- TaskScheduler& Update(success_t const& callback = EmptyCallback);
-
- /// Update the scheduler with a difftime in ms.
- /// Calls the optional callback on successfully finish.
- TaskScheduler& Update(size_t const milliseconds, success_t const& callback = EmptyCallback);
-
- /// Update the scheduler with a difftime.
- /// Calls the optional callback on successfully finish.
- template<class _Rep, class _Period>
- TaskScheduler& Update(std::chrono::duration<_Rep, _Period> const& difftime,
- success_t const& callback = EmptyCallback)
- {
- _now += difftime;
- Dispatch(callback);
- return *this;
- }
-
- /// Schedule an callable function that is executed at the next update tick.
- /// Its safe to modify the TaskScheduler from within the callable.
- TaskScheduler& Async(std::function<void()> const& callable);
-
- /// Schedule an event with a fixed rate.
- /// Never call this from within a task context! Use TaskContext::Schedule instead!
- template<class _Rep, class _Period>
- TaskScheduler& Schedule(std::chrono::duration<_Rep, _Period> const& time,
- task_handler_t const& task)
- {
- return ScheduleAt(_now, time, task);
- }
-
- /// Schedule an event with a fixed rate.
- /// Never call this from within a task context! Use TaskContext::Schedule instead!
- template<class _Rep, class _Period>
- TaskScheduler& Schedule(std::chrono::duration<_Rep, _Period> const& time,
- group_t const group, task_handler_t const& task)
- {
- return ScheduleAt(_now, time, group, task);
- }
-
- /// Schedule an event with a randomized rate between min and max rate.
- /// Never call this from within a task context! Use TaskContext::Schedule instead!
- template<class _RepLeft, class _PeriodLeft, class _RepRight, class _PeriodRight>
- TaskScheduler& Schedule(std::chrono::duration<_RepLeft, _PeriodLeft> const& min,
- std::chrono::duration<_RepRight, _PeriodRight> const& max, task_handler_t const& task)
- {
- return Schedule(RandomDurationBetween(min, max), task);
- }
-
- /// Schedule an event with a fixed rate.
- /// Never call this from within a task context! Use TaskContext::Schedule instead!
- template<class _RepLeft, class _PeriodLeft, class _RepRight, class _PeriodRight>
- TaskScheduler& Schedule(std::chrono::duration<_RepLeft, _PeriodLeft> const& min,
- std::chrono::duration<_RepRight, _PeriodRight> const& max, group_t const group,
- task_handler_t const& task)
- {
- return Schedule(RandomDurationBetween(min, max), group, task);
- }
-
- /// Cancels all tasks.
- /// Never call this from within a task context! Use TaskContext::CancelAll instead!
- TaskScheduler& CancelAll();
-
- /// Cancel all tasks of a single group.
- /// Never call this from within a task context! Use TaskContext::CancelGroup instead!
- TaskScheduler& CancelGroup(group_t const group);
-
- /// Cancels all groups in the given std::vector.
- /// Hint: Use std::initializer_list for this: "{1, 2, 3, 4}"
- TaskScheduler& CancelGroupsOf(std::vector<group_t> const& groups);
-
- /// Delays all tasks with the given duration.
- template<class _Rep, class _Period>
- TaskScheduler& DelayAll(std::chrono::duration<_Rep, _Period> const& duration)
- {
- _task_holder.ModifyIf([&duration](TaskContainer const& task) -> bool
- {
- task->_end += duration;
- return true;
- });
- return *this;
- }
-
- /// Delays all tasks with a random duration between min and max.
- template<class _RepLeft, class _PeriodLeft, class _RepRight, class _PeriodRight>
- TaskScheduler& DelayAll(std::chrono::duration<_RepLeft, _PeriodLeft> const& min,
- std::chrono::duration<_RepRight, _PeriodRight> const& max)
- {
- return DelayAll(RandomDurationBetween(min, max));
- }
-
- /// Delays all tasks of a group with the given duration.
- template<class _Rep, class _Period>
- TaskScheduler& DelayGroup(group_t const group, std::chrono::duration<_Rep, _Period> const& duration)
- {
- _task_holder.ModifyIf([&duration, group](TaskContainer const& task) -> bool
- {
- if (task->IsInGroup(group))
- {
- task->_end += duration;
- return true;
- }
- else
- return false;
- });
- return *this;
- }
-
- /// Delays all tasks of a group with a random duration between min and max.
- template<class _RepLeft, class _PeriodLeft, class _RepRight, class _PeriodRight>
- TaskScheduler& DelayGroup(group_t const group,
- std::chrono::duration<_RepLeft, _PeriodLeft> const& min,
- std::chrono::duration<_RepRight, _PeriodRight> const& max)
- {
- return DelayGroup(group, RandomDurationBetween(min, max));
- }
-
- /// Reschedule all tasks with a given duration.
- template<class _Rep, class _Period>
- TaskScheduler& RescheduleAll(std::chrono::duration<_Rep, _Period> const& duration)
- {
- auto const end = _now + duration;
- _task_holder.ModifyIf([end](TaskContainer const& task) -> bool
- {
- task->_end = end;
- return true;
- });
- return *this;
- }
-
- /// Reschedule all tasks with a random duration between min and max.
- template<class _RepLeft, class _PeriodLeft, class _RepRight, class _PeriodRight>
- TaskScheduler& RescheduleAll(std::chrono::duration<_RepLeft, _PeriodLeft> const& min,
- std::chrono::duration<_RepRight, _PeriodRight> const& max)
- {
- return RescheduleAll(RandomDurationBetween(min, max));
- }
-
- /// Reschedule all tasks of a group with the given duration.
- template<class _Rep, class _Period>
- TaskScheduler& RescheduleGroup(group_t const group, std::chrono::duration<_Rep, _Period> const& duration)
- {
- auto const end = _now + duration;
- _task_holder.ModifyIf([end, group](TaskContainer const& task) -> bool
- {
- if (task->IsInGroup(group))
- {
- task->_end = end;
- return true;
- }
- else
- return false;
- });
- return *this;
- }
-
- /// Reschedule all tasks of a group with a random duration between min and max.
- template<class _RepLeft, class _PeriodLeft, class _RepRight, class _PeriodRight>
- TaskScheduler& RescheduleGroup(group_t const group,
- std::chrono::duration<_RepLeft, _PeriodLeft> const& min,
- std::chrono::duration<_RepRight, _PeriodRight> const& max)
- {
- return RescheduleGroup(group, RandomDurationBetween(min, max));
- }
-
-private:
- /// Insert a new task to the enqueued tasks.
- TaskScheduler& InsertTask(TaskContainer task);
-
- template<class _Rep, class _Period>
- TaskScheduler& ScheduleAt(timepoint_t const& end,
- std::chrono::duration<_Rep, _Period> const& time, task_handler_t const& task)
- {
- return InsertTask(TaskContainer(new Task(end + time, time, task)));
- }
-
- /// Schedule an event with a fixed rate.
- /// Never call this from within a task context! Use TaskContext::schedule instead!
- template<class _Rep, class _Period>
- TaskScheduler& ScheduleAt(timepoint_t const& end,
- std::chrono::duration<_Rep, _Period> const& time,
- group_t const group, task_handler_t const& task)
- {
- static repeated_t const DEFAULT_REPEATED = 0;
- return InsertTask(TaskContainer(new Task(end + time, time, group, DEFAULT_REPEATED, task)));
- }
-
- // Returns a random duration between min and max
- template<class _RepLeft, class _PeriodLeft, class _RepRight, class _PeriodRight>
- static std::chrono::milliseconds
- RandomDurationBetween(std::chrono::duration<_RepLeft, _PeriodLeft> const& min,
- std::chrono::duration<_RepRight, _PeriodRight> const& max)
- {
- auto const milli_min = std::chrono::duration_cast<std::chrono::milliseconds>(min);
- auto const milli_max = std::chrono::duration_cast<std::chrono::milliseconds>(max);
-
- // TC specific: use SFMT URandom
- return std::chrono::milliseconds(urand(milli_min.count(), milli_max.count()));
- }
-
- /// Dispatch remaining tasks
- void Dispatch(success_t const& callback);
-};
-
-class TaskContext
-{
- friend class TaskScheduler;
-
- /// Associated task
- TaskScheduler::TaskContainer _task;
-
- /// Owner
- std::weak_ptr<TaskScheduler> _owner;
-
- /// Marks the task as consumed
- std::shared_ptr<bool> _consumed;
-
- /// Dispatches an action safe on the TaskScheduler
- TaskContext& Dispatch(std::function<TaskScheduler&(TaskScheduler&)> const& apply);
-
-public:
- // Empty constructor
- TaskContext()
- : _task(), _owner(), _consumed(std::make_shared<bool>(true)) { }
-
- // Construct from task and owner
- explicit TaskContext(TaskScheduler::TaskContainer&& task, std::weak_ptr<TaskScheduler>&& owner)
- : _task(task), _owner(owner), _consumed(std::make_shared<bool>(false)) { }
-
- // Copy construct
- TaskContext(TaskContext const& right)
- : _task(right._task), _owner(right._owner), _consumed(right._consumed) { }
-
- // Move construct
- TaskContext(TaskContext&& right)
- : _task(std::move(right._task)), _owner(std::move(right._owner)), _consumed(std::move(right._consumed)) { }
-
- // Copy assign
- TaskContext& operator= (TaskContext const& right)
- {
- _task = right._task;
- _owner = right._owner;
- _consumed = right._consumed;
- return *this;
- }
-
- // Move assign
- TaskContext& operator= (TaskContext&& right)
- {
- _task = std::move(right._task);
- _owner = std::move(right._owner);
- _consumed = std::move(right._consumed);
- return *this;
- }
-
- /// Returns true if the owner was deallocated and this context has expired.
- bool IsExpired() const;
-
- /// Returns true if the event is in the given group
- bool IsInGroup(TaskScheduler::group_t const group) const;
-
- /// Sets the event in the given group
- TaskContext& SetGroup(TaskScheduler::group_t const group);
-
- /// Removes the group from the event
- TaskContext& ClearGroup();
-
- /// Returns the repeat counter which increases every time the task is repeated.
- TaskScheduler::repeated_t GetRepeatCounter() const;
-
- /// Repeats the event and sets a new duration.
- /// std::chrono::seconds(5) for example.
- /// This will consume the task context, its not possible to repeat the task again
- /// from the same task context!
- template<class _Rep, class _Period>
- TaskContext& Repeat(std::chrono::duration<_Rep, _Period> const& duration)
- {
- AssertOnConsumed();
-
- // Set new duration, in-context timing and increment repeat counter
- _task->_duration = duration;
- _task->_end += duration;
- _task->_repeated += 1;
- (*_consumed) = true;
- return Dispatch(std::bind(&TaskScheduler::InsertTask, std::placeholders::_1, _task));
- }
-
- /// Repeats the event with the same duration.
- /// This will consume the task context, its not possible to repeat the task again
- /// from the same task context!
- TaskContext& Repeat()
- {
- return Repeat(_task->_duration);
- }
-
- /// Repeats the event and set a new duration that is randomized between min and max.
- /// std::chrono::seconds(5) for example.
- /// This will consume the task context, its not possible to repeat the task again
- /// from the same task context!
- template<class _RepLeft, class _PeriodLeft, class _RepRight, class _PeriodRight>
- TaskContext& Repeat(std::chrono::duration<_RepLeft, _PeriodLeft> const& min,
- std::chrono::duration<_RepRight, _PeriodRight> const& max)
- {
- return Repeat(TaskScheduler::RandomDurationBetween(min, max));
- }
-
- /// Schedule a callable function that is executed at the next update tick from within the context.
- /// Its safe to modify the TaskScheduler from within the callable.
- TaskContext& Async(std::function<void()> const& callable);
-
- /// Schedule an event with a fixed rate from within the context.
- /// Its possible that the new event is executed immediately!
- /// Use TaskScheduler::Async to create a task
- /// which will be called at the next update tick.
- template<class _Rep, class _Period>
- TaskContext& Schedule(std::chrono::duration<_Rep, _Period> const& time,
- TaskScheduler::task_handler_t const& task)
- {
- auto const end = _task->_end;
- return Dispatch([end, time, task](TaskScheduler& scheduler) -> TaskScheduler&
- {
- return scheduler.ScheduleAt<_Rep, _Period>(end, time, task);
- });
- }
-
- /// Schedule an event with a fixed rate from within the context.
- /// Its possible that the new event is executed immediately!
- /// Use TaskScheduler::Async to create a task
- /// which will be called at the next update tick.
- template<class _Rep, class _Period>
- TaskContext& Schedule(std::chrono::duration<_Rep, _Period> const& time,
- TaskScheduler::group_t const group, TaskScheduler::task_handler_t const& task)
- {
- auto const end = _task->_end;
- return Dispatch([end, time, group, task](TaskScheduler& scheduler) -> TaskScheduler&
- {
- return scheduler.ScheduleAt<_Rep, _Period>(end, time, group, task);
- });
- }
-
- /// Schedule an event with a randomized rate between min and max rate from within the context.
- /// Its possible that the new event is executed immediately!
- /// Use TaskScheduler::Async to create a task
- /// which will be called at the next update tick.
- template<class _RepLeft, class _PeriodLeft, class _RepRight, class _PeriodRight>
- TaskContext& Schedule(std::chrono::duration<_RepLeft, _PeriodLeft> const& min,
- std::chrono::duration<_RepRight, _PeriodRight> const& max, TaskScheduler::task_handler_t const& task)
- {
- return Schedule(TaskScheduler::RandomDurationBetween(min, max), task);
- }
-
- /// Schedule an event with a randomized rate between min and max rate from within the context.
- /// Its possible that the new event is executed immediately!
- /// Use TaskScheduler::Async to create a task
- /// which will be called at the next update tick.
- template<class _RepLeft, class _PeriodLeft, class _RepRight, class _PeriodRight>
- TaskContext& Schedule(std::chrono::duration<_RepLeft, _PeriodLeft> const& min,
- std::chrono::duration<_RepRight, _PeriodRight> const& max, TaskScheduler::group_t const group,
- TaskScheduler::task_handler_t const& task)
- {
- return Schedule(TaskScheduler::RandomDurationBetween(min, max), group, task);
- }
-
- /// Cancels all tasks from within the context.
- TaskContext& CancelAll();
-
- /// Cancel all tasks of a single group from within the context.
- TaskContext& CancelGroup(TaskScheduler::group_t const group);
-
- /// Cancels all groups in the given std::vector from within the context.
- /// Hint: Use std::initializer_list for this: "{1, 2, 3, 4}"
- TaskContext& CancelGroupsOf(std::vector<TaskScheduler::group_t> const& groups);
-
- /// Delays all tasks with the given duration from within the context.
- template<class _Rep, class _Period>
- TaskContext& DelayAll(std::chrono::duration<_Rep, _Period> const& duration)
- {
- return Dispatch(std::bind(&TaskScheduler::DelayAll<_Rep, _Period>, std::placeholders::_1, duration));
- }
-
- /// Delays all tasks with a random duration between min and max from within the context.
- template<class _RepLeft, class _PeriodLeft, class _RepRight, class _PeriodRight>
- TaskContext& DelayAll(std::chrono::duration<_RepLeft, _PeriodLeft> const& min,
- std::chrono::duration<_RepRight, _PeriodRight> const& max)
- {
- return DelayAll(TaskScheduler::RandomDurationBetween(min, max));
- }
-
- /// Delays all tasks of a group with the given duration from within the context.
- template<class _Rep, class _Period>
- TaskContext& DelayGroup(TaskScheduler::group_t const group, std::chrono::duration<_Rep, _Period> const& duration)
- {
- return Dispatch(std::bind(&TaskScheduler::DelayGroup<_Rep, _Period>, std::placeholders::_1, group, duration));
- }
-
- /// Delays all tasks of a group with a random duration between min and max from within the context.
- template<class _RepLeft, class _PeriodLeft, class _RepRight, class _PeriodRight>
- TaskContext& DelayGroup(TaskScheduler::group_t const group,
- std::chrono::duration<_RepLeft, _PeriodLeft> const& min,
- std::chrono::duration<_RepRight, _PeriodRight> const& max)
- {
- return DelayGroup(group, TaskScheduler::RandomDurationBetween(min, max));
- }
-
- /// Reschedule all tasks with the given duration.
- template<class _Rep, class _Period>
- TaskContext& RescheduleAll(std::chrono::duration<_Rep, _Period> const& duration)
- {
- return Dispatch(std::bind(&TaskScheduler::RescheduleAll, std::placeholders::_1, duration));
- }
-
- /// Reschedule all tasks with a random duration between min and max.
- template<class _RepLeft, class _PeriodLeft, class _RepRight, class _PeriodRight>
- TaskContext& RescheduleAll(std::chrono::duration<_RepLeft, _PeriodLeft> const& min,
- std::chrono::duration<_RepRight, _PeriodRight> const& max)
- {
- return RescheduleAll(TaskScheduler::RandomDurationBetween(min, max));
- }
-
- /// Reschedule all tasks of a group with the given duration.
- template<class _Rep, class _Period>
- TaskContext& RescheduleGroup(TaskScheduler::group_t const group, std::chrono::duration<_Rep, _Period> const& duration)
- {
- return Dispatch(std::bind(&TaskScheduler::RescheduleGroup<_Rep, _Period>, std::placeholders::_1, group, duration));
- }
-
- /// Reschedule all tasks of a group with a random duration between min and max.
- template<class _RepLeft, class _PeriodLeft, class _RepRight, class _PeriodRight>
- TaskContext& RescheduleGroup(TaskScheduler::group_t const group,
- std::chrono::duration<_RepLeft, _PeriodLeft> const& min,
- std::chrono::duration<_RepRight, _PeriodRight> const& max)
- {
- return RescheduleGroup(group, TaskScheduler::RandomDurationBetween(min, max));
- }
-
-private:
- /// Asserts if the task was consumed already.
- void AssertOnConsumed() const;
-
- /// Invokes the associated hook of the task.
- void Invoke();
-};
-
-#endif /// _TASK_SCHEDULER_H_