api: allow lights.read() to be filtered by light names (#271)

## Link to GitHub Issue, if one exists
Fixes https://github.com/spice2x/spice2x.github.io/issues/179

## Description of change
Previously, lights.read() did not accept any parameters, instead it
returned all lights implemented by the current game.
Problem is that this is too large for some embedded implementations;
also just inefficient.

Add code to accept light names as strings and only return those in the
result.
For performance, use `robin_hood::unordered_map` to speed up the lookup
operation, which was previously a linear search.
Add this to Python wrapper library. Did not bother with others.

Also, as a bonus - update lights.write_reset() to also accept a flat
list of strings, as opposed to array containing a string, which was
silly.

## Compiling
 

## Testing
Tested using spiceremote and writing a short Python program.
This commit is contained in:
bicarus-dev
2025-03-22 21:50:51 -07:00
committed by GitHub
parent b267ad09ac
commit 6de122c9f8
4 changed files with 98 additions and 48 deletions
+12
View File
@@ -181,6 +181,12 @@ All of those three modules have equally named methods for you to call.
- removes the override value from the objects specified by name - removes the override value from the objects specified by name
- if no names were passed, all overrides will be removed - if no names were passed, all overrides will be removed
##### Additional API for lights
- read(name: string, ...)
- same as read(), but you can specify light names
- write_reset(name: str, ...)
- same as write_reset(), but it accepts a flat list of strings
#### Touch #### Touch
- read() - read()
- returns an array of state objects containing id, x and y - returns an array of state objects containing id, x and y
@@ -252,6 +258,12 @@ which also means that your hex edits are applicable directly.
- image_resize_set_scene(scene: int) - image_resize_set_scene(scene: int)
- sets the active scene for image resize state; set to 0 to disable resize - sets the active scene for image resize state; set to 0 to disable resize
## Native wrapper libraries
Spicetools provides wrapper libraries in: Arduino, C++, Dart, and Python.
Python is the only one that is fully spec compliant.
Other libraries may be missing features and contain bugs; please feel free to
contribute code to fill the gaps if you work on a project using these libraries.
## License ## License
Unless otherwise noted, all files are licensed under the GPLv3. Unless otherwise noted, all files are licensed under the GPLv3.
See the LICENSE file for the full license text. See the LICENSE file for the full license text.
+74 -46
View File
@@ -19,11 +19,16 @@ namespace api::modules {
functions["read"] = std::bind(&Lights::read, this, _1, _2); functions["read"] = std::bind(&Lights::read, this, _1, _2);
functions["write"] = std::bind(&Lights::write, this, _1, _2); functions["write"] = std::bind(&Lights::write, this, _1, _2);
functions["write_reset"] = std::bind(&Lights::write_reset, this, _1, _2); functions["write_reset"] = std::bind(&Lights::write_reset, this, _1, _2);
lights = games::get_lights(eamuse_get_game());
this->lights = games::get_lights(eamuse_get_game());
for (auto &light : *this->lights) {
this->lights_by_names.emplace(light.getName(), light);
}
} }
/** /**
* read() * read()
* read(name: str, ...)
*/ */
void Lights::read(api::Request &req, Response &res) { void Lights::read(api::Request &req, Response &res) {
@@ -32,17 +37,39 @@ namespace api::modules {
return; return;
} }
// add state for each light // all lights for this game
for (auto &light : *this->lights) { if (req.params.Size() == 0) {
Value state(kArrayType); // add state for each light
Value light_name(light.getName().c_str(), res.doc()->GetAllocator()); for (auto &light : *this->lights) {
Value light_state(GameAPI::Lights::readLight(RI_MGR, light)); get_light(light, res);
Value light_enabled(light.override_enabled); }
state.PushBack(light_name, res.doc()->GetAllocator());
state.PushBack(light_state, res.doc()->GetAllocator()); return;
state.PushBack(light_enabled, res.doc()->GetAllocator());
res.add_data(state);
} }
// specified light names
for (Value &param : req.params.GetArray()) {
// check params
if (!param.IsString()) {
error_type(res, "name", "string");
return;
}
const auto name = param.GetString();
if (this->lights_by_names.contains(name)) {
get_light(this->lights_by_names.at(name).get(), res);
}
}
}
void Lights::get_light(Light &light, Response &res) {
Value state(kArrayType);
Value light_name(light.getName().c_str(), res.doc()->GetAllocator());
Value light_state(GameAPI::Lights::readLight(RI_MGR, light));
Value light_enabled(light.override_enabled);
state.PushBack(light_name, res.doc()->GetAllocator());
state.PushBack(light_state, res.doc()->GetAllocator());
state.PushBack(light_enabled, res.doc()->GetAllocator());
res.add_data(state);
} }
/** /**
@@ -90,6 +117,7 @@ namespace api::modules {
/** /**
* write_reset() * write_reset()
* write_reset(name: str, ...)
* write_reset([name: str], ...) * write_reset([name: str], ...)
*/ */
void Lights::write_reset(Request &req, Response &res) { void Lights::write_reset(Request &req, Response &res) {
@@ -119,26 +147,28 @@ namespace api::modules {
// loop parameters // loop parameters
for (Value &param : req.params.GetArray()) { for (Value &param : req.params.GetArray()) {
const char* light_name = nullptr;
// check params // check params
if (!param.IsArray()) { if (param.IsArray()) {
error(res, "parameters must be arrays"); if (param.Size() < 1) {
return; error_params_insufficient(res);
continue;
}
if (!param[0].IsString()) {
error_type(res, "name", "string");
continue;
}
// get params
light_name = param[0].GetString();
} else if (param.IsString()) {
light_name = param.GetString();
} else {
error(res, "parameters must be arrays or strings");
} }
if (param.Size() < 1) {
error_params_insufficient(res);
continue;
}
if (!param[0].IsString()) {
error_type(res, "name", "string");
continue;
}
// get params
auto light_name = param[0].GetString();
// write analog state // write analog state
if (!this->write_light_reset(light_name)) { if (light_name && !this->write_light_reset(light_name)) {
error_unknown(res, "analog", light_name); error_unknown(res, "analog", light_name);
continue; continue;
} }
@@ -153,21 +183,20 @@ namespace api::modules {
} }
// find light // find light
for (auto &light : *this->lights) { if (this->lights_by_names.contains(name)) {
if (light.getName() == name) { auto &light = this->lights_by_names.at(name).get();
light.override_state = CLAMP(state, 0.f, 1.f); light.override_state = CLAMP(state, 0.f, 1.f);
light.override_enabled = true; light.override_enabled = true;
if (cfg::CONFIGURATOR_STANDALONE) { if (cfg::CONFIGURATOR_STANDALONE) {
GameAPI::Lights::writeLight(RI_MGR, light, state); GameAPI::Lights::writeLight(RI_MGR, light, state);
}
return true;
} }
}
// unknown light return true;
return false; } else {
// unknown light
return false;
}
} }
bool Lights::write_light_reset(std::string name) { bool Lights::write_light_reset(std::string name) {
@@ -178,14 +207,13 @@ namespace api::modules {
} }
// find light // find light
for (auto &light : *this->lights) { if (this->lights_by_names.contains(name)) {
if (light.getName() == name) { auto &light = this->lights_by_names.at(name).get();
light.override_enabled = false; light.override_enabled = false;
return true; return true;
} } else {
// unknown light
return false;
} }
// unknown light
return false;
} }
} }
+4
View File
@@ -1,6 +1,8 @@
#pragma once #pragma once
#include <vector> #include <vector>
#include <external/robin_hood.h>
#include "api/module.h" #include "api/module.h"
#include "api/request.h" #include "api/request.h"
#include "cfg/api.h" #include "cfg/api.h"
@@ -15,6 +17,7 @@ namespace api::modules {
// state // state
std::vector<Light> *lights; std::vector<Light> *lights;
robin_hood::unordered_map<std::string, std::reference_wrapper<Light>> lights_by_names;
// function definitions // function definitions
void read(Request &req, Response &res); void read(Request &req, Response &res);
@@ -22,6 +25,7 @@ namespace api::modules {
void write_reset(Request &req, Response &res); void write_reset(Request &req, Response &res);
// helper // helper
void get_light(Light &light, Response &res);
bool write_light(std::string name, float state); bool write_light(std::string name, float state);
bool write_light_reset(std::string name); bool write_light_reset(std::string name);
}; };
@@ -2,8 +2,14 @@ from .connection import Connection
from .request import Request from .request import Request
def lights_read(con: Connection): def lights_read(con: Connection, light_names=None):
res = con.request(Request("lights", "read")) req = Request("lights", "read")
if light_names:
for light_name in light_names:
req.add_param(light_name)
res = con.request(req)
return res.get_data() return res.get_data()