Zoeken

Plugins tutorial 4: code genereren

Deze tutorial gaat verder waar de tutorial rond TLCGen events gebleven was. Volg deze dus eerst, of download de code om direct te beginnen met deze tutorial, op basis van de code uit de vorige tutorial.

De resulterende code van deze tutorial is hier te downloaden.

Inhoud:

Stap 1: de plugin als generator

De generatie van C (CCOL) bron code gebeurt binnen TLCGen door een andere plugin: het proces van genereren van code staat dus volledig los van de verdere applicatie. Dit heeft als voordeel dat het eenvoudig mogelijk is een nieuwe plugin te schrijven, die op basis van dezelfde data (de TLCGen XML file) andere code genereert (zoals andere C code, maar ook XML of iets anders).

Als eerste moeten we een referentie toevoegen naar deze CCOL generator plugin. Voor het toevoegen van de referentie: rechtermuitklik op “References” in de Solution Explorer in Visual, vervolgens “Add reference”, en dan via “Browse” het bestand TLCGen.Generators.CCOL.dll zoeken en toevoegen. Zie hier bij stap 1 en stap 3. De dll wordt meegelevered met TLCGen in de map Plugins.

Binnen de CCOL code generator plugin is het genereren van code onderverdeeld in klassen, waarbij elke klasse de code voor een specifieke functionaliteit (zoals aanvragen, verlengen, alternatieven, etc.) genereert. Dit zorgt voor een modulaire opzet, die gemakkelijk is uit te breiden, en ook bij toename van de complexiteit overzichtelijk blijft. Onze plugin moet zich richting de CCOL code generator plugin voor doen als een dergelijke klasse, zodat de generator plugin onze plugin kan “bevragen”, ongeveer zoals de TLCGen dat doet voor plugins (zie hier). Dit doen we door onze plugin af te leiden van de klasse CCOLCodePieceGeneratorBase (namespace TLCGen.Generators.CCOL.CodeGeneration). We moeten de class ook decoreren met het CCOLCodePieceGenerator attribuut. De plugin class ziet er dan zo uit:

[TLCGenPlugin(TLCGenPluginElems.TabControl | TLCGenPluginElems.XMLNodeWriter | TLCGenPluginElems.PlugMessaging)]
[TLCGenTabItem(-1, TabItemTypeEnum.MainWindow)]
[CCOLCodePieceGenerator]
public class GroenBegrenzerPlugin : CCOLCodePieceGeneratorBase, ITLCGenTabItem, ITLCGenXMLNodeWriter, ITLCGenPlugMessaging
{
    ...

Na het laden van plugins door TLCGen, scant de CCOL generator plugin alle ingeladen plugins op classes met het CCOLCodePieceGenerator attribuut. Wanneer dat gevonden wordt, wordt de betreffende plugin toegevoegd in de interne lijst met ‘code piece generators’ van de CCOL generator plugin. Genereert de gebruiker nu een regeling, dat wordt onze plugin daarom bevraagd door de CCOL generator plugin: gevraagd wordt bijvoorbeeld om aan te geven waar we code willen plaatsen, welke code, of we CCOL elementen hebben, zo ja welke, etc.

De plugin zal nu compileren en functioneren, echter zijn alle functies uit de CCOLCodePieceGeneratorBase base class leeg, waardoor er nog niets gegenereerd zal worden.

Stap 2: code laten genereren

Om code te genereren moeten we twee functies uit de base class overriden. Onze plugin gaat het groen van richtingen waarvoor dat is ingesteld hard begrenzen. We kunnen de code daarvoor bijvoorbeeld plaatsen in post_application() in de reg.c. Tevens zullen we bovenaan de reg.c wat code plaatsen. Als eerste moeten we aangeven dat we code willen plaatsen op die plekken:

public override int HasCode(CCOLCodeTypeEnum type)
{
    switch (type)
    {
        case CCOLCodeTypeEnum.RegCTop:
            return 112;
        case CCOLCodeTypeEnum.RegCPostApplication:
            return 112;
        default:
            return 0;
    }
}

Een aantal opmerkingen over deze functie:

  • De enumeration CCOLCodeTypeEnum bevat een veelheid aan mogelijkheden waar code geplaatst kan worden. Gebruik Intellisense in Visual om de opties te zien, of kijk in de sources van TLCGen.
  • Het getal dat wordt teruggeven is arbitrair, maar wel belangrijk:
    • Dit moet uniek zijn, wat wil zeggen dat deze class de enige is die voor deze waarde van de enumeration dit getal gebruikt. De reden hiervoor is dat dit wordt gebruikt voor het bepalen van de volgorde waarin code geplaatst moet worden.
    • De TLCGen CCOL generator plugin gebruikt in principe alleen tientallen (10, 20, …). Het is nog niet voor gekomen dat er door TLCGen een hondertal is gebruikt. De suggestie is daarom: gebruik hogere getallen voor plugin code, tenzij deze per sé vóór bepaalde TLCGen code moet komen te staan: gebruik dan 11, 12… etc.

Een tweede functie geeft de code die op de betreffende plek geplaatst moet worden. Bijvoorbeeld:

public override string GetCode(ControllerModel c, CCOLCodeTypeEnum type, string ts)
{
    if (!_dataModel.SignaalGroepen.Any(x => x.BegrensGroen)) return "";

    var sb = new StringBuilder();
    var sgs = _dataModel.SignaalGroepen.Where(x => x.BegrensGroen);

    switch (type)
    {
        case CCOLCodeTypeEnum.RegCTop:
            sb.AppendLine($"{ts}/* T.b.c. begrenzen groen */");
            foreach (var sg in sgs)
            {
                sb.AppendLine($"{ts}int grgr{sg.SignaalGroepNaam} = 0;");
            }
            return sb.ToString();

        case CCOLCodeTypeEnum.RegCPostApplication:
            sb.AppendLine($"{ts}/* Begrens groen */");
            foreach (var sg in sgs)
            {
                sb.AppendLine($"{ts}if (SG[{_fcpf}{sg.SignaalGroepNaam}]) grgr{sg.SignaalGroepNaam} = 0;");
                sb.AppendLine($"{ts}if (G[{_fcpf}{sg.SignaalGroepNaam}]) grgr{sg.SignaalGroepNaam} += TE;");
                sb.AppendLine($"{ts}if (G[{_fcpf}{sg.SignaalGroepNaam}] && grgr{sg.SignaalGroepNaam} >= {sg.MaximaalGroen}) Z[{_fcpf}{sg.SignaalGroepNaam}] |= BIT13;");
                sb.AppendLine($"{ts}else Z[{_fcpf}{sg.SignaalGroepNaam}] &= ~BIT13;"); 
            }
            return sb.ToString();

        default:
            return null;
    }
}

Ook bij deze functie een aantal opmerkingen:

  • De functie krijgt een instance van het type ControllerModel mee: hierin zit alle data van de regeling; voor de huidige plugin hebben we dit niet nodig, maar het kan handig zijn wanneer genereren van code afhankelijk is van externe factoren, bijv. synchronisaties, etc.
  • De functie krijgt ook een string “ts”: dit is de “tabspace”, ofwel de te gebruiken tekst voor inspringen van code. Dit is instelbaar door de gebruiker in TLCGen, en moeten we daarom hier gebruiken.
  • Er zijn een aantal fields, zoals “_fcpf”, beschikbaar binnen de class. Dit is “fasecyclus prefix”, meestal “fc”. Dit is ook instelbaar, en moet om die reden zo worden gebruikt. De CCOL generator plugin zorgt ervoor dat deze velden de juiste waarde krijgen bij genereren. Er zijn ook velden voor detectors (_dpf), parameters (_prmpf), etc.

Stap 3: CCOL elementen aanmaken

De plugin plaats nu de door gebruiker ingestelde waarde voor groen begrenzing hard in de code. Dit is op straat mogelijk niet handig, wanneer we toch besluiten dat een hoofdrichting meer dan 6 seconden groen moet krijgen. Hiertoe kunnen we de betreffende waarde als parameter opnemen in de gegenereerde code. Omdat we nog niet helemaal zeker weten of we het groen altijd willen begrenzen, voegen we direct een globale schakelaar toe voor de functionaliteit. Dit gaat wederom middels 2 functies.

public override bool HasCCOLElements()
{
    return true;
}

public override void CollectCCOLElements(ControllerModel c)
{
    _myElements = new List<CCOLElement>();

    if (_dataModel.SignaalGroepen.Any(x => x.BegrensGroen))
    {
        _myElements.Add(new CCOLElement($"grgr", 1, CCOLElementTimeTypeEnum.SCH_type, CCOLElementTypeEnum.Schakelaar));
        foreach (var sg in _dataModel.SignaalGroepen.Where(x => x.BegrensGroen))
        {
            _myElements.Add(new CCOLElement($"grgr{sg.SignaalGroepNaam}", sg.MaximaalGroen, CCOLElementTimeTypeEnum.TE_type, CCOLElementTypeEnum.Parameter));
        }
    }
}

We moeten nu de generatie code ook aanpassen zodat deze elementen gebruikt worden. Dit wordt hier niet weergegeven.

Merk op: in versies van TLCGen <= 0.3.7.0 moet ook de volgende functie worden toegevoegd, omdat die in de base class leeg is:

public override IEnumerable<CCOLElement> GetCCOLElements(CCOLElementTypeEnum type)
{
    return _myElements.Where(x => x.Type == type);
}

Stap 4: verdere opties toevoegen

Hieronder volgt een overzicht van de functies uit de base class, die middels een override gebruikt kunnen worden:

public virtual bool HasCCOLElements();
public virtual void CollectCCOLElements(ControllerModel c);
public virtual IEnumerable<CCOLElement> GetCCOLElements();
public virtual IEnumerable<CCOLElement> GetCCOLElements(CCOLElementTypeEnum type);

public virtual bool HasCCOLBitmapInputs(); 
public virtual bool HasCCOLBitmapOutputs();
public virtual IEnumerable<CCOLIOElement> GetCCOLBitmapInputs();
public virtual IEnumerable<CCOLIOElement> GetCCOLBitmapOutputs();

public virtual bool HasDetectors();
public virtual IEnumerable<DetectorModel> GetDetectors();
public virtual bool HasSimulationElements();
public virtual IEnumerable<DetectorSimulatieModel> GetSimulationElements();

public virtual bool HasSettings(); 
public virtual bool SetSettings(CCOLGeneratorClassWithSettingsModel settings);

public virtual bool HasFunctionLocalVariables();
public virtual IEnumerable<Tuple<string, string, string>> GetFunctionLocalVariables(ControllerModel c, CCOLCodeTypeEnum type);

public virtual int HasCode(CCOLCodeTypeEnum type); 
public virtual string GetCode(ControllerModel c, CCOLCodeTypeEnum type, string ts);
public virtual bool HasCodeForController(ControllerModel c, CCOLCodeTypeEnum type); 

public virtual List<string> GetSourcesToCopy(); 

Hieronder een beknopte toelichting per functie:

  • bool HasCCOLElements() – gebruikt om aan te geven of de class CCOL elementen wil opgeven; default false
  • void CollectCCOLElements(ControllerModel c) – zie stap 3 van deze tutorial: bij genereren (vooraf aan het maken van de code) worden hier CCOL elementen verzameld
  • IEnumerable<CCOLElement> GetCCOLElements() – geeft _myElements terug; hoeft niets mee te gebeuren, wordt verzorgd door de base class
  • IEnumerable<CCOLElement> GetCCOLElements(CCOLElementTypeEnum type) – idem, vanaf TLCGen 0.3.8.0 (zie onderaan stap 3 in deze tutorial).
  • bool HasCCOLBitmapInputs() en bool HasCCOLBitmapOutputs() – gebruikt om aan te geven of de class IO elementen heeft, voor in de dpl.c; dit wordt door externe plugins niet gebruikt, omdat het dan verloopt via ITLCGenElementProvider. De reden is dat ITLCGenElementProvider het mogelijk maakt de gebruiker via de GUI de coordinaten in te laten stellen.
  • IEnumerable<CCOLIOElement> GetCCOLBitmapInputs() en IEnumerable<CCOLIOElement> GetCCOLBitmapOutputs() – idem.
  • bool HasDetectors() en IEnumerable<DetectorModel> GetDetectors() – gebruikt om vanuit de class detectors te laten genereren. Het is momenteel echter niet mogelijk detectors uit een plugin in de bitmap aan te klikken.
  • bool HasSimulationElements() en IEnumerable<DetectorSimulatieModel> – gebruikt om te zorgen dat door een plugin aangemaakte detectoren, ook in de sim.c opgenomen kunnen worden.
  • bool HasSettings() – uitsluitend bedoeld voor intern gebruik door de CCOL generator
  • bool SetSettings(CCOLGeneratorClassWithSettingsModel settings) – Kan worden gebruikt om namen van andere CCOL elementen, die gebruikt worden door de CCOL generator plugin, op te halen. Zie bijv. DetectieStoringGenerator.cs voor hoe dit kan worden gebruikt.
  • bool HasFunctionLocalVariables() – gebruikt om aan te geven dat de class lokale variabelen wil plaatsen, zoals bv “int fc”, aan het begin van een functie (zoals “post_application”)
  • IEnumerable<Tuple<string, string, string>> GetFunctionLocalVariables(ControllerModel c, CCOLCodeTypeEnum type) – hiermee kunnen lokale variabelen worden aangemaakt. Zie bijv. DetectieAanvragenMeetkriteriumCodeGenerator.cs van de CCOL generator plugin voor een voorbeeld van het gebruik hiervan. De Tuple<string, string, string> krijgt als waarden: Item1 = type variabele, Item2 = naam variabele, Item3 = initiele waarde variabele. Bijv: var t = new Tuple<string, string, string>("int", "question", "42");.
  • int HasCode(CCOLCodeTypeEnum type) – gebruikt om aan te geven dat de class ergens code wil plaatsen: zie stap 2 vam deze tutorial
  • string GetCode(ControllerModel c, CCOLCodeTypeEnum type, string ts) – gebruikt om feitelijke code te genereren: zie stap 2 vam deze tutorial
  • bool HasCodeForController(ControllerModel c, CCOLCodeTypeEnum type) – optioneel kan hiermee buiten de GetCode() functie om worden aangegeven om al dan niet code gegenereerd moet worden, afhankelijke van de data van de regeling (zie bv DetectieAanvragenMeetkriteriumCodeGenerator.cs van de CCOL generator plugin voor een voorbeeld van het gebruik hiervan).
  • List<string> GetSourcesToCopy()  – gebruikt om te zorgen dat bij een functionaliteit behorende source worden meegekopieerd tijdens genereren. Let op dat de sources in de map SourceFiles moet zitten. Zet sources op “Copy always” in Visual (in properties). Zie bv TLCGen.Plugins.AFM voor een voorbeeld voor het gebruik hiervan (deze plugin zit bij TLCGen in de sources).
Inhoudsopgave