File size: 6,954 Bytes
2cf2375
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
#pragma once
#include <vector>
#include <string>
#include <iostream>
#include <filesystem>
#include <fstream>
#include <mutex>
#include <atomic>
#include "StateIndex.h"
#include "Compressor.h"

namespace Bestemshe {

enum class GameValue : uint8_t {
    UNKNOWN = 0,
    WIN = 1,
    LOSS = 2,
    DRAW = 3
};

class InferenceEngine {
private:
    struct RawLayerCacheEntry {
        std::vector<uint8_t> win_bits;
        std::vector<uint8_t> draw_bits;
        std::atomic<int> loaded{0};

        RawLayerCacheEntry() = default;
        RawLayerCacheEntry(RawLayerCacheEntry&& other) noexcept {
            win_bits = std::move(other.win_bits);
            draw_bits = std::move(other.draw_bits);
            loaded.store(other.loaded.load(std::memory_order_relaxed), std::memory_order_relaxed);
        }
        RawLayerCacheEntry& operator=(RawLayerCacheEntry&& other) noexcept {
            if (this != &other) {
                win_bits = std::move(other.win_bits);
                draw_bits = std::move(other.draw_bits);
                loaded.store(other.loaded.load(std::memory_order_relaxed), std::memory_order_relaxed);
            }
            return *this;
        }

        RawLayerCacheEntry(const RawLayerCacheEntry&) = delete;
        RawLayerCacheEntry& operator=(const RawLayerCacheEntry&) = delete;
    };

    // THE BREAKTHROUGH: Flat 2D Array Cache. No std::string, no std::unordered_map.
    // M ranges from 0 to 48. K2 ranges from 0 to 24.
    // We size to [50][26] to safely cover bounds without math operations.
    RawLayerCacheEntry fast_cache[50][26];
    std::mutex cache_mutex;

    // Hardcoded ZSTD configuration
    static constexpr size_t ZSTD_BLOCK_SIZE_BITS = 33554432; 

public:
    InferenceEngine() = default;

    // Evicts all preloaded data from memory
    void clear_cache() {
        std::lock_guard<std::mutex> lock(cache_mutex);
        for (int m = 0; m < 50; ++m) {
            for (int k2 = 0; k2 < 26; ++k2) {
                fast_cache[m][k2].win_bits.clear();
                fast_cache[m][k2].win_bits.shrink_to_fit();
                fast_cache[m][k2].draw_bits.clear();
                fast_cache[m][k2].draw_bits.shrink_to_fit();
                fast_cache[m][k2].loaded.store(0, std::memory_order_relaxed);
            }
        }
    }

    // Preloads ONLY the higher micro-layers strictly reachable from pair (K1, K2).
    bool preload_pair(uint16_t K1, uint16_t K2, uint8_t layer_M) {
        clear_cache(); 

        std::cout << "[INFO] InferenceEngine: Preloading selective reach set for Pair ("
                  << K1 << "," << K2 << ") in Layer M=" << static_cast<int>(layer_M) << "...\n";

        bool ok = true;
        // 1. (K2, j) where j is even and K1 < j <= 24
        for (int j = K1 + 2; j <= 24; j += 2) {
            uint8_t M_next = K2 + j;
            if (M_next <= 48 && !preload_uncompressed_layer(M_next, static_cast<uint8_t>(j))) ok = false;
        }

        // 2. (K1, j) where j is even and K2 < j <= 24
        for (int j = K2 + 2; j <= 24; j += 2) {
            uint8_t M_next = K1 + j;
            if (M_next <= 48 && !preload_uncompressed_layer(M_next, static_cast<uint8_t>(j))) ok = false;
        }
        return ok;
    }

    // High-performance $O(1)$ single-state query. Zero heap allocations.
    inline GameValue query_state(uint8_t M, uint8_t k2, uint64_t state_index) {
        if (k2 > M) return GameValue::UNKNOWN;
        
        const auto& entry = fast_cache[M][k2];
        if (entry.loaded.load(std::memory_order_acquire) == 0) {
            // Lazy load fallback (should rarely hit during active sweep if preloaded correctly)
            if (!preload_uncompressed_layer(M, k2)) return GameValue::UNKNOWN;
        }

        int R = 50 - static_cast<int>(M);
        uint64_t b_count = StateIndex::nCr(R + 9, 9);
        uint64_t local_idx = state_index % b_count;

        if (extract_bit(entry.draw_bits, local_idx)) return GameValue::DRAW;
        if (extract_bit(entry.win_bits, local_idx)) return GameValue::WIN;
        return GameValue::LOSS;
    }

private:
    bool preload_uncompressed_layer(uint8_t M, uint8_t k2) {
        // Fast path: Check without lock
        if (fast_cache[M][k2].loaded.load(std::memory_order_acquire) == 1) return true;

        std::lock_guard<std::mutex> lock(cache_mutex);
        // Double check with lock
        if (fast_cache[M][k2].loaded.load(std::memory_order_relaxed) == 1) return true;

        uint16_t k1 = static_cast<uint16_t>(M) - static_cast<uint16_t>(k2);
        
        std::string raw_win = "layers/layer_" + std::to_string(k1) + "_" + std::to_string(k2) + "_win.raw";
        std::string raw_draw = "layers/layer_" + std::to_string(k1) + "_" + std::to_string(k2) + "_draw.raw";
        std::string comp_win = "layers/compressed/layer_" + std::to_string(k1) + "_" + std::to_string(k2) + "_win.bin";
        std::string comp_draw = "layers/compressed/layer_" + std::to_string(k1) + "_" + std::to_string(k2) + "_draw.bin";

        std::vector<uint8_t> win_bits;
        std::vector<uint8_t> draw_bits;

        // Try RAW files first (if we are actively generating/verifying)
        if (std::filesystem::exists(raw_win) && std::filesystem::exists(raw_draw)) {
            auto read_raw = [](const std::string& path) -> std::vector<uint8_t> {
                std::ifstream f(path, std::ios::binary | std::ios::ate);
                if (!f) return {};
                size_t sz = f.tellg();
                f.seekg(0);
                std::vector<uint8_t> data(sz);
                f.read(reinterpret_cast<char*>(data.data()), sz);
                return data;
            };
            win_bits = read_raw(raw_win);
            draw_bits = read_raw(raw_draw);
        } 
        // Fallback to ZSTD COMPRESSED files
        else if (std::filesystem::exists(comp_win) && std::filesystem::exists(comp_draw)) {
            int R = 50 - M;
            uint64_t b_count = StateIndex::nCr(R + 9, 9);
            uint64_t expected_bytes = (b_count + 7) / 8;

            win_bits = Compressor::DecompressMicroLayer(comp_win, ZSTD_BLOCK_SIZE_BITS / 8, expected_bytes);
            draw_bits = Compressor::DecompressMicroLayer(comp_draw, ZSTD_BLOCK_SIZE_BITS / 8, expected_bytes);
        } else {
            std::cerr << "[FATAL] Dependencies missing for Pair (" << k1 << "," << (int)k2 
                      << ") in M=" << (int)M << ". Ensure .raw or .bin files exist.\n";
            return false;
        }

        if (win_bits.empty() || draw_bits.empty()) return false;

        fast_cache[M][k2].win_bits = std::move(win_bits);
        fast_cache[M][k2].draw_bits = std::move(draw_bits);
        fast_cache[M][k2].loaded.store(1, std::memory_order_release);
        return true;
    }

    inline bool extract_bit(const std::vector<uint8_t>& block_data, size_t offset) const {
        return (block_data[offset / 8] >> (offset % 8)) & 1;
    }
};

} // namespace Bestemshe