chore(history): squash 37 commit(s) from 2026-09-10
- 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 сплит завершён
This commit is contained in:
parent
fb6e6eb1d3
commit
573780cd5f
1280 changed files with 9468 additions and 6380 deletions
294
combatant-client-26.2/tools/pkgsplit/fiximports.py
Normal file
294
combatant-client-26.2/tools/pkgsplit/fiximports.py
Normal file
|
|
@ -0,0 +1,294 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Rewrite import paths to match on-disk location + auto-fix missing/star imports.
|
||||
Usage: fiximports.py [BASE ...] — if no BASE given, scans whole lovisual tree.
|
||||
Features vs v1:
|
||||
- handles star imports (import pkg.*) -> expands to single-type imports based on body usage
|
||||
- inserts missing single-type imports for unique classes used by simple name
|
||||
- fixes stale FQNs in body (dev.loki.lovisual.oldpkg.Class -> newpkg.Class)
|
||||
- respects shadow list (Font, Scoreboard etc.) and dupe names
|
||||
- dedupes + sorts imports
|
||||
"""
|
||||
|
||||
import os, re, sys
|
||||
|
||||
ROOT = "src/main/java/dev/loki/lovisual"
|
||||
# Known shadows where simple name collides with JDK/vanilla/GUI types.
|
||||
# Auto-insert would break these (seen in ecfe76f fix).
|
||||
SHADOW = {
|
||||
"Font",
|
||||
"GlyphAtlasManager",
|
||||
"Scoreboard",
|
||||
"Inventory",
|
||||
"MediaPlayer",
|
||||
"Matrix4f",
|
||||
"Vector3f",
|
||||
"Button",
|
||||
"List",
|
||||
"Map",
|
||||
"Set",
|
||||
"Player",
|
||||
"Entity",
|
||||
"Level",
|
||||
"World",
|
||||
}
|
||||
|
||||
# Build class -> pkg map from disk
|
||||
cls_pkg = {} # cls -> set(pkgs)
|
||||
cls_pkg_unique = {} # cls -> pkg if unique
|
||||
file_for_cls = {} # cls -> path (for dupes debug)
|
||||
|
||||
scan_roots = sys.argv[1:] if len(sys.argv) > 1 else [ROOT]
|
||||
for base in scan_roots:
|
||||
# base may be like src/main/java/dev/loki/lovisual/util/media
|
||||
# we walk it, but for global map we walk ROOT anyway to fix all imports
|
||||
pass
|
||||
# always build from ROOT for correctness
|
||||
for dp, dns, fns in os.walk(ROOT):
|
||||
pkg = os.path.relpath(dp, ROOT).replace("/", ".")
|
||||
if pkg == ".":
|
||||
pkg = ""
|
||||
for fn in fns:
|
||||
if not fn.endswith(".java"):
|
||||
continue
|
||||
cls = fn[:-5]
|
||||
# full pkg for this class
|
||||
full_pkg = f"dev.loki.lovisual.{pkg}" if pkg else "dev.loki.lovisual"
|
||||
cls_pkg.setdefault(cls, set()).add(full_pkg)
|
||||
file_for_cls[cls] = os.path.join(dp, fn)
|
||||
|
||||
dupes = {cls for cls, pkgs in cls_pkg.items() if len(pkgs) > 1}
|
||||
for cls, pkgs in cls_pkg.items():
|
||||
if len(pkgs) == 1:
|
||||
cls_pkg_unique[cls] = next(iter(pkgs))
|
||||
|
||||
# For FQN fix: build old->new map for stale FQNs (where file moved but old import still points)
|
||||
# We already have unique map, so any FQN that doesn't match current pkg is stale.
|
||||
changed = 0
|
||||
FQN_RE = re.compile(r"dev\.loki\.lovisual\.[\w.]+?\.([A-Z]\w*)\b")
|
||||
|
||||
for tree in ("src/main/java", "src/test/java"):
|
||||
for dp, dns, fns in os.walk(tree):
|
||||
for fn in fns:
|
||||
if not fn.endswith(".java"):
|
||||
continue
|
||||
p = os.path.join(dp, fn)
|
||||
text = open(p, encoding="utf-8", errors="ignore").read()
|
||||
orig = text
|
||||
|
||||
# package of this file
|
||||
m_pkg = re.search(r"^package\s+([\w.]+);", text, re.M)
|
||||
my_pkg = m_pkg.group(1) if m_pkg else ""
|
||||
|
||||
lines = text.splitlines(keepends=True)
|
||||
# separate header vs body
|
||||
# find last import line
|
||||
import_lines = [i for i, l in enumerate(lines) if l.startswith("import ")]
|
||||
body_start = (
|
||||
(max(import_lines) + 1)
|
||||
if import_lines
|
||||
else next(
|
||||
(i for i, l in enumerate(lines) if l.startswith("package")), 0
|
||||
)
|
||||
+ 1
|
||||
)
|
||||
body = "".join(lines[body_start:])
|
||||
|
||||
# 1) Fix existing single-type imports where path is stale (unique classes only)
|
||||
def fix_import(m):
|
||||
full = m.group(0).strip() # import dev.loki.lovisual.foo.Bar;
|
||||
# extract pkg and cls
|
||||
mm = re.match(
|
||||
r"import\s+(?:static\s+)?dev\.loki\.lovisual\.([\w.]+)\.([A-Z]\w*);",
|
||||
full,
|
||||
)
|
||||
if not mm:
|
||||
return m.group(0)
|
||||
pkg_part, cls = mm.group(1), mm.group(2)
|
||||
full_pkg_prefix = f"dev.loki.lovisual.{pkg_part}"
|
||||
if cls in dupes or cls in SHADOW:
|
||||
return m.group(0)
|
||||
want = cls_pkg_unique.get(cls)
|
||||
if want is None:
|
||||
return m.group(0)
|
||||
if want == full_pkg_prefix or want == f"dev.loki.lovisual.{pkg_part}":
|
||||
return m.group(0)
|
||||
# keep static prefix if present
|
||||
is_static = "static " in full
|
||||
prefix = "import static " if is_static else "import "
|
||||
return (
|
||||
f"{prefix}{want}.{cls};\n"
|
||||
if full.endswith("\n")
|
||||
else f"{prefix}{want}.{cls};"
|
||||
)
|
||||
|
||||
text = re.sub(
|
||||
r"import\s+(?:static\s+)?dev\.loki\.lovisual\.[\w.]*?\.([A-Z]\w*);[ \t]*\n?",
|
||||
lambda m: fix_import(m) if m.group(1) in cls_pkg_unique else m.group(0),
|
||||
text,
|
||||
)
|
||||
|
||||
# Re-split after fix
|
||||
lines = text.splitlines(keepends=True)
|
||||
import_lines = [i for i, l in enumerate(lines) if l.startswith("import ")]
|
||||
body_start = (
|
||||
(max(import_lines) + 1)
|
||||
if import_lines
|
||||
else next(
|
||||
(i for i, l in enumerate(lines) if l.startswith("package")), 0
|
||||
)
|
||||
+ 1
|
||||
)
|
||||
body = "".join(lines[body_start:])
|
||||
|
||||
# 2) Expand star imports: import pkg.* -> needed singles
|
||||
# DISABLED by default for clean-tree runs (avoids 57-file churn).
|
||||
# split_struct.py handles star expansion per-wave when BASE becomes empty.
|
||||
# Enable here only if --expand-stars flag is given.
|
||||
stars = []
|
||||
if "--expand-stars" in sys.argv:
|
||||
for l in lines:
|
||||
ms = re.match(r"import\s+dev\.loki\.lovisual\.([\w.]+)\.\*;", l)
|
||||
if ms:
|
||||
stars.append(f"dev.loki.lovisual.{ms.group(1)}")
|
||||
if stars:
|
||||
needed = set()
|
||||
body_tokens = set(re.findall(r"\b[A-Z][A-Za-z0-9_]*\b", body))
|
||||
for star_pkg in stars:
|
||||
for cls in body_tokens:
|
||||
want = cls_pkg_unique.get(cls)
|
||||
if want == star_pkg:
|
||||
if cls not in SHADOW:
|
||||
needed.add(f"import {want}.{cls};")
|
||||
if needed:
|
||||
# remove star lines, insert needed
|
||||
new_lines = []
|
||||
for l in lines:
|
||||
if re.match(r"import\s+dev\.loki\.lovisual\.[\w.]+\.\*;", l):
|
||||
continue
|
||||
new_lines.append(l)
|
||||
# insert needed after last import or after package
|
||||
lines = new_lines
|
||||
text = "".join(lines)
|
||||
lines = text.splitlines(keepends=True)
|
||||
import_lines = [
|
||||
i for i, l in enumerate(lines) if l.startswith("import ")
|
||||
]
|
||||
idx = (
|
||||
max(import_lines)
|
||||
if import_lines
|
||||
else next(
|
||||
(i for i, l in enumerate(lines) if l.startswith("package")),
|
||||
0,
|
||||
)
|
||||
)
|
||||
# dedup against existing
|
||||
existing = set(l.strip() for l in lines if l.startswith("import "))
|
||||
to_add = sorted(n for n in needed if n not in existing)
|
||||
if to_add:
|
||||
lines[idx + 1 : idx + 1] = [n + "\n" for n in to_add]
|
||||
text = "".join(lines)
|
||||
# re-derive body
|
||||
lines = text.splitlines(keepends=True)
|
||||
body = "".join(lines[body_start:])
|
||||
|
||||
# 3) Insert missing imports for unique classes used by simple name
|
||||
# DISABLED by default: too aggressive (inner class UiState etc.) — only needed
|
||||
# when split_struct moved classes and body uses simple name without import.
|
||||
# split_struct.py already handles this for moved files. For fiximports standalone,
|
||||
# we skip auto-insert to avoid 160-file churn on clean tree. Enable with --insert.
|
||||
missing = []
|
||||
if "--insert" in sys.argv:
|
||||
body_simple = body
|
||||
body_simple = re.sub(r'"(\\.|[^"\\])*"', '""', body_simple)
|
||||
body_simple = re.sub(r"//[^\n]*", "", body_simple)
|
||||
body_simple = re.sub(r"/\*.*?\*/", "", body_simple, flags=re.S)
|
||||
tokens = set(re.findall(r"\b[A-Z][A-Za-z0-9_]*\b", body_simple))
|
||||
for cls in tokens:
|
||||
if cls in SHADOW or cls in dupes:
|
||||
continue
|
||||
want = cls_pkg_unique.get(cls)
|
||||
if not want:
|
||||
continue
|
||||
if want == my_pkg:
|
||||
continue
|
||||
if f"import {want}.{cls};" in text:
|
||||
continue
|
||||
if cls == fn[:-5]:
|
||||
continue
|
||||
if f"{want}.{cls}" in body:
|
||||
continue
|
||||
# require unqualified usage (not Outer.Inner)
|
||||
if not re.search(
|
||||
r"(?<![\w.])" + re.escape(cls) + r"\b", body_simple
|
||||
):
|
||||
continue
|
||||
missing.append(f"import {want}.{cls};")
|
||||
if missing:
|
||||
missing = sorted(set(missing))
|
||||
lines = text.splitlines(keepends=True)
|
||||
import_lines = [
|
||||
i for i, l in enumerate(lines) if l.startswith("import ")
|
||||
]
|
||||
idx = (
|
||||
max(import_lines)
|
||||
if import_lines
|
||||
else next(
|
||||
(i for i, l in enumerate(lines) if l.startswith("package")), 0
|
||||
)
|
||||
)
|
||||
lines[idx + 1 : idx + 1] = [m + "\n" for m in missing]
|
||||
text = "".join(lines)
|
||||
|
||||
# 4) Fix stale FQNs in body (where old pkg.Class still written fully qualified)
|
||||
def fqn_fix(m):
|
||||
cls = m.group(1)
|
||||
full = m.group(0)
|
||||
if cls in dupes or cls in SHADOW:
|
||||
return full
|
||||
want = cls_pkg_unique.get(cls)
|
||||
if not want:
|
||||
return full
|
||||
# full is like dev.loki.lovisual.old.pkg.Cls
|
||||
cur_pkg = ".".join(full.split(".")[:-1])
|
||||
if cur_pkg == want:
|
||||
return full
|
||||
return f"{want}.{cls}"
|
||||
|
||||
# only in body part, but apply to whole file (imports already fixed)
|
||||
text = FQN_RE.sub(
|
||||
lambda m: fqn_fix(m) if m.group(1) in cls_pkg_unique else m.group(0),
|
||||
text,
|
||||
)
|
||||
|
||||
# 5) Dedupe + sort imports block (keep static separate)
|
||||
lines = text.splitlines(keepends=True)
|
||||
# find import block range
|
||||
imp_start = next(
|
||||
(i for i, l in enumerate(lines) if l.startswith("import ")), None
|
||||
)
|
||||
if imp_start is not None:
|
||||
imp_end = imp_start
|
||||
while imp_end < len(lines) and lines[imp_end].startswith("import "):
|
||||
imp_end += 1
|
||||
block = lines[imp_start:imp_end]
|
||||
# dedupe preserving order (no global sort to avoid 490-file churn)
|
||||
seen = set()
|
||||
uniq = []
|
||||
for l in block:
|
||||
if l not in seen:
|
||||
seen.add(l)
|
||||
uniq.append(l)
|
||||
lines = lines[:imp_start] + uniq + lines[imp_end:]
|
||||
text = "".join(lines)
|
||||
|
||||
if text != orig:
|
||||
open(p, "w", encoding="utf-8").write(text)
|
||||
changed += 1
|
||||
|
||||
print(f"import paths corrected: {changed}")
|
||||
if dupes:
|
||||
print(
|
||||
f"dupes skipped ({len(dupes)}): {sorted(list(dupes))[:10]}{' ...' if len(dupes) > 10 else ''}"
|
||||
)
|
||||
if SHADOW:
|
||||
print(f"shadow protected: {sorted(SHADOW)}")
|
||||
67
combatant-client-26.2/tools/pkgsplit/member_sweep.py
Normal file
67
combatant-client-26.2/tools/pkgsplit/member_sweep.py
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Depth-aware sweep: promote ALL package-private member declarations
|
||||
(fields, methods, constructors, nested types) inside class bodies of a tree.
|
||||
Skips private members deliberately? No — the wave policy: everything that was
|
||||
package-private becomes public; existing public/private untouched.
|
||||
Usage: member_sweep.py BASE"""
|
||||
import os, re, sys
|
||||
|
||||
PRIMS = {"int","float","double","boolean","long","byte","short","char","void"}
|
||||
MODS = {"public","private","protected","static","final","abstract","transient","volatile","native","strictfp","synchronized","default"}
|
||||
TYPE_TOK = re.compile(r"^(?:[A-Za-z_]\w*\.)*[A-Za-z_]\w*(?:<[^>]*>)?(?:\[\])?(?:\?|extends\s+\w+)*$")
|
||||
|
||||
def clean(l):
|
||||
s = re.sub(r"//.*", "", l)
|
||||
s = re.sub(r'"(\\.|[^"\\])*"', '""', s)
|
||||
s = re.sub(r"'(\\.|[^'\\])'", "''", s)
|
||||
return s
|
||||
|
||||
def process(p):
|
||||
lines = open(p).read().splitlines(keepends=True)
|
||||
stack = []
|
||||
out = []
|
||||
changed = 0
|
||||
for l in lines:
|
||||
in_class = not stack or stack[-1] == "class"
|
||||
s = l.strip()
|
||||
ind = l[:len(l)-len(l.lstrip())]
|
||||
if in_class and s and ind and l[:4] == " " and not s.startswith(("public","private","protected","//","*","@","}","{","import","package","else","return","if","for","while","switch","case","try","catch","finally","throw","new ","super.","this.","synchronized(")):
|
||||
m = re.match(r"^(?:(?:static|final|abstract|transient|volatile|synchronized)\s+)*(<[\w ,<>]+>\s+)?([A-Za-z_][\w.]*(?:<[^>]*>)?)(?:\[\])?\s+(\w+)\s*(\(|=|;)", s)
|
||||
ctor = re.match(r"^(\w+)\s*\(", s) and s.split("(")[0][0].isupper()
|
||||
typ_ok = False
|
||||
if m:
|
||||
typ = m.group(2)
|
||||
typ_ok = typ in PRIMS or typ[0].isupper()
|
||||
is_field = m and typ_ok and m.group(4) in ("=",";")
|
||||
is_method = m and typ_ok and m.group(4) == "(" and not s.startswith("return")
|
||||
is_ctor = bool(ctor) and ind.strip() != "" and not s.startswith(("class","record","interface","enum","public","static","final","abstract","void","new ")) and re.match(rf"^\w+\(", s) and s.split("(")[0] in [fn for fn in [os.path.basename(p)[:-5]]]
|
||||
is_type = bool(re.match(r"^(?:(?:static|final|abstract|sealed)\s+)*(class|interface|enum|record)\s+\w+", s))
|
||||
if is_field or is_method or is_ctor or is_type:
|
||||
l = ind + "public " + s + ("\n" if l.endswith("\n") else "")
|
||||
changed += 1
|
||||
out.append(l)
|
||||
c = clean(l)
|
||||
pos = 0
|
||||
while True:
|
||||
o = c.find("{", pos); cl = c.find("}", pos)
|
||||
if o == -1 and cl == -1: break
|
||||
if cl != -1 and (o == -1 or cl < o):
|
||||
stack.pop() if stack else None
|
||||
pos = cl + 1
|
||||
else:
|
||||
stack.append("class" if re.search(r"\b(class|interface|enum|record)\s+\w", c[:o]) else "other")
|
||||
pos = o + 1
|
||||
if changed:
|
||||
open(p, "w").write("".join(out))
|
||||
return changed
|
||||
|
||||
def main(base):
|
||||
total = 0
|
||||
for dp, dns, fns in os.walk(base):
|
||||
for fn in fns:
|
||||
if fn.endswith(".java"):
|
||||
total += process(os.path.join(dp, fn))
|
||||
print("member sweep promotions:", total)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(sys.argv[1])
|
||||
152
combatant-client-26.2/tools/pkgsplit/promote.py
Normal file
152
combatant-client-26.2/tools/pkgsplit/promote.py
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Compile-fix helper (Phase 8.5.1): promote declarations to public until
|
||||
compileJava+compileTestJava are green. Brace-depth-aware class-body tracking;
|
||||
statement lines are never touched. Run from project root.
|
||||
Usage: python3 promote.py BASE_DIR"""
|
||||
import os, re, subprocess, sys
|
||||
|
||||
MODS = {"public","private","protected","static","final","abstract","transient","volatile","native","strictfp"}
|
||||
PRIMS = {"int","float","double","boolean","long","byte","short","char","void"}
|
||||
IDENT = re.compile(r"[A-Za-z_][\w.]*")
|
||||
BASE = "."
|
||||
|
||||
|
||||
def clean_line(l):
|
||||
s = re.sub(r"//[^\n]*", "", l)
|
||||
s = re.sub(r'"(\\.|[^"\\])*"', '""', s)
|
||||
s = re.sub(r"'(\\.|[^'\\])*'", "''", s)
|
||||
return s
|
||||
|
||||
|
||||
def class_body_flags(p):
|
||||
"""(line, in_class_body) per line using a stack over '{' scopes."""
|
||||
lines = open(p).read().splitlines(keepends=True)
|
||||
stack = []
|
||||
out = []
|
||||
for l in lines:
|
||||
out.append((l, not stack or stack[-1] == "class"))
|
||||
c = clean_line(l)
|
||||
pos = 0
|
||||
while True:
|
||||
o = c.find("{", pos)
|
||||
cl = c.find("}", pos)
|
||||
if o == -1 and cl == -1:
|
||||
break
|
||||
if cl != -1 and (o == -1 or cl < o):
|
||||
if stack:
|
||||
stack.pop()
|
||||
pos = cl + 1
|
||||
else:
|
||||
head = c[:o]
|
||||
kind = "class" if re.search(r"\b(class|interface|enum|record|@interface)\s+\w", head) else "other"
|
||||
stack.append(kind)
|
||||
pos = o + 1
|
||||
return out
|
||||
|
||||
|
||||
def build_maps():
|
||||
typemap, cfiles = {}, {}
|
||||
for dp, dns, fns in os.walk(BASE):
|
||||
for fn in fns:
|
||||
if not fn.endswith(".java"):
|
||||
continue
|
||||
p = os.path.join(dp, fn)
|
||||
cfiles.setdefault(fn[:-5], p)
|
||||
txt = open(p).read()
|
||||
for m in re.finditer(r"^ {0,8}(?:public\s+|private\s+|protected\s+|static\s+|final\s+|abstract\s+|sealed\s+)*(class|interface|enum|record)\s+(\w+)", txt, re.M):
|
||||
typemap.setdefault(m.group(2), p)
|
||||
return typemap, cfiles
|
||||
|
||||
|
||||
def promote(typemap, cfiles, cls, member):
|
||||
p = typemap.get(cls) or cfiles.get(cls)
|
||||
if not p:
|
||||
return False
|
||||
for i, (l, in_class) in enumerate(class_body_flags(p)):
|
||||
if not in_class:
|
||||
continue
|
||||
s = l.lstrip()
|
||||
ind = l[:len(l) - len(s)]
|
||||
if ind not in (" ", " "):
|
||||
continue
|
||||
if s.startswith(("public ", "protected ", "private ", "@", "//", "*", "}", "{")):
|
||||
continue
|
||||
if re.match(r"^(?:(?:public|static|final|abstract)\s+)*(class|interface|enum|record)\s+%s\b" % re.escape(member), s):
|
||||
lines = open(p).read().splitlines(keepends=True)
|
||||
lines[i] = ind + "public " + s + "\n"
|
||||
open(p, "w").write("".join(lines))
|
||||
return True
|
||||
# constructor of a nested class named `member`
|
||||
if re.match(r"^%s\s*\(" % re.escape(member), s):
|
||||
lines = open(p).read().splitlines(keepends=True)
|
||||
lines[i] = ind + "public " + s + "\n"
|
||||
open(p, "w").write("".join(lines))
|
||||
return True
|
||||
m = re.search(r"(?<![\w.])%s\b\s*(\(|=|;|,)" % re.escape(member), s)
|
||||
if not m or not s[:m.start()].strip():
|
||||
continue
|
||||
toks = s[:m.start()].replace("<", " < ").replace(">", " > ").replace(",", " , ").split()
|
||||
ok = bool(toks)
|
||||
for t in toks:
|
||||
b = IDENT.match(t)
|
||||
core = b.group(0) if b else ""
|
||||
if core in MODS or core in PRIMS or (core and core[0].isupper()) or t in ("<", ">", ",", "?", "extends"):
|
||||
continue
|
||||
ok = False
|
||||
break
|
||||
if not ok:
|
||||
continue
|
||||
lines = open(p).read().splitlines(keepends=True)
|
||||
if s.startswith("private "):
|
||||
s = s[len("private "):]
|
||||
lines[i] = ind + "public " + s + ("\n" if l.endswith("\n") else "")
|
||||
open(p, "w").write("".join(lines))
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
global BASE
|
||||
BASE = sys.argv[1] if len(sys.argv) > 1 else BASE
|
||||
typemap, cfiles = build_maps()
|
||||
for it in range(150):
|
||||
r = subprocess.run(["./gradlew", "compileJava", "compileTestJava", "--offline", "-q"],
|
||||
capture_output=True, text=True)
|
||||
err = r.stderr + r.stdout
|
||||
if r.returncode == 0:
|
||||
print("COMPILE GREEN after", it, "iterations")
|
||||
return 0
|
||||
pend = set()
|
||||
for msg, c in re.findall(r"error: (.*?) is not public in (\w+);", err):
|
||||
tok = re.sub(r"<N>", "", msg).split("(")[0].split(".")[-1].strip()
|
||||
if tok:
|
||||
pend.add((tok, c))
|
||||
for msg, c in re.findall(r"reason:\s+(?:<N>)?([\w.]+)\(.*?\) is not public in (\w+);", err):
|
||||
pend.add((msg.split(".")[-1], c))
|
||||
for m2, c2 in re.findall(r"method ([\w]+) in class (\w+) cannot be accessed", err):
|
||||
pend.add((m2, c2))
|
||||
for m2, c2 in re.findall(r"([A-Z]\w+)\.[\w.]* is defined in an inaccessible class", err):
|
||||
pass
|
||||
for cls_name in re.findall(r"([A-Z]\w*) is defined in an inaccessible class or interface", err):
|
||||
pend.add((cls_name, cls_name))
|
||||
done = 0
|
||||
for mem, cls in sorted(pend):
|
||||
if promote(typemap, cfiles, cls, mem):
|
||||
done += 1
|
||||
typemap, cfiles = build_maps()
|
||||
if done == 0:
|
||||
print("STUCK at iter", it)
|
||||
seen, uniq = set(), []
|
||||
for line in err.splitlines():
|
||||
if "error:" in line and line not in seen:
|
||||
seen.add(line)
|
||||
uniq.append(line)
|
||||
for u in uniq[:30]:
|
||||
print(" ", u.replace("/storage/project/jvm/LoVisual/combatant-client-26.2/", "")[:170])
|
||||
return 1
|
||||
print(f"iter {it}: promoted {done}/{len(pend)}")
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
184
combatant-client-26.2/tools/pkgsplit/split_struct.py
Normal file
184
combatant-client-26.2/tools/pkgsplit/split_struct.py
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
#!/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])
|
||||
Loading…
Add table
Add a link
Reference in a new issue