<commit_before>/* FiberTaskingLib - A tasking library that uses fibers for efficient task switching
 *
 * This library was created as a proof of concept of the ideas presented by
 * Christian Gyrling in his 2015 GDC Talk 'Parallelizing the Naughty Dog Engine Using Fibers'
 *
 * http://gdcvault.com/play/1022186/Parallelizing-the-Naughty-Dog-Engine
 *
 * FiberTaskingLib is the legal property of Adrian Astley
 * Copyright Adrian Astley 2015
 */

#include "fiber_tasking_lib/task_scheduler.h"

#include "fiber_tasking_lib/global_args.h"

#include <process.h>

namespace FiberTaskingLib {

__declspec(thread) uint tls_threadId;

__declspec(thread) void *tls_fiberToSwitchTo;
__declspec(thread) void *tls_currentFiber;
__declspec(thread) AtomicCounter *tls_waitingCounter;
__declspec(thread) int tls_waitingValue;

struct ThreadStartArgs {
	GlobalArgs *globalArgs;
	uint threadId;
};

uint TaskScheduler::ThreadStart(void *arg) {
	ThreadStartArgs *threadArgs = (ThreadStartArgs *)arg;
	tls_threadId = threadArgs->threadId;
	GlobalArgs *globalArgs = threadArgs->globalArgs;

	// Clean up
	delete threadArgs;

	ConvertThreadToFiberEx(nullptr, FIBER_FLAG_FLOAT_SWITCH);
	FiberStart(globalArgs);

	ConvertFiberToThread();
	return 1;
}

void TaskScheduler::FiberStart(void *arg) {
	GlobalArgs *globalArgs = (GlobalArgs *)arg;
	TaskScheduler *taskScheduler = &globalArgs->TaskScheduler;

	while (!taskScheduler->m_quit.load()) {
		// Check if any of the waiting tasks are ready
		WaitingTask waitingTask;
		bool waitingTaskReady = false;

		EnterCriticalSection(&taskScheduler->m_waitingTaskLock);
		auto iter = taskScheduler->m_waitingTasks.begin();
		for ( ; iter != taskScheduler->m_waitingTasks.end(); ++iter) {
			if (iter->Counter->load() == iter->Value) {
				waitingTaskReady = true;
				break;
			}
		}
		if (waitingTaskReady) {
			waitingTask = *iter;
			taskScheduler->m_waitingTasks.erase(iter);
		}
		LeaveCriticalSection(&taskScheduler->m_waitingTaskLock);

		if (waitingTaskReady) {
			taskScheduler->SwitchFibers(waitingTask.Fiber);
		}

		TaskBundle nextTask;
		if (!taskScheduler->GetNextTask(&nextTask)) {
			Sleep(1);
		} else {
			nextTask.Task.Function(&globalArgs->TaskScheduler, nextTask.Task.ArgData);
			nextTask.Counter->fetch_sub(1);
		}
	}
}

void __stdcall TaskScheduler::FiberSwitchStart(void *arg) {
	TaskScheduler *taskScheduler = (TaskScheduler *)arg;

	while (true) {
		taskScheduler->m_fiberPool.enqueue(tls_currentFiber);
		SwitchToFiber(tls_fiberToSwitchTo);
	}
}

void __stdcall TaskScheduler::CounterWaitStart(void *arg) {
	TaskScheduler *taskScheduler = (TaskScheduler *)arg;

	while (true) {
		EnterCriticalSection(&taskScheduler->m_waitingTaskLock);
		taskScheduler->m_waitingTasks.emplace_back(tls_currentFiber, tls_waitingCounter, tls_waitingValue);
		LeaveCriticalSection(&taskScheduler->m_waitingTaskLock);

		SwitchToFiber(tls_fiberToSwitchTo);
	}
}

TaskScheduler::TaskScheduler()
		: m_numThreads(0),
		  m_threads(nullptr) {
	m_quit.store(false, std::memory_order_relaxed);
	InitializeCriticalSection(&m_waitingTaskLock);
}

TaskScheduler::~TaskScheduler() {
	delete[] m_threads;

	void *fiber;
	while (m_fiberPool.try_dequeue(fiber)) {
		DeleteFiber(fiber);
	}

	DeleteCriticalSection(&m_waitingTaskLock);
}

void TaskScheduler::Initialize(GlobalArgs *globalArgs) {
	for (uint i = 0; i < FIBER_POOL_SIZE; ++i) {
		m_fiberPool.enqueue(CreateFiberEx(524288, 0, FIBER_FLAG_FLOAT_SWITCH, FiberStart, globalArgs));
	}

	SYSTEM_INFO sysinfo;
	GetSystemInfo(&sysinfo);

	// Create an additional thread for each logical processor
	m_numThreads = sysinfo.dwNumberOfProcessors;
	m_threads = new HANDLE[m_numThreads];
	m_fiberSwitchingFibers = new void *[m_numThreads];
	m_counterWaitingFibers = new void *[m_numThreads];

	// Create a switching fiber for each thread
	for (uint i = 0; i < m_numThreads; ++i) {
		m_fiberSwitchingFibers[i] = CreateFiberEx(32768, 0, FIBER_FLAG_FLOAT_SWITCH, FiberSwitchStart, &globalArgs->TaskScheduler);
		m_counterWaitingFibers[i] = CreateFiberEx(32768, 0, FIBER_FLAG_FLOAT_SWITCH, CounterWaitStart, &globalArgs->TaskScheduler);
	}

	// Set the affinity for the current thread and convert it to a fiber
	SetThreadAffinityMask(GetCurrentThread(), 1);
	ConvertThreadToFiberEx(nullptr, FIBER_FLAG_FLOAT_SWITCH);
	m_threads[0] = GetCurrentThread();
	tls_threadId = 0;

	// Create the remaining threads
	for (DWORD i = 1; i < m_numThreads; ++i) {
		ThreadStartArgs *threadArgs = new ThreadStartArgs();
		threadArgs->globalArgs = globalArgs;
		threadArgs->threadId = i;

		HANDLE threadHandle = (HANDLE)_beginthreadex(nullptr, 524288, ThreadStart, threadArgs, CREATE_SUSPENDED, nullptr);
		m_threads[i] = threadHandle;

		DWORD_PTR mask = 1 << i;
		SetThreadAffinityMask(threadHandle, mask);
		ResumeThread(threadHandle);
	}
}

std::shared_ptr<AtomicCounter> TaskScheduler::AddTask(Task task) {
	std::shared_ptr<AtomicCounter> counter(new AtomicCounter());
	counter->store(1, std::memory_order_relaxed);

	TaskBundle bundle = {task, counter};
	m_taskQueue.enqueue(bundle);

	return counter;
}

std::shared_ptr<AtomicCounter> TaskScheduler::AddTasks(uint numTasks, Task *tasks) {
	std::shared_ptr<AtomicCounter> counter(new AtomicCounter());
	counter->store(numTasks, std::memory_order_relaxed);

	for (uint i = 0; i < numTasks; ++i) {
		TaskBundle bundle = {tasks[i], counter};
		m_taskQueue.enqueue(bundle);
	}

	return counter;
}

bool TaskScheduler::GetNextTask(TaskBundle *nextTask) {
	bool success = m_taskQueue.try_dequeue(*nextTask);

	return success;
}

void TaskScheduler::SwitchFibers(void *fiberToSwitchTo) {
	tls_currentFiber = GetCurrentFiber();
	tls_fiberToSwitchTo = fiberToSwitchTo;

	SwitchToFiber(m_fiberSwitchingFibers[tls_threadId]);
}

void TaskScheduler::WaitForCounter(std::shared_ptr<AtomicCounter> &counter, int value) {
	if (counter->load() == value) {
		return;
	}

	// Switch to a new Fiber
	m_fiberPool.wait_dequeue(tls_fiberToSwitchTo);

	tls_currentFiber = GetCurrentFiber();
	tls_waitingCounter = counter.get();
	tls_waitingValue = value;

	SwitchToFiber(m_counterWaitingFibers[tls_threadId]);
}

void TaskScheduler::Quit() {
	m_quit.store(true, std::memory_order_relaxed);
	ConvertFiberToThread();

	std::vector<HANDLE> workerThreads;
	for (uint i = 0; i < m_numThreads; ++i) {
		if (m_threads != GetCurrentThread()) {
			workerThreads.push_back(m_threads[i]);
		}
	}

	DWORD result = WaitForMultipleObjects(workerThreads.size(), &workerThreads[0], true, INFINITE);

	for (auto &workerThread : workerThreads) {
		CloseHandle(workerThread);
	}
}

} // End of namespace FiberTaskingLib
<commit_msg>FIBER_TASKING_LIB: Use the default sequential memory ordering for all atomics<commit_after>/* FiberTaskingLib - A tasking library that uses fibers for efficient task switching
 *
 * This library was created as a proof of concept of the ideas presented by
 * Christian Gyrling in his 2015 GDC Talk 'Parallelizing the Naughty Dog Engine Using Fibers'
 *
 * http://gdcvault.com/play/1022186/Parallelizing-the-Naughty-Dog-Engine
 *
 * FiberTaskingLib is the legal property of Adrian Astley
 * Copyright Adrian Astley 2015
 */

#include "fiber_tasking_lib/task_scheduler.h"

#include "fiber_tasking_lib/global_args.h"

#include <process.h>

namespace FiberTaskingLib {

__declspec(thread) uint tls_threadId;

__declspec(thread) void *tls_fiberToSwitchTo;
__declspec(thread) void *tls_currentFiber;
__declspec(thread) AtomicCounter *tls_waitingCounter;
__declspec(thread) int tls_waitingValue;

struct ThreadStartArgs {
	GlobalArgs *globalArgs;
	uint threadId;
};

uint TaskScheduler::ThreadStart(void *arg) {
	ThreadStartArgs *threadArgs = (ThreadStartArgs *)arg;
	tls_threadId = threadArgs->threadId;
	GlobalArgs *globalArgs = threadArgs->globalArgs;

	// Clean up
	delete threadArgs;

	ConvertThreadToFiberEx(nullptr, FIBER_FLAG_FLOAT_SWITCH);
	FiberStart(globalArgs);

	ConvertFiberToThread();
	return 1;
}

void TaskScheduler::FiberStart(void *arg) {
	GlobalArgs *globalArgs = (GlobalArgs *)arg;
	TaskScheduler *taskScheduler = &globalArgs->TaskScheduler;

	while (!taskScheduler->m_quit.load()) {
		// Check if any of the waiting tasks are ready
		WaitingTask waitingTask;
		bool waitingTaskReady = false;

		EnterCriticalSection(&taskScheduler->m_waitingTaskLock);
		auto iter = taskScheduler->m_waitingTasks.begin();
		for ( ; iter != taskScheduler->m_waitingTasks.end(); ++iter) {
			if (iter->Counter->load() == iter->Value) {
				waitingTaskReady = true;
				break;
			}
		}
		if (waitingTaskReady) {
			waitingTask = *iter;
			taskScheduler->m_waitingTasks.erase(iter);
		}
		LeaveCriticalSection(&taskScheduler->m_waitingTaskLock);

		if (waitingTaskReady) {
			taskScheduler->SwitchFibers(waitingTask.Fiber);
		}

		TaskBundle nextTask;
		if (!taskScheduler->GetNextTask(&nextTask)) {
			Sleep(1);
		} else {
			nextTask.Task.Function(&globalArgs->TaskScheduler, nextTask.Task.ArgData);
			nextTask.Counter->fetch_sub(1);
		}
	}
}

void __stdcall TaskScheduler::FiberSwitchStart(void *arg) {
	TaskScheduler *taskScheduler = (TaskScheduler *)arg;

	while (true) {
		taskScheduler->m_fiberPool.enqueue(tls_currentFiber);
		SwitchToFiber(tls_fiberToSwitchTo);
	}
}

void __stdcall TaskScheduler::CounterWaitStart(void *arg) {
	TaskScheduler *taskScheduler = (TaskScheduler *)arg;

	while (true) {
		EnterCriticalSection(&taskScheduler->m_waitingTaskLock);
		taskScheduler->m_waitingTasks.emplace_back(tls_currentFiber, tls_waitingCounter, tls_waitingValue);
		LeaveCriticalSection(&taskScheduler->m_waitingTaskLock);

		SwitchToFiber(tls_fiberToSwitchTo);
	}
}

TaskScheduler::TaskScheduler()
		: m_numThreads(0),
		  m_threads(nullptr) {
	InitializeCriticalSection(&m_waitingTaskLock);
	m_quit.store(false);
}

TaskScheduler::~TaskScheduler() {
	delete[] m_threads;

	void *fiber;
	while (m_fiberPool.try_dequeue(fiber)) {
		DeleteFiber(fiber);
	}

	DeleteCriticalSection(&m_waitingTaskLock);
}

void TaskScheduler::Initialize(GlobalArgs *globalArgs) {
	for (uint i = 0; i < FIBER_POOL_SIZE; ++i) {
		m_fiberPool.enqueue(CreateFiberEx(524288, 0, FIBER_FLAG_FLOAT_SWITCH, FiberStart, globalArgs));
	}

	SYSTEM_INFO sysinfo;
	GetSystemInfo(&sysinfo);

	// Create an additional thread for each logical processor
	m_numThreads = sysinfo.dwNumberOfProcessors;
	m_threads = new HANDLE[m_numThreads];
	m_fiberSwitchingFibers = new void *[m_numThreads];
	m_counterWaitingFibers = new void *[m_numThreads];

	// Create a switching fiber for each thread
	for (uint i = 0; i < m_numThreads; ++i) {
		m_fiberSwitchingFibers[i] = CreateFiberEx(32768, 0, FIBER_FLAG_FLOAT_SWITCH, FiberSwitchStart, &globalArgs->TaskScheduler);
		m_counterWaitingFibers[i] = CreateFiberEx(32768, 0, FIBER_FLAG_FLOAT_SWITCH, CounterWaitStart, &globalArgs->TaskScheduler);
	}

	// Set the affinity for the current thread and convert it to a fiber
	SetThreadAffinityMask(GetCurrentThread(), 1);
	ConvertThreadToFiberEx(nullptr, FIBER_FLAG_FLOAT_SWITCH);
	m_threads[0] = GetCurrentThread();
	tls_threadId = 0;

	// Create the remaining threads
	for (DWORD i = 1; i < m_numThreads; ++i) {
		ThreadStartArgs *threadArgs = new ThreadStartArgs();
		threadArgs->globalArgs = globalArgs;
		threadArgs->threadId = i;

		HANDLE threadHandle = (HANDLE)_beginthreadex(nullptr, 524288, ThreadStart, threadArgs, CREATE_SUSPENDED, nullptr);
		m_threads[i] = threadHandle;

		DWORD_PTR mask = 1 << i;
		SetThreadAffinityMask(threadHandle, mask);
		ResumeThread(threadHandle);
	}
}

std::shared_ptr<AtomicCounter> TaskScheduler::AddTask(Task task) {
	std::shared_ptr<AtomicCounter> counter(new AtomicCounter());
	counter->store(1);

	TaskBundle bundle = {task, counter};
	m_taskQueue.enqueue(bundle);

	return counter;
}

std::shared_ptr<AtomicCounter> TaskScheduler::AddTasks(uint numTasks, Task *tasks) {
	std::shared_ptr<AtomicCounter> counter(new AtomicCounter());
	counter->store(numTasks);

	for (uint i = 0; i < numTasks; ++i) {
		TaskBundle bundle = {tasks[i], counter};
		m_taskQueue.enqueue(bundle);
	}

	return counter;
}

bool TaskScheduler::GetNextTask(TaskBundle *nextTask) {
	bool success = m_taskQueue.try_dequeue(*nextTask);

	return success;
}

void TaskScheduler::SwitchFibers(void *fiberToSwitchTo) {
	tls_currentFiber = GetCurrentFiber();
	tls_fiberToSwitchTo = fiberToSwitchTo;

	SwitchToFiber(m_fiberSwitchingFibers[tls_threadId]);
}

void TaskScheduler::WaitForCounter(std::shared_ptr<AtomicCounter> &counter, int value) {
	if (counter->load() == value) {
		return;
	}

	// Switch to a new Fiber
	m_fiberPool.wait_dequeue(tls_fiberToSwitchTo);

	tls_currentFiber = GetCurrentFiber();
	tls_waitingCounter = counter.get();
	tls_waitingValue = value;

	SwitchToFiber(m_counterWaitingFibers[tls_threadId]);
}

void TaskScheduler::Quit() {
	m_quit.store(true);
	ConvertFiberToThread();

	std::vector<HANDLE> workerThreads;
	for (uint i = 0; i < m_numThreads; ++i) {
		if (m_threads != GetCurrentThread()) {
			workerThreads.push_back(m_threads[i]);
		}
	}

	DWORD result = WaitForMultipleObjects((DWORD)workerThreads.size(), &workerThreads[0], true, INFINITE);

	for (auto &workerThread : workerThreads) {
		CloseHandle(workerThread);
	}
}

} // End of namespace FiberTaskingLib
<|endoftext|>


<commit_before>#include "labels.h"

#include "tangram.h"
#include "platform.h"
#include "gl/shaderProgram.h"
#include "gl/primitives.h"
#include "view/view.h"
#include "style/material.h"
#include "style/style.h"
#include "tile/tile.h"
#include "tile/tileCache.h"

#include "glm/glm.hpp"
#include "glm/gtc/matrix_transform.hpp"
#include "glm/gtx/rotate_vector.hpp"
#include "glm/gtx/norm.hpp"

namespace Tangram {

Labels::Labels()
    : m_needUpdate(false),
      m_lastZoom(0.0f)
{}

Labels::~Labels() {}

int Labels::LODDiscardFunc(float _maxZoom, float _zoom) {
    return (int) MIN(floor(((log(-_zoom + (_maxZoom + 2)) / log(_maxZoom + 2) * (_maxZoom )) * 0.5)), MAX_LOD);
}

void Labels::update(const View& _view, float _dt, const std::vector<std::unique_ptr<Style>>& _styles,
                    const std::vector<std::shared_ptr<Tile>>& _tiles, std::unique_ptr<TileCache>& _cache) {

    m_needUpdate = false;

    // float zoom = _view.getZoom();
    // int lodDiscard = LODDiscardFunc(View::s_maxZoom, zoom);
    // LOG("loddiscard %f %d", zoom, lodDiscard);

    std::set<std::pair<Label*, Label*>> occlusions;

    // Could clear this at end of function unless debug draw is active
    m_labels.clear();
    m_aabbs.clear();
    m_visibleTextSet.clear();
    m_collideComponents.clear();
    m_repeatGroups.clear();

    glm::vec2 screenSize = glm::vec2(_view.getWidth(), _view.getHeight());

    float currentZoom = _view.getZoom();
    float dz = currentZoom - std::floor(currentZoom);

    //// Collect labels from visible tiles

    for (const auto& tile : _tiles) {

        // discard based on level of detail
        // if ((zoom - tile->getID().z) > lodDiscard) {
        //     LOG("discard %d %d %d", tile->getID().z, tile->getID().x, tile->getID().y);
        //     continue;
        // }

        bool proxyTile = tile->isProxy();

        glm::mat4 mvp = _view.getViewProjectionMatrix() * tile->getModelMatrix();

        for (const auto& style : _styles) {
            const auto& mesh = tile->getMesh(*style);
            if (!mesh) { continue; }

            const LabelMesh* labelMesh = dynamic_cast<const LabelMesh*>(mesh.get());
            if (!labelMesh) { continue; }

            for (auto& label : labelMesh->getLabels()) {
                m_needUpdate |= label->update(mvp, screenSize, _dt, dz);

                label->setProxy(proxyTile);

                if (label->canOcclude()) {
                    m_aabbs.push_back(label->aabb());
                    m_aabbs.back().m_userData = (void*)label.get();
                }
                m_labels.push_back(label.get());
            }
        }
    }

    //// Manage occlusions

    // broad phase
    m_isect2d.resize({_view.getWidth() / 256, _view.getHeight() / 256}, {_view.getWidth(), _view.getHeight()});
    m_isect2d.intersect(m_aabbs);

    // narrow phase
    for (auto pair : m_isect2d.pairs) {
        const auto& aabb1 = m_aabbs[pair.first];
        const auto& aabb2 = m_aabbs[pair.second];

        auto l1 = static_cast<Label*>(aabb1.m_userData);
        auto l2 = static_cast<Label*>(aabb2.m_userData);

        if (intersect(l1->obb(), l2->obb())) { occlusions.insert({l1, l2}); }
    }

    for (auto& pair : occlusions) {
        if (!pair.first->occludedLastFrame() || !pair.second->occludedLastFrame()) {
            // check first is the label belongs to a proxy tile
            if (pair.first->isProxy() && !pair.second->isProxy()) {
                pair.first->setOcclusion(true);
            } else if (!pair.first->isProxy() && pair.second->isProxy()) {
                pair.second->setOcclusion(true);
            } else {
                // lower numeric priority means higher priority
                if (pair.first->options().priority < pair.second->options().priority) {
                    pair.second->setOcclusion(true);
                } else {
                    pair.first->setOcclusion(true);
                }
            }
        }
    }

    //// Mark labels to skip transitions

    if ((int) m_lastZoom != (int) currentZoom) {
        for (const auto& t0 : _tiles) {
            TileID tileID = t0->getID();
            std::vector<std::shared_ptr<Tile>> tiles;

            if (m_lastZoom < currentZoom) {
                // zooming in, add the one cached parent tile
                tiles.push_back(_cache->contains(t0->sourceID(), tileID.getParent()));
            } else {
                // zooming out, add the 4 cached children tiles
                tiles.push_back(_cache->contains(t0->sourceID(), tileID.getChild(0)));
                tiles.push_back(_cache->contains(t0->sourceID(), tileID.getChild(1)));
                tiles.push_back(_cache->contains(t0->sourceID(), tileID.getChild(2)));
                tiles.push_back(_cache->contains(t0->sourceID(), tileID.getChild(3)));
            }

            for (const auto& t1 : tiles) {
                if (!t1) { continue; }
                for (const auto& style : _styles) {
                    const auto& m0 = t0->getMesh(*style);
                    if (!m0) { continue; }
                    const LabelMesh* mesh0 = dynamic_cast<const LabelMesh*>(m0.get());
                    if (!mesh0) { continue; }
                    const auto& m1 = t1->getMesh(*style);
                    if (!m1) { continue; }
                    const LabelMesh* mesh1 = static_cast<const LabelMesh*>(m1.get());

                    for (auto& l0 : mesh0->getLabels()) {
                        if (!l0->canOcclude()) { continue; }

                        for (auto& l1 : mesh1->getLabels()) {
                            if (!l1 || !l1->canOcclude() || l0->hash() != l1->hash()) {
                                continue;
                            }
                            float d2 = glm::distance2(l0->transform().state.screenPos,
                                    l1->transform().state.screenPos);

                            // The new label lies within the circle defined by the bbox of l0
                            if (sqrt(d2) < std::max(l0->dimension().x, l0->dimension().y)) {
                                l0->skipTransitions();
                            }
                        }
                    }
                }
            }
        }
    }

    //// Update label meshes

    for (auto label : m_labels) {
        label->occlusionSolved();
        label->pushTransform();

        if (label->canOcclude()) {
            if (!label->visibleState()) { continue; }
            TextLabel* textLabel = dynamic_cast<TextLabel*>(label);
            if (!textLabel) { continue; }
            m_visibleTextSet.push_back(textLabel);
        }
    }

    /// Apply repeat groups

    // TODO: optimize
    // Ensure the labels are always treated in the same order
    std::sort(m_visibleTextSet.begin(), m_visibleTextSet.end(), [](TextLabel* _a, TextLabel* _b) {
        std::size_t seed0 = 0;
        std::size_t seed1 = 0;
        hash_combine(seed0, _a->hash());
        hash_combine(seed0, _a->transform().modelPosition1.x);
        hash_combine(seed0, _a->transform().modelPosition1.y);
        hash_combine(seed1, _b->hash());
        hash_combine(seed1, _b->transform().modelPosition1.x);
        hash_combine(seed1, _b->transform().modelPosition1.y);
        return seed0 < seed1;
    });

    auto textLabelIt = m_visibleTextSet.begin();
    while (textLabelIt != m_visibleTextSet.end()) {
        auto textLabel = *textLabelIt;
        CollideComponent component;
        component.position = textLabel->transform().state.screenPos;
        component.userData = (void*)textLabel;

        std::size_t seed = 0;
        hash_combine(seed, textLabel->text);

        component.group = seed;
        component.mask = seed;

        auto it = m_repeatGroups.find(seed);
        if (it != m_repeatGroups.end()) {
            std::vector<CollideComponent>& group = m_repeatGroups[seed];

            if (std::find(group.begin(), group.end(), component) == group.end()) {
                std::vector<CollideComponent> newGroup(group);
                newGroup.push_back(component);

                isect2d::CollideOption options;
                options.thresholdDistance = 300.0f;
                options.rule = isect2d::CollideRuleOption::UNIDIRECTIONNAL;

                auto collisionMaskPairs = isect2d::intersect(newGroup, options);

                if (collisionMaskPairs.size() == 0) {
                    group.push_back(component);
                    textLabelIt++;
                } else {
                    textLabel->setOcclusion(true);
                    m_visibleTextSet.erase(textLabelIt);
                }
            } else {
                textLabelIt++;
            }
        } else {
            m_repeatGroups[seed].push_back(component);
            textLabelIt++;
        }
    }

    // Request for render if labels are in fading in/out states
    if (m_needUpdate) {
        requestRender();
    }

    m_lastZoom = currentZoom;
}

const std::vector<TouchItem>& Labels::getFeaturesAtPoint(const View& _view, float _dt,
                                                         const std::vector<std::unique_ptr<Style>>& _styles,
                                                         const std::vector<std::shared_ptr<Tile>>& _tiles,
                                                         float _x, float _y, bool _visibleOnly) {
    // FIXME dpi dependent threshold
    const float thumbSize = 50;

    m_touchItems.clear();

    glm::vec2 screenSize = glm::vec2(_view.getWidth(), _view.getHeight());
    glm::vec2 touchPoint(_x, _y);

    OBB obb(_x - thumbSize/2, _y - thumbSize/2, 0, thumbSize, thumbSize);

    float z = _view.getZoom();
    float dz = z - std::floor(z);

    for (const auto& tile : _tiles) {

        glm::mat4 mvp = _view.getViewProjectionMatrix() * tile->getModelMatrix();

        for (const auto& style : _styles) {
            const auto& mesh = tile->getMesh(*style);
            if (!mesh) { continue; }

            const LabelMesh* labelMesh = dynamic_cast<const LabelMesh*>(mesh.get());
            if (!labelMesh) { continue; }

            for (auto& label : labelMesh->getLabels()) {

                auto& options = label->options();
                if (!options.interactive) { continue; }

                if (!_visibleOnly) {
                    label->updateScreenTransform(mvp, screenSize, false);
                    label->updateBBoxes(dz);
                }

                if (isect2d::intersect(label->obb(), obb)) {
                    float distance = glm::length2(label->transform().state.screenPos - touchPoint);

                    m_touchItems.push_back({options.properties, std::sqrt(distance)});
                }
            }
        }
    }

    std::sort(m_touchItems.begin(), m_touchItems.end(),
              [](auto& a, auto& b){ return a.distance < b.distance; });

    return m_touchItems;
}

void Labels::drawDebug(const View& _view) {

    if (!Tangram::getDebugFlag(Tangram::DebugFlags::labels)) {
        return;
    }

    for (auto label : m_labels) {
        if (label->canOcclude()) {
            glm::vec2 offset = label->options().offset;
            glm::vec2 sp = label->transform().state.screenPos;
            float angle = label->transform().state.rotation;
            offset = glm::rotate(offset, angle);

            // draw bounding box
            Label::State state = label->state();
            switch (state) {
                case Label::State::sleep:
                    Primitives::setColor(0x00ff00);
                    break;
                case Label::State::visible:
                    Primitives::setColor(0x000000);
                    break;
                case Label::State::wait_occ:
                    Primitives::setColor(0x0000ff);
                    break;
                case Label::State::fading_in:
                case Label::State::fading_out:
                    Primitives::setColor(0xffff00);
                    break;
                default:
                    Primitives::setColor(0xff0000);
            }

            Primitives::drawPoly(&(label->obb().getQuad())[0], 4);

            // draw offset
            Primitives::setColor(0x000000);
            Primitives::drawLine(sp, sp - offset);

            // draw projected anchor point
            Primitives::setColor(0x0000ff);
            Primitives::drawRect(sp - glm::vec2(1.f), sp + glm::vec2(1.f));
        }
    }

    glm::vec2 split(_view.getWidth() / 256, _view.getHeight() / 256);
    glm::vec2 res(_view.getWidth(), _view.getHeight());
    const short xpad = short(ceilf(res.x / split.x));
    const short ypad = short(ceilf(res.y / split.y));

    Primitives::setColor(0x7ef586);
    short x = 0, y = 0;
    for (int j = 0; j < split.y; ++j) {
        for (int i = 0; i < split.x; ++i) {
            AABB cell(x, y, x + xpad, y + ypad);
            Primitives::drawRect({x, y}, {x + xpad, y + ypad});
            x += xpad;
            if (x >= res.x) {
                x = 0;
                y += ypad;
            }
        }
    }
}

}
<commit_msg>Sort by distance of the feature world position<commit_after>#include "labels.h"

#include "tangram.h"
#include "platform.h"
#include "gl/shaderProgram.h"
#include "gl/primitives.h"
#include "view/view.h"
#include "style/material.h"
#include "style/style.h"
#include "tile/tile.h"
#include "tile/tileCache.h"

#include "glm/glm.hpp"
#include "glm/gtc/matrix_transform.hpp"
#include "glm/gtx/rotate_vector.hpp"
#include "glm/gtx/norm.hpp"

namespace Tangram {

Labels::Labels()
    : m_needUpdate(false),
      m_lastZoom(0.0f)
{}

Labels::~Labels() {}

int Labels::LODDiscardFunc(float _maxZoom, float _zoom) {
    return (int) MIN(floor(((log(-_zoom + (_maxZoom + 2)) / log(_maxZoom + 2) * (_maxZoom )) * 0.5)), MAX_LOD);
}

void Labels::update(const View& _view, float _dt, const std::vector<std::unique_ptr<Style>>& _styles,
                    const std::vector<std::shared_ptr<Tile>>& _tiles, std::unique_ptr<TileCache>& _cache) {

    m_needUpdate = false;

    // float zoom = _view.getZoom();
    // int lodDiscard = LODDiscardFunc(View::s_maxZoom, zoom);
    // LOG("loddiscard %f %d", zoom, lodDiscard);

    std::set<std::pair<Label*, Label*>> occlusions;

    // Could clear this at end of function unless debug draw is active
    m_labels.clear();
    m_aabbs.clear();
    m_visibleTextSet.clear();
    m_collideComponents.clear();
    m_repeatGroups.clear();

    glm::vec2 screenSize = glm::vec2(_view.getWidth(), _view.getHeight());

    float currentZoom = _view.getZoom();
    float dz = currentZoom - std::floor(currentZoom);

    //// Collect labels from visible tiles

    for (const auto& tile : _tiles) {

        // discard based on level of detail
        // if ((zoom - tile->getID().z) > lodDiscard) {
        //     LOG("discard %d %d %d", tile->getID().z, tile->getID().x, tile->getID().y);
        //     continue;
        // }

        bool proxyTile = tile->isProxy();

        glm::mat4 mvp = _view.getViewProjectionMatrix() * tile->getModelMatrix();

        for (const auto& style : _styles) {
            const auto& mesh = tile->getMesh(*style);
            if (!mesh) { continue; }

            const LabelMesh* labelMesh = dynamic_cast<const LabelMesh*>(mesh.get());
            if (!labelMesh) { continue; }

            for (auto& label : labelMesh->getLabels()) {
                m_needUpdate |= label->update(mvp, screenSize, _dt, dz);

                label->setProxy(proxyTile);

                if (label->canOcclude()) {
                    m_aabbs.push_back(label->aabb());
                    m_aabbs.back().m_userData = (void*)label.get();
                }
                m_labels.push_back(label.get());
            }
        }
    }

    //// Manage occlusions

    // broad phase
    m_isect2d.resize({_view.getWidth() / 256, _view.getHeight() / 256}, {_view.getWidth(), _view.getHeight()});
    m_isect2d.intersect(m_aabbs);

    // narrow phase
    for (auto pair : m_isect2d.pairs) {
        const auto& aabb1 = m_aabbs[pair.first];
        const auto& aabb2 = m_aabbs[pair.second];

        auto l1 = static_cast<Label*>(aabb1.m_userData);
        auto l2 = static_cast<Label*>(aabb2.m_userData);

        if (intersect(l1->obb(), l2->obb())) { occlusions.insert({l1, l2}); }
    }

    for (auto& pair : occlusions) {
        if (!pair.first->occludedLastFrame() || !pair.second->occludedLastFrame()) {
            // check first is the label belongs to a proxy tile
            if (pair.first->isProxy() && !pair.second->isProxy()) {
                pair.first->setOcclusion(true);
            } else if (!pair.first->isProxy() && pair.second->isProxy()) {
                pair.second->setOcclusion(true);
            } else {
                // lower numeric priority means higher priority
                if (pair.first->options().priority < pair.second->options().priority) {
                    pair.second->setOcclusion(true);
                } else {
                    pair.first->setOcclusion(true);
                }
            }
        }
    }

    //// Mark labels to skip transitions

    if ((int) m_lastZoom != (int) currentZoom) {
        for (const auto& t0 : _tiles) {
            TileID tileID = t0->getID();
            std::vector<std::shared_ptr<Tile>> tiles;

            if (m_lastZoom < currentZoom) {
                // zooming in, add the one cached parent tile
                tiles.push_back(_cache->contains(t0->sourceID(), tileID.getParent()));
            } else {
                // zooming out, add the 4 cached children tiles
                tiles.push_back(_cache->contains(t0->sourceID(), tileID.getChild(0)));
                tiles.push_back(_cache->contains(t0->sourceID(), tileID.getChild(1)));
                tiles.push_back(_cache->contains(t0->sourceID(), tileID.getChild(2)));
                tiles.push_back(_cache->contains(t0->sourceID(), tileID.getChild(3)));
            }

            for (const auto& t1 : tiles) {
                if (!t1) { continue; }
                for (const auto& style : _styles) {
                    const auto& m0 = t0->getMesh(*style);
                    if (!m0) { continue; }
                    const LabelMesh* mesh0 = dynamic_cast<const LabelMesh*>(m0.get());
                    if (!mesh0) { continue; }
                    const auto& m1 = t1->getMesh(*style);
                    if (!m1) { continue; }
                    const LabelMesh* mesh1 = static_cast<const LabelMesh*>(m1.get());

                    for (auto& l0 : mesh0->getLabels()) {
                        if (!l0->canOcclude()) { continue; }

                        for (auto& l1 : mesh1->getLabels()) {
                            if (!l1 || !l1->canOcclude() || l0->hash() != l1->hash()) {
                                continue;
                            }
                            float d2 = glm::distance2(l0->transform().state.screenPos,
                                    l1->transform().state.screenPos);

                            // The new label lies within the circle defined by the bbox of l0
                            if (sqrt(d2) < std::max(l0->dimension().x, l0->dimension().y)) {
                                l0->skipTransitions();
                            }
                        }
                    }
                }
            }
        }
    }

    //// Update label meshes

    for (auto label : m_labels) {
        label->occlusionSolved();
        label->pushTransform();

        if (label->canOcclude()) {
            if (!label->visibleState()) { continue; }
            TextLabel* textLabel = dynamic_cast<TextLabel*>(label);
            if (!textLabel) { continue; }
            m_visibleTextSet.push_back(textLabel);
        }
    }

    /// Apply repeat groups

    // Ensure the labels are always treated in the same order
    std::sort(m_visibleTextSet.begin(), m_visibleTextSet.end(), [](TextLabel* _a, TextLabel* _b) {
        return glm::length2(_a->transform().modelPosition1) < glm::length2(_b->transform().modelPosition1);
    });

    auto textLabelIt = m_visibleTextSet.begin();
    while (textLabelIt != m_visibleTextSet.end()) {
        auto textLabel = *textLabelIt;
        CollideComponent component;
        component.position = textLabel->transform().state.screenPos;
        component.userData = (void*)textLabel;

        std::size_t seed = 0;
        hash_combine(seed, textLabel->text);

        component.group = seed;
        component.mask = seed;

        auto it = m_repeatGroups.find(seed);
        if (it != m_repeatGroups.end()) {
            std::vector<CollideComponent>& group = m_repeatGroups[seed];

            if (std::find(group.begin(), group.end(), component) == group.end()) {
                std::vector<CollideComponent> newGroup(group);
                newGroup.push_back(component);

                isect2d::CollideOption options;
                options.thresholdDistance = 100.0f;
                options.rule = isect2d::CollideRuleOption::UNIDIRECTIONNAL;

                auto collisionMaskPairs = isect2d::intersect(newGroup, options);

                if (collisionMaskPairs.size() == 0) {
                    group.push_back(component);
                    textLabelIt++;
                } else {
                    textLabel->setOcclusion(true);
                    m_visibleTextSet.erase(textLabelIt);
                }
            } else {
                textLabelIt++;
            }
        } else {
            m_repeatGroups[seed].push_back(component);
            textLabelIt++;
        }
    }

    // Request for render if labels are in fading in/out states
    if (m_needUpdate) {
        requestRender();
    }

    m_lastZoom = currentZoom;
}

const std::vector<TouchItem>& Labels::getFeaturesAtPoint(const View& _view, float _dt,
                                                         const std::vector<std::unique_ptr<Style>>& _styles,
                                                         const std::vector<std::shared_ptr<Tile>>& _tiles,
                                                         float _x, float _y, bool _visibleOnly) {
    // FIXME dpi dependent threshold
    const float thumbSize = 50;

    m_touchItems.clear();

    glm::vec2 screenSize = glm::vec2(_view.getWidth(), _view.getHeight());
    glm::vec2 touchPoint(_x, _y);

    OBB obb(_x - thumbSize/2, _y - thumbSize/2, 0, thumbSize, thumbSize);

    float z = _view.getZoom();
    float dz = z - std::floor(z);

    for (const auto& tile : _tiles) {

        glm::mat4 mvp = _view.getViewProjectionMatrix() * tile->getModelMatrix();

        for (const auto& style : _styles) {
            const auto& mesh = tile->getMesh(*style);
            if (!mesh) { continue; }

            const LabelMesh* labelMesh = dynamic_cast<const LabelMesh*>(mesh.get());
            if (!labelMesh) { continue; }

            for (auto& label : labelMesh->getLabels()) {

                auto& options = label->options();
                if (!options.interactive) { continue; }

                if (!_visibleOnly) {
                    label->updateScreenTransform(mvp, screenSize, false);
                    label->updateBBoxes(dz);
                }

                if (isect2d::intersect(label->obb(), obb)) {
                    float distance = glm::length2(label->transform().state.screenPos - touchPoint);

                    m_touchItems.push_back({options.properties, std::sqrt(distance)});
                }
            }
        }
    }

    std::sort(m_touchItems.begin(), m_touchItems.end(),
              [](auto& a, auto& b){ return a.distance < b.distance; });

    return m_touchItems;
}

void Labels::drawDebug(const View& _view) {

    if (!Tangram::getDebugFlag(Tangram::DebugFlags::labels)) {
        return;
    }

    for (auto label : m_labels) {
        if (label->canOcclude()) {
            glm::vec2 offset = label->options().offset;
            glm::vec2 sp = label->transform().state.screenPos;
            float angle = label->transform().state.rotation;
            offset = glm::rotate(offset, angle);

            // draw bounding box
            Label::State state = label->state();
            switch (state) {
                case Label::State::sleep:
                    Primitives::setColor(0x00ff00);
                    break;
                case Label::State::visible:
                    Primitives::setColor(0x000000);
                    break;
                case Label::State::wait_occ:
                    Primitives::setColor(0x0000ff);
                    break;
                case Label::State::fading_in:
                case Label::State::fading_out:
                    Primitives::setColor(0xffff00);
                    break;
                default:
                    Primitives::setColor(0xff0000);
            }

            Primitives::drawPoly(&(label->obb().getQuad())[0], 4);

            // draw offset
            Primitives::setColor(0x000000);
            Primitives::drawLine(sp, sp - offset);

            // draw projected anchor point
            Primitives::setColor(0x0000ff);
            Primitives::drawRect(sp - glm::vec2(1.f), sp + glm::vec2(1.f));
        }
    }

    glm::vec2 split(_view.getWidth() / 256, _view.getHeight() / 256);
    glm::vec2 res(_view.getWidth(), _view.getHeight());
    const short xpad = short(ceilf(res.x / split.x));
    const short ypad = short(ceilf(res.y / split.y));

    Primitives::setColor(0x7ef586);
    short x = 0, y = 0;
    for (int j = 0; j < split.y; ++j) {
        for (int i = 0; i < split.x; ++i) {
            AABB cell(x, y, x + xpad, y + ypad);
            Primitives::drawRect({x, y}, {x + xpad, y + ypad});
            x += xpad;
            if (x >= res.x) {
                x = 0;
                y += ypad;
            }
        }
    }
}

}
<|endoftext|>


<commit_before>/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
Copyright (C) 2007 HermitWorks Entertainment Corporation
Copyright (C) 2006-2009 Robert Beckebans <trebor_7@users.sourceforge.net>

This file is part of Daemon source code.

Daemon source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.

Daemon source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Daemon source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/

#include "tr_local.h"
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpedantic"
#endif

// HACK: Try to crash less when asked to decode invalid inputs.
class crnd_decompression_exception : public std::exception {};
#define CRND_ASSERT(_exp) (!!(_exp) ? (void)0 : throw crnd_decompression_exception())

#include "crunch/inc/crn_decomp.h"

#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif

bool LoadInMemoryCRN(void* buff, size_t buffLen, byte **data, int *width, int *height,
                     int *numLayers, int *numMips, int *bits)
{
    if (crnd::crnd_validate_file(buff, buffLen, nullptr)) { // Checks the header, not the whole file.
        // Found height and width in [1, 4096], num mip levels in [1, 13], faces in {1, 6}
    } else {
        return false;
    }
    crnd::crn_texture_info ti;
    if (!crnd::crnd_get_texture_info(buff, buffLen, &ti)) {
        return false;
    }

    switch (ti.m_format) {
    case cCRNFmtDXT1:
        *bits |= IF_BC1;
        break;
    case cCRNFmtDXT3:
        *bits |= IF_BC2;
        break;
    case cCRNFmtDXT5:
        *bits |= IF_BC3;
        break;
    case cCRNFmtDXT5A:
        *bits |= IF_BC4;
        break;
    case cCRNFmtDXN_XY:
        *bits |= IF_BC5;
        break;
    default:
        return false;
    }

    *width = ti.m_width;
    *height = ti.m_height;
    *numMips = ti.m_levels;
    *numLayers = ti.m_faces == 6 ? 6 : 0;

    uint32_t totalSize = 0;
    uint32_t sizes[cCRNMaxLevels];
    for (unsigned i = 0; i < ti.m_levels; i++) {
        crnd::crn_level_info li;
        if (!crnd::crnd_get_level_info(buff, buffLen, i, &li)) {
            return false;
        }
        sizes[i] = li.m_blocks_x * li.m_blocks_y * li.m_bytes_per_block;
        totalSize += sizes[i] * ti.m_faces;
    }

    crnd::crnd_unpack_context ctx = crnd::crnd_unpack_begin(buff, buffLen);
    if (!ctx) {
        return false;
    }
    byte* nextImage = (byte *)ri.Z_Malloc(totalSize);
    bool success = true;
    for (unsigned i = 0; i < ti.m_levels; i++) {
        for (unsigned j = 0; j < ti.m_faces; j++) {
            data[i * ti.m_faces + j] = nextImage;
            nextImage += sizes[i];
        }
        try {
            if (!crnd::crnd_unpack_level(ctx, (void **)&data[i * ti.m_faces], sizes[i], 0, i)) {
                success = false;
                break;
            }
        } catch (const crnd_decompression_exception&) {
            // Exception added as a hack to try and avoid crashing on files using the old format.
            // In general though, it seems the crunch library does not try to validate the files and may crash while decoding.
            success = false;
            break;
        }
    }
    crnd::crnd_unpack_end(ctx);
    return success;
}

void LoadCRN(const char* name, byte **data, int *width, int *height,
             int *numLayers, int *numMips, int *bits, byte)
{
    void* buff;
    size_t buffLen = ri.FS_ReadFile(name, &buff);
    *numLayers = 0;
    if (!buff) {
        return;
    }
    if (!LoadInMemoryCRN(buff, buffLen, data, width, height, numLayers, numMips, bits)) {
        if (*data) {
            ri.Free(*data);
            *data = nullptr; // This signals failure.
        }
        Log::Warn("CRN image '%s' has an invalid format", name);
    }
    ri.FS_FreeFile(buff);
}
<commit_msg>Show detailed CRN image load warnings<commit_after>/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
Copyright (C) 2007 HermitWorks Entertainment Corporation
Copyright (C) 2006-2009 Robert Beckebans <trebor_7@users.sourceforge.net>

This file is part of Daemon source code.

Daemon source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.

Daemon source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Daemon source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/

#include "tr_local.h"
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpedantic"
#endif

// HACK: Try to crash less when asked to decode invalid inputs.
class crnd_decompression_exception : public std::exception {};
#define CRND_ASSERT(_exp) (!!(_exp) ? (void)0 : throw crnd_decompression_exception())

#include "crunch/inc/crn_decomp.h"

#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif

#define CASE_CRN_FORMAT(format) \
  case format: return STRING(format);

namespace {
std::string CRNFormatToString(crn_format format)
{
  switch (format)
  {
    CASE_CRN_FORMAT(cCRNFmtDXT1);
    CASE_CRN_FORMAT(cCRNFmtDXT3);
    CASE_CRN_FORMAT(cCRNFmtDXT5);
    CASE_CRN_FORMAT(cCRNFmtDXT5_CCxY);
    CASE_CRN_FORMAT(cCRNFmtDXT5_xGxR);
    CASE_CRN_FORMAT(cCRNFmtDXT5_xGBR);
    CASE_CRN_FORMAT(cCRNFmtDXT5_AGBR);
    CASE_CRN_FORMAT(cCRNFmtDXN_XY);
    CASE_CRN_FORMAT(cCRNFmtDXN_YX);
    CASE_CRN_FORMAT(cCRNFmtDXT5A);
    CASE_CRN_FORMAT(cCRNFmtETC1);
    CASE_CRN_FORMAT(cCRNFmtETC2);
    CASE_CRN_FORMAT(cCRNFmtETC2A);
    CASE_CRN_FORMAT(cCRNFmtETC1S);
    CASE_CRN_FORMAT(cCRNFmtETC2AS);
  default:
    return "unknown (" + std::to_string(Util::ordinal(format)) + ")";
  }
}

bool LoadInMemoryCRN(const char* name, void* buff, size_t buffLen, byte **data, int *width, int *height,
                     int *numLayers, int *numMips, int *bits)
{
    if (crnd::crnd_validate_file(buff, buffLen, nullptr)) { // Checks the header, not the whole file.
        // Found height and width in [1, 4096], num mip levels in [1, 13], faces in {1, 6}
    } else {
        Log::Warn("CRN image '%s' has an invalid header", name);
        return false;
    }
    crnd::crn_texture_info ti;
    if (!crnd::crnd_get_texture_info(buff, buffLen, &ti)) {
        Log::Warn("CRN image '%s' has bad texture info", name);
        return false;
    }

    switch (ti.m_format) {
    case cCRNFmtDXT1:
        *bits |= IF_BC1;
        break;
    case cCRNFmtDXT3:
        *bits |= IF_BC2;
        break;
    case cCRNFmtDXT5:
        *bits |= IF_BC3;
        break;
    case cCRNFmtDXT5A:
        *bits |= IF_BC4;
        break;
    case cCRNFmtDXN_XY:
        *bits |= IF_BC5;
        break;
    default:
        Log::Warn("CRN image '%s' has unsupported format '%s'", name, CRNFormatToString(ti.m_format));
        return false;
    }

    *width = ti.m_width;
    *height = ti.m_height;
    *numMips = ti.m_levels;
    *numLayers = ti.m_faces == 6 ? 6 : 0;

    uint32_t totalSize = 0;
    uint32_t sizes[cCRNMaxLevels];
    for (unsigned i = 0; i < ti.m_levels; i++) {
        crnd::crn_level_info li;
        if (!crnd::crnd_get_level_info(buff, buffLen, i, &li)) {
            Log::Warn("CRN image '%s' has bad info on level '%d'", name, i);
            return false;
        }
        sizes[i] = li.m_blocks_x * li.m_blocks_y * li.m_bytes_per_block;
        totalSize += sizes[i] * ti.m_faces;
    }

    crnd::crnd_unpack_context ctx = crnd::crnd_unpack_begin(buff, buffLen);
    if (!ctx) {
        Log::Warn("CRN image '%s' has bad data", name);
        return false;
    }
    byte* nextImage = (byte *)ri.Z_Malloc(totalSize);
    bool success = true;
    for (unsigned i = 0; i < ti.m_levels; i++) {
        for (unsigned j = 0; j < ti.m_faces; j++) {
            data[i * ti.m_faces + j] = nextImage;
            nextImage += sizes[i];
        }
        try {
            if (!crnd::crnd_unpack_level(ctx, (void **)&data[i * ti.m_faces], sizes[i], 0, i)) {
                Log::Warn("CRN image '%s' has bad level '%d'", name, i);
                success = false;
                break;
            }
        } catch (const crnd_decompression_exception& ex) {
            // Exception added as a hack to try and avoid crashing on files using the old format.
            // In general though, it seems the crunch library does not try to validate the files and may crash while decoding.
            Log::Warn("CRN image '%s' decompression failure for level '%d': %s", name, i, ex.what());
            success = false;
            break;
        }
    }
    crnd::crnd_unpack_end(ctx);
    return success;
}
}  // namespace

void LoadCRN(const char* name, byte **data, int *width, int *height,
             int *numLayers, int *numMips, int *bits, byte)
{
    void* buff;
    size_t buffLen = ri.FS_ReadFile(name, &buff);
    *numLayers = 0;
    if (!buff) {
        return;
    }
    if (!LoadInMemoryCRN(name, buff, buffLen, data, width, height, numLayers, numMips, bits)) {
        if (*data) {
            ri.Free(*data);
            *data = nullptr; // This signals failure.
        }
    }
    ri.FS_FreeFile(buff);
}
<|endoftext|>


<commit_before>/******************************************************************************
**
** Copyright (C) 2009-2011 Kyle Lutz <kyle.r.lutz@gmail.com>
**
** This file is part of chemkit. For more information see
** <http://www.chemkit.org>.
**
** chemkit is free software: you can redistribute it and/or modify
** it under the terms of the GNU Lesser General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** chemkit is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU Lesser General Public License for more details.
**
** You should have received a copy of the GNU Lesser General Public License
** along with chemkit. If not, see <http://www.gnu.org/licenses/>.
**
******************************************************************************/

#include <QtTest>

#include <chemkit/staticmatrix.h>

class StaticMatrixTest : public QObject
{
    Q_OBJECT

    private slots:
        void multiply();
        void multiplyScalar();
        void determinant();
        void invert();
};

void StaticMatrixTest::multiply()
{
    chemkit::StaticMatrix<chemkit::Float, 2, 3> a;
    a(0, 0) = 8;
    a(0, 1) = -6;
    a(0, 2) = 1;
    a(1, 0) = 4;
    a(1, 1) = 7;
    a(1, 2) = -2;

    chemkit::StaticMatrix<chemkit::Float, 3, 2> b;
    b(0, 0) = 0;
    b(0, 1) = 3;
    b(1, 0) = 3;
    b(1, 1) = 4;
    b(2, 0) = 7;
    b(2, 1) = -2;

    // c = a * b
    chemkit::StaticMatrix<chemkit::Float, 2, 2> c = a.multiply(b);
    QCOMPARE(c(0, 0), chemkit::Float(-11.0));
    QCOMPARE(c(0, 1), chemkit::Float(-2.0));
    QCOMPARE(c(1, 0), chemkit::Float(7.0));
    QCOMPARE(c(1, 1), chemkit::Float(44.0));

    // d = b * a
    chemkit::StaticMatrix<chemkit::Float, 3, 3> d = b.multiply(a);
    QCOMPARE(d(0, 0), chemkit::Float(12.0));
    QCOMPARE(d(0, 1), chemkit::Float(21.0));
    QCOMPARE(d(0, 2), chemkit::Float(-6.0));
    QCOMPARE(d(1, 0), chemkit::Float(40.0));
    QCOMPARE(d(1, 1), chemkit::Float(10.0));
    QCOMPARE(d(1, 2), chemkit::Float(-5.0));
    QCOMPARE(d(2, 0), chemkit::Float(48.0));
    QCOMPARE(d(2, 1), chemkit::Float(-56.0));
    QCOMPARE(d(2, 2), chemkit::Float(11.0));
}

void StaticMatrixTest::multiplyScalar()
{
    chemkit::StaticMatrix<int, 2, 3> a;
    a << 1, 2, 3,
         4, 5, 6;

    chemkit::StaticMatrix<int, 2, 3> b = a.multiply(4);
    QCOMPARE(b(0, 0), 4);
    QCOMPARE(b(0, 1), 8);
    QCOMPARE(b(0, 2), 12);
    QCOMPARE(b(1, 0), 16);
    QCOMPARE(b(1, 1), 20);
    QCOMPARE(b(1, 2), 24);

    chemkit::StaticMatrix<int, 3, 3> c;
    c <<  2,  4,  6,
          8, 10, 12,
         14, 16, 18;

    chemkit::StaticMatrix<int, 3, 3> d = c.multiply(-1);
    QCOMPARE(d(0, 0), -2);
    QCOMPARE(d(0, 1), -4);
    QCOMPARE(d(0, 2), -6);
    QCOMPARE(d(1, 0), -8);
    QCOMPARE(d(1, 1), -10);
    QCOMPARE(d(1, 2), -12);
    QCOMPARE(d(2, 0), -14);
    QCOMPARE(d(2, 1), -16);
    QCOMPARE(d(2, 2), -18);
}

void StaticMatrixTest::determinant()
{
    // 3x3 matrix filled with 2's
    chemkit::StaticMatrix<chemkit::Float, 3, 3> matrix3;
    matrix3.fill(2);
    QCOMPARE(qRound(matrix3.determinant()), 0);

    // another matrix
    matrix3.fill(0);
    matrix3(0, 0) = 6;
    matrix3(0, 1) = 3;
    matrix3(0, 2) = 2;
    matrix3(1, 0) = 4;
    matrix3(1, 1) = -3;
    matrix3(1, 2) = 2;
    matrix3(2, 0) = -1;
    matrix3(2, 1) = 9;
    matrix3(2, 2) = -2;
    QCOMPARE(qRound(matrix3.determinant()), 12);

    // change last row
    matrix3(2, 0) = 0;
    matrix3(2, 1) = 4;
    matrix3(2, 2) = 0;
    QCOMPARE(qRound(matrix3.determinant()), -16);

    // change first row
    matrix3(0, 0) = 0;
    matrix3(0, 1) = 4;
    matrix3(0, 2) = 0;
    QCOMPARE(qRound(matrix3.determinant()), 0);
}

void StaticMatrixTest::invert()
{
    chemkit::StaticMatrix<chemkit::Float, 3, 3> matrix3;
    matrix3(0, 0) = 1;
    matrix3(0, 1) = 2;
    matrix3(0, 2) = 3;
    matrix3(1, 0) = 0;
    matrix3(1, 1) = 1;
    matrix3(1, 2) = 0;
    matrix3(2, 0) = 4;
    matrix3(2, 1) = 0;
    matrix3(2, 2) = 4;

    chemkit::StaticMatrix<chemkit::Float, 3, 3> inverse3 = matrix3.inverted();
    QCOMPARE(inverse3(0, 0), chemkit::Float(-0.5));
    QCOMPARE(inverse3(0, 1), chemkit::Float(1.0));
    QCOMPARE(inverse3(0, 2), chemkit::Float(0.375));
    QCOMPARE(inverse3(1, 0), chemkit::Float(0.0));
    QCOMPARE(inverse3(1, 1), chemkit::Float(1.0));
    QCOMPARE(inverse3(1, 2), chemkit::Float(0.0));
    QCOMPARE(inverse3(2, 0), chemkit::Float(0.5));
    QCOMPARE(inverse3(2, 1), chemkit::Float(-1.0));
    QCOMPARE(inverse3(2, 2), chemkit::Float(-0.125));
}

QTEST_APPLESS_MAIN(StaticMatrixTest)
#include "staticmatrixtest.moc"
<commit_msg>Add test for StaticMatrix::multiply()<commit_after>/******************************************************************************
**
** Copyright (C) 2009-2011 Kyle Lutz <kyle.r.lutz@gmail.com>
**
** This file is part of chemkit. For more information see
** <http://www.chemkit.org>.
**
** chemkit is free software: you can redistribute it and/or modify
** it under the terms of the GNU Lesser General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** chemkit is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU Lesser General Public License for more details.
**
** You should have received a copy of the GNU Lesser General Public License
** along with chemkit. If not, see <http://www.gnu.org/licenses/>.
**
******************************************************************************/

#include <QtTest>

#include <chemkit/staticmatrix.h>
#include <chemkit/staticvector.h>

class StaticMatrixTest : public QObject
{
    Q_OBJECT

    private slots:
        void multiply();
        void multiplyScalar();
        void multiplyVector();
        void determinant();
        void invert();
};

void StaticMatrixTest::multiply()
{
    chemkit::StaticMatrix<chemkit::Float, 2, 3> a;
    a(0, 0) = 8;
    a(0, 1) = -6;
    a(0, 2) = 1;
    a(1, 0) = 4;
    a(1, 1) = 7;
    a(1, 2) = -2;

    chemkit::StaticMatrix<chemkit::Float, 3, 2> b;
    b(0, 0) = 0;
    b(0, 1) = 3;
    b(1, 0) = 3;
    b(1, 1) = 4;
    b(2, 0) = 7;
    b(2, 1) = -2;

    // c = a * b
    chemkit::StaticMatrix<chemkit::Float, 2, 2> c = a.multiply(b);
    QCOMPARE(c(0, 0), chemkit::Float(-11.0));
    QCOMPARE(c(0, 1), chemkit::Float(-2.0));
    QCOMPARE(c(1, 0), chemkit::Float(7.0));
    QCOMPARE(c(1, 1), chemkit::Float(44.0));

    // d = b * a
    chemkit::StaticMatrix<chemkit::Float, 3, 3> d = b.multiply(a);
    QCOMPARE(d(0, 0), chemkit::Float(12.0));
    QCOMPARE(d(0, 1), chemkit::Float(21.0));
    QCOMPARE(d(0, 2), chemkit::Float(-6.0));
    QCOMPARE(d(1, 0), chemkit::Float(40.0));
    QCOMPARE(d(1, 1), chemkit::Float(10.0));
    QCOMPARE(d(1, 2), chemkit::Float(-5.0));
    QCOMPARE(d(2, 0), chemkit::Float(48.0));
    QCOMPARE(d(2, 1), chemkit::Float(-56.0));
    QCOMPARE(d(2, 2), chemkit::Float(11.0));
}

void StaticMatrixTest::multiplyScalar()
{
    chemkit::StaticMatrix<int, 2, 3> a;
    a << 1, 2, 3,
         4, 5, 6;

    chemkit::StaticMatrix<int, 2, 3> b = a.multiply(4);
    QCOMPARE(b(0, 0), 4);
    QCOMPARE(b(0, 1), 8);
    QCOMPARE(b(0, 2), 12);
    QCOMPARE(b(1, 0), 16);
    QCOMPARE(b(1, 1), 20);
    QCOMPARE(b(1, 2), 24);

    chemkit::StaticMatrix<int, 3, 3> c;
    c <<  2,  4,  6,
          8, 10, 12,
         14, 16, 18;

    chemkit::StaticMatrix<int, 3, 3> d = c.multiply(-1);
    QCOMPARE(d(0, 0), -2);
    QCOMPARE(d(0, 1), -4);
    QCOMPARE(d(0, 2), -6);
    QCOMPARE(d(1, 0), -8);
    QCOMPARE(d(1, 1), -10);
    QCOMPARE(d(1, 2), -12);
    QCOMPARE(d(2, 0), -14);
    QCOMPARE(d(2, 1), -16);
    QCOMPARE(d(2, 2), -18);
}

void StaticMatrixTest::multiplyVector()
{
    chemkit::StaticMatrix<double, 3, 3> a;
    a << 1, 2, 3,
         4, 5, 6,
         7, 8, 9;

    chemkit::StaticVector<double, 3> p;
    p << 4, 0, -12;

    chemkit::StaticVector<double, 3> ap = a.multiply(p);
    QCOMPARE(qRound(ap[0]), -32);
    QCOMPARE(qRound(ap[1]), -56);
    QCOMPARE(qRound(ap[2]), -80);

    a << -10, 15, 20,
           0,  3,  9,
           1,  2,  8;
    p << 5, 1, 9;
    ap = a.multiply(p);
    QCOMPARE(qRound(ap[0]), 145);
    QCOMPARE(qRound(ap[1]), 84);
    QCOMPARE(qRound(ap[2]), 79);
}

void StaticMatrixTest::determinant()
{
    // 3x3 matrix filled with 2's
    chemkit::StaticMatrix<chemkit::Float, 3, 3> matrix3;
    matrix3.fill(2);
    QCOMPARE(qRound(matrix3.determinant()), 0);

    // another matrix
    matrix3.fill(0);
    matrix3(0, 0) = 6;
    matrix3(0, 1) = 3;
    matrix3(0, 2) = 2;
    matrix3(1, 0) = 4;
    matrix3(1, 1) = -3;
    matrix3(1, 2) = 2;
    matrix3(2, 0) = -1;
    matrix3(2, 1) = 9;
    matrix3(2, 2) = -2;
    QCOMPARE(qRound(matrix3.determinant()), 12);

    // change last row
    matrix3(2, 0) = 0;
    matrix3(2, 1) = 4;
    matrix3(2, 2) = 0;
    QCOMPARE(qRound(matrix3.determinant()), -16);

    // change first row
    matrix3(0, 0) = 0;
    matrix3(0, 1) = 4;
    matrix3(0, 2) = 0;
    QCOMPARE(qRound(matrix3.determinant()), 0);
}

void StaticMatrixTest::invert()
{
    chemkit::StaticMatrix<chemkit::Float, 3, 3> matrix3;
    matrix3(0, 0) = 1;
    matrix3(0, 1) = 2;
    matrix3(0, 2) = 3;
    matrix3(1, 0) = 0;
    matrix3(1, 1) = 1;
    matrix3(1, 2) = 0;
    matrix3(2, 0) = 4;
    matrix3(2, 1) = 0;
    matrix3(2, 2) = 4;

    chemkit::StaticMatrix<chemkit::Float, 3, 3> inverse3 = matrix3.inverted();
    QCOMPARE(inverse3(0, 0), chemkit::Float(-0.5));
    QCOMPARE(inverse3(0, 1), chemkit::Float(1.0));
    QCOMPARE(inverse3(0, 2), chemkit::Float(0.375));
    QCOMPARE(inverse3(1, 0), chemkit::Float(0.0));
    QCOMPARE(inverse3(1, 1), chemkit::Float(1.0));
    QCOMPARE(inverse3(1, 2), chemkit::Float(0.0));
    QCOMPARE(inverse3(2, 0), chemkit::Float(0.5));
    QCOMPARE(inverse3(2, 1), chemkit::Float(-1.0));
    QCOMPARE(inverse3(2, 2), chemkit::Float(-0.125));
}

QTEST_APPLESS_MAIN(StaticMatrixTest)
#include "staticmatrixtest.moc"
<|endoftext|>


<commit_before>/*************************************************************************
 *
 *  $RCSfile: plocroot.hxx,v $
 *
 *  $Revision: 1.1.1.1 $
 *
 *  last change: $Author: np $ $Date: 2002-03-08 14:25:39 $
 *
 *  The Contents of this file are made available subject to the terms of
 *  either of the following licenses
 *
 *         - GNU Lesser General Public License Version 2.1
 *         - Sun Industry Standards Source License Version 1.1
 *
 *  Sun Microsystems Inc., October, 2000
 *
 *  GNU Lesser General Public License Version 2.1
 *  =============================================
 *  Copyright 2000 by Sun Microsystems, Inc.
 *  901 San Antonio Road, Palo Alto, CA 94303, USA
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License version 2.1, as published by the Free Software Foundation.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 *  MA  02111-1307  USA
 *
 *
 *  Sun Industry Standards Source License Version 1.1
 *  =================================================
 *  The contents of this file are subject to the Sun Industry Standards
 *  Source License Version 1.1 (the "License"); You may not use this file
 *  except in compliance with the License. You may obtain a copy of the
 *  License at http://www.openoffice.org/license.html.
 *
 *  Software provided under this License is provided on an "AS IS" basis,
 *  WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
 *  WITHOUT LIMITATION, WARRANTIES THAT THE SOFTWARE IS FREE OF DEFECTS,
 *  MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE, OR NON-INFRINGING.
 *  See the License for the specific provisions governing your rights and
 *  obligations concerning the Software.
 *
 *  The Initial Developer of the Original Code is: Sun Microsystems, Inc.
 *
 *  Copyright: 2000 by Sun Microsystems, Inc.
 *
 *  All Rights Reserved.
 *
 *  Contributor(s): _______________________________________
 *
 *
 ************************************************************************/

#ifndef CSV_PLOCROOT_HXX
#define CSV_PLOCROOT_HXX

// USED SERVICES
    // BASE CLASSES
    // COMPONENTS
#include <cosv/string.hxx>
    // PARAMETERS
#include <cosv/csv_ostream.hxx>
#include <cosv/persist.hxx>

namespace csv
{

class bostream;

namespace ploc
{

class Root
{
  public:
    virtual             ~Root();

    static DYN Root *   Create_(
                            const char * &      o_sPathAfterRoot,
                            const char *        i_sPath,
                            const char *        i_sDelimiter = Delimiter() );

    virtual void        Get(                    /// Does not add a '\0' at the end,
                            ostream &           o_rPath ) const = 0;
    virtual void        Get(                    /// Does not add a '\0' at the end.
                            bostream &          so_rPath ) const = 0;
    virtual DYN Root *  CreateCopy() const = 0;
    virtual const char *
                        OwnDelimiter() const = 0;
};

}   // namespace ploc
}   // namespace csv

#endif

<commit_msg>INTEGRATION: CWS ooo19126 (1.1.1.1.118); FILE MERGED 2005/09/05 14:08:52 rt 1.1.1.1.118.1: #i54170# Change license header: remove SISSL<commit_after>/*************************************************************************
 *
 *  OpenOffice.org - a multi-platform office productivity suite
 *
 *  $RCSfile: plocroot.hxx,v $
 *
 *  $Revision: 1.2 $
 *
 *  last change: $Author: rt $ $Date: 2005-09-08 07:56:55 $
 *
 *  The Contents of this file are made available subject to
 *  the terms of GNU Lesser General Public License Version 2.1.
 *
 *
 *    GNU Lesser General Public License Version 2.1
 *    =============================================
 *    Copyright 2005 by Sun Microsystems, Inc.
 *    901 San Antonio Road, Palo Alto, CA 94303, USA
 *
 *    This library is free software; you can redistribute it and/or
 *    modify it under the terms of the GNU Lesser General Public
 *    License version 2.1, as published by the Free Software Foundation.
 *
 *    This library is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *    Lesser General Public License for more details.
 *
 *    You should have received a copy of the GNU Lesser General Public
 *    License along with this library; if not, write to the Free Software
 *    Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 *    MA  02111-1307  USA
 *
 ************************************************************************/

#ifndef CSV_PLOCROOT_HXX
#define CSV_PLOCROOT_HXX

// USED SERVICES
    // BASE CLASSES
    // COMPONENTS
#include <cosv/string.hxx>
    // PARAMETERS
#include <cosv/csv_ostream.hxx>
#include <cosv/persist.hxx>

namespace csv
{

class bostream;

namespace ploc
{

class Root
{
  public:
    virtual             ~Root();

    static DYN Root *   Create_(
                            const char * &      o_sPathAfterRoot,
                            const char *        i_sPath,
                            const char *        i_sDelimiter = Delimiter() );

    virtual void        Get(                    /// Does not add a '\0' at the end,
                            ostream &           o_rPath ) const = 0;
    virtual void        Get(                    /// Does not add a '\0' at the end.
                            bostream &          so_rPath ) const = 0;
    virtual DYN Root *  CreateCopy() const = 0;
    virtual const char *
                        OwnDelimiter() const = 0;
};

}   // namespace ploc
}   // namespace csv

#endif

<|endoftext|>


<commit_before>/** \file   EmailSender.cc
 *  \brief  Utility functions etc. related to the sending of email messages.
 *  \author Dr. Johannes Ruscheinski (johannes.ruscheinski@uni-tuebingen.de)
 *  \author Artur Kedzierski
 *  \author Dr. Gordon W. Paynter
 *
 *  \copyright 2015 Universitätsbibliothek Tübingen.
 *  \copyright 2002-2008 Project iVia.
 *  \copyright 2002-2008 The Regents of The University of California.
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as
 *  published by the Free Software Foundation, either version 3 of the
 *  License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#include "EmailSender.h"
#include <iostream>
#include <memory>
#include <list>
#include <stdexcept>
#include <vector>
#include <cstdlib>
#include "Compiler.h"
#include "DnsUtil.h"
#include "FileDescriptor.h"
#include "IniFile.h"
#include "MiscUtil.h"
#include "SocketUtil.h"
#include "SslConnection.h"
#include "StringUtil.h"
#include "util.h"

namespace {

// GetDateInRFC822Format()  -- returns current date and time in an RFC-822 compliant format.
//
std::string GetDateInRFC822Format() {
    const time_t now(std::time(nullptr));

    // Convert struct time to an RFC-822 formatted string:
    char date_string[100 + 1];
    std::strftime(date_string, sizeof(date_string), "%a, %e %b %Y %H:%M:%S %z", std::localtime(&now));

    return date_string;
}

static std::string smtp_server;

std::string GetSmtpServer() {
    if (smtp_server.empty()) {
        IniFile ini_file("/usr/local/var/lib/tuelib/cronjobs/smtp_server.conf");
        smtp_server = ini_file.getString("SMTPServer", "server_address");
    }

    return smtp_server;
}

void WriteToConnection(const int socket_fd, const TimeLimit &time_limit, const std::string &data,
                              SslConnection * const ssl_connection)
{
    if (unlikely(SocketUtil::TimedWrite(socket_fd, time_limit, data, ssl_connection) == -1))
        throw std::runtime_error("in WriteToConnection(EmailSender.cc) SocketUtil::TimedWrite failed! ("
                                 + std::string(strerror(errno)) + "))");
}

std::string GetDotStuffedMessage(const std::string &message) {
    std::list<std::string> lines;
    StringUtil::SplitThenTrim(message, "\n", "\r", &lines, /* suppress_empty_words = */ false);
    for (std::list<std::string>::iterator line(lines.begin()); line != lines.end(); ++line) {
        if (*line == ".")
            *line = "." + *line;
    }

    std::string dot_stuffed_message(message);
    StringUtil::Join(lines, "\r\n", &dot_stuffed_message);

    return dot_stuffed_message;
}

std::string CreateEmailMessage(const EmailSender::Priority priority, const EmailSender::Format format,
                               const std::string &sender, const std::string &recipient, const std::string &subject,
                               const std::string &message_body, const std::string &cc = "")
{
    std::string message;
    message  = "Date: " + GetDateInRFC822Format() + "\r\n";
    message += "From: " + sender + "\r\n";
    message += "To: " + recipient + "\r\n";
    if (not cc.empty())
        message += "Cc: " + cc + "\r\n";
    message += "Subject: " + subject + "\r\n\r\n";
    if (format == EmailSender::PLAIN_TEXT)
        message += "Content-Type: text/plain; charset=\"utf-8\"";
    else
        message += "Content-Type: text/html; charset=\"utf-8\"";
    if (priority != EmailSender::DO_NOT_SET_PRIORITY)
        message += "X-Priority: " + std::to_string(priority) + "\r\n\r\n";
    message += GetDotStuffedMessage(message_body) + ".\r\n";

    return message;
}

} // unnamed namespace

namespace EmailSender {

bool SendEmail(const std::string &sender, const std::string &recipient, const std::string &subject,
               const std::string &message_body, const Priority priority, const Format format,
               const std::string &reply_to, const bool use_ssl)
{
    if (unlikely(sender.empty() and reply_to.empty()))
        Error("in EmailSender::SendEmail: both \"sender\" and \"reply_to\" can't be empty!");

    const TimeLimit time_limit(10000 /* ms */);

    const bool log(not MiscUtil::SafeGetEnv("ENABLE_SMPT_CLIENT_LOGGING").empty());

    // Open connection:
    const unsigned short PORT(use_ssl ? 587 : 25);
    std::string error_message;
    const FileDescriptor socket_fd(
        SocketUtil::TcpConnect(GetSmtpServer(), PORT, time_limit, &error_message, SocketUtil::DISABLE_NAGLE));
    if (socket_fd == -1) {
        Warning("in EmailSender::SendEmail: can't connect to SMTP server \"" + GetSmtpServer() + ":"
                + std::to_string(PORT) + " (" + error_message + ")!");
        return false;
    }

    std::unique_ptr<SslConnection> ssl_connection;
    if (use_ssl)
        ssl_connection.reset(new SslConnection(socket_fd));

    // Read the welcome message:
    char buf[1000];
    ssize_t response_size;
    if ((response_size = SocketUtil::TimedRead(socket_fd, time_limit, buf, sizeof(buf), ssl_connection.get())) <= 0) {
        Warning("in EmailSender::SendEmail: Can't read SMTP server's welcome message!");
        return false;
    }
    if (log) {
        buf[std::min(static_cast<size_t>(response_size), sizeof(buf) - 1)] = '\0';
        std::clog << "Sever sent: " << buf << '\n';
    }

    // HELO <hostname>
    WriteToConnection(socket_fd, time_limit, "HELO " + DnsUtil::GetHostname() + "\r\n", ssl_connection.get());
    if ((response_size = SocketUtil::TimedRead(socket_fd, time_limit, buf, sizeof(buf), ssl_connection.get())) <= 0) { // read the response
        Warning("in EmailSender::SendEmail: Can't read SMTP server's response to HELO!");
        return false;
    }
    buf[std::min(static_cast<size_t>(response_size), sizeof(buf) - 1)] = '\0';
    // Expect a 2xx success code:
    if (not StringUtil::Match("2[0-9][0-9]*", buf)) {
        Warning("in EmailSender::SendEmail: Bad status code in response to \"HELO\" command: " + std::string(buf));
        return false;
    }

    // MAIL FROM: <email address of sender>
    WriteToConnection(socket_fd, time_limit, "MAIL FROM:<" + sender + ">\r\n", ssl_connection.get());
    if ((response_size = SocketUtil::TimedRead(socket_fd, time_limit, buf, sizeof(buf), ssl_connection.get())) <= 0) { // read the response
        Warning("in EmailSender::SendEmail: Can't read SMTP server's response to MAIL FROM:!");
        return false;
    }
    buf[std::min(static_cast<size_t>(response_size), sizeof(buf) - 1)] = '\0';
    // Expect a 2xx success code:
    if (not StringUtil::Match("2[0-9][0-9]*", buf)) {
        Warning("in EmailSender::SendEmail: Bad status code in response to \"MAIL FROM:\" command: "
                + std::string(buf));
        return false;
    }

    // Send email to each recipient:
    const std::list<std::string> receiver_email_address_list{ recipient };
    for (std::list<std::string>::const_iterator receiver_email_address(receiver_email_address_list.begin());
         receiver_email_address != receiver_email_address_list.end(); ++receiver_email_address)
    {
        // RCPT TO: <email address of receiver>
        WriteToConnection(socket_fd, time_limit, "RCPT TO:<" + *receiver_email_address + ">\r\n", ssl_connection.get());
        if ((response_size = SocketUtil::TimedRead(socket_fd, time_limit, buf, sizeof(buf), ssl_connection.get())) <= 0) { // read the response
            Warning("in EmailSender::SendEmail: Can't read SMTP server's response to RCPT TO:!");
            return false;
        }
        buf[std::min(static_cast<size_t>(response_size), sizeof(buf) - 1)] = '\0';
        // Expect a 2xx success code:
        if (not StringUtil::Match("2[0-9][0-9]*", buf)) {
            Warning("in EmailSender::SendEmail: Bad status code in response to \"RCPT TO:\" command: "
                    + std::string(buf));
            return false;
        }
    }

    // DATA
    WriteToConnection(socket_fd, time_limit, "DATA\r\n", ssl_connection.get());
    if ((response_size = SocketUtil::TimedRead(socket_fd, time_limit, buf, sizeof(buf))) <= 0) { // read the response
        Warning("in EmailSender::SendEmail: Can't read SMTP server's response to DATA!");
        return false;
    }
    buf[std::min(static_cast<size_t>(response_size), sizeof(buf) - 1)] = '\0';
    // Expect a 3xx code:
    if (not StringUtil::Match("3[0-9][0-9]*", buf)) {
        Warning("in EmailSender::SendEmail: Bad status code in response to \"DATA\" command: " + std::string(buf));
        return false;
    }

    // <data terminated by "." on a line by itself>
    WriteToConnection(socket_fd, time_limit,
                      CreateEmailMessage(priority, format, sender, recipient, subject, message_body) + "\r\n.\r\n",
                      ssl_connection.get());
    if ((response_size = SocketUtil::TimedRead(socket_fd, time_limit, buf, sizeof(buf), ssl_connection.get())) <= 0) { // read the response
        Warning("in EmailSender::SendEmail: Can't read SMTP server's response to sent data!");
        return false;
    }
    buf[std::min(static_cast<size_t>(response_size), sizeof(buf) - 1)] = '\0';
    // Expect a 2xx success code:
    if (not StringUtil::Match("2[0-9][0-9]*", buf)) {
        Warning("in EmailSender::SendEmail: Bad status code in response to sent data: " + std::string(buf));
        return false;
    }

    // QUIT
    WriteToConnection(socket_fd, time_limit, "QUIT\r\n", ssl_connection.get());
    if ((response_size = SocketUtil::TimedRead(socket_fd, time_limit, buf, sizeof(buf))) <= 0) { // read the response
        Warning("in EmailSender::SendEmail: Can't read SMTP server's response to QUIT!");
        return false;
    }
    buf[std::min(static_cast<size_t>(response_size), sizeof(buf) - 1)] = '\0';
    // Expect a 2xx code:
    if (not StringUtil::Match("2[0-9][0-9]*", buf)) {
        Warning("in EmailSender::SendEmail: Bad status code in response to \"QUIT\" command: " + std::string(buf));
        return false;
    }

    return true;
}

} // namespace EmailSender
<commit_msg>Refactored a lot of code.<commit_after>/** \file   EmailSender.cc
 *  \brief  Utility functions etc. related to the sending of email messages.
 *  \author Dr. Johannes Ruscheinski (johannes.ruscheinski@uni-tuebingen.de)
 *  \author Artur Kedzierski
 *  \author Dr. Gordon W. Paynter
 *
 *  \copyright 2015 Universitätsbibliothek Tübingen.
 *  \copyright 2002-2008 Project iVia.
 *  \copyright 2002-2008 The Regents of The University of California.
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as
 *  published by the Free Software Foundation, either version 3 of the
 *  License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#include "EmailSender.h"
#include <iostream>
#include <memory>
#include <list>
#include <stdexcept>
#include <vector>
#include <cstdlib>
#include "Compiler.h"
#include "DnsUtil.h"
#include "FileDescriptor.h"
#include "IniFile.h"
#include "MiscUtil.h"
#include "SocketUtil.h"
#include "SslConnection.h"
#include "StringUtil.h"
#include "util.h"

#define NUL '\0'

namespace {

// GetDateInRFC822Format()  -- returns current date and time in an RFC-822 compliant format.
//
std::string GetDateInRFC822Format() {
    const time_t now(std::time(nullptr));

    // Convert struct time to an RFC-822 formatted string:
    char date_string[100 + 1];
    std::strftime(date_string, sizeof(date_string), "%a, %e %b %Y %H:%M:%S %z", std::localtime(&now));

    return date_string;
}

static std::string smtp_server;

std::string GetSmtpServer() {
    if (smtp_server.empty()) {
        IniFile ini_file("/usr/local/var/lib/tuelib/cronjobs/smtp_server.conf");
        smtp_server = ini_file.getString("SMTPServer", "server_address");
    }

    return smtp_server;
}

std::string GetDotStuffedMessage(const std::string &message) {
    std::list<std::string> lines;
    StringUtil::SplitThenTrim(message, "\n", "\r", &lines, /* suppress_empty_words = */ false);
    for (auto &line : lines) {
        if (line == ".")
            line = "." + line;
    }

    std::string dot_stuffed_message(message);
    StringUtil::Join(lines, "\r\n", &dot_stuffed_message);

    return dot_stuffed_message;
}

std::string CreateEmailMessage(const EmailSender::Priority priority, const EmailSender::Format format,
                               const std::string &sender, const std::string &recipient, const std::string &subject,
                               const std::string &message_body, const std::string &cc = "")
{
    std::string message;
    message  = "Date: " + GetDateInRFC822Format() + "\r\n";
    message += "From: " + sender + "\r\n";
    message += "To: " + recipient + "\r\n";
    if (not cc.empty())
        message += "Cc: " + cc + "\r\n";
    message += "Subject: " + subject + "\r\n\r\n";
    if (format == EmailSender::PLAIN_TEXT)
        message += "Content-Type: text/plain; charset=\"utf-8\"";
    else
        message += "Content-Type: text/html; charset=\"utf-8\"";
    if (priority != EmailSender::DO_NOT_SET_PRIORITY)
        message += "X-Priority: " + std::to_string(priority) + "\r\n\r\n";
    message += GetDotStuffedMessage(message_body) + ".\r\n";

    return message;
}

bool PerformHeloExchange(const int socket_fd, const TimeLimit &time_limit, SslConnection * const ssl_connection) {
    if (unlikely(SocketUtil::TimedWrite(socket_fd, time_limit, "HELO " + DnsUtil::GetHostname() + "\r\n",
                                        ssl_connection) == -1))
        throw std::runtime_error("in PerformHeloExchange(EmailSender.cc) SocketUtil::TimedWrite failed! ("
                                 + std::string(strerror(errno)) + "))");

    // Read the response:
    ssize_t response_size;
    char buf[1000];
    if ((response_size = SocketUtil::TimedRead(socket_fd, time_limit, buf, sizeof(buf), ssl_connection)) <= 0) {
        Warning("in PerformHeloExchange(EmailSender.cc): Can't read SMTP server's response to HELO!");
        return false;
    }
    buf[std::min(static_cast<size_t>(response_size), sizeof(buf) - 1)] = NUL;
    // Expect a 2xx success code:
    if (not StringUtil::Match("2[0-9][0-9]*", buf)) {
        Warning("in PerformHeloExchange(SendEmail.cc): Bad status code in response to \"HELO\" command: "
                + std::string(buf));
        return false;
    }

    return true;
}

bool PerformMailFromExchange(const int socket_fd, const std::string &sender_email_address,
                             const TimeLimit &time_limit, SslConnection * const ssl_connection)
{
    if (unlikely(SocketUtil::TimedWrite(socket_fd, time_limit, "MAIL FROM:<" + sender_email_address + "\r\n",
                                        ssl_connection) == -1))
        throw std::runtime_error("in PerformMailFromExchange(EmailSender.cc) SocketUtil::TimedWrite failed! ("
                                 + std::string(strerror(errno)) + "))");

    // Read the response:
    ssize_t response_size;
    char buf[1000];
    if ((response_size = SocketUtil::TimedRead(socket_fd, time_limit, buf, sizeof(buf), ssl_connection)) <= 0) {
        Warning("in PerformMailFromExchange(EmailSender.cc): Can't read SMTP server's response to MAIL FROM:!");
        return false;
    }
    buf[std::min(static_cast<size_t>(response_size), sizeof(buf) - 1)] = NUL;
    // Expect a 2xx success code:
    if (not StringUtil::Match("2[0-9][0-9]*", buf)) {
        Warning("in PerformMailFromExchange(EmailSender.cc): Bad status code in response to \"MAIL FROM:\" command: "
                + std::string(buf));
        return false;
    }

    return true;
}

bool PerformReceipientToExchange(const int socket_fd, const std::string &receiver_email_address,
                                 const TimeLimit &time_limit, SslConnection * const ssl_connection)
{
    if (unlikely(SocketUtil::TimedWrite(socket_fd, time_limit, "MAIL FROM:<" + receiver_email_address + "\r\n",
                                        ssl_connection) == -1))
        throw std::runtime_error("in PerformReceipientToExchange(EmailSender.cc) SocketUtil::TimedWrite failed! ("
                                 + std::string(strerror(errno)) + "))");

    // Read the response:
    ssize_t response_size;
    char buf[1000];
    if ((response_size = SocketUtil::TimedRead(socket_fd, time_limit, buf, sizeof(buf), ssl_connection)) <= 0) {
        Warning("in PerformReceipientToExchange(EmailSender.cc): Can't read SMTP server's response to MAIL FROM:!");
        return false;
    }
    buf[std::min(static_cast<size_t>(response_size), sizeof(buf) - 1)] = NUL;
    // Expect a 2xx success code:
    if (not StringUtil::Match("2[0-9][0-9]*", buf)) {
        Warning("in PerformReceipientToExchange(EmailSender.cc): Bad status code in response to \"RCPT TO:\" "
                "command: " + std::string(buf));
        return false;
    }

    return true;
}

bool ProcessSendEmailExchange(const int socket_fd, const EmailSender::Priority priority,
                              const EmailSender::Format format, const std::string &sender,
                              const std::string &recipient, const std::string &subject,
                              const std::string &message_body, const TimeLimit &time_limit,
                              SslConnection * const ssl_connection)
{
    if (unlikely(SocketUtil::TimedWrite(socket_fd, time_limit, "DATA\r\n", ssl_connection) == -1))
        throw std::runtime_error("in ProcessSendEmailExchange(EmailSender.cc) SocketUtil::TimedWrite failed! ("
                                 + std::string(strerror(errno)) + ")) (1)");

    char buf[1000];
    ssize_t response_size;
    if ((response_size = SocketUtil::TimedRead(socket_fd, time_limit, buf, sizeof(buf))) <= 0) { // read the response
        Warning("in EmailSender::SendEmail: Can't read SMTP server's response to DATA!");
        return false;
    }
    buf[std::min(static_cast<size_t>(response_size), sizeof(buf) - 1)] = NUL;
    // Expect a 3xx code:
    if (not StringUtil::Match("3[0-9][0-9]*", buf)) {
        Warning("in EmailSender::SendEmail: Bad status code in response to \"DATA\" command: " + std::string(buf));
        return false;
    }

    if (unlikely(SocketUtil::TimedWrite(socket_fd, time_limit,
             CreateEmailMessage(priority, format, sender, recipient, subject, message_body) + "\r\n.\r\n",
                                        ssl_connection) == -1))
        throw std::runtime_error("in ProcessSendEmailExchange(EmailSender.cc): SocketUtil::TimedWrite failed! ("
                                 + std::string(strerror(errno)) + ")) (2)");
    if ((response_size = SocketUtil::TimedRead(socket_fd, time_limit, buf, sizeof(buf), ssl_connection)) <= 0) {
        Warning("in ProcessSendEmailExchange(EmailSender.cc): Can't read SMTP server's response to sent data!");
        return false;
    }
    buf[std::min(static_cast<size_t>(response_size), sizeof(buf) - 1)] = NUL;
    // Expect a 2xx success code:
    if (not StringUtil::Match("2[0-9][0-9]*", buf)) {
        Warning("in ProcessSendEmailExchange(EmailSender.cc): Bad status code in response to sent data: "
                + std::string(buf));
        return false;
    }

    return true;
}

bool PerformQuitExchange(const int socket_fd, const TimeLimit &time_limit, SslConnection * const ssl_connection) {
    if (unlikely(SocketUtil::TimedWrite(socket_fd, time_limit, "QUIT\r\n",
                                        ssl_connection) == -1))
        throw std::runtime_error("in PerformQuitExchange(EmailSender.cc) SocketUtil::TimedWrite failed! ("
                                 + std::string(strerror(errno)) + "))");

    // Read the response:
    ssize_t response_size;
    char buf[1000];
    if ((response_size = SocketUtil::TimedRead(socket_fd, time_limit, buf, sizeof(buf), ssl_connection)) <= 0) {
        Warning("in PerformQuitExchange(EmailSender.cc): Can't read SMTP server's response to QUIT!");
        return false;
    }
    buf[std::min(static_cast<size_t>(response_size), sizeof(buf) - 1)] = NUL;
    // Expect a 2xx success code:
    if (not StringUtil::Match("2[0-9][0-9]*", buf)) {
        Warning("in PerformQuitExchange(SendEmail.cc): Bad status code in response to \"QUIT\" command: "
                + std::string(buf));
        return false;
    }

    return true;
}

} // unnamed namespace

namespace EmailSender {

bool SendEmail(const std::string &sender, const std::string &recipient, const std::string &subject,
               const std::string &message_body, const Priority priority, const Format format,
               const std::string &reply_to, const bool use_ssl)
{
    if (unlikely(sender.empty() and reply_to.empty()))
        Error("in EmailSender::SendEmail: both \"sender\" and \"reply_to\" can't be empty!");

    const TimeLimit time_limit(10000 /* ms */);

    const bool log(not MiscUtil::SafeGetEnv("ENABLE_SMPT_CLIENT_LOGGING").empty());

    // Open connection:
    const unsigned short PORT(use_ssl ? 587 : 25);
    std::string error_message;
    const FileDescriptor socket_fd(
        SocketUtil::TcpConnect(GetSmtpServer(), PORT, time_limit, &error_message, SocketUtil::DISABLE_NAGLE));
    if (socket_fd == -1) {
        Warning("in EmailSender::SendEmail: can't connect to SMTP server \"" + GetSmtpServer() + ":"
                + std::to_string(PORT) + " (" + error_message + ")!");
        return false;
    }

    std::unique_ptr<SslConnection> ssl_connection;
    if (use_ssl)
        ssl_connection.reset(new SslConnection(socket_fd));

    // Read the welcome message:
    char buf[1000];
    ssize_t response_size;
    if ((response_size = SocketUtil::TimedRead(socket_fd, time_limit, buf, sizeof(buf), ssl_connection.get())) <= 0) {
        Warning("in EmailSender::SendEmail: Can't read SMTP server's welcome message!");
        return false;
    }
    if (log) {
        buf[std::min(static_cast<size_t>(response_size), sizeof(buf) - 1)] = NUL;
        std::clog << "Sever sent: " << buf << '\n';
    }

    if (not PerformHeloExchange(socket_fd, time_limit, ssl_connection.get()))
        return false;

    if (not PerformMailFromExchange(socket_fd, sender, time_limit, ssl_connection.get()))
        return false;

    // Send email to each recipient:
    const std::list<std::string> receiver_email_address_list{ recipient };
    for (const auto &receiver_email_address : receiver_email_address_list) {
        if (not PerformReceipientToExchange(socket_fd, receiver_email_address, time_limit, ssl_connection.get()))
            return false;
    }

    if (not ProcessSendEmailExchange(socket_fd, priority, format, sender, recipient, subject, message_body,
                                     time_limit, ssl_connection.get()))
        return false;

    if (not PerformQuitExchange(socket_fd, time_limit, ssl_connection.get()))
        return false;

    return true;
}

} // namespace EmailSender
<|endoftext|>