Files
HelloVulkan/Engine/src/Scripting/ScriptRuntime.cpp
T

367 lines
15 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// ScriptRuntime.cpp:脚本对账与生命周期驱动(多脚本挂载)。
//
#include <Engine/Scripting/ScriptRuntime.h>
#include <Engine/Core/Application.h>
#include <Engine/Core/Log.h>
#include <Engine/ECS/Components.h>
#include <Engine/ECS/EntityHandle.h>
#include <Engine/Physics/PhysicsEvents.h>
#include <Engine/Physics/PhysicsWorld.h>
#include <Engine/Serialization/SceneSerializer.h>
#include <algorithm>
namespace Engine {
namespace Scripting {
// 宿主门面访问器:Application 完整类型只在脚本 cpp 里需要,
// 这里仅回引成员(避免引擎头循环依赖)
Application& ScriptBehavior::App() const {
return *m_App;
}
// ---- 场景实体 spawn/destroy 底座(声明在 Script.h,实现下沉
// 此处——Application 完整类型只在 cpp 可见)----
ECS::EntityHandle ScriptBehavior::Spawn(const std::string& name) {
return App().GetScene().CreateEntity(name);
}
void ScriptBehavior::Destroy(ECS::Entity e) {
App().GetScene().DestroyEntity(e);
}
// Prefab 实例化(声明在 Script.h,实现下沉此处——同 Spawn/
// Destroy 模式;序列化器与 Application 门面都在引擎内可达)
ECS::Entity ScriptBehavior::Instantiate(const std::string& prefabPath,
const glm::vec3& pos,
const glm::quat& rot) {
return Serialization::SceneSerializer::InstantiatePrefab(
App().GetScene(), App().GetAssets(), prefabPath, &pos, &rot);
}
// ---- 参数按名读写(ScriptBehavior 声明,经绑定表;写穿
// Item.params 由运行时 SetParam 完成)----
bool ScriptBehavior::WriteParam(const std::string& name,
const ParamValue& v) {
for (const auto& b : m_ParamBinds) {
if (b.name == name) {
return ParamBuilder::WriteBind(b, v);
}
}
return false;
}
bool ScriptBehavior::ReadParam(const std::string& name,
ParamValue& out) const {
for (const auto& b : m_ParamBinds) {
if (b.name == name) {
return ParamBuilder::ReadBind(b, out);
}
}
return false;
}
// ---- 碰撞事件派发(Update 开头、Sync 之前):PhysicsWorld 最近
// 一次步进收集的事件转发到涉事实例。时序保证:Step 先于
// Update(帧循环序)→ 此处读到的是本帧物理结果;回调里
// spawn/destroy 安全——destroy 入延迟队列(帧尾 flush),
// spawn 不改 m_Instances(下一帧 Sync 对账建实例)。禁用
// 实例不收(Unity enabled 语义对齐);Persisted 每步都来,
// 直接跳过(Enter/Exit 已够 gameplay 用)
void ScriptRuntime::DispatchCollisions() {
if (!m_App) {
return;
}
const std::vector<CollisionEvent>& events =
m_App->GetPhysics().GetEvents();
for (const auto& ev : events) {
if (ev.kind == CollisionEvent::Kind::Persisted) {
continue;
}
const bool enter = ev.kind == CollisionEvent::Kind::Added;
for (const u32 self : {ev.a, ev.b}) {
const u32 other = (self == ev.a) ? ev.b : ev.a;
const auto it = m_Instances.find(self);
if (it == m_Instances.end()) {
continue;
}
for (auto& inst : it->second) {
if (!inst.behavior || !inst.enabled) {
continue;
}
if (enter) {
inst.behavior->OnCollisionEnter(
other, ev.point, ev.normal);
} else {
inst.behavior->OnCollisionExit(other);
}
}
}
}
}
void ScriptRuntime::Update(f32 dt) {
DispatchCollisions();
Sync();
auto& reg = m_Scene.GetRegistry();
for (auto& [bits, list] : m_Instances) {
const auto e = static_cast<entt::entity>(bits);
const ECS::ScriptComponent* sc = reg.valid(e)
? reg.try_get<ECS::ScriptComponent>(e) : nullptr;
for (auto& inst : list) {
if (!inst.behavior) {
continue;
}
// 启停翻转检测:wantOn = 组件总开关 && 项开关(组件
// 关闭时零查找;项按名查找沿用每帧既有的那一次)。
// 与上帧 inst.enabled 比较出沿:true→OnEnable,
// false→OnDisable(Unity enabled 翻转语义)
bool wantOn = false;
if (sc && sc->active) {
const auto item = std::find_if(
sc->items.begin(), sc->items.end(),
[&](const ECS::ScriptComponent::Item& it) {
return it.name == inst.behavior->Name();
});
wantOn = item != sc->items.end() && item->active;
}
if (!wantOn) {
if (inst.enabled) {
inst.enabled = false;
inst.behavior->OnDisable();
}
continue; // 禁用 = 实例保留停 OnUpdate
}
if (!inst.created) {
inst.behavior->OnCreate();
inst.created = true;
}
if (!inst.enabled) {
inst.enabled = true;
inst.behavior->OnEnable();
}
if (!inst.started) {
inst.started = true;
inst.behavior->OnStart(); // 首次 OnUpdate 前一次
}
inst.behavior->OnUpdate(dt);
}
}
}
// ---- 终局(GameFlow →Stopped 广播):全实例 OnDisable →
// OnDestroy。与 Sync 的孤儿清理同序,范围 = 全部且不重建;
// Application 的监听先于编辑器回滚注册(组合根构造序),
// OnDestroy 里读到的仍是上局实体 ----
void ScriptRuntime::EndPlay() {
for (auto& [bits, list] : m_Instances) {
for (auto& inst : list) {
if (!inst.behavior) {
continue;
}
if (inst.enabled) {
inst.enabled = false;
inst.behavior->OnDisable();
}
inst.behavior->OnDestroy();
}
}
m_Instances.clear();
}
void ScriptRuntime::Sync() {
auto& reg = m_Scene.GetRegistry();
// 1. 孤儿/改名清理:实体或组件没了 → 整组 OnDestroy;
// 实例名不在 items 里(改名/移除)→ 单个 OnDestroy。
// 销毁次序 = OnDisable(守卫防双发)→ OnDestroy
for (auto it = m_Instances.begin(); it != m_Instances.end();) {
const auto e = static_cast<entt::entity>(it->first);
const ECS::ScriptComponent* sc =
reg.valid(e) ? reg.try_get<ECS::ScriptComponent>(e)
: nullptr;
if (!sc) {
for (auto& inst : it->second) {
if (!inst.behavior) {
continue;
}
if (inst.enabled) {
inst.enabled = false;
inst.behavior->OnDisable();
}
inst.behavior->OnDestroy();
}
it = m_Instances.erase(it);
continue;
}
auto& list = it->second;
for (auto li = list.begin(); li != list.end();) {
const bool keep = li->behavior &&
std::any_of(sc->items.begin(), sc->items.end(),
[&](const ECS::ScriptComponent::Item& item) {
return item.name == li->behavior->Name();
});
if (keep) {
++li;
continue;
}
if (li->behavior) {
if (li->enabled) {
li->enabled = false;
li->behavior->OnDisable();
}
li->behavior->OnDestroy();
}
li = list.erase(li);
}
if (list.empty()) {
it = m_Instances.erase(it);
} else {
++it;
}
}
// 2. 缺失实例化:逐项按名查注册表(未知名警告——场景含未注册
// 脚本时弱前向兼容,跳过不影响其余脚本)。灌值次序对齐
// Unity:声明(成员=声明默认)→ 组件存档覆盖 → 收值回调
// → OnCreate(= Awake,拿到最终参数值)
const auto view = reg.view<ECS::ScriptComponent>();
for (auto [e, sc] : view.each()) {
const u32 bits = static_cast<u32>(entt::to_integral(e));
auto& list = m_Instances[bits];
for (const auto& item : sc.items) {
if (item.name.empty()) {
continue;
}
const bool exists = std::any_of(
list.begin(), list.end(),
[&](const ScriptInstance& inst) {
return inst.behavior &&
inst.behavior->Name() == item.name;
});
if (exists) {
continue;
}
auto behavior = ScriptRegistry::Create(item.name);
if (!behavior) {
ENGINE_WARN("Script '{}' not registered (entity {})",
item.name, bits);
continue;
}
behavior->Bind(m_Scene, e, m_App);
ParamBuilder pb;
behavior->DeclareParams(pb);
pb.Apply(item.params);
behavior->SetParamBinds(pb.TakeBinds());
behavior->OnParamsApplied();
ScriptInstance inst;
inst.behavior = std::move(behavior);
inst.behavior->OnCreate(); // Unity Awake 语义:实例化即回调
inst.created = true;
list.push_back(std::move(inst));
}
}
}
// ---- 参数热刷新(编辑期一次性):重新声明 + 按存档灌值 ----
void ScriptRuntime::RefreshParams(u32 entityBits) {
const auto it = m_Instances.find(entityBits);
if (it == m_Instances.end()) {
return;
}
auto& reg = m_Scene.GetRegistry();
const auto e = static_cast<entt::entity>(entityBits);
const ECS::ScriptComponent* sc = reg.valid(e)
? reg.try_get<ECS::ScriptComponent>(e) : nullptr;
if (!sc) {
return;
}
for (auto& inst : it->second) {
if (!inst.behavior) {
continue;
}
ParamBuilder pb;
inst.behavior->DeclareParams(pb);
const auto item = std::find_if(
sc->items.begin(), sc->items.end(),
[&](const ECS::ScriptComponent::Item& it2) {
return it2.name == inst.behavior->Name();
});
if (item != sc->items.end()) {
pb.Apply(item->params);
}
inst.behavior->SetParamBinds(pb.TakeBinds());
inst.behavior->OnParamsApplied();
}
}
// ---- 跨脚本按名参数访问 ----
bool ScriptRuntime::SetParam(u32 entityBits,
const std::string& scriptName,
const std::string& param,
const ParamValue& value) {
auto& reg = m_Scene.GetRegistry();
const auto e = static_cast<entt::entity>(entityBits);
ECS::ScriptComponent* sc = reg.valid(e)
? reg.try_get<ECS::ScriptComponent>(e) : nullptr;
if (!sc) {
return false;
}
const auto item = std::find_if(
sc->items.begin(), sc->items.end(),
[&](const ECS::ScriptComponent::Item& it) {
return it.name == scriptName;
});
if (item == sc->items.end()) {
return false;
}
// 目标实例成员直写(实例未建/未声明则跳过,存档仍写穿)
const auto ii = m_Instances.find(entityBits);
if (ii != m_Instances.end()) {
for (auto& inst : ii->second) {
if (inst.behavior &&
inst.behavior->Name() == scriptName) {
inst.behavior->WriteParam(param, value);
break;
}
}
}
// 写穿 Item.params:首次写新名一次分配,此后零分配
const auto pit = std::find_if(
item->params.begin(), item->params.end(),
[&](const ScriptParam& p) { return p.name == param; });
if (pit != item->params.end()) {
pit->value = value;
} else {
item->params.push_back({param, value});
}
return true;
}
bool ScriptRuntime::GetParam(u32 entityBits,
const std::string& scriptName,
const std::string& param,
ParamValue& out) const {
const auto ii = m_Instances.find(entityBits);
if (ii == m_Instances.end()) {
return false;
}
for (const auto& inst : ii->second) {
if (inst.behavior && inst.behavior->Name() == scriptName) {
return inst.behavior->ReadParam(param, out);
}
}
return false;
}
} // namespace Scripting
} // namespace Engine