<commit_before>/*****************************************************************************
 *
 * This file is part of Mapnik (c++ mapping toolkit)
 *
 * Copyright (C) 2006 Artem Pavlenko
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 *****************************************************************************/
// $Id$

#include <mapnik/map.hpp>
#include <mapnik/datasource_cache.hpp>
#include <mapnik/font_engine_freetype.hpp>
#include <mapnik/agg_renderer.hpp>
#include <mapnik/filter_factory.hpp>
#include <mapnik/color_factory.hpp>
#include <mapnik/image_util.hpp>
#include <mapnik/config_error.hpp>

#include <iostream>

int main ( int argc , char** argv)
{
    if (argc != 2)
    {
        std::cout << "usage: ./rundemo <plugins_dir>\n";
        return EXIT_SUCCESS;
    }

    using namespace mapnik;
    try {
        std::cout << " running demo ... \n";
        datasource_cache::instance()->register_datasources(argv[1]);
        freetype_engine::register_font("/usr/local/lib/mapnik/fonts/DejaVuSans.ttf");

        Map m(800,600);
        m.set_background(color_factory::from_string("white"));

        // create styles

        // Provinces (polygon)
        feature_type_style provpoly_style;

        rule_type provpoly_rule_on;
        provpoly_rule_on.set_filter(create_filter("[NAME_EN] = 'Ontario'"));
        provpoly_rule_on.append(polygon_symbolizer(Color(250, 190, 183)));
        provpoly_style.add_rule(provpoly_rule_on);

        rule_type provpoly_rule_qc;
        provpoly_rule_qc.set_filter(create_filter("[NAME_EN] = 'Quebec'"));
        provpoly_rule_qc.append(polygon_symbolizer(Color(217, 235, 203)));
        provpoly_style.add_rule(provpoly_rule_qc);

        m.insert_style("provinces",provpoly_style);

        // Provinces (polyline)
        feature_type_style provlines_style;

        stroke provlines_stk (Color(0,0,0),1.0);
        provlines_stk.add_dash(8, 4);
        provlines_stk.add_dash(2, 2);
        provlines_stk.add_dash(2, 2);

        rule_type provlines_rule;
        provlines_rule.append(line_symbolizer(provlines_stk));
        provlines_style.add_rule(provlines_rule);

        m.insert_style("provlines",provlines_style);

        // Drainage
        feature_type_style qcdrain_style;

        rule_type qcdrain_rule;
        qcdrain_rule.set_filter(create_filter("[HYC] = 8"));
        qcdrain_rule.append(polygon_symbolizer(Color(153, 204, 255)));
        qcdrain_style.add_rule(qcdrain_rule);

        m.insert_style("drainage",qcdrain_style);

        // Roads 3 and 4 (The "grey" roads)
        feature_type_style roads34_style;
        rule_type roads34_rule;
        roads34_rule.set_filter(create_filter("[CLASS] = 3 or [CLASS] = 4"));
        stroke roads34_rule_stk(Color(171,158,137),2.0);
        roads34_rule_stk.set_line_cap(ROUND_CAP);
        roads34_rule_stk.set_line_join(ROUND_JOIN);
        roads34_rule.append(line_symbolizer(roads34_rule_stk));
        roads34_style.add_rule(roads34_rule);

        m.insert_style("smallroads",roads34_style);

        // Roads 2 (The thin yellow ones)
        feature_type_style roads2_style_1;
        rule_type roads2_rule_1;
        roads2_rule_1.set_filter(create_filter("[CLASS] = 2"));
        stroke roads2_rule_stk_1(Color(171,158,137),4.0);
        roads2_rule_stk_1.set_line_cap(ROUND_CAP);
        roads2_rule_stk_1.set_line_join(ROUND_JOIN);
        roads2_rule_1.append(line_symbolizer(roads2_rule_stk_1));
        roads2_style_1.add_rule(roads2_rule_1);

        m.insert_style("road-border", roads2_style_1);

        feature_type_style roads2_style_2;
        rule_type roads2_rule_2;
        roads2_rule_2.set_filter(create_filter("[CLASS] = 2"));
        stroke roads2_rule_stk_2(Color(255,250,115),2.0);
        roads2_rule_stk_2.set_line_cap(ROUND_CAP);
        roads2_rule_stk_2.set_line_join(ROUND_JOIN);
        roads2_rule_2.append(line_symbolizer(roads2_rule_stk_2));
        roads2_style_2.add_rule(roads2_rule_2);

        m.insert_style("road-fill", roads2_style_2);

        // Roads 1 (The big orange ones, the highways)
        feature_type_style roads1_style_1;
        rule_type roads1_rule_1;
        roads1_rule_1.set_filter(create_filter("[CLASS] = 1"));
        stroke roads1_rule_stk_1(Color(188,149,28),7.0);
        roads1_rule_stk_1.set_line_cap(ROUND_CAP);
        roads1_rule_stk_1.set_line_join(ROUND_JOIN);
        roads1_rule_1.append(line_symbolizer(roads1_rule_stk_1));
        roads1_style_1.add_rule(roads1_rule_1);
        m.insert_style("highway-border", roads1_style_1);

        feature_type_style roads1_style_2;
        rule_type roads1_rule_2;
        roads1_rule_2.set_filter(create_filter("[CLASS] = 1"));
        stroke roads1_rule_stk_2(Color(242,191,36),5.0);
        roads1_rule_stk_2.set_line_cap(ROUND_CAP);
        roads1_rule_stk_2.set_line_join(ROUND_JOIN);
        roads1_rule_2.append(line_symbolizer(roads1_rule_stk_2));
        roads1_style_2.add_rule(roads1_rule_2);
        m.insert_style("highway-fill", roads1_style_2);

        // Populated Places

        feature_type_style popplaces_style;
        rule_type popplaces_rule;
        text_symbolizer popplaces_text_symbolizer("GEONAME","DejaVu Sans Book",10,Color(0,0,0));
        popplaces_text_symbolizer.set_halo_fill(Color(255,255,200));
        popplaces_text_symbolizer.set_halo_radius(1);
        popplaces_rule.append(popplaces_text_symbolizer);
        popplaces_style.add_rule(popplaces_rule);

        m.insert_style("popplaces",popplaces_style );

        // Layers
        // Provincial  polygons
        {
            parameters p;
            p["type"]="shape";
            p["file"]="../data/boundaries";

            Layer lyr("Provinces");
            lyr.set_datasource(datasource_cache::instance()->create(p));
            lyr.add_style("provinces");
            m.addLayer(lyr);
        }

        // Drainage
        {
            parameters p;
            p["type"]="shape";
            p["file"]="../data/qcdrainage";
            Layer lyr("Quebec Hydrography");
            lyr.set_datasource(datasource_cache::instance()->create(p));
            lyr.add_style("drainage");
            m.addLayer(lyr);
        }

        {
            parameters p;
            p["type"]="shape";
            p["file"]="../data/ontdrainage";

            Layer lyr("Ontario Hydrography");
            lyr.set_datasource(datasource_cache::instance()->create(p));
            lyr.add_style("drainage");
            m.addLayer(lyr);
        }

        // Provincial boundaries
        {
            parameters p;
            p["type"]="shape";
            p["file"]="../data/boundaries_l";
            Layer lyr("Provincial borders");
            lyr.set_datasource(datasource_cache::instance()->create(p));
            lyr.add_style("provlines");
            m.addLayer(lyr);
        }

        // Roads
        {
            parameters p;
            p["type"]="shape";
            p["file"]="../data/roads";
            Layer lyr("Roads");
            lyr.set_datasource(datasource_cache::instance()->create(p));
            lyr.add_style("smallroads");
            lyr.add_style("road-border");
            lyr.add_style("road-fill");
            lyr.add_style("highway-border");
            lyr.add_style("highway-fill");

            m.addLayer(lyr);
        }
        // popplaces
        {
            parameters p;
            p["type"]="shape";
            p["file"]="../data/popplaces";
            p["encoding"] = "latin1";
            Layer lyr("Populated Places");
            lyr.set_datasource(datasource_cache::instance()->create(p));
            lyr.add_style("popplaces");
            m.addLayer(lyr);
        }

        m.zoomToBox(Envelope<double>(1405120.04127408,-247003.813399447,
                                     1706357.31328276,-25098.593149577));

        Image32 buf(m.getWidth(),m.getHeight());
        agg_renderer<Image32> ren(m,buf);
        ren.apply();

        save_to_file<ImageData32>("demo.jpg","jpeg",buf.data());
        save_to_file<ImageData32>("demo.png","png",buf.data());

        std::cout << "Two maps have been rendered in the current directory:\n"
            "- demo.jpg\n"
            "- demo.png\n"
            "Have a look!\n";

    }
    catch ( const mapnik::config_error & ex )
    {
        std::cerr << "### Configuration error: " << ex.what();
        return EXIT_FAILURE;
    }
    catch ( const std::exception & ex )
    {
        std::cerr << "### std::exception: " << ex.what();
        return EXIT_FAILURE;
    }
    catch ( ... )
    {
        std::cerr << "### Unknown exception." << std::endl;
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}
<commit_msg>1. define BOOST_SPIRIT_THREADSAFE (should be defined in config.hpp??) to be compatible with the core library. 2. use mapnik install_dir as input argument.  3. Generate three images as in rundemo.py<commit_after>/*****************************************************************************
 *
 * This file is part of Mapnik (c++ mapping toolkit)
 *
 * Copyright (C) 2006 Artem Pavlenko
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 *****************************************************************************/
// $Id$

// define before any includes
#define BOOST_SPIRIT_THREADSAFE

#include <mapnik/map.hpp>
#include <mapnik/datasource_cache.hpp>
#include <mapnik/font_engine_freetype.hpp>
#include <mapnik/agg_renderer.hpp>
#include <mapnik/filter_factory.hpp>
#include <mapnik/color_factory.hpp>
#include <mapnik/image_util.hpp>
#include <mapnik/config_error.hpp>

#include <iostream>

int main ( int argc , char** argv)
{
    if (argc != 2)
    {
        std::cout << "usage: ./rundemo <mapnik_install_dir>\n";
        return EXIT_SUCCESS;
    }

    using namespace mapnik;
    try {
        std::cout << " running demo ... \n";
        std::string mapnik_dir(argv[1]);
        datasource_cache::instance()->register_datasources(mapnik_dir + "/lib/mapnik/input/");
        freetype_engine::register_font(mapnik_dir + "/lib/mapnik/fonts/DejaVuSans.ttf");

        Map m(800,600);
        m.set_background(color_factory::from_string("white"));

        // create styles

        // Provinces (polygon)
        feature_type_style provpoly_style;

        rule_type provpoly_rule_on;
        provpoly_rule_on.set_filter(create_filter("[NAME_EN] = 'Ontario'"));
        provpoly_rule_on.append(polygon_symbolizer(Color(250, 190, 183)));
        provpoly_style.add_rule(provpoly_rule_on);

        rule_type provpoly_rule_qc;
        provpoly_rule_qc.set_filter(create_filter("[NAME_EN] = 'Quebec'"));
        provpoly_rule_qc.append(polygon_symbolizer(Color(217, 235, 203)));
        provpoly_style.add_rule(provpoly_rule_qc);

        m.insert_style("provinces",provpoly_style);

        // Provinces (polyline)
        feature_type_style provlines_style;

        stroke provlines_stk (Color(0,0,0),1.0);
        provlines_stk.add_dash(8, 4);
        provlines_stk.add_dash(2, 2);
        provlines_stk.add_dash(2, 2);

        rule_type provlines_rule;
        provlines_rule.append(line_symbolizer(provlines_stk));
        provlines_style.add_rule(provlines_rule);

        m.insert_style("provlines",provlines_style);

        // Drainage
        feature_type_style qcdrain_style;

        rule_type qcdrain_rule;
        qcdrain_rule.set_filter(create_filter("[HYC] = 8"));
        qcdrain_rule.append(polygon_symbolizer(Color(153, 204, 255)));
        qcdrain_style.add_rule(qcdrain_rule);

        m.insert_style("drainage",qcdrain_style);

        // Roads 3 and 4 (The "grey" roads)
        feature_type_style roads34_style;
        rule_type roads34_rule;
        roads34_rule.set_filter(create_filter("[CLASS] = 3 or [CLASS] = 4"));
        stroke roads34_rule_stk(Color(171,158,137),2.0);
        roads34_rule_stk.set_line_cap(ROUND_CAP);
        roads34_rule_stk.set_line_join(ROUND_JOIN);
        roads34_rule.append(line_symbolizer(roads34_rule_stk));
        roads34_style.add_rule(roads34_rule);

        m.insert_style("smallroads",roads34_style);

        // Roads 2 (The thin yellow ones)
        feature_type_style roads2_style_1;
        rule_type roads2_rule_1;
        roads2_rule_1.set_filter(create_filter("[CLASS] = 2"));
        stroke roads2_rule_stk_1(Color(171,158,137),4.0);
        roads2_rule_stk_1.set_line_cap(ROUND_CAP);
        roads2_rule_stk_1.set_line_join(ROUND_JOIN);
        roads2_rule_1.append(line_symbolizer(roads2_rule_stk_1));
        roads2_style_1.add_rule(roads2_rule_1);

        m.insert_style("road-border", roads2_style_1);

        feature_type_style roads2_style_2;
        rule_type roads2_rule_2;
        roads2_rule_2.set_filter(create_filter("[CLASS] = 2"));
        stroke roads2_rule_stk_2(Color(255,250,115),2.0);
        roads2_rule_stk_2.set_line_cap(ROUND_CAP);
        roads2_rule_stk_2.set_line_join(ROUND_JOIN);
        roads2_rule_2.append(line_symbolizer(roads2_rule_stk_2));
        roads2_style_2.add_rule(roads2_rule_2);

        m.insert_style("road-fill", roads2_style_2);

        // Roads 1 (The big orange ones, the highways)
        feature_type_style roads1_style_1;
        rule_type roads1_rule_1;
        roads1_rule_1.set_filter(create_filter("[CLASS] = 1"));
        stroke roads1_rule_stk_1(Color(188,149,28),7.0);
        roads1_rule_stk_1.set_line_cap(ROUND_CAP);
        roads1_rule_stk_1.set_line_join(ROUND_JOIN);
        roads1_rule_1.append(line_symbolizer(roads1_rule_stk_1));
        roads1_style_1.add_rule(roads1_rule_1);
        m.insert_style("highway-border", roads1_style_1);

        feature_type_style roads1_style_2;
        rule_type roads1_rule_2;
        roads1_rule_2.set_filter(create_filter("[CLASS] = 1"));
        stroke roads1_rule_stk_2(Color(242,191,36),5.0);
        roads1_rule_stk_2.set_line_cap(ROUND_CAP);
        roads1_rule_stk_2.set_line_join(ROUND_JOIN);
        roads1_rule_2.append(line_symbolizer(roads1_rule_stk_2));
        roads1_style_2.add_rule(roads1_rule_2);
        m.insert_style("highway-fill", roads1_style_2);

        // Populated Places

        feature_type_style popplaces_style;
        rule_type popplaces_rule;
        text_symbolizer popplaces_text_symbolizer("GEONAME","DejaVu Sans Book",10,Color(0,0,0));
        popplaces_text_symbolizer.set_halo_fill(Color(255,255,200));
        popplaces_text_symbolizer.set_halo_radius(1);
        popplaces_rule.append(popplaces_text_symbolizer);
        popplaces_style.add_rule(popplaces_rule);

        m.insert_style("popplaces",popplaces_style );

        // Layers
        // Provincial  polygons
        {
            parameters p;
            p["type"]="shape";
            p["file"]="../data/boundaries";

            Layer lyr("Provinces");
            lyr.set_datasource(datasource_cache::instance()->create(p));
            lyr.add_style("provinces");
            m.addLayer(lyr);
        }

        // Drainage
        {
            parameters p;
            p["type"]="shape";
            p["file"]="../data/qcdrainage";
            Layer lyr("Quebec Hydrography");
            lyr.set_datasource(datasource_cache::instance()->create(p));
            lyr.add_style("drainage");
            m.addLayer(lyr);
        }

        {
            parameters p;
            p["type"]="shape";
            p["file"]="../data/ontdrainage";

            Layer lyr("Ontario Hydrography");
            lyr.set_datasource(datasource_cache::instance()->create(p));
            lyr.add_style("drainage");
            m.addLayer(lyr);
        }

        // Provincial boundaries
        {
            parameters p;
            p["type"]="shape";
            p["file"]="../data/boundaries_l";
            Layer lyr("Provincial borders");
            lyr.set_datasource(datasource_cache::instance()->create(p));
            lyr.add_style("provlines");
            m.addLayer(lyr);
        }

        // Roads
        {
            parameters p;
            p["type"]="shape";
            p["file"]="../data/roads";
            Layer lyr("Roads");
            lyr.set_datasource(datasource_cache::instance()->create(p));
            lyr.add_style("smallroads");
            lyr.add_style("road-border");
            lyr.add_style("road-fill");
            lyr.add_style("highway-border");
            lyr.add_style("highway-fill");

            m.addLayer(lyr);
        }
        // popplaces
        {
            parameters p;
            p["type"]="shape";
            p["file"]="../data/popplaces";
            p["encoding"] = "latin1";
            Layer lyr("Populated Places");
            lyr.set_datasource(datasource_cache::instance()->create(p));
            lyr.add_style("popplaces");
            m.addLayer(lyr);
        }

        m.zoomToBox(Envelope<double>(1405120.04127408,-247003.813399447,
                                     1706357.31328276,-25098.593149577));

        Image32 buf(m.getWidth(),m.getHeight());
        agg_renderer<Image32> ren(m,buf);
        ren.apply();

        save_to_file<ImageData32>("demo.jpg","jpeg",buf.data());
        save_to_file<ImageData32>("demo.png","png",buf.data());
        save_to_file<ImageData32>("demo256.png","png256",buf.data());
        std::cout << "Three maps have been rendered in the current directory:\n"
           "- demo.jpg\n"
           "- demo.png\n"
           "- demo256.png\n"
           "Have a look!\n";
    }
    catch ( const mapnik::config_error & ex )
    {
        std::cerr << "### Configuration error: " << ex.what();
        return EXIT_FAILURE;
    }
    catch ( const std::exception & ex )
    {
        std::cerr << "### std::exception: " << ex.what();
        return EXIT_FAILURE;
    }
    catch ( ... )
    {
        std::cerr << "### Unknown exception." << std::endl;
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}
<|endoftext|>


<commit_before>#include "stdafx.h"
#include "util/fixes.h"
#include <temple/dll.h>
#include "temple_functions.h"
#include "config/config.h"
#include "xp.h"
#include "obj.h"
#include "critter.h"
#include "party.h"
#include "d20_level.h"
#include "gamesystems/objects/objsystem.h"
#include "sound.h"
#include "combat.h"
#include "history.h"
#include "float_line.h"
#include "gamesystems/gamesystems.h"
#include "gamesystems/particlesystems.h"
#include "ui/ui_systems.h"
#include "ui/ui_legacysystems.h"

temple::GlobalPrimitive<float, 0x102CF708> experienceMultiplier;
temple::GlobalPrimitive<int, 0x10BCA850> numCrittersSlainByCR;
temple::GlobalPrimitive<int, 0x10BCA8BC> xpPile; // sum of all the XP given to party members before divinding by party size

static_assert(CRMIN == -2, "CRMIN for XP award definition should be at most -2!");
static_assert(CRMAX >= 20, "CRMAX for XP award definition should be at least 20!");
static_assert(XPTABLE_MAXLEVEL >= 20, "XPTABLE_MAXLEVEL for XP award definition should be at least 20!");

// static int XPAwardTable[XPTABLE_MAXLEVEL][CRMAX - CRMIN + 1] = {};

BOOL XPAward::XpGainProcess(objHndl handle, int xpGainRaw){

	if (!xpGainRaw || !handle)
		return FALSE;

	auto obj = objSystem->GetObject(handle);
	auto couldAlreadyLevelup = d20LevelSys.CanLevelup(handle);

	auto xpReduction = GetMulticlassXpReductionPercent(handle);

	//Check if the multiclass xp penalty should be disabled for this character
	auto res = d20Sys.D20QueryPython(handle, "No MultiClass XP Penalty");
	if (res != 0) {
		xpReduction = 0;
	}
	auto xpGain = (int)((1.0 - xpReduction / 100.0)*xpGainRaw);

	std::string text(fmt::format("{} {} {} {}", description.getDisplayName(handle), combatSys.GetCombatMesLine(145), xpGain, combatSys.GetCombatMesLine(146) )); // [obj] gains [xpGain] experience points
	if (xpReduction){
		text.append(fmt::format(" {}", combatSys.GetCombatMesLine(147)));
	}
	histSys.CreateFromFreeText( (text + "\n").c_str());

	auto xpNew = obj->GetInt32(obj_f_critter_experience) + xpGain;
	auto curLvl = critterSys.GetEffectiveLevel(handle);

	auto xpCap = d20LevelSys.GetXpRequireForLevel(curLvl + 2) - 1;
	if (curLvl >= (int)config.maxLevel)
		xpCap = d20LevelSys.GetXpRequireForLevel(config.maxLevel);

	if (!config.allowXpOverflow && xpNew > (int)xpCap)
		xpNew = xpCap;

	d20Sys.d20SendSignal(handle, DK_SIG_Experience_Awarded, xpNew, 0);
	obj->SetInt32(obj_f_critter_experience, xpNew);

	if (couldAlreadyLevelup || !d20LevelSys.CanLevelup(handle))
		return FALSE;

	floatSys.FloatCombatLine(handle, 148); // gains a level!
	histSys.CreateFromFreeText(fmt::format("{} {}\n", description.getDisplayName(handle), combatSys.GetCombatMesLine(148)).c_str()); // [obj] gains a level!
	gameSystems->GetParticleSys().CreateAtObj("LEVEL UP", handle);

	return TRUE;
}

int XPAward::GetMulticlassXpReductionPercent(objHndl handle){
	auto highestLvl = -1;
	auto reductionPct = 0;

	if (config.laxRules && config.disableMulticlassXpPenalty)
		return reductionPct;

	for (auto it: d20ClassSys.baseClassEnums){
		auto classEnum = (Stat)it;
		auto classLvl = objects.StatLevelGet(handle, classEnum);
		if (classLvl > highestLvl && !d20Sys.d20QueryWithData(handle, DK_QUE_FavoredClass, classEnum,0)){
			highestLvl = classLvl;
		}
	}

	for (auto it : d20ClassSys.baseClassEnums) {
		auto classEnum = (Stat)it;
		auto classLvl = objects.StatLevelGet(handle, classEnum);
		if (classLvl > 0 && classLvl < highestLvl - 1 && !d20Sys.d20QueryWithData(handle, DK_QUE_FavoredClass, classEnum,0)){
			reductionPct += 20;
		}
	}

	if (reductionPct >= 80)
		reductionPct = 80;

	return reductionPct;
}

XPAward::XPAward(){
	int table[XPTABLE_MAXLEVEL][CRMAX - CRMIN + 1];
	memset(table, 0, sizeof(table));

	// First set the table's "spine" - when CR = Level  then   XP = 300*level
	for (int level = 1; level <= XPTABLE_MAXLEVEL; level++) {
		assert(level >= 1);
		assert(level - CRMIN < CRCOUNT);
		if (!config.slowerLevelling || level < 3)
			table[level - 1][level - CRMIN] = level * 300;
		else
			table[level - 1][level - CRMIN] = (int)(
					level * 300 *
						(
							1 - min(0.66f,
								    0.66f * powf(level - 2.0f, 0.1f) / powf(16.0f, 0.1f)
							    )
						)
				);
	}

	// Fill out the bottom left portion - CRs less than level - from highest to lowest
	for (int level = 1; level <= XPTABLE_MAXLEVEL; level++){
		for (int j = level - CRMIN - 1; j >= 2; j--){
			int i = level - 1;
			int cr = j + CRMIN;

			assert(i >= 0 && i < XPTABLE_MAXLEVEL);
			assert(j >= 0 && j < CRCOUNT);

			// 8 CRs below level grant nothing
			if (cr <= level - 8){
				table[i][j] = 0;
			}
			else if (cr == 0){
				table[i][2] = table[i][3] / 2; // CR 1/2
				table[i][1] = table[i][3] / 3; // CR 1/3
				table[i][0] = table[i][3] / 4; // CR 1/4
			}
			else if (cr == level - 1) {
				assert(i >= 1);
				if (config.slowerLevelling)
					table[i][j] = min(table[i - 1][j], (table[i][j + 1] * 6) / 11);
				else
					table[i][j] = min(table[i - 1][j], (table[i][j+1] * 2) /3);
			}
			else {
				assert(i >= 1);
				assert(j + 2 < CRCOUNT);
				if (config.slowerLevelling)
					table[i][j] = min(table[i - 1][j], ( table[i][j + 2] * 3) / 10);
				else
					table[i][j] = min(table[i - 1][j], table[i][j + 2] / 2);
			}
		}
	}

	// Fill out the top right portion
	for (int cr_off = 1; cr_off < CRMAX; cr_off++){

		for (int level = 1; level <= XPTABLE_MAXLEVEL && level + cr_off <= CRMAX; level++){
			int i = level - 1;
			int j = level - CRMIN + cr_off;

			assert(i >= 0 && i < XPTABLE_MAXLEVEL);
			assert(j >= 0 && j < CRCOUNT);

			if (cr_off >= 10){
				table[i][j] = table[level - 1][j - 1]; // repeat the last value
			}
			else if (cr_off == 1){
				assert(j >= 1);
				assert(i + 1 < XPTABLE_MAXLEVEL);
				table[i][j] = max((table[i][j - 1] * 3) / 2, table[i + 1][j]);
			}
			else {
				assert(i + 1 < XPTABLE_MAXLEVEL);
				assert(j >= 2);
				table[i][j] = max(table[i][j - 2] * 2, table[i + 1][j]);
			}
		}
	}

	memcpy(XPAwardTable, table, sizeof(table));
};

// XP Table Fix for higher levels
class XPTableForHighLevels : public TempleFix {
public:

	XPAward *xpawarddd;
	void GiveXPAwards();
	void apply() override;
} xpTableFix;

void XPTableForHighLevels::GiveXPAwards(){
	float fNumLivingPartyMembers = 0.0;

	//int XPAwardTable[XPTABLE_MAXLEVEL][CRMAX - CRMIN + 1] = {};

	for (uint32_t i = 0; i < party.GroupPCsLen(); i++){
		objHndl objHndPC = party.GroupPCsGetMemberN(i);
		if (!critterSys.IsDeadNullDestroyed(objHndPC)){
			fNumLivingPartyMembers += 1.0;
		}
	};
	for (uint32_t i = 0; i < party.GroupNPCFollowersLen(); i++){
		objHndl objHndNPCFollower = party.GroupNPCFollowersGetMemberN(i);
		if (!critterSys.IsDeadNullDestroyed(objHndNPCFollower)
			&& !d20Sys.d20Query(objHndNPCFollower, DK_QUE_ExperienceExempt)){
			fNumLivingPartyMembers += 1.0;
		}
	};
	if (fNumLivingPartyMembers < 0.99){
		return;
	};

	auto killCountByCR = numCrittersSlainByCR.ptr();
	bool bShouldUpdatePartyUI = false;
	int xpForxpPile = 0;

	for (uint32_t i = 0; i < party.GroupListGetLen(); i++){
		objHndl objHnd = party.GroupListGetMemberN(i);

		if (critterSys.IsDeadNullDestroyed(objHnd)){ continue; };
		if (d20Sys.d20Query(objHnd, DK_QUE_ExperienceExempt)) { continue; };
		if (party.ObjIsAIFollower(objHnd)) { continue; };

		int level = critterSys.GetEffectiveLevel(objHnd);
		if (level <= 0) { continue; };

		int xpGainRaw = 0; // raw means it's prior to applying multiclass penalties, which  is Someone Else's Problem :P

		for (int n = 0; n < CR_KILLED_TABLE_SIZE; n++){
			float nkill = (float)killCountByCR[n];
			float xp = (float)xpawarddd->XPAwardTable[level - 1][n];
			if (nkill){
				xpGainRaw += (int)(
					experienceMultiplier *
					nkill *	xp
					);
			}

		}
		xpForxpPile += xpGainRaw;
		xpGainRaw = int((float)(xpGainRaw) / fNumLivingPartyMembers);
		//if (templeFuncs.ObjXPGainProcess(objHnd, xpGainRaw)) {
		if (xpawarddd->XpGainProcess(objHnd, xpGainRaw)){
			auto obj = objSystem->GetObject(objHnd);
			if (obj->IsPC() || config.NPCsLevelLikePCs){
				bShouldUpdatePartyUI = true;
			}
		}
	}

	for (int n = 0; n < CR_KILLED_TABLE_SIZE; n++){
		killCountByCR[n] = 0;
	};
	*(xpPile.ptr()) = xpForxpPile;

	if (bShouldUpdatePartyUI){
		uiSystems->GetParty().Update();
		sound.PlaySound(100001); // LEVEL_UP.WAV
		sound.PlaySound(100001); // amp it up a bit
	}

	return;
}

void XPTableForHighLevels::apply() {
	logger->info("Applying XP Table Extension upto Level 20");
	replaceFunction<void(__cdecl)()>(0x100B5700, []() {
		xpTableFix.GiveXPAwards(); }
	);

	xpawarddd = new XPAward();

	if (config.allowXpOverflow)	{
		writeNoops(0x100B5608);
	}
}
<commit_msg>XP table update: replaced usage of LUT in Restoration spell<commit_after>#include "stdafx.h"
#include "util/fixes.h"
#include <temple/dll.h>
#include "temple_functions.h"
#include "config/config.h"
#include "xp.h"
#include "obj.h"
#include "critter.h"
#include "party.h"
#include "d20_level.h"
#include "gamesystems/objects/objsystem.h"
#include "sound.h"
#include "combat.h"
#include "history.h"
#include "float_line.h"
#include "gamesystems/gamesystems.h"
#include "gamesystems/particlesystems.h"
#include "ui/ui_systems.h"
#include "ui/ui_legacysystems.h"

temple::GlobalPrimitive<float, 0x102CF708> experienceMultiplier;
temple::GlobalPrimitive<int, 0x10BCA850> numCrittersSlainByCR;
temple::GlobalPrimitive<int, 0x10BCA8BC> xpPile; // sum of all the XP given to party members before divinding by party size

static_assert(CRMIN == -2, "CRMIN for XP award definition should be at most -2!");
static_assert(CRMAX >= 20, "CRMAX for XP award definition should be at least 20!");
static_assert(XPTABLE_MAXLEVEL >= 20, "XPTABLE_MAXLEVEL for XP award definition should be at least 20!");

// static int XPAwardTable[XPTABLE_MAXLEVEL][CRMAX - CRMIN + 1] = {};

/* 0x100B5480 */
BOOL XPAward::XpGainProcess(objHndl handle, int xpGainRaw){

	if (!xpGainRaw || !handle)
		return FALSE;

	auto obj = objSystem->GetObject(handle);
	auto couldAlreadyLevelup = d20LevelSys.CanLevelup(handle);

	auto xpReduction = GetMulticlassXpReductionPercent(handle);

	//Check if the multiclass xp penalty should be disabled for this character
	auto res = d20Sys.D20QueryPython(handle, "No MultiClass XP Penalty");
	if (res != 0) {
		xpReduction = 0;
	}
	auto xpGain = (int)((1.0 - xpReduction / 100.0)*xpGainRaw);

	std::string text(fmt::format("{} {} {} {}", description.getDisplayName(handle), combatSys.GetCombatMesLine(145), xpGain, combatSys.GetCombatMesLine(146) )); // [obj] gains [xpGain] experience points
	if (xpReduction){
		text.append(fmt::format(" {}", combatSys.GetCombatMesLine(147)));
	}
	histSys.CreateFromFreeText( (text + "\n").c_str());

	auto xpNew = obj->GetInt32(obj_f_critter_experience) + xpGain;
	auto curLvl = critterSys.GetEffectiveLevel(handle);

	auto xpCap = d20LevelSys.GetXpRequireForLevel(curLvl + 2) - 1;
	if (curLvl >= (int)config.maxLevel)
		xpCap = d20LevelSys.GetXpRequireForLevel(config.maxLevel);

	if (!config.allowXpOverflow && xpNew > (int)xpCap)
		xpNew = xpCap;

	d20Sys.d20SendSignal(handle, DK_SIG_Experience_Awarded, xpNew, 0);
	obj->SetInt32(obj_f_critter_experience, xpNew);

	if (couldAlreadyLevelup || !d20LevelSys.CanLevelup(handle))
		return FALSE;

	floatSys.FloatCombatLine(handle, 148); // gains a level!
	histSys.CreateFromFreeText(fmt::format("{} {}\n", description.getDisplayName(handle), combatSys.GetCombatMesLine(148)).c_str()); // [obj] gains a level!
	gameSystems->GetParticleSys().CreateAtObj("LEVEL UP", handle);

	return TRUE;
}

int XPAward::GetMulticlassXpReductionPercent(objHndl handle){
	auto highestLvl = -1;
	auto reductionPct = 0;

	if (config.laxRules && config.disableMulticlassXpPenalty)
		return reductionPct;

	for (auto it: d20ClassSys.baseClassEnums){
		auto classEnum = (Stat)it;
		auto classLvl = objects.StatLevelGet(handle, classEnum);
		if (classLvl > highestLvl && !d20Sys.d20QueryWithData(handle, DK_QUE_FavoredClass, classEnum,0)){
			highestLvl = classLvl;
		}
	}

	for (auto it : d20ClassSys.baseClassEnums) {
		auto classEnum = (Stat)it;
		auto classLvl = objects.StatLevelGet(handle, classEnum);
		if (classLvl > 0 && classLvl < highestLvl - 1 && !d20Sys.d20QueryWithData(handle, DK_QUE_FavoredClass, classEnum,0)){
			reductionPct += 20;
		}
	}

	if (reductionPct >= 80)
		reductionPct = 80;

	return reductionPct;
}

XPAward::XPAward(){
	int table[XPTABLE_MAXLEVEL][CRMAX - CRMIN + 1];
	memset(table, 0, sizeof(table));

	// First set the table's "spine" - when CR = Level  then   XP = 300*level
	for (int level = 1; level <= XPTABLE_MAXLEVEL; level++) {
		assert(level >= 1);
		assert(level - CRMIN < CRCOUNT);
		if (!config.slowerLevelling || level < 3)
			table[level - 1][level - CRMIN] = level * 300;
		else
			table[level - 1][level - CRMIN] = (int)(
					level * 300 *
						(
							1 - min(0.66f,
								    0.66f * powf(level - 2.0f, 0.1f) / powf(16.0f, 0.1f)
							    )
						)
				);
	}

	// Fill out the bottom left portion - CRs less than level - from highest to lowest
	for (int level = 1; level <= XPTABLE_MAXLEVEL; level++){
		for (int j = level - CRMIN - 1; j >= 2; j--){
			int i = level - 1;
			int cr = j + CRMIN;

			assert(i >= 0 && i < XPTABLE_MAXLEVEL);
			assert(j >= 0 && j < CRCOUNT);

			// 8 CRs below level grant nothing
			if (cr <= level - 8){
				table[i][j] = 0;
			}
			else if (cr == 0){
				table[i][2] = table[i][3] / 2; // CR 1/2
				table[i][1] = table[i][3] / 3; // CR 1/3
				table[i][0] = table[i][3] / 4; // CR 1/4
			}
			else if (cr == level - 1) {
				assert(i >= 1);
				if (config.slowerLevelling)
					table[i][j] = min(table[i - 1][j], (table[i][j + 1] * 6) / 11);
				else
					table[i][j] = min(table[i - 1][j], (table[i][j+1] * 2) /3);
			}
			else {
				assert(i >= 1);
				assert(j + 2 < CRCOUNT);
				if (config.slowerLevelling)
					table[i][j] = min(table[i - 1][j], ( table[i][j + 2] * 3) / 10);
				else
					table[i][j] = min(table[i - 1][j], table[i][j + 2] / 2);
			}
		}
	}

	// Fill out the top right portion
	for (int cr_off = 1; cr_off < CRMAX; cr_off++){

		for (int level = 1; level <= XPTABLE_MAXLEVEL && level + cr_off <= CRMAX; level++){
			int i = level - 1;
			int j = level - CRMIN + cr_off;

			assert(i >= 0 && i < XPTABLE_MAXLEVEL);
			assert(j >= 0 && j < CRCOUNT);

			if (cr_off >= 10){
				table[i][j] = table[level - 1][j - 1]; // repeat the last value
			}
			else if (cr_off == 1){
				assert(j >= 1);
				assert(i + 1 < XPTABLE_MAXLEVEL);
				table[i][j] = max((table[i][j - 1] * 3) / 2, table[i + 1][j]);
			}
			else {
				assert(i + 1 < XPTABLE_MAXLEVEL);
				assert(j >= 2);
				table[i][j] = max(table[i][j - 2] * 2, table[i + 1][j]);
			}
		}
	}

	memcpy(XPAwardTable, table, sizeof(table));
};

// XP Table Fix for higher levels
class XPTableForHighLevels : public TempleFix {
public:

	XPAward *xpawarddd;
	void GiveXPAwards();
	void apply() override;
} xpTableFix;

/* 0x100B5700 */
void XPTableForHighLevels::GiveXPAwards(){
	float fNumLivingPartyMembers = 0.0;

	//int XPAwardTable[XPTABLE_MAXLEVEL][CRMAX - CRMIN + 1] = {};

	for (uint32_t i = 0; i < party.GroupPCsLen(); i++){
		objHndl objHndPC = party.GroupPCsGetMemberN(i);
		if (!critterSys.IsDeadNullDestroyed(objHndPC)){
			fNumLivingPartyMembers += 1.0;
		}
	};
	for (uint32_t i = 0; i < party.GroupNPCFollowersLen(); i++){
		objHndl objHndNPCFollower = party.GroupNPCFollowersGetMemberN(i);
		if (!critterSys.IsDeadNullDestroyed(objHndNPCFollower)
			&& !d20Sys.d20Query(objHndNPCFollower, DK_QUE_ExperienceExempt)){
			fNumLivingPartyMembers += 1.0;
		}
	};
	if (fNumLivingPartyMembers < 0.99){
		return;
	};

	auto killCountByCR = numCrittersSlainByCR.ptr();
	bool bShouldUpdatePartyUI = false;
	int xpForxpPile = 0;

	for (uint32_t i = 0; i < party.GroupListGetLen(); i++){
		objHndl objHnd = party.GroupListGetMemberN(i);

		if (critterSys.IsDeadNullDestroyed(objHnd)){ continue; };
		if (d20Sys.d20Query(objHnd, DK_QUE_ExperienceExempt)) { continue; };
		if (party.ObjIsAIFollower(objHnd)) { continue; };

		int level = critterSys.GetEffectiveLevel(objHnd);
		if (level <= 0) { continue; };

		int xpGainRaw = 0; // raw means it's prior to applying multiclass penalties, which  is Someone Else's Problem :P

		for (int n = 0; n < CR_KILLED_TABLE_SIZE; n++){
			float nkill = (float)killCountByCR[n];
			float xp = (float)xpawarddd->XPAwardTable[level - 1][n];
			if (nkill){
				xpGainRaw += (int)(
					experienceMultiplier *
					nkill *	xp
					);
			}

		}
		xpForxpPile += xpGainRaw;
		xpGainRaw = int((float)(xpGainRaw) / fNumLivingPartyMembers);
		//if (templeFuncs.ObjXPGainProcess(objHnd, xpGainRaw)) {
		if (xpawarddd->XpGainProcess(objHnd, xpGainRaw)){
			auto obj = objSystem->GetObject(objHnd);
			if (obj->IsPC() || config.NPCsLevelLikePCs){
				bShouldUpdatePartyUI = true;
			}
		}
	}

	for (int n = 0; n < CR_KILLED_TABLE_SIZE; n++){
		killCountByCR[n] = 0;
	};
	*(xpPile.ptr()) = xpForxpPile;

	if (bShouldUpdatePartyUI){
		uiSystems->GetParty().Update();
		sound.PlaySound(100001); // LEVEL_UP.WAV
		sound.PlaySound(100001); // amp it up a bit
	}

	return;
}

void XPTableForHighLevels::apply() {
	logger->info("Applying XP Table Extension upto Level 20");
	replaceFunction<void(__cdecl)()>(0x100B5700, []() {
		xpTableFix.GiveXPAwards(); }
	);

	// GetLevelForXp (used for restoration)
	replaceFunction<int(__cdecl)(int)>(0x10080300, [](int xp)->int {
		int maxLvl = (int)config.maxLevel;
		auto xpMaxLvl = d20LevelSys.GetXpRequireForLevel(maxLvl);
		if (xp <= xpMaxLvl) {
			for (int lvl = maxLvl; lvl > 0; --lvl) {
				if (xp >= d20LevelSys.GetXpRequireForLevel(lvl)) {
					return lvl;
				}
			}
			return 0;
		}
		else {
			return maxLvl + 1;
		}
		});

	xpawarddd = new XPAward();

	if (config.allowXpOverflow)	{
		writeNoops(0x100B5608);
	}
}
<|endoftext|>


<commit_before>#include "DatagramIterator.h"
#include "../bits/buffers.h"
#include "../values/Value.h"
#include "../module/ArrayType.h"
#include "../module/Struct.h"
#include "../module/Method.h"
#include "../module/Field.h"
#include "../module/Parameter.h"
using namespace std;
namespace bamboo {  // close namespace bamboo

// read_value interprets the following data as a
// value for the DistributedType in native endianness.
Value DatagramIterator::read_value(const DistributedType *type) {
    // Note: We can probably make this a lot more efficient by constructing the value manually
    //       from the wire-endian data instead of unpacking it into native endianess first.
    //       Though that would probably cause some code duplication with Value::from_packed.
    vector<uint8_t> packed = read_packed(type);
    return Value::from_packed(type, packed);
}

vector<uint8_t> DatagramIterator::read_packed(const DistributedType *type) {
#ifndef PLATFORM_BIG_ENDIAN
    // We're little endian so we don't have to worry about byte-swapping the data
    const uint8_t *start = m_dg->get_data() + m_offset;
    skip_type(type); // note: this will advanced m_offset
    return vector<uint8_t>(start, m_dg->get_data() + m_offset);
#else
    // Lets go ahead and unpack that manually
    vector<uint8_t> buf;
    read_packed(type, buf);
    return buf;
#endif

}

void DatagramIterator::read_packed(const DistributedType *dtype, vector<uint8_t>& buffer) {
#ifndef PLATFORM_BIG_ENDIAN
    if(dtype->has_fixed_size()) {
        // If we're a fixed-sized type like uint, int, float, etc
        // Also any other type lucky enough to be fixed size will be faster.
        vector<uint8_t> data = read_data(dtype->get_size());
        pack_value(data, buffer);
    }
#endif

    // For the unlucky types/machines, we have to figure out their size manually
    switch(dtype->get_subtype()) {
        case kTypeChar:
            pack_value(read_char(), buffer);
            break;
        case kTypeInt8:
            pack_value(read_int8(), buffer);
            break;
        case kTypeInt16:
            pack_value(read_int16(), buffer);
            break;
        case kTypeInt32:
            pack_value(read_int32(), buffer);
            break;
        case kTypeInt64:
            pack_value(read_int64(), buffer);
            break;
        case kTypeUint8:
            pack_value(read_uint8(), buffer);
            break;
        case kTypeUint16:
            pack_value(read_uint16(), buffer);
            break;
        case kTypeUint32:
            pack_value(read_uint32(), buffer);
            break;
        case kTypeUint64:
            pack_value(read_uint64(), buffer);
            break;
        case kTypeFloat32:
            pack_value(read_float32(), buffer);
            break;
        case kTypeFloat64:
            pack_value(read_float64(), buffer);
            break;
        case kTypeString:
        case kTypeBlob:
            pack_value(read_data(dtype->get_size()), buffer);
            break;
        case kTypeArray: {
            const ArrayType *arr = dtype->as_array();
            if(arr->get_element_type()->get_size() == 1) {
                // If the element size is 1, we don't have to worry about endianness
                pack_value(read_data(dtype->get_size()), buffer);
                break;
            }

            // Read elements from the array till we reach the expected size
            sizetag_t len = dtype->get_size();
            sizetag_t array_end = m_offset + len;
            while(m_offset < array_end) {
                read_packed(arr->get_element_type(), buffer);
            }
            if(m_offset > array_end) {
                stringstream error;
                error << "Datagram iterator tried to read array data, but array data"
                      " exceeded the expected array length of " << len << ".\n";
                throw DatagramIteratorEOF(error.str());
            }
            break;
        }
        case kTypeVarstring:
        case kTypeVarblob: {
            sizetag_t len = read_size();
            pack_value(len, buffer);
            pack_value(read_data(len), buffer);
            break;
        }
        case kTypeVararray: {
            sizetag_t len = read_size();
            pack_value(len, buffer);

            const ArrayType *arr = dtype->as_array();
            if(arr->get_element_type()->get_size() == 1) {
                // If the element size is 1, we don't have to worry about endianness
                pack_value(read_data(len), buffer);
                break;
            }

            // Read elements from the array till we reach the expected size
            sizetag_t array_end = m_offset + len;
            while(m_offset < array_end) {
                read_packed(arr->get_element_type(), buffer);
            }
            if(m_offset > array_end) {
                stringstream error;
                error << "Datagram iterator tried to read array data, but array data"
                      " exceeded the expected array length of " << len << ".\n";
                throw DatagramIteratorEOF(error.str());
            }
            break;
        }
        case kTypeStruct: {
            const Struct *dstruct = dtype->as_struct();
            size_t num_fields = dstruct->get_num_fields();
            for(unsigned int i = 0; i < num_fields; ++i) {
                read_packed(dstruct->get_field(i)->get_type(), buffer);
            }
            break;
        }
        case kTypeMethod: {
            const Method *dmethod = dtype->as_method();
            size_t num_params = dmethod->get_num_parameters();
            for(unsigned int i = 0; i < num_params; ++i) {
                read_packed(dmethod->get_parameter(i)->get_type(), buffer);
            }
            break;
        }
        case kTypeInvalid: {
            break;
        }
    }
}

// skip_type can be used to seek past the packed data for a DistributedType.
//     Throws DatagramIteratorEOF if it skips past the end of the datagram.
void DatagramIterator::skip_type(const DistributedType *dtype) {
    if(dtype->has_fixed_size()) {
        sizetag_t length = dtype->get_size();
        check_read_length(length);
        m_offset += length;
        return;
    }

    switch(dtype->get_subtype()) {
        case kTypeVarstring:
        case kTypeVarblob:
        case kTypeVararray: {
            sizetag_t length = read_size();
            check_read_length(length);
            m_offset += length;
            break;
        }
        case kTypeStruct: {
            const Struct *dstruct = dtype->as_struct();
            size_t num_fields = dstruct->get_num_fields();
            for(unsigned int i = 0; i < num_fields; ++i) {
                skip_type(dstruct->get_field(i)->get_type());
            }
            break;
        }
        case kTypeMethod: {
            const Method *dmethod = dtype->as_method();
            size_t num_params = dmethod->get_num_parameters();
            for(unsigned int i = 0; i < num_params; ++i) {
                skip_type(dmethod->get_parameter(i)->get_type());
            }
            break;
        }
        default: {
            // This case should be impossible, but a default is required by compilers
            break;
        }
    }
}

} // close namespace bamboo
<commit_msg>Bugfix: Add missing return in DatagramIterator::read_packed<commit_after>#include "DatagramIterator.h"
#include "../bits/buffers.h"
#include "../values/Value.h"
#include "../module/ArrayType.h"
#include "../module/Struct.h"
#include "../module/Method.h"
#include "../module/Field.h"
#include "../module/Parameter.h"
using namespace std;
namespace bamboo {  // close namespace bamboo

// read_value interprets the following data as a
// value for the DistributedType in native endianness.
Value DatagramIterator::read_value(const DistributedType *type) {
    // Note: We can probably make this a lot more efficient by constructing the value manually
    //       from the wire-endian data instead of unpacking it into native endianess first.
    //       Though that would probably cause some code duplication with Value::from_packed.
    vector<uint8_t> packed = read_packed(type);
    return Value::from_packed(type, packed);
}

vector<uint8_t> DatagramIterator::read_packed(const DistributedType *type) {
#ifndef PLATFORM_BIG_ENDIAN
    // We're little endian so we don't have to worry about byte-swapping the data
    const uint8_t *start = m_dg->get_data() + m_offset;
    skip_type(type); // note: this will advanced m_offset
    return vector<uint8_t>(start, m_dg->get_data() + m_offset);
#else
    // Lets go ahead and unpack that manually
    vector<uint8_t> buf;
    read_packed(type, buf);
    return buf;
#endif

}

void DatagramIterator::read_packed(const DistributedType *dtype, vector<uint8_t>& buffer) {
#ifndef PLATFORM_BIG_ENDIAN
    if(dtype->has_fixed_size()) {
        // If we're a fixed-sized type like uint, int, float, etc
        // Also any other type lucky enough to be fixed size will be faster.
        vector<uint8_t> data = read_data(dtype->get_size());
        pack_value(data, buffer);
        return;
    }
#endif

    // For the unlucky types/machines, we have to figure out their size manually
    switch(dtype->get_subtype()) {
        case kTypeChar:
            pack_value(read_char(), buffer);
            break;
        case kTypeInt8:
            pack_value(read_int8(), buffer);
            break;
        case kTypeInt16:
            pack_value(read_int16(), buffer);
            break;
        case kTypeInt32:
            pack_value(read_int32(), buffer);
            break;
        case kTypeInt64:
            pack_value(read_int64(), buffer);
            break;
        case kTypeUint8:
            pack_value(read_uint8(), buffer);
            break;
        case kTypeUint16:
            pack_value(read_uint16(), buffer);
            break;
        case kTypeUint32:
            pack_value(read_uint32(), buffer);
            break;
        case kTypeUint64:
            pack_value(read_uint64(), buffer);
            break;
        case kTypeFloat32:
            pack_value(read_float32(), buffer);
            break;
        case kTypeFloat64:
            pack_value(read_float64(), buffer);
            break;
        case kTypeString:
        case kTypeBlob:
            pack_value(read_data(dtype->get_size()), buffer);
            break;
        case kTypeArray: {
            const ArrayType *arr = dtype->as_array();
            if(arr->get_element_type()->get_size() == 1) {
                // If the element size is 1, we don't have to worry about endianness
                pack_value(read_data(dtype->get_size()), buffer);
                break;
            }

            // Read elements from the array till we reach the expected size
            sizetag_t len = dtype->get_size();
            sizetag_t array_end = m_offset + len;
            while(m_offset < array_end) {
                read_packed(arr->get_element_type(), buffer);
            }
            if(m_offset > array_end) {
                stringstream error;
                error << "Datagram iterator tried to read array data, but array data"
                      " exceeded the expected array length of " << len << ".\n";
                throw DatagramIteratorEOF(error.str());
            }
            break;
        }
        case kTypeVarstring:
        case kTypeVarblob: {
            sizetag_t len = read_size();
            pack_value(len, buffer);
            pack_value(read_data(len), buffer);
            break;
        }
        case kTypeVararray: {
            sizetag_t len = read_size();
            pack_value(len, buffer);

            const ArrayType *arr = dtype->as_array();
            if(arr->get_element_type()->get_size() == 1) {
                // If the element size is 1, we don't have to worry about endianness
                pack_value(read_data(len), buffer);
                break;
            }

            // Read elements from the array till we reach the expected size
            sizetag_t array_end = m_offset + len;
            while(m_offset < array_end) {
                read_packed(arr->get_element_type(), buffer);
            }
            if(m_offset > array_end) {
                stringstream error;
                error << "Datagram iterator tried to read array data, but array data"
                      " exceeded the expected array length of " << len << ".\n";
                throw DatagramIteratorEOF(error.str());
            }
            break;
        }
        case kTypeStruct: {
            const Struct *dstruct = dtype->as_struct();
            size_t num_fields = dstruct->get_num_fields();
            for(unsigned int i = 0; i < num_fields; ++i) {
                read_packed(dstruct->get_field(i)->get_type(), buffer);
            }
            break;
        }
        case kTypeMethod: {
            const Method *dmethod = dtype->as_method();
            size_t num_params = dmethod->get_num_parameters();
            for(unsigned int i = 0; i < num_params; ++i) {
                read_packed(dmethod->get_parameter(i)->get_type(), buffer);
            }
            break;
        }
        case kTypeInvalid: {
            break;
        }
    }
}

// skip_type can be used to seek past the packed data for a DistributedType.
//     Throws DatagramIteratorEOF if it skips past the end of the datagram.
void DatagramIterator::skip_type(const DistributedType *dtype) {
    if(dtype->has_fixed_size()) {
        sizetag_t length = dtype->get_size();
        check_read_length(length);
        m_offset += length;
        return;
    }

    switch(dtype->get_subtype()) {
        case kTypeVarstring:
        case kTypeVarblob:
        case kTypeVararray: {
            sizetag_t length = read_size();
            check_read_length(length);
            m_offset += length;
            break;
        }
        case kTypeStruct: {
            const Struct *dstruct = dtype->as_struct();
            size_t num_fields = dstruct->get_num_fields();
            for(unsigned int i = 0; i < num_fields; ++i) {
                skip_type(dstruct->get_field(i)->get_type());
            }
            break;
        }
        case kTypeMethod: {
            const Method *dmethod = dtype->as_method();
            size_t num_params = dmethod->get_num_parameters();
            for(unsigned int i = 0; i < num_params; ++i) {
                skip_type(dmethod->get_parameter(i)->get_type());
            }
            break;
        }
        default: {
            // This case should be impossible, but a default is required by compilers
            break;
        }
    }
}

} // close namespace bamboo
<|endoftext|>


<commit_before>#include "xlink/XLinkConnection.hpp"

#include <array>
#include <cassert>
#include <chrono>
#include <cstring>
#include <fstream>
#include <iostream>
#include <thread>
#include <vector>

// project
#include "depthai/utility/Initialization.hpp"

// libraries
#include <XLink/XLink.h>
extern "C" {
#include <XLink/XLinkLog.h>
}

#include <spdlog/spdlog.h>
#include "spdlog/details/os.h"
#include "spdlog/fmt/chrono.h"

namespace dai {

// DeviceInfo
DeviceInfo::DeviceInfo(std::string mxId) {
    // Add dash at the end of mxId ([mxId]-[xlinkDevName] format)
    mxId += "-";
    // Construct device info which will points to device with specific mxId
    std::strncpy(desc.name, mxId.c_str(), sizeof(desc.name));

    // set protocol to any
    desc.protocol = X_LINK_ANY_PROTOCOL;

    // set platform to any
    desc.platform = X_LINK_ANY_PLATFORM;

    // set state to any
    state = X_LINK_ANY_STATE;
}
DeviceInfo::DeviceInfo(const char* mxId) : DeviceInfo(std::string(mxId)) {}

std::string DeviceInfo::getMxId() const {
    std::string mxId = "";
    auto len = std::strlen(desc.name);
    for(std::size_t i = 0; i < len; i++) {
        if(desc.name[i] == '-') break;
        mxId += desc.name[i];
    }
    return mxId;
}

static DeviceInfo deviceInfoFix(const DeviceInfo& d, XLinkDeviceState_t state);

// STATIC
constexpr std::chrono::milliseconds XLinkConnection::WAIT_FOR_BOOTUP_TIMEOUT;
constexpr std::chrono::milliseconds XLinkConnection::WAIT_FOR_CONNECT_TIMEOUT;

std::vector<DeviceInfo> XLinkConnection::getAllConnectedDevices(XLinkDeviceState_t state) {
    initialize();

    std::vector<DeviceInfo> devices;

    std::vector<XLinkDeviceState_t> states;
    if(state == X_LINK_ANY_STATE) {
        states = {X_LINK_UNBOOTED, X_LINK_BOOTLOADER, X_LINK_BOOTED, X_LINK_FLASH_BOOTED};
    } else {
        states = {state};
    }

    // Get all available devices (unbooted & booted)
    for(const auto& state : states) {
        unsigned int numdev = 0;
        std::array<deviceDesc_t, 32> deviceDescAll = {};
        deviceDesc_t suitableDevice = {};
        suitableDevice.protocol = X_LINK_ANY_PROTOCOL;
        suitableDevice.platform = X_LINK_ANY_PLATFORM;

        auto status = XLinkFindAllSuitableDevices(state, suitableDevice, deviceDescAll.data(), static_cast<unsigned int>(deviceDescAll.size()), &numdev);
        if(status != X_LINK_SUCCESS) throw std::runtime_error("Couldn't retrieve all connected devices");

        for(unsigned i = 0; i < numdev; i++) {
            DeviceInfo info = {};
            info.desc = deviceDescAll.at(i);
            info.state = state;
            devices.push_back(info);
        }
    }

    return devices;
}

std::tuple<bool, DeviceInfo> XLinkConnection::getFirstDevice(XLinkDeviceState_t state) {
    auto devices = getAllConnectedDevices();
    for(const auto& d : devices) {
        if(d.state == state || state == X_LINK_ANY_STATE) return {true, d};
    }
    return {false, DeviceInfo()};
}

std::tuple<bool, DeviceInfo> XLinkConnection::getDeviceByMxId(std::string mxId, XLinkDeviceState_t state) {
    auto devices = getAllConnectedDevices();
    for(const auto& d : devices) {
        if(d.state == state || state == X_LINK_ANY_STATE) {
            if(d.getMxId() == mxId) {
                return {true, d};
            }
        }
    }
    return {false, DeviceInfo()};
}

DeviceInfo XLinkConnection::bootBootloader(const DeviceInfo& deviceInfo) {
    using namespace std::chrono;

    // Device is in flash booted state. Boot to bootloader first
    XLinkBootBootloader(&deviceInfo.desc);

    // Fix deviceInfo for BOOTLOADER state
    DeviceInfo deviceToWait = deviceInfoFix(deviceInfo, X_LINK_BOOTLOADER);

    // Device desc if found
    deviceDesc_t foundDeviceDesc = {};

    // Wait for device to get to bootloader state
    XLinkError_t rc;
    auto tstart = steady_clock::now();
    do {
        rc = XLinkFindFirstSuitableDevice(X_LINK_BOOTLOADER, deviceToWait.desc, &foundDeviceDesc);
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
        if(rc == X_LINK_SUCCESS) break;
    } while(steady_clock::now() - tstart < WAIT_FOR_BOOTUP_TIMEOUT);

    // If device not found
    if(rc != X_LINK_SUCCESS) {
        throw std::runtime_error("Failed to find device (" + std::string(deviceToWait.desc.name) + "), error message: " + convertErrorCodeToString(rc));
    }

    deviceToWait.state = X_LINK_BOOTLOADER;
    deviceToWait.desc = foundDeviceDesc;
    return deviceToWait;
}

XLinkConnection::XLinkConnection(const DeviceInfo& deviceDesc, std::vector<std::uint8_t> mvcmdBinary, XLinkDeviceState_t expectedState) {
    bootDevice = true;
    bootWithPath = false;
    this->mvcmd = std::move(mvcmdBinary);
    initDevice(deviceDesc, expectedState);
}

XLinkConnection::XLinkConnection(const DeviceInfo& deviceDesc, std::string pathToMvcmd, XLinkDeviceState_t expectedState) {
    if(!pathToMvcmd.empty()) {
        std::ifstream f(pathToMvcmd.c_str());
        if(!f.good()) throw std::runtime_error("Error path doesn't exist. Note: Environment variables in path are not expanded. (E.g. '~', '$PATH').");
    }
    bootDevice = true;
    bootWithPath = true;
    this->pathToMvcmd = std::move(pathToMvcmd);
    initDevice(deviceDesc, expectedState);
}

// Skip boot
XLinkConnection::XLinkConnection(const DeviceInfo& deviceDesc, XLinkDeviceState_t expectedState) {
    bootDevice = false;
    initDevice(deviceDesc, expectedState);
}

bool XLinkConnection::isClosed() const {
    return closed;
}

void XLinkConnection::checkClosed() const {
    if(isClosed()) throw std::invalid_argument("XLinkConnection already closed or disconnected");
}

void XLinkConnection::close() {
    if(closed.exchange(true)) return;

    if(deviceLinkId != -1 && rebootOnDestruction) {
        auto tmp = deviceLinkId;
        XLinkResetRemote(deviceLinkId);
        deviceLinkId = -1;

        // TODO(themarpe) - revisit for TCPIP protocol
        using namespace std::chrono;
        const auto BOOTUP_SEARCH = std::chrono::seconds(5);

        // Wait till same device reappears (is rebooted).
        // Only in case if device was booted to begin with
        if(bootDevice) {
            auto t1 = steady_clock::now();
            bool found = false;
            do {
                DeviceInfo tmp;
                for(const auto& state : {X_LINK_UNBOOTED, X_LINK_BOOTLOADER}) {
                    std::tie(found, tmp) = XLinkConnection::getDeviceByMxId(deviceInfo.getMxId(), state);
                    if(found) break;
                }
            } while(!found && steady_clock::now() - t1 < BOOTUP_SEARCH);
        }

        spdlog::debug("XLinkResetRemote of linkId: ({})", tmp);
    }
}

XLinkConnection::~XLinkConnection() {
    close();
}

void XLinkConnection::setRebootOnDestruction(bool reboot) {
    rebootOnDestruction = reboot;
}

bool XLinkConnection::getRebootOnDestruction() const {
    return rebootOnDestruction;
}

bool XLinkConnection::bootAvailableDevice(const deviceDesc_t& deviceToBoot, const std::string& pathToMvcmd) {
    auto status = XLinkBoot(&deviceToBoot, pathToMvcmd.c_str());
    return status == X_LINK_SUCCESS;
}

bool XLinkConnection::bootAvailableDevice(const deviceDesc_t& deviceToBoot, std::vector<std::uint8_t>& mvcmd) {
    auto status = XLinkBootMemory(&deviceToBoot, mvcmd.data(), static_cast<unsigned long>(mvcmd.size()));
    return status == X_LINK_SUCCESS;
}

void XLinkConnection::initDevice(const DeviceInfo& deviceToInit, XLinkDeviceState_t expectedState) {
    initialize();
    assert(deviceLinkId == -1);

    XLinkError_t rc = X_LINK_ERROR;
    deviceDesc_t deviceDesc = {};

    using namespace std::chrono;

    // if device is in UNBOOTED then boot
    bootDevice = deviceToInit.state == X_LINK_UNBOOTED;

    std::chrono::milliseconds connectTimeout = WAIT_FOR_CONNECT_TIMEOUT;
    std::chrono::milliseconds bootupTimeout = WAIT_FOR_BOOTUP_TIMEOUT;

    // Override with environment variables, if set
    const std::vector<std::pair<std::string, std::chrono::milliseconds*>> evars = {
            {"DEPTHAI_CONNECT_TIMEOUT", &connectTimeout},
            {"DEPTHAI_BOOTUP_TIMEOUT", &bootupTimeout},
    };

    for(auto ev : evars) {
        auto name = ev.first;
        auto valstr = spdlog::details::os::getenv(name.c_str());
        if(!valstr.empty()) {
            try {
                std::chrono::milliseconds value{std::stoi(valstr)};
                auto initial = *ev.second;
                *ev.second = value;
                spdlog::warn("{} override: {} -> {}", name, initial, value);
            } catch(const std::invalid_argument& e) {
                spdlog::warn("{} value invalid: {}", name, e.what());
            }
        }
    }

    // boot device
    if(bootDevice) {
        DeviceInfo deviceToBoot = deviceInfoFix(deviceToInit, X_LINK_UNBOOTED);
        deviceDesc_t foundDeviceDesc = {};

        // Wait for the device to be available
        auto tstart = steady_clock::now();
        do {
            rc = XLinkFindFirstSuitableDevice(X_LINK_UNBOOTED, deviceToBoot.desc, &foundDeviceDesc);
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
            if(rc == X_LINK_SUCCESS) break;
        } while(steady_clock::now() - tstart < bootupTimeout);

        // If device not found
        if(rc != X_LINK_SUCCESS) {
            throw std::runtime_error("Failed to find device (" + std::string(deviceToBoot.desc.name) + "), error message: " + convertErrorCodeToString(rc));
        }

        bool bootStatus;
        if(bootWithPath) {
            bootStatus = bootAvailableDevice(foundDeviceDesc, pathToMvcmd);
        } else {
            bootStatus = bootAvailableDevice(foundDeviceDesc, mvcmd);
        }
        if(!bootStatus) {
            throw std::runtime_error("Failed to boot device!");
        }
    }

    // Search for booted device
    {
        // Create description of device to look for
        DeviceInfo bootedDeviceInfo = deviceToInit;
        if(deviceToInit.desc.protocol != X_LINK_TCP_IP) {
            bootedDeviceInfo = deviceInfoFix(deviceToInit, expectedState);
        }

        // Find booted device
        auto tstart = steady_clock::now();
        do {
            rc = XLinkFindFirstSuitableDevice(expectedState, bootedDeviceInfo.desc, &deviceDesc);
            if(rc == X_LINK_SUCCESS) break;
        } while(steady_clock::now() - tstart < bootupTimeout);

        if(rc != X_LINK_SUCCESS) {
            throw std::runtime_error("Failed to find device after booting, error message: " + convertErrorCodeToString(rc));
        }
    }

    // Try to connect to device
    {
        XLinkHandler_t connectionHandler = {};
        connectionHandler.devicePath = deviceDesc.name;
        connectionHandler.protocol = deviceDesc.protocol;

        auto tstart = steady_clock::now();
        do {
            if((rc = XLinkConnect(&connectionHandler)) == X_LINK_SUCCESS) break;
        } while(steady_clock::now() - tstart < connectTimeout);

        if(rc != X_LINK_SUCCESS) throw std::runtime_error("Failed to connect to device, error message: " + convertErrorCodeToString(rc));

        deviceLinkId = connectionHandler.linkId;
        deviceInfo.desc = deviceDesc;
        deviceInfo.state = X_LINK_BOOTED;
    }
}

int XLinkConnection::getLinkId() const {
    return deviceLinkId;
}

std::string XLinkConnection::convertErrorCodeToString(XLinkError_t errorCode) {
    switch(errorCode) {
        case X_LINK_SUCCESS:
            return "X_LINK_SUCCESS";
        case X_LINK_ALREADY_OPEN:
            return "X_LINK_ALREADY_OPEN";
        case X_LINK_COMMUNICATION_NOT_OPEN:
            return "X_LINK_COMMUNICATION_NOT_OPEN";
        case X_LINK_COMMUNICATION_FAIL:
            return "X_LINK_COMMUNICATION_FAIL";
        case X_LINK_COMMUNICATION_UNKNOWN_ERROR:
            return "X_LINK_COMMUNICATION_UNKNOWN_ERROR";
        case X_LINK_DEVICE_NOT_FOUND:
            return "X_LINK_DEVICE_NOT_FOUND";
        case X_LINK_TIMEOUT:
            return "X_LINK_TIMEOUT";
        case X_LINK_ERROR:
            return "X_LINK_ERROR";
        case X_LINK_OUT_OF_MEMORY:
            return "X_LINK_OUT_OF_MEMORY";
        case X_LINK_NOT_IMPLEMENTED:
            return "X_LINK_NOT_IMPLEMENTED";
        default:
            return "<UNKNOWN ERROR>";
    }
}

DeviceInfo deviceInfoFix(const DeviceInfo& dev, XLinkDeviceState_t state) {
    if(dev.desc.protocol == X_LINK_TCP_IP) {
        // X_LINK_TCP_IP doesn't need a fix
        return dev;
    }

    DeviceInfo fixed(dev);

    // Remove everything after dash
    for(int i = sizeof(fixed.desc.name) - 1; i >= 0; i--) {
        if(fixed.desc.name[i] != '-')
            fixed.desc.name[i] = 0;
        else
            break;
    }

    // If bootloader state add "bootloader"
    if(state == X_LINK_BOOTLOADER) {
        std::strncat(fixed.desc.name, "bootloader", sizeof(fixed.desc.name) - std::strlen(fixed.desc.name));
        // set platform to any
        fixed.desc.platform = X_LINK_ANY_PLATFORM;
    } else if(state == X_LINK_UNBOOTED) {
        // if unbooted add ending ("ma2480" or "ma2450")
        if(fixed.desc.platform == X_LINK_MYRIAD_2) {
            std::strncat(fixed.desc.name, "ma2450", sizeof(fixed.desc.name) - std::strlen(fixed.desc.name));
        } else {
            std::strncat(fixed.desc.name, "ma2480", sizeof(fixed.desc.name) - std::strlen(fixed.desc.name));
        }
    } else if(state == X_LINK_BOOTED) {
        // set platform to any
        fixed.desc.platform = X_LINK_ANY_PLATFORM;
    }

    return fixed;
}

}  // namespace dai
<commit_msg>Fix strncpy build warning: specified bound 48 equals destination size [-Wstringop-truncation]<commit_after>#include "xlink/XLinkConnection.hpp"

#include <array>
#include <cassert>
#include <chrono>
#include <cstring>
#include <fstream>
#include <iostream>
#include <thread>
#include <vector>

// project
#include "depthai/utility/Initialization.hpp"

// libraries
#include <XLink/XLink.h>
extern "C" {
#include <XLink/XLinkLog.h>
}

#include <spdlog/spdlog.h>
#include "spdlog/details/os.h"
#include "spdlog/fmt/chrono.h"

namespace dai {

// DeviceInfo
DeviceInfo::DeviceInfo(std::string mxId) {
    // Add dash at the end of mxId ([mxId]-[xlinkDevName] format)
    mxId += "-";
    // Construct device info which will points to device with specific mxId
    std::strncpy(desc.name, mxId.c_str(), sizeof(desc.name) - 1);

    // set protocol to any
    desc.protocol = X_LINK_ANY_PROTOCOL;

    // set platform to any
    desc.platform = X_LINK_ANY_PLATFORM;

    // set state to any
    state = X_LINK_ANY_STATE;
}
DeviceInfo::DeviceInfo(const char* mxId) : DeviceInfo(std::string(mxId)) {}

std::string DeviceInfo::getMxId() const {
    std::string mxId = "";
    auto len = std::strlen(desc.name);
    for(std::size_t i = 0; i < len; i++) {
        if(desc.name[i] == '-') break;
        mxId += desc.name[i];
    }
    return mxId;
}

static DeviceInfo deviceInfoFix(const DeviceInfo& d, XLinkDeviceState_t state);

// STATIC
constexpr std::chrono::milliseconds XLinkConnection::WAIT_FOR_BOOTUP_TIMEOUT;
constexpr std::chrono::milliseconds XLinkConnection::WAIT_FOR_CONNECT_TIMEOUT;

std::vector<DeviceInfo> XLinkConnection::getAllConnectedDevices(XLinkDeviceState_t state) {
    initialize();

    std::vector<DeviceInfo> devices;

    std::vector<XLinkDeviceState_t> states;
    if(state == X_LINK_ANY_STATE) {
        states = {X_LINK_UNBOOTED, X_LINK_BOOTLOADER, X_LINK_BOOTED, X_LINK_FLASH_BOOTED};
    } else {
        states = {state};
    }

    // Get all available devices (unbooted & booted)
    for(const auto& state : states) {
        unsigned int numdev = 0;
        std::array<deviceDesc_t, 32> deviceDescAll = {};
        deviceDesc_t suitableDevice = {};
        suitableDevice.protocol = X_LINK_ANY_PROTOCOL;
        suitableDevice.platform = X_LINK_ANY_PLATFORM;

        auto status = XLinkFindAllSuitableDevices(state, suitableDevice, deviceDescAll.data(), static_cast<unsigned int>(deviceDescAll.size()), &numdev);
        if(status != X_LINK_SUCCESS) throw std::runtime_error("Couldn't retrieve all connected devices");

        for(unsigned i = 0; i < numdev; i++) {
            DeviceInfo info = {};
            info.desc = deviceDescAll.at(i);
            info.state = state;
            devices.push_back(info);
        }
    }

    return devices;
}

std::tuple<bool, DeviceInfo> XLinkConnection::getFirstDevice(XLinkDeviceState_t state) {
    auto devices = getAllConnectedDevices();
    for(const auto& d : devices) {
        if(d.state == state || state == X_LINK_ANY_STATE) return {true, d};
    }
    return {false, DeviceInfo()};
}

std::tuple<bool, DeviceInfo> XLinkConnection::getDeviceByMxId(std::string mxId, XLinkDeviceState_t state) {
    auto devices = getAllConnectedDevices();
    for(const auto& d : devices) {
        if(d.state == state || state == X_LINK_ANY_STATE) {
            if(d.getMxId() == mxId) {
                return {true, d};
            }
        }
    }
    return {false, DeviceInfo()};
}

DeviceInfo XLinkConnection::bootBootloader(const DeviceInfo& deviceInfo) {
    using namespace std::chrono;

    // Device is in flash booted state. Boot to bootloader first
    XLinkBootBootloader(&deviceInfo.desc);

    // Fix deviceInfo for BOOTLOADER state
    DeviceInfo deviceToWait = deviceInfoFix(deviceInfo, X_LINK_BOOTLOADER);

    // Device desc if found
    deviceDesc_t foundDeviceDesc = {};

    // Wait for device to get to bootloader state
    XLinkError_t rc;
    auto tstart = steady_clock::now();
    do {
        rc = XLinkFindFirstSuitableDevice(X_LINK_BOOTLOADER, deviceToWait.desc, &foundDeviceDesc);
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
        if(rc == X_LINK_SUCCESS) break;
    } while(steady_clock::now() - tstart < WAIT_FOR_BOOTUP_TIMEOUT);

    // If device not found
    if(rc != X_LINK_SUCCESS) {
        throw std::runtime_error("Failed to find device (" + std::string(deviceToWait.desc.name) + "), error message: " + convertErrorCodeToString(rc));
    }

    deviceToWait.state = X_LINK_BOOTLOADER;
    deviceToWait.desc = foundDeviceDesc;
    return deviceToWait;
}

XLinkConnection::XLinkConnection(const DeviceInfo& deviceDesc, std::vector<std::uint8_t> mvcmdBinary, XLinkDeviceState_t expectedState) {
    bootDevice = true;
    bootWithPath = false;
    this->mvcmd = std::move(mvcmdBinary);
    initDevice(deviceDesc, expectedState);
}

XLinkConnection::XLinkConnection(const DeviceInfo& deviceDesc, std::string pathToMvcmd, XLinkDeviceState_t expectedState) {
    if(!pathToMvcmd.empty()) {
        std::ifstream f(pathToMvcmd.c_str());
        if(!f.good()) throw std::runtime_error("Error path doesn't exist. Note: Environment variables in path are not expanded. (E.g. '~', '$PATH').");
    }
    bootDevice = true;
    bootWithPath = true;
    this->pathToMvcmd = std::move(pathToMvcmd);
    initDevice(deviceDesc, expectedState);
}

// Skip boot
XLinkConnection::XLinkConnection(const DeviceInfo& deviceDesc, XLinkDeviceState_t expectedState) {
    bootDevice = false;
    initDevice(deviceDesc, expectedState);
}

bool XLinkConnection::isClosed() const {
    return closed;
}

void XLinkConnection::checkClosed() const {
    if(isClosed()) throw std::invalid_argument("XLinkConnection already closed or disconnected");
}

void XLinkConnection::close() {
    if(closed.exchange(true)) return;

    if(deviceLinkId != -1 && rebootOnDestruction) {
        auto tmp = deviceLinkId;
        XLinkResetRemote(deviceLinkId);
        deviceLinkId = -1;

        // TODO(themarpe) - revisit for TCPIP protocol
        using namespace std::chrono;
        const auto BOOTUP_SEARCH = std::chrono::seconds(5);

        // Wait till same device reappears (is rebooted).
        // Only in case if device was booted to begin with
        if(bootDevice) {
            auto t1 = steady_clock::now();
            bool found = false;
            do {
                DeviceInfo tmp;
                for(const auto& state : {X_LINK_UNBOOTED, X_LINK_BOOTLOADER}) {
                    std::tie(found, tmp) = XLinkConnection::getDeviceByMxId(deviceInfo.getMxId(), state);
                    if(found) break;
                }
            } while(!found && steady_clock::now() - t1 < BOOTUP_SEARCH);
        }

        spdlog::debug("XLinkResetRemote of linkId: ({})", tmp);
    }
}

XLinkConnection::~XLinkConnection() {
    close();
}

void XLinkConnection::setRebootOnDestruction(bool reboot) {
    rebootOnDestruction = reboot;
}

bool XLinkConnection::getRebootOnDestruction() const {
    return rebootOnDestruction;
}

bool XLinkConnection::bootAvailableDevice(const deviceDesc_t& deviceToBoot, const std::string& pathToMvcmd) {
    auto status = XLinkBoot(&deviceToBoot, pathToMvcmd.c_str());
    return status == X_LINK_SUCCESS;
}

bool XLinkConnection::bootAvailableDevice(const deviceDesc_t& deviceToBoot, std::vector<std::uint8_t>& mvcmd) {
    auto status = XLinkBootMemory(&deviceToBoot, mvcmd.data(), static_cast<unsigned long>(mvcmd.size()));
    return status == X_LINK_SUCCESS;
}

void XLinkConnection::initDevice(const DeviceInfo& deviceToInit, XLinkDeviceState_t expectedState) {
    initialize();
    assert(deviceLinkId == -1);

    XLinkError_t rc = X_LINK_ERROR;
    deviceDesc_t deviceDesc = {};

    using namespace std::chrono;

    // if device is in UNBOOTED then boot
    bootDevice = deviceToInit.state == X_LINK_UNBOOTED;

    std::chrono::milliseconds connectTimeout = WAIT_FOR_CONNECT_TIMEOUT;
    std::chrono::milliseconds bootupTimeout = WAIT_FOR_BOOTUP_TIMEOUT;

    // Override with environment variables, if set
    const std::vector<std::pair<std::string, std::chrono::milliseconds*>> evars = {
            {"DEPTHAI_CONNECT_TIMEOUT", &connectTimeout},
            {"DEPTHAI_BOOTUP_TIMEOUT", &bootupTimeout},
    };

    for(auto ev : evars) {
        auto name = ev.first;
        auto valstr = spdlog::details::os::getenv(name.c_str());
        if(!valstr.empty()) {
            try {
                std::chrono::milliseconds value{std::stoi(valstr)};
                auto initial = *ev.second;
                *ev.second = value;
                spdlog::warn("{} override: {} -> {}", name, initial, value);
            } catch(const std::invalid_argument& e) {
                spdlog::warn("{} value invalid: {}", name, e.what());
            }
        }
    }

    // boot device
    if(bootDevice) {
        DeviceInfo deviceToBoot = deviceInfoFix(deviceToInit, X_LINK_UNBOOTED);
        deviceDesc_t foundDeviceDesc = {};

        // Wait for the device to be available
        auto tstart = steady_clock::now();
        do {
            rc = XLinkFindFirstSuitableDevice(X_LINK_UNBOOTED, deviceToBoot.desc, &foundDeviceDesc);
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
            if(rc == X_LINK_SUCCESS) break;
        } while(steady_clock::now() - tstart < bootupTimeout);

        // If device not found
        if(rc != X_LINK_SUCCESS) {
            throw std::runtime_error("Failed to find device (" + std::string(deviceToBoot.desc.name) + "), error message: " + convertErrorCodeToString(rc));
        }

        bool bootStatus;
        if(bootWithPath) {
            bootStatus = bootAvailableDevice(foundDeviceDesc, pathToMvcmd);
        } else {
            bootStatus = bootAvailableDevice(foundDeviceDesc, mvcmd);
        }
        if(!bootStatus) {
            throw std::runtime_error("Failed to boot device!");
        }
    }

    // Search for booted device
    {
        // Create description of device to look for
        DeviceInfo bootedDeviceInfo = deviceToInit;
        if(deviceToInit.desc.protocol != X_LINK_TCP_IP) {
            bootedDeviceInfo = deviceInfoFix(deviceToInit, expectedState);
        }

        // Find booted device
        auto tstart = steady_clock::now();
        do {
            rc = XLinkFindFirstSuitableDevice(expectedState, bootedDeviceInfo.desc, &deviceDesc);
            if(rc == X_LINK_SUCCESS) break;
        } while(steady_clock::now() - tstart < bootupTimeout);

        if(rc != X_LINK_SUCCESS) {
            throw std::runtime_error("Failed to find device after booting, error message: " + convertErrorCodeToString(rc));
        }
    }

    // Try to connect to device
    {
        XLinkHandler_t connectionHandler = {};
        connectionHandler.devicePath = deviceDesc.name;
        connectionHandler.protocol = deviceDesc.protocol;

        auto tstart = steady_clock::now();
        do {
            if((rc = XLinkConnect(&connectionHandler)) == X_LINK_SUCCESS) break;
        } while(steady_clock::now() - tstart < connectTimeout);

        if(rc != X_LINK_SUCCESS) throw std::runtime_error("Failed to connect to device, error message: " + convertErrorCodeToString(rc));

        deviceLinkId = connectionHandler.linkId;
        deviceInfo.desc = deviceDesc;
        deviceInfo.state = X_LINK_BOOTED;
    }
}

int XLinkConnection::getLinkId() const {
    return deviceLinkId;
}

std::string XLinkConnection::convertErrorCodeToString(XLinkError_t errorCode) {
    switch(errorCode) {
        case X_LINK_SUCCESS:
            return "X_LINK_SUCCESS";
        case X_LINK_ALREADY_OPEN:
            return "X_LINK_ALREADY_OPEN";
        case X_LINK_COMMUNICATION_NOT_OPEN:
            return "X_LINK_COMMUNICATION_NOT_OPEN";
        case X_LINK_COMMUNICATION_FAIL:
            return "X_LINK_COMMUNICATION_FAIL";
        case X_LINK_COMMUNICATION_UNKNOWN_ERROR:
            return "X_LINK_COMMUNICATION_UNKNOWN_ERROR";
        case X_LINK_DEVICE_NOT_FOUND:
            return "X_LINK_DEVICE_NOT_FOUND";
        case X_LINK_TIMEOUT:
            return "X_LINK_TIMEOUT";
        case X_LINK_ERROR:
            return "X_LINK_ERROR";
        case X_LINK_OUT_OF_MEMORY:
            return "X_LINK_OUT_OF_MEMORY";
        case X_LINK_NOT_IMPLEMENTED:
            return "X_LINK_NOT_IMPLEMENTED";
        default:
            return "<UNKNOWN ERROR>";
    }
}

DeviceInfo deviceInfoFix(const DeviceInfo& dev, XLinkDeviceState_t state) {
    if(dev.desc.protocol == X_LINK_TCP_IP) {
        // X_LINK_TCP_IP doesn't need a fix
        return dev;
    }

    DeviceInfo fixed(dev);

    // Remove everything after dash
    for(int i = sizeof(fixed.desc.name) - 1; i >= 0; i--) {
        if(fixed.desc.name[i] != '-')
            fixed.desc.name[i] = 0;
        else
            break;
    }

    // If bootloader state add "bootloader"
    if(state == X_LINK_BOOTLOADER) {
        std::strncat(fixed.desc.name, "bootloader", sizeof(fixed.desc.name) - std::strlen(fixed.desc.name));
        // set platform to any
        fixed.desc.platform = X_LINK_ANY_PLATFORM;
    } else if(state == X_LINK_UNBOOTED) {
        // if unbooted add ending ("ma2480" or "ma2450")
        if(fixed.desc.platform == X_LINK_MYRIAD_2) {
            std::strncat(fixed.desc.name, "ma2450", sizeof(fixed.desc.name) - std::strlen(fixed.desc.name));
        } else {
            std::strncat(fixed.desc.name, "ma2480", sizeof(fixed.desc.name) - std::strlen(fixed.desc.name));
        }
    } else if(state == X_LINK_BOOTED) {
        // set platform to any
        fixed.desc.platform = X_LINK_ANY_PLATFORM;
    }

    return fixed;
}

}  // namespace dai
<|endoftext|>