use crate::engine::ecs::component::{EmissiveComponent, TransformComponent, TransitionComponent};
use crate::engine::ecs::{ComponentId, IntentValue, World};
use crate::scripting::object::Value;
pub(crate) fn supports_component_method(component_type: &str, method: &str) -> bool {
(matches!(
component_type,
"T" | "Transform" | "TransformComponent" | "transform"
) && matches!(method, "update_transform" | "look_at"))
|| (matches!(
component_type,
"EM" | "Emissive" | "EmissiveComponent" | "emissive"
) && matches!(method, "set_intensity" | "on" | "off"))
|| (matches!(
component_type,
"HttpClient" | "HttpClientComponent" | "http_client"
) && matches!(method, "get" | "post" | "put" | "delete"))
|| (matches!(
component_type,
"HttpServer" | "HttpServerComponent" | "http_server"
) && matches!(method, "reply_text"))
}
pub(crate) fn invoke_component_method(
world: &mut World,
id: ComponentId,
component_type: &str,
method: &str,
args: &[Value],
mut emit_intent: impl FnMut(IntentValue),
) -> Result<Value, String> {
match (component_type, method) {
("T" | "Transform" | "TransformComponent" | "transform", "update_transform") => {
let [translation, rotation_euler, scale] = match args {
[translation, rotation, scale] => [
value_as_f32_array::<3>(translation)?,
value_as_f32_array::<3>(rotation)?,
value_as_f32_array::<3>(scale)?,
],
other => {
return Err(format!(
"update_transform: expected three vec3 array arguments, got {:?}",
other
));
}
};
world
.get_component_by_id_as::<TransformComponent>(id)
.ok_or_else(|| "update_transform(): not a TransformComponent".to_string())?;
emit_intent(IntentValue::UpdateTransform {
component_ids: vec![id],
translation,
rotation_quat_xyzw: TransformComponent::new()
.with_rotation_euler(rotation_euler[0], rotation_euler[1], rotation_euler[2])
.transform
.rotation,
scale,
});
Ok(Value::Null)
}
("T" | "Transform" | "TransformComponent" | "transform", "look_at") => {
let [target_world] = match args {
[target_world] => [value_as_f32_array::<3>(target_world)?],
other => {
return Err(format!(
"look_at: expected one vec3 array argument, got {:?}",
other
));
}
};
world
.get_component_by_id_as::<TransformComponent>(id)
.ok_or_else(|| "look_at(): not a TransformComponent".to_string())?;
emit_intent(IntentValue::LookAt {
component_ids: vec![id],
target_world,
});
Ok(Value::Null)
}
("EM" | "Emissive" | "EmissiveComponent" | "emissive", "set_intensity" | "on" | "off") => {
let intensity = match method {
"on" => 1.0,
"off" => 0.0,
"set_intensity" => match args.first() {
Some(Value::Number(n)) => (*n as f32).max(0.0),
Some(other) => {
return Err(format!(
"set_intensity: expected number argument, got {:?}",
other
));
}
None => return Err("set_intensity: missing number argument".into()),
},
_ => unreachable!(),
};
world
.get_component_by_id_as::<EmissiveComponent>(id)
.ok_or_else(|| format!("{method}(): not an EmissiveComponent"))?;
let has_transition_child = world.children_of(id).iter().any(|&child| {
world
.get_component_by_id_as::<TransitionComponent>(child)
.is_some()
});
let is_attached = world.parent_of(id).is_some();
if !(is_attached && has_transition_child) {
let emissive = world
.get_component_by_id_as_mut::<EmissiveComponent>(id)
.ok_or_else(|| format!("{method}(): not an EmissiveComponent"))?;
emissive.intensity = intensity;
}
emit_intent(IntentValue::SetEmissiveIntensity {
component_ids: vec![id],
intensity,
});
Ok(Value::Null)
}
("HttpClient" | "HttpClientComponent" | "http_client", "get" | "delete") => {
let [url] = match args {
[url] => [value_as_string(url, method)?],
other => {
return Err(format!(
"{method}: expected one string url argument, got {:?}",
other
));
}
};
emit_intent(IntentValue::HttpClientRequest {
component_id: id,
method: method.to_ascii_uppercase(),
url,
headers: vec![],
body_text: None,
});
Ok(Value::Null)
}
("HttpClient" | "HttpClientComponent" | "http_client", "post" | "put") => {
let (url, body_text) = match args {
[url, body_text] => (
value_as_string(url, method)?,
value_as_string(body_text, method)?,
),
other => {
return Err(format!(
"{method}: expected url and body_text string arguments, got {:?}",
other
));
}
};
emit_intent(IntentValue::HttpClientRequest {
component_id: id,
method: method.to_ascii_uppercase(),
url,
headers: vec![],
body_text: Some(body_text),
});
Ok(Value::Null)
}
("HttpServer" | "HttpServerComponent" | "http_server", "reply_text") => {
let (request_id, status, body_text) = match args {
[request, status, body_text] => (
request_id_from_value(request)?,
value_as_u16(status, method)?,
value_as_string(body_text, method)?,
),
other => {
return Err(format!(
"reply_text: expected request, status, body_text arguments, got {:?}",
other
));
}
};
emit_intent(IntentValue::HttpServerReply {
component_id: id,
request_id,
status,
headers: vec![],
body_text,
});
Ok(Value::Null)
}
_ => Err(format!(
"unsupported live component method '{}.{}'",
component_type, method
)),
}
}
fn value_as_f32_array<const N: usize>(value: &Value) -> Result<[f32; N], String> {
let Value::Array(values) = value else {
return Err(format!("expected array, got {:?}", value));
};
if values.len() != N {
return Err(format!("expected array of len {}, got {}", N, values.len()));
}
let mut out = [0.0_f32; N];
for (i, value) in values.iter().enumerate() {
match value {
Value::Number(n) => out[i] = *n as f32,
other => return Err(format!("expected numeric array element, got {:?}", other)),
}
}
Ok(out)
}
fn value_as_string(value: &Value, method: &str) -> Result<String, String> {
match value {
Value::String(s) => Ok(s.clone()),
other => Err(format!("{method}: expected string, got {:?}", other)),
}
}
fn value_as_u16(value: &Value, method: &str) -> Result<u16, String> {
match value {
Value::Number(n) if *n >= 0.0 && *n <= u16::MAX as f64 => Ok(*n as u16),
other => Err(format!("{method}: expected status number, got {:?}", other)),
}
}
fn request_id_from_value(value: &Value) -> Result<u64, String> {
let Value::Map(map) = value else {
return Err(format!(
"reply_text: expected request object, got {:?}",
value
));
};
let Some(Value::Number(request_id)) = map.get("request_id") else {
return Err("reply_text: request missing numeric request_id".to_string());
};
if *request_id < 0.0 {
return Err("reply_text: request_id must be non-negative".to_string());
}
Ok(*request_id as u64)
}