improved logging facilities, improved game traversal efficiency

This commit is contained in:
Rudis Muiznieks 2019-09-21 18:52:00 -05:00
parent bace78ff2b
commit 44ed5f165b
13 changed files with 143 additions and 25 deletions

View File

@ -84,6 +84,9 @@
return 0; return 0;
} }
Logger.Initialize(debug);
var logger = Logger.GetLogger<Program>();
var lintMode = format == "lint"; var lintMode = format == "lint";
if(!lintMode) if(!lintMode)
@ -95,7 +98,7 @@
} }
if (!File.Exists(infile)) if (!File.Exists(infile))
{ {
Console.WriteLine(@"Source file {0} not found.", infile); logger.Error($"Source file {infile} not found.");
return 2; return 2;
} }
if (string.IsNullOrWhiteSpace(output)) if (string.IsNullOrWhiteSpace(output))
@ -108,28 +111,28 @@
if (!string.IsNullOrWhiteSpace(output) && (Directory.Exists(output) || File.Exists(output))) if (!string.IsNullOrWhiteSpace(output) && (Directory.Exists(output) || File.Exists(output)))
{ {
Console.WriteLine(@"Specified output {0} already exists.", output); logger.Error($"Specified output {output} already exists.");
return 2; return 2;
} }
if (!string.IsNullOrWhiteSpace(tempdir)) if (!string.IsNullOrWhiteSpace(tempdir))
{ {
if (!Directory.Exists(tempdir)) if (!Directory.Exists(tempdir))
{ {
Console.WriteLine(@"Template directory {0} does not exist.", tempdir); logger.Error($"Template directory {tempdir} does not exist.");
return 2; return 2;
} }
if (!File.Exists(Path.Combine(tempdir, "index.html")) || if (!File.Exists(Path.Combine(tempdir, "index.html")) ||
!File.Exists(Path.Combine(tempdir, "scene.html")) || !File.Exists(Path.Combine(tempdir, "scene.html")) ||
!File.Exists(Path.Combine(tempdir, "styles.css"))) !File.Exists(Path.Combine(tempdir, "styles.css")))
{ {
Console.WriteLine( logger.Error(
@"Template directory must contain ""index.html"", ""scene.html"", and ""style.css"" files."); @"Template directory must contain ""index.html"", ""scene.html"", and ""style.css"" files.");
} }
} }
if (!string.IsNullOrWhiteSpace(images) && !Directory.Exists(images)) if (!string.IsNullOrWhiteSpace(images) && !Directory.Exists(images))
{ {
Console.WriteLine(@"Images directory {0} does not exist.", images); logger.Error($"Images directory {images} does not exist.");
return 2; return 2;
} }
} }
@ -140,7 +143,7 @@
if(!lintMode) if(!lintMode)
{ {
storyText = File.ReadAllText(infile); storyText = File.ReadAllText(infile);
Console.WriteLine(@"Parsing story..."); logger.Log(@"Parsing story...");
} }
else else
{ {
@ -149,8 +152,8 @@
var story = parser.ParseStory(storyText); var story = parser.ParseStory(storyText);
parser.Warnings.Select(w => w.ToString()).Distinct().ToList().ForEach(s => Console.WriteLine(s)); parser.Warnings.Select(w => w.ToString()).Distinct().ToList().ForEach(s => logger.Raw(s));
story.Orphans.ToList().ForEach(o => Console.WriteLine("Warning L{0},1: \"{1}\": Unreachable {2}", o.LineNumber, o.Name, o.Type)); story.Orphans.ToList().ForEach(o => logger.Raw($"Warning L{o.LineNumber},1: \"{o.Name}\": Unreachable {o.Type}"));
if(!lintMode && parser.Warnings.Count() == 0) if(!lintMode && parser.Warnings.Count() == 0)
{ {
@ -164,7 +167,7 @@
case "epub": case "epub":
if (string.IsNullOrWhiteSpace(author)) if (string.IsNullOrWhiteSpace(author))
{ {
Console.WriteLine(@"Epub format requires the --author argument."); logger.Error(@"Epub format requires the --author argument.");
return 1; return 1;
} }
rend = new EpubRenderer(author, bookid, language); rend = new EpubRenderer(author, bookid, language);
@ -183,11 +186,11 @@
if (!string.IsNullOrWhiteSpace(images)) rend.ImageDir = images; if (!string.IsNullOrWhiteSpace(images)) rend.ImageDir = images;
Console.WriteLine(@"Rendering story..."); logger.Log(@"Rendering story...");
rend.Render(story, output, debug); rend.Render(story, output, debug);
Console.WriteLine(@"Done."); logger.Log(@"Done.");
} }
return 0; return 0;
} }

View File

@ -9,6 +9,7 @@
[Fact] [Fact]
public void CanParseValidStoryFile() public void CanParseValidStoryFile()
{ {
Logger.Initialize(true);
var parser = new FicdownParser(); var parser = new FicdownParser();
var storyText = File.ReadAllText(Path.Combine(Template.BaseDir, "TestStories", "CloakOfDarkness.md")); var storyText = File.ReadAllText(Path.Combine(Template.BaseDir, "TestStories", "CloakOfDarkness.md"));
var story = parser.ParseStory(storyText); var story = parser.ParseStory(storyText);

View File

@ -12,6 +12,7 @@ namespace Ficdown.Parser
public class FicdownParser public class FicdownParser
{ {
private static Logger _logger = Logger.GetLogger<FicdownParser>();
public List<FicdownException> Warnings { get; private set; } public List<FicdownException> Warnings { get; private set; }
private IBlockHandler _blockHandler; private IBlockHandler _blockHandler;
@ -64,8 +65,11 @@ namespace Ficdown.Parser
public ResolvedStory ParseStory(string storyText) public ResolvedStory ParseStory(string storyText)
{ {
var lines = storyText.Split(new[] {"\n", "\r\n"}, StringSplitOptions.None); var lines = storyText.Split(new[] {"\n", "\r\n"}, StringSplitOptions.None);
_logger.Debug($"Parsed {lines.Length} lines.");
var blocks = BlockHandler.ExtractBlocks(lines); var blocks = BlockHandler.ExtractBlocks(lines);
_logger.Debug($"Extracted {blocks.Count()} blocks.");
var story = BlockHandler.ParseBlocks(blocks); var story = BlockHandler.ParseBlocks(blocks);
_logger.Debug("Finished initial story breakdown.");
// dupe scene sanity check // dupe scene sanity check
foreach(var key in story.Scenes.Keys) foreach(var key in story.Scenes.Keys)

View File

@ -12,7 +12,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Epub4Net" Version="1.2.0" /> <PackageReference Include="Epub4Net" Version="1.2.0" />
<PackageReference Include="Ionic.Zip" Version="1.9.1.8" /> <PackageReference Include="Ionic.Zip" Version="1.9.1.8" />
<PackageReference Include="MarkdownSharp" Version="2.0.5" /> <PackageReference Include="Markdig" Version="0.17.1" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="4.5.1" /> <PackageReference Include="System.Text.Encoding.CodePages" Version="4.5.1" />
</ItemGroup> </ItemGroup>
</Project> </Project>

63
Ficdown.Parser/Logger.cs Normal file
View File

@ -0,0 +1,63 @@
namespace Ficdown.Parser
{
using System;
using System.Collections.Generic;
public class Logger
{
private static bool _initialized = false;
private static bool _debug = false;
private static Dictionary<Type, Logger> _cache;
public Type Type { get; private set; }
private Logger(Type type)
{
Type = type;
}
public static void Initialize(bool debug)
{
_debug = debug;
_cache = new Dictionary<Type, Logger>();
_initialized = true;
}
public static Logger GetLogger<T>()
{
var type = typeof(T);
lock(_cache)
{
if(!_cache.ContainsKey(type))
_cache.Add(type, new Logger(type));
}
return _cache[type];
}
private string Decorate(string message)
{
return $"{DateTime.Now.ToString("")} <{Type.Name}> {message}";
}
public void Raw(string message)
{
Console.WriteLine(message);
}
public void Log(string message)
{
Raw(Decorate(message));
}
public void Debug(string message)
{
if(!_debug) return;
Log($"DEBUG: {message}");
}
public void Error(string message, Exception ex = null)
{
Console.Error.WriteLine(Decorate($"ERROR: {message}"));
}
}
}

View File

@ -7,6 +7,8 @@
internal class PageState internal class PageState
{ {
public StateManager Manager { get; set; }
public Guid Id { get; set; } public Guid Id { get; set; }
public Scene Scene { get; set; } public Scene Scene { get; set; }
public State State { get; set; } public State State { get; set; }
@ -21,12 +23,12 @@
public string UniqueHash public string UniqueHash
{ {
get { return _uniqueHash ?? (_uniqueHash = StateManager.GetUniqueHash(State, Scene.Key)); } get { return _uniqueHash ?? (_uniqueHash = Manager.GetUniqueHash(State, Scene.Key)); }
} }
public string CompressedHash public string CompressedHash
{ {
get { return _compressedHash ?? (_compressedHash = StateManager.GetCompressedHash(this)); } get { return _compressedHash ?? (_compressedHash = Manager.GetCompressedHash(this)); }
} }
} }
} }

View File

@ -10,6 +10,7 @@
internal class StateResolver : IStateResolver internal class StateResolver : IStateResolver
{ {
private static Logger _logger = Logger.GetLogger<StateResolver>();
private static readonly Random _random = new Random((int) DateTime.Now.Ticks); private static readonly Random _random = new Random((int) DateTime.Now.Ticks);
private readonly IDictionary<string, string> _pageNames; private readonly IDictionary<string, string> _pageNames;
private readonly HashSet<string> _usedNames; private readonly HashSet<string> _usedNames;
@ -25,6 +26,7 @@
public ResolvedStory Resolve(IEnumerable<PageState> pages, Story story) public ResolvedStory Resolve(IEnumerable<PageState> pages, Story story)
{ {
_logger.Debug("Resolving story paths...");
_story = story; _story = story;
return new ResolvedStory return new ResolvedStory
{ {

View File

@ -9,6 +9,15 @@
{ {
private List<FicdownException> _warnings { get; set; } private List<FicdownException> _warnings { get; set; }
public static Utilities GetInstance()
{
return new Utilities
{
_warnings = new List<FicdownException>(),
_blockName = string.Empty
};
}
public static Utilities GetInstance(List<FicdownException> warnings, string blockName, int lineNumber) public static Utilities GetInstance(List<FicdownException> warnings, string blockName, int lineNumber)
{ {
return new Utilities return new Utilities

View File

@ -11,6 +11,7 @@
internal class GameTraverser : IGameTraverser internal class GameTraverser : IGameTraverser
{ {
private static Logger _logger = Logger.GetLogger<GameTraverser>();
private StateManager _manager; private StateManager _manager;
private Queue<StateQueueItem> _processingQueue; private Queue<StateQueueItem> _processingQueue;
private IDictionary<string, PageState> _processed; private IDictionary<string, PageState> _processed;
@ -59,6 +60,7 @@
_wasRun = true; _wasRun = true;
// generate comprehensive enumeration // generate comprehensive enumeration
_logger.Debug("Enumerating story scenes...");
var initial = _manager.InitialState; var initial = _manager.InitialState;
_processingQueue.Enqueue(new StateQueueItem _processingQueue.Enqueue(new StateQueueItem
@ -66,6 +68,7 @@
Page = initial, Page = initial,
AffectedStates = new List<State> {initial.AffectedState} AffectedStates = new List<State> {initial.AffectedState}
}); });
var interval = 0;
while (_processingQueue.Count > 0) while (_processingQueue.Count > 0)
{ {
var state = _processingQueue.Dequeue(); var state = _processingQueue.Dequeue();
@ -74,9 +77,13 @@
_processed.Add(state.Page.UniqueHash, state.Page); _processed.Add(state.Page.UniqueHash, state.Page);
ProcessState(state); ProcessState(state);
} }
if(++interval % 100 == 0 || _processingQueue.Count == 0)
_logger.Debug($"Processed {interval} scenes, {_processingQueue.Count} queued...");
} }
// make sure every page gets affected data on every page that it links to // make sure every page gets affected data on every page that it links to
_logger.Debug("Processing scene links...");
foreach (var pageTuple in _processed) foreach (var pageTuple in _processed)
{ {
foreach (var linkTuple in pageTuple.Value.Links) foreach (var linkTuple in pageTuple.Value.Links)
@ -95,6 +102,7 @@
} }
// compress redundancies // compress redundancies
_logger.Debug("Compressing redundant scenes...");
foreach (var row in _processed) foreach (var row in _processed)
{ {
if (!_compressed.ContainsKey(row.Value.CompressedHash)) if (!_compressed.ContainsKey(row.Value.CompressedHash))

View File

@ -11,10 +11,12 @@
internal class StateManager internal class StateManager
{ {
private static Logger _logger = Logger.GetLogger<StateManager>();
private readonly Story _story; private readonly Story _story;
private readonly Dictionary<string, int> _stateMatrix; private readonly Dictionary<string, int> _stateMatrix;
private readonly int _sceneCount; private readonly int _sceneCount;
private readonly int _actionCount; private readonly int _actionCount;
private BitArray _scenesSeenMask;
private List<FicdownException> _warnings { get; set; } private List<FicdownException> _warnings { get; set; }
@ -24,6 +26,18 @@
_story = story; _story = story;
var allScenes = _story.Scenes.SelectMany(s => s.Value); var allScenes = _story.Scenes.SelectMany(s => s.Value);
_sceneCount = allScenes.Max(s => s.Id); _sceneCount = allScenes.Max(s => s.Id);
_scenesSeenMask = new BitArray(_sceneCount);
// figure out which scenes can affect state
var masked = 0;
foreach(var scene in allScenes)
{
if(Utilities.GetInstance().ParseAnchors(scene.RawDescription).Any(a => a.Href.Toggles != null) || RegexLib.BlockQuotes.IsMatch(scene.RawDescription))
{
_scenesSeenMask[scene.Id - 1] = true;
masked++;
}
}
_actionCount = _story.Actions.Count > 0 ? _story.Actions.Max(a => a.Value.Id) : 0; _actionCount = _story.Actions.Count > 0 ? _story.Actions.Max(a => a.Value.Id) : 0;
_stateMatrix = new Dictionary<string, int>(); _stateMatrix = new Dictionary<string, int>();
var state = 0; var state = 0;
@ -40,6 +54,9 @@
{ {
_stateMatrix.Add(toggle, state++); _stateMatrix.Add(toggle, state++);
} }
_logger.Debug($"{_sceneCount} scenes ({masked} can change state).");
_logger.Debug($"{_actionCount} actions.");
_logger.Debug($"{_stateMatrix.Count()} states.");
} }
public PageState InitialState public PageState InitialState
@ -54,6 +71,8 @@
return new PageState return new PageState
{ {
Manager = this,
Id = Guid.Empty, Id = Guid.Empty,
Links = new Dictionary<string, string>(), Links = new Dictionary<string, string>(),
State = new State State = new State
@ -119,14 +138,14 @@
state.ScenesSeen[sceneId - 1] = true; state.ScenesSeen[sceneId - 1] = true;
} }
public static string GetUniqueHash(State state, string sceneKey) public string GetUniqueHash(State state, string sceneKey)
{ {
var combined = var combined =
new bool[ new bool[
state.PlayerState.Count + state.ScenesSeen.Count + state.ActionsToShow.Count + state.PlayerState.Count + state.ScenesSeen.Count + state.ActionsToShow.Count +
(state.ActionFirstToggles != null ? state.ActionFirstToggles.Count : 0)]; (state.ActionFirstToggles != null ? state.ActionFirstToggles.Count : 0)];
state.PlayerState.CopyTo(combined, 0); state.PlayerState.CopyTo(combined, 0);
state.ScenesSeen.CopyTo(combined, state.PlayerState.Count); state.ScenesSeen.And(_scenesSeenMask).CopyTo(combined, state.PlayerState.Count);
state.ActionsToShow.CopyTo(combined, state.PlayerState.Count + state.ScenesSeen.Count); state.ActionsToShow.CopyTo(combined, state.PlayerState.Count + state.ScenesSeen.Count);
if (state.ActionFirstToggles != null) if (state.ActionFirstToggles != null)
state.ActionFirstToggles.CopyTo(combined, state.ActionFirstToggles.CopyTo(combined,
@ -134,11 +153,15 @@
var ba = new BitArray(combined); var ba = new BitArray(combined);
var byteSize = (int)Math.Ceiling(combined.Length / 8.0); var byteSize = (int)Math.Ceiling(combined.Length / 8.0);
var encoded = new byte[byteSize]; var encoded = new byte[byteSize];
for(var i = 0; i < byteSize; i++)
{
encoded[i] = 0;
}
ba.CopyTo(encoded, 0); ba.CopyTo(encoded, 0);
return string.Format("{0}=={1}", sceneKey, Convert.ToBase64String(encoded)); return string.Format("{0}=={1}", sceneKey, Convert.ToBase64String(encoded));
} }
public static string GetCompressedHash(PageState page) public string GetCompressedHash(PageState page)
{ {
var compressed = new State var compressed = new State
{ {
@ -187,6 +210,8 @@
{ {
return new PageState return new PageState
{ {
Manager = this,
Id = Guid.NewGuid(), Id = Guid.NewGuid(),
Links = new Dictionary<string, string>(), Links = new Dictionary<string, string>(),
State = new State State = new State

View File

@ -89,6 +89,7 @@
public class EpubRenderer : HtmlRenderer public class EpubRenderer : HtmlRenderer
{ {
private static readonly Logger _logger = Logger.GetLogger<EpubRenderer>();
private readonly string _author; private readonly string _author;
private readonly string _bookId; private readonly string _bookId;
private readonly string _language; private readonly string _language;
@ -103,6 +104,7 @@
public override void Render(Model.Parser.ResolvedStory story, string outPath, bool debug = false) public override void Render(Model.Parser.ResolvedStory story, string outPath, bool debug = false)
{ {
_logger.Debug("Generating epub...");
var temppath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); var temppath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
Directory.CreateDirectory(temppath); Directory.CreateDirectory(temppath);
base.Render(story, temppath, debug); base.Render(story, temppath, debug);

View File

@ -3,16 +3,15 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using MarkdownSharp; using Markdig;
using Model.Parser; using Model.Parser;
using Parser; using Parser;
public class HtmlRenderer : IRenderer public class HtmlRenderer : IRenderer
{ {
private static Logger _logger = Logger.GetLogger<HtmlRenderer>();
private readonly string _language; private readonly string _language;
protected readonly Markdown Markdown;
public List<FicdownException> Warnings { private get; set; } public List<FicdownException> Warnings { private get; set; }
public string IndexTemplate { get; set; } public string IndexTemplate { get; set; }
@ -25,7 +24,6 @@
public HtmlRenderer(string language) public HtmlRenderer(string language)
{ {
_language = language; _language = language;
Markdown = new Markdown();
} }
public virtual void Render(ResolvedStory story, string outPath, bool debug = false) public virtual void Render(ResolvedStory story, string outPath, bool debug = false)
@ -42,11 +40,12 @@
protected void GenerateHtml(ResolvedStory story, string outPath, bool debug) protected void GenerateHtml(ResolvedStory story, string outPath, bool debug)
{ {
_logger.Debug("Generating HTML...");
var index = FillTemplate(IndexTemplate ?? Template.Index, new Dictionary<string, string> var index = FillTemplate(IndexTemplate ?? Template.Index, new Dictionary<string, string>
{ {
{"Language", _language}, {"Language", _language},
{"Title", story.Name}, {"Title", story.Name},
{"Description", Markdown.Transform(story.Description)}, {"Description", Markdown.ToHtml(story.Description)},
{"FirstScene", string.Format("{0}.html", story.FirstPage)} {"FirstScene", string.Format("{0}.html", story.FirstPage)}
}); });
@ -72,7 +71,7 @@
{ {
{"Language", _language}, {"Language", _language},
{"Title", story.Name}, {"Title", story.Name},
{"Content", Markdown.Transform(content)} {"Content", Markdown.ToHtml(content)}
}); });
File.WriteAllText(Path.Combine(outPath, string.Format("{0}.html", page.Name)), scene); File.WriteAllText(Path.Combine(outPath, string.Format("{0}.html", page.Name)), scene);

View File

@ -10,7 +10,7 @@ test:
dotnet test Ficdown.Parser.Tests dotnet test Ficdown.Parser.Tests
publish: clean publish: clean
rm /tmp/ficdown* rm -rf /tmp/ficdown*
dotnet publish --self-contained -c Release -r linux-x64 Ficdown.Console dotnet publish --self-contained -c Release -r linux-x64 Ficdown.Console
tar -C Ficdown.Console/bin/Release/netcoreapp2.1/linux-x64/publish -cvzf /tmp/ficdown-linux64.tar.gz . tar -C Ficdown.Console/bin/Release/netcoreapp2.1/linux-x64/publish -cvzf /tmp/ficdown-linux64.tar.gz .
dotnet publish --self-contained -c Release -r win-x64 Ficdown.Console dotnet publish --self-contained -c Release -r win-x64 Ficdown.Console