RyanHub – file viewer
filename: src/world/format/chunk/Palette.h
branch: feature/world
back to repo
/*
 *
 *               _____  _                 _
 *              /  ___|| |               | |
 *              \ `--. | |_  _ __   __ _ | |_   ___   ___
 *               `--. \| __|| '__| / _` || __| / _ \ / __|
 *              /\__/ /| |_ | |   | (_| || |_ | (_) |\__ \
 *              \____/  \__||_|    \__,_| \__| \___/ |___/
 *
 *
 * This program 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 3 of the License, or
 * (at your option) any later version.
 *
 * Copyright (C) 2025 Armen Deroian
 *
 */

#ifndef PALETTE_H
#define PALETTE_H
#include "utils/collection/Iterable.h"
#include "utils/collection/PalettedStorage.h"
#include "utils/io/ByteBuffer.h"
#include "utils/MathUtils.h"
#include "utils/Predicate.h"
#include "block/BlockState.h"
#include "world/format/Biome.h"

#include <format>
#include <optional>
#include <stdexcept>

namespace stratos {
class ByteBuffer;

template <typename T> class Palette {
  public:
    virtual ~Palette()                             = default;
    virtual int  index(T value)                    = 0;
    virtual T    get(int index) const              = 0;
    virtual bool has(Predicate<T> predicate) const = 0;
    virtual int  size() const                      = 0;

    virtual void read(ByteBuffer& buffer)        = 0;
    virtual void write(ByteBuffer& buffer) const = 0;
    virtual int  writeSize() const               = 0;
};

template <typename T> class SingularPalette : public Palette<T> {
  public:
    SingularPalette(const utils::IndexedIterable<T>* idList, const std::vector<T>& entries);

    int  index(T value) override;
    T    get(int index) const override;
    bool has(Predicate<T> predicate) const override;
    int  size() const override;

    void read(ByteBuffer& buffer) override;
    void write(ByteBuffer& buffer) const override;
    int  writeSize() const override;

  private:
    const utils::IndexedIterable<T>* idList;
    std::optional<T>                 entry;
};

template <typename T> class ArrayPalette : public Palette<T> {
  public:
    ArrayPalette(const utils::IndexedIterable<T>* idList, int bits, const std::vector<T>& entries);

    int  index(T value) override;
    T    get(int index) const override;
    bool has(Predicate<T> predicate) const override;
    int  size() const override;

    void read(ByteBuffer& buffer) override;
    void write(ByteBuffer& buffer) const override;
    int  writeSize() const override;

  private:
    const utils::IndexedIterable<T>* idList;
    std::vector<T>                   entries;
    int                              indexBits;
    int                              _size;
};

template <typename T> class IdListPalette : public Palette<T> {
  public:
    explicit IdListPalette(const utils::IndexedIterable<T>* idList) : idList(idList) {};

    int  index(T value) override;
    T    get(int index) const override;
    bool has(Predicate<T> predicate) const override;
    int  size() const override;

    void read(ByteBuffer& buffer) override;
    void write(ByteBuffer& buffer) const override;
    int  writeSize() const override;

  private:
    const utils::IndexedIterable<T>* idList;
};

template <typename T> Palette<T>* createSingularPalette(int bits, const utils::IndexedIterable<T>* idList, const std::vector<T>& entries);
template <typename T> Palette<T>*    createArrayPalette(int bits, const utils::IndexedIterable<T>* idList, const std::vector<T>& entries);
template <typename T> Palette<T>*   createIdListPalette(int bits, const utils::IndexedIterable<T>* idList, const std::vector<T>& entries);

template <typename T> class PalettedContainer {
public:
    /**
     * Tuple container for data storage of a palette and its associated data.
     */
    template <typename U> struct Data {
        Palette<U>* palette;
        PalettedStorage* storage;
        void write(ByteBuffer& buffer) const;
    };

    template <typename U> struct DataProvider {
        Palette<U>* (*paletteFactory)(int, const utils::IndexedIterable<U>*, const std::vector<U>&);
        int bits;

        Data<U> createData(const utils::IndexedIterable<U>* idList, int size) const;
    };

    template <typename U> struct PaletteProvider {
        DataProvider<U> (*dataFactory)(int, const utils::IndexedIterable<U>*);
        int edgeBits; // Number of bits used for each edge of the chunk section (i.e., 4 for 16x16x16 sections)

        int getContainerSize() const;
        int computeIndex(int x, int y, int z) const;
    };

    PalettedContainer(const utils::IndexedIterable<T>* idList, PaletteProvider<T> provider, Data<T> data);
    PalettedContainer(const utils::IndexedIterable<T>* idList, T entry, PaletteProvider<T> provider);

    T get(int x, int y, int z) const;
    bool has(Predicate<T> predicate) const;
    void set(int x, int y, int z, T value);
    T swap(int x, int y, int z, T value);

    void read(ByteBuffer& buffer);
    void write(ByteBuffer& buffer) const;

    ~PalettedContainer();
private:
    const utils::IndexedIterable<T>* idList;
    PaletteProvider<T> provider;
    Data<T> data;

    T get(int index) const;
    void set(int index, T value);
    T swap(int index, T value);
    Data<T> createData(int bits) const;
};

template <typename T> Palette<T>* createSingularPalette(int bits, const utils::IndexedIterable<T>* idList, const std::vector<T>& entries) {
    return new SingularPalette<T>(idList, entries);
}
template <typename T> Palette<T>* createArrayPalette(int bits, const utils::IndexedIterable<T>* idList, const std::vector<T>& entries) {
    return new ArrayPalette<T>(idList, bits, entries);
}
template <typename T> Palette<T>* createIdListPalette(int bits, const utils::IndexedIterable<T>* idList, const std::vector<T>& entries) {
    return new IdListPalette<T>(idList);
}

template <typename T> SingularPalette<T>::SingularPalette(const utils::IndexedIterable<T>* idList, const std::vector<T>& entries) : idList(idList) {
    if (entries.size() > 0) {
        if (entries.size() != 1) throw std::invalid_argument(std::format("SingularPalette can only have one entry, got {}", entries.size()));
        entry = entries[0];
    } else {
        entry = std::nullopt;
    }
}

PalettedContainer<BlockState>::DataProvider<BlockState> blockStateDataFactory(int bits, const utils::IndexedIterable<BlockState>* idList);
PalettedContainer<Biome>::DataProvider<Biome> biomeDataFactory(int bits, const utils::IndexedIterable<Biome>* idList);

constexpr PalettedContainer<BlockState>::PaletteProvider BLOCK_STATE = { blockStateDataFactory, 4 };
constexpr PalettedContainer<Biome>::PaletteProvider BIOME = { biomeDataFactory, 2 };

template <typename T> int SingularPalette<T>::index(T value) {
    return entry.has_value() && *entry == value ? 0 : -1;
}
template <typename T> T SingularPalette<T>::get(int index) const {
    if (index != 0 || !entry.has_value()) throw std::out_of_range(std::format("Index '{}' out of range for SingularPalette", index));
    return *entry;
}
template <typename T> bool SingularPalette<T>::has(Predicate<T> predicate) const {
    return predicate(get(0));
}
template <typename T> int SingularPalette<T>::size() const {
    return 1;
}
template <typename T> void SingularPalette<T>::read(ByteBuffer& buffer) {
    entry = idList->getOrThrow(buffer.readVarInt());
}
template <typename T> void SingularPalette<T>::write(ByteBuffer& buffer) const {
    if (!entry.has_value()) throw std::runtime_error("Use of a uninitialized palette");
    buffer.writeVarInt(idList->getRawIndexOrThrow(*entry));
}
template <typename T> int SingularPalette<T>::writeSize() const {
    return getEncodedSizeInBytes(idList->getRawIndex(*entry));
}
template <typename T> ArrayPalette<T>::ArrayPalette(const utils::IndexedIterable<T>* idList, int bits, const std::vector<T>& entries) : idList(idList), indexBits(bits) {
    _size = entries.size();
    this->entries.reserve(_size);
    for (int i = 0; i < entries.size(); ++i)
        this->entries[i] = entries[i];
}
template <typename T> int ArrayPalette<T>::index(T value) {
    for (int i = 0; i < _size; ++i)
        if (entries[i] == value) return i;
    return -1;
}
template <typename T> T ArrayPalette<T>::get(int index) const {
    if (index < 0 || index >= _size) throw std::out_of_range(std::format("Index '{}' out of range for ArrayPalette", index));
    return entries[index];
}
template <typename T> bool ArrayPalette<T>::has(Predicate<T> predicate) const {
    for (const T& entry : entries)
        if (predicate(entry)) return true;
    return false;
}
template <typename T> int ArrayPalette<T>::size() const {
    return _size;
}
template <typename T> void ArrayPalette<T>::read(ByteBuffer& buffer) {
    _size = buffer.readVarInt();
    entries.resize(_size);
    for (int i = 0; i < _size; ++i) {
        entries[i] = idList->getOrThrow(buffer.readVarInt());
    }
}
template <typename T> void ArrayPalette<T>::write(ByteBuffer& buffer) const {
    buffer.writeVarInt(_size);
    for (int i = 0; i < _size; ++i) buffer.writeVarInt(idList->getRawIndex(entries[i]));
}
template <typename T> int ArrayPalette<T>::writeSize() const {
    int size = getEncodedSizeInBytes(_size);
    for (const T& entry : entries) {
        size += getEncodedSizeInBytes(idList->getRawIndex(entry));
    }
    return size;
}
template <typename T> int IdListPalette<T>::index(T value) {
    return idList->getRawIndex(value);
}
template <typename T> T IdListPalette<T>::get(int index) const {
    return idList->getOrThrow(index);
}
template <typename T> bool IdListPalette<T>::has(Predicate<T> predicate) const {
    return true;
}
template <typename T> int IdListPalette<T>::size() const {
    return idList->size();
}
template <typename T> void IdListPalette<T>::read(ByteBuffer& buffer) {
    // No read, uses global registries
}
template <typename T> void IdListPalette<T>::write(ByteBuffer& buffer) const {
    // No write, uses global registries
}
template <typename T> int IdListPalette<T>::writeSize() const {
    return 0;
}
template <typename T> template <typename U> void PalettedContainer<T>::Data<U>::write(ByteBuffer& buffer) const {
    buffer.writeByte(storage->getBitsPerEntry());
    palette->write(buffer);
    buffer.writeFixedLongArray(storage->getData());
}
template <typename T> template <typename U>
typename PalettedContainer<T>::template Data<U> PalettedContainer<T>::DataProvider<U>::createData(const utils::IndexedIterable<U>* idList, const int size) const {
    return { paletteFactory(bits, idList, std::vector<U>(0)), new PackedIntegerArray(bits, size) };
}
template <typename T> template <typename U> int PalettedContainer<T>::PaletteProvider<U>::getContainerSize() const {
    return 1 << edgeBits * 3;
}
template <typename T> template <typename U> int PalettedContainer<T>::PaletteProvider<U>::computeIndex(const int x, const int y, const int z) const {
    return (y << edgeBits | z) << edgeBits | x;
}
template <typename T> PalettedContainer<T>::PalettedContainer(const utils::IndexedIterable<T>* idList, PaletteProvider<T> provider, Data<T> data)
    : idList(idList), provider(std::move(provider)), data(data) {}
template <typename T> PalettedContainer<T>::PalettedContainer(const utils::IndexedIterable<T>* idList, T entry, PaletteProvider<T> provider) : idList(idList), provider(std::move(provider)) {
    data = createData(0);
    data.palette->index(entry);
}
template <typename T> T PalettedContainer<T>::get(const int x, const int y, const int z) const {
    return get(provider->computeIndex(x, y, z));
}
template <typename T> bool PalettedContainer<T>::has(Predicate<T> predicate) const {
    return data.palette->has(predicate);
}
template <typename T> void PalettedContainer<T>::set(const int x, const int y, const int z, T value) {
    set(provider->computeIndex(x, y, z), value);
}
template <typename T> T PalettedContainer<T>::swap(const int x, const int y, const int z, T value) {
    return swap(provider->computeIndex(x, y, z), value);
}
template <typename T> void PalettedContainer<T>::read(ByteBuffer& buffer) {
    data = createData(buffer.readByte());
    data.palette->read(buffer);
    buffer.readFixedLongArray(data.storage->getData());
}
template <typename T> void PalettedContainer<T>::write(ByteBuffer& buffer) const {
    data.write(buffer);
}
template <typename T> PalettedContainer<T>::~PalettedContainer() {
    delete data.palette;
    delete data.storage;
}
template <typename T> T PalettedContainer<T>::get(const int index) const {
    return data.palette->get(data.storage->get(index));
}
template <typename T> void PalettedContainer<T>::set(const int index, T value) {
    data.storage->set(index, data.palette->index(value));
}
template <typename T> T PalettedContainer<T>::swap(const int index, T value) {
    int i = data.storage->swap(index, data.palette->index(value));
    return data.palette->get(i);
}
template <typename T> typename PalettedContainer<T>::template Data<T> PalettedContainer<T>::createData(int bits) const {
    DataProvider<T> provider = data.palette->getDataProvider();
    return provider.createData(idList, provider.getContainerSize());
}
inline PalettedContainer<BlockState>::DataProvider<BlockState> blockStateDataFactory(const int bits, const utils::IndexedIterable<BlockState>* idList) {
    if (bits == 0) return PalettedContainer<BlockState>::DataProvider {createSingularPalette<BlockState>, bits};
    if (bits <= 8) return PalettedContainer<BlockState>::DataProvider {createArrayPalette<BlockState>, bits > 4 ? bits : 4};
    return PalettedContainer<BlockState>::DataProvider {createIdListPalette<BlockState>, utils::ceilLog2(idList->size())};
}
inline PalettedContainer<Biome>::DataProvider<Biome> biomeDataFactory(const int bits, const utils::IndexedIterable<Biome>* idList) {
    if (bits == 0) return PalettedContainer<Biome>::DataProvider {createSingularPalette<Biome>, bits};
    if (bits < 4) return PalettedContainer<Biome>::DataProvider {createArrayPalette<Biome>, bits};
    return PalettedContainer<Biome>::DataProvider {createIdListPalette<Biome>, utils::ceilLog2(idList->size())};
}
} // namespace stratos

#endif // PALETTE_H