367 lines
15 KiB
C++
367 lines
15 KiB
C++
//
|
||
// 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
|