Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,25 @@ Entries below v2.5.0 were reconstructed from the release notes and the commit
history, so they summarise each release rather than list every change. The
`Full Changelog` link on each version gives the complete commit range.

## [Unreleased]

### Fixed

- Commands that need a domain print an error when there is none, instead of
segfaulting: `FireNode`, `FireFront`, `startFire`, `goTo`, `save[]`,
`trigger[fuel]`, `trigger[fuelIndice]`, any command scheduled with `@t` or
`@nowplus`, and a `FireDomain[...]` with missing options. A script whose
`loadData` failed used to crash on the next `startFire`.
- `clear[]` followed by a new domain no longer crashes on the first
`startFire`. The destroyed domain's layers stayed in the `DataBroker`
shortcuts, so the new domain never created its own altitude and wind
layers.
- The `BMap` layer holds the arrival times again; its recomputation had been
commented out, so `getDoubleArray("BMap")` returned zeros.
- `plot[...;filename=*.asc]` writes a valid ESRI ASCII grid: one row per y,
north first, at full precision. It used to write one row per x and round
values to six significant digits.

## [v2.6.0] — 2026-10-26

### Added
Expand Down Expand Up @@ -233,6 +252,7 @@ The V2 release: the HTTP interface, and alignment with Meso-NH V4.7.2.
Tagged before the sources were moved into `src/`, so that this point in the
repository stays easy to return to.

[Unreleased]: https://github.com/forefireAPI/forefire/compare/v2.6.0...dev
[v2.6.0]: https://github.com/forefireAPI/forefire/releases/tag/v2.6.0
[v2.5.0]: https://github.com/forefireAPI/forefire/releases/tag/v2.5.0
[v2.4.2]: https://github.com/forefireAPI/forefire/releases/tag/v2.4.2
Expand Down
6 changes: 2 additions & 4 deletions src/BurningMapLayer.h
Original file line number Diff line number Diff line change
Expand Up @@ -130,16 +130,14 @@ T BurningMapLayer<T>::getNearestData(FFPoint loc){
template<typename T>
void BurningMapLayer<T>::getMatrix(
FFArray<T>** matrix, const double& t){
// cout << "getting matrix lmayer "<<nx<<"BMAPS"<<ny<<endl;
/*if ( t != latestCall ){

if ( t != latestCall ){
for ( size_t i=0; i < nx; i++ ){
for ( size_t j=0; j < ny; j++ ){
(*arrivalTimes)(i,j) = domain->getArrivalTime(i, j);
}
}
latestCall = t;
}*/
}
// Affecting the computed matrix to the desired array
*matrix = arrivalTimes;
}
Expand Down
61 changes: 49 additions & 12 deletions src/Command.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include <dirent.h>
#include <cmath>
#include <fstream>
#include <limits>
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"

Expand Down Expand Up @@ -254,6 +255,8 @@ namespace libforefire

int Command::startFire(const string &arg, size_t &numTabs)
{
if (!domainExists("startFire"))
return error;
double t = getDomain()->getTime();
SimulationParameters *simParam = SimulationParameters::GetInstance();

Expand Down Expand Up @@ -676,6 +679,8 @@ namespace libforefire

int Command::createFireFront(const string &arg, size_t &numTabs)
{
if (!domainExists("FireFront"))
return error;

size_t n = argCount(arg);
if (n >= 1)
Expand Down Expand Up @@ -731,6 +736,8 @@ namespace libforefire

int Command::addFireNode(const string &arg, size_t &numTabs)
{
if (!domainExists("FireNode"))
return error;

if (lastReadLoc == 0)
lastReadLoc = new FFPoint(-numeric_limits<double>::infinity(), -numeric_limits<double>::infinity(), 0);
Expand Down Expand Up @@ -977,6 +984,8 @@ namespace libforefire

int Command::goTo(const string &arg, size_t &numTabs)
{
if (!domainExists("goTo"))
return error;

/* Advancing simulation to the prescribed time */
endTime = getFloat("t", arg);
Expand Down Expand Up @@ -1930,6 +1939,8 @@ namespace libforefire
}
else
{
if (!domainExists("save"))
return error;
getDomain()->saveArrivalTimeNC(); // Default save operation if no arguments provided
}

Expand Down Expand Up @@ -2160,6 +2171,8 @@ namespace libforefire

if (tmpArgs[0] == "fuel")
{
if (!domainExists("trigger[fuel]"))
return error;

if (getDomain()->getDataBroker() != 0)
{
Expand All @@ -2180,6 +2193,8 @@ namespace libforefire
}
if (tmpArgs[0] == "fuelIndice")
{
if (!domainExists("trigger[fuelIndice]"))
return error;
if (getDomain()->getDataBroker() != 0)
{
FFPoint loc = getPoint("loc", arg);
Expand Down Expand Up @@ -2991,7 +3006,8 @@ namespace libforefire
delete currentSession.fd;
currentSession.fd =nullptr;
//delete currentSession.outStrRep;
currentSession.sim->getSchedule()->clear();
if (currentSession.sim != nullptr)
currentSession.sim->getSchedule()->clear();
// delete currentSession.params;
return normal;
}
Expand Down Expand Up @@ -3365,6 +3381,12 @@ namespace libforefire
{
if (postcmd.size() > 1)
{
// Scheduled commands go to the domain's time table.
if (!domainExists(scmd + " scheduled with @"))
{
command.clear();
return;
}
string atCommand = line.substr(0, line.rfind("]") + 1);
string whenCommand = line.substr(line.rfind("]") + 1, -1);
double whenDouble = getFloat("@t", whenCommand);
Expand Down Expand Up @@ -3393,6 +3415,9 @@ namespace libforefire
}
else
{
// The domain ID, when there is a domain, prefixes the error messages.
auto where = []()
{ return getDomain() ? to_string(getDomain()->getDomainID()) + ": " : string(); };
try
{
// calling the right function
Expand All @@ -3401,26 +3426,26 @@ namespace libforefire
}
catch (BadOption &)
{
cout << getDomain()->getDomainID() << ": "
cout << where()
<< "argument(s) '" << command[1] << "' is (are) not fit for command '"
<< command[0] << "'" << endl;
cout << "type 'man[" << command[0] << "]' for more information" << endl;
}
catch (MissingOption &mo)
{
cout << getDomain()->getDomainID() << ": "
cout << where()
<< "command '" << command[0] << "' misses "
<< mo.num << " options." << endl;
cout << "type 'man[" << command[0] << "]' for more information" << endl;
}
catch (MissingTime &)
{
cout << getDomain()->getDomainID() << ": "
cout << where()
<< "you have to specify a time for that command (as in 't=0.')" << endl;
}
catch (...)
{
cout << getDomain()->getDomainID() << ": "
cout << where()
<< " PROBLEM: Command associated to " << line << " ended in error" << endl;
}
}
Expand Down Expand Up @@ -3526,6 +3551,15 @@ namespace libforefire
return currentSession.fd;
}

bool Command::domainExists(const string &command)
{
if (getDomain() != nullptr)
return true;
cout << "Error: " << command << " needs a domain, create one first with "
<< "FireDomain[...] or loadData[...]" << endl;
return false;
}

string Command::dumpString()
{
return currentSession.outStrRep->outputstr.str();
Expand Down Expand Up @@ -3792,9 +3826,10 @@ namespace libforefire
return;
}

// Determine the number of rows and columns.
size_t nrows = matrix.size();
size_t ncols = (nrows > 0) ? matrix[0].size() : 0;
// The matrix is indexed [x][y] from the south-west corner, while an
// ASCII grid has one row per y, from north to south.
size_t ncols = matrix.size();
size_t nrows = (ncols > 0) ? matrix[0].size() : 0;

// Compute the cell size.
// Here we assume that (NEX - SWX) corresponds to the total width of the matrix.
Expand All @@ -3808,13 +3843,15 @@ namespace libforefire
out << "cellsize " << cellsize << "\n";
out << "NODATA_value -9999\n";

// Write the matrix data row by row.
for (size_t i = 0; i < nrows; i++)
// Write the matrix data row by row, at full precision.
out.precision(std::numeric_limits<double>::max_digits10);
for (size_t row = 0; row < nrows; row++)
{
for (size_t j = 0; j < ncols; j++)
size_t j = nrows - 1 - row;
for (size_t i = 0; i < ncols; i++)
{
out << matrix[i][j];
if (j < ncols - 1)
if (i < ncols - 1)
out << " ";
}
out << "\n";
Expand Down
2 changes: 2 additions & 0 deletions src/Command.h
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,8 @@ class Command {

/*! \brief accessor to the domain */
static FireDomain* getDomain();
/*! \brief true if a domain exists, otherwise prints why `command` cannot run */
static bool domainExists(const string& command);
/*! \brief command to redirect the ostringstream */
static void setOstringstream(ostringstream*);

Expand Down
32 changes: 32 additions & 0 deletions src/DataBroker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,40 @@ namespace libforefire
commonInitialization();
}

namespace
{
// Nulls a static layer shortcut that points into `owned`.
template <typename L>
void forgetIfOwned(L *&shortcut, const list<DataLayer<double> *> &owned)
{
for (DataLayer<double> *layer : owned)
{
if (layer == shortcut)
{
shortcut = 0;
return;
}
}
}
}

DataBroker::~DataBroker()
{
// The static shortcuts outlive this broker. Left pointing at the
// layers deleted below, they make the next domain's broker believe
// its altitude and wind layers exist, and skip creating them.
forgetIfOwned(fuelLayer, layers);
forgetIfOwned(moistureLayer, layers);
forgetIfOwned(temperatureLayer, layers);
forgetIfOwned(dummyLayer, layers);
forgetIfOwned(altitudeLayer, layers);
forgetIfOwned(forcedArrivalTimeLayer, layers);
forgetIfOwned(slopeLayer, layers);
forgetIfOwned(windULayer, layers);
forgetIfOwned(windVLayer, layers);
forgetIfOwned(PwindULayer, layers);
forgetIfOwned(PwindVLayer, layers);

// Deleting layers. loadData lists the domain's propagative layer here
// too, so it can be looked up, but the domain frees that one (#205).
while (!layers.empty())
Expand Down
3 changes: 2 additions & 1 deletion tests/unit/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ add_executable(forefire_unit_tests
test_topology.cpp
test_timetable.cpp
test_quit_command.cpp
test_commands.cpp
test_data_broker.cpp)

target_link_libraries(forefire_unit_tests PRIVATE forefireL)
Expand All @@ -33,7 +34,7 @@ endif()

# doctest groups its cases into suites; registering one CTest entry per suite
# keeps `ctest` output useful without needing doctest's CMake integration.
foreach(_suite "model registry" "propagation models" "flux models" "http server" "topology" "fire front" "timetable" "quit command" "data broker")
foreach(_suite "model registry" "propagation models" "flux models" "http server" "topology" "fire front" "timetable" "quit command" "commands" "data broker")
string(REPLACE " " "_" _suite_id "${_suite}")
add_test(NAME "unit.${_suite_id}"
COMMAND forefire_unit_tests --test-suite=${_suite} --no-skipped-summary)
Expand Down
Loading
Loading