874b6f20c2
Основа — Horizon (MIT). История апстрима не переносится: этот репозиторий ведёт свою.
133 lines
5.6 KiB
Python
133 lines
5.6 KiB
Python
#!/usr/bin/env python3
|
|
"""Compare the order block entities tick in with vanilla's `Level.blockEntityTickers`.
|
|
|
|
Random scenarios load chunks with block entities in a save order (enough in
|
|
one chunk to make its block entity map grow), place, replace and remove block
|
|
entities, give jukeboxes a record and take it, and unload chunks, with ticks
|
|
in between. `VanillaBlockEntityTickerOracle` runs them on real `LevelChunk`s
|
|
of a server level and prints, after each tick, the positions of the live
|
|
tickers in list order; `examples/probe/block_entity_tickers_fixture` runs the same
|
|
on `horizon_client::block_entities::BlockEntities`. The block entities are
|
|
the kinds the game ticks in that list (hoppers, furnaces, beacons, spawners,
|
|
trial spawners, vaults, hives, crafters, brewing stands, jukebox), with chests and barrels,
|
|
which have no ticker, among them.
|
|
"""
|
|
import argparse
|
|
import os
|
|
import random
|
|
import subprocess
|
|
import sys
|
|
try:
|
|
from . import lab, test_rust_parity as base
|
|
except ImportError:
|
|
import lab
|
|
import test_rust_parity as base
|
|
|
|
PARITY = lab.ROOT / 'mods/horizon/src/test/java/dev/horizonlab/parity'
|
|
TICKING = ['hopper', 'furnace', 'blast_furnace', 'smoker', 'beacon', 'spawner', 'trial_spawner', 'vault',
|
|
'beehive', 'bee_nest', 'crafter', 'brewing_stand', 'jukebox_record']
|
|
KINDS = TICKING + ['jukebox', 'chest', 'barrel']
|
|
|
|
|
|
def scenario(rng):
|
|
"""One scenario's lines: a few chunks near the origin and the ops on them."""
|
|
lines = ['reset']
|
|
loaded, blocks = set(), {}
|
|
|
|
def free(cx, cz):
|
|
while True:
|
|
p = (cx * 16 + rng.randrange(16), rng.randrange(-64, 320), cz * 16 + rng.randrange(16))
|
|
if p not in blocks:
|
|
return p
|
|
|
|
def load():
|
|
options = [(x, z) for x in range(-2, 3) for z in range(-2, 3) if (x, z) not in loaded]
|
|
if not options:
|
|
return
|
|
cx, cz = rng.choice(options)
|
|
loaded.add((cx, cz))
|
|
words = ['load', str(cx), str(cz)]
|
|
for _ in range(rng.choice([0, 1, 3, 8, 20, 30, 60])):
|
|
p, kind = free(cx, cz), rng.choice(KINDS)
|
|
blocks[p] = kind
|
|
words += [kind, *map(str, p)]
|
|
lines.append(' '.join(words))
|
|
for _ in range(rng.randrange(1, 4)):
|
|
load()
|
|
lines.append('tick')
|
|
for _ in range(rng.randrange(10, 60)):
|
|
op = rng.random()
|
|
mine = sorted(blocks)
|
|
if op < 0.25 and loaded:
|
|
cx, cz = rng.choice(sorted(loaded))
|
|
p, kind = free(cx, cz), rng.choice(KINDS)
|
|
blocks[p] = kind
|
|
lines.append(f'place {kind} {p[0]} {p[1]} {p[2]}')
|
|
elif op < 0.4 and mine:
|
|
p = rng.choice(mine)
|
|
lines.append(f'replace {p[0]} {p[1]} {p[2]}')
|
|
elif op < 0.6 and mine:
|
|
p = rng.choice(mine)
|
|
del blocks[p]
|
|
lines.append(f'remove {p[0]} {p[1]} {p[2]}')
|
|
elif op < 0.72:
|
|
jukeboxes = [p for p in mine if blocks[p].startswith('jukebox')]
|
|
if jukeboxes:
|
|
p = rng.choice(jukeboxes)
|
|
record = blocks[p] == 'jukebox'
|
|
blocks[p] = 'jukebox_record' if record else 'jukebox'
|
|
lines.append(f'record {p[0]} {p[1]} {p[2]} {int(record)}')
|
|
elif op < 0.78 and loaded:
|
|
cx, cz = rng.choice(sorted(loaded))
|
|
loaded.discard((cx, cz))
|
|
for p in [p for p in blocks if (p[0] >> 4, p[2] >> 4) == (cx, cz)]:
|
|
del blocks[p]
|
|
lines.append(f'unload {cx} {cz}')
|
|
elif op < 0.85:
|
|
load()
|
|
else:
|
|
lines.append('tick')
|
|
lines.append('tick')
|
|
return lines
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument('--scenarios', type=int, default=3000)
|
|
parser.add_argument('--seed', type=int, default=0x7469636b)
|
|
args = parser.parse_args()
|
|
lock, jars, libraries = base.verify_artifacts()
|
|
build = lab.LOCAL / 'rust-block-entity-tickers-parity'
|
|
build.mkdir(parents=True, exist_ok=True)
|
|
rng = random.Random(args.seed)
|
|
lines = [line for _ in range(args.scenarios) for line in scenario(rng)]
|
|
inputs = build / 'inputs.txt'
|
|
inputs.write_text('\n'.join(lines) + '\n')
|
|
classes = build / 'classes'
|
|
classes.mkdir(exist_ok=True)
|
|
dependencies = os.pathsep.join(map(str, [jars['server'], *libraries]))
|
|
|
|
def run(command, name):
|
|
with (build / (name + '.log')).open('w') as log:
|
|
result = subprocess.run(list(map(str, command)), cwd=lab.ROOT, timeout=3600, stdout=log,
|
|
stderr=subprocess.STDOUT)
|
|
if result.returncode:
|
|
print((build / (name + '.log')).read_text()[-4000:])
|
|
sys.exit(f'{name} failed')
|
|
run(['javac', '--release', lock['minecraft']['java_major'], '-proc:none', '-nowarn', '-cp', dependencies,
|
|
'-d', classes, *(PARITY / f'{n}.java' for n in ('ParityData', 'VanillaBlockEntityTickerOracle'))],
|
|
'compile-java')
|
|
run(['java', '-Xmx1G', '-cp', str(classes) + os.pathsep + dependencies,
|
|
'dev.horizonlab.parity.VanillaBlockEntityTickerOracle', inputs, build / 'vanilla.txt'], 'vanilla')
|
|
run(['cargo', 'run', '--quiet', '--profile', 'fast', '-p', 'horizon-client', '--example',
|
|
'client_probe', '--', 'block_entity_tickers_fixture', inputs, build / 'rust.txt'], 'rust')
|
|
matched = base.compare(build / 'vanilla.txt', build / 'rust.txt')
|
|
ticks = sum(1 for line in lines if line == 'tick')
|
|
listed = sum(len(line.split()) - 1 for line in (build / 'vanilla.txt').read_text().splitlines())
|
|
print(f'block entity tickers: {args.scenarios} scenarios, {matched} of {ticks} ticks match vanilla '
|
|
f'({listed} tickers listed)')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|