GL32 shaders and errors

- Update shaders to glsl 150
 - Objectfy errors
This commit is contained in:
Jozufozu 2021-09-20 19:27:04 -07:00
parent 5ce960c5d5
commit 0663218b67
27 changed files with 404 additions and 257 deletions

View file

@ -1,8 +1,12 @@
package com.jozufozu.flywheel.backend.gl;
public enum GLSLVersion {
@Deprecated
V110(110),
@Deprecated
V120(120),
V150(150),
V330(330),
;
public final int version;

View file

@ -29,14 +29,14 @@ public class InstancingProgramMetaData {
Optional<ShaderFunction> vertexFunc = file.findFunction("vertex");
Optional<ShaderFunction> fragmentFunc = file.findFunction("fragment");
if (!fragmentFunc.isPresent()) {
if (fragmentFunc.isEmpty()) {
ErrorReporter.generateMissingFunction(file, "fragment", "\"fragment\" function not defined");
}
if (!vertexFunc.isPresent()) {
if (vertexFunc.isEmpty()) {
ErrorReporter.generateFileError(file, "could not find \"vertex\" function");
}
if (!fragmentFunc.isPresent() || !vertexFunc.isPresent()) {
if (fragmentFunc.isEmpty() || vertexFunc.isEmpty()) {
throw new ShaderLoadingException();
}
@ -64,19 +64,19 @@ public class InstancingProgramMetaData {
Optional<ShaderStruct> maybeVertex = file.findStruct(vertexName);
Optional<ShaderStruct> maybeInstance = file.findStruct(instanceName);
if (!maybeVertex.isPresent()) {
if (maybeVertex.isEmpty()) {
ErrorReporter.generateMissingStruct(file, vertexName, "struct not defined");
}
if (!maybeInterpolant.isPresent()) {
if (maybeInterpolant.isEmpty()) {
ErrorReporter.generateMissingStruct(file, interpolantName, "struct not defined");
}
if (!maybeInstance.isPresent()) {
if (maybeInstance.isEmpty()) {
ErrorReporter.generateMissingStruct(file, instanceName, "struct not defined");
}
if (!maybeVertex.isPresent() || !maybeInterpolant.isPresent() || !maybeInstance.isPresent()) {
if (maybeVertex.isEmpty() || maybeInterpolant.isEmpty() || maybeInstance.isEmpty()) {
throw new ShaderLoadingException();
}

View file

@ -37,9 +37,9 @@ public class InstancingTemplate extends Template<InstancingProgramMetaData> {
public void vertexFooter(StringBuilder template, SourceFile file) {
InstancingProgramMetaData data = getMetadata(file);
Template.prefixFields(template, data.vertex, "attribute", "a_v_");
Template.prefixFields(template, data.instance, "attribute", "a_i_");
Template.prefixFields(template, data.interpolant, "varying", "v2f_");
Template.prefixFields(template, data.vertex, "in", "a_v_");
Template.prefixFields(template, data.instance, "in", "a_i_");
Template.prefixFields(template, data.interpolant, "out", "v2f_");
template.append("void main() {\n");
template.append(data.vertexName)
@ -63,7 +63,7 @@ public class InstancingTemplate extends Template<InstancingProgramMetaData> {
public void fragmentFooter(StringBuilder template, SourceFile file) {
InstancingProgramMetaData data = getMetadata(file);
Template.prefixFields(template, data.interpolant, "varying", "v2f_");
Template.prefixFields(template, data.interpolant, "in", "v2f_");
template.append("void main() {\n");
template.append(data.interpolantName)

View file

@ -26,15 +26,15 @@ public class OneShotProgramMetaData {
Optional<ShaderFunction> maybeVertexMain = file.findFunction("vertex");
Optional<ShaderFunction> maybeFragmentMain = file.findFunction("fragment");
if (!maybeVertexMain.isPresent()) {
if (maybeVertexMain.isEmpty()) {
ErrorReporter.generateFileError(file, "could not find \"vertex\" function");
}
if (!maybeFragmentMain.isPresent()) {
if (maybeFragmentMain.isEmpty()) {
ErrorReporter.generateMissingFunction(file, "fragment", "\"fragment\" function not defined");
}
if (!maybeVertexMain.isPresent() || !maybeFragmentMain.isPresent()) {
if (maybeVertexMain.isEmpty() || maybeFragmentMain.isEmpty()) {
throw new RuntimeException();
}
@ -62,13 +62,13 @@ public class OneShotProgramMetaData {
Optional<ShaderStruct> maybeInterpolant = file.findStruct(interpolantName);
Optional<ShaderStruct> maybeVertex = file.findStruct(vertexName);
if (!maybeVertex.isPresent())
if (maybeVertex.isEmpty())
ErrorReporter.generateMissingStruct(file, vertexName, "struct not defined");
if (!maybeInterpolant.isPresent())
if (maybeInterpolant.isEmpty())
ErrorReporter.generateMissingStruct(file, interpolantName, "struct not defined");
if (!maybeVertex.isPresent() || !maybeInterpolant.isPresent()) {
if (maybeVertex.isEmpty() || maybeInterpolant.isEmpty()) {
throw new RuntimeException();
}

View file

@ -32,8 +32,8 @@ public class OneShotTemplate extends Template<OneShotProgramMetaData> {
public void vertexFooter(StringBuilder template, SourceFile file) {
OneShotProgramMetaData data = getMetadata(file);
Template.prefixFields(template, data.vertex, "attribute", "a_v_");
Template.prefixFields(template, data.interpolant, "varying", "v2f_");
Template.prefixFields(template, data.vertex, "in", "a_v_");
Template.prefixFields(template, data.interpolant, "out", "v2f_");
template.append("void main() {\n");
template.append(data.vertexName)
@ -53,7 +53,7 @@ public class OneShotTemplate extends Template<OneShotProgramMetaData> {
public void fragmentFooter(StringBuilder template, SourceFile file) {
OneShotProgramMetaData data = getMetadata(file);
Template.prefixFields(template, data.interpolant, "varying", "v2f_");
Template.prefixFields(template, data.interpolant, "in", "v2f_");
template.append("void main() {\n");
template.append(data.interpolant.name)

View file

@ -25,10 +25,10 @@ public abstract class Template<D> {
private final Map<SourceFile, D> metadata = new HashMap<>();
private final Function<SourceFile, D> parser;
private final Function<SourceFile, D> reader;
protected Template(Function<SourceFile, D> parser) {
this.parser = parser;
protected Template(Function<SourceFile, D> reader) {
this.reader = reader;
}
/**
@ -46,11 +46,12 @@ public abstract class Template<D> {
public abstract Collection<ShaderInput> getShaderInputs(SourceFile file);
public D getMetadata(SourceFile file) {
return metadata.computeIfAbsent(file, parser);
// lazily read files, cache results
return metadata.computeIfAbsent(file, reader);
}
public GLSLVersion getVersion() {
return GLSLVersion.V120;
return GLSLVersion.V150;
}
public static void prefixFields(StringBuilder builder, ShaderStruct struct, String qualifier, String prefix) {

View file

@ -57,7 +57,7 @@ public class WorldShader {
.append(template.getVersion())
.append('\n')
.append("#define ")
.append(type.define)
.append(type.define) // special case shader type declaration
.append('\n')
.append(defines != null ? defines : "")
.append(header)

View file

@ -67,11 +67,10 @@ public class FileResolution {
try {
file = sources.findSource(fileLoc);
} catch (RuntimeException error) {
ErrorBuilder builder = new ErrorBuilder();
builder.error(String.format("could not find source for file %s", fileLoc));
ErrorBuilder builder = ErrorBuilder.error(String.format("could not find source for file %s", fileLoc));
// print the location of all places where this file was referenced
for (Span span : foundSpans) {
builder.in(span.getSourceFile())
builder.pointAtFile(span.getSourceFile())
.pointAt(span, 2);
}
Backend.log.error(builder.build());

View file

@ -27,8 +27,8 @@ public class Index {
Collection<SourceFile> files = sources.values();
for (SourceFile file : files) {
file.getStructs().forEach(knownStructs::put);
file.getFunctions().forEach(knownFunctions::put);
file.structs.forEach(knownStructs::put);
file.functions.forEach(knownFunctions::put);
}
}

View file

@ -13,80 +13,65 @@ import com.google.common.collect.ImmutableMap;
import com.jozufozu.flywheel.backend.source.parse.Import;
import com.jozufozu.flywheel.backend.source.parse.ShaderFunction;
import com.jozufozu.flywheel.backend.source.parse.ShaderStruct;
import com.jozufozu.flywheel.backend.source.span.CharPos;
import com.jozufozu.flywheel.backend.source.span.ErrorSpan;
import com.jozufozu.flywheel.backend.source.span.Span;
import com.jozufozu.flywheel.backend.source.span.StringSpan;
import com.jozufozu.flywheel.util.StringUtil;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import net.minecraft.resources.ResourceLocation;
/**
* Immutable class representing a shader file.
*
* <p>
* This class parses shader files and generates what is effectively a high level AST of the source.
* </p>
*/
public class SourceFile {
private static final Pattern includePattern = Pattern.compile("#use \"(.*)\"");
private static final Pattern newLine = Pattern.compile("(\\r\\n|\\r|\\n)");
// https://regexr.com/60n3d
public static final Pattern functionDeclaration = Pattern.compile("(\\w+)\\s+(\\w+)\\s*\\(([\\w,\\s]*)\\)\\s*\\{");
public final ResourceLocation name;
public final ShaderSources parent;
private final String source;
private final CharSequence elided;
private final ImmutableList<String> lines;
public final String source;
/**
* Sections of the source that must be trimmed for compilation. Currently, it only contains the spans of imports.
*/
public final String elided;
private final IntList lineStarts;
public final SourceLines lines;
// Function name -> Function object
private final ImmutableMap<String, ShaderFunction> functions;
private final ImmutableMap<String, ShaderStruct> structs;
/**
* Function lookup by name.
*/
public final ImmutableMap<String, ShaderFunction> functions;
// Includes ordered as defined in the source
private final ImmutableList<Import> imports;
/**
* Struct lookup by name.
*/
public final ImmutableMap<String, ShaderStruct> structs;
// Sections of the source that must be trimmed for compilation.
private final List<Span> elisions = new ArrayList<>();
/**
* Includes ordered as defined in the source.
*/
public final ImmutableList<Import> imports;
public SourceFile(ShaderSources parent, ResourceLocation name, String source) {
this.parent = parent;
this.name = name;
this.source = source;
this.lineStarts = createLineStarts();
this.lines = createLineList(lineStarts);
this.lines = new SourceLines(source);
this.imports = parseImports();
List<Span> elisions = new ArrayList<>();
this.imports = parseImports(elisions);
this.functions = parseFunctions();
this.structs = parseStructs();
this.elided = createElidedSource();
}
public String getSource() {
return source;
}
public CharSequence getElidedSource() {
return elided;
}
public ShaderSources getParent() {
return parent;
}
public ImmutableMap<String, ShaderFunction> getFunctions() {
return functions;
}
public ImmutableMap<String, ShaderStruct> getStructs() {
return structs;
}
public ImmutableList<Import> getIncludes() {
return imports;
this.elided = elideSource(source, elisions).toString();
}
/**
@ -96,11 +81,11 @@ public class SourceFile {
* @return null if no definition matches the name.
*/
public Optional<ShaderStruct> findStruct(CharSequence name) {
ShaderStruct struct = getStructs().get(name.toString());
ShaderStruct struct = structs.get(name.toString());
if (struct != null) return Optional.of(struct);
for (Import include : getIncludes()) {
for (Import include : imports) {
Optional<ShaderStruct> externalStruct = include.getOptional()
.flatMap(file -> file.findStruct(name));
@ -117,11 +102,11 @@ public class SourceFile {
* @return Optional#empty() if no definition matches the name.
*/
public Optional<ShaderFunction> findFunction(CharSequence name) {
ShaderFunction local = getFunctions().get(name.toString());
ShaderFunction local = functions.get(name.toString());
if (local != null) return Optional.of(local);
for (Import include : getIncludes()) {
for (Import include : imports) {
Optional<ShaderFunction> external = include.getOptional()
.flatMap(file -> file.findFunction(name));
@ -142,29 +127,12 @@ public class SourceFile {
}
public void generateFinalSource(StringBuilder source) {
for (Import include : getIncludes()) {
for (Import include : imports) {
SourceFile file = include.getFile();
if (file != null) file.generateFinalSource(source);
}
source.append(getElidedSource());
}
public CharPos getCharPos(int charPos) {
int lineNo = 0;
for (; lineNo < lineStarts.size(); lineNo++) {
int ls = lineStarts.getInt(lineNo);
if (charPos < ls) {
break;
}
}
lineNo -= 1;
int lineStart = lineStarts.getInt(lineNo);
return new CharPos(charPos, lineNo, charPos - lineStart);
source.append(elided);
}
public String printSource() {
@ -172,18 +140,13 @@ public class SourceFile {
builder.append("Source for shader '")
.append(name)
.append("':\n");
int i = 1;
for (String s : lines) {
builder.append(String.format("%1$4s: ", i++))
.append(s)
.append('\n');
}
.append("':\n")
.append(lines.printLinesWithNumbers());
return builder.toString();
}
private CharSequence createElidedSource() {
private static CharSequence elideSource(String source, List<Span> elisions) {
StringBuilder out = new StringBuilder();
int lastEnd = 0;
@ -199,34 +162,6 @@ public class SourceFile {
return out;
}
/**
* Scan the source for line breaks, recording the position of the first character of each line.
*/
private IntList createLineStarts() {
IntList l = new IntArrayList();
l.add(0); // first line is always at position 0
Matcher matcher = newLine.matcher(source);
while (matcher.find()) {
l.add(matcher.end());
}
return l;
}
private ImmutableList<String> createLineList(IntList lines) {
ImmutableList.Builder<String> builder = ImmutableList.builder();
for (int i = 1; i < lines.size(); i++) {
int start = lines.getInt(i - 1);
int end = lines.getInt(i);
builder.add(StringUtil.trimEnd(source.substring(start, end)));
}
return builder.build();
}
/**
* Scan the source for function definitions and "parse" them into objects that contain properties of the function.
*/
@ -284,8 +219,9 @@ public class SourceFile {
/**
* Scan the source for <code>#use "..."</code> directives.
* Records the contents of the directive into an {@link Import} object, and marks the directive for elision.
* @param elisions
*/
private ImmutableList<Import> parseImports() {
private ImmutableList<Import> parseImports(List<Span> elisions) {
Matcher uses = includePattern.matcher(source);
List<Import> imports = new ArrayList<>();
@ -321,11 +257,4 @@ public class SourceFile {
return -1;
}
public int getLineCount() {
return lines.size();
}
public CharSequence getLine(int lineNo) {
return lines.get(lineNo);
}
}

View file

@ -0,0 +1,97 @@
package com.jozufozu.flywheel.backend.source;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.google.common.collect.ImmutableList;
import com.jozufozu.flywheel.backend.source.span.CharPos;
import com.jozufozu.flywheel.util.StringUtil;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
public class SourceLines {
private static final Pattern newLine = Pattern.compile("(\\r\\n|\\r|\\n)");
/**
* 0-indexed line to char pos mapping.
*/
private final IntList lineStarts;
/**
* 0-indexed line lookup
*/
private final ImmutableList<String> lines;
public SourceLines(String source) {
this.lineStarts = createLineLookup(source);
this.lines = getLines(source, lineStarts);
}
/**
* Scan the source for line breaks, recording the position of the first character of each line.
* @param source
*/
private static IntList createLineLookup(String source) {
IntList l = new IntArrayList();
l.add(0); // first line is always at position 0
Matcher matcher = newLine.matcher(source);
while (matcher.find()) {
l.add(matcher.end());
}
return l;
}
private static ImmutableList<String> getLines(String source, IntList lines) {
ImmutableList.Builder<String> builder = ImmutableList.builder();
for (int i = 1; i < lines.size(); i++) {
int start = lines.getInt(i - 1);
int end = lines.getInt(i);
builder.add(StringUtil.trimEnd(source.substring(start, end)));
}
return builder.build();
}
public CharPos getCharPos(int charPos) {
int lineNo = 0;
for (; lineNo < lineStarts.size(); lineNo++) {
int ls = lineStarts.getInt(lineNo);
if (charPos < ls) {
break;
}
}
lineNo -= 1;
int lineStart = lineStarts.getInt(lineNo);
return new CharPos(charPos, lineNo, charPos - lineStart);
}
public int getLineCount() {
return lines.size();
}
public String getLine(int lineNo) {
return lines.get(lineNo);
}
public String printLinesWithNumbers() {
StringBuilder builder = new StringBuilder();
for (int i = 0, linesSize = lines.size(); i < linesSize; i++) {
builder.append(String.format("%1$4s: ", i + 1))
.append(lines.get(i))
.append('\n');
}
return builder.toString();
}
}

View file

@ -1,95 +1,79 @@
package com.jozufozu.flywheel.backend.source.error;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nullable;
import com.jozufozu.flywheel.backend.source.SourceFile;
import com.jozufozu.flywheel.backend.source.SourceLines;
import com.jozufozu.flywheel.backend.source.error.lines.ErrorLine;
import com.jozufozu.flywheel.backend.source.error.lines.FileLine;
import com.jozufozu.flywheel.backend.source.error.lines.HeaderLine;
import com.jozufozu.flywheel.backend.source.error.lines.SourceLine;
import com.jozufozu.flywheel.backend.source.error.lines.SpanHighlightLine;
import com.jozufozu.flywheel.backend.source.span.Span;
import com.jozufozu.flywheel.util.FlwUtil;
public class ErrorBuilder {
private final StringBuilder internal = new StringBuilder();
private final List<ErrorLine> lines = new ArrayList<>();
public ErrorBuilder error(CharSequence msg) {
internal.append("error: ")
.append(msg);
return endLine();
private final Level level;
public ErrorBuilder(Level level, CharSequence msg) {
this.level = level;
lines.add(new HeaderLine(level.toString(), msg));
}
public ErrorBuilder note(CharSequence msg) {
internal.append("note: ")
.append(msg);
return endLine();
public static ErrorBuilder error(CharSequence msg) {
return new ErrorBuilder(Level.ERROR, msg);
}
public ErrorBuilder hint(CharSequence msg) {
internal.append("hint: ")
.append(msg);
return endLine();
public static ErrorBuilder warn(CharSequence msg) {
return new ErrorBuilder(Level.WARN, msg);
}
public ErrorBuilder in(SourceFile file) {
internal.append("--> ")
.append(file.name);
return endLine();
}
public ErrorBuilder numberedLine(int no, CharSequence content) {
return line(String.valueOf(no), content);
}
public ErrorBuilder numberedLine(int no, int width, CharSequence content) {
String fmt = "%1$" + width + 's';
return line(String.format(fmt, no), content);
}
public ErrorBuilder line(CharSequence leftColumn, CharSequence rightColumn) {
internal.append(leftColumn)
.append(" | ")
.append(rightColumn);
return endLine();
}
public ErrorBuilder endLine() {
internal.append('\n');
public ErrorBuilder pointAtFile(SourceFile file) {
lines.add(new FileLine(file.name.toString()));
return this;
}
public ErrorBuilder hintIncludeFor(@Nullable Span span, CharSequence msg) {
if (span == null) return this;
hint("add " + span.getSourceFile().importStatement() + " " + msg)
.in(span.getSourceFile())
.pointAt(span, 1);
String builder = "add " + span.getSourceFile()
.importStatement() + ' ' + msg;
return this;
lines.add(new HeaderLine("hint", builder));
return this.pointAtFile(span.getSourceFile())
.pointAt(span, 1);
}
public ErrorBuilder pointAt(Span span, int ctxLines) {
SourceFile file = span.getSourceFile();
if (span.lines() == 1) {
SourceLines lines = span.getSourceFile().lines;
int spanLine = span.firstLine();
int firstLine = Math.max(0, spanLine - ctxLines);
int lastLine = Math.min(file.getLineCount(), spanLine + ctxLines);
int lastLine = Math.min(lines.getLineCount(), spanLine + ctxLines);
int digits = FlwUtil.numDigits(lastLine);
int firstCol = span.getStart().getCol();
int lastCol = span.getEnd().getCol();
int firstCol = span.getStart()
.col();
int lastCol = span.getEnd()
.col();
for (int i = firstLine; i <= lastLine; i++) {
CharSequence line = file.getLine(i);
CharSequence line = lines.getLine(i);
numberedLine(i + 1, digits, line);
this.lines.add(SourceLine.numbered(i + 1, line.toString()));
if (i == spanLine) {
line(FlwUtil.repeatChar(' ', digits), generateUnderline(firstCol, lastCol));
this.lines.add(new SpanHighlightLine(firstCol, lastCol));
}
}
}
@ -98,19 +82,23 @@ public class ErrorBuilder {
}
public CharSequence build() {
return internal;
}
public CharSequence generateUnderline(int firstCol, int lastCol) {
StringBuilder line = new StringBuilder(lastCol);
for (int i = 0; i < firstCol; i++) {
line.append(' ');
int maxLength = -1;
for (ErrorLine line : lines) {
int length = line.neededMargin();
if (length > maxLength) maxLength = length;
}
for (int i = firstCol; i < lastCol; i++) {
line.append('^');
StringBuilder builder = new StringBuilder();
for (ErrorLine line : lines) {
int length = line.neededMargin();
builder.append(FlwUtil.repeatChar(' ', maxLength - length))
.append(line.build())
.append('\n');
}
return line;
return builder;
}
}

View file

@ -13,10 +13,8 @@ public class ErrorReporter {
public static void generateSpanError(Span span, String message) {
SourceFile file = span.getSourceFile();
ErrorBuilder builder = new ErrorBuilder();
CharSequence error = builder.error(message)
.in(file)
CharSequence error = ErrorBuilder.error(message)
.pointAtFile(file)
.pointAt(span, 2)
.build();
@ -25,10 +23,8 @@ public class ErrorReporter {
public static void generateFileError(SourceFile file, String message) {
ErrorBuilder builder = new ErrorBuilder();
CharSequence error = builder.error(message)
.in(file)
CharSequence error = ErrorBuilder.error(message)
.pointAtFile(file)
.build();
Backend.log.error(error);
@ -43,10 +39,9 @@ public class ErrorReporter {
.stream()
.findFirst()
.map(ShaderStruct::getName);
ErrorBuilder builder = new ErrorBuilder();
ErrorBuilder error = builder.error(msg)
.in(file)
ErrorBuilder error = ErrorBuilder.error(msg)
.pointAtFile(file)
.pointAt(vertexName, 2)
.hintIncludeFor(span.orElse(null), hint);
@ -62,10 +57,9 @@ public class ErrorReporter {
.stream()
.findFirst()
.map(ShaderFunction::getName);
ErrorBuilder builder = new ErrorBuilder();
ErrorBuilder error = builder.error(msg)
.in(file)
ErrorBuilder error = ErrorBuilder.error(msg)
.pointAtFile(file)
.hintIncludeFor(span.orElse(null), hint);
Backend.log.error(error.build());

View file

@ -0,0 +1,18 @@
package com.jozufozu.flywheel.backend.source.error;
public enum Level {
WARN("warn"),
ERROR("error"),
;
private final String error;
Level(String error) {
this.error = error;
}
@Override
public String toString() {
return error;
}
}

View file

@ -0,0 +1,18 @@
package com.jozufozu.flywheel.backend.source.error.lines;
public enum Divider {
BAR(" | "),
ARROW("-->"),
;
private final String s;
Divider(String s) {
this.s = s;
}
@Override
public String toString() {
return s;
}
}

View file

@ -0,0 +1,19 @@
package com.jozufozu.flywheel.backend.source.error.lines;
public interface ErrorLine {
default int neededMargin() {
return left().length();
}
default Divider divider() {
return Divider.BAR;
}
default String build() {
return left() + divider() + right();
}
String left();
String right();
}

View file

@ -0,0 +1,19 @@
package com.jozufozu.flywheel.backend.source.error.lines;
public record FileLine(String fileName) implements ErrorLine {
@Override
public String left() {
return "--";
}
@Override
public Divider divider() {
return Divider.ARROW;
}
@Override
public String right() {
return fileName;
}
}

View file

@ -0,0 +1,26 @@
package com.jozufozu.flywheel.backend.source.error.lines;
import com.jozufozu.flywheel.backend.source.error.Level;
public record HeaderLine(String level, CharSequence message) implements ErrorLine {
@Override
public int neededMargin() {
return 0;
}
@Override
public String build() {
return level + ": " + message;
}
@Override
public String left() {
return null;
}
@Override
public String right() {
return null;
}
}

View file

@ -0,0 +1,18 @@
package com.jozufozu.flywheel.backend.source.error.lines;
public record SourceLine(String number, String line) implements ErrorLine {
public static SourceLine numbered(int number, String line) {
return new SourceLine(Integer.toString(number), line);
}
@Override
public String left() {
return number;
}
@Override
public String right() {
return line;
}
}

View file

@ -0,0 +1,23 @@
package com.jozufozu.flywheel.backend.source.error.lines;
public class SpanHighlightLine implements ErrorLine {
private final String line;
public SpanHighlightLine(int firstCol, int lastCol) {
line = generateUnderline(firstCol, lastCol);
}
@Override
public String left() {
return "";
}
@Override
public String right() {
return line;
}
public static String generateUnderline(int firstCol, int lastCol) {
return " ".repeat(Math.max(0, firstCol)) + "^".repeat(Math.max(0, lastCol - firstCol));
}
}

View file

@ -3,27 +3,5 @@ package com.jozufozu.flywheel.backend.source.span;
/**
* A position in a file.
*/
public class CharPos {
private final int idx;
private final int line;
private final int col;
public CharPos(int idx, int line, int col) {
this.idx = idx;
this.line = line;
this.col = col;
}
public int getPos() {
return idx;
}
public int getLine() {
return line;
}
public int getCol() {
return col;
}
public record CharPos(int pos, int line, int col) {
}

View file

@ -5,7 +5,11 @@ import java.util.regex.Matcher;
import com.jozufozu.flywheel.backend.source.SourceFile;
/**
* A span of code in a {@link SourceFile}.
* A segment of code in a {@link SourceFile}.
*
* <p>
* Spans are used for pretty-printing errors.
* </p>
*/
public abstract class Span implements CharSequence {
@ -14,7 +18,7 @@ public abstract class Span implements CharSequence {
protected final CharPos end;
public Span(SourceFile in, int start, int end) {
this(in, in.getCharPos(start), in.getCharPos(end));
this(in, in.lines.getCharPos(start), in.lines.getCharPos(end));
}
public Span(SourceFile in, CharPos start, CharPos end) {
@ -42,14 +46,14 @@ public abstract class Span implements CharSequence {
* @return the string index at the (inclusive) beginning of this code segment.
*/
public int getStartPos() {
return start.getPos();
return start.pos();
}
/**
* @return the string index at the (exclusive) end of this code segment.
*/
public int getEndPos() {
return end.getPos();
return end.pos();
}
/**
@ -63,11 +67,11 @@ public abstract class Span implements CharSequence {
* @return how many lines this span spans.
*/
public int lines() {
return end.getLine() - start.getLine() + 1;
return end.line() - start.line() + 1;
}
public int firstLine() {
return start.getLine();
return start.line();
}
/**
@ -89,7 +93,7 @@ public abstract class Span implements CharSequence {
@Override
public char charAt(int index) {
return in.getSource().charAt(start.getPos() + index);
return in.source.charAt(start.pos() + index);
}
@Override

View file

@ -14,13 +14,13 @@ public class StringSpan extends Span {
@Override
public Span subSpan(int from, int to) {
return new StringSpan(in, start.getPos() + from, start.getPos() + to);
return new StringSpan(in, start.pos() + from, start.pos() + to);
}
@Override
public String get() {
return in.getSource()
.substring(start.getPos(), end.getPos());
return in.source
.substring(start.pos(), end.pos());
}
@Override

View file

@ -9,8 +9,8 @@ import net.minecraft.resources.ResourceLocation;
public class SpecificValueCondition implements IGameStateCondition {
public static final Codec<SpecificValueCondition> CODEC = RecordCodecBuilder.create(condition -> condition.group(IGameStateProvider.CODEC.fieldOf("provider")
.forGetter(SpecificValueCondition::getStateProvider), Codec.STRING.fieldOf("value")
.forGetter(SpecificValueCondition::getValue))
.forGetter(SpecificValueCondition::getStateProvider), Codec.STRING.fieldOf("value")
.forGetter(SpecificValueCondition::getValue))
.apply(condition, SpecificValueCondition::new));
private final String required;

View file

@ -26,6 +26,8 @@ void FLWFinalizeWorldPos(inout vec4 worldPos) {
#elif defined(FRAGMENT_SHADER)
out vec4 fragColor;
vec4 FLWBlockTexture(vec2 texCoords) {
vec4 cr = texture2D(uCrumbling, texCoords * uTextureScale);
float diffuseAlpha = texture2D(uBlockAtlas, texCoords).a;
@ -42,7 +44,11 @@ void FLWFinalizeColor(vec4 color) {
color.a = a;
#endif
gl_FragColor = color;
if (color.a < 0.1) {
discard;
}
fragColor = color;
}
vec4 FLWLight(vec2 lightCoords) {

View file

@ -1,4 +1,8 @@
varying float FragDistance;
#if defined(VERTEX_SHADER)
out float FragDistance;
#elif defined(FRAGMENT_SHADER)
in float FragDistance;
#endif
uniform vec4 uFogColor;
uniform vec2 uFogRange;

View file

@ -34,6 +34,8 @@ void FLWFinalizeWorldPos(inout vec4 worldPos) {
//#endif
//#endif
out vec4 fragColor;
vec4 FLWBlockTexture(vec2 texCoords) {
return texture2D(uBlockAtlas, texCoords);
}
@ -53,7 +55,7 @@ void FLWFinalizeColor(vec4 color) {
}
#endif
gl_FragColor = color;
fragColor = color;
}
vec4 FLWLight(vec2 lightCoords) {