384 lines
13 KiB
HTML
384 lines
13 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>DETI Coin Miner - WebAssembly</title>
|
|
<style>
|
|
body {
|
|
font-family: monospace;
|
|
padding: 20px;
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
}
|
|
button {
|
|
padding: 10px 20px;
|
|
margin: 5px;
|
|
font-size: 16px;
|
|
cursor: pointer;
|
|
}
|
|
button:disabled {
|
|
opacity: 0.5;
|
|
cursor: not-allowed;
|
|
}
|
|
#stats {
|
|
margin-top: 20px;
|
|
padding: 10px;
|
|
background: #f0f0f0;
|
|
border-radius: 5px;
|
|
box-sizing: border-box;
|
|
}
|
|
#coins-container {
|
|
margin-top: 20px;
|
|
}
|
|
#coins {
|
|
width: 100%;
|
|
height: 300px;
|
|
padding: 10px;
|
|
background: #f9f9f9;
|
|
border: 2px solid #ccc;
|
|
border-radius: 5px;
|
|
font-family: monospace;
|
|
font-size: 12px;
|
|
overflow-y: auto;
|
|
white-space: pre-wrap;
|
|
word-wrap: break-word;
|
|
box-sizing: border-box;
|
|
}
|
|
.control-group {
|
|
margin: 10px 0;
|
|
}
|
|
label {
|
|
display: inline-block;
|
|
width: 200px;
|
|
}
|
|
h2 {
|
|
margin-top: 20px;
|
|
margin-bottom: 10px;
|
|
}
|
|
.coin-entry {
|
|
color: #006400;
|
|
font-weight: bold;
|
|
}
|
|
.coin-data {
|
|
color: #000080;
|
|
}
|
|
.simd-toggle {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
}
|
|
.toggle-switch {
|
|
position: relative;
|
|
display: inline-block;
|
|
width: 60px;
|
|
height: 34px;
|
|
}
|
|
.toggle-switch input {
|
|
opacity: 0;
|
|
width: 0;
|
|
height: 0;
|
|
}
|
|
.slider {
|
|
position: absolute;
|
|
cursor: pointer;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
background-color: #ccc;
|
|
transition: .4s;
|
|
border-radius: 34px;
|
|
}
|
|
.slider:before {
|
|
position: absolute;
|
|
content: "";
|
|
height: 26px;
|
|
width: 26px;
|
|
left: 4px;
|
|
bottom: 4px;
|
|
background-color: white;
|
|
transition: .4s;
|
|
border-radius: 50%;
|
|
}
|
|
input:checked + .slider {
|
|
background-color: #2196F3;
|
|
}
|
|
input:checked + .slider:before {
|
|
transform: translateX(26px);
|
|
}
|
|
input:disabled + .slider {
|
|
background-color: #ddd;
|
|
cursor: not-allowed;
|
|
}
|
|
.simd-status {
|
|
font-weight: bold;
|
|
}
|
|
.simd-available {
|
|
color: #28a745;
|
|
}
|
|
.simd-unavailable {
|
|
color: #dc3545;
|
|
}
|
|
.button-group {
|
|
margin: 15px 0;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h1>DETI Coin Miner (WebAssembly)</h1>
|
|
|
|
<div class="control-group simd-toggle">
|
|
<label>Enable SIMD:</label>
|
|
<label class="toggle-switch">
|
|
<input type="checkbox" id="simdToggle">
|
|
<span class="slider"></span>
|
|
</label>
|
|
<span id="simdStatus" class="simd-status"></span>
|
|
</div>
|
|
|
|
<div class="control-group">
|
|
<label>Iterations per batch:</label>
|
|
<input type="number" id="batchSize" value="1000000" step="100000" min="100000">
|
|
</div>
|
|
|
|
<div class="control-group">
|
|
<label>Update interval (ms):</label>
|
|
<input type="number" id="updateInterval" value="100" step="50" min="50">
|
|
</div>
|
|
|
|
<div class="button-group">
|
|
<button id="start">Start Mining</button>
|
|
<button id="stop">Stop Mining</button>
|
|
<button id="reset">Reset</button>
|
|
<button id="clearCoins">Clear Coins Display</button>
|
|
</div>
|
|
|
|
<div id="stats">
|
|
Waiting to start...
|
|
</div>
|
|
|
|
<div id="coins-container">
|
|
<h2>Found Coins (<span id="coin-count">0</span>)</h2>
|
|
<div id="coins"></div>
|
|
</div>
|
|
|
|
<script src="coin_miner_wasm.js"></script>
|
|
<script>
|
|
let mining = false;
|
|
let Module;
|
|
let miningInterval;
|
|
let updateInterval;
|
|
let lastDisplayedCoinCount = 0;
|
|
let pausedStats = false;
|
|
let simdAvailable = false;
|
|
|
|
CoinMinerModule().then(mod => {
|
|
Module = mod;
|
|
console.log('WebAssembly module loaded');
|
|
|
|
// Check if SIMD is available
|
|
simdAvailable = Module._is_simd_available();
|
|
const simdToggle = document.getElementById('simdToggle');
|
|
const simdStatus = document.getElementById('simdStatus');
|
|
|
|
if (simdAvailable) {
|
|
simdStatus.textContent = 'SIMD Available';
|
|
simdStatus.className = 'simd-status simd-available';
|
|
simdToggle.disabled = false;
|
|
|
|
// SYNC LOGIC: Read C state first to match backend
|
|
const cState = Module._is_simd_enabled();
|
|
simdToggle.checked = (cState === 1);
|
|
|
|
// Force sync again just to be safe
|
|
Module._set_simd_enabled(simdToggle.checked ? 1 : 0);
|
|
console.log(`JS: Initialized - SIMD Available, Toggle set to ${simdToggle.checked}`);
|
|
} else {
|
|
simdStatus.textContent = 'SIMD Not Available';
|
|
simdStatus.className = 'simd-status simd-unavailable';
|
|
simdToggle.disabled = true;
|
|
simdToggle.checked = false;
|
|
Module._set_simd_enabled(0);
|
|
console.log('JS: Initialized - SIMD Not Available');
|
|
}
|
|
|
|
// SIMD toggle handler
|
|
simdToggle.onchange = () => {
|
|
if (simdAvailable) {
|
|
const newState = simdToggle.checked ? 1 : 0;
|
|
console.log(`JS: User toggled SIMD to ${newState}`);
|
|
|
|
// Call the C function
|
|
Module._set_simd_enabled(newState);
|
|
|
|
// Update status display
|
|
const currentMode = simdToggle.checked ? 'SIMD Mode' : 'Scalar Mode';
|
|
if (!mining) {
|
|
document.getElementById('stats').innerHTML =
|
|
`${currentMode} selected. Click Start to begin mining.`;
|
|
}
|
|
}
|
|
};
|
|
|
|
document.getElementById('start').onclick = () => {
|
|
if (!mining) {
|
|
mining = true;
|
|
Module._resume_mining();
|
|
pausedStats = false;
|
|
console.log('Starting mining...');
|
|
startMining();
|
|
}
|
|
};
|
|
|
|
document.getElementById('stop').onclick = () => {
|
|
mining = false;
|
|
Module._stop_mining();
|
|
clearInterval(miningInterval);
|
|
clearInterval(updateInterval);
|
|
|
|
updateStats();
|
|
|
|
let currentHTML = document.getElementById('stats').innerHTML;
|
|
document.getElementById('stats').innerHTML =
|
|
currentHTML.replace('Mining Statistics:', 'Mining Statistics (PAUSED):');
|
|
|
|
pausedStats = true;
|
|
console.log('Mining stopped');
|
|
};
|
|
|
|
document.getElementById('reset').onclick = () => {
|
|
Module._reset_mining();
|
|
mining = false;
|
|
pausedStats = false;
|
|
clearInterval(miningInterval);
|
|
clearInterval(updateInterval);
|
|
lastDisplayedCoinCount = 0;
|
|
|
|
const mode = simdToggle.checked ? 'SIMD' : 'Scalar';
|
|
document.getElementById('stats').innerHTML =
|
|
`Reset complete. Using ${mode} mode. Click Start to begin.`;
|
|
document.getElementById('coins').innerHTML = '';
|
|
document.getElementById('coin-count').textContent = '0';
|
|
console.log('Mining reset');
|
|
};
|
|
|
|
document.getElementById('clearCoins').onclick = () => {
|
|
document.getElementById('coins').innerHTML = '';
|
|
console.log('Coins display cleared');
|
|
};
|
|
|
|
function coinToString(coinPtr) {
|
|
let coinStr = '';
|
|
for (let i = 0; i < 55; i++) {
|
|
const byteIdx = i ^ 3;
|
|
const wordIdx = Math.floor(byteIdx / 4);
|
|
const byteInWord = byteIdx % 4;
|
|
|
|
const word = Module.getValue(coinPtr + wordIdx * 4, 'i32');
|
|
const byte = (word >> (byteInWord * 8)) & 0xFF;
|
|
|
|
if (byte >= 32 && byte <= 126) {
|
|
coinStr += String.fromCharCode(byte);
|
|
} else if (byte === 10) {
|
|
coinStr += '\\n';
|
|
} else if (byte === 0x80) {
|
|
coinStr += '[0x80]';
|
|
} else {
|
|
coinStr += `[0x${byte.toString(16).padStart(2, '0')}]`;
|
|
}
|
|
}
|
|
return coinStr;
|
|
}
|
|
|
|
function displayNewCoins() {
|
|
const totalCoins = Module._get_found_coins_count();
|
|
|
|
if (totalCoins > lastDisplayedCoinCount) {
|
|
const coinsDiv = document.getElementById('coins');
|
|
|
|
for (let i = lastDisplayedCoinCount; i < totalCoins; i++) {
|
|
const coinPtr = Module._get_found_coin(i);
|
|
if (coinPtr !== 0) {
|
|
const coinStr = coinToString(coinPtr);
|
|
const timestamp = new Date().toLocaleTimeString();
|
|
|
|
const entry = document.createElement('div');
|
|
entry.innerHTML =
|
|
`<span class="coin-entry">[${timestamp}] Coin #${i + 1}:</span> <span class="coin-data">${coinStr}</span>`;
|
|
coinsDiv.appendChild(entry);
|
|
|
|
coinsDiv.scrollTop = coinsDiv.scrollHeight;
|
|
}
|
|
}
|
|
|
|
lastDisplayedCoinCount = totalCoins;
|
|
document.getElementById('coin-count').textContent = totalCoins;
|
|
}
|
|
}
|
|
|
|
function updateStats() {
|
|
const attemptsPtr = Module._malloc(8);
|
|
const coinsPtr = Module._malloc(4);
|
|
const hashRatePtr = Module._malloc(8);
|
|
const elapsedPtr = Module._malloc(8);
|
|
|
|
Module.setValue(attemptsPtr, 0, 'i32');
|
|
Module.setValue(attemptsPtr + 4, 0, 'i32');
|
|
|
|
Module._get_statistics(attemptsPtr, coinsPtr, hashRatePtr, elapsedPtr);
|
|
|
|
const attemptsLowUnsigned = Module.getValue(attemptsPtr, 'i32') >>> 0;
|
|
const attemptsHighUnsigned = Module.getValue(attemptsPtr + 4, 'i32') >>> 0;
|
|
|
|
let attempts;
|
|
if (attemptsHighUnsigned === 0) {
|
|
attempts = attemptsLowUnsigned;
|
|
} else {
|
|
const low = BigInt(attemptsLowUnsigned);
|
|
const high = BigInt(attemptsHighUnsigned);
|
|
attempts = (high * BigInt(4294967296)) + low;
|
|
}
|
|
|
|
const coins = Module.getValue(coinsPtr, 'i32');
|
|
const hashRate = Module.getValue(hashRatePtr, 'double');
|
|
const elapsed = Module.getValue(elapsedPtr, 'double');
|
|
|
|
Module._free(attemptsPtr);
|
|
Module._free(coinsPtr);
|
|
Module._free(hashRatePtr);
|
|
Module._free(elapsedPtr);
|
|
|
|
const mode = Module._is_simd_enabled() ? 'SIMD' : 'Scalar';
|
|
|
|
document.getElementById('stats').innerHTML = `
|
|
<strong>Mining Statistics (${mode} Mode):</strong><br>
|
|
Attempts: ${attempts.toString()}<br>
|
|
Coins Found: ${coins}<br>
|
|
Hash Rate: ${(hashRate / 1e6).toFixed(2)} MH/s<br>
|
|
Elapsed Time: ${elapsed.toFixed(2)} seconds
|
|
`;
|
|
|
|
displayNewCoins();
|
|
}
|
|
|
|
function startMining() {
|
|
const batchSize = parseInt(document.getElementById('batchSize').value);
|
|
const updateMs = parseInt(document.getElementById('updateInterval').value);
|
|
|
|
miningInterval = setInterval(() => {
|
|
if (!mining) {
|
|
clearInterval(miningInterval);
|
|
return;
|
|
}
|
|
Module._mine_coins_wasm(batchSize);
|
|
}, 0);
|
|
|
|
updateInterval = setInterval(updateStats, updateMs);
|
|
}
|
|
}).catch(err => {
|
|
console.error('Failed to load WebAssembly module:', err);
|
|
document.getElementById('stats').innerHTML = 'Error loading module: ' + err.message;
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|