mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-09 04:49:39 -10:00
In the particle effect code there is a small hack required for getting some constant pointers in a vector. However, there is another mechanism where sources are not added to the sources vector if the sources are currently being iterated through. That is necessary to avoid accidentally invalidating the iterators. The issue here was that the vector resizing hack was not applied to the correct vector when we were currently iterating. This fixes that. This fixes #2928.
386 lines
9.5 KiB
C++
386 lines
9.5 KiB
C++
#include <algorithm>
|
|
#include <memory>
|
|
|
|
#include "particle/ParticleManager.h"
|
|
|
|
#include "particle/effects/SingleParticleEffect.h"
|
|
#include "particle/effects/CompositeEffect.h"
|
|
|
|
#include "particle/effects/ConeShape.h"
|
|
#include "particle/effects/SphereShape.h"
|
|
#include "particle/effects/GenericShapeEffect.h"
|
|
|
|
#include "bmpman/bmpman.h"
|
|
#include "globalincs/systemvars.h"
|
|
#include "tracing/tracing.h"
|
|
|
|
/**
|
|
* @defgroup particleSystems Particle System
|
|
*/
|
|
|
|
namespace {
|
|
using namespace particle;
|
|
|
|
const char* effectTypeNames[static_cast<int64_t>(EffectType::MAX)] = {
|
|
"Single",
|
|
"Composite",
|
|
"Cone",
|
|
"Sphere"
|
|
};
|
|
|
|
const char* getEffectTypeName(EffectType type) {
|
|
Assertion(static_cast<int64_t>(type) >= static_cast<int64_t>(EffectType::Single)
|
|
&& static_cast<int64_t>(type) < static_cast<int64_t>(EffectType::MAX),
|
|
"Invalid effect type specified!");
|
|
|
|
return effectTypeNames[static_cast<int64_t>(type)];
|
|
}
|
|
|
|
ParticleEffectPtr constructEffect(const SCP_string& name, EffectType type) {
|
|
using namespace effects;
|
|
// Use an unique_ptr to make sure memory is deallocated if an exception is thrown
|
|
std::unique_ptr<ParticleEffect> effect;
|
|
|
|
switch (type) {
|
|
case EffectType::Single: {
|
|
effect.reset(new SingleParticleEffect(name));
|
|
effect->parseValues(false);
|
|
break;
|
|
}
|
|
case EffectType::Composite: {
|
|
effect.reset(new CompositeEffect(name));
|
|
effect->parseValues(false);
|
|
break;
|
|
}
|
|
case EffectType::Cone: {
|
|
effect.reset(new GenericShapeEffect<ConeShape>(name));
|
|
effect->parseValues(false);
|
|
break;
|
|
}
|
|
case EffectType::Sphere: {
|
|
effect.reset(new GenericShapeEffect<SphereShape>(name));
|
|
effect->parseValues(false);
|
|
break;
|
|
}
|
|
default: {
|
|
Error(LOCATION, "Unimplemented effect type %d encountered! Get a coder!", static_cast<int>(type));
|
|
throw std::runtime_error("Unimplemented effect type encountered!");
|
|
}
|
|
}
|
|
|
|
return effect.release();
|
|
}
|
|
|
|
EffectType parseEffectType() {
|
|
required_string("$Type:");
|
|
|
|
SCP_string type;
|
|
stuff_string(type, F_NAME);
|
|
|
|
for (size_t i = 0; i < static_cast<size_t>(EffectType::MAX); ++i) {
|
|
if (!stricmp(type.c_str(), effectTypeNames[i])) {
|
|
return static_cast<EffectType>(i);
|
|
}
|
|
}
|
|
|
|
error_display(0, "Unknown effect type '%s'!", type.c_str());
|
|
return EffectType::Invalid;
|
|
}
|
|
|
|
void parseCallback(const char* fileName) {
|
|
using namespace particle;
|
|
|
|
try {
|
|
read_file_text(fileName, CF_TYPE_TABLES);
|
|
|
|
reset_parse();
|
|
|
|
required_string("#Particle Effects");
|
|
|
|
while (optional_string("$Effect:")) {
|
|
SCP_string name;
|
|
stuff_string(name, F_NAME);
|
|
|
|
auto type = parseEffectType();
|
|
|
|
ParticleManager::get()->addEffect(constructEffect(name, type));
|
|
}
|
|
|
|
required_string("#End");
|
|
}
|
|
catch (const parse::ParseException& e)
|
|
{
|
|
mprintf(("TABLES: Unable to parse '%s'! Error message = %s.\n", fileName, e.what()));
|
|
return;
|
|
}
|
|
}
|
|
|
|
void parseConfigFiles() {
|
|
parse_modular_table("*-part.tbm", parseCallback);
|
|
}
|
|
}
|
|
|
|
namespace particle {
|
|
std::unique_ptr<ParticleManager> ParticleManager::m_manager = nullptr;
|
|
|
|
void ParticleManager::init() {
|
|
Assertion(m_manager == nullptr, "ParticleManager was not properly shut down!");
|
|
|
|
m_manager.reset(new ParticleManager());
|
|
|
|
parseConfigFiles();
|
|
}
|
|
|
|
void ParticleManager::shutdown() {
|
|
Assertion(m_manager != nullptr, "ParticleManager was not properly inited!");
|
|
|
|
m_manager = nullptr;
|
|
}
|
|
|
|
ParticleSource* ParticleManager::createSource() {
|
|
ParticleSource* source;
|
|
|
|
// If we are currently in the onFrame function, adding stuff to the vector would invalidate the iterator currently in use
|
|
if (m_processingSources) {
|
|
m_deferredSourceAdding.emplace_back();
|
|
|
|
source = &m_deferredSourceAdding.back();
|
|
}
|
|
else {
|
|
m_sources.emplace_back();
|
|
|
|
source = &m_sources.back();
|
|
}
|
|
|
|
return source;
|
|
}
|
|
|
|
ParticleEffectHandle ParticleManager::getEffectByName(const SCP_string& name)
|
|
{
|
|
if (name.empty()) {
|
|
// Don't allow empty names, it's a special case for effects that should not be referenced.
|
|
return ParticleEffectHandle::invalid();
|
|
}
|
|
|
|
auto foundIterator = find_if(m_effects.begin(), m_effects.end(),
|
|
[&name](const std::shared_ptr<ParticleEffect>& ptr) {
|
|
return !stricmp(ptr->getName().c_str(), name.c_str());
|
|
});
|
|
|
|
if (foundIterator == m_effects.end()) {
|
|
return ParticleEffectHandle::invalid();
|
|
}
|
|
|
|
return ParticleEffectHandle(distance(m_effects.begin(), foundIterator));
|
|
}
|
|
|
|
void ParticleManager::doFrame(float) {
|
|
if (Is_standalone) {
|
|
// Don't process sources for standalone server
|
|
m_sources.clear(); // Always clear the vector to free memory
|
|
}
|
|
else {
|
|
TRACE_SCOPE(tracing::ProcessParticleEffects);
|
|
|
|
m_processingSources = true;
|
|
|
|
for (auto source = std::begin(m_sources); source != std::end(m_sources);) {
|
|
if (!source->isValid() || !source->process()) {
|
|
// if we're sitting on the very last source, popping-back will invalidate the iterator!
|
|
if (std::next(source) == m_sources.end()) {
|
|
m_sources.pop_back();
|
|
break;
|
|
}
|
|
|
|
*source = std::move(m_sources.back());
|
|
m_sources.pop_back();
|
|
continue;
|
|
}
|
|
|
|
// source is only incremented here as elements would be skipped in
|
|
// the case that a source needs to be removed
|
|
++source;
|
|
}
|
|
|
|
m_processingSources = false;
|
|
|
|
for (auto& source : m_deferredSourceAdding) {
|
|
m_sources.push_back(source);
|
|
}
|
|
m_deferredSourceAdding.clear();
|
|
}
|
|
}
|
|
|
|
ParticleEffectHandle ParticleManager::addEffect(ParticleEffectPtr effect)
|
|
{
|
|
Assertion(effect, "Invalid effect pointer passed!");
|
|
|
|
#ifndef NDEBUG
|
|
if (!effect->getName().empty()) {
|
|
// This check is a bit expensive and will only be used in debug
|
|
auto index = getEffectByName(effect->getName());
|
|
|
|
if (index.isValid()) {
|
|
Warning(LOCATION, "Effect with name '%s' already exists!", effect->getName().c_str());
|
|
return index;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
m_effects.push_back(std::shared_ptr<ParticleEffect>(effect));
|
|
|
|
return ParticleEffectHandle(static_cast<ParticleEffectHandle::impl_type>(m_effects.size() - 1));
|
|
}
|
|
|
|
void ParticleManager::pageIn() {
|
|
for (auto& effect : m_effects) {
|
|
effect->pageIn();
|
|
}
|
|
}
|
|
|
|
ParticleSourceWrapper ParticleManager::createSource(ParticleEffectHandle index)
|
|
{
|
|
ParticleEffectPtr eff = this->getEffect(index);
|
|
ParticleSourceWrapper wrapper;
|
|
|
|
if (eff->getType() == EffectType::Composite) {
|
|
SCP_vector<ParticleSource*> sources;
|
|
auto composite = static_cast<effects::CompositeEffect*>(eff);
|
|
auto& childEffects = composite->getEffects();
|
|
|
|
// UGH, HACK! To implement the source wrapper we need constant pointers to all sources.
|
|
// To ensure this we reserve the number of sources we will need (current sources + sources being created)
|
|
if (m_processingSources) {
|
|
// If we are already in our onFrame, we need to apply the hack to the right vector though
|
|
m_deferredSourceAdding.reserve(m_sources.size() + childEffects.size());
|
|
} else {
|
|
m_sources.reserve(m_sources.size() + childEffects.size());
|
|
}
|
|
|
|
for (auto& effect : childEffects) {
|
|
ParticleSource* source = createSource();
|
|
source->setEffect(effect);
|
|
effect->initializeSource(*source);
|
|
|
|
sources.push_back(source);
|
|
}
|
|
|
|
wrapper = ParticleSourceWrapper(std::move(sources));
|
|
}
|
|
else {
|
|
ParticleSource* source = createSource();
|
|
source->setEffect(eff);
|
|
eff->initializeSource(*source);
|
|
|
|
wrapper = ParticleSourceWrapper(source);
|
|
}
|
|
|
|
wrapper.setCreationTimestamp(timestamp());
|
|
|
|
return wrapper;
|
|
}
|
|
|
|
void ParticleManager::clearSources() {
|
|
m_sources.clear();
|
|
}
|
|
|
|
namespace util {
|
|
ParticleEffectHandle parseEffect(const SCP_string& objectName)
|
|
{
|
|
SCP_string name;
|
|
stuff_string(name, F_NAME);
|
|
|
|
auto idx = ParticleManager::get()->getEffectByName(name);
|
|
|
|
if (!idx.isValid()) {
|
|
if (objectName.empty()) {
|
|
error_display(0, "Unknown particle effect name '%s' encountered!", name.c_str());
|
|
} else {
|
|
error_display(0, "Unknown particle effect name '%s' encountered while parsing '%s'!", name.c_str(),
|
|
objectName.c_str());
|
|
}
|
|
}
|
|
|
|
return idx;
|
|
}
|
|
}
|
|
|
|
namespace internal {
|
|
ParticleEffectHandle parseEffectElement(EffectType forcedType, const SCP_string& name)
|
|
{
|
|
if (!optional_string("$New Effect")) {
|
|
SCP_string newName;
|
|
stuff_string(newName, F_NAME);
|
|
|
|
auto index = ParticleManager::get()->getEffectByName(newName);
|
|
|
|
if (!index.isValid()) {
|
|
error_display(0, "Unknown particle effect name '%s' encountered!", newName.c_str());
|
|
}
|
|
if (forcedType != EffectType::Invalid) {
|
|
// Validate the effect type
|
|
auto effect = ParticleManager::get()->getEffect(index);
|
|
|
|
if (effect->getType() != forcedType) {
|
|
error_display(0, "Particle effect '%s' has the wrong effect type! Expected %s but was %s!",
|
|
newName.c_str(), getEffectTypeName(forcedType), getEffectTypeName(effect->getType()));
|
|
}
|
|
}
|
|
|
|
return index;
|
|
}
|
|
|
|
if (forcedType == EffectType::Invalid) {
|
|
forcedType = parseEffectType();
|
|
}
|
|
|
|
auto effect = constructEffect(name, forcedType);
|
|
|
|
return ParticleManager::get()->addEffect(effect);
|
|
}
|
|
|
|
bool required_string_if_new(const char* token, bool no_create) {
|
|
if (no_create) {
|
|
return optional_string(token) == 1;
|
|
}
|
|
|
|
required_string(token);
|
|
return true;
|
|
}
|
|
|
|
SCP_vector<int> parseAnimationList(bool critical) {
|
|
|
|
SCP_vector<SCP_string> bitmap_strings;
|
|
|
|
// check to see if we are parsing a single value or list
|
|
ignore_white_space();
|
|
if (*Mp == '(') {
|
|
// list of names case
|
|
stuff_string_list(bitmap_strings);
|
|
}
|
|
else {
|
|
// single name case
|
|
SCP_string name;
|
|
stuff_string(name, F_FILESPEC);
|
|
bitmap_strings.push_back(name);
|
|
}
|
|
|
|
SCP_vector<int> handles;
|
|
|
|
for (auto const &name: bitmap_strings) {
|
|
auto handle = bm_load_animation(name.c_str());
|
|
if (handle >= 0) {
|
|
handles.push_back(handle);
|
|
}
|
|
else {
|
|
int level = critical ? 1 : 0;
|
|
error_display(level, "Failed to load effect %s!", name.c_str());
|
|
}
|
|
}
|
|
|
|
return handles;
|
|
}
|
|
|
|
}
|
|
}
|