- refactor(8.5.1): split render/engine/uniform/impl 28 classes into hand/sky/esp/post/ui/world(+liquid) leaves - refactor(8.5.1): split renderer/ui top 28 files into blur/batches(+ordered)/item/deferred(+sched)/pass/dispatch leaves; add tools/pkgsplit scripts (split_struct.py, promote.py) for reproducible waves - refactor(8.5.1): split render/engine/text 24 files into font(+faces/info)/glyph/msdf/render/command leaves; fiximports tool; disambiguate Font/GlyphAtlasManager shadowing - refactor(8.5.1): split nondraggable/impl into bars(+jump)/locator(+panel)/buttons/tooltips/dynamicisland/overlay; promote.py +8-indent & ctor support - refactor(8.5.1): nondraggable dynamicisland 5->4 files (IslandUiUtil into controls) - refactor(8.5.1): split features/gui/clickgui top 20 files into render/screen/editor/input/tabs/picker/font; ClickGuiModel records nested - refactor(8.5.1): picker 6->5+screen leaf - refactor(8.5.1): picker catalog leaf (picker=2: state+icon manager) - refactor(tools): improve pkgsplit import handling (fiximports v2) - refactor(8.5.1): split util/text 8 -> mask/format/edit leaves (≤4) - refactor(8.5.1): split util/network 5, util/player/effect 5, util/media 5 (+ shadow fix) - refactor(8.5.1): split util/pvp 13, util/projectile 13, util/block 8 - refactor(8.5.1): split util/wav 9, util/item 9, util/combat 9, util/player/inventory 9 - refactor(8.5.1): split render/helpers 16 + config/values 16 - refactor(8.5.1): split renderer/ui/draw 23 -> shape/box/corner/edge/paint/effect - refactor(8.5.1): split render/engine/command 17 -> core/shape/effect/texture/world - refactor(8.5.1): split renderer/ui/runtime/script 23 -> engine/runtime/module/registry/render/patch - refactor(8.5.1): split profiler 14 + hud 14 + addon 14 - refactor(8.5.1): split iris 13 + profile 13 + text/backend 12 - refactor(8.5.1): split config 12 + module 12 + cosmetic 11 - refactor(8.5.1): split rhi/pipeline 11 + mixininterface 11 + config/common/impl 11 - refactor(8.5.1): split util/player 10 + rhi/resource 10 + runtime/render 10 - refactor(8.5.1): split rhi 9 + svg 9 + command 8 - refactor(8.5.1): split core 9 + postprocess 9 + combat 9 - refactor(8.5.1): split command/impl 16 + modules/player 14 + modules/misc 12 - refactor(8.5.1): split ui/runtime 43 -> core/style/input/reconcile/asset - refactor(8.5.1): split session/microsoft 8 + relations 7 + animation 7 - refactor(8.5.1): split engine/world 7 + postprocess/graph 7 + api/v0/render 7 - refactor(8.5.1): split sodium 12 + aiming 21 - refactor(8.5.1): split хвост 13 папок 34 файла - fix(8.5.1): видимость членов и enum после расщепления пакетов - refactor(8.5.1): FailRotationProcessor -> processors root (impl 5 -> 4) - docs(8.5.1): TODO — волна 8.5.1 завершена (0 папок >4 файлов) - refactor(8.5.2): split LoVisualAltManagerScreen 1113 -> 11 файлов alt/ (≤200 строк) - docs(8.5.2): LoVisualAltManagerScreen сплит завершён - refactor(8.5.2): split UiPrimitiveRenderer 1053 -> фасад 188 + primitive/ + rect/ (7 файлов ≤200); fix readPoints индексация - docs(8.5.2): UiPrimitiveRenderer сплит завершён
184 lines
7.4 KiB
Python
184 lines
7.4 KiB
Python
#!/usr/bin/env python3
|
|
"""Structure split helper (Phase 8.5.1): move classes into leaf subpackages by spec JSON,
|
|
fix package lines, rewrite FQNs repo-wide, expand star imports of emptied bases,
|
|
insert single-type imports for moved classes, promote the PRIMARY top-level type
|
|
(class name == file name) to public when it was package-private.
|
|
Does NOT run the compile loop — call promote.py afterwards.
|
|
Usage: python3 split_struct.py spec.json (run from project root, e.g. combatant-client-26.2)"""
|
|
|
|
import os, re, subprocess, json, sys
|
|
|
|
ROOT = "src/main/java/dev/loki/lovisual"
|
|
|
|
|
|
def git_mv(src, dstdir, name):
|
|
os.makedirs(dstdir, exist_ok=True)
|
|
dst = os.path.join(dstdir, name + ".java")
|
|
if os.path.abspath(src) == os.path.abspath(dst):
|
|
return
|
|
subprocess.run(["git", "mv", "-f", src, dst], check=True)
|
|
|
|
|
|
def main(specpath):
|
|
spec = json.load(open(specpath))
|
|
BASE = spec["base"]
|
|
files = {}
|
|
for dp, dns, fns in os.walk(BASE):
|
|
for fn in fns:
|
|
if fn.endswith(".java"):
|
|
files[fn[:-5]] = os.path.join(dp, fn)
|
|
reldots = {}
|
|
for sub, names in spec["move"].items():
|
|
dst = os.path.join(BASE, sub)
|
|
os.makedirs(dst, exist_ok=True)
|
|
for n in names:
|
|
src = files.get(n)
|
|
if src is None:
|
|
print("MISSING:", n)
|
|
continue
|
|
cur = os.path.relpath(os.path.dirname(src), ROOT).replace("/", ".")
|
|
newp = os.path.relpath(dst, ROOT).replace("/", ".")
|
|
if cur == newp:
|
|
continue
|
|
reldots[f"{cur}.{n}"] = f"{newp}.{n}"
|
|
git_mv(src, dst, n)
|
|
if not reldots:
|
|
print("nothing moved")
|
|
|
|
# package lines for whole base tree
|
|
for dp, dns, fns in os.walk(BASE):
|
|
pkg = "dev.loki.lovisual." + os.path.relpath(dp, ROOT).replace("/", ".")
|
|
for fn in fns:
|
|
if not fn.endswith(".java"):
|
|
continue
|
|
p = os.path.join(dp, fn)
|
|
t = open(p).read()
|
|
n = re.sub(r"^package [^;]*;", f"package {pkg};", t, count=1, flags=re.M)
|
|
if n != t:
|
|
open(p, "w").write(n)
|
|
|
|
moved_pkg = {}
|
|
for dp, dns, fns in os.walk(BASE):
|
|
pkg = os.path.relpath(dp, ROOT).replace("/", ".")
|
|
for fn in fns:
|
|
if fn.endswith(".java"):
|
|
moved_pkg[fn[:-5]] = pkg
|
|
|
|
# global dupes (WorldCommandBuffer, GlyphAtlas* etc.) should not be auto-imported
|
|
global_cls = {}
|
|
for dp, dns, fns in os.walk(ROOT):
|
|
for fn in fns:
|
|
if fn.endswith(".java"):
|
|
global_cls.setdefault(fn[:-5], set()).add(
|
|
os.path.relpath(dp, ROOT).replace("/", ".")
|
|
)
|
|
dupes = {k for k, v in global_cls.items() if len(v) > 1}
|
|
|
|
base_pkg = "dev.loki.lovisual." + os.path.relpath(BASE, ROOT).replace("/", ".")
|
|
WILD_STAR = f"import {base_pkg}.*;"
|
|
WILD_LEGACY = f"import {base_pkg}.;"
|
|
SHADOW = {
|
|
"Font",
|
|
"GlyphAtlasManager",
|
|
"Scoreboard",
|
|
"Inventory",
|
|
"MediaPlayer",
|
|
"Player",
|
|
"Entity",
|
|
"Level",
|
|
"World",
|
|
}
|
|
for tree in ("src/main/java", "src/test/java"):
|
|
for dp, dns, fns in os.walk(tree):
|
|
my = None
|
|
rel = os.path.relpath(dp, tree)
|
|
if rel.startswith("dev/loki/lovisual"):
|
|
my = rel.replace("/", ".")
|
|
for fn in fns:
|
|
if not fn.endswith(".java"):
|
|
continue
|
|
p = os.path.join(dp, fn)
|
|
t = open(p).read()
|
|
o = t
|
|
# replace longest FQNs first to avoid prefix collision (InventorySwap vs InventorySwapVisibility)
|
|
for old, new in sorted(reldots.items(), key=lambda kv: -len(kv[0])):
|
|
# exact FQN replace: old must be followed by non-word or end, use regex
|
|
t = re.sub(
|
|
r"dev\.loki\.lovisual\." + re.escape(old) + r"(?=[\W])",
|
|
f"dev.loki.lovisual.{new}",
|
|
t,
|
|
)
|
|
# expand star imports (both .* and legacy .%;) only if base became empty
|
|
wild_hit = (WILD_STAR in t) or (WILD_LEGACY in t)
|
|
if wild_hit:
|
|
base_still_has_top = any(
|
|
cp == os.path.relpath(BASE, ROOT).replace("/", ".")
|
|
for cp in moved_pkg.values()
|
|
)
|
|
if not base_still_has_top:
|
|
body = "\n".join(t.splitlines()[8:])
|
|
need = [
|
|
f"import dev.loki.lovisual.{cp}.{cls};"
|
|
for cls, cp in moved_pkg.items()
|
|
if cls not in SHADOW
|
|
and cls not in dupes
|
|
and re.search(r"(?<![\w.])%s\b" % re.escape(cls), body)
|
|
]
|
|
repl = "".join(n + "\n" for n in sorted(need))
|
|
if WILD_STAR in t:
|
|
t = t.replace(WILD_STAR + "\n", repl)
|
|
if WILD_LEGACY in t:
|
|
t = t.replace(WILD_LEGACY + "\n", repl)
|
|
# insert missing single-type imports for moved classes used by simple name
|
|
body = "\n".join(t.splitlines()[8:])
|
|
need = []
|
|
for cls, cp in moved_pkg.items():
|
|
if cls in dupes or cls in SHADOW:
|
|
continue
|
|
if my is not None and cp == my:
|
|
continue
|
|
if cls == fn[:-5]:
|
|
continue
|
|
if not re.search(r"(?<![\w.])%s\b" % re.escape(cls), body):
|
|
continue
|
|
if f"import dev.loki.lovisual.{cp}.{cls};" in t:
|
|
continue
|
|
need.append(f"import dev.loki.lovisual.{cp}.{cls};")
|
|
if need:
|
|
lines = t.splitlines(keepends=True)
|
|
imps = [i for i, l in enumerate(lines) if l.startswith("import ")]
|
|
idx = (
|
|
max(imps)
|
|
if imps
|
|
else next(
|
|
(i for i, l in enumerate(lines) if l.startswith("package")),
|
|
0,
|
|
)
|
|
)
|
|
lines.insert(idx + 1, "\n".join(sorted(need)) + "\n")
|
|
t = "".join(lines)
|
|
|
|
# promote PRIMARY type only (name == file stem) if package-private
|
|
def prom_primary(m):
|
|
if m.group(3) != fn[:-5]:
|
|
return m.group(0)
|
|
return "public " + m.group(0) if not m.group(1) else m.group(0)
|
|
|
|
t = re.sub(
|
|
r"^(public\s+)?((?:(?:final|abstract|sealed|strictfp)\s+)*)(class|interface|enum|record)\s+(\w+)",
|
|
lambda m: (
|
|
m.group(0)
|
|
if (m.group(4) != fn[:-5] or m.group(1))
|
|
else "public " + m.group(2) + m.group(3) + " " + m.group(4)
|
|
),
|
|
t,
|
|
count=1,
|
|
flags=re.M,
|
|
)
|
|
if t != o:
|
|
open(p, "w").write(t)
|
|
print("structure done for", BASE)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main(sys.argv[1])
|