Merge branch 'develop' into persist

develop
lethosor 2019-05-28 21:08:16 -04:00
commit ceebef5fe2
69 changed files with 3497 additions and 318 deletions

@ -28,7 +28,7 @@ matrix:
- g++-4.8 - g++-4.8
before_install: before_install:
- export DF_VERSION=$(sh travis/get-df-version.sh) - export DF_VERSION=$(sh travis/get-df-version.sh)
- export DF_FOLDER="$HOME/DF-travis/$DF_VERSION" - export DF_FOLDER="$HOME/DF-travis/$DF_VERSION/df_linux"
- pip install --user "sphinx==1.4" "requests[security]" - pip install --user "sphinx==1.4" "requests[security]"
- sh travis/build-lua.sh - sh travis/build-lua.sh
- sh travis/download-df.sh - sh travis/download-df.sh
@ -56,9 +56,7 @@ script:
- python travis/check-rpc.py "$DF_FOLDER/dfhack-rpc.txt" - python travis/check-rpc.py "$DF_FOLDER/dfhack-rpc.txt"
before_cache: before_cache:
- cat "$DF_FOLDER/stderr.log" - cat "$DF_FOLDER/stderr.log"
- rm -rf "$DF_FOLDER/hack" - rm -rf "$DF_FOLDER"
- rm -rf "$DF_FOLDER/dfhack-config"
- rm -f "$DF_FOLDER"/*.log
notifications: notifications:
email: false email: false
irc: irc:

@ -168,7 +168,7 @@ endif()
# set up versioning. # set up versioning.
set(DF_VERSION "0.44.12") set(DF_VERSION "0.44.12")
set(DFHACK_RELEASE "r1") set(DFHACK_RELEASE "r2")
set(DFHACK_PRERELEASE FALSE) set(DFHACK_PRERELEASE FALSE)
set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}")

@ -23,6 +23,7 @@ Bearskie Bearskie
belal jimhester belal jimhester
Ben Lubar BenLubar Ben Lubar BenLubar
Ben Rosser TC01 Ben Rosser TC01
billw2012 billw2012
brndd brndd burneddi brndd brndd burneddi
Bumber Bumber64 Bumber Bumber64
Caldfir caldfir Caldfir caldfir
@ -50,6 +51,7 @@ gchristopher gchristopher
grubsteak grubsteak grubsteak grubsteak
Harlan Playford playfordh Harlan Playford playfordh
Hayati Ayguen hayguen Hayati Ayguen hayguen
Herwig Hochleitner bendlas
IndigoFenix IndigoFenix
James Logsdon jlogsdon James Logsdon jlogsdon
Japa JapaMala Japa JapaMala
@ -68,6 +70,7 @@ Kromtec Kromtec
Kurik Amudnil Kurik Amudnil
Lethosor lethosor Lethosor lethosor
Mason11987 Mason11987 Mason11987 Mason11987
Matt Regul mattregul
Matthew Cline Matthew Cline
Matthew Lindner mlindner Matthew Lindner mlindner
Max maxthyme Max^TM Max maxthyme Max^TM

@ -392,6 +392,98 @@ Otherwise somewhat similar to `gui/quickcmd`.
.. image:: images/command-prompt.png .. image:: images/command-prompt.png
.. _debug:
debug
=====
Manager for DFHack runtime debug prints. Debug prints are grouped by plugin name,
category name and print level. Levels are ``trace``, ``debug``, ``info``,
``warning`` and ``error``.
The runtime message printing is controlled using filters. Filters set the
visible messages of all matching categories. Matching uses regular expression syntax,
which allows listing multiple alternative matches or partial name matches.
This syntax is a C++ version of the ECMA-262 grammar (Javascript regular expressions).
Details of differences can be found at
https://en.cppreference.com/w/cpp/regex/ecmascript
Persistent filters are stored in ``dfhack-config/runtime-debug.json``.
Oldest filters are applied first. That means a newer filter can override the
older printing level selection.
Usage: ``debugfilter [subcommand] [parameters...]``
The following subcommands are supported:
help
----
Give overall help or a detailed help for a subcommand.
Usage: ``debugfilter help [subcommand]``
category
--------
List available debug plugin and category names.
Usage: ``debugfilter category [plugin regex] [category regex]``
The list can be filtered using optional regex parameters. If filters aren't
given then the it uses ``"."`` regex which matches any character. The regex
parameters are good way to test regex before passing them to ``set``.
filter
------
List active and passive debug print level changes.
Usage: ``debugfilter filter [id]``
Optional ``id`` parameter is the id listed as first column in the filter list.
If id is given then the command shows information for the given filter only in
multi line format that is better format if filter has long regex.
set
---
Creates a new debug filter to set category printing levels.
Usage: ``debugfilter set [level] [plugin regex] [category regex]``
Adds a filter that will be deleted when DF process exists or plugin is unloaded.
Usage: ``debugfilter set persistent [level] [plugin regex] [category regex]``
Stores the filter in the configuration file to until ``unset`` is used to remove
it.
Level is the minimum debug printing level to show in log.
* ``trace``: Possibly very noisy messages which can be printed many times per second
* ``debug``: Messages that happen often but they should happen only a couple of times per second
* ``info``: Important state changes that happen rarely during normal execution
* ``warning``: Enabled by default. Shows warnings about unexpected events which code managed to handle correctly.
* ``error``: Enabled by default. Shows errors which code can't handle without user intervention.
unset
-----
Delete a space separated list of filters
Usage: ``debugfilter unset [id...]``
disable
-------
Disable a space separated list of filters but keep it in the filter list
Usage: ``debugfilter disable [id...]``
enable
------
Enable a space sperate list of filters
Usage: ``debugfilter enable [id...]``
.. _hotkeys: .. _hotkeys:
hotkeys hotkeys
@ -469,16 +561,19 @@ directly to the main dwarf mode screen.
Professions Professions
----------- -----------
The manipulator plugin supports saving Professions: a named set of Labors labors that can be The manipulator plugin supports saving professions: a named set of labors that can be
quickly applied to one or multiple Dwarves. quickly applied to one or multiple dwarves.
To save a profession, highlight a dwarf and press :kbd:`P`. The profession will be saved using
the custom profession name of the dwarf, or the default for that dwarf if no custom profession
name has been set.
To save a Profession highlight a Dwarf and press :kbd:`P`. The Profession will be saved using To apply a profession, either highlight a single dwarf or select multiple with
the Custom Profession Name of the Dwarf, or the default for that Dwarf if no Custom Profession :kbd:`x`, and press :kbd:`p` to select the profession to apply. All labors for
Name has been set. the selected dwarves will be reset to the labors of the chosen profession.
To apply a Profession either highlight a single Dwarf, or select multiple with :kbd:`x`, and press Professions are saved as human-readable text files in the "professions" folder
:kbd:`p` to select the Profession to apply. All labors for the selected Dwarves will be reset to within the DF folder, and can be edited or deleted there.
the labors of the chosen Profession.
.. comment - the link target "search" is reserved for the Sphinx search page .. comment - the link target "search" is reserved for the Sphinx search page
.. _search-plugin: .. _search-plugin:
@ -1728,6 +1823,14 @@ Subcommands:
:import NAME: Imports manager orders from a file named ``dfhack-config/orders/NAME.json``. :import NAME: Imports manager orders from a file named ``dfhack-config/orders/NAME.json``.
:clear: Deletes all manager orders in the current embark. :clear: Deletes all manager orders in the current embark.
.. _nestboxes:
nestboxes
=========
Automatically scan for and forbid fertile eggs incubating in a nestbox.
Toggle status with `enable` or `disable`.
================ ================
Map modification Map modification
================ ================

@ -37,32 +37,117 @@ changelog.txt uses a syntax similar to RST, with a few special sequences:
================================================================================ ================================================================================
# Future # Future
## Internals
- Fixed some OpenGL build issues with `stonesense`
# 0.44.12-r2
## New Plugins
- `debug`: manages runtime debug print category filtering
- `nestboxes`: automatically scan for and forbid fertile eggs incubating in a nestbox
## New Scripts
- `devel/query`: searches for field names in DF objects
- `extinguish`: puts out fires
- `tame`: sets tamed/trained status of animals
## Fixes ## Fixes
- `building-hacks`: fixed error when dealing with custom animation tables - `building-hacks`: fixed error when dealing with custom animation tables
- `devel/test-perlin`: fixed Lua error (``math.pow()``) - `devel/test-perlin`: fixed Lua error (``math.pow()``)
- `embark-assistant`: fixed crash when entering finder with a 16x16 embark selected, and added 16 to dimension choices - `embark-assistant`: fixed crash when entering finder with a 16x16 embark selected, and added 16 to dimension choices
- `embark-skills`: fixed missing ``skill_points_remaining`` field
- `full-heal`:
- stopped wagon resurrection
- fixed a minor issue with post-resurrection hostility
- `gui/companion-order`:
- fixed issues with printing coordinates
- fixed issues with move command
- fixed cheat commands (and removed "Power up", which was broken)
- `gui/gm-editor`: fixed reinterpret cast (``r``)
- `gui/pathable`: fixed error when sidebar is hidden with ``Tab``
- `labormanager`: - `labormanager`:
- stopped assigning labors to ineligible dwarves, pets, etc. - stopped assigning labors to ineligible dwarves, pets, etc.
- stopped assigning invalid labors - stopped assigning invalid labors
- added support for crafting jobs that use pearl - added support for crafting jobs that use pearl
- fixed issues causing cleaning jobs to not be assigned
- added support for disabling management of specific labors
- `prospector`: (also affected `embark-tools`) - fixed a crash when prospecting an unusable site (ocean, mountains, etc.) with a large default embark size in d_init.txt (e.g. 16x16) - `prospector`: (also affected `embark-tools`) - fixed a crash when prospecting an unusable site (ocean, mountains, etc.) with a large default embark size in d_init.txt (e.g. 16x16)
- `siege-engine`: fixed a few Lua errors (``math.pow()``, ``unit.relationship_ids``) - `siege-engine`: fixed a few Lua errors (``math.pow()``, ``unit.relationship_ids``)
- `tweak`: fixed ``hotkey-clear``
## Misc Improvements ## Misc Improvements
- `devel/export-dt-ini`: added viewscreen offsets for DT 40.1.2 - `armoks-blessing`: improved documentation to list all available arguments
- `devel/export-dt-ini`:
- added viewscreen offsets for DT 40.1.2
- added item base flags offset
- added needs offsets
- `embark-assistant`:
- added match indicator display on the right ("World") map
- changed 'c'ancel to abort find if it's under way and clear results if not, allowing use of partial surveys.
- added Coal as a search criterion, as well as a coal indication as current embark selection info.
- `full-heal`:
- added ``-all``, ``-all_civ`` and ``-all_citizens`` arguments
- added module support
- now removes historical figure death dates and ghost data
- `growcrops`: added ``all`` argument to grow all crops
- `gui/load-screen`: improved documentation
- `labormanager`: now takes nature value into account when assigning jobs - `labormanager`: now takes nature value into account when assigning jobs
- `open-legends`: added warning about risk of save corruption and improved related documentation
- `points`: added support when in ``viewscreen_setupdwarfgamest`` and improved error messages
- `siren`: removed break handling (relevant ``misc_trait_type`` was no longer used - see "Structures" section)
## Internals ## Internals
- Linux/macOS: changed recommended build backend from Make to Ninja (Make builds will be significantly slower now) - Linux/macOS: changed recommended build backend from Make to Ninja (Make builds will be significantly slower now)
- Added a usable unit test framework for basic tests, and a few basic tests - Added a usable unit test framework for basic tests, and a few basic tests
- Core: various thread safety and memory management improvements - Core: various thread safety and memory management improvements
- Fixed cmake build dependencies for generated header files - Fixed CMake build dependencies for generated header files
- Fixed custom ``CMAKE_CXX_FLAGS`` not being passed to plugins - Fixed custom ``CMAKE_CXX_FLAGS`` not being passed to plugins
- Changed ``plugins/CMakeLists.custom.txt`` to be ignored by git and created (if needed) at build time instead - Changed ``plugins/CMakeLists.custom.txt`` to be ignored by git and created (if needed) at build time instead
- Added ``CMakeSettings.json`` with intellisense support
## Lua ## Lua
- ``utils``: new ``OrderedTable`` class - ``utils``: new ``OrderedTable`` class
## Structures
- Win32: added missing vtables for ``viewscreen_storesst`` and ``squad_order_rescue_hfst``
- ``activity_event_performancest``: renamed poem as written_content_id
- ``dance_form``: named musical_form_id and musical_written_content_id
- ``incident_sub6_performance.participants``: named performance_event and role_index
- ``incident_sub6_performance``: made performance_event an enum
- ``incident_sub6_performance``: named poetic_form_id, musical_form_id, and dance_form_id
- ``musical_form_instruments``: named minimum_required and maximum_permitted
- ``musical_form``: named voices field
- ``poetic_form``: identified many fields and related enum/bitfield types
- ``setup_character_info``: identified ``skill_points_remaining`` (for `embark-skills`)
- ``unit_thought_type``: added new expulsion thoughts from 0.44.12
- ``viewscreen_layer_militaryst``: identified ``equip.assigned.assigned_items``
- ``world_data``: added ``mountain_peak_flags`` type, including ``is_volcano``
- ``written_content``: named poetic_form
- ``unit_action.attack``: identified ``attack_skill``
- ``unit_action.attack``: added ``lightly_tap`` and ``spar_report`` flags
- ``misc_trait_type``: removed ``LikesOutdoors``, ``Hardened``, ``TimeSinceBreak``, ``OnBreak`` (all unused by DF)
- ``unit_personality``: identified ``stress_drain``, ``stress_boost``, ``likes_outdoors``, ``combat_hardened``
- ``plant_tree_tile``: gave connection bits more meaningful names (e.g. ``connection_east`` instead of ``thick_branches_1``)
- ``plant_tree_info``: identified ``extent_east``, etc.
- ``ui``: fixed alignment of ``main`` and ``squads`` (fixes `tweak` hotkey-clear and DF-AI)
- ``ui.main``: identified ``fortress_site``
- ``ui.squads``: identified ``kill_rect_targets_scroll``
- ``world_site``: identified names and/or types of some fields
- ``world_history``: identified names and/or types of some fields
- ``viewscreen_setupadventurest``: identified some nemesis and personality fields, and ``page.ChooseHistfig``
- ``unit_storage_status``: newly identified type, stores noble holdings information (used in ``viewscreen_layer_noblelistst``)
- ``viewscreen_layer_noblelistst``: identified ``storage_status`` (see ``unit_storage_status`` type)
- ``viewscreen_layer_arena_creaturest``: identified item- and name-related fields
- ``viewscreen_new_regionst``: identified ``rejection_msg``, ``raw_folder``, ``load_world_params``
- ``viewscreen_new_regionst``: changed many ``int8_t`` fields to ``bool``
- ``unit_flags3``: identified ``marked_for_gelding``
- ``body_part_status``: identified ``gelded``
## API
- New debug features related to `debug` plugin:
- Classes (C++ only): ``Signal<Signature, type_tag>``, ``DebugCategory``, ``DebugManager``
- Macros: ``TRACE``, ``DEBUG``, ``INFO``, ``WARN``, ``ERR``, ``DBG_DECLARE``, ``DBG_EXTERN``
================================================================================ ================================================================================
# 0.44.12-r1 # 0.44.12-r1

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 32 KiB

@ -28,6 +28,8 @@ include/Core.h
include/ColorText.h include/ColorText.h
include/DataDefs.h include/DataDefs.h
include/DataIdentity.h include/DataIdentity.h
include/Debug.h
include/DebugManager.h
include/VTableInterpose.h include/VTableInterpose.h
include/LuaWrapper.h include/LuaWrapper.h
include/LuaTools.h include/LuaTools.h
@ -38,6 +40,7 @@ include/MiscUtils.h
include/Module.h include/Module.h
include/Pragma.h include/Pragma.h
include/MemAccess.h include/MemAccess.h
include/Signal.hpp
include/TileTypes.h include/TileTypes.h
include/Types.h include/Types.h
include/VersionInfo.h include/VersionInfo.h
@ -54,7 +57,9 @@ include/wdirent.h
SET(MAIN_SOURCES SET(MAIN_SOURCES
Core.cpp Core.cpp
ColorText.cpp ColorText.cpp
CompilerWorkAround.cpp
DataDefs.cpp DataDefs.cpp
Debug.cpp
Error.cpp Error.cpp
VTableInterpose.cpp VTableInterpose.cpp
LuaWrapper.cpp LuaWrapper.cpp
@ -261,10 +266,14 @@ FILE(GLOB GENERATE_INPUT_SCRIPTS ${dfapi_SOURCE_DIR}/xml/*.pm ${dfapi_SOURCE_DIR
FILE(GLOB GENERATE_INPUT_XMLS ${dfapi_SOURCE_DIR}/xml/df.*.xml) FILE(GLOB GENERATE_INPUT_XMLS ${dfapi_SOURCE_DIR}/xml/df.*.xml)
set(CODEGEN_OUT ${dfapi_SOURCE_DIR}/include/df/codegen.out.xml) set(CODEGEN_OUT ${dfapi_SOURCE_DIR}/include/df/codegen.out.xml)
IF(NOT("${CMAKE_GENERATOR}" STREQUAL Ninja))
# use BYPRODUCTS instead under Ninja to avoid rebuilds
LIST(APPEND CODEGEN_OUT ${GENERATED_HDRS}) LIST(APPEND CODEGEN_OUT ${GENERATED_HDRS})
ENDIF()
ADD_CUSTOM_COMMAND( ADD_CUSTOM_COMMAND(
OUTPUT ${CODEGEN_OUT} OUTPUT ${CODEGEN_OUT}
BYPRODUCTS ${GENERATED_HDRS}
COMMAND ${PERL_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/xml/codegen.pl COMMAND ${PERL_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/xml/codegen.pl
${CMAKE_CURRENT_SOURCE_DIR}/xml ${CMAKE_CURRENT_SOURCE_DIR}/xml
${CMAKE_CURRENT_SOURCE_DIR}/include/df ${CMAKE_CURRENT_SOURCE_DIR}/include/df

@ -0,0 +1,32 @@
#include <regex>
namespace DFHack {
namespace neverCalled {
/**
* gcc/linstdc++ seems to generate code that links libstdc++ back to first
* shared object using std::regex. To allow plugins unload with std::regex in
* the code we need the std::regex functions inside libdfhack.so.
*
* If your plugin decides to use any overloads that aren't listed here it may
* stay in memory after dlclose.
*/
std::regex stdRegexPluginUnloadWorkaround()
{
std::regex fake("foo");
std::string haystack("bar is foo in the world");
std::regex fake2(std::string("bar"));
if (std::regex_match(haystack, fake))
std::swap(fake, fake2);
if (std::regex_search(haystack, fake))
std::swap(fake, fake2);
const char* haystack2 = "foo";
if (std::regex_match(haystack2, fake))
std::swap(fake, fake2);
if (std::regex_search(haystack2, fake))
std::swap(fake, fake2);
return fake;
}
}
}

@ -480,7 +480,7 @@ namespace DFHack
{ {
char seq[64]; char seq[64];
int cols = get_columns(); int cols = get_columns();
int plen = prompt.size(); int plen = prompt.size() % cols;
int len = raw_buffer.size(); int len = raw_buffer.size();
int begin = 0; int begin = 0;
int cooked_cursor = raw_cursor; int cooked_cursor = raw_cursor;
@ -493,7 +493,15 @@ namespace DFHack
} }
if (plen+len > cols) if (plen+len > cols)
len -= plen+len - cols; len -= plen+len - cols;
std::string mbstr = toLocaleMB(raw_buffer.substr(begin,len)); std::string mbstr;
try {
mbstr = toLocaleMB(raw_buffer.substr(begin,len));
}
catch (std::out_of_range&) {
// fallback check in case begin is still out of range
// (this behaves badly but at least doesn't crash)
mbstr = toLocaleMB(raw_buffer);
}
/* Cursor to left edge */ /* Cursor to left edge */
snprintf(seq,64,"\x1b[1G"); snprintf(seq,64,"\x1b[1G");
if (::write(STDIN_FILENO,seq,strlen(seq)) == -1) return; if (::write(STDIN_FILENO,seq,strlen(seq)) == -1) return;

@ -42,6 +42,7 @@ using namespace std;
#include "Core.h" #include "Core.h"
#include "DataDefs.h" #include "DataDefs.h"
#include "Console.h" #include "Console.h"
#include "MiscUtils.h"
#include "Module.h" #include "Module.h"
#include "VersionInfoFactory.h" #include "VersionInfoFactory.h"
#include "VersionInfo.h" #include "VersionInfo.h"
@ -1523,7 +1524,7 @@ Core::~Core()
} }
Core::Core() : Core::Core() :
d{new Private}, d(dts::make_unique<Private>()),
script_path_mutex{}, script_path_mutex{},
HotkeyMutex{}, HotkeyMutex{},
HotkeyCond{}, HotkeyCond{},
@ -1536,10 +1537,7 @@ Core::Core() :
{ {
// init the console. This must be always the first step! // init the console. This must be always the first step!
plug_mgr = 0; plug_mgr = 0;
vif = 0;
p = 0;
errorstate = false; errorstate = false;
vinfo = 0;
started = false; started = false;
memset(&(s_mods), 0, sizeof(s_mods)); memset(&(s_mods), 0, sizeof(s_mods));
@ -1618,27 +1616,27 @@ bool Core::Init()
#else #else
const char * path = "hack\\symbols.xml"; const char * path = "hack\\symbols.xml";
#endif #endif
vif = new DFHack::VersionInfoFactory(); auto local_vif = dts::make_unique<DFHack::VersionInfoFactory>();
cerr << "Identifying DF version.\n"; cerr << "Identifying DF version.\n";
try try
{ {
vif->loadFile(path); local_vif->loadFile(path);
} }
catch(Error::All & err) catch(Error::All & err)
{ {
std::stringstream out; std::stringstream out;
out << "Error while reading symbols.xml:\n"; out << "Error while reading symbols.xml:\n";
out << err.what() << std::endl; out << err.what() << std::endl;
delete vif;
vif = NULL;
errorstate = true; errorstate = true;
fatal(out.str()); fatal(out.str());
return false; return false;
} }
p = new DFHack::Process(vif); vif = std::move(local_vif);
vinfo = p->getDescriptor(); auto local_p = dts::make_unique<DFHack::Process>(*vif);
local_p->ValidateDescriptionOS();
vinfo = local_p->getDescriptor();
if(!vinfo || !p->isIdentified()) if(!vinfo || !local_p->isIdentified())
{ {
if (!Version::git_xml_match()) if (!Version::git_xml_match())
{ {
@ -1669,23 +1667,10 @@ bool Core::Init()
fatal("Not a known DF version.\n"); fatal("Not a known DF version.\n");
} }
errorstate = true; errorstate = true;
delete p;
p = NULL;
return false; return false;
} }
cerr << "Version: " << vinfo->getVersion() << endl; cerr << "Version: " << vinfo->getVersion() << endl;
p = std::move(local_p);
#if defined(_WIN32)
const OSType expected = OS_WINDOWS;
#elif defined(_DARWIN)
const OSType expected = OS_APPLE;
#else
const OSType expected = OS_LINUX;
#endif
if (expected != vinfo->getOS()) {
cerr << "OS mismatch; resetting to " << int(expected) << endl;
vinfo->setOS(expected);
}
// Init global object pointers // Init global object pointers
df::global::InitGlobals(); df::global::InitGlobals();
@ -2343,14 +2328,9 @@ int Core::Shutdown ( void )
plug_mgr = 0; plug_mgr = 0;
} }
// invalidate all modules // invalidate all modules
for(size_t i = 0 ; i < allModules.size(); i++)
{
delete allModules[i];
}
allModules.clear(); allModules.clear();
memset(&(s_mods), 0, sizeof(s_mods)); memset(&(s_mods), 0, sizeof(s_mods));
delete d; d.reset();
d = nullptr;
return -1; return -1;
} }
@ -2779,7 +2759,7 @@ void ClassNameCheck::getKnownClassNames(std::vector<std::string> &names)
MemoryPatcher::MemoryPatcher(Process *p_) : p(p_) MemoryPatcher::MemoryPatcher(Process *p_) : p(p_)
{ {
if (!p) if (!p)
p = Core::getInstance().p; p = Core::getInstance().p.get();
} }
MemoryPatcher::~MemoryPatcher() MemoryPatcher::~MemoryPatcher()
@ -2870,9 +2850,9 @@ TYPE * Core::get##TYPE() \
if(errorstate) return NULL;\ if(errorstate) return NULL;\
if(!s_mods.p##TYPE)\ if(!s_mods.p##TYPE)\
{\ {\
Module * mod = create##TYPE();\ std::unique_ptr<Module> mod = create##TYPE();\
s_mods.p##TYPE = (TYPE *) mod;\ s_mods.p##TYPE = (TYPE *) mod.get();\
allModules.push_back(mod);\ allModules.push_back(std::move(mod));\
}\ }\
return s_mods.p##TYPE;\ return s_mods.p##TYPE;\
} }

@ -17,7 +17,7 @@ namespace {
} }
#define INIT_GLOBAL_FUNCTION_PREFIX \ #define INIT_GLOBAL_FUNCTION_PREFIX \
DFHack::VersionInfo *global_table_ = DFHack::Core::getInstance().vinfo; \ DFHack::VersionInfo *global_table_ = DFHack::Core::getInstance().vinfo.get(); \
void * tmp_; void * tmp_;
#define INIT_GLOBAL_FUNCTION_ITEM(type,name) \ #define INIT_GLOBAL_FUNCTION_ITEM(type,name) \

@ -0,0 +1,186 @@
/**
Copyright © 2018 Pauli <suokkos@gmail.com>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any
damages arising from the use of this software.
Permission is granted to anyone to use this software for any
purpose, including commercial applications, and to alter it and
redistribute it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must
not claim that you wrote the original software. If you use this
software in a product, an acknowledgment in the product
documentation would be appreciated but is not required.
2. Altered source versions must be plainly marked as such, and
must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any source
distribution.
*/
#define _POSIX_C_SOURCE 200809L
#include "Core.h"
#include "Debug.h"
#include "DebugManager.h"
#include <chrono>
#include <iomanip>
#include <thread>
#ifdef _MSC_VER
static tm* localtime_r(const time_t* time, tm* result)
{
localtime_s(result, time);
return result;
}
#endif
namespace DFHack {
DBG_DECLARE(core,debug);
void DebugManager::registerCategory(DebugCategory& cat)
{
DEBUG(debug) << "register DebugCategory '" << cat.category()
<< "' from '" << cat.plugin()
<< "' allowed " << cat.allowed() << std::endl;
std::lock_guard<std::mutex> guard(access_mutex_);
push_back(&cat);
categorySignal(CAT_ADD, cat);
}
void DebugManager::unregisterCategory(DebugCategory& cat)
{
DEBUG(debug) << "unregister DebugCategory '" << cat.category()
<< "' from '" << cat.plugin()
<< "' allowed " << cat.allowed() << std::endl;
std::lock_guard<std::mutex> guard(access_mutex_);
auto iter = std::find(begin(), end(), &cat);
std::swap(*iter, back());
pop_back();
categorySignal(CAT_REMOVE, cat);
}
DebugRegisterBase::DebugRegisterBase(DebugCategory* cat)
{
// Make sure Core lives at least as long any DebugCategory to
// allow debug prints until all Debugcategories has been destructed
Core::getInstance();
DebugManager::getInstance().registerCategory(*cat);
}
void DebugRegisterBase::unregister(DebugCategory* cat)
{
DebugManager::getInstance().unregisterCategory(*cat);
}
static color_value selectColor(const DebugCategory::level msgLevel)
{
switch(msgLevel) {
case DebugCategory::LTRACE:
return COLOR_GREY;
case DebugCategory::LDEBUG:
return COLOR_LIGHTBLUE;
case DebugCategory::LINFO:
return COLOR_CYAN;
case DebugCategory::LWARNING:
return COLOR_YELLOW;
case DebugCategory::LERROR:
return COLOR_LIGHTRED;
}
return COLOR_WHITE;
}
#if __GNUC__
// Allow gcc to optimize tls access. It also makes sure initialized is done as
// early as possible. The early initialization helps to give threads same ids as
// gdb shows.
#define EXEC_ATTR __attribute__((tls_model("initial-exec")))
#else
#define EXEC_ATTR
#endif
namespace {
static std::atomic<uint32_t> nextId{0};
static EXEC_ATTR thread_local uint32_t thread_id{nextId.fetch_add(1)+1};
}
DebugCategory::ostream_proxy_prefix::ostream_proxy_prefix(
const DebugCategory& cat,
color_ostream& target,
const DebugCategory::level msgLevel) :
color_ostream_proxy(target)
{
color(selectColor(msgLevel));
auto now = std::chrono::system_clock::now();
tm local{};
//! \todo c++ 2020 will have std::chrono::to_stream(fmt, system_clock::now())
//! but none implements it yet.
std::time_t now_c = std::chrono::system_clock::to_time_t(now);
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()) % 1000;
// Output time in format %02H:%02M:%02S.%03ms
#if __GNUC__ < 5
// Fallback for gcc 4
char buffer[32];
size_t sz = strftime(buffer, sizeof(buffer)/sizeof(buffer[0]),
"%T.", localtime_r(&now_c, &local));
*this << (sz > 0 ? buffer : "HH:MM:SS.")
#else
*this << std::put_time(localtime_r(&now_c, &local),"%T.")
#endif
<< std::setfill('0') << std::setw(3) << ms.count()
// Thread id is allocated in the thread creation order to a thread_local
// variable
<< ":t" << thread_id
// Output plugin and category names to make it easier to locate where
// the message is coming. It would be easy replaces these with __FILE__
// and __LINE__ passed from the macro if that would be preferred prefix.
<< ':' << cat.plugin() << ':' << cat.category() << ": ";
}
DebugCategory::level DebugCategory::allowed() const noexcept
{
return allowed_.load(std::memory_order_relaxed);
}
void DebugCategory::allowed(DebugCategory::level value) noexcept
{
level old = allowed_.exchange(value, std::memory_order_relaxed);
if (old == value)
return;
TRACE(debug) << "modify DebugCategory '" << category()
<< "' from '" << plugin()
<< "' allowed " << value << std::endl;
auto& manager = DebugManager::getInstance();
manager.categorySignal(DebugManager::CAT_MODIFIED, *this);
}
DebugCategory::cstring_ref DebugCategory::category() const noexcept
{
return category_;
}
DebugCategory::cstring_ref DebugCategory::plugin() const noexcept
{
return plugin_;
}
#if __cplusplus < 201703L && __cpp_lib_atomic_is_always_lock_free < 201603
//! C++17 has std::atomic::is_always_lock_free for static_assert. Older
//! standards only provide runtime checks if an atomic type is lock free
struct failIfEnumAtomicIsNotLockFree {
failIfEnumAtomicIsNotLockFree() {
std::atomic<DebugCategory::level> test;
if (test.is_lock_free())
return;
std::cerr << __FILE__ << ':' << __LINE__
<< ": error: std::atomic<DebugCategory::level> should be lock free. Your compiler reports the atomic requires runtime locks. Either you are using a very old CPU or we need to change code to use integer atomic type." << std::endl;
std::abort();
}
} failIfEnumAtomicIsNotLockFree;
#endif
}

@ -2521,7 +2521,7 @@ static const LuaWrapper::FunctionReg dfhack_internal_module[] = {
static int internal_getmd5(lua_State *L) static int internal_getmd5(lua_State *L)
{ {
auto p = Core::getInstance().p; auto& p = Core::getInstance().p;
if (p->getDescriptor()->getOS() == OS_WINDOWS) if (p->getDescriptor()->getOS() == OS_WINDOWS)
luaL_error(L, "process MD5 not available on Windows"); luaL_error(L, "process MD5 not available on Windows");
lua_pushstring(L, p->getMD5().c_str()); lua_pushstring(L, p->getMD5().c_str());
@ -2530,7 +2530,7 @@ static int internal_getmd5(lua_State *L)
static int internal_getPE(lua_State *L) static int internal_getPE(lua_State *L)
{ {
auto p = Core::getInstance().p; auto& p = Core::getInstance().p;
if (p->getDescriptor()->getOS() != OS_WINDOWS) if (p->getDescriptor()->getOS() != OS_WINDOWS)
luaL_error(L, "process PE timestamp not available on non-Windows"); luaL_error(L, "process PE timestamp not available on non-Windows");
lua_pushinteger(L, p->getPE()); lua_pushinteger(L, p->getPE());

@ -48,7 +48,7 @@ using namespace std;
#include <string.h> #include <string.h>
using namespace DFHack; using namespace DFHack;
Process::Process(VersionInfoFactory * known_versions) Process::Process(const VersionInfoFactory& known_versions) : identified(false), my_pe(0)
{ {
int target_result; int target_result;
@ -59,10 +59,6 @@ Process::Process(VersionInfoFactory * known_versions)
real_path = realpath(path, NULL); real_path = realpath(path, NULL);
} }
identified = false;
my_descriptor = 0;
my_pe = 0;
md5wrapper md5; md5wrapper md5;
uint32_t length; uint32_t length;
uint8_t first_kb [1024]; uint8_t first_kb [1024];
@ -70,10 +66,10 @@ Process::Process(VersionInfoFactory * known_versions)
// get hash of the running DF process // get hash of the running DF process
my_md5 = md5.getHashFromFile(real_path, length, (char *) first_kb); my_md5 = md5.getHashFromFile(real_path, length, (char *) first_kb);
// create linux process, add it to the vector // create linux process, add it to the vector
VersionInfo * vinfo = known_versions->getVersionInfoByMD5(my_md5); auto vinfo = known_versions.getVersionInfoByMD5(my_md5);
if(vinfo) if(vinfo)
{ {
my_descriptor = new VersionInfo(*vinfo); my_descriptor = std::make_shared<VersionInfo>(*vinfo);
identified = true; identified = true;
} }
else else
@ -112,8 +108,7 @@ Process::Process(VersionInfoFactory * known_versions)
Process::~Process() Process::~Process()
{ {
// destroy our copy of the memory descriptor // Nothing to do here
delete my_descriptor;
} }
string Process::doReadClassName (void * vptr) string Process::doReadClassName (void * vptr)

@ -46,7 +46,7 @@ using namespace std;
#include <string.h> #include <string.h>
using namespace DFHack; using namespace DFHack;
Process::Process(VersionInfoFactory * known_versions) Process::Process(const VersionInfoFactory& known_versions) : identified(false), my_pe(0)
{ {
const char * dir_name = "/proc/self/"; const char * dir_name = "/proc/self/";
const char * exe_link_name = "/proc/self/exe"; const char * exe_link_name = "/proc/self/exe";
@ -54,10 +54,6 @@ Process::Process(VersionInfoFactory * known_versions)
const char * cmdline_name = "/proc/self/cmdline"; const char * cmdline_name = "/proc/self/cmdline";
int target_result; int target_result;
identified = false;
my_descriptor = 0;
my_pe = 0;
// valgrind replaces readlink for /proc/self/exe, but not open. // valgrind replaces readlink for /proc/self/exe, but not open.
char self_exe[1024]; char self_exe[1024];
memset(self_exe, 0, sizeof(self_exe)); memset(self_exe, 0, sizeof(self_exe));
@ -74,10 +70,10 @@ Process::Process(VersionInfoFactory * known_versions)
// get hash of the running DF process // get hash of the running DF process
my_md5 = md5.getHashFromFile(self_exe_name, length, (char *) first_kb); my_md5 = md5.getHashFromFile(self_exe_name, length, (char *) first_kb);
// create linux process, add it to the vector // create linux process, add it to the vector
VersionInfo * vinfo = known_versions->getVersionInfoByMD5(my_md5); auto vinfo = known_versions.getVersionInfoByMD5(my_md5);
if(vinfo) if(vinfo)
{ {
my_descriptor = new VersionInfo(*vinfo); my_descriptor = std::make_shared<VersionInfo>(*vinfo);
identified = true; identified = true;
} }
else else
@ -116,8 +112,7 @@ Process::Process(VersionInfoFactory * known_versions)
Process::~Process() Process::~Process()
{ {
// destroy our copy of the memory descriptor // Nothing to do here
delete my_descriptor;
} }
string Process::doReadClassName (void * vptr) string Process::doReadClassName (void * vptr)

@ -62,13 +62,11 @@ namespace DFHack
char * base; char * base;
}; };
} }
Process::Process(VersionInfoFactory * factory) Process::Process(const VersionInfoFactory& factory) : identified(false)
{ {
HMODULE hmod = NULL; HMODULE hmod = NULL;
DWORD needed; DWORD needed;
bool found = false; bool found = false;
identified = false;
my_descriptor = NULL;
d = new PlatformSpecific(); d = new PlatformSpecific();
// open process // open process
@ -97,12 +95,12 @@ Process::Process(VersionInfoFactory * factory)
return; return;
} }
my_pe = d->pe_header.FileHeader.TimeDateStamp; my_pe = d->pe_header.FileHeader.TimeDateStamp;
VersionInfo* vinfo = factory->getVersionInfoByPETimestamp(my_pe); auto vinfo = factory.getVersionInfoByPETimestamp(my_pe);
if(vinfo) if(vinfo)
{ {
identified = true; identified = true;
// give the process a data model and memory layout fixed for the base of first module // give the process a data model and memory layout fixed for the base of first module
my_descriptor = new VersionInfo(*vinfo); my_descriptor = std::make_shared<VersionInfo>(*vinfo);
my_descriptor->rebaseTo(getBase()); my_descriptor->rebaseTo(getBase());
} }
else else
@ -115,7 +113,6 @@ Process::Process(VersionInfoFactory * factory)
Process::~Process() Process::~Process()
{ {
// destroy our rebased copy of the memory descriptor // destroy our rebased copy of the memory descriptor
delete my_descriptor;
if(d->sections != NULL) if(d->sections != NULL)
free(d->sections); free(d->sections);
} }

@ -52,33 +52,28 @@ VersionInfoFactory::~VersionInfoFactory()
void VersionInfoFactory::clear() void VersionInfoFactory::clear()
{ {
// for each stored version, delete
for(size_t i = 0; i < versions.size();i++)
{
delete versions[i];
}
versions.clear(); versions.clear();
error = false; error = false;
} }
VersionInfo * VersionInfoFactory::getVersionInfoByMD5(string hash) std::shared_ptr<const VersionInfo> VersionInfoFactory::getVersionInfoByMD5(string hash) const
{ {
for(size_t i = 0; i < versions.size();i++) for (const auto& version : versions)
{ {
if(versions[i]->hasMD5(hash)) if(version->hasMD5(hash))
return versions[i]; return version;
} }
return 0; return nullptr;
} }
VersionInfo * VersionInfoFactory::getVersionInfoByPETimestamp(uintptr_t timestamp) std::shared_ptr<const VersionInfo> VersionInfoFactory::getVersionInfoByPETimestamp(uintptr_t timestamp) const
{ {
for(size_t i = 0; i < versions.size();i++) for (const auto& version : versions)
{ {
if(versions[i]->hasPE(timestamp)) if(version->hasPE(timestamp))
return versions[i]; return version;
} }
return 0; return nullptr;
} }
void VersionInfoFactory::ParseVersion (TiXmlElement* entry, VersionInfo* mem) void VersionInfoFactory::ParseVersion (TiXmlElement* entry, VersionInfo* mem)
@ -230,8 +225,8 @@ bool VersionInfoFactory::loadFile(string path_to_xml)
const char *name = pMemInfo->Attribute("name"); const char *name = pMemInfo->Attribute("name");
if(name) if(name)
{ {
VersionInfo *version = new VersionInfo(); auto version = std::make_shared<VersionInfo>();
ParseVersion( pMemInfo , version ); ParseVersion( pMemInfo , version.get() );
versions.push_back(version); versions.push_back(version);
} }
} }

@ -30,6 +30,7 @@ distribution.
#include <vector> #include <vector>
#include <stack> #include <stack>
#include <map> #include <map>
#include <memory>
#include <stdint.h> #include <stdint.h>
#include "Console.h" #include "Console.h"
#include "modules/Graphic.h" #include "modules/Graphic.h"
@ -135,7 +136,6 @@ namespace DFHack
/// Get the single Core instance or make one. /// Get the single Core instance or make one.
static Core& getInstance() static Core& getInstance()
{ {
// FIXME: add critical section for thread safety here.
static Core instance; static Core instance;
return instance; return instance;
} }
@ -191,8 +191,8 @@ namespace DFHack
DFHack::Console &getConsole() { return con; } DFHack::Console &getConsole() { return con; }
DFHack::Process * p; std::unique_ptr<DFHack::Process> p;
DFHack::VersionInfo * vinfo; std::shared_ptr<DFHack::VersionInfo> vinfo;
DFHack::Windows::df_window * screen_window; DFHack::Windows::df_window * screen_window;
static void print(const char *format, ...) Wformat(printf,1,2); static void print(const char *format, ...) Wformat(printf,1,2);
@ -209,7 +209,7 @@ namespace DFHack
~Core(); ~Core();
struct Private; struct Private;
Private *d; std::unique_ptr<Private> d;
bool Init(); bool Init();
int Update (void); int Update (void);
@ -237,7 +237,7 @@ namespace DFHack
struct Cond; struct Cond;
// FIXME: shouldn't be kept around like this // FIXME: shouldn't be kept around like this
DFHack::VersionInfoFactory * vif; std::unique_ptr<DFHack::VersionInfoFactory> vif;
// Module storage // Module storage
struct struct
{ {
@ -245,7 +245,7 @@ namespace DFHack
Notes * pNotes; Notes * pNotes;
Graphic * pGraphic; Graphic * pGraphic;
} s_mods; } s_mods;
std::vector <Module *> allModules; std::vector<std::unique_ptr<Module>> allModules;
DFHack::PluginManager * plug_mgr; DFHack::PluginManager * plug_mgr;
std::vector<std::string> script_paths[2]; std::vector<std::string> script_paths[2];

@ -0,0 +1,369 @@
/**
Copyright © 2018 Pauli <suokkos\gmail.com>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any
damages arising from the use of this software.
Permission is granted to anyone to use this software for any
purpose, including commercial applications, and to alter it and
redistribute it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must
not claim that you wrote the original software. If you use this
software in a product, an acknowledgment in the product
documentation would be appreciated but is not required.
2. Altered source versions must be plainly marked as such, and
must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any source
distribution.
*/
#pragma once
#include "ColorText.h"
#include <atomic>
#include "Core.h"
namespace DFHack {
/*! \file Debug.h
* Light weight wrappers to runtime debug output filtering. Idea is to add as
* little as possible code compared to debug output without filtering. The
* effect is archived using #TRACE, #DEBUG, #INFO, #WARN and #ERR macros. They
* "return" color_ostream object or reference that can be then used normally for
* either printf or stream style debug output.
*
* Internally macros do inline filtering check which allows compiler to have a
* fast path without debug output only checking unlikely condition. But if
* output is enabled then runtime code will jump to debug printing function
* calls. The macro setup code will also print standardized leading part of
* debug string including time stamp, plugin name and debug category name.
*
* \code{.cpp}
* #include "Debug.h"
* DBG_DECLARE(myplugin,init);
*
* DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
* {
* command_result rv = CR_OK;
* DEBUG(init, out).print("initializing\n")
* if ((rv = initWork()) != CR_OK) {
* ERR(init, out) << "initWork failed with "
* << rv << " error code" << std::endl;
* return rv;
* }
* return rv
* }
* \endcode
*
* The debug print filtering levels can be changed using debugger. Following
* gdb example would automatically setup core/init and core/render to trace
* level when SDL_init is called.
*
* \code{.unparsed}
* break SDL_init
* commands
* silent
* p DFHack::debug::core::debug_init.allowed_ = 0
* p DFHack::debug::core::debug_render.allowed_ = 0
* c
* end
* \endcode
*
*/
#ifndef __has_cpp_attribute
#define __has_cpp_attribute(x) 0
#endif
/*!
* \defgroup debug_branch_prediction Branch prediction helper macros
* Helper macro tells compiler that debug output branch is unlikely and should
* be optimized to cold section of the function.
* \{
*/
#if __cplusplus >= 202000L || __has_cpp_attribute(likely)
// c++20 will have standard branch prediction hint attributes
#define likely(x) (x) [[likely]]
#define unlikely(x) (x) [[unlikely]]
#elif defined(__GNUC__)
// gcc has builtin functions that give hints to the branch prediction
#define likely(x) (__builtin_expect(!!(x), 1))
#define unlikely(x) (__builtin_expect(!!(x), 0))
#else
#define likely(x) (x)
#define unlikely(x) (x)
#endif
//! \}
#ifdef NDEBUG
//! Reduce minimum compiled in debug levels if NDEBUG is defined
#define DBG_FILTER DFHack::DebugCategory::LINFO
#else
//! Set default compiled in debug levels to include all prints
#define DBG_FILTER DFHack::DebugCategory::LTRACE
#endif
/*!
* DebugCategory is used to enable and disable debug messages in runtime.
* Declaration and definition are handled by #DBG_DECLARE and #DBG_DEFINE
* macros. Runtime filtering support is handled by #TRACE, #DEBUG, #INFO, #WARN
* and #ERR macros.
*/
class DFHACK_EXPORT DebugCategory final {
public:
//! type helper to maybe make it easier to convert to std::string_view when
//! c++17 can be required.
using cstring = const char*;
using cstring_ref = const char*;
/*!
* Debug level enum for message filtering
*/
enum level {
LTRACE = 0,
LDEBUG = 1,
LINFO = 2,
LWARNING = 3,
LERROR = 4,
};
/*!
* \param plugin the name of plugin the category belongs to
* \param category the name of category
* \param defaultLevel optional default filtering level for the category
*/
constexpr DebugCategory(cstring_ref plugin,
cstring_ref category,
level defaultLevel = LWARNING) noexcept :
plugin_{plugin},
category_{category},
allowed_{defaultLevel}
{}
DebugCategory(const DebugCategory&) = delete;
DebugCategory(DebugCategory&&) = delete;
DebugCategory& operator=(DebugCategory) = delete;
DebugCategory& operator=(DebugCategory&&) = delete;
/*!
* Used by debug macros to check if message should be printed.
*
* It is defined in the header to allow compiler inline it and make disabled
* state a fast path without function calls.
*
* \param msgLevel the debug message level the following print belongs to
* \return boolean with true indicating that message should be printed
*/
bool isEnabled(const level msgLevel) const noexcept {
const uint32_t intLevel = static_cast<uint32_t>(msgLevel);
// Compile time filtering to allow compiling out debug checks prints
// from binary.
return static_cast<uint32_t>(DBG_FILTER) <= intLevel &&
// Runtime filtering for debug messages
static_cast<uint32_t>(allowed_.load(std::memory_order_relaxed)) <= intLevel;
}
struct DFHACK_EXPORT ostream_proxy_prefix : public color_ostream_proxy {
ostream_proxy_prefix(const DebugCategory& cat,
color_ostream& target,
DebugCategory::level level);
~ostream_proxy_prefix()
{
flush();
}
};
/*!
* Fetch a steam object proxy object for output. It also adds standard
* message components like time and plugin and category names to the line.
*
* User must make sure that the line is terminated with a line end.
*
* \param msgLevel Specifies the level which next debug message belongs
* \return color_ostream_proxy that can be used to print the message
* \sa DFHack::Core::getConsole()
*/
ostream_proxy_prefix getStream(const level msgLevel) const
{
return {*this,Core::getInstance().getConsole(),msgLevel};
}
/*!
* Add standard message components to existing output stream object to begin
* a new message line to an shared buffered object.
*
* \param msgLevel Specifies the level which next debug message belongs
* \param target An output stream object where a debug output is printed
* \return color_ostream reference that was passed as second parameter
*/
ostream_proxy_prefix getStream(const level msgLevel, color_ostream& target) const
{
return {*this,target,msgLevel};
}
/*!
* \brief Allow management code to set a new filtering level
* Caller must have locked DebugManager::access_mutex_.
*/
void allowed(level value) noexcept;
//! Query current filtering level
level allowed() const noexcept;
//! Query plugin name
cstring_ref plugin() const noexcept;
//! Query category name
cstring_ref category() const noexcept;
private:
cstring plugin_;
cstring category_;
std::atomic<level> allowed_;
#if __cplusplus >= 201703L || __cpp_lib_atomic_is_always_lock_free >= 201603
static_assert(std::atomic<level>::is_always_lock_free,
"std::atomic<level> should be lock free. You are using a very old CPU or code needs to use std::atomic<int>");
#endif
};
/**
* Handle actual registering wrong template parameter generated pointer
* calculation.
*/
class DFHACK_EXPORT DebugRegisterBase {
protected:
DebugRegisterBase(DebugCategory* category);
void unregister(DebugCategory* category);
};
/**
* Register DebugCategory to DebugManager
*/
template<DebugCategory* category>
class DebugRegister final : public DebugRegisterBase {
public:
DebugRegister() :
DebugRegisterBase{category}
{}
~DebugRegister() {
unregister(category);
}
};
#define DBG_NAME(category) debug_ ## category
/*!
* Declares a debug category. There must be only a declaration per category.
* Declaration should be in same plugin where it is used. If same category name
* is used in core and multiple plugins they all are changed with same command
* unless user specifies explicitly plugin name.
*
* Must be used in one translation unit only.
*
* \param plugin the name of plugin where debug category is used
* \param category the name of category
* \param level the initial DebugCategory::level filtering level.
*/
#define DBG_DECLARE(plugin,category, ...) \
namespace debug { namespace plugin { \
DebugCategory DBG_NAME(category){#plugin,#category,__VA_ARGS__}; \
DebugRegister<&DBG_NAME(category)> register_ ## category; \
} } \
using debug::plugin::DBG_NAME(category)
/*!
* Can be used to access a shared DBG_DECLARE category. But may not be used from
* static initializer because translation unit order is undefined.
*
* Can be used in shared headers to gain access to one definition from
* DBG_DECLARE.
* \param plugin The plugin name that must match DBG_DECLARE
* \param category The category name that must matnch DBG_DECLARE
*/
#define DBG_EXTERN(plugin,category) \
namespace debug { namespace plugin { \
extern DFHack::DebugCategory DBG_NAME(category); \
} } \
using debug::plugin::DBG_NAME(category)
#define DBG_PRINT(category,pred,level,...) \
if pred(!DFHack::DBG_NAME(category).isEnabled(level)) \
; /* nop fast path when debug category is disabled */ \
else /* else to allow macro use in if-else branches */ \
DFHack::DBG_NAME(category).getStream(level, ## __VA_ARGS__) \
/* end of DBG_PRINT */
/*!
* Open a line for trace level debug output if enabled
*
* Preferred category for inside loop debug messages or callbacks/methods called
* multiple times per second. Good example would be render or onUpdate methods.
*
* \param category the debug category
* \param optional the optional second parameter is an existing
* color_ostream_proxy object
* \return color_ostream object that can be used for stream output
*/
#define TRACE(category, ...) DBG_PRINT(category, likely, \
DFHack::DebugCategory::LTRACE, ## __VA_ARGS__)
/*!
* Open a line for debug level debug output if enabled
*
* Preferred place to use it would be commonly called functions that don't fall
* into trace category.
*
* \param category the debug category
* \param optional the optional second parameter is an existing
* color_ostream_proxy object
* \return color_ostream object that can be used for stream output
*/
#define DEBUG(category, ...) DBG_PRINT(category, likely, \
DFHack::DebugCategory::LDEBUG, ## __VA_ARGS__)
/*!
* Open a line for error level debug output if enabled
*
* Important debug messages when some rarely changed state changes. Example
* would be when a debug category filtering level changes.
*
* \param category the debug category
* \param optional the optional second parameter is an existing
* color_ostream_proxy object
* \return color_ostream object that can be used for stream output
*/
#define INFO(category, ...) DBG_PRINT(category, likely, \
DFHack::DebugCategory::LINFO, ## __VA_ARGS__)
/*!
* Open a line for warning level debug output if enabled
*
* Warning category is for recoverable errors. This generally signals that
* something unusual happened but there is code handling the error which should
* allow df continue running without issues.
*
* \param category the debug category
* \param optional the optional second parameter is an existing
* color_ostream_proxy object
* \return color_ostream object that can be used for stream output
*/
#define WARN(category, ...) DBG_PRINT(category, unlikely, \
DFHack::DebugCategory::LWARNING, ## __VA_ARGS__)
/*!
* Open a line for error level error output if enabled
*
* Errors should be printed only for cases where plugin or dfhack can't recover
* from reported error and it requires manual handling from the user.
*
* \param category the debug category
* \param optional the optional second parameter is an existing
* color_ostream_proxy object
* \return color_ostream object that can be used for stream output
*/
#define ERR(category, ...) DBG_PRINT(category, unlikely, \
DFHack::DebugCategory::LERROR, ## __VA_ARGS__)
}

@ -0,0 +1,111 @@
/**
Copyright © 2018 Pauli <suokkos@gmail.com>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any
damages arising from the use of this software.
Permission is granted to anyone to use this software for any
purpose, including commercial applications, and to alter it and
redistribute it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must
not claim that you wrote the original software. If you use this
software in a product, an acknowledgment in the product
documentation would be appreciated but is not required.
2. Altered source versions must be plainly marked as such, and
must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any source
distribution.
*/
#pragma once
#include "Export.h"
#include "Signal.hpp"
#include <mutex>
#include <vector>
namespace DFHack {
/*! \file DebugManager.h
* Expose an simple interface to runtime debug output filtering. The management
* interface is separated from output interface because output is required in
* many places while management is expected to be required only in a few places.
*/
class DebugCategory;
/*!
* \brief Container holding all registered runtime debug categories
* Singleton DebugManager is a minor extension to std::vector that allows signal
* callbacks to be attached from ui code that manages.
*
* To avoid parallel plugin unload causing issues access to DebugManager must be
* protected by mutex. The access mutex will be taken when
* DFHack::DebugCategory::~DebugCategory performs unregister calls to
* DFHack::DebugManager. The mutex will protect from memory disappearing while
* ui code is accessing or changing the runtime state.
*
* Signal emitting happens from a locked contexts. Taking the
* DFHack::DebugManager::access_mutex_ in a signal callback will results to a
* deadlock.
*
* The interface is extremely simple but enough to implement persistent filter
* states and runtime configuration code in a plugin.
*/
class DFHACK_EXPORT DebugManager : public std::vector<DebugCategory*> {
public:
friend class DebugRegisterBase;
//! access_mutex_ protects all readers and writers to DFHack::DebugManager
std::mutex access_mutex_;
//! Different signals that all will be routed to
//! DebugManager::categorySignal
enum signalType {
CAT_ADD,
CAT_REMOVE,
CAT_MODIFIED,
};
//! type to help access signal features like Connection and BlockGuard
using categorySignal_t = Signal<void (signalType, DebugCategory&)>;
/*!
* Signal object where callbacks can be connected. Connecting to a class
* method can use a lambda wrapper to the capture object pointer and correctly
* call required method.
*
* Signal is internally serialized allowing multiple threads call it
* freely.
*/
categorySignal_t categorySignal;
//! Get the singleton object
static DebugManager& getInstance() {
static DebugManager instance;
return instance;
}
//! Prevent copies
DebugManager(const DebugManager&) = delete;
//! Prevent copies
DebugManager(DebugManager&&) = delete;
//! Prevent copies
DebugManager& operator=(DebugManager) = delete;
//! Prevent copies
DebugManager& operator=(DebugManager&&) = delete;
protected:
DebugManager() = default;
//! Helper for automatic category registering and signaling
void registerCategory(DebugCategory &);
//! Helper for automatic category unregistering and signaling
void unregisterCategory(DebugCategory &);
private:
};
}

@ -33,10 +33,12 @@ distribution.
#include <iostream> #include <iostream>
#include <cstring> #include <cstring>
#include <map> #include <map>
#include <memory>
#include "VersionInfo.h"
namespace DFHack namespace DFHack
{ {
struct VersionInfo;
class Process; class Process;
//class Window; //class Window;
class DFVector; class DFVector;
@ -78,7 +80,7 @@ namespace DFHack
{ {
public: public:
/// this is the single most important destructor ever. ~px /// this is the single most important destructor ever. ~px
Process(VersionInfoFactory * known_versions); Process(const VersionInfoFactory& known_versions);
~Process(); ~Process();
/// read a 8-byte integer /// read a 8-byte integer
uint64_t readQuad(const void * address) uint64_t readQuad(const void * address)
@ -246,10 +248,15 @@ namespace DFHack
void getMemRanges(std::vector<t_memrange> & ranges ); void getMemRanges(std::vector<t_memrange> & ranges );
/// get the symbol table extension of this process /// get the symbol table extension of this process
VersionInfo *getDescriptor() std::shared_ptr<DFHack::VersionInfo> getDescriptor()
{ {
return my_descriptor; return my_descriptor;
}; };
void ValidateDescriptionOS() {
my_descriptor->ValidateOS();
};
uintptr_t getBase(); uintptr_t getBase();
/// get the DF Process ID /// get the DF Process ID
int getPID(); int getPID();
@ -291,7 +298,7 @@ namespace DFHack
std::string getMD5() { return my_md5; } std::string getMD5() { return my_md5; }
private: private:
VersionInfo * my_descriptor; std::shared_ptr<VersionInfo> my_descriptor;
PlatformSpecific *d; PlatformSpecific *d;
bool identified; bool identified;
uint32_t my_pid; uint32_t my_pid;

@ -27,13 +27,13 @@ distribution.
#ifndef MODULE_FACTORY_H_INCLUDED #ifndef MODULE_FACTORY_H_INCLUDED
#define MODULE_FACTORY_H_INCLUDED #define MODULE_FACTORY_H_INCLUDED
#include <memory>
namespace DFHack namespace DFHack
{ {
class Module; class Module;
Module* createGui(); std::unique_ptr<Module> createMaterials();
Module* createWorld(); std::unique_ptr<Module> createNotes();
Module* createMaterials(); std::unique_ptr<Module> createGraphic();
Module* createNotes();
Module* createGraphic();
} }
#endif #endif

@ -0,0 +1,782 @@
/**
Copyright © 2018 Pauli <suokkos@gmail.com>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any
damages arising from the use of this software.
Permission is granted to anyone to use this software for any
purpose, including commercial applications, and to alter it and
redistribute it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must
not claim that you wrote the original software. If you use this
software in a product, an acknowledgment in the product
documentation would be appreciated but is not required.
2. Altered source versions must be plainly marked as such, and
must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any source
distribution.
*/
#pragma once
#include <climits>
#include <atomic>
#include <functional>
#include <list>
#include <memory>
#include <mutex>
#ifdef __SSE__
#include <xmmintrin.h>
#endif
namespace DFHack {
/*!
* Select inline implementation for Signal members
* This requires careful destruction order where all connection has been
* disconnected before Signal::~Signal()
*/
class signal_inline_tag;
/*!
* Select share_ptr managed implementation for Signal members.
*
* If Connection holding object may be deleted without full serialization
* between disconnect and signal emit the holding object must be managed by
* shared_ptr and derive from ConnectedBase. It will also have to pass the
* std::shared_ptr<ConnectedBase> to connect.
* It uses two way std::weak_ptr reference to guarantee destruction of either
* object doesn't happen when call is made to them.
*
* It is still possible to get a callback call after manual disconnect from
* outside destructor. But without destruction risk the disconnect race can be
* handled by slot implementation side.
*/
class signal_shared_tag;
/**
* Used for signal_shared_tag holders that may race with destructor triggered
* disconnect and emit from Signal.
*/
class ConnectedBase {
};
template<typename Signature, typename tag = signal_inline_tag>
class Signal;
namespace details {
template<typename Signature, typename tag>
struct SignalImpl;
template<typename Signature, typename tag>
struct selectImpl;
//! Manage callback states in thread safe manner
template<typename Signature, typename tag>
class CallbackHolderImpl;
template<typename RT, typename... Args>
struct CallbackHolderBase {
using Callback = std::function<RT(Args...)>;
CallbackHolderBase(const Callback& cb) :
cb_{cb},
state_{}
{}
//! Block the connection
void block() noexcept
{
state_ += blocked;
}
//! Unblock the connection
void unblock() noexcept
{
state_ -= blocked;
}
//! Check if connection is deleted
bool erased() const noexcept
{
return state_ & deleted;
}
//! Check if connection is still active (not blocked or erased)
operator bool() const noexcept
{
return !(state_ & ~inCall);
}
protected:
//! Immutable callback object
const Callback cb_;
using state_t = unsigned;
//! Single shared state as a bitfield to simplify synchronization
//! between state changes.
std::atomic<state_t> state_;
static constexpr state_t deleted = 0x1 << (sizeof(state_t)*CHAR_BIT - 1);
static constexpr state_t inCall = deleted >> (sizeof(state_t)*CHAR_BIT/2);
static constexpr state_t blocked = 0x1;
static constexpr state_t blockedMask = inCall - 1;
static constexpr state_t inCallMask = (deleted - 1) ^ blockedMask;
};
template<typename RT, typename... Args>
class CallbackHolderImpl<RT(Args...), signal_inline_tag> :
public CallbackHolderBase<RT, Args...> {
using parent_t = CallbackHolderBase<RT, Args...>;
public:
using Callback = typename parent_t::Callback;
private:
using state_t = typename parent_t::state_t;
//! Make sure callback pointed object doesn't disappear under us
//! while we call it.
struct CallGuard {
//! Prevent copies but allow copy elision
CallGuard(const CallGuard&);
//! Allow implicit conversion to callback for simply syntax
const Callback& operator*() const noexcept
{
return holder_->cb_;
}
operator bool() const noexcept
{
return *holder_;
}
//! Mark call not to be called any more
~CallGuard() {
holder_->state_ -= parent_t::inCall;
}
private:
//! Reference to the connection
CallbackHolderImpl* holder_;
//! Mark call to be in process
CallGuard(CallbackHolderImpl* holder) :
holder_{holder}
{
holder_->state_ += parent_t::inCall;
}
//! Only allow construction from the CallbackHolderImpl::prepareCall
friend class CallbackHolderImpl;
};
public:
//! Construct the callback state for a callback
CallbackHolderImpl(const Callback& cb) :
parent_t{cb}
{}
/*!
* Data race free disconnection for the connection. It spins until
* no more callers to wait. Spinning should be problem as callbacks
* are expected to be simple and fast to execute.
*
* Must not be called from withing callback!
*
* \todo Maybe use monitor instruction to avoid busy wait and call
* std::thread::yield() if wait is longer than expected.
*/
void erase() noexcept
{
state_t oldstate;
state_t newstate;
/** Spin until no callers to this callback */
spin:
while ((oldstate = parent_t::state_) & parent_t::inCallMask) {
// pause would be portable to all old processors but there
// isn't portable way to generate it without SSE header.
#ifdef __SSE__
_mm_pause();
#endif
}
do {
if (oldstate & parent_t::inCallMask)
goto spin;
newstate = oldstate | parent_t::deleted;
} while(!parent_t::state_.compare_exchange_weak(oldstate, newstate));
}
//! Return RAII CallGuard to protect race between callback and
//! disconnect.
CallGuard prepareCall()
{
return {this};
}
};
template<typename RT, typename... Args>
class CallbackHolderImpl<RT(Args...), signal_shared_tag> :
public CallbackHolderBase<RT, Args...> {
using parent_t = CallbackHolderBase<RT, Args...>;
public:
using Callback = typename parent_t::Callback;
private:
using state_t = typename parent_t::state_t;
//! Make sure callback pointed object doesn't disappear under us
//! while we call it.
struct CallGuard {
//! Prevent copies but allow copy elision
CallGuard(const CallGuard&);
//! Allow implicit conversion to callback for simply syntax
const Callback& operator*() const noexcept
{
return holder_->cb_;
}
operator bool() const noexcept
{
// If this is not marked erased then weak_ref->lock succeeded or
// the slot isn't managed by shared_ptr<ConnectedBase>
return *holder_;
}
private:
//! Reference to the connection
CallbackHolderImpl* holder_;
std::shared_ptr<ConnectedBase> strong_ref_;
//! Mark call to be in process
CallGuard(CallbackHolderImpl* holder) :
holder_{holder},
strong_ref_{holder->weak_ref_.lock()}
{
}
//! Only allow construction from the CallbackHolderImpl::prepareCall
friend class CallbackHolderImpl;
};
std::weak_ptr<ConnectedBase> weak_ref_;
friend CallGuard;
public:
//! Construct the callback state for an automatically synchronized object
CallbackHolderImpl(const Callback& cb,
std::shared_ptr<ConnectedBase>& ref) :
parent_t{cb},
weak_ref_{ref}
{}
//! Construct the callback state for an externally synchronized object
CallbackHolderImpl(const Callback& cb) :
parent_t{cb},
weak_ref_{}
{}
/*!
* erase from destructor can't happen while we are in call because
*/
void erase() noexcept
{
parent_t::state_ |= parent_t::deleted;
}
//! Return RAII CallGuard to protect race between callback and
//! disconnect.
CallGuard prepareCall()
{
return {this};
}
};
template<typename RT, typename... Args, typename tag>
struct SignalImpl<RT(Args...), tag> : public selectImpl<RT(Args...), tag>::parent_t {
protected:
using select_t = selectImpl<RT(Args...), tag>;
using parent_t = typename select_t::parent_t;
public:
using CallbackHolder = CallbackHolderImpl<RT(Args...), tag>;
using Callback = typename CallbackHolder::Callback;
//! The container type used to store callbacks
using CallbackContainer = std::list<CallbackHolder>;
struct BlockGuard;
//! Simple connection class that is required to disconnect from the
//! signal.
struct Connection {
//! Construct a default Connection object but using it will result
//! to undefined behavior unless proper connection is assigned to it
Connection() = default;
Connection(Connection&& o) :
iter_{o.iter_},
signal_{}
{
std::swap(signal_, o.signal_);
}
Connection& operator=(Connection&& o)
{
disconnect();
iter_ = o.iter_;
std::swap(signal_, o.signal_);
return *this;
}
Connection(const Connection&) = delete;
Connection& operator=(const Connection&) = delete;
//! Disconnect from signal
void disconnect()
{
auto s = select_t::lock(signal_);
if (!s)
return;
s->disconnect(*this);
}
~Connection()
{
disconnect();
}
private:
//! Block the connection temporary
void block()
{
auto s = select_t::lock(signal_);
if (!s)
return;
iter_->block();
}
//! Restore blocked connection
void unblock()
{
auto s = select_t::lock(signal_);
if (!s)
return;
iter_->unblock();
}
//! Construct connection object
Connection(const typename CallbackContainer::iterator &iter,
typename select_t::weak_ptr ptr) :
iter_{iter},
signal_{ptr}
{}
//! std::list iterator that is used to access the callback and allow
//! removal from the list.
typename CallbackContainer::iterator iter_;
//! Reference to signal object
typename select_t::weak_ptr signal_;
friend SignalImpl;
friend BlockGuard;
};
/*!
* BlockGuard allows temporary RAII guard managed blocking of a
* connection object.
*/
struct BlockGuard {
/*!
* Block a connection that belongs to signal
* \param connection The connection that will be temporary blocked
*/
BlockGuard(Connection& connection) :
blocked_{&connection}
{
connection.block();
}
/*!
* Unblock the temporary blocked connection
*/
~BlockGuard()
{
blocked_->unblock();
}
//! Prevent copies but allow copy elision
BlockGuard(const BlockGuard&);
private:
Connection* blocked_;
};
Connection connect(const Callback& f)
{
std::lock_guard<std::mutex> lock(access_);
auto iter = callbacks_.emplace(callbacks_.begin(), f);
return {iter, parent_t::shared_from_this()};
}
Connection connect(std::shared_ptr<ConnectedBase> c, const Callback& f)
{
std::lock_guard<std::mutex> lock(access_);
auto iter = callbacks_.emplace(callbacks_.begin(), f, c);
return {iter, parent_t::shared_from_this()};
}
void disconnect(Connection& connection) {
std::lock_guard<std::mutex> lock(access_);
if (recursion_) {
deleted_ = true;
connection.iter_->erase();
} else {
callbacks_.erase(connection.iter_);
}
select_t::reset(connection.signal_);
}
template<typename Combiner>
void operator()(Combiner &combiner, Args&&... arg)
{
std::unique_lock<std::mutex> lock(access_);
struct RecursionGuard {
SignalImpl* signal_;
std::unique_lock<std::mutex>* lock_;
//! Increment access count to make sure disconnect doesn't erase
RecursionGuard(SignalImpl *signal, std::unique_lock<std::mutex>* lock) :
signal_{signal},
lock_{lock}
{
++signal_->recursion_;
}
/*!
* Clean up deleted functions in data race free and exception
* safe manner.
*/
~RecursionGuard()
{
lock_->lock();
if (--signal_->recursion_ == 0 && signal_->deleted_) {
for (auto iter = signal_->callbacks_.begin(); iter != signal_->callbacks_.end();) {
if (iter->erased())
iter = signal_->callbacks_.erase(iter);
else
++iter;
}
signal_->deleted_ = false;
}
}
} guard{this, &lock};
// Call begin in locked context to allow data race free iteration
// even if there is parallel inserts to the begin after unlocking.
auto iter = callbacks_.begin();
lock.unlock();
for (; iter != callbacks_.end(); ++iter) {
// Quickly skip blocked calls without memory writes
if (!*iter)
continue;
// Protect connection from deletion while we are about to call
// it.
auto cb = iter->prepareCall();
if (cb)
combiner(*cb, std::forward<Args>(arg)...);
}
}
void operator()(Args&&... arg)
{
auto combiner = [](const Callback& cb, Args&&... arg2)
{
cb(std::forward<Args>(arg2)...);
};
(*this)(combiner,std::forward<Args>(arg)...);
}
~SignalImpl() {
// Check that callbacks are empty. If this triggers then signal may
// have to be extended to allow automatic disconnection of active
// connections in the destructor.
if (std::is_same<tag, signal_inline_tag>::value)
assert(callbacks_.empty() && "It is very likely that this signal should use signal_shared_tag");
}
//! Simplify access to pimpl when it is inline
SignalImpl* operator->() {
return this;
}
SignalImpl& operator*() {
return *this;
}
SignalImpl() = default;
private:
SignalImpl(const SignalImpl&) :
SignalImpl{}
{}
std::mutex access_;
CallbackContainer callbacks_;
int recursion_;
bool deleted_;
friend Signal<RT(Args...), tag>;
};
template<typename RT, typename... Args>
struct selectImpl<RT(Args...), signal_inline_tag> {
using impl_t = SignalImpl<RT(Args...), signal_inline_tag>;
using interface_t = Signal<RT(Args...), signal_inline_tag>;
using type = impl_t;
using weak_ptr = impl_t*;
struct ptr_from_this {
weak_ptr shared_from_this()
{
return static_cast<weak_ptr>(this);
}
};
using parent_t = ptr_from_this;
selectImpl() = default;
// Disallow copies for inline version.
selectImpl(const selectImpl&) = delete;
selectImpl(selectImpl&&) = delete;
selectImpl& operator=(const selectImpl&) = delete;
selectImpl& operator=(selectImpl&&) = delete;
static type make() {
return {};
}
static void reset(weak_ptr& ptr) {
ptr = nullptr;
}
static weak_ptr lock(weak_ptr& ptr) {
return ptr;
}
static weak_ptr get(interface_t& signal) {
return &signal.pimpl;
}
};
template<typename RT, typename... Args>
struct selectImpl<RT(Args...), signal_shared_tag> {
using impl_t = SignalImpl<RT(Args...), signal_shared_tag>;
using interface_t = Signal<RT(Args...), signal_shared_tag>;
using type = std::shared_ptr<impl_t>;
using weak_ptr = std::weak_ptr<impl_t>;
using parent_t = std::enable_shared_from_this<impl_t>;
// Allow copies for shared version
static type make() {
return std::make_shared<SignalImpl<RT(Args...), signal_shared_tag>>();
}
static void reset(weak_ptr& ptr) {
ptr.reset();
}
static type lock(weak_ptr& ptr) {
return ptr.lock();
}
static weak_ptr get(interface_t& signal) {
return signal.pimpl;
}
};
}
/*!
* As I couldn't figure out which signal library would be a good. Too bad all
* signal libraries seem to be either heavy with unnecessary features or written
* before C++11/14 have become useable targets. That seems to indicate everyone
* is now building signal system with standard components.
*
* Implementation and interface is build around std::function holding delegates
* to a function pointer or a functor. One can put there example lambda function
* that captures this pointer from connect side. The lambda function then calls
* the slot method of object correctly.
*
* It is fairly simple to change the signal signature to directly call methods
* but internally that std::function becomes more complex. The pointer to
* member function is problematic because multiple inheritance requires
* adjustments to this. The lambda capture approach should be easy to use while
* letting compiler optimize method call in the callee side.
*
* DFHack::Signal::Connection is an connection handle. The handle can be used to
* disconnect and block a callback. Connection destructor will automatically
* disconnect from the signal.
*
* DFHack::Signal::BlockGuard is an automatic blocked callback guard object. It
* prevents any signals from calling the slot as long the BlockGuard object is
* alive. Internally it replaces the callback with an empty callback and stores
* the real callback in a member variable. Destructor then puts back the real
* callback. This allows easily recursive BlockGuard work correctly because only
* the first BlockGuard has the real callback.
*
* signal_inline_tag requires careful destruction order where all connection are
* disconnected before signal destruction. The implementation is specifically
* targeting places like static and singleton variables and widget hierarchies.
* It provides data race free connect, disconnect and emit operations.
*
* signal_shared_tag allows a bit more freedom when destroying the Signal. It
* adds data race safety between Connection, BlockGuard and destructor. If
* multiple callers need access to Signal with potential of destruction of
* original owner then callers can use Signal copy constructor to take a strong
* reference managed by shared_ptr or weak_ptr with Signal::weak_from_this().
* weak_from_this returns an object that forwards call directly to
* implementation when the shared_ptr is created using Signal::lock
*
* \param RT return type is derived from a single signature template argument
* \param Args Variable argument type list that is derived from a signature
* template argument.
* \param tag The tag type which selects between shared_ptr managed pimpl and
* inline member variables.
*/
template<typename RT, typename... Args, typename tag>
class Signal<RT(Args...), tag> : protected details::selectImpl<RT(Args...), tag> {
public:
//! Type of callable that can be connected to the signal.
using Callback = std::function<RT(Args...)>;
protected:
using select_t = details::selectImpl<RT(Args...), tag>;
using CallbackContainer = typename select_t::impl_t::CallbackContainer;
public:
using weak_ptr = typename select_t::weak_ptr;
/*!
* Simple connection class that is required to disconnect from the
* signal.
* \sa SignalImpl::Connection
*/
using Connection = typename select_t::impl_t::Connection;
/*!
* BlockGuard allows temporary RAII guard managed blocking of a
* connection object.
* \sa SignalImpl::BlockGuard
*/
using BlockGuard = typename select_t::impl_t::BlockGuard;
/*!
* Connect a callback function to the signal
*
* Safe to call from any context as long as SignalImpl destructor can't be
* called simultaneously from other thread.
*
* \param f callable that will connected to the signal
* \return connection handle that can be used to disconnect it
*/
Connection connect(const Callback& f)
{
return pimpl->connect(f);
}
/*!
* Thread safe connect variant connection and Connected object destruction
* can't race with emit from different threads.
*
* Safe to call from any context as long as SignalImpl destructor can't be
* called simultaneously from other thread.
*/
Connection connect(std::shared_ptr<ConnectedBase> c, const Callback& f)
{
static_assert(std::is_same<tag, signal_shared_tag>::value,
"Race free destruction is only possible with signal_shared_tag");
return pimpl->connect(c, f);
}
/*!
* Disconnection a callback from slots
*
* signal_inline_tag:
* This may not be called if the callback has been called in same
* thread. If callback should trigger destruction an object then
* deletion must use deferred. This rule prevents issues if other thread
* are trying to call the callback when disconnecting.
*
* signal_shared_tag:
* disconnect can be freely called from anywhere as long as caller holds a
* strong reference to the Signal. Strong reference can be obtained by using
* Connection::disconnect, Signal copy constructor to have a copy of signal
* or weak_ptr from weak_from_this() passed to Signal::lock().
*
* \param connection the object returned from DFHack::Signal::connect
*/
void disconnect(Connection& connection)
{
pimpl->disconnect(connection);
}
/*!
* Call all connected callbacks using passed arguments.
*
* signal_inline_tag:
* Must not call operator() from callbacks.
* Must not disconnect called callback from inside callback. Solution often
* is to set just atomic state variables in callback and do actual
* processing including deletion in update handler or logic vmethod.
*
* signal_shared_tag:
* Safe to call from any context as long as SignalImpl destructor can't be
* called simultaneously from other thread.
* Safe to disconnect any connection from callbacks.
*
* \param combiner that calls callbacks and processes return values
* \param arg arguments list defined by template parameter signature.
*/
template<typename Combiner>
void operator()(Combiner &combiner, Args&&... arg)
{
(*pimpl)(combiner, std::forward<Args>(arg)...);
}
/*!
* Call all connected callbacks using passed arguments.
*
* signal_inline_tag:
* Must not call operator() from callbacks.
* Must not disconnect called callback from inside callback. Solution often
* is to set just atomic state variables in callback and do actual
* processing including deletion in update handler or logic vmethod.
*
* signal_shared_tag:
* Safe to call from any context as long as SignalImpl destructor can't be
* called simultaneously from other thread.
* Safe to disconnect any connection from callbacks.
*
* \param arg arguments list defined by template parameter signature.
*/
void operator()(Args&&... arg)
{
(*pimpl)(std::forward<Args>(arg)...);
}
/*!
* Helper to lock the weak_ptr
*/
static typename select_t::type lock(weak_ptr& ptr)
{
return select_t::lock(ptr);
}
/*!
* Helper to create a weak reference to pimpl which can be used to access
* pimpl directly. If the tag is signal_shared_tag then it provides race
* free access to Signal when using Signal::lock and checking returned
* shared_ptr.
*/
weak_ptr weak_from_this() noexcept
{
return select_t::get(*this);
}
Signal() :
pimpl{select_t::make()}
{}
private:
typename select_t::type pimpl;
friend select_t;
};
}

@ -26,6 +26,7 @@ distribution.
#pragma once #pragma once
#include <algorithm> #include <algorithm>
#include <iostream>
#include <map> #include <map>
#include <sys/types.h> #include <sys/types.h>
#include <vector> #include <vector>
@ -169,5 +170,19 @@ namespace DFHack
{ {
return OS; return OS;
}; };
void ValidateOS() {
#if defined(_WIN32)
const OSType expected = OS_WINDOWS;
#elif defined(_DARWIN)
const OSType expected = OS_APPLE;
#else
const OSType expected = OS_LINUX;
#endif
if (expected != getOS()) {
std::cerr << "OS mismatch; resetting to " << int(expected) << std::endl;
setOS(expected);
}
}
}; };
} }

@ -25,6 +25,8 @@ distribution.
#pragma once #pragma once
#include <memory>
#include "Pragma.h" #include "Pragma.h"
#include "Export.h" #include "Export.h"
@ -39,12 +41,12 @@ namespace DFHack
~VersionInfoFactory(); ~VersionInfoFactory();
bool loadFile( std::string path_to_xml); bool loadFile( std::string path_to_xml);
bool isInErrorState() const {return error;}; bool isInErrorState() const {return error;};
VersionInfo * getVersionInfoByMD5(std::string md5string); std::shared_ptr<const VersionInfo> getVersionInfoByMD5(std::string md5string) const;
VersionInfo * getVersionInfoByPETimestamp(uintptr_t timestamp); std::shared_ptr<const VersionInfo> getVersionInfoByPETimestamp(uintptr_t timestamp) const;
std::vector<VersionInfo*> versions;
// trash existing list // trash existing list
void clear(); void clear();
private: private:
std::vector<std::shared_ptr<const VersionInfo>> versions;
void ParseVersion (TiXmlElement* version, VersionInfo* mem); void ParseVersion (TiXmlElement* version, VersionInfo* mem);
bool error; bool error;
}; };

@ -226,7 +226,7 @@ MOVEMENT_KEYS = {
CURSOR_UP_Z_AUX = { 0, 0, 1 }, CURSOR_DOWN_Z_AUX = { 0, 0, -1 }, CURSOR_UP_Z_AUX = { 0, 0, 1 }, CURSOR_DOWN_Z_AUX = { 0, 0, -1 },
} }
local function get_movement_delta(key, delta, big_step) function get_movement_delta(key, delta, big_step)
local info = MOVEMENT_KEYS[key] local info = MOVEMENT_KEYS[key]
if info then if info then
if info[4] then if info[4] then
@ -243,7 +243,7 @@ for i,v in ipairs(df.global.ui.main.hotkeys) do
HOTKEY_KEYS['D_HOTKEY'..(i+1)] = v HOTKEY_KEYS['D_HOTKEY'..(i+1)] = v
end end
local function get_hotkey_target(key) function get_hotkey_target(key)
local hk = HOTKEY_KEYS[key] local hk = HOTKEY_KEYS[key]
if hk and hk.cmd == df.ui_hotkey.T_cmd.Zoom then if hk and hk.cmd == df.ui_hotkey.T_cmd.Zoom then
return xyz2pos(hk.x, hk.y, hk.z) return xyz2pos(hk.x, hk.y, hk.z)

@ -36,14 +36,15 @@ using namespace std;
#include "Error.h" #include "Error.h"
#include "VersionInfo.h" #include "VersionInfo.h"
#include "MemAccess.h" #include "MemAccess.h"
#include "MiscUtils.h"
#include "ModuleFactory.h" #include "ModuleFactory.h"
#include "Core.h" #include "Core.h"
using namespace DFHack; using namespace DFHack;
Module* DFHack::createGraphic() std::unique_ptr<Module> DFHack::createGraphic()
{ {
return new Graphic(); return dts::make_unique<Graphic>();
} }
struct Graphic::Private struct Graphic::Private

@ -592,12 +592,11 @@ bool DFHack::isStoneInorganic(int material)
return true; return true;
} }
Module* DFHack::createMaterials() std::unique_ptr<Module> DFHack::createMaterials()
{ {
return new Materials(); return dts::make_unique<Materials>();
} }
Materials::Materials() Materials::Materials()
{ {
} }

@ -33,6 +33,7 @@ using namespace std;
#include "Types.h" #include "Types.h"
#include "Error.h" #include "Error.h"
#include "MemAccess.h" #include "MemAccess.h"
#include "MiscUtils.h"
#include "ModuleFactory.h" #include "ModuleFactory.h"
#include "Core.h" #include "Core.h"
#include "modules/Notes.h" #include "modules/Notes.h"
@ -40,9 +41,9 @@ using namespace std;
#include "df/ui.h" #include "df/ui.h"
using namespace DFHack; using namespace DFHack;
Module* DFHack::createNotes() std::unique_ptr<Module> DFHack::createNotes()
{ {
return new Notes(); return dts::make_unique<Notes>();
} }
// FIXME: not even a wrapper now // FIXME: not even a wrapper now

@ -1 +1 @@
Subproject commit de83a453d7e55aa48ffc92c8f4c147b1a1acd525 Subproject commit 2be1fc4afea4d3345b9b76d0f27f56087ac9b6e0

@ -107,6 +107,7 @@ if (BUILD_SUPPORTED)
DFHACK_PLUGIN(cursecheck cursecheck.cpp) DFHACK_PLUGIN(cursecheck cursecheck.cpp)
DFHACK_PLUGIN(cxxrandom cxxrandom.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(cxxrandom cxxrandom.cpp LINK_LIBRARIES lua)
DFHACK_PLUGIN(deramp deramp.cpp) DFHACK_PLUGIN(deramp deramp.cpp)
DFHACK_PLUGIN(debug debug.cpp LINK_LIBRARIES jsoncpp_lib_static)
DFHACK_PLUGIN(dig dig.cpp) DFHACK_PLUGIN(dig dig.cpp)
DFHACK_PLUGIN(digFlood digFlood.cpp) DFHACK_PLUGIN(digFlood digFlood.cpp)
add_subdirectory(diggingInvaders) add_subdirectory(diggingInvaders)
@ -138,6 +139,7 @@ if (BUILD_SUPPORTED)
DFHACK_PLUGIN(misery misery.cpp) DFHACK_PLUGIN(misery misery.cpp)
DFHACK_PLUGIN(mode mode.cpp) DFHACK_PLUGIN(mode mode.cpp)
DFHACK_PLUGIN(mousequery mousequery.cpp) DFHACK_PLUGIN(mousequery mousequery.cpp)
DFHACK_PLUGIN(nestboxes nestboxes.cpp)
DFHACK_PLUGIN(orders orders.cpp LINK_LIBRARIES jsoncpp_lib_static) DFHACK_PLUGIN(orders orders.cpp LINK_LIBRARIES jsoncpp_lib_static)
DFHACK_PLUGIN(pathable pathable.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(pathable pathable.cpp LINK_LIBRARIES lua)
DFHACK_PLUGIN(petcapRemover petcapRemover.cpp) DFHACK_PLUGIN(petcapRemover petcapRemover.cpp)

@ -879,11 +879,8 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
// Account for the military // Account for the military
else if (ENUM_ATTR(profession, military, dwarfs[dwarf]->profession)) else if (ENUM_ATTR(profession, military, dwarfs[dwarf]->profession))
dwarf_info[dwarf].state = MILITARY; dwarf_info[dwarf].state = MILITARY;
// Account for dwarves on break or migrants // Account for incoming migrants
// DF leaves the OnBreak trait type on some dwarves while they're not actually on break
// Since they have no current job, they will default to IDLE
else if (is_migrant) else if (is_migrant)
// Dwarf is unemployed with null job
{ {
dwarf_info[dwarf].state = OTHER; dwarf_info[dwarf].state = OTHER;
} }

@ -1202,14 +1202,6 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
for (int dwarf = 0; dwarf < n_dwarfs; dwarf++) for (int dwarf = 0; dwarf < n_dwarfs; dwarf++)
{ {
bool is_on_break = false;
for (auto p = dwarfs[dwarf]->status.misc_traits.begin(); p < dwarfs[dwarf]->status.misc_traits.end(); p++)
{
if ((*p)->id == misc_trait_type::Migrant || (*p)->id == misc_trait_type::OnBreak)
is_on_break = true;
}
if (Units::isBaby(dwarfs[dwarf]) || if (Units::isBaby(dwarfs[dwarf]) ||
Units::isChild(dwarfs[dwarf]) || Units::isChild(dwarfs[dwarf]) ||
dwarfs[dwarf]->profession == profession::DRUNK) dwarfs[dwarf]->profession == profession::DRUNK)
@ -1220,7 +1212,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
dwarf_info[dwarf].state = MILITARY; dwarf_info[dwarf].state = MILITARY;
else if (dwarfs[dwarf]->job.current_job == NULL) else if (dwarfs[dwarf]->job.current_job == NULL)
{ {
if (is_on_break) if (Units::getMiscTrait(dwarfs[dwarf], misc_trait_type::Migrant))
dwarf_info[dwarf].state = OTHER; dwarf_info[dwarf].state = OTHER;
else if (dwarfs[dwarf]->specific_refs.size() > 0) else if (dwarfs[dwarf]->specific_refs.size() > 0)
dwarf_info[dwarf].state = OTHER; dwarf_info[dwarf].state = OTHER;

File diff suppressed because it is too large Load Diff

@ -9,9 +9,8 @@ DFHACK_PLUGIN(counters counters.cpp)
DFHACK_PLUGIN(dumpmats dumpmats.cpp) DFHACK_PLUGIN(dumpmats dumpmats.cpp)
DFHACK_PLUGIN(eventExample eventExample.cpp) DFHACK_PLUGIN(eventExample eventExample.cpp)
DFHACK_PLUGIN(frozen frozen.cpp) DFHACK_PLUGIN(frozen frozen.cpp)
DFHACK_PLUGIN(kittens kittens.cpp) DFHACK_PLUGIN(kittens kittens.cpp LINK_LIBRARIES ${CMAKE_THREAD_LIBS_INIT})
DFHACK_PLUGIN(memview memview.cpp memutils.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(memview memview.cpp memutils.cpp LINK_LIBRARIES lua)
DFHACK_PLUGIN(nestboxes nestboxes.cpp)
DFHACK_PLUGIN(notes notes.cpp) DFHACK_PLUGIN(notes notes.cpp)
DFHACK_PLUGIN(onceExample onceExample.cpp) DFHACK_PLUGIN(onceExample onceExample.cpp)
DFHACK_PLUGIN(renderer-msg renderer-msg.cpp) DFHACK_PLUGIN(renderer-msg renderer-msg.cpp)

@ -1,12 +1,16 @@
#include <atomic> #include <atomic>
#include <vector> #include <vector>
#include <random>
#include <string> #include <string>
#include <thread>
#include "Console.h" #include "Console.h"
#include "Core.h" #include "Core.h"
#include "Debug.h"
#include "Export.h" #include "Export.h"
#include "MiscUtils.h" #include "MiscUtils.h"
#include "PluginManager.h" #include "PluginManager.h"
#include "Signal.hpp"
#include "modules/Gui.h" #include "modules/Gui.h"
#include "modules/Items.h" #include "modules/Items.h"
@ -25,6 +29,10 @@ DFHACK_PLUGIN_IS_ENABLED(is_enabled);
REQUIRE_GLOBAL(ui); REQUIRE_GLOBAL(ui);
REQUIRE_GLOBAL(world); REQUIRE_GLOBAL(world);
namespace DFHack {
DBG_DECLARE(kittens,command);
}
std::atomic<bool> shutdown_flag{false}; std::atomic<bool> shutdown_flag{false};
std::atomic<bool> final_flag{true}; std::atomic<bool> final_flag{true};
std::atomic<bool> timering{false}; std::atomic<bool> timering{false};
@ -42,6 +50,7 @@ command_result trackmenu (color_ostream &out, vector <string> & parameters);
command_result trackpos (color_ostream &out, vector <string> & parameters); command_result trackpos (color_ostream &out, vector <string> & parameters);
command_result trackstate (color_ostream &out, vector <string> & parameters); command_result trackstate (color_ostream &out, vector <string> & parameters);
command_result colormods (color_ostream &out, vector <string> & parameters); command_result colormods (color_ostream &out, vector <string> & parameters);
command_result sharedsignal (color_ostream &out, vector <string> & parameters);
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands) DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{ {
@ -51,6 +60,7 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <Plug
commands.push_back(PluginCommand("trackpos","Track mouse and designation coords (toggle).",trackpos)); commands.push_back(PluginCommand("trackpos","Track mouse and designation coords (toggle).",trackpos));
commands.push_back(PluginCommand("trackstate","Track world and map state (toggle).",trackstate)); commands.push_back(PluginCommand("trackstate","Track world and map state (toggle).",trackstate));
commands.push_back(PluginCommand("colormods","Dump colormod vectors.",colormods)); commands.push_back(PluginCommand("colormods","Dump colormod vectors.",colormods));
commands.push_back(PluginCommand("sharedsignal","Test Signal with signal_shared_tag",sharedsignal));
return CR_OK; return CR_OK;
} }
@ -195,6 +205,174 @@ command_result ktimer (color_ostream &out, vector <string> & parameters)
return CR_OK; return CR_OK;
} }
struct Connected;
using shared = std::shared_ptr<Connected>;
using weak = std::weak_ptr<Connected>;
static constexpr std::chrono::microseconds delay{1};
template<typename Derived>
struct ClearMem : public ConnectedBase {
~ClearMem()
{
memset(reinterpret_cast<void*>(this), 0xDE, sizeof(Derived));
}
};
struct Connected : public ClearMem<Connected> {
using Sig = Signal<void(int), signal_shared_tag>;
std::array<Sig::Connection,4> con;
Sig signal;
weak other;
Sig::weak_ptr other_sig;
color_ostream *out;
int id;
uint32_t count;
uint32_t caller;
alignas(64) std::atomic<uint32_t> callee;
Connected() = default;
Connected(int id) :
Connected{}
{
this->id = id;
}
void connect(color_ostream& o, shared& b, size_t pos, uint32_t c)
{
out = &o;
count = c*2;
other = b;
other_sig = b->signal.weak_from_this();
// Externally synchronized object destruction is only safe to this
// connect.
con[pos] = b->signal.connect(
[this](int) {
uint32_t old = callee.fetch_add(1);
assert(old != 0xDEDEDEDE);
std::this_thread::sleep_for(delay);
assert(callee != 0xDEDEDEDE);
});
// Shared object managed object with possibility of destruction while
// other threads calling emit must pass the shared_ptr to connect.
Connected *bptr = b.get();
b->con[pos] = signal.connect(b,
[bptr](int) {
uint32_t old = bptr->callee.fetch_add(1);
assert(old != 0xDEDEDEDE);
std::this_thread::sleep_for(delay);
assert(bptr->callee != 0xDEDEDEDE);
});
}
void reconnect(size_t pos) {
auto b = other.lock();
if (!b)
return;
// Not required to use Sig::lock because other holds strong reference to
// Signal. But this just shows how weak_ref could be used.
auto sig = Sig::lock(other_sig);
if (!sig)
return;
con[pos] = sig->connect(b,
[this](int) {
uint32_t old = callee.fetch_add(1);
assert(old != 0xDEDEDEDE);
std::this_thread::sleep_for(delay);
assert(callee != 0xDEDEDEDE);
});
}
void connect(color_ostream& o, shared& a, shared& b,size_t pos, uint32_t c)
{
out = &o;
count = c;
con[pos] = b->signal.connect(a,
[this](int) {
uint32_t old = callee.fetch_add(1);
assert(old != 0xDEDEDEDE);
std::this_thread::sleep_for(delay);
assert(callee != 0xDEDEDEDE);
});
}
Connected* operator->() noexcept
{
return this;
}
~Connected() {
INFO(command,*out).print("Connected %d had %d count. "
"It was caller %d times. "
"It was callee %d times.\n",
id, count, caller, callee.load());
}
};
command_result sharedsignal (color_ostream &out, vector <string> & parameters)
{
using rng_t = std::linear_congruential_engine<uint32_t, 747796405U, 2891336453U, 0>;
rng_t rng(std::random_device{}());
size_t count = 10;
if (0 < parameters.size()) {
std::stringstream ss(parameters[0]);
ss >> count;
DEBUG(command, out) << "Parsed " << count
<< " from paramters[0] '" << parameters[0] << '\'' << std::endl;
}
std::uniform_int_distribution<uint32_t> dis(4096,8192);
out << "Running signal_shared_tag destruction test "
<< count << " times" << std::endl;
for (size_t nr = 0; nr < count; ++nr) {
std::array<std::thread,4> t{};
// Make an object which destruction is protected by std::thread::join()
Connected external{static_cast<int>(t.size())};
TRACE(command, out) << "begin " << std::endl;
{
int id = 0;
// Make objects that are automatically protected using weak_ptr
// references that are promoted to shared_ptr when Signal is
// accessed.
std::array<shared,4> c = {
std::make_shared<Connected>(id++),
std::make_shared<Connected>(id++),
std::make_shared<Connected>(id++),
std::make_shared<Connected>(id++),
};
assert(t.size() == c.size());
for (unsigned i = 1; i < c.size(); ++i) {
c[0]->connect(out, c[0], c[i], i - 1, dis(rng));
c[i]->connect(out, c[i], c[0], 0, dis(rng));
}
external.connect(out, c[1], 1, dis(rng));
auto thr = [&out](shared c) {
TRACE(command, out) << "Thread " << c->id << " started." << std::endl;
weak ref = c;
for (;c->caller < c->count; ++c->caller) {
c->signal(c->caller);
}
TRACE(command, out) << "Thread " << c->id << " resets shared." << std::endl;
c.reset();
while((c = ref.lock())) {
++c->caller;
c->signal(c->caller);
c.reset();
std::this_thread::sleep_for(delay*25);
}
};
for (unsigned i = 0; i < c.size(); ++i) {
TRACE(command, out) << "start thread " << i << std::endl;
t[i] = std::thread{thr, c[i]};
}
}
TRACE(command, out) << "running " << std::endl;
for (;external->caller < external->count; ++external->caller) {
external->signal(external->caller);
external->reconnect(1);
}
TRACE(command, out) << "join " << std::endl;
for (unsigned i = 0; i < t.size(); ++i)
t[i].join();
}
return CR_OK;
}
command_result kittens (color_ostream &out, vector <string> & parameters) command_result kittens (color_ostream &out, vector <string> & parameters)
{ {
if (parameters.size() >= 1) if (parameters.size() >= 1)

@ -1742,11 +1742,11 @@ static void add_work_history(df::unit *unit, activity_type type)
static bool is_at_leisure(df::unit *unit) static bool is_at_leisure(df::unit *unit)
{ {
for (auto p = unit->status.misc_traits.begin(); p < unit->status.misc_traits.end(); p++) if (Units::getMiscTrait(unit, misc_trait_type::Migrant))
{ return true;
if ((*p)->id == misc_trait_type::Migrant || (*p)->id == misc_trait_type::OnBreak)
if (!unit->job.current_job && Units::getMainSocialActivity(unit))
return true; return true;
}
return false; return false;
} }

@ -28,6 +28,7 @@ namespace embark_assist {
bool clay = false; bool clay = false;
bool sand = false; bool sand = false;
bool flux = false; bool flux = false;
bool coal = false;
int8_t soil_depth; int8_t soil_depth;
int8_t offset; int8_t offset;
int16_t elevation; int16_t elevation;
@ -51,6 +52,7 @@ namespace embark_assist {
uint16_t clay_count = 0; uint16_t clay_count = 0;
uint16_t sand_count = 0; uint16_t sand_count = 0;
uint16_t flux_count = 0; uint16_t flux_count = 0;
uint16_t coal_count = 0;
uint8_t min_region_soil = 10; uint8_t min_region_soil = 10;
uint8_t max_region_soil = 0; uint8_t max_region_soil = 0;
bool waterfall = false; bool waterfall = false;
@ -90,6 +92,7 @@ namespace embark_assist {
bool clay_absent = true; bool clay_absent = true;
bool sand_absent = true; bool sand_absent = true;
bool flux_absent = true; bool flux_absent = true;
bool coal_absent = true;
std::vector<bool> possible_metals; std::vector<bool> possible_metals;
std::vector<bool> possible_economics; std::vector<bool> possible_economics;
std::vector<bool> possible_minerals; std::vector<bool> possible_minerals;
@ -113,6 +116,7 @@ namespace embark_assist {
bool clay; bool clay;
bool sand; bool sand;
bool flux; bool flux;
bool coal;
std::vector<uint16_t> metals; std::vector<uint16_t> metals;
std::vector<uint16_t> economics; std::vector<uint16_t> economics;
std::vector<uint16_t> minerals; std::vector<uint16_t> minerals;
@ -253,6 +257,7 @@ namespace embark_assist {
present_absent_ranges clay; present_absent_ranges clay;
present_absent_ranges sand; present_absent_ranges sand;
present_absent_ranges flux; present_absent_ranges flux;
present_absent_ranges coal;
soil_ranges soil_min; soil_ranges soil_min;
all_present_ranges soil_min_everywhere; all_present_ranges soil_min_everywhere;
soil_ranges soil_max; soil_ranges soil_max;

@ -143,7 +143,7 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector <Plugi
" the embark rectangle as well as normally undisplayed sites in the\n" " the embark rectangle as well as normally undisplayed sites in the\n"
" current embark region. It also has a site selection tool with more\n" " current embark region. It also has a site selection tool with more\n"
" options than DF's vanilla search tool. For detailed help invoke the\n" " options than DF's vanilla search tool. For detailed help invoke the\n"
" in game info screen. Requires 46 lines to display properly.\n" " in game info screen. Prefers 46 lines to display properly.\n"
)); ));
return CR_OK; return CR_OK;
} }

@ -44,6 +44,7 @@ namespace embark_assist {
clay, clay,
sand, sand,
flux, flux,
coal,
soil_min, soil_min,
soil_min_everywhere, soil_min_everywhere,
soil_max, soil_max,
@ -508,6 +509,7 @@ namespace embark_assist {
case fields::clay: case fields::clay:
case fields::sand: case fields::sand:
case fields::flux: case fields::flux:
case fields::coal:
{ {
embark_assist::defs::present_absent_ranges k = embark_assist::defs::present_absent_ranges::NA; embark_assist::defs::present_absent_ranges k = embark_assist::defs::present_absent_ranges::NA;
while (true) { while (true) {
@ -993,6 +995,10 @@ namespace embark_assist {
state->finder_list.push_back({ "Flux", static_cast<int8_t>(i) }); state->finder_list.push_back({ "Flux", static_cast<int8_t>(i) });
break; break;
case fields::coal:
state->finder_list.push_back({ "Coal", static_cast<int8_t>(i) });
break;
case fields::soil_min: case fields::soil_min:
state->finder_list.push_back({ "Min Soil", static_cast<int8_t>(i) }); state->finder_list.push_back({ "Min Soil", static_cast<int8_t>(i) });
break; break;
@ -1228,6 +1234,11 @@ namespace embark_assist {
static_cast<embark_assist::defs::present_absent_ranges>(state->ui[static_cast<uint8_t>(i)]->current_value); static_cast<embark_assist::defs::present_absent_ranges>(state->ui[static_cast<uint8_t>(i)]->current_value);
break; break;
case fields::coal:
finder.coal =
static_cast<embark_assist::defs::present_absent_ranges>(state->ui[static_cast<uint8_t>(i)]->current_value);
break;
case fields::soil_min: case fields::soil_min:
finder.soil_min = finder.soil_min =
static_cast<embark_assist::defs::soil_ranges>(state->ui[static_cast<uint8_t>(i)]->current_value); static_cast<embark_assist::defs::soil_ranges>(state->ui[static_cast<uint8_t>(i)]->current_value);

@ -127,8 +127,8 @@ namespace embark_assist{
help_text.push_back("Main screen control keys used by the Embark Assistant:"); help_text.push_back("Main screen control keys used by the Embark Assistant:");
help_text.push_back("i: Info/Help. Brings up this display."); help_text.push_back("i: Info/Help. Brings up this display.");
help_text.push_back("f: Brings up the Find Embark screen. See the Find page for more information."); help_text.push_back("f: Brings up the Find Embark screen. See the Find page for more information.");
help_text.push_back("c: Clears the results of a Find operation, and also cancels an operation if"); help_text.push_back("c: Clears the results of a Find operation, or cancels an operation if one is");
help_text.push_back(" one is under way."); help_text.push_back(" under way (at which time a second 'c' clears it).");
help_text.push_back("q: Quits the Embark Assistant and brings you back to the vanilla DF interface."); help_text.push_back("q: Quits the Embark Assistant and brings you back to the vanilla DF interface.");
help_text.push_back(" It can be noted that the Embark Assistant automatically cancels itself"); help_text.push_back(" It can be noted that the Embark Assistant automatically cancels itself");
help_text.push_back(" when DF leaves the embark screen either through <ESC>Abort Game or by"); help_text.push_back(" when DF leaves the embark screen either through <ESC>Abort Game or by");
@ -171,8 +171,8 @@ namespace embark_assist{
help_text.push_back("A list of all economic minerals present in the embark. Both clays and flux"); help_text.push_back("A list of all economic minerals present in the embark. Both clays and flux");
help_text.push_back("stones are economic, so they show up here as well."); help_text.push_back("stones are economic, so they show up here as well.");
help_text.push_back("In addition to the above, the Find functionality can also produce blinking"); help_text.push_back("In addition to the above, the Find functionality can also produce blinking");
help_text.push_back("overlays over the region map and the middle world map to indicate where"); help_text.push_back("overlays over the Local, Region, and World maps to indicate where");
help_text.push_back("matching embarks are found. The region display marks the top left corner of"); help_text.push_back("matching embarks are found. The Local display marks the top left corner of");
help_text.push_back("a matching embark rectangle as a matching tile."); help_text.push_back("a matching embark rectangle as a matching tile.");
break; break;
@ -257,14 +257,13 @@ namespace embark_assist{
help_text.push_back("- The geo information is gathered by code which is essentially a"); help_text.push_back("- The geo information is gathered by code which is essentially a");
help_text.push_back(" copy of parts of prospector's code adapted for this plugin."); help_text.push_back(" copy of parts of prospector's code adapted for this plugin.");
help_text.push_back("- Clay determination is made by finding the reaction MAKE_CLAY_BRICKS."); help_text.push_back("- Clay determination is made by finding the reaction MAKE_CLAY_BRICKS.");
help_text.push_back(" Flux determination is made by finding the reaction PIG_IRON_MAKING."); help_text.push_back("- Flux determination is made by finding the reaction PIG_IRON_MAKING.");
help_text.push_back("- Right world map overlay not implemented as author has failed to"); help_text.push_back("- Coal is detected by finding COAL producing reactions on minerals.");
help_text.push_back(" emulate the sizing logic exactly.");
help_text.push_back("- There's currently a DF bug (#0010267) that causes adamantine spires"); help_text.push_back("- There's currently a DF bug (#0010267) that causes adamantine spires");
help_text.push_back(" reaching caverns that have been removed at world gen to fail to be"); help_text.push_back(" reaching caverns that have been removed at world gen to fail to be");
help_text.push_back(" generated at all. It's likely this bug also affects magma pools."); help_text.push_back(" generated at all. It's likely this bug also affects magma pools.");
help_text.push_back(" This plugin does not address this but scripts can correct it."); help_text.push_back(" This plugin does not address this but scripts can correct it.");
help_text.push_back("Version 0.5 2018-07-13"); help_text.push_back("Version 0.8 2018-12-04");
break; break;
} }

@ -46,6 +46,7 @@ namespace embark_assist {
bool clay_found = false; bool clay_found = false;
bool sand_found = false; bool sand_found = false;
bool flux_found = false; bool flux_found = false;
bool coal_found = false;
uint8_t max_soil = 0; uint8_t max_soil = 0;
bool uneven = false; bool uneven = false;
int16_t min_temperature = survey_results->at(x).at(y).min_temperature[mlt->at(start_x).at(start_y).biome_offset]; int16_t min_temperature = survey_results->at(x).at(y).min_temperature[mlt->at(start_x).at(start_y).biome_offset];
@ -174,6 +175,12 @@ namespace embark_assist {
flux_found = true; flux_found = true;
} }
// Coal
if (mlt->at(i).at(k).coal) {
if (finder->coal == embark_assist::defs::present_absent_ranges::Absent) return false;
coal_found = true;
}
// Min Soil // Min Soil
if (finder->soil_min != embark_assist::defs::soil_ranges::NA && if (finder->soil_min != embark_assist::defs::soil_ranges::NA &&
mlt->at(i).at(k).soil_depth < static_cast<uint16_t>(finder->soil_min) && mlt->at(i).at(k).soil_depth < static_cast<uint16_t>(finder->soil_min) &&
@ -335,6 +342,9 @@ namespace embark_assist {
// Flux // Flux
if (finder->flux == embark_assist::defs::present_absent_ranges::Present && !flux_found) return false; if (finder->flux == embark_assist::defs::present_absent_ranges::Present && !flux_found) return false;
// Coal
if (finder->coal == embark_assist::defs::present_absent_ranges::Present && !coal_found) return false;
// Min Soil // Min Soil
if (finder->soil_min != embark_assist::defs::soil_ranges::NA && if (finder->soil_min != embark_assist::defs::soil_ranges::NA &&
finder->soil_min_everywhere == embark_assist::defs::all_present_ranges::Present && finder->soil_min_everywhere == embark_assist::defs::all_present_ranges::Present &&
@ -571,6 +581,7 @@ namespace embark_assist {
case embark_assist::defs::present_absent_ranges::Present: case embark_assist::defs::present_absent_ranges::Present:
if (tile->clay_count == 0) return false; if (tile->clay_count == 0) return false;
break; break;
case embark_assist::defs::present_absent_ranges::Absent: case embark_assist::defs::present_absent_ranges::Absent:
if (tile->clay_count > 256 - embark_size) return false; if (tile->clay_count > 256 - embark_size) return false;
break; break;
@ -584,6 +595,7 @@ namespace embark_assist {
case embark_assist::defs::present_absent_ranges::Present: case embark_assist::defs::present_absent_ranges::Present:
if (tile->sand_count == 0) return false; if (tile->sand_count == 0) return false;
break; break;
case embark_assist::defs::present_absent_ranges::Absent: case embark_assist::defs::present_absent_ranges::Absent:
if (tile->sand_count > 256 - embark_size) return false; if (tile->sand_count > 256 - embark_size) return false;
break; break;
@ -597,11 +609,26 @@ namespace embark_assist {
case embark_assist::defs::present_absent_ranges::Present: case embark_assist::defs::present_absent_ranges::Present:
if (tile->flux_count == 0) return false; if (tile->flux_count == 0) return false;
break; break;
case embark_assist::defs::present_absent_ranges::Absent: case embark_assist::defs::present_absent_ranges::Absent:
if (tile->flux_count > 256 - embark_size) return false; if (tile->flux_count > 256 - embark_size) return false;
break; break;
} }
// Coal
switch (finder->coal) {
case embark_assist::defs::present_absent_ranges::NA:
break; // No restriction
case embark_assist::defs::present_absent_ranges::Present:
if (tile->coal_count == 0) return false;
break;
case embark_assist::defs::present_absent_ranges::Absent:
if (tile->coal_count > 256 - embark_size) return false;
break;
}
// Soil Min // Soil Min
switch (finder->soil_min) { switch (finder->soil_min) {
case embark_assist::defs::soil_ranges::NA: case embark_assist::defs::soil_ranges::NA:
@ -1027,6 +1054,7 @@ namespace embark_assist {
case embark_assist::defs::present_absent_ranges::Present: case embark_assist::defs::present_absent_ranges::Present:
if (tile->clay_count == 0) return false; if (tile->clay_count == 0) return false;
break; break;
case embark_assist::defs::present_absent_ranges::Absent: case embark_assist::defs::present_absent_ranges::Absent:
if (tile->clay_count == 256) return false; if (tile->clay_count == 256) return false;
break; break;
@ -1040,6 +1068,7 @@ namespace embark_assist {
case embark_assist::defs::present_absent_ranges::Present: case embark_assist::defs::present_absent_ranges::Present:
if (tile->sand_count == 0) return false; if (tile->sand_count == 0) return false;
break; break;
case embark_assist::defs::present_absent_ranges::Absent: case embark_assist::defs::present_absent_ranges::Absent:
if (tile->sand_count == 256) return false; if (tile->sand_count == 256) return false;
break; break;
@ -1053,11 +1082,26 @@ namespace embark_assist {
case embark_assist::defs::present_absent_ranges::Present: case embark_assist::defs::present_absent_ranges::Present:
if (tile->flux_count == 0) return false; if (tile->flux_count == 0) return false;
break; break;
case embark_assist::defs::present_absent_ranges::Absent: case embark_assist::defs::present_absent_ranges::Absent:
if (tile->flux_count == 256) return false; if (tile->flux_count == 256) return false;
break; break;
} }
// Coal
switch (finder->coal) {
case embark_assist::defs::present_absent_ranges::NA:
break; // No restriction
case embark_assist::defs::present_absent_ranges::Present:
if (tile->coal_count == 0) return false;
break;
case embark_assist::defs::present_absent_ranges::Absent:
if (tile->coal_count == 256) return false;
break;
}
// Soil Min // Soil Min
switch (finder->soil_min) { switch (finder->soil_min) {
case embark_assist::defs::soil_ranges::NA: case embark_assist::defs::soil_ranges::NA:
@ -1518,11 +1562,11 @@ uint16_t embark_assist::matcher::find(embark_assist::defs::match_iterators *iter
preliminary_matches = preliminary_world_match(survey_results, &iterator->finder, match_results); preliminary_matches = preliminary_world_match(survey_results, &iterator->finder, match_results);
if (preliminary_matches == 0) { if (preliminary_matches == 0) {
out.printerr("matcher::find: Preliminarily matching world tiles: %i\n", preliminary_matches); out.printerr("matcher::find: Preliminarily matching World Tiles: %i\n", preliminary_matches);
return 0; return 0;
} }
else { else {
out.print("matcher::find: Preliminarily matching world tiles: %i\n", preliminary_matches); out.print("matcher::find: Preliminarily matching World Tiles: %i\n", preliminary_matches);
} }
while (screen->location.region_pos.x != 0 || screen->location.region_pos.y != 0) { while (screen->location.region_pos.x != 0 || screen->location.region_pos.y != 0) {

@ -48,7 +48,7 @@ namespace embark_assist {
std::vector<display_strings> embark_info; std::vector<display_strings> embark_info;
Screen::Pen region_match_grid[16][16]; Screen::Pen local_match_grid[16][16];
pen_column *world_match_grid = nullptr; pen_column *world_match_grid = nullptr;
uint16_t match_count = 0; uint16_t match_count = 0;
@ -60,26 +60,15 @@ namespace embark_assist {
//==================================================================== //====================================================================
/* // Attempt to replicate the DF logic for sizing the right world map. This // Logic for sizing the World map to the right.
// code seems to compute the values correctly, but the author hasn't been df::coord2d world_dimension_size(uint16_t map_size, uint16_t region_size) {
// able to apply them at the same time as DF does to 100%. uint16_t factor = (map_size - 1 + region_size - 1) / region_size;
// DF seems to round down on 0.5 values. uint16_t result = (map_size + ((factor - 1) / 2)) / factor;
df::coord2d world_dimension_size(uint16_t available_screen, uint16_t map_size) { if (result > region_size) { result = region_size; }
uint16_t result;
for (uint16_t factor = 1; factor < 17; factor++) {
result = map_size / factor;
if ((map_size - result * factor) * 2 != factor) {
result = (map_size + factor / 2) / factor;
}
if (result <= available_screen) {
return{ result, factor}; return{ result, factor};
} }
}
return{16, 16}; // Should never get here.
}
*/
//==================================================================== //====================================================================
class ViewscreenOverlay : public df::viewscreen_choose_start_sitest class ViewscreenOverlay : public df::viewscreen_choose_start_sitest
@ -114,10 +103,14 @@ namespace embark_assist {
state->embark_update(); state->embark_update();
} }
else if (input->count(df::interface_key::CUSTOM_C)) { else if (input->count(df::interface_key::CUSTOM_C)) {
state->match_active = false; if (state->matching) {
state->matching = false; state->matching = false;
}
else {
state->match_active = false;
state->clear_match_callback(); state->clear_match_callback();
} }
}
else if (input->count(df::interface_key::CUSTOM_F)) { else if (input->count(df::interface_key::CUSTOM_F)) {
if (!state->match_active && !state->matching) { if (!state->match_active && !state->matching) {
embark_assist::finder_ui::init(embark_assist::overlay::plugin_self, state->find_callback, state->max_inorganic); embark_assist::finder_ui::init(embark_assist::overlay::plugin_self, state->find_callback, state->max_inorganic);
@ -155,6 +148,7 @@ namespace embark_assist {
Screen::Pen pen_lr(' ', COLOR_LIGHTRED); Screen::Pen pen_lr(' ', COLOR_LIGHTRED);
Screen::Pen pen_w(' ', COLOR_WHITE); Screen::Pen pen_w(' ', COLOR_WHITE);
Screen::Pen pen_g(' ', COLOR_GREY);
Screen::paintString(pen_lr, width - 28, 20, DFHack::Screen::getKeyDisplay(df::interface_key::CUSTOM_I).c_str(), false); Screen::paintString(pen_lr, width - 28, 20, DFHack::Screen::getKeyDisplay(df::interface_key::CUSTOM_I).c_str(), false);
Screen::paintString(pen_w, width - 27, 20, ": Embark Assistant Info", false); Screen::paintString(pen_w, width - 27, 20, ": Embark Assistant Info", false);
@ -166,6 +160,7 @@ namespace embark_assist {
Screen::paintString(pen_w, width - 27, 23, ": Quit Embark Assistant", false); Screen::paintString(pen_w, width - 27, 23, ": Quit Embark Assistant", false);
Screen::paintString(pen_w, width - 28, 25, "Matching World Tiles:", false); Screen::paintString(pen_w, width - 28, 25, "Matching World Tiles:", false);
Screen::paintString(empty_pen, width - 6, 25, to_string(state->match_count), false); Screen::paintString(empty_pen, width - 6, 25, to_string(state->match_count), false);
Screen::paintString(pen_g, width - 28, 26, "(Those on the Region Map)", false);
if (height > 25) { // Mask the vanilla DF find help as it's overridden. if (height > 25) { // Mask the vanilla DF find help as it's overridden.
Screen::paintString(pen_w, 50, height - 2, " ", false); Screen::paintString(pen_w, 50, height - 2, " ", false);
@ -221,29 +216,22 @@ namespace embark_assist {
for (uint8_t i = 0; i < 16; i++) { for (uint8_t i = 0; i < 16; i++) {
for (uint8_t k = 0; k < 16; k++) { for (uint8_t k = 0; k < 16; k++) {
if (state->region_match_grid[i][k].ch) { if (state->local_match_grid[i][k].ch) {
Screen::paintTile(state->region_match_grid[i][k], i + 1, k + 2); Screen::paintTile(state->local_match_grid[i][k], i + 1, k + 2);
} }
} }
} }
/* // Stuff for trying to replicate the DF right world map sizing logic. Close, but not there. df::coord2d size_factor_x = world_dimension_size(world->worldgen.worldgen_parms.dim_x, width / 2 - 24);
Screen::Pen pen(' ', COLOR_YELLOW); df::coord2d size_factor_y = world_dimension_size(world->worldgen.worldgen_parms.dim_y, height - 9);
// Boundaries of the top level world map
Screen::paintString(pen, width / 2 - 5, 2, "X", false); // Marks UL corner of right world map. Constant
// Screen::paintString(pen, width - 30, 2, "X", false); // Marks UR corner of right world map area.
// Screen::paintString(pen, width / 2 - 5, height - 8, "X", false); // BL corner of right world map area.
// Screen::paintString(pen, width - 30, height - 8, "X", false); // BR corner of right world map area.
uint16_t l_width = width - 30 - (width / 2 - 5) + 1; // Horizontal space available for right world map.
uint16_t l_height = height - 8 - 2 + 1; // Vertical space available for right world map.
df::coord2d size_factor_x = world_dimension_size(l_width, world->worldgen.worldgen_parms.dim_x);
df::coord2d size_factor_y = world_dimension_size(l_height, world->worldgen.worldgen_parms.dim_y);
Screen::paintString(pen, width / 2 - 5 + size_factor_x.x - 1, 2, "X", false); for (uint16_t i = 0; i < world->worldgen.worldgen_parms.dim_x; i++) {
Screen::paintString(pen, width / 2 - 5, 2 + size_factor_y.x - 1, "X", false); for (uint16_t k = 0; k < world->worldgen.worldgen_parms.dim_y; k++) {
Screen::paintString(pen, width / 2 - 5 + size_factor_x.x - 1, 2 + size_factor_y.x - 1, "X", false); if (state->world_match_grid[i][k].ch) {
*/ Screen::paintTile(state->world_match_grid[i][k], width / 2 - 5 + min(size_factor_x.x - 1, i / size_factor_x.y), 2 + min(size_factor_y.x - 1, k / size_factor_y.y));
}
}
}
} }
if (state->matching) { if (state->matching) {
@ -351,6 +339,10 @@ void embark_assist::overlay::set_embark(embark_assist::defs::site_infos *site_in
state->embark_info.push_back({ Screen::Pen(' ', COLOR_RED), "Clay" }); state->embark_info.push_back({ Screen::Pen(' ', COLOR_RED), "Clay" });
} }
if (site_info->coal) {
state->embark_info.push_back({ Screen::Pen(' ', COLOR_GREY), "Coal" });
}
state->embark_info.push_back({ Screen::Pen(' ', COLOR_BROWN), "Soil " + std::to_string(site_info->min_soil) + " - " + std::to_string(site_info->max_soil) }); state->embark_info.push_back({ Screen::Pen(' ', COLOR_BROWN), "Soil " + std::to_string(site_info->min_soil) + " - " + std::to_string(site_info->max_soil) });
if (site_info->flat) { if (site_info->flat) {
@ -390,11 +382,11 @@ void embark_assist::overlay::set_mid_level_tile_match(embark_assist::defs::mlt_m
for (uint8_t i = 0; i < 16; i++) { for (uint8_t i = 0; i < 16; i++) {
for (uint8_t k = 0; k < 16; k++) { for (uint8_t k = 0; k < 16; k++) {
if (mlt_matches[i][k]) { if (mlt_matches[i][k]) {
state->region_match_grid[i][k] = green_x_pen; state->local_match_grid[i][k] = green_x_pen;
} }
else { else {
state->region_match_grid[i][k] = empty_pen; state->local_match_grid[i][k] = empty_pen;
} }
} }
} }
@ -411,7 +403,7 @@ void embark_assist::overlay::clear_match_results() {
for (uint8_t i = 0; i < 16; i++) { for (uint8_t i = 0; i < 16; i++) {
for (uint8_t k = 0; k < 16; k++) { for (uint8_t k = 0; k < 16; k++) {
state->region_match_grid[i][k] = empty_pen; state->local_match_grid[i][k] = empty_pen;
} }
} }
} }

@ -10,6 +10,7 @@
#include "modules/Materials.h" #include "modules/Materials.h"
#include "DataDefs.h" #include "DataDefs.h"
#include "df/builtin_mats.h"
#include "df/coord2d.h" #include "df/coord2d.h"
#include "df/creature_interaction_effect.h" #include "df/creature_interaction_effect.h"
#include "df/creature_interaction_effect_display_symbolst.h" #include "df/creature_interaction_effect_display_symbolst.h"
@ -34,6 +35,9 @@
#include "df/interaction_target_materialst.h" #include "df/interaction_target_materialst.h"
#include "df/material_common.h" #include "df/material_common.h"
#include "df/reaction.h" #include "df/reaction.h"
#include "df/reaction_product.h"
#include "df/reaction_product_itemst.h"
#include "df/reaction_product_type.h"
#include "df/region_map_entry.h" #include "df/region_map_entry.h"
#include "df/syndrome.h" #include "df/syndrome.h"
#include "df/viewscreen.h" #include "df/viewscreen.h"
@ -66,6 +70,7 @@ namespace embark_assist {
struct states { struct states {
uint16_t clay_reaction = -1; uint16_t clay_reaction = -1;
uint16_t flux_reaction = -1; uint16_t flux_reaction = -1;
std::vector<uint16_t> coals;
uint16_t x; uint16_t x;
uint16_t y; uint16_t y;
uint8_t local_min_x; uint8_t local_min_x;
@ -104,6 +109,19 @@ namespace embark_assist {
out.printerr("The reaction 'PIG_IRON_MAKING' was not found, so flux can't be identified.\n"); out.printerr("The reaction 'PIG_IRON_MAKING' was not found, so flux can't be identified.\n");
} }
for (uint16_t i = 0; i < world->raws.inorganics.size(); i++) {
for (uint16_t k = 0; k < world->raws.inorganics[i]->economic_uses.size(); k++) {
for (uint16_t l = 0; l < world->raws.reactions.reactions[world->raws.inorganics[i]->economic_uses[k]]->products.size(); l++) {
df::reaction_product_itemst *product = static_cast<df::reaction_product_itemst*>(world->raws.reactions.reactions[world->raws.inorganics[i]->economic_uses[k]]->products[l]);
if (product->mat_type == df::builtin_mats::COAL) {
state->coals.push_back(i);
break;
}
}
}
}
for (uint16_t i = 0; i < world_data->geo_biomes.size(); i++) { for (uint16_t i = 0; i < world_data->geo_biomes.size(); i++) {
geo_summary->at(i).possible_metals.resize(state->max_inorganic); geo_summary->at(i).possible_metals.resize(state->max_inorganic);
geo_summary->at(i).possible_economics.resize(state->max_inorganic); geo_summary->at(i).possible_economics.resize(state->max_inorganic);
@ -154,6 +172,13 @@ namespace embark_assist {
} }
} }
for (uint16_t l = 0; l < state->coals.size(); l++) {
if (layer->mat_index == state->coals[l]) {
geo_summary->at(i).coal_absent = false;
break;
}
}
size = (uint16_t)layer->vein_mat.size(); size = (uint16_t)layer->vein_mat.size();
for (uint16_t l = 0; l < size; l++) { for (uint16_t l = 0; l < size; l++) {
@ -176,6 +201,14 @@ namespace embark_assist {
geo_summary->at(i).flux_absent = false; geo_summary->at(i).flux_absent = false;
} }
} }
for (uint16_t m = 0; m < state->coals.size(); m++) {
if (vein== state->coals[m]) {
geo_summary->at(i).coal_absent = false;
break;
}
}
} }
} }
@ -531,6 +564,7 @@ void embark_assist::survey::high_level_world_survey(embark_assist::defs::geo_dat
results.clay_count = 0; results.clay_count = 0;
results.sand_count = 0; results.sand_count = 0;
results.flux_count = 0; results.flux_count = 0;
results.coal_count = 0;
results.min_region_soil = 10; results.min_region_soil = 10;
results.max_region_soil = 0; results.max_region_soil = 0;
results.waterfall = false; results.waterfall = false;
@ -576,6 +610,7 @@ void embark_assist::survey::high_level_world_survey(embark_assist::defs::geo_dat
if (!geo_summary->at(geo_index).clay_absent) results.clay_count++; if (!geo_summary->at(geo_index).clay_absent) results.clay_count++;
if (!geo_summary->at(geo_index).sand_absent) results.sand_count++; if (!geo_summary->at(geo_index).sand_absent) results.sand_count++;
if (!geo_summary->at(geo_index).flux_absent) results.flux_count++; if (!geo_summary->at(geo_index).flux_absent) results.flux_count++;
if (!geo_summary->at(geo_index).coal_absent) results.coal_count++;
if (geo_summary->at(geo_index).soil_size < results.min_region_soil) if (geo_summary->at(geo_index).soil_size < results.min_region_soil)
results.min_region_soil = geo_summary->at(geo_index).soil_size; results.min_region_soil = geo_summary->at(geo_index).soil_size;
@ -614,6 +649,8 @@ void embark_assist::survey::high_level_world_survey(embark_assist::defs::geo_dat
if (results.clay_count == offset_count) results.clay_count = 256; if (results.clay_count == offset_count) results.clay_count = 256;
if (results.sand_count == offset_count) results.sand_count = 256; if (results.sand_count == offset_count) results.sand_count = 256;
if (results.flux_count == offset_count) results.flux_count = 256; if (results.flux_count == offset_count) results.flux_count = 256;
if (results.coal_count == offset_count) results.coal_count = 256;
for (uint8_t l = 0; l < 3; l++) { for (uint8_t l = 0; l < 3; l++) {
if (results.savagery_count[l] == offset_count) results.savagery_count[l] = 256; if (results.savagery_count[l] == offset_count) results.savagery_count[l] = 256;
if (results.evilness_count[l] == offset_count) results.evilness_count[l] = 256; if (results.evilness_count[l] == offset_count) results.evilness_count[l] = 256;
@ -776,6 +813,8 @@ void embark_assist::survey::survey_mid_level_tile(embark_assist::defs::geo_data
mlt->at(i).at(k).clay = false; mlt->at(i).at(k).clay = false;
mlt->at(i).at(k).sand = false; mlt->at(i).at(k).sand = false;
mlt->at(i).at(k).flux = false; mlt->at(i).at(k).flux = false;
mlt->at(i).at(k).coal = false;
if (max_soil_depth == 0) { if (max_soil_depth == 0) {
mlt->at(i).at(k).soil_depth = 0; mlt->at(i).at(k).soil_depth = 0;
} }
@ -869,6 +908,13 @@ void embark_assist::survey::survey_mid_level_tile(embark_assist::defs::geo_data
mlt->at(i).at(k).flux = true; mlt->at(i).at(k).flux = true;
} }
} }
for (uint16_t m = 0; m < state->coals.size(); m++) {
if (layer->mat_index == state->coals [m]) {
mlt->at(i).at(k).coal = true;
break;
}
}
} }
end_check_m = static_cast<uint16_t>(layer->vein_mat.size()); end_check_m = static_cast<uint16_t>(layer->vein_mat.size());
@ -895,6 +941,13 @@ void embark_assist::survey::survey_mid_level_tile(embark_assist::defs::geo_data
mlt->at(i).at(k).flux = true; mlt->at(i).at(k).flux = true;
} }
} }
for (uint16_t n = 0; n < state->coals.size(); n++) {
if (layer->vein_mat [m] == state->coals[n]) {
mlt->at(i).at(k).coal = true;
break;
}
}
} }
} }
@ -911,6 +964,7 @@ void embark_assist::survey::survey_mid_level_tile(embark_assist::defs::geo_data
survey_results->at(x).at(y).clay_count = 0; survey_results->at(x).at(y).clay_count = 0;
survey_results->at(x).at(y).sand_count = 0; survey_results->at(x).at(y).sand_count = 0;
survey_results->at(x).at(y).flux_count = 0; survey_results->at(x).at(y).flux_count = 0;
survey_results->at(x).at(y).coal_count = 0;
survey_results->at(x).at(y).min_region_soil = 10; survey_results->at(x).at(y).min_region_soil = 10;
survey_results->at(x).at(y).max_region_soil = 0; survey_results->at(x).at(y).max_region_soil = 0;
survey_results->at(x).at(y).savagery_count[0] = 0; survey_results->at(x).at(y).savagery_count[0] = 0;
@ -929,6 +983,7 @@ void embark_assist::survey::survey_mid_level_tile(embark_assist::defs::geo_data
if (mlt->at(i).at(k).clay) { survey_results->at(x).at(y).clay_count++; } if (mlt->at(i).at(k).clay) { survey_results->at(x).at(y).clay_count++; }
if (mlt->at(i).at(k).sand) { survey_results->at(x).at(y).sand_count++; } if (mlt->at(i).at(k).sand) { survey_results->at(x).at(y).sand_count++; }
if (mlt->at(i).at(k).flux) { survey_results->at(x).at(y).flux_count++; } if (mlt->at(i).at(k).flux) { survey_results->at(x).at(y).flux_count++; }
if (mlt->at(i).at(k).coal) { survey_results->at(x).at(y).coal_count++; }
if (mlt->at(i).at(k).soil_depth < survey_results->at(x).at(y).min_region_soil) { if (mlt->at(i).at(k).soil_depth < survey_results->at(x).at(y).min_region_soil) {
survey_results->at(x).at(y).min_region_soil = mlt->at(i).at(k).soil_depth; survey_results->at(x).at(y).min_region_soil = mlt->at(i).at(k).soil_depth;
@ -1170,6 +1225,7 @@ void embark_assist::survey::survey_embark(embark_assist::defs::mid_level_tiles *
site_info->clay = false; site_info->clay = false;
site_info->sand = false; site_info->sand = false;
site_info->flux = false; site_info->flux = false;
site_info->coal = false;
site_info->metals.clear(); site_info->metals.clear();
site_info->economics.clear(); site_info->economics.clear();
site_info->metals.clear(); site_info->metals.clear();
@ -1222,6 +1278,10 @@ void embark_assist::survey::survey_embark(embark_assist::defs::mid_level_tiles *
site_info->flux = true; site_info->flux = true;
} }
if (mlt->at(i).at(k).coal) {
site_info->coal = true;
}
for (uint16_t l = 0; l < state->max_inorganic; l++) { for (uint16_t l = 0; l < state->max_inorganic; l++) {
metals[l] = metals[l] || mlt->at(i).at(k).metals[l]; metals[l] = metals[l] || mlt->at(i).at(k).metals[l];
economics[l] = economics[l] || mlt->at(i).at(k).economics[l]; economics[l] = economics[l] || mlt->at(i).at(k).economics[l];

@ -100,6 +100,11 @@ enum ConfigFlags {
CF_ALLOW_HUNTING = 4, CF_ALLOW_HUNTING = 4,
}; };
// Value of 0 for max dwarfs means uncapped.
const int MAX_DWARFS_NONE = 0;
// Value < 0 for max dwarfs means don't manager the labor.
const int MAX_DWARFS_UNMANAGED = -1;
// Here go all the command declarations... // Here go all the command declarations...
// mostly to allow having the mandatory stuff on top of the file and commands on the bottom // mostly to allow having the mandatory stuff on top of the file and commands on the bottom
@ -390,16 +395,18 @@ struct labor_info
int idle_dwarfs; int idle_dwarfs;
int busy_dwarfs; int busy_dwarfs;
int priority() { return config.ival(1); } int priority() const { return config.ival(1); }
void set_priority(int priority) { config.ival(1) = priority; } void set_priority(int priority) { config.ival(1) = priority; }
int maximum_dwarfs() { return config.ival(2); } bool is_unmanaged() const { return maximum_dwarfs() == MAX_DWARFS_UNMANAGED; }
int maximum_dwarfs() const { return config.ival(2); }
void set_maximum_dwarfs(int maximum_dwarfs) { config.ival(2) = maximum_dwarfs; } void set_maximum_dwarfs(int maximum_dwarfs) { config.ival(2) = maximum_dwarfs; }
int time_since_last_assigned() int time_since_last_assigned() const
{ {
return (*df::global::cur_year - config.ival(3)) * 403200 + *df::global::cur_year_tick - config.ival(4); return (*df::global::cur_year - config.ival(3)) * 403200 + *df::global::cur_year_tick - config.ival(4);
} }
void mark_assigned() { void mark_assigned() {
config.ival(3) = (*df::global::cur_year); config.ival(3) = (*df::global::cur_year);
config.ival(4) = (*df::global::cur_year_tick); config.ival(4) = (*df::global::cur_year_tick);
@ -412,7 +419,6 @@ enum tools_enum {
TOOLS_MAX TOOLS_MAX
}; };
struct labor_default struct labor_default
{ {
int priority; int priority;
@ -524,10 +530,13 @@ struct dwarf_info_t
bool has_children; bool has_children;
bool armed; bool armed;
int unmanaged_labors_assigned;
df::unit_labor using_labor; df::unit_labor using_labor;
dwarf_info_t(df::unit* dw) : dwarf(dw), state(OTHER), dwarf_info_t(df::unit* dw) : dwarf(dw), state(OTHER),
clear_all(false), high_skill(0), has_children(false), armed(false), using_labor(df::unit_labor::NONE) clear_all(false), high_skill(0), has_children(false), armed(false),
unmanaged_labors_assigned(0), using_labor(df::unit_labor::NONE)
{ {
for (int e = TOOL_NONE; e < TOOLS_MAX; e++) for (int e = TOOL_NONE; e < TOOLS_MAX; e++)
has_tool[e] = false; has_tool[e] = false;
@ -841,6 +850,11 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector <Plugin
" Enables or disables the plugin.\n" " Enables or disables the plugin.\n"
" labormanager max <labor> <maximum>\n" " labormanager max <labor> <maximum>\n"
" Set max number of dwarves assigned to a labor.\n" " Set max number of dwarves assigned to a labor.\n"
" labormanager max <labor> unmanaged\n"
" labormanager max <labor> disable\n"
" Don't attempt to manage this labor.\n"
" Any dwarves with unmanaged labors assigned will be less\n"
" likely to have managed labors assigned to them.\n"
" labormanager max <labor> none\n" " labormanager max <labor> none\n"
" Unrestrict the number of dwarves assigned to a labor.\n" " Unrestrict the number of dwarves assigned to a labor.\n"
" labormanager priority <labor> <priority>\n" " labormanager priority <labor> <priority>\n"
@ -857,8 +871,8 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector <Plugin
" When enabled, labormanager periodically checks your dwarves and enables or\n" " When enabled, labormanager periodically checks your dwarves and enables or\n"
" disables labors. Generally, each dwarf will be assigned exactly one labor.\n" " disables labors. Generally, each dwarf will be assigned exactly one labor.\n"
" Warning: labormanager will override any manual changes you make to labors\n" " Warning: labormanager will override any manual changes you make to labors\n"
" while it is enabled. Do not try to run both autolabor and labormanager at\n" " while it is enabled, except where the labor is marked as unmanaged.\n"
" the same time.\n" " Do not try to run both autolabor and labormanager at the same time.\n"
)); ));
generate_labor_to_skill_map(); generate_labor_to_skill_map();
@ -944,7 +958,7 @@ private:
private: private:
void set_labor(dwarf_info_t* dwarf, df::unit_labor labor, bool value) void set_labor(dwarf_info_t* dwarf, df::unit_labor labor, bool value)
{ {
if (labor >= 0 && labor <= ENUM_LAST_ITEM(unit_labor)) if (labor >= 0 && labor <= ENUM_LAST_ITEM(unit_labor) && !labor_infos[labor].is_unmanaged())
{ {
if (!Units::isValidLabor(dwarf->dwarf, labor)) if (!Units::isValidLabor(dwarf->dwarf, labor))
{ {
@ -954,7 +968,6 @@ private:
ENUM_KEY_STR(unit_labor, labor).c_str()); ENUM_KEY_STR(unit_labor, labor).c_str());
return; return;
} }
bool old = dwarf->dwarf->status.labors[labor]; bool old = dwarf->dwarf->status.labors[labor];
dwarf->dwarf->status.labors[labor] = value; dwarf->dwarf->status.labors[labor] = value;
if (old != value) if (old != value)
@ -1009,7 +1022,7 @@ private:
df::unit_labor labor = labor_mapper->find_job_labor(j); df::unit_labor labor = labor_mapper->find_job_labor(j);
if (labor != df::unit_labor::NONE) if (labor != df::unit_labor::NONE && !labor_infos[labor].is_unmanaged())
{ {
labor_needed[labor]++; labor_needed[labor]++;
if (worker == -1) if (worker == -1)
@ -1136,7 +1149,7 @@ private:
{ {
df::item* item = *i; df::item* item = *i;
if (item->flags.bits.dump) if (item->flags.bits.dump && !labor_infos[df::unit_labor::HAUL_REFUSE].is_unmanaged())
labor_needed[df::unit_labor::HAUL_REFUSE]++; labor_needed[df::unit_labor::HAUL_REFUSE]++;
if (item->flags.whole & bad_flags.whole) if (item->flags.whole & bad_flags.whole)
@ -1382,6 +1395,8 @@ private:
dwarf->state = state; dwarf->state = state;
dwarf->unmanaged_labors_assigned = 0;
FOR_ENUM_ITEMS(unit_labor, l) FOR_ENUM_ITEMS(unit_labor, l)
{ {
if (l == df::unit_labor::NONE) if (l == df::unit_labor::NONE)
@ -1389,6 +1404,8 @@ private:
if (dwarf->dwarf->status.labors[l]) if (dwarf->dwarf->status.labors[l])
if (state == IDLE) if (state == IDLE)
labor_infos[l].idle_dwarfs++; labor_infos[l].idle_dwarfs++;
if (labor_infos[l].is_unmanaged())
dwarf->unmanaged_labors_assigned++;
} }
@ -1436,7 +1453,7 @@ private:
FOR_ENUM_ITEMS(unit_labor, labor) FOR_ENUM_ITEMS(unit_labor, labor)
{ {
if (labor == df::unit_labor::NONE) if (labor == df::unit_labor::NONE || labor_infos[labor].is_unmanaged())
continue; continue;
df::job_skill skill = labor_to_skill[labor]; df::job_skill skill = labor_to_skill[labor];
@ -1456,7 +1473,7 @@ private:
{ {
FOR_ENUM_ITEMS(unit_labor, labor) FOR_ENUM_ITEMS(unit_labor, labor)
{ {
if (labor == unit_labor::NONE) if (labor == unit_labor::NONE || labor_infos[labor].is_unmanaged())
continue; continue;
if (Units::isValidLabor(dwarf->dwarf, labor)) if (Units::isValidLabor(dwarf->dwarf, labor))
set_labor(dwarf, labor, false); set_labor(dwarf, labor, false);
@ -1572,6 +1589,9 @@ private:
score -= Units::computeMovementSpeed(d->dwarf); score -= Units::computeMovementSpeed(d->dwarf);
// significantly disfavor dwarves who have unmanaged labors assigned
score -= 1000 * d->unmanaged_labors_assigned;
return score; return score;
} }
@ -1700,6 +1720,8 @@ public:
if (l == df::unit_labor::NONE) if (l == df::unit_labor::NONE)
continue; continue;
if (!labor_infos[l].is_unmanaged())
{
int before = labor_needed[l]; int before = labor_needed[l];
labor_needed[l] = max(0, labor_needed[l] - labor_in_use[l]); labor_needed[l] = max(0, labor_needed[l] - labor_in_use[l]);
@ -1709,11 +1731,16 @@ public:
if (print_debug && before != labor_needed[l]) if (print_debug && before != labor_needed[l])
out.print("labor %s reduced from %d to %d\n", ENUM_KEY_STR(unit_labor, l).c_str(), before, labor_needed[l]); out.print("labor %s reduced from %d to %d\n", ENUM_KEY_STR(unit_labor, l).c_str(), before, labor_needed[l]);
}
else
{
labor_needed[l] = 0;
}
} }
/* assign food haulers for rotting food items */ /* assign food haulers for rotting food items */
if (!labor_infos[df::unit_labor::HAUL_FOOD].is_unmanaged())
{
if (priority_food > 0 && labor_infos[df::unit_labor::HAUL_FOOD].idle_dwarfs > 0) if (priority_food > 0 && labor_infos[df::unit_labor::HAUL_FOOD].idle_dwarfs > 0)
priority_food = 1; priority_food = 1;
@ -1762,6 +1789,11 @@ public:
break; break;
} }
}
else
{
priority_food = 0;
}
if (print_debug) if (print_debug)
{ {
@ -1779,12 +1811,15 @@ public:
for (auto i = labor_needed.begin(); i != labor_needed.end(); i++) for (auto i = labor_needed.begin(); i != labor_needed.end(); i++)
{ {
df::unit_labor l = i->first; df::unit_labor l = i->first;
if (l == df::unit_labor::NONE) if (l == df::unit_labor::NONE || labor_infos[l].is_unmanaged())
continue; continue;
if (labor_infos[l].maximum_dwarfs() > 0 && const int user_specified_max_dwarfs = labor_infos[l].maximum_dwarfs();
i->second > labor_infos[l].maximum_dwarfs())
i->second = labor_infos[l].maximum_dwarfs(); if (user_specified_max_dwarfs != MAX_DWARFS_NONE && i->second > user_specified_max_dwarfs)
{
i->second = user_specified_max_dwarfs;
}
int priority = labor_infos[l].priority(); int priority = labor_infos[l].priority();
@ -1911,6 +1946,11 @@ public:
labors_changed = true; labors_changed = true;
} }
} }
else if (l == df::unit_labor::CLEAN && best_score < 0)
{
if (Units::isValidLabor((*bestdwarf)->dwarf, l))
set_labor(*bestdwarf, l, true);
}
else if ((*bestdwarf)->state == IDLE) else if ((*bestdwarf)->state == IDLE)
{ {
if (Units::isValidLabor((*bestdwarf)->dwarf, l)) if (Units::isValidLabor((*bestdwarf)->dwarf, l))
@ -1940,7 +1980,7 @@ public:
FOR_ENUM_ITEMS(unit_labor, l) FOR_ENUM_ITEMS(unit_labor, l)
{ {
if (l == df::unit_labor::NONE) if (l == df::unit_labor::NONE || labor_infos[l].is_unmanaged())
continue; continue;
if (l == (*d)->using_labor) if (l == (*d)->using_labor)
continue; continue;
@ -1961,6 +2001,10 @@ public:
{ {
set_labor(*d, l, true); set_labor(*d, l, true);
} }
if (score < 0)
set_labor(*d, df::unit_labor::CLEAN, true);
if ((*d)->using_labor != df::unit_labor::NONE && if ((*d)->using_labor != df::unit_labor::NONE &&
(score > current_score + 5000 || base_priority[(*d)->using_labor] < base_priority[l]) && (score > current_score + 5000 || base_priority[(*d)->using_labor] < base_priority[l]) &&
default_labor_infos[(*d)->using_labor].tool == TOOL_NONE) default_labor_infos[(*d)->using_labor].tool == TOOL_NONE)
@ -1990,13 +2034,20 @@ public:
set_labor(canary_dwarf, l, true); set_labor(canary_dwarf, l, true);
} }
/* Also set the canary to remove constructions, because we have no way yet to tell if there are constructions needing removal */ set_labor(canary_dwarf, df::unit_labor::CLEAN, true);
/* Also set the canary to remove constructions, because we have no way yet to tell if there are constructions needing removal */
if (!labor_infos[df::unit_labor::REMOVE_CONSTRUCTION].is_unmanaged())
{
set_labor(canary_dwarf, df::unit_labor::REMOVE_CONSTRUCTION, true); set_labor(canary_dwarf, df::unit_labor::REMOVE_CONSTRUCTION, true);
}
/* Set HAUL_WATER so we can detect ponds that need to be filled ponds. */ /* Set HAUL_WATER so we can detect ponds that need to be filled ponds. */
if (!labor_infos[df::unit_labor::HAUL_WATER].is_unmanaged())
{
set_labor(canary_dwarf, df::unit_labor::HAUL_WATER, true); set_labor(canary_dwarf, df::unit_labor::HAUL_WATER, true);
}
if (print_debug) if (print_debug)
out.print("Setting %s as the hauling canary\n", canary_dwarf->dwarf->name.first_name.c_str()); out.print("Setting %s as the hauling canary\n", canary_dwarf->dwarf->name.first_name.c_str());
@ -2016,7 +2067,7 @@ public:
{ {
FOR_ENUM_ITEMS(unit_labor, l) FOR_ENUM_ITEMS(unit_labor, l)
{ {
if (l == df::unit_labor::NONE) if (l == df::unit_labor::NONE || labor_infos[l].is_unmanaged())
continue; continue;
if (Units::isValidLabor((*d)->dwarf, l)) if (Units::isValidLabor((*d)->dwarf, l))
@ -2048,13 +2099,16 @@ public:
} }
} }
if (!labor_infos[df::unit_labor::PULL_LEVER].is_unmanaged())
{
set_labor(*d, df::unit_labor::PULL_LEVER, true); set_labor(*d, df::unit_labor::PULL_LEVER, true);
}
if (any) continue; if (any) continue;
FOR_ENUM_ITEMS(unit_labor, l) FOR_ENUM_ITEMS(unit_labor, l)
{ {
if (l == df::unit_labor::NONE) if (l == df::unit_labor::NONE || labor_infos[l].is_unmanaged())
continue; continue;
if (to_assign[l] > 0 || l == df::unit_labor::CLEAN) if (to_assign[l] > 0 || l == df::unit_labor::CLEAN)
@ -2075,7 +2129,7 @@ public:
FOR_ENUM_ITEMS(unit_labor, l) FOR_ENUM_ITEMS(unit_labor, l)
{ {
if (l == df::unit_labor::NONE) if (l == df::unit_labor::NONE || labor_infos[l].is_unmanaged())
continue; continue;
tools_enum t = default_labor_infos[l].tool; tools_enum t = default_labor_infos[l].tool;
@ -2172,25 +2226,40 @@ void print_labor(df::unit_labor labor, color_ostream &out)
out << labor_name << ": "; out << labor_name << ": ";
for (int i = 0; i < 20 - (int)labor_name.length(); i++) for (int i = 0; i < 20 - (int)labor_name.length(); i++)
out << ' '; out << ' ';
out << "priority " << labor_infos[labor].priority() const auto& labor_info = labor_infos[labor];
<< ", maximum " << labor_infos[labor].maximum_dwarfs() if (labor_info.is_unmanaged())
<< ", currently " << labor_infos[labor].active_dwarfs << " dwarfs (" {
<< labor_infos[labor].busy_dwarfs << " busy, " out << "UNMANAGED";
<< labor_infos[labor].idle_dwarfs << " idle)" }
else
{
out << "priority " << labor_info.priority();
if (labor_info.maximum_dwarfs() == MAX_DWARFS_NONE)
out << ", no maximum";
else
out << ", maximum " << labor_info.maximum_dwarfs();
}
out << ", currently " << labor_info.active_dwarfs << " dwarfs ("
<< labor_info.busy_dwarfs << " busy, "
<< labor_info.idle_dwarfs << " idle)"
<< endl; << endl;
} }
df::unit_labor lookup_labor_by_name(std::string& name) df::unit_labor lookup_labor_by_name(std::string name)
{ {
df::unit_labor labor = df::unit_labor::NONE; // We should accept incorrect casing, there is no ambiguity.
std::transform(name.begin(), name.end(), name.begin(), ::toupper);
FOR_ENUM_ITEMS(unit_labor, test_labor) FOR_ENUM_ITEMS(unit_labor, test_labor)
{ {
if (name == ENUM_KEY_STR(unit_labor, test_labor)) if (name == ENUM_KEY_STR(unit_labor, test_labor))
labor = test_labor; {
return test_labor;
}
} }
return labor; return df::unit_labor::NONE;
} }
DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) DFhackCExport command_result plugin_enable(color_ostream &out, bool enable)
@ -2250,7 +2319,9 @@ command_result labormanager(color_ostream &out, std::vector <std::string> & para
int v; int v;
if (parameters[2] == "none") if (parameters[2] == "none")
v = 0; v = MAX_DWARFS_NONE;
else if (parameters[2] == "disable" || parameters[2] == "unmanaged")
v = MAX_DWARFS_UNMANAGED;
else else
v = atoi(parameters[2].c_str()); v = atoi(parameters[2].c_str());

@ -188,7 +188,7 @@ function num_sequence:new(a,b)
else else
error("Invalid arguments - a: " .. tostring(a) .. " and b: " .. tostring(b)) error("Invalid arguments - a: " .. tostring(a) .. " and b: " .. tostring(b))
end end
print("seqID:"..o.seqID) --print("seqID:"..o.seqID)
setmetatable(o,self) setmetatable(o,self)
return o return o
end end

@ -369,8 +369,7 @@ struct mousequery_hook : public df::viewscreen_dwarfmodest
// Can't check limits earlier as we must be sure we are in query or default mode // Can't check limits earlier as we must be sure we are in query or default mode
// (so we can clear the button down flag) // (so we can clear the button down flag)
auto dims = Gui::getDwarfmodeViewDims(); auto dims = Gui::getDwarfmodeViewDims();
int right_bound = (dims.menu_x1 > 0) ? dims.menu_x1 - 2 : gps->dimx - 2; if (mx < 1 || mx > dims.map_x2 || my < 1 || my > dims.map_y2)
if (mx < 1 || mx > right_bound || my < 1 || my > gps->dimy - 2)
return false; return false;
if (ui->main.mode == df::ui_sidebar_mode::Zones || if (ui->main.mode == df::ui_sidebar_mode::Zones ||
@ -435,13 +434,13 @@ struct mousequery_hook : public df::viewscreen_dwarfmodest
if (mx < scroll_trigger_x) if (mx < scroll_trigger_x)
sendKey(interface_key::CURSOR_LEFT_FAST); sendKey(interface_key::CURSOR_LEFT_FAST);
if (mx > ((dims.menu_x1 > 0) ? dims.menu_x1 : gps->dimx) - scroll_trigger_x) if (mx > dims.map_x2 - scroll_trigger_x)
sendKey(interface_key::CURSOR_RIGHT_FAST); sendKey(interface_key::CURSOR_RIGHT_FAST);
if (my < scroll_trigger_y) if (my < scroll_trigger_y)
sendKey(interface_key::CURSOR_UP_FAST); sendKey(interface_key::CURSOR_UP_FAST);
if (my > gps->dimy - scroll_trigger_y) if (my > dims.map_y2 - scroll_trigger_y)
sendKey(interface_key::CURSOR_DOWN_FAST); sendKey(interface_key::CURSOR_DOWN_FAST);
} }
@ -572,12 +571,11 @@ struct mousequery_hook : public df::viewscreen_dwarfmodest
static decltype(enabler->clock) last_t = 0; static decltype(enabler->clock) last_t = 0;
auto dims = Gui::getDwarfmodeViewDims(); auto dims = Gui::getDwarfmodeViewDims();
auto right_margin = (dims.menu_x1 > 0) ? dims.menu_x1 : gps->dimx;
int32_t mx, my; int32_t mx, my;
auto mpos = get_mouse_pos(mx, my); auto mpos = get_mouse_pos(mx, my);
bool mpos_valid = mpos.x != -30000 && mpos.y != -30000 && mpos.z != -30000; bool mpos_valid = mpos.x != -30000 && mpos.y != -30000 && mpos.z != -30000;
if (mx < 1 || mx > right_margin - 2 || my < 1 || my > gps->dimy - 2) if (mx < 1 || mx > dims.map_x2 || my < 1 || my > dims.map_y2)
mpos_valid = false; mpos_valid = false;
// Check if in lever binding mode // Check if in lever binding mode
@ -683,7 +681,7 @@ struct mousequery_hook : public df::viewscreen_dwarfmodest
return; return;
} }
if (mx > right_margin - scroll_buffer) if (mx > dims.map_x2 - scroll_buffer)
{ {
sendKey(interface_key::CURSOR_RIGHT); sendKey(interface_key::CURSOR_RIGHT);
return; return;
@ -695,7 +693,7 @@ struct mousequery_hook : public df::viewscreen_dwarfmodest
return; return;
} }
if (my > gps->dimy - scroll_buffer) if (my > dims.map_y2 - scroll_buffer)
{ {
sendKey(interface_key::CURSOR_DOWN); sendKey(interface_key::CURSOR_DOWN);
return; return;
@ -732,9 +730,9 @@ struct mousequery_hook : public df::viewscreen_dwarfmodest
if (shouldTrack()) if (shouldTrack())
{ {
if (delta_t <= scroll_delay && (mx < scroll_buffer || if (delta_t <= scroll_delay && (mx < scroll_buffer ||
mx > dims.menu_x1 - scroll_buffer || mx > dims.map_x2 - scroll_buffer ||
my < scroll_buffer || my < scroll_buffer ||
my > gps->dimy - scroll_buffer)) my > dims.map_y2 - scroll_buffer))
{ {
return; return;
} }
@ -836,26 +834,35 @@ static command_result mousequery_cmd(color_ostream &out, vector <string> & param
else if (cmd[0] == 'p') else if (cmd[0] == 'p')
{ {
plugin_enabled = (state == "enable"); plugin_enabled = (state == "enable");
out << "mousequery: plugin " << (plugin_enabled ? "enabled" : "disabled") << endl;
} }
else if (cmd[0] == 'r') else if (cmd[0] == 'r')
{ {
rbutton_enabled = (state == "enable"); rbutton_enabled = (state == "enable");
out << "mousequery: rbutton " << (rbutton_enabled ? "enabled" : "disabled") << endl;
} }
else if (cmd[0] == 't') else if (cmd[0] == 't')
{ {
tracking_enabled = (state == "enable"); tracking_enabled = (state == "enable");
if (!tracking_enabled) if (!tracking_enabled) {
out << "mousequery: edge scrolling disabled" << endl;
active_scrolling = false; active_scrolling = false;
} }
out << "mousequery: tracking " << (tracking_enabled ? "enabled" : "disabled") << endl;
}
else if (cmd[0] == 'e') else if (cmd[0] == 'e')
{ {
active_scrolling = (state == "enable"); active_scrolling = (state == "enable");
if (active_scrolling) if (active_scrolling) {
out << "mousequery: tracking enabled" << endl;
tracking_enabled = true; tracking_enabled = true;
} }
out << "mousequery: edge scrolling " << (active_scrolling ? "enabled" : "disabled") << endl;
}
else if (cmd[0] == 'l') else if (cmd[0] == 'l')
{ {
live_view = (state == "enable"); live_view = (state == "enable");
out << "mousequery: live view " << (live_view ? "enabled" : "disabled") << endl;
} }
else if (cmd == "drag") else if (cmd == "drag")
{ {

@ -8,6 +8,7 @@
#include "df/ui.h" #include "df/ui.h"
#include "df/building_nest_boxst.h" #include "df/building_nest_boxst.h"
#include "df/building_type.h" #include "df/building_type.h"
#include "df/buildings_other_id.h"
#include "df/global_objects.h" #include "df/global_objects.h"
#include "df/item.h" #include "df/item.h"
#include "df/unit.h" #include "df/unit.h"
@ -37,7 +38,7 @@ static void eggscan(color_ostream &out)
{ {
CoreSuspender suspend; CoreSuspender suspend;
for (df::building *build : world->buildings.all) for (df::building *build : world->buildings.other[df::buildings_other_id::NEST_BOX])
{ {
auto type = build->getType(); auto type = build->getType();
if (df::enums::building_type::NestBox == type) if (df::enums::building_type::NestBox == type)
@ -68,9 +69,11 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector <Plugi
{ {
if (world && ui) { if (world && ui) {
commands.push_back( commands.push_back(
PluginCommand("nestboxes", "Derp.", PluginCommand("nestboxes", "Automatically scan for and forbid fertile eggs incubating in a nestbox.",
nestboxes, false, nestboxes, false,
"Derp.\n" "To enable: nestboxes enable\n"
"To disable: nestboxes disable\n"
"There is no other configuration.\n"
) )
); );
} }
@ -105,9 +108,6 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable)
static command_result nestboxes(color_ostream &out, vector <string> & parameters) static command_result nestboxes(color_ostream &out, vector <string> & parameters)
{ {
CoreSuspender suspend; CoreSuspender suspend;
bool clean = false;
int dump_count = 0;
int good_egg = 0;
if (parameters.size() == 1) { if (parameters.size() == 1) {
if (parameters[0] == "enable") if (parameters[0] == "enable")

@ -252,8 +252,8 @@ module DFHack
not u.specific_refs.find { |s| s.type == :ACTIVITY } and not u.specific_refs.find { |s| s.type == :ACTIVITY } and
# filter soldiers (TODO check schedule) # filter soldiers (TODO check schedule)
u.military.squad_id == -1 and u.military.squad_id == -1 and
# filter 'on break' # filter incoming migrants
not u.status.misc_traits.find { |t| t.id == :OnBreak } not u.status.misc_traits.find { |t| t.id == :Migrant }
end end
def unit_entitypositions(unit) def unit_entitypositions(unit)

@ -1122,10 +1122,9 @@ private:
return ""; return "";
for (auto p = unit->status.misc_traits.begin(); p < unit->status.misc_traits.end(); p++) for (auto p = unit->status.misc_traits.begin(); p < unit->status.misc_traits.end(); p++)
{ {
if ((*p)->id == misc_trait_type::Migrant || (*p)->id == misc_trait_type::OnBreak) if ((*p)->id == misc_trait_type::Migrant)
{ {
int i = (*p)->value; return ".new arrival.migrant";
return ".on break";
} }
} }

@ -1492,7 +1492,7 @@ static void releaseTiredWorker(EngineInfo *engine, df::job *job, df::unit *worke
if (unit == worker || if (unit == worker ||
unit->job.current_job || !unit->status.labors[unit_labor::SIEGEOPERATE] || unit->job.current_job || !unit->status.labors[unit_labor::SIEGEOPERATE] ||
!Units::isCitizen(unit) || Units::getMiscTrait(unit, misc_trait_type::OnBreak) || !Units::isCitizen(unit) || Units::getMiscTrait(unit, misc_trait_type::Migrant) ||
isTired(unit) || !Maps::canWalkBetween(job->pos, unit->pos)) isTired(unit) || !Maps::canWalkBetween(job->pos, unit->pos))
continue; continue;

@ -1 +1 @@
Subproject commit 4a1953f27c6acd1ceeb2d5447613c106e708c26c Subproject commit 6b4df3995f53e0579fc590a78d68e54fc2ca2b81

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

@ -1 +1 @@
Subproject commit 87cc0fcad57a3a8ff7b558be9f3523101ac2f69d Subproject commit c8394fa75ff20abcdcdbe975dbf157d21882172e

@ -67,9 +67,9 @@ for plugin_name in actual:
io = methods[m] io = methods[m]
if m in expected[plugin_name]: if m in expected[plugin_name]:
if expected[plugin_name][m] != io: if expected[plugin_name][m] != io:
wrong.add('// RPC ' + m + ' : ' + io[0] + ' -> ' + io[1]) wrong.append('// RPC ' + m + ' : ' + io[0] + ' -> ' + io[1])
else: else:
missing.add('// RPC ' + m + ' : ' + io[0] + ' -> ' + io[1]) missing.append('// RPC ' + m + ' : ' + io[0] + ' -> ' + io[1])
if len(missing) > 0: if len(missing) > 0:
print('Incomplete documentation for ' + ('core' if plugin_name == '' else 'plugin "' + plugin_name + '"') + ' proto files. Add the following lines:') print('Incomplete documentation for ' + ('core' if plugin_name == '' else 'plugin "' + plugin_name + '"') + ' proto files. Add the following lines:')
@ -98,7 +98,7 @@ for plugin_name in expected:
for m in methods: for m in methods:
io = methods[m] io = methods[m]
if m not in actual[plugin_name]: if m not in actual[plugin_name]:
missing.add('// RPC ' + m + ' : ' + io[0] + ' -> ' + io[1]) missing.append('// RPC ' + m + ' : ' + io[0] + ' -> ' + io[1])
if len(missing) > 0: if len(missing) > 0:
print('Incorrect documentation for ' + ('core' if plugin_name == '' else 'plugin "' + plugin_name + '"') + ' proto files. Remove the following lines:') print('Incorrect documentation for ' + ('core' if plugin_name == '' else 'plugin "' + plugin_name + '"') + ' proto files. Remove the following lines:')

@ -10,31 +10,37 @@ cd "$(dirname "$0")"
echo "DF_VERSION: $DF_VERSION" echo "DF_VERSION: $DF_VERSION"
echo "DF_FOLDER: $DF_FOLDER" echo "DF_FOLDER: $DF_FOLDER"
mkdir -p "$DF_FOLDER" mkdir -p "$DF_FOLDER"
cd "$DF_FOLDER" # back out of df_linux
cd "$DF_FOLDER/.."
if [ -f receipt ]; then if [ -f receipt ]; then
if [ "$selfmd5" != "$(cat receipt)" ]; then if [ "$selfmd5" != "$(cat receipt)" ]; then
echo "download-df.sh changed; removing DF" echo "download-df.sh changed; removing DF"
rm receipt
else else
echo "Already downloaded $DF_VERSION" echo "Already downloaded $DF_VERSION"
exit 0
fi fi
fi fi
rm -rif "$tardest" df_linux if [ ! -f receipt ]; then
rm -f "$tardest"
minor=$(echo "$DF_VERSION" | cut -d. -f2) minor=$(echo "$DF_VERSION" | cut -d. -f2)
patch=$(echo "$DF_VERSION" | cut -d. -f3) patch=$(echo "$DF_VERSION" | cut -d. -f3)
url="http://www.bay12games.com/dwarves/df_${minor}_${patch}_linux.tar.bz2" url="http://www.bay12games.com/dwarves/df_${minor}_${patch}_linux.tar.bz2"
echo Downloading echo Downloading
wget "$url" -O "$tardest" wget "$url" -O "$tardest"
fi
rm -rf df_linux
mkdir df_linux
echo Extracting echo Extracting
tar xf "$tardest" --strip-components=1 tar xf "$tardest" --strip-components=1 -C df_linux
echo Changing settings echo Changing settings
echo '' >> "$DF_FOLDER/data/init/init.txt" echo '' >> "df_linux/data/init/init.txt"
echo '[PRINT_MODE:TEXT]' >> "$DF_FOLDER/data/init/init.txt" echo '[PRINT_MODE:TEXT]' >> "df_linux/data/init/init.txt"
echo '[SOUND:NO]' >> "$DF_FOLDER/data/init/init.txt" echo '[SOUND:NO]' >> "df_linux/data/init/init.txt"
echo Done echo Done
echo "$selfmd5" > receipt echo "$selfmd5" > receipt
ls

@ -1,4 +1,4 @@
#!/bin/sh #!/bin/sh
cd "$(dirname "$0")" cd "$(dirname "$0")"
cd .. cd ..
grep DF_VERSION CMakeLists.txt | perl -ne 'print "$&\n" if /[\d\.]+/' grep -i 'set(DF_VERSION' CMakeLists.txt | perl -ne 'print "$&\n" if /[\d\.]+/'

@ -14,6 +14,9 @@ os.chdir(sys.argv[1])
if os.path.exists(test_stage): if os.path.exists(test_stage):
os.remove(test_stage) os.remove(test_stage)
print(os.getcwd())
print(os.listdir('.'))
tries = 0 tries = 0
while True: while True:
tries += 1 tries += 1
@ -33,3 +36,5 @@ while True:
process = subprocess.Popen([dfhack], stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE) process = subprocess.Popen([dfhack], stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
process.communicate() process.communicate()
if process.returncode != 0:
print('DF exited with ' + repr(process.returncode))